master
lion 2 weeks ago
parent d01d5f4cce
commit 6ece9ecfc3

@ -0,0 +1,56 @@
import request from "@/utils/request";
function customParamsSerializer(params) {
let result = '';
for (let key in params) {
if (params.hasOwnProperty(key)) {
if (Array.isArray(params[key])) {
params[key].forEach((item, index) => {
if (item.key) {
result += `${key}[${index}][key]=${item.key}&${key}[${index}][op]=${item.op}&${key}[${index}][value]=${item.value}&`;
} else {
result += `${key}[${index}]=${item}&`
}
});
} else {
result += `${key}=${params[key]}&`;
}
}
}
return result.slice(0, -1);
}
export function index(params, isLoading = false) {
return request({
method: "get",
url: "/api/admin/history-courses/index",
params,
paramsSerializer: customParamsSerializer,
isLoading
})
}
export function show(params, isLoading = true) {
return request({
method: "get",
url: "/api/admin/history-courses/show",
params,
isLoading
})
}
export function save(data) {
return request({
method: "post",
url: "/api/admin/history-courses/save",
data
})
}
export function destroy(params) {
return request({
method: "get",
url: "/api/admin/history-courses/destroy",
params
})
}

@ -1,5 +1,6 @@
<template>
<section class="app-main" :style="{'padding':key.includes('/dashboard')?'0!important':''}">
<!-- -->
<section class="app-main" :style="{'padding':key.includes('/home')?'0!important':''}">
<transition name="fade-transform" mode="out-in">
<router-view :key="key" />
</transition>

@ -3,7 +3,8 @@
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside" />
<sidebar class="sidebar-container" />
<div class="main-container">
<div :class="{'fixed-header':fixedHeader}" :style="{'display':key.includes('/dashboard')?'none':''}">
<!-- -->
<div :class="{'fixed-header':fixedHeader}" :style="{'display':key.includes('/home')?'none':''}">
<navbar />
</div>
<app-main />

@ -67,13 +67,14 @@ export const constantRoutes = [{
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: '系统首页',
component: () => import('@/views/dashboard/index'),
name: '课程发布管理',
component: () => import('@/views/course/index'),
meta: {
title: '系统首页',
title: '课程发布管理',
icon: 'dashboard'
}
}]
}],
hidden: true
},
{
path: '/course/txl',

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4625369 */
src: url('//at.alicdn.com/t/c/font_4625369_3liiqv0f3lb.woff2?t=1754754074864') format('woff2'),
url('//at.alicdn.com/t/c/font_4625369_3liiqv0f3lb.woff?t=1754754074864') format('woff'),
url('//at.alicdn.com/t/c/font_4625369_3liiqv0f3lb.ttf?t=1754754074864') format('truetype');
src: url('iconfont.woff2?t=1763968012909') format('woff2'),
url('iconfont.woff?t=1763968012909') format('woff'),
url('iconfont.ttf?t=1763968012909') format('truetype');
}
.iconfont {
@ -13,6 +13,26 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-shijianzhou:before {
content: "\e69a";
}
.icon-wenzhangguanli:before {
content: "\e634";
}
.icon-lishishuju:before {
content: "\e601";
}
.icon-shujutongji:before {
content: "\e67a";
}
.icon-shujuzonglan:before {
content: "\e62f";
}
.icon-tushu:before {
content: "\e615";
}
@ -152,3 +172,4 @@
.icon-tongzhigonggao:before {
content: "\e7ab";
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -182,6 +182,19 @@
</div>
</div>
</template>
<template v-slot:is_history>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;"></span>是否历史课程体系
</div>
<div class="xy-table-item-content">
<el-radio-group style="width:100%" v-model="form.is_history">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</div>
</div>
</template>
</xy-dialog>
@ -219,6 +232,7 @@
show_txl:'',
show_mobile:'',
auto_schoolmate:'',
is_history:0,
},
rules: {
name: [{
@ -263,6 +277,7 @@
this.form.show_txl = res.show_txl?res.show_txl:(res.show_txl==0?0:'')
this.form.show_mobile = res.show_mobile?res.show_mobile:(res.show_mobile==0?0:'')
this.form.auto_schoolmate = res.auto_schoolmate?res.auto_schoolmate:(res.auto_schoolmate==0?0:'')
this.form.is_history = res.is_history?res.is_history:0
})
},
@ -294,6 +309,7 @@
show_txl:'',
show_mobile:'',
auto_schoolmate:'',
is_history:0,
},
this.$refs['dialog'].reset()
}

@ -6,8 +6,27 @@
<lx-header icon="md-apps" :text="$route.meta.title" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<div slot="content">
<div class="searchwrap" style="display: flex;align-items: center;">
<el-button type="primary" size="small" @click="editTypes('add')"></el-button>
<div>
<el-select v-model="select.status" placeholder="请选择状态" clearable style="width: 100%;">
<el-option v-for="item in types_status" :key="item.id" :label="item.value" :value="item.id">
</el-option>
</el-select>
</div>
<div>
<el-select v-model="select.is_history" placeholder="请选择是否历史课程体系" clearable style="width: 100%;">
<el-option label="是" :value="1">
</el-option>
<el-option label="否" :value="0">
</el-option>
</el-select>
</div>
<div>
<el-button type="primary" size="small" @click="select.page=1,getList()"></el-button>
<el-button type="primary" size="small" @click="resetSelect"></el-button>
</div>
<div>
<el-button type="primary" size="small" @click="editTypes('add')"></el-button>
</div>
</div>
</div>
@ -27,14 +46,22 @@
</template>
</el-table-column>
</template>
<template v-slot:is_history>
<el-table-column align='center' label="是否历史课程体系" width="180" header-align="center">
<template slot-scope="scope">
<el-tag :type="scope.row.is_history === 1 ? 'success' : 'info'" v-if="scope.row.is_history === 1"></el-tag>
<el-tag :type="scope.row.is_history === 0 ? 'info' : 'success'" v-else></el-tag>
</template>
</el-table-column>
</template>
<template v-slot:btns>
<el-table-column align='left' label="操作" width="180" header-align="center">
<template slot-scope="scope">
<el-button type="primary" size="small"
@click="editTypes('editor',scope.row.id)">编辑</el-button>
<el-popconfirm style="margin:0 10px" @confirm="deleteList(scope.row.id)" title="确定删除吗?">
<el-button type="danger" size="small" slot="reference">删除</el-button>
@click="editTypes('editor',scope.row.id)">编辑</el-button>
<el-popconfirm style="margin:0 10px" @confirm="deleteList(scope.row.id)" title="确定删除吗?">
<el-button type="danger" size="small" slot="reference">删除</el-button>
</el-popconfirm>
<!-- <el-button type="primary" size="small" @click="goAttendance"></el-button>
<el-button type="primary" size="small" @click="setMain(scope.row.id)"></el-button> -->
@ -63,6 +90,8 @@
data() {
return {
select: {
status: '',
is_history: '',
page: 1,
page_size: 10,
},
@ -77,6 +106,14 @@
label: '状态',
align: 'center',
width: 120,
}, {
prop: 'is_history',
label: '是否历史课程体系',
align: 'center',
width: 180,
formatter: (row) => {
return row.is_history === 1 ? '是' : '否'
}
}]
}
@ -95,32 +132,54 @@
this.getList()
},
async getList() {
const filter = []
if (this.select.status !== '') {
filter.push({
key: 'status',
op: 'like',
value: this.select.status
})
}
if (this.select.is_history !== '') {
filter.push({
key: 'is_history',
op: 'like',
value: this.select.is_history
})
}
const res = await index({
page: this.select.page,
page_size: this.select.page_size
page_size: this.select.page_size,
filter: filter
})
this.list = res.data
this.total = res.total
},
resetSelect() {
this.select.status = ''
this.select.is_history = ''
this.select.page = 1
this.getList()
},
editTypes(type, id) {
this.$refs.addTypes.type = type
if (id) {
this.$refs.addTypes.id = id
}
this.$refs.addTypes.isShow = true
},
deleteList(id) {
var that = this;
destroy({
id: id,
}).then(response => {
this.$Message.success('删除成功');
this.getList()
}).catch(error => {
console.log(error)
reject(error)
})
},
deleteList(id) {
var that = this;
destroy({
id: id,
}).then(response => {
this.$Message.success('删除成功');
this.getList()
}).catch(error => {
console.log(error)
reject(error)
})
},
}
}
@ -141,4 +200,4 @@
}
}
}
</style>
</style>

@ -319,16 +319,16 @@ export default {
nationalMapInstance: null,
nationalMarkers: [],
mapData: [
{ name: '张家港市', value: 12, imageKey: 'zjg' },
{ name: '常熟市', value: 10, imageKey: 'cs' },
{ name: '太仓市', value: 16, imageKey: 'tc' },
{ name: '相城区', value: 82, imageKey: 'xc' },
{ name: '高新区', value: 35, imageKey: 'gx' },
{ name: '姑苏区', value: 26, imageKey: 'gs' },
{ name: '工业园区', value: 96, imageKey: 'gyy' },
{ name: '昆山市', value: 42, imageKey: 'ks' },
{ name: '吴中区', value: 66, imageKey: 'wz' },
{ name: '吴江区', value: 52, imageKey: 'wj' }
{ name: '张家港市', value: 0, imageKey: 'zjg' },
{ name: '常熟市', value: 0, imageKey: 'cs' },
{ name: '太仓市', value: 0, imageKey: 'tc' },
{ name: '相城区', value: 0, imageKey: 'xc' },
{ name: '高新区', value: 0, imageKey: 'gx' },
{ name: '姑苏区', value: 0, imageKey: 'gs' },
{ name: '工业园区', value: 0, imageKey: 'gyy' },
{ name: '昆山市', value: 0, imageKey: 'ks' },
{ name: '吴中区', value: 0, imageKey: 'wz' },
{ name: '吴江区', value: 0, imageKey: 'wj' }
],
//
// : 534 * 444

@ -0,0 +1,274 @@
<template>
<div>
<xy-dialog ref="dialog" :width="70" :is-show.sync="isShow" :type="'form'"
:title="type === 'add' ? '新增历史课程体系' : '编辑历史课程体系'" :form="form" :rules="rules" @submit="submit">
<template v-slot:type>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>课程体系
</div>
<div class="xy-table-item-content">
<el-select v-model="form.type" placeholder="请选择课程体系" clearable style="width: 100%;">
<el-option v-for="item in courseTypeOptions" :key="item.id" :label="item.name" :value="String(item.id)">
</el-option>
</el-select>
</div>
</div>
</template>
<template v-slot:course_name>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>课程名称
</div>
<div class="xy-table-item-content">
<el-input v-model="form.course_name" placeholder="请输入课程名称" clearable style="width: 100%;" />
</div>
</div>
</template>
<template v-slot:start_time>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>开始日期
</div>
<div class="xy-table-item-content">
<el-date-picker
v-model="form.start_time"
type="date"
placeholder="请选择开始日期"
style="width: 100%;"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
</div>
</template>
<template v-slot:end_time>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>结束日期
</div>
<div class="xy-table-item-content">
<el-date-picker
v-model="form.end_time"
type="date"
placeholder="请选择结束日期"
style="width: 100%;"
value-format="yyyy-MM-dd">
</el-date-picker>
</div>
</div>
</template>
<template v-slot:course_type_signs_pass>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>培养人数未去重
</div>
<div class="xy-table-item-content">
<el-input-number v-model="form.course_type_signs_pass" :min="0" placeholder="请输入培养人数(未去重)" style="width: 100%;" />
</div>
</div>
</template>
<template v-slot:course_type_signs_pass_unique>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>培养人数去重
</div>
<div class="xy-table-item-content">
<el-input-number v-model="form.course_type_signs_pass_unique" :min="0" placeholder="请输入培养人数(去重)" style="width: 100%;" />
</div>
</div>
</template>
<template v-slot:course_signs_pass>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
<span style="color: red;font-weight: bold;padding-right: 4px;">*</span>课程培养人数
</div>
<div class="xy-table-item-content">
<el-input-number v-model="form.course_signs_pass" :min="0" placeholder="请输入课程培养人数" style="width: 100%;" />
</div>
</div>
</template>
</xy-dialog>
</div>
</template>
<script>
import { save, show } from '@/api/historyCourse/index.js'
import { index as courseTypeIndex } from '@/api/course/courseType.js'
export default {
components: {
},
data() {
return {
isShow: false,
type: 'add',
id: '',
courseTypeOptions: [],
form: {
type: '',
course_name: '',
start_time: '',
end_time: '',
course_type_signs_pass: 0,
course_type_signs_pass_unique: 0,
course_signs_pass: 0
},
rules: {
type: [{
required: true,
message: '请选择课程体系'
}],
course_name: [{
required: true,
message: '请输入课程名称'
}],
start_time: [{
required: true,
message: '请选择开始日期'
}],
end_time: [{
required: true,
message: '请选择结束日期'
}],
course_type_signs_pass: [{
required: true,
message: '请输入培养人数(未去重)'
}],
course_type_signs_pass_unique: [{
required: true,
message: '请输入培养人数(去重)'
}],
course_signs_pass: [{
required: true,
message: '请输入课程培养人数'
}]
}
}
},
created() {
this.getCourseTypeList()
},
methods: {
async getCourseTypeList() {
try {
const res = await courseTypeIndex({
page: 1,
page_size: 999,
filter: [{
key: 'is_history',
op: 'eq',
value: 1
}]
})
if (res && res.data) {
this.courseTypeOptions = res.data
if (this.courseTypeOptions.length === 0) {
this.$message.warning('请先创建历史课程体系')
}
}
} catch (error) {
console.error('获取课程体系列表失败:', error)
this.$message.error('获取课程体系列表失败')
}
},
submit() {
if (this.courseTypeOptions.length === 0) {
this.$message.warning('请先创建历史课程体系')
return
}
if (this.id) {
this.form.id = this.id
}
if (this.type === 'add') {
this.form.id = ''
}
// type
if (this.form.type !== null && this.form.type !== undefined) {
this.form.type = String(this.form.type)
}
//
if (this.form.course_type_signs_pass === null || this.form.course_type_signs_pass === undefined) {
this.form.course_type_signs_pass = 0
}
if (this.form.course_type_signs_pass_unique === null || this.form.course_type_signs_pass_unique === undefined) {
this.form.course_type_signs_pass_unique = 0
}
if (this.form.course_signs_pass === null || this.form.course_signs_pass === undefined) {
this.form.course_signs_pass = 0
}
save(this.form).then(res => {
this.$message({
type: 'success',
message: this.type === 'add' ? '新增成功' : '编辑成功'
})
this.isShow = false
this.$emit('refresh')
}).catch(error => {
console.log(error)
this.$message.error(this.type === 'add' ? '新增失败' : '编辑失败')
})
},
getDetail() {
show({
id: this.id
}).then(res => {
// type
const typeValue = res.type !== null && res.type !== undefined ? String(res.type) : ''
this.form = {
type: typeValue,
course_name: res.course_name || '',
start_time: res.start_time || '',
end_time: res.end_time || '',
course_type_signs_pass: res.course_type_signs_pass !== undefined ? res.course_type_signs_pass : 0,
course_type_signs_pass_unique: res.course_type_signs_pass_unique !== undefined ? res.course_type_signs_pass_unique : 0,
course_signs_pass: res.course_signs_pass !== undefined ? res.course_signs_pass : 0
}
}).catch(error => {
console.log(error)
this.$message.error('获取详情失败')
})
}
},
watch: {
isShow(newVal) {
if (newVal) {
//
this.getCourseTypeList()
if (this.type === 'editor') {
this.getDetail()
} else {
this.form = {
type: '',
course_name: '',
start_time: '',
end_time: '',
course_type_signs_pass: 0,
course_type_signs_pass_unique: 0,
course_signs_pass: 0
}
}
} else {
this.id = ''
this.type = 'add'
this.form = {
type: '',
course_name: '',
start_time: '',
end_time: '',
course_type_signs_pass: 0,
course_type_signs_pass_unique: 0,
course_signs_pass: 0
}
this.$refs['dialog'].reset()
}
}
}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,201 @@
<template>
<div>
<div>
<div ref="lxHeader">
<lx-header icon="md-apps" :text="$route.meta.title" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<div slot="content">
<div class="searchwrap" style="display: flex;align-items: center;">
<div>
<el-input v-model="select.course_name" placeholder="请输入课程名称" clearable />
</div>
<div>
<el-button type="primary" size="small" @click="select.page=1,getList()"></el-button>
</div>
<div>
<el-button type="primary" size="small" @click="editHistoryCourse('add')"></el-button>
</div>
</div>
</div>
</lx-header>
</div>
</div>
<div>
<xy-table :list="list" :total="total" :table-item="table_item" @pageIndexChange="pageIndexChange" @pageSizeChange="pageSizeChange">
<template v-slot:type>
<el-table-column align="center" label="课程体系" width="200" header-align="center">
<template slot-scope="scope">
{{ getCourseTypeName(scope.row.type) }}
</template>
</el-table-column>
</template>
<template v-slot:btns>
<el-table-column align="center" label="操作" width="180" header-align="center">
<template slot-scope="scope">
<el-button type="primary" size="small" @click="editHistoryCourse('editor',scope.row.id)"></el-button>
<el-popconfirm title="确定删除吗?" style="margin:0 10px" @confirm="deleteList(scope.row.id)">
<el-button slot="reference" type="danger" size="small">删除</el-button>
</el-popconfirm>
</template>
</el-table-column>
</template>
</xy-table>
</div>
<add-history-course ref="addHistoryCourse" @refresh="getList" />
</div>
</template>
<script>
import addHistoryCourse from './components/addHistoryCourse.vue'
import { index, destroy } from '@/api/historyCourse/index.js'
import { index as courseTypeIndex } from '@/api/course/courseType.js'
export default {
components: {
addHistoryCourse
},
data() {
return {
select: {
course_name: '',
page: 1,
page_size: 10
},
list: [],
total: 0,
courseTypeOptions: [],
table_item: [{
prop: 'type',
label: '课程体系',
align: 'center',
width: 200
}, {
prop: 'course_name',
label: '课程名称',
align: 'left',
minWidth: 200
}, {
prop: 'start_time',
label: '开始日期',
align: 'center',
width: 150
}, {
prop: 'end_time',
label: '结束日期',
align: 'center',
width: 150
}, {
prop: 'course_type_signs_pass',
label: '培养人数(未去重)',
align: 'center',
width: 150
}, {
prop: 'course_type_signs_pass_unique',
label: '培养人数(去重)',
align: 'center',
width: 150
}, {
prop: 'course_signs_pass',
label: '课程培养人数',
align: 'center',
width: 150
}]
}
},
created() {
this.getCourseTypeList()
this.getList()
},
methods: {
async getCourseTypeList() {
try {
const res = await courseTypeIndex({
page: 1,
page_size: 999,
filter: [{
key: 'is_history',
op: 'eq',
value: 1
}]
})
if (res && res.data) {
this.courseTypeOptions = res.data
}
} catch (error) {
console.error('获取课程体系列表失败:', error)
}
},
getCourseTypeName(type) {
if (!type) return ''
// type id
const courseType = this.courseTypeOptions.find(item => {
//
return String(item.id) === String(type) || item.id === type
})
return courseType ? courseType.name : type
},
pageIndexChange(e) {
this.select.page = e
this.getList()
},
pageSizeChange(e) {
this.select.page_size = e
this.select.page = 1
this.getList()
},
editHistoryCourse(type, id) {
if (type === 'editor') {
this.$refs.addHistoryCourse.id = id
}
this.$refs.addHistoryCourse.type = type
this.$refs.addHistoryCourse.isShow = true
},
async getList() {
const params = {
page_size: this.select.page_size,
page: this.select.page
}
if (this.select.course_name) {
params.filter = [{
key: 'course_name',
op: 'like',
value: this.select.course_name
}]
}
const res = await index(params, false)
this.list = res.data
this.total = res.total
},
deleteList(id) {
destroy({
id: id
}).then(response => {
this.$message.success('删除成功')
this.getList()
}).catch(error => {
console.log(error)
this.$message.error('删除失败')
})
}
}
}
</script>
<style lang="scss" scoped>
.searchwrap {
display: flex;
align-items: center;
&>div {
display: flex;
align-items: center;
margin-right: 10px;
span {
min-width: 70px;
}
}
}
</style>

@ -67,14 +67,56 @@
</div>
<!-- 学员统计卡片 -->
<div class="stats-container">
<div class="stats-card" :class="stat.cardClass" v-for="(stat, index) in studentStats" :key="index">
<i class="el-icon-download stats-download" @click.stop="exportStat(stat)"></i>
<div class="stats-icon">
<i :class="stat.icon"></i>
<div class="stats-categories-container">
<!-- 全宽分类 -->
<div
class="stats-category stats-category-full"
v-for="(category, categoryIndex) in fullCategories"
:key="'full-' + categoryIndex">
<h4 class="category-title">{{ category.title }}</h4>
<div
class="stats-container"
:class="{
'stats-container-full': category.layout !== 'half',
'stats-container-half': category.layout === 'half'
}">
<div class="stats-card" :class="stat.cardClass" v-for="(stat, index) in category.stats" :key="stat.key || index">
<i class="el-icon-download stats-download" @click.stop="exportStat(stat)"></i>
<div class="stats-icon">
<i :class="stat.icon"></i>
</div>
<h3>{{ stat.value }}</h3>
<p>{{ stat.label }}</p>
</div>
</div>
</div>
<!-- 半宽分类每行两个 -->
<div
class="stats-half-row"
v-for="(row, rowIndex) in halfCategoryRows"
:key="'half-row-' + rowIndex">
<div
class="stats-category stats-category-half"
v-for="(category, categoryIndex) in row"
:key="'half-' + rowIndex + '-' + categoryIndex">
<h4 class="category-title">{{ category.title }}</h4>
<div
class="stats-container"
:class="{
'stats-container-full': category.layout !== 'half',
'stats-container-half': category.layout === 'half'
}">
<div class="stats-card" :class="stat.cardClass" v-for="(stat, index) in category.stats" :key="stat.key || index">
<i class="el-icon-download stats-download" @click.stop="exportStat(stat)"></i>
<div class="stats-icon">
<i :class="stat.icon"></i>
</div>
<h3>{{ stat.value }}</h3>
<p>{{ stat.label }}</p>
</div>
</div>
</div>
<h3>{{ stat.value }}</h3>
<p>{{ stat.label }}</p>
</div>
</div>
@ -98,19 +140,19 @@
</div>
</div>
<div class="detail-table">
<el-table :data="courseDetailData" :span-method="objectSpanMethod" :header-cell-style="headerCellStyle">
<el-table
:data="courseDetailData"
:span-method="objectSpanMethod"
:header-cell-style="headerCellStyle"
show-summary
:summary-method="getCourseDetailSummary">
<el-table-column prop="courseSystem" label="课程体系" width="200" align="center"></el-table-column>
<el-table-column prop="totalPeople" label="培养人数(未去重)" width="200" align="center"></el-table-column>
<el-table-column prop="uniquePeople" label="培养人数(课程体系内已去重)" width="280" align="center"></el-table-column>
<el-table-column prop="courseIndex" label="期数" width="120" align="center"></el-table-column>
<el-table-column prop="courseName" label="开课" min-width="200"></el-table-column>
<el-table-column prop="coursePeople" label="课程培养人数" width="150" align="center"></el-table-column>
</el-table>
<div class="table-summary">
<span>累计</span>
<span>培养人数未去重{{ courseDetailSummary.totalPeople }}</span>
<span>培养人数课程体系内已去重{{ courseDetailSummary.uniquePeople }}</span>
<span>课程培养人数{{ courseDetailSummary.coursePeople }}</span>
</div>
</div>
</el-col>
</el-row>
@ -132,16 +174,16 @@
</div>
</div>
<div class="detail-table">
<el-table :data="regionData" style="width: 100%" :header-cell-style="headerCellStyle">
<el-table
:data="regionData"
style="width: 100%"
:header-cell-style="headerCellStyle"
show-summary
:summary-method="getRegionSummary">
<el-table-column prop="region" label="区域" width="200" align="center"></el-table-column>
<el-table-column prop="totalPeople" label="培养人数(未去重)" align="center"></el-table-column>
<el-table-column prop="uniquePeople" label="培养人数(已去重)" align="center"></el-table-column>
</el-table>
<div class="table-summary">
<span>累计</span>
<span>培养人数未去重{{ regionSummary.totalPeople }}</span>
<span>培养人数已去重{{ regionSummary.uniquePeople }}</span>
</div>
</div>
</el-col>
</el-row>
@ -160,6 +202,19 @@ export default {
name: 'Statistics',
mixins: [formMixin],
components: {},
computed: {
fullCategories() {
return this.statsCategories.filter(category => category.layout !== 'half')
},
halfCategoryRows() {
const halves = this.statsCategories.filter(category => category.layout === 'half')
const rows = []
for (let i = 0; i < halves.length; i += 2) {
rows.push(halves.slice(i, i + 2))
}
return rows
}
},
data() {
return {
chartLoading: false,
@ -172,69 +227,134 @@ export default {
},
courseOptions: [],
courseTypeList: [], //
studentStats: [
{
key: 'course_signs_invested',
icon: 'el-icon-user-solid',
value: '0',
label: '被投企业数',
cardClass: 'student-card-1'
},
{
key: 'course_signs_pass',
icon: 'el-icon-s-check',
value: '0',
label: '培养人数(未去重)',
cardClass: 'student-card-2'
},
statsCategories: [
{
key: 'course_signs_pass_unique',
icon: 'el-icon-s-custom',
value: '0',
label: '培养人数(已去重)',
cardClass: 'student-card-3'
},
{
key: 'course_total',
icon: 'el-icon-date',
value: '0',
label: '开课场次',
cardClass: 'student-card-4'
},
{
key: 'course_day_total',
icon: 'el-icon-c-scale-to-original',
value: '0',
label: '开课天数',
cardClass: 'student-card-5'
layout: 'full',
title: '培养数据',
stats: [
{
key: 'course_signs_pass',
icon: 'el-icon-s-check',
value: '0',
label: '培养人次(未去重)',
cardClass: 'student-card-1'
},
{
key: 'course_signs_pass_unique',
icon: 'el-icon-s-custom',
value: '0',
label: '培养人数(已去重)',
cardClass: 'student-card-2'
},
{
key: 'ganbu_total',
icon: 'el-icon-s-opportunity',
value: '0',
label: '跟班学员数',
cardClass: 'student-card-3'
},
{
key: 'company_ganbu_total',
icon: 'el-icon-s-check',
value: '0',
label: '全市干部参与数',
cardClass: 'student-card-4'
},
{
key: 'company_join_total',
icon: 'el-icon-s-promotion',
value: '0',
label: '元禾员工参与数',
cardClass: 'student-card-5'
}
]
},
{
key: 'company_market_total',
icon: 'el-icon-office-building',
value: '0',
label: '上市公司数',
cardClass: 'student-card-1'
layout: 'half',
title: '开课数据',
stats: [
{
key: 'course_total',
icon: 'el-icon-date',
value: '0',
label: '开课场次',
cardClass: 'student-card-1'
},
{
key: 'course_day_total',
icon: 'el-icon-c-scale-to-original',
value: '0',
label: '开课天数',
cardClass: 'student-card-2'
}
]
},
{
key: 'company_market_year_total',
icon: 'el-icon-s-finance',
value: '0',
label: '今年上市公司数',
cardClass: 'student-card-2'
layout: 'half',
title: '上市数据',
stats: [
{
key: 'company_market_total',
icon: 'el-icon-office-building',
value: '0',
label: '上市公司数',
cardClass: 'student-card-1'
},
{
key: 'company_market_after_enrollment_total',
icon: 'el-icon-s-marketing',
value: '0',
label: '入学后上市公司数',
cardClass: 'student-card-2'
},
{
key: 'company_market_year_total',
icon: 'el-icon-s-finance',
value: '0',
label: '今年上市公司数',
cardClass: 'student-card-3'
}
]
},
{
key: 'company_market_after_enrollment_total',
icon: 'el-icon-s-marketing',
value: '0',
label: '入学后上市公司数',
cardClass: 'student-card-3'
layout: 'half',
title: '被投数据',
stats: [
{
key: 'course_signs_invested',
icon: 'el-icon-user-solid',
value: '0',
label: '被投企业数',
cardClass: 'student-card-1'
}
]
},
{
key: 'ganbu_total',
icon: 'el-icon-s-opportunity',
value: '0',
label: '跟班学员数',
cardClass: 'student-card-4'
layout: 'half',
title: '三个全覆盖数据',
stats: [
{
key: 'cover_head_total',
icon: 'el-icon-s-shop',
value: '0',
label: '苏州头部科技企业数',
cardClass: 'student-card-1'
},
{
key: 'cover_rencai_total',
icon: 'el-icon-s-custom',
value: '0',
label: '苏州高层次科技人才数',
cardClass: 'student-card-2'
},
{
key: 'cover_stock_total',
icon: 'el-icon-s-opportunity',
value: '0',
label: '苏州重点上市公司数',
cardClass: 'student-card-3'
}
]
}
],
courseDetailData: [],
@ -685,12 +805,14 @@ export default {
// API
if (data && data.list) {
const statsData = data.list
this.studentStats.forEach(stat => {
if (stat.key && statsData.hasOwnProperty(stat.key)) {
stat.value = statsData[stat.key] ?? '0'
} else {
stat.value = '0'
}
this.statsCategories.forEach(category => {
category.stats.forEach(stat => {
if (stat.key && Object.prototype.hasOwnProperty.call(statsData, stat.key)) {
stat.value = statsData[stat.key] ?? '0'
} else {
stat.value = '0'
}
})
})
}
@ -728,6 +850,7 @@ export default {
courseSystem: group.courseSystem,
totalPeople: group.totalPeople,
uniquePeople: group.uniquePeople,
courseIndex: index + 1,
courseName: course.courseName,
coursePeople: course.coursePeople,
isFirstRow: index === 0 //
@ -762,7 +885,7 @@ export default {
this.regionSummary = { totalPeople: 0, uniquePeople: 0 }
}
console.log('统计数据已更新:', this.studentStats)
console.log('统计数据已更新:', this.statsCategories)
console.log('课程分类明细数据已更新:', this.courseDetailData)
console.log('区域明细数据已更新:', this.regionData)
},
@ -840,6 +963,55 @@ export default {
fontSize: '0.99rem',
textAlign: 'center'
};
},
//
getCourseDetailSummary({ columns, data }) {
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
// ""
sums[index] = '累计'
} else if (index === 1) {
//
sums[index] = this.courseDetailSummary.totalPeople
} else if (index === 2) {
//
sums[index] = this.courseDetailSummary.uniquePeople
} else if (index === 3) {
//
sums[index] = data.length
} else if (index === 4) {
//
sums[index] = ''
} else if (index === 5) {
//
sums[index] = this.courseDetailSummary.coursePeople
} else {
sums[index] = ''
}
})
return sums
},
//
getRegionSummary({ columns, data }) {
const sums = []
columns.forEach((column, index) => {
if (index === 0) {
// ""
sums[index] = '累计'
} else if (index === 1) {
//
sums[index] = this.regionSummary.totalPeople
} else if (index === 2) {
//
sums[index] = this.regionSummary.uniquePeople
} else {
sums[index] = ''
}
})
return sums
}
}
}
@ -947,31 +1119,69 @@ export default {
}
}
.stats-container {
.stats-categories-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
flex-direction: column;
gap: 20px;
margin-bottom: 30px;
}
.stats-category-full {
width: 100%;
}
.stats-half-row {
display: flex;
gap: 20px;
width: 100%;
}
.stats-category-half {
flex: 1;
}
.category-title {
color: #0f4c75;
font-weight: bold;
font-size: 1.2rem;
margin-bottom: 15px;
padding-left: 10px;
border-left: 4px solid #00a8ff;
}
.stats-container {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 15px;
padding: 0 10px;
}
.stats-container-full {
width: 100%;
justify-content: space-between;
}
.stats-container-half {
width: 100%;
}
.stats-card {
border-radius: 20px;
padding: 28px 20px;
border-radius: 15px;
padding: 20px 15px;
margin-bottom: 0;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
transition: all 0.4s ease;
position: relative;
overflow: hidden;
text-align: center;
height: 200px;
height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
flex: 0 0 calc(20% - 16px);
min-width: calc(20% - 16px);
flex: 1 1 calc(20% - 15px);
min-width: 180px;
&::before {
content: '';
@ -989,28 +1199,28 @@ export default {
}
h3 {
font-size: 2.24rem;
font-size: 1.6rem;
font-weight: bold;
margin-bottom: 15px;
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
p {
font-size: 1.08rem;
font-size: 0.85rem;
opacity: 0.95;
margin: 0;
font-weight: 500;
flex-shrink: 0;
margin-bottom:15px;
line-height: 1.3;
}
}
.stats-download {
position: absolute;
top: 12px;
right: 12px;
font-size: 1.1rem;
top: 8px;
right: 8px;
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.8);
cursor: pointer;
transition: color 0.2s ease;
@ -1022,9 +1232,9 @@ export default {
}
.stats-icon {
font-size: 2.8rem;
font-size: 2rem;
opacity: 0.9;
margin-bottom: 15px;
margin-bottom: 8px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
@ -1141,6 +1351,21 @@ export default {
background: linear-gradient(135deg, #f8f9fa 0%, #e3f2fd 100%) !important;
}
/* 合计行样式 */
.detail-table .el-table .el-table__footer-wrapper {
.el-table__footer {
background: rgba(15, 76, 117, 0.05) !important;
td {
background: rgba(15, 76, 117, 0.05) !important;
color: #0f4c75 !important;
font-weight: 600 !important;
border-top: 1px solid rgba(15, 76, 117, 0.12) !important;
border-bottom: 1px solid rgba(15, 76, 117, 0.12) !important;
}
}
}
.table-summary {
display: flex;
flex-wrap: wrap;
@ -1195,6 +1420,12 @@ export default {
}
/* 响应式布局 */
@media (max-width: 1024px) {
.stats-half-row {
flex-direction: column;
}
}
@media (max-width: 767.98px) {
.stats-container {
flex-direction: column;
@ -1202,7 +1433,8 @@ export default {
}
.stats-card {
width: 100%;
width: 100% !important;
max-width: 100% !important;
margin-bottom: 15px;
}

@ -21,6 +21,26 @@
</el-option>
</el-select>
</div>
<div>
<el-input v-model="select.course_name" placeholder="请输入课程名称"></el-input>
</div>
<div>
<el-input v-model="select.user_name" placeholder="请输入学员名称"></el-input>
</div>
<div>
<el-select v-model="select.course_type_id" placeholder="请选择课程体系" clearable style="width: 100%;">
<el-option v-for="item in courseTypeOptions" :key="item.id" :label="item.name" :value="item.id">
</el-option>
</el-select>
</div>
<div>
<el-select v-model="select.is_schoolmate" placeholder="请选择是否校友" clearable style="width: 100%;">
<el-option label="是" :value="1">
</el-option>
<el-option label="否" :value="0">
</el-option>
</el-select>
</div>
<div>
<el-button type="primary" size="small" @click="select.page=1,getList()"></el-button>
<el-button type="primary" size="small" @click="resetSelect"></el-button>
@ -69,12 +89,21 @@
</el-table-column>
</template>
<template v-slot:project_users>
<el-table-column align='center' label="管理平台-项目经理-首次出资时间" width="300" header-align="center">
<el-table-column align='center' label="管理平台-项目经理-首次出资时间-投资金额" width="360" header-align="center">
<template slot-scope="scope">
<div v-for="(item,index) in scope.row.project_users">
<div style="text-align: left;">
{{index+1}}{{ item.groupName }}-{{ item.userName }}-{{ item.investDate }}
<div style="display: flex; align-items: center; justify-content: space-between;">
<div style="flex: 1;">
<div v-for="(item,index) in scope.row.project_users" :key="index">
<div style="text-align: left;">
{{index+1}}{{ item.groupName }}-{{ item.userName }}-{{ item.investDate }}{{ item.amount ? '-' + item.amount : '' }}
</div>
</div>
</div>
<i v-if="scope.row.project_users && scope.row.project_users.length > 0"
class="el-icon-edit"
style="cursor: pointer; color: #409EFF; margin-left: 10px; font-size: 16px;"
@click="openEditProjectUsers(scope.row)">
</i>
</div>
</template>
</el-table-column>
@ -185,6 +214,32 @@
</div>
<add-company ref="addCompany" @refresh="getList"></add-company>
<!-- 编辑项目经理信息弹窗 -->
<el-dialog
title="编辑项目经理信息"
:visible.sync="editProjectUsersDialogVisible"
width="800px">
<el-table :data="editProjectUsersData" border style="width: 100%">
<el-table-column prop="groupName" label="管理平台" width="150" align="center"></el-table-column>
<el-table-column prop="userName" label="项目经理" width="150" align="center"></el-table-column>
<el-table-column prop="investDate" label="首次出资时间" width="150" align="center"></el-table-column>
<el-table-column label="投资金额" width="200" align="center">
<template slot-scope="scope">
<el-input
v-model="scope.row.amount"
style="width: 100%;"
placeholder="请输入投资金额"
clearable>
</el-input>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button @click="editProjectUsersDialogVisible = false">取消</el-button>
<el-button type="primary" @click="saveProjectUsers"></el-button>
</div>
</el-dialog>
</div>
</template>
@ -196,8 +251,12 @@
import {
index,
destroy,
companyConfig
companyConfig,
save
} from "@/api/student/schoolmateCompany.js"
import {
index as courseTypeIndex
} from "@/api/course/courseType.js"
import formMixin from "@/mixin/formMixin.js";
export default {
mixins: [formMixin],
@ -210,12 +269,20 @@
company_name: '',
is_yh_invested: '',
company_tag: '',
course_name: '',
user_name: '',
is_schoolmate: '',
course_type_id: '',
page: 1,
page_size: 10
},
companiesTags:[],
courseTypeOptions: [],
list: [],
total: 0,
editProjectUsersDialogVisible: false,
editProjectUsersData: [],
currentEditCompanyId: null,
table_item: [{
type: 'index',
align: 'center',
@ -274,6 +341,7 @@
created() {
this.getList()
this.getCompanyConfig()
this.getCourseTypeList()
},
updated() {
//
@ -298,6 +366,10 @@
page_size: 9999,
page: 1,
is_export:1,
course_name: this.select.course_name,
user_name: this.select.user_name,
is_schoolmate: this.select.is_schoolmate !== '' ? this.select.is_schoolmate : '',
course_type_id: this.select.course_type_id,
filter: [{
key: 'company_name',
op: 'like',
@ -318,6 +390,19 @@
const res = await companyConfig()
this.companiesTags = res.companiesTags
},
async getCourseTypeList() {
try {
const res = await courseTypeIndex({
page: 1,
page_size: 999
})
if (res && res.data) {
this.courseTypeOptions = res.data
}
} catch (error) {
console.error('获取课程体系列表失败:', error)
}
},
toQicc(company_name) {
var url = 'https://www.qcc.com/web/search?key='
if(company_name){
@ -347,6 +432,10 @@
this.select.company_name = ''
this.select.is_yh_invested = ''
this.select.company_tag = ''
this.select.course_name = ''
this.select.user_name = ''
this.select.is_schoolmate = ''
this.select.course_type_id = ''
this.select.page = 1
this.getList()
},
@ -354,6 +443,10 @@
const res = await index({
page_size: this.select.page_size,
page: this.select.page,
course_name: this.select.course_name,
user_name: this.select.user_name,
is_schoolmate: this.select.is_schoolmate !== '' ? this.select.is_schoolmate : '',
course_type_id: this.select.course_type_id,
filter: [{
key: 'company_name',
op: 'like',
@ -388,6 +481,36 @@
reject(error)
})
},
//
openEditProjectUsers(row) {
this.currentEditCompanyId = row.id
// amount
this.editProjectUsersData = (row.project_users || []).map(item => {
return {
...item,
amount: item.amount !== undefined && item.amount !== null ? String(item.amount) : ''
}
})
this.editProjectUsersDialogVisible = true
},
//
saveProjectUsers() {
if (!this.currentEditCompanyId) {
this.$message.error('缺少企业ID')
return
}
save({
id: this.currentEditCompanyId,
project_users: this.editProjectUsersData
}).then(response => {
this.$message.success('保存成功')
this.editProjectUsersDialogVisible = false
this.getList()
}).catch(error => {
console.log(error)
this.$message.error('保存失败')
})
},
// 线
alignUserItemHeights() {
this.$nextTick(() => {

@ -28,9 +28,9 @@ module.exports = {
*/
publicPath: process.env.ENV === 'staging' ? '/admin' : '/admin',
// 测试
outputDir: '/Users/mac/Documents/朗业/2025/s-苏州科技商学院/wx.sstbc.com/public/admin',
// 正式
// outputDir: '/Users/mac/Documents/朗业/2024/s-苏州科技商学院/wx.sstbc.com/public/admin',
// outputDir: '/Users/mac/Documents/朗业/2025/s-苏州科技商学院/wx.sstbc.com/public/admin',
// 正式
outputDir: '/Users/mac/Documents/朗业/2024/s-苏州科技商学院/wx.sstbc.com/public/admin',
assetsDir: 'static',
css: {
loaderOptions: { // 向 CSS 相关的 loader 传递选项

Loading…
Cancel
Save