Compare commits

...

7 Commits
dev ... master

@ -83,7 +83,7 @@
:value="item.id"
>
<div style="display: flex; justify-content: space-between;padding: 0 10px;">
<span style="color: #999; font-size: 12px;">{{item.type_detail.name}} | {{ item.name }}</span>
<span style="color: #999; font-size: 12px;">{{item.type_detail?item.type_detail.name:''}} | {{ item.name }}</span>
<span style="color: #999; font-size: 12px;">{{ item.is_arrange?'需排课':'无需排课' }}</span>
</div>
</el-option>
@ -516,7 +516,7 @@ export default {
: "";
this.form.date = item.start_date;
this.form.url = item.url;
this.form.color = item.type_detail.color?item.type_detail.color:''
this.form.color = item.type_detail?(item.type_detail.color?item.type_detail.color:''):''
}
});
}
@ -595,6 +595,7 @@ export default {
id: this.id,
}).then((res) => {
this.form = this.base.requestToForm(res, this.form);
this.form.is_publish = res.is_publish?res.is_publish:0
this.showTinymce = true;
});
},

@ -13,10 +13,11 @@
<div class="cell-content">
<span class="date-number">{{ date.getDate() }}</span>
<div class="event-list">
<div
v-for="ev in eventsForDate(date)"
:key="ev._id"
<div
v-for="ev in eventsForDate(date)"
:key="ev._id"
:class="['event-item', getEventClass(ev, date)]"
:style="getEventItemStyle(ev)"
:title="getEventTooltip(ev, date)"
@click.stop="openCreateModal('editor', ev.id)"
>
@ -28,8 +29,8 @@
</el-calendar>
<!-- 连续事件覆盖层 -->
<div class="continuous-events-overlay">
<div
v-for="event in getContinuousEvents()"
<div
v-for="event in getContinuousEvents()"
:key="`continuous-${event.id}-${event.segStartISO}`"
:class="['continuous-event', getContinuousEventClass(event)]"
:style="getContinuousEventStyle(event)"
@ -101,7 +102,7 @@ import addCalendar from './components/addCalendar.vue'
eventsForDate(date) {
const d = new Date(date)
return this.list.filter(ev => {
const events = this.list.filter(ev => {
const startDate = this.parseDateTime(ev.start_time)
// end_timestart_time
@ -128,6 +129,111 @@ import addCalendar from './components/addCalendar.vue'
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
return currentDate.getTime() === eventStartDate.getTime()
})
//
return this.arrangeEventsVertically(events)
},
//
arrangeEventsVertically(events) {
if (!events || events.length === 0) return events
//
const sortedEvents = events.sort((a, b) => {
const timeA = this.parseDateTime(a.start_time)
const timeB = this.parseDateTime(b.start_time)
return timeA.getTime() - timeB.getTime()
})
//
const lanes = [] //
const eventHeight = 16 //
const eventSpacing = 2 //
sortedEvents.forEach(event => {
const startTime = this.parseDateTime(event.start_time)
const endTime = event.end_time ? this.parseDateTime(event.end_time) : startTime
//
const startHour = startTime.getHours() + startTime.getMinutes() / 60
const endHour = endTime.getHours() + endTime.getMinutes() / 60
//
let laneIndex = 0
let foundLane = false
for (let i = 0; i < lanes.length; i++) {
const lane = lanes[i]
//
const hasConflict = lane.some(occupied => {
return !(endHour <= occupied.start || startHour >= occupied.end)
})
if (!hasConflict) {
laneIndex = i
foundLane = true
break
}
}
//
if (!foundLane) {
laneIndex = lanes.length
lanes.push([])
}
//
lanes[laneIndex].push({
start: startHour,
end: endHour
})
//
event.verticalPosition = laneIndex
// 使bottom
event.topOffset = (lanes.length - 1 - laneIndex) * (eventHeight + eventSpacing)
})
return sortedEvents
},
//
getDayEventsWithPositions(date) {
const d = new Date(date)
const currentDateStr = d.toDateString()
//
const singleDayEvents = this.list.filter(ev => {
const startDate = this.parseDateTime(ev.start_time)
const currentDate = new Date(d.getFullYear(), d.getMonth(), d.getDate())
const eventStartDate = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate())
return currentDate.getTime() === eventStartDate.getTime()
})
//
const multiDayEvents = this.getContinuousEvents().filter(ev => {
const eventStart = new Date(ev.segStartISO)
const eventEnd = new Date(ev.segEndISO)
return eventStart <= d && d <= eventEnd
})
//
const allEvents = [
...singleDayEvents.map(ev => ({ ...ev, isMultiDay: false })),
...multiDayEvents.map(ev => ({ ...ev, isMultiDay: true }))
]
//
return this.arrangeEventsVertically(allEvents)
},
//
getEventItemStyle(event) {
if (event.topOffset !== undefined) {
return {
position: 'relative',
bottom: `${event.topOffset}px`,
zIndex: 70 + (event.verticalPosition || 0) //
}
}
return {}
},
getEventClass(event, date) {
const startDate = new Date(event.start_time)
@ -205,6 +311,14 @@ import addCalendar from './components/addCalendar.vue'
return s.toDateString() !== e.toDateString()
})
//
const singleDayEvents = this.list.filter(ev => {
if (!ev.end_time) return true
const s = this.parseDateTime(ev.start_time)
const e = this.parseDateTime(ev.end_time)
return s.toDateString() === e.toDateString()
})
const currentMonth = this.calendarDate.getMonth()
const currentYear = this.calendarDate.getFullYear()
const monthStart = new Date(currentYear, currentMonth, 1)
@ -238,6 +352,15 @@ import addCalendar from './components/addCalendar.vue'
const clampedStart = eventStart < monthStart ? monthStart : eventStart
const clampedEnd = eventEnd > monthEnd ? monthEnd : eventEnd
//
const hasSingleDayConflict = (date) => {
const dateStr = date.toDateString()
return singleDayEvents.some(singleEv => {
const singleStart = this.parseDateTime(singleEv.start_time)
return singleStart.toDateString() === dateStr
})
}
let cursor = getWeekStart(clampedStart)
while (cursor <= clampedEnd) {
const weekStart = new Date(cursor)
@ -272,7 +395,9 @@ import addCalendar from './components/addCalendar.vue'
displayWeekStartISO: displayWeekStart.toISOString(),
displayStartCol,
displayEndCol,
laneIndex: 0
laneIndex: 0,
//
hasSingleDayConflict: hasSingleDayConflict
})
}
cursor.setDate(cursor.getDate() + 7)
@ -360,7 +485,11 @@ import addCalendar from './components/addCalendar.vue'
const dateNumberHeight = 40 //
const eventHeight = 16
const eventSpacing = 2
const verticalOffset = (event.laneIndex || 0) * (eventHeight + eventSpacing)
//
const dayEvents = this.getDayEventsWithPositions(adjStart)
const currentEvent = dayEvents.find(ev => ev.id === event.id)
const verticalOffset = currentEvent ? currentEvent.topOffset : 0
return {
position: 'absolute',
@ -368,7 +497,7 @@ import addCalendar from './components/addCalendar.vue'
top: `${headerHeight + weekRow * cellHeight + dateNumberHeight + 25 + verticalOffset}px`, // 25px
width: `calc(${event.spanCols * cellWidth}% - 4px)`,
height: `${eventHeight}px`,
zIndex: 1000,
zIndex: 50, //
background: `linear-gradient(90deg, ${this.getEventTypeColor(event.type)} 0%, ${this.darkenColor(this.getEventTypeColor(event.type))} 100%)`,
borderRadius: '3px',
fontSize: '11px',
@ -639,7 +768,7 @@ import addCalendar from './components/addCalendar.vue'
right: 0;
bottom: 0;
pointer-events: none;
z-index: 100;
z-index: 50; /* 降低跨天事件的层级 */
}
.continuous-event {
@ -650,7 +779,7 @@ import addCalendar from './components/addCalendar.vue'
.continuous-event:hover {
transform: translateY(-1px);
filter: brightness(1.1);
z-index: 101;
z-index: 51; /* 调整悬停时的层级 */
}
/* Element UI 日历样式覆盖 */
@ -701,7 +830,8 @@ import addCalendar from './components/addCalendar.vue'
flex: 1;
overflow: visible;
position: relative;
z-index: 5;
z-index: 60; /* 提高单天事件的层级,确保在跨天事件之上 */
min-height: 60px; /* 确保有足够空间显示多个事件 */
}
.event-item {
@ -718,6 +848,9 @@ import addCalendar from './components/addCalendar.vue'
text-overflow: ellipsis;
transition: background-color 0.2s;
position: relative;
height: 16px; /* 固定高度 */
display: flex;
align-items: center;
}
.event-title {

@ -13,7 +13,7 @@
</el-select>
</div>
<div>
<el-select filterable v-model="select.direction" placeholder="请选择课程方向" clearable style="width: 100%;">
<el-select filterable v-model="select.direction" placeholder="请选择或输入课程方向" clearable style="width: 100%;">
<el-option v-for="(item,index) in direction_options" :key="index" :label="item.value" :value="item.id">
</el-option>
</el-select>
@ -130,7 +130,7 @@
align: 'center',
width: 120,
},{
prop: 'direction_detail.value',
prop: 'direction',
label: '课程方向',
align: 'center',
width: 120,
@ -180,7 +180,7 @@
const res = await index({
page_size: this.select.page_size,
page: this.select.page,
show_relation: ['course','teacher','directionDetail'],
show_relation: ['course','teacher'],
filter: [{
key: 'file_ids',
op: 'notnull',

@ -7,7 +7,7 @@
<div slot="content">
<div class="searchwrap" style="display: flex;align-items: center;">
<div>
<el-input v-model="select.name" placeholder="请输入授课老师姓名"></el-input>
<el-input v-model="select.name" placeholder="请输入授课老师姓名\方向" clearable @clear="select.page=1,getList()"></el-input>
</div>
<div>
<el-button type="primary" size="small" @click="select.page=1,getList()"></el-button>
@ -57,12 +57,71 @@
<el-table-column prop="direction" label="课程方向" width="240" align="left">
<template slot-scope="scope">
<div class="cc-item" v-for="(item, index) in scope.row.course_contents" :key="index">
<el-tooltip :content="item.direction_detail ? item.direction_detail.value : ''" placement="top" :disabled="!item.direction_detail || item.direction_detail.value.length <= 20">
<span>{{ item.direction_detail ? item.direction_detail.value : '' }}</span>
<el-tooltip :content="item.direction ? item.direction : ''" placement="top" :disabled="!item.direction || item.direction.length <= 20">
<span>{{ item.direction ? item.direction : '' }}</span>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column prop="direction" label="课酬" width="120" align="left">
<template slot-scope="scope">
<div class="cc-item" v-for="(item, index) in scope.row.course_contents" :key="index">
<div class="salary-display">
<el-popover
:ref="`popover-${item.id}`"
placement="top"
width="200"
trigger="click"
@show="startEdit(item)"
>
<div class="salary-edit-popover">
<el-input
v-model="editingSalary"
placeholder="请输入课酬"
size="small"
style="margin-bottom: 10px;"
@keyup.enter.native="saveSalary(item)"
/>
<div class="popover-buttons">
<el-button size="mini" type="primary" @click="saveSalary(item)"></el-button>
</div>
</div>
<span slot="reference">{{ item.salary ? item.salary : '' }}<i class="el-icon-edit salary-edit-icon" ></i></span>
</el-popover>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="tutor" label="导师" width="120" align="left">
<template slot-scope="scope">
<div class="cc-item" v-for="(item, index) in scope.row.course_contents" :key="index">
<div class="salary-display">
<el-popover
:ref="`tutor-popover-${item.id}`"
placement="top"
width="200"
trigger="click"
@show="startEditTutor(item)"
>
<div class="salary-edit-popover">
<el-input
v-model="editingTutor"
placeholder="请输入导师"
size="small"
style="margin-bottom: 10px;"
@keyup.enter.native="saveTutor(item)"
/>
<div class="popover-buttons">
<el-button size="mini" type="primary" @click="saveTutor(item)"></el-button>
</div>
</div>
<span slot="reference">{{ item.tutor ? item.tutor : '' }}<i class="el-icon-edit salary-edit-icon"></i></span>
</el-popover>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" width="240" align="left">
<template slot-scope="scope">
<div class="cc-item" v-for="(item, index) in scope.row.course_contents" :key="index">
@ -104,6 +163,9 @@
import {
saveAs
} from "file-saver";
import {
save as saveCourseContent
} from '@/api/course/courseContent.js'
export default {
components: {
addTeacher,
@ -119,6 +181,10 @@
list: [],
total: 0,
editingItem: null,
editingSalary: '',
editingTutorItem: null,
editingTutor: '',
table_item: [{
type: 'index',
align: 'center',
@ -177,11 +243,12 @@
const res = await index({
page: this.select.page,
page_size: this.select.page_size,
filter: [{
key: 'name',
op: 'like',
value: this.select.name
}]
keyword: this.select.name
// filter: [{
// key: 'name',
// op: 'like',
// value: this.select.name
// }]
})
this.list = res.data
this.total = res.total
@ -199,25 +266,75 @@
page_size: 9999
})
if (res.data) {
let headers = this.table_item.map(i => {
//
let headers = this.table_item.slice(1).map(i => {
return {
key: i.prop,
title: i.label
}
})
const data = res.data.map(row => headers.map(header => row[header.key]));
data.unshift(headers.map(header => header.title));
const wb = XLSX.utils.book_new();
const ws = XLSX.utils.aoa_to_sheet(data);
XLSX.utils.book_append_sheet(wb, ws, sheetName);
// nullExcel
const data = res.data.map((row, rowIndex) => {
return headers.map((header, colIndex) => {
let value = row[header.key]
// undefined
if (value === undefined) {
return ''
}
// null
if (value === null) {
return ''
}
//
if (Array.isArray(value)) {
if (value.length === 0) {
return ''
}
//
return value.map(item => {
if (typeof item === 'object' && item !== null) {
//
let parts = []
if (item.course && item.course.name) parts.push(`课程: ${item.course.name}`)
if (item.theme) parts.push(`主题: ${item.theme}`)
if (item.direction) parts.push(`方向: ${item.direction}`)
if (item.salary) parts.push(`课酬: ${item.salary}`)
if (item.tutor) parts.push(`导师: ${item.tutor}`)
if (item.remark) parts.push(`备注: ${item.remark}`)
return parts.join('; ')
}
return String(item)
}).join('\n')
}
//
if (typeof value === 'object' && value !== null) {
return JSON.stringify(value)
}
// Excel
return String(value)
})
})
//
data.unshift(headers.map(header => header.title))
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.aoa_to_sheet(data)
XLSX.utils.book_append_sheet(wb, ws, sheetName)
const wbout = XLSX.write(wb, {
bookType: 'xlsx',
bookSST: true,
type: 'array'
});
})
saveAs(new Blob([wbout], {
type: 'application/octet-stream'
}), `${sheetName}.xlsx`);
}), `${sheetName}.xlsx`)
}
},
editTeacher(type, id) {
@ -240,6 +357,68 @@
reject(error)
})
},
startEdit(item) {
this.editingItem = item
this.editingSalary = item.salary || ''
},
cancelEdit() {
this.editingItem = null
this.editingSalary = ''
// popover
this.$nextTick(() => {
if (this.editingItem) {
const popoverRef = this.$refs[`popover-${this.editingItem.id}`]
if (popoverRef && popoverRef[0]) {
popoverRef[0].doClose()
}
}
})
},
async saveSalary(item) {
try {
await saveCourseContent({
id: item.id,
salary: this.editingSalary
})
this.$message.success('保存成功')
this.cancelEdit()
this.getList()
} catch (error) {
this.$message.error('保存失败')
console.error('保存课酬失败:', error)
}
},
startEditTutor(item) {
this.editingTutorItem = item
this.editingTutor = item.tutor || ''
},
cancelEditTutor() {
this.editingTutorItem = null
this.editingTutor = ''
// tutor popover
this.$nextTick(() => {
if (this.editingTutorItem) {
const popoverRef = this.$refs[`tutor-popover-${this.editingTutorItem.id}`]
if (popoverRef && popoverRef[0]) {
popoverRef[0].doClose()
}
}
})
},
async saveTutor(item) {
try {
await saveCourseContent({
id: item.id,
tutor: this.editingTutor
})
this.$message.success('保存成功')
this.cancelEditTutor()
this.getList()
} catch (error) {
this.$message.error('保存失败')
console.error('保存导师失败:', error)
}
},
importTable() {
this.$refs.imports.show()
}
@ -276,4 +455,32 @@
.cc-item:last-child {
border-bottom: none;
}
/* 课酬编辑样式 */
.salary-display {
display: flex;
align-items: center;
justify-content: space-between;
}
.salary-edit-icon {
margin-left: 4px;
cursor: pointer;
color: #409eff;
font-size: 12px;
}
.salary-edit-icon:hover {
color: #66b1ff;
}
.salary-edit-popover {
padding: 10px;
}
.popover-buttons {
display: flex;
justify-content: flex-end;
gap: 8px;
}
</style>

@ -263,6 +263,12 @@
width: 120,
fixed: 'left'
},
{
prop: 'user.is_schoolmate_text',
label: '校友',
align: 'center',
width: 120,
},
{
prop: 'username',
label: '姓名',
@ -293,6 +299,16 @@
}
}
}, {
prop: 'is_yh_invested',
label: '集团标签',
align: 'center',
width: 120,
customFn:(row)=>{
if(row.company){
return(<div>{row.company.is_yh_invested?'被投企业':''}</div>)
}
}
},{
prop: 'user.company_position',
label: '职务',
align: 'center',

@ -124,7 +124,7 @@
align="left"
></el-table-column>
<el-table-column
prop="direction_detail.value"
prop="direction"
label="课程方向"
min-width="150"
align="left"
@ -406,7 +406,7 @@ import editClass from "../components/editClass.vue";
const res = await index({
page: 1,
page_size: 999,
show_relation: ["teacher",'directionDetail'],
show_relation: ["teacher"],
sort_name: "date",
sort_type: "DESC",
filter: [

@ -51,6 +51,26 @@
</div>
</div>
</template>
<!-- <template v-slot:student_prefix v-if="active===0">
<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>
<div class="xy-table-item-content">
<el-input v-model="form.student_prefix" placeholder="" clearable style="width: 100%;"></el-input>
</div>
</template> -->
<template v-slot:student_prefix v-if="active===0">
<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.student_prefix" placeholder="例高研班第一期填入GY01" clearable style="width: 100%;"></el-input>
</div>
</div>
</template>
<template v-slot:url v-if="active===0">
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
@ -376,6 +396,7 @@
type: '',
name: '',
is_virtual: 0, //01
student_prefix:'',
url: '',
year: '',
dateRange: '',
@ -815,6 +836,7 @@
type: '',
name: '',
is_virtual: 0,
student_prefix:'',
url: '',
year: '',
dateRange: '',

@ -26,6 +26,20 @@
</div>
</div>
</template>
<template v-slot:open_mobile>
<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.open_mobile" placeholder="请选择学员是否开放联系方式" style="width: 100%;">
<el-option v-for="item in [{id:0,value:'否'},{id:1,value:'是'}]" :key="item.id" :label="item.value" :value="item.id">
</el-option>
</el-select>
</div>
</div>
</template>
<template v-slot:is_chart>
<div class="xy-table-item">
<div class="xy-table-item-label" style="font-weight: bold">
@ -191,7 +205,7 @@
id: '',
form: {
name:'',
open_mobile:0,
status:1,
is_chart:0,
wait_tip:"",
@ -241,6 +255,7 @@
id: this.id
}).then(res => {
this.form = this.base.deepCopy(res)
this.form.open_mobile = res.open_mobile?res.open_mobile:0
this.form.status = res.status?res.status:0
this.form.is_chart = res.is_chart?res.is_chart:0
this.form.is_fee = res.is_fee?res.is_fee:(res.is_fee==0?0:'')
@ -265,6 +280,7 @@
this.form = {
name:'',
open_mobile:0,
status:1,
is_chart:0,
wait_tip:"",

@ -77,7 +77,7 @@
<span style="color: red;font-weight: bold;padding-right: 4px;"></span>课程方向
</div>
<div class="xy-table-item-content">
<el-select filterable v-model="form.direction" style="width:100%" placeholder="请选择课程方向" clearable>
<el-select multiple filterable allow-create v-model="form.direction" style="width:100%" placeholder="请选择或创建课程方向" clearable>
<el-option v-for="item in direction_options" :key="item.id" :label="item.value" :value="item.value">
</el-option>
</el-select>
@ -336,7 +336,9 @@
const finalFileIds = [...existingFileIds, ...newFileIds].filter(id =>
!this.removedFileIds.includes(id)
);
if(this.form.direction && this.form.direction.length > 0){
this.form.direction = this.form.direction.join(',')
}
const submitData = {
...this.form,
file_ids: finalFileIds
@ -382,7 +384,7 @@
this.form.files = res.files ? res.files : []
//
this.form.teacher_introduce = res.teacher ? res.teacher.introduce || '' : ''
this.form.direction = res.direction ? parseInt(res.direction) : ''
this.form.direction = res.direction ? res.direction.split(',') : []
// this.form.timeRange = [this.form.start_time ? this.form.start_time : '', this.form.end_time ? this.form
// .end_time : ''
// ]

@ -138,8 +138,8 @@ export default {
data() {
return {
isShow: false,
course_content_check_count:0,
course_content_check_count_no:0,
course_content_check_count: 0,
course_content_check_count_no: 0,
searchForm: {
page: 1,
page_size: 10,
@ -148,11 +148,10 @@ export default {
has_check: ''
},
tableReqOpt: {
course_id: this.courseId,
course_content_id: this.courseContentId || ''
course_id: this.courseId
},
list:[],
total:0,
list: [],
total: 0,
tableColumns: [
{
prop: 'user.name',
@ -196,7 +195,11 @@ export default {
this.tableReqOpt.course_id = newVal
},
courseContentId(newVal) {
this.tableReqOpt.course_content_id = newVal || ''
if (newVal) {
this.tableReqOpt.course_content_id = newVal
} else {
delete this.tableReqOpt.course_content_id
}
}
},
methods: {
@ -227,8 +230,10 @@ export default {
handleReset() {
this.resetForm()
this.tableReqOpt = {
course_id: this.courseId,
course_content_id: this.courseContentId || ''
course_id: this.courseId
}
if (this.courseContentId) {
this.tableReqOpt.course_content_id = this.courseContentId
}
this.refreshTable()
},
@ -252,7 +257,6 @@ export default {
const exportParams = {
...this.searchForm,
course_id: this.courseId,
course_content_id: this.courseContentId || '',
export_fields: exportFields,
is_export: 1,
clear: 1,
@ -260,6 +264,11 @@ export default {
page_size: 999
}
// IDcourse_content_id
if (this.courseContentId) {
exportParams.course_content_id = this.courseContentId
}
//
let fileName = '签到记录'
if (this.courseInfo && this.courseInfo.name) {
@ -295,14 +304,20 @@ export default {
//
async fetchSignList(params) {
const res = await getSignList({
const requestParams = {
...this.searchForm,
course_id: this.courseId,
course_content_id: this.courseContentId || '',
})
course_id: this.courseId
}
// IDcourse_content_id
if (this.courseContentId) {
requestParams.course_content_id = this.courseContentId
}
const res = await getSignList(requestParams)
this.list = res.list.data
this.course_content_check_count = res.course_content_check_count
this.course_content_check_count_no = res.list.total - res.course_content_check_count
this.course_content_check_count_no = res.total - res.course_content_check_count
this.total = res.list.total
}
}

@ -174,7 +174,7 @@
<signList
ref="signList"
:course-id="course_id"
:course-content-id="selectedCourse ? selectedCourse.id : null"
:course-content-id="is_arrange === 1 ? (selectedCourse ? selectedCourse.id : null) : null"
:course-info="is_arrange === 0 ? selectedCourse : null"
:schedule-info="is_arrange === 1 ? selectedCourse : null"
/>

@ -158,7 +158,8 @@ export default {
{
icon: 'el-icon-user-solid',
value: '0',
label: '报名人数(未去重)',
// label: '',
label: '投后企业数',
cardClass: 'student-card-1'
},
{
@ -589,7 +590,7 @@ export default {
// API
if (data && data.list) {
//
this.studentStats[0].value = data.list.course_signs_total || '0'
this.studentStats[0].value = data.list.course_signs_invested || '0'
//
this.studentStats[1].value = data.list.course_signs_pass || '0'

@ -3,20 +3,53 @@
<div v-if="finalDetail" class="supply-detail">
<div class="section">
<div class="label">供需类型</div>
<div>{{ finalDetail.type === 1 ? '供应' : '需求' }}</div>
<div>{{ finalDetail.type === 1 ? '供应' : finalDetail.type === 2 ? '需求' : finalDetail.type === 3 ? '投融资' : '' }}</div>
</div>
<div class="section">
<div class="label">标题</div>
<div>{{ finalDetail.title || '-' }}</div>
</div>
<div class="section">
<!-- 非投融资时显示原有字段 -->
<div v-if="finalDetail.type !== 3" class="section">
<div class="label">详细描述</div>
<div>{{ finalDetail.content || '-' }}</div>
</div>
<div class="section">
<div v-if="finalDetail.type !== 3" class="section">
<div class="label">行业标签</div>
<div>{{ finalDetail.tag || '-' }}</div>
</div>
<!-- 投融资专属字段 -->
<template v-if="finalDetail.type === 3">
<div class="section">
<div class="label">资金类型</div>
<div>{{ finalDetail.fund_type || '-' }}</div>
</div>
<div class="section">
<div class="label">金额</div>
<div>{{ formatAmount(finalDetail.amount) }}万元</div>
</div>
<div class="section">
<div class="label">融资阶段</div>
<div>{{ finalDetail.fund_stage || '-' }}</div>
</div>
<div class="section">
<div class="label">期望资金属性</div>
<div>{{ finalDetail.fund_company || '-' }}</div>
</div>
<div class="section">
<div class="label">行业类型</div>
<div>{{ finalDetail.industry_type || '-' }}</div>
</div>
<div class="section">
<div class="label">主要产品</div>
<div>{{ finalDetail.product || '-' }}</div>
</div>
<div class="section">
<div class="label">简要描述</div>
<div>{{ finalDetail.desc || '-' }}</div>
</div>
</template>
<div class="section">
<div class="label">联系人</div>
<div>{{ finalDetail.contact_name || '-' }}</div>
@ -127,6 +160,11 @@ export default {
}
},
methods: {
formatAmount(val) {
const n = typeof val === 'number' ? val : parseFloat(val)
if (Number.isNaN(n)) return '-'
return n.toFixed(2)
},
async fetchDetail() {
if (!this.id) return

@ -5,15 +5,17 @@
<el-radio-group v-model="form.type">
<el-radio :label="1">供应</el-radio>
<el-radio :label="2">需求</el-radio>
<el-radio :label="3">投融资</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="标题">
<el-input v-model="form.title" maxlength="50" show-word-limit />
</el-form-item>
<el-form-item label="详细描述">
<!-- 非投融资时显示原有描述与行业标签 -->
<el-form-item v-if="form.type !== 3" label="详细描述">
<el-input v-model="form.content" type="textarea" :rows="4" maxlength="200" show-word-limit />
</el-form-item>
<el-form-item label="行业标签">
<el-form-item v-if="form.type !== 3" label="行业标签">
<el-input
v-model="tagInput"
placeholder="输入后回车键确认"
@ -35,6 +37,34 @@
</div>
</el-form-item>
<!-- 投融资专属字段 type === 3 时显示 -->
<template v-if="form.type === 3">
<el-form-item label="资金类型">
<el-select v-model="form.fund_type" placeholder="请选择资金类型" style="width: 240px;">
<el-option label="投资" value="投资" />
<el-option label="融资" value="融资" />
</el-select>
</el-form-item>
<el-form-item label="金额">
<el-input-number v-model="form.amount" :precision="2" :step="0.01" :min="0" style="width: 240px;" />万元
</el-form-item>
<el-form-item label="融资阶段">
<el-input v-model="form.fund_stage" maxlength="50" />
</el-form-item>
<el-form-item label="公司名称">
<el-input v-model="form.fund_company" maxlength="50" />
</el-form-item>
<el-form-item label="行业类型">
<el-input v-model="form.industry_type" maxlength="50" />
</el-form-item>
<el-form-item label="主要产品">
<el-input v-model="form.product" maxlength="50" />
</el-form-item>
<el-form-item label="简要描述">
<el-input v-model="form.desc" type="textarea" :rows="4" maxlength="200" show-word-limit />
</el-form-item>
</template>
<!-- 图片上传 -->
<el-form-item label="相关图片">
<el-upload
@ -167,6 +197,14 @@ export default {
type: 1,
title: '',
content: '',
//
fund_type: '',
amount: 0,
fund_stage: '',
fund_company: '',
industry_type: '',
product: '',
desc: '',
contactType: 'mobile',
mobile: '',
wechat: '',
@ -208,6 +246,13 @@ export default {
type: 1,
title: '',
content: '',
fund_type: '',
amount: 0,
fund_stage: '',
fund_company: '',
industry_type: '',
product: '',
desc: '',
contactType: 'mobile',
mobile: '',
wechat: '',
@ -226,6 +271,13 @@ export default {
type: val.type || 1,
title: val.title || '',
content: val.content || '',
fund_type: val.fund_type || '',
amount: typeof val.amount === 'number' ? val.amount : (parseFloat(val.amount) || 0),
fund_stage: val.fund_stage || '',
fund_company: val.fund_company || '',
industry_type: val.industry_type || '',
product: val.product || '',
desc: val.desc || '',
contactType: val.wechat ? 'wechat' : (val.email ? 'email' : 'mobile'),
mobile: val.mobile || '',
wechat: val.wechat || '',
@ -316,7 +368,7 @@ export default {
const data = {
id: this.form.id,
title: this.form.title,
content: this.form.content,
content: this.form.type === 3 ? '' : this.form.content,
tag: this.tagList.join(','),
wechat: this.form.wechat,
mobile: this.form.mobile,
@ -328,6 +380,26 @@ export default {
contact_name: this.form.contactName,
file_ids: this.imageList.map(item => item.response?.id || item.id).filter(Boolean)
}
//
if (this.form.type === 3) {
data.fund_type = this.form.fund_type
data.amount = this.form.amount
data.fund_stage = this.form.fund_stage
data.fund_company = this.form.fund_company
data.industry_type = this.form.industry_type
data.product = this.form.product
data.desc = this.form.desc
//
data.tag = ''
} else {
data.fund_type = ''
data.amount = 0
data.fund_stage = ''
data.fund_company = ''
data.industry_type = ''
data.product = ''
data.desc = ''
}
// file_ids
if (this.newUploadedFileIds && this.newUploadedFileIds.length) {
const fileIdSet = new Set(data.file_ids)

@ -51,10 +51,12 @@
<span style="color: red;font-weight: bold;padding-right: 4px;"></span>集团标签
</div>
<div class="xy-table-item-content">
<el-select v-model="form.company_tag" placeholder="请选择集团标签" clearable style="width: 100%;">
<el-input v-model="form.company_tag" placeholder="请输入集团标签" clearable style="width: 100%;"></el-input>
<!-- <el-select v-model="form.company_tag" placeholder="请选择集团标签" clearable style="width: 100%;">
<el-option v-for="(item,index) in formSelect.company_tag" :key="index" :label="item.value" :value="item.value">
</el-option>
</el-select>
</el-select> -->
</div>
</div>
</template>

@ -138,6 +138,9 @@
</el-option> -->
</el-select>
</div>
<div>
<el-input v-model="select.company_tag" placeholder="请输入集团标签"></el-input>
</div>
<div>
<el-select v-model="select.has_openid" placeholder="是否绑定小程序" clearable>
<el-option v-for="item in false_or_true" :key="item.id" :label="item.value" :value="item.id">
@ -347,6 +350,7 @@
company_area: '',
company_type: '',
company_industry: '',
company_tag: '',
is_vip: '',
is_schoolmate: '',
courses_end_date: '',
@ -441,12 +445,25 @@
label: '是否校友库学员',
align: 'center',
width: 120,
},{
prop: 'is_vip',
label: '学员身份',
align: 'center',
width: 120,
},{
},
// {
// prop: 'is_vip',
// label: '',
// align: 'center',
// width: 120,
// },
{
prop: 'is_yuanhe',
label: '集团标签',
align: 'center',
width: 120,
customFn:(row)=>{
if(row.company){
return(<div>{row.company.is_yh_invested?'被投企业':''}</div>)
}
}
},
{
prop: 'is_wx',
label: '是否绑定小程序',
align: 'center',
@ -580,6 +597,7 @@
this.select.company_area = ''
this.select.company_type = ''
this.select.company_industry = ''
this.select.company_tag = ''
this.select.is_vip = ''
this.select.courses_end_date = ''
this.select.is_schoolmate = ''
@ -612,6 +630,7 @@
company_area: this.select.company_area,
company_type: this.select.company_type,
company_industry: this.select.company_industry,
company_tag: this.select.company_tag,
is_vip: this.select.is_vip,
courses_end_date: this.select.courses_end_date,
is_schoolmate: this.select.is_schoolmate,

@ -53,6 +53,7 @@
<el-option label="全部" value=""></el-option>
<el-option label="供应" value="supply"></el-option>
<el-option label="需求" value="demand"></el-option>
<el-option label="投融资" value="finance"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -460,6 +461,7 @@ export default {
getTypeDisplayValue(type) {
if (type === 'demand' || type === 2) return '需求'
if (type === 'supply' || type === 1) return '供应'
if (type === 'finance' || type === 3) return '投融资'
return '未知'
},
@ -467,6 +469,7 @@ export default {
getTypeTagType(type) {
if (type === 'demand' || type === 2) return 'warning'
if (type === 'supply' || type === 1) return 'success'
if (type === 'finance' || type === 3) return 'warning'
return 'info'
},

@ -138,6 +138,9 @@
</el-option> -->
</el-select>
</div>
<div>
<el-input v-model="select.company_tag" placeholder="请输入集团标签"></el-input>
</div>
<div>
<el-select v-model="select.has_openid" placeholder="是否绑定小程序" clearable>
<el-option v-for="item in false_or_true" :key="item.id" :label="item.value" :value="item.id">
@ -316,6 +319,7 @@
company_area: '',
company_type: '',
company_industry: '',
company_tag: '',
is_vip: '',
is_schoolmate: 1,
courses_end_date: '',
@ -395,6 +399,11 @@
label: '职务',
align: 'center',
width: 120,
}, {
prop: 'from',
label: '学员标签',
align: 'center',
width: 120,
}, {
prop: 'is_schoolmate',
label: '是否校友库学员',
@ -494,6 +503,7 @@
this.select.company_type = ''
this.select.company_industry = ''
this.select.is_vip = ''
this.select.company_tag = ''
this.select.courses_end_date = ''
this.select.is_schoolmate = 1
this.select.education = ''
@ -525,6 +535,7 @@
company_area: this.select.company_area,
company_type: this.select.company_type,
company_industry: this.select.company_industry,
company_tag: this.select.company_tag,
is_vip: this.select.is_vip,
courses_end_date: this.select.courses_end_date,
is_schoolmate: this.select.is_schoolmate,

@ -16,12 +16,13 @@
</el-select>
</div>
<div>
<el-select v-model="select.company_tag" placeholder="请选择集团标签" clearable style="width: 100%;">
<el-option label="被投企业" value="被投企业">
</el-option>
<el-option v-for="(item,index) in formSelect.company_tag" :key="index" :label="item.value" :value="item.value">
</el-option>
</el-select>
<el-input v-model="select.company_tag" placeholder="请输入集团标签"></el-input>
<!-- <el-select v-model="select.company_tag" placeholder="请选择集团标签" clearable style="width: 100%;">
<el-option label="被投企业" value="被投企业">
</el-option>
<el-option v-for="(item,index) in formSelect.company_tag" :key="index" :label="item.value" :value="item.value">
</el-option>
</el-select> -->
</div>
<div>
<el-button type="primary" size="small" @click="select.page=1,getList()"></el-button>
@ -79,7 +80,7 @@
</template>
</el-table-column>
</template>
<template v-slot:tag>
<!-- <template v-slot:tag>
<el-table-column align='center' label="集团标签" width="120" header-align="center">
<template slot-scope="scope">
<div v-if="scope.row.is_yh_invested" style="margin:3px">
@ -91,9 +92,9 @@
</div>
</template>
</el-table-column>
</template>
</template> -->
<template v-slot:users>
<el-table-column align='center' label="校友信息" width="800" header-align="center">
<el-table-column align='center' label="学员信息" width="800" header-align="center">
<el-table-column label="学号" width="120" align="center">
<template slot-scope="scope">
<div class="user-item" v-for="(item, uIdx) in (scope.row.users || [])" :key="uIdx">
@ -101,7 +102,7 @@
</div>
</template>
</el-table-column>
<el-table-column label="校友" width="120" align="left">
<el-table-column label="学员" width="120" align="left">
<template slot-scope="scope">
<div class="user-item" v-for="(item, uIdx) in (scope.row.users || [])" :key="uIdx">
{{ item.name || '' }}
@ -186,7 +187,7 @@
align: 'center',
width: 260
}, {
prop: 'tag',
prop: 'company_tag',
label: '集团标签',
align: 'center',
width: 160,
@ -222,7 +223,7 @@
width: 120,
}, {
prop: 'users',
label: '校友信息',
label: '学员信息',
align: 'center',
width: 240,
}]

@ -24,13 +24,14 @@
<el-option label="已通过" value="1"></el-option>
<el-option label="已拒绝" value="2"></el-option>
<el-option label="退回修改" value="3"></el-option>
<el-option label="永久隐藏" value="4"></el-option>
<el-option label="取消发布" value="4"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-select v-model="filters.type" placeholder="全部类型" clearable>
<el-option label="供应" value="1"></el-option>
<el-option label="需求" value="2"></el-option>
<el-option label="投融资" value="3"></el-option>
</el-select>
</el-form-item>
<el-form-item>
@ -63,7 +64,7 @@
<el-table-column label="类型" width="100">
<template slot-scope="scope">
<el-tag :type="getTypeTagType(scope.row.type)" size="small">
{{ scope.row.type === 1 ? '供应' : '需求' }}
{{ scope.row.type === 1 ? '供应' : scope.row.type === 2 ? '需求' : scope.row.type === 3 ? '投融资' : '' }}
</el-tag>
</template>
</el-table-column>
@ -109,7 +110,7 @@
<el-button type="primary" size="mini" icon="el-icon-edit" @click="handleEdit(scope.row)"></el-button>
<el-button v-if="scope.row.status === 0" type="danger" size="mini" icon="el-icon-close" @click="handleReject(scope.row)"></el-button>
<el-button v-if="scope.row.status === 0 || scope.row.status === 1" type="warning" size="mini" icon="el-icon-refresh" @click="handleReturnForRevision(scope.row)">退</el-button>
<el-button v-if="scope.row.status === 0 || scope.row.status === 1" type="info" size="mini" icon="el-icon-hide" @click="handlePermanentlyHide(scope.row)"></el-button>
<el-button v-if="scope.row.status === 0 || scope.row.status === 1" type="info" size="mini" icon="el-icon-hide" @click="handlePermanentlyHide(scope.row)"></el-button>
<el-button type="info" size="mini" icon="el-icon-view" @click="handleView(scope.row)"></el-button>
</div>
</template>
@ -229,7 +230,7 @@ export default {
1: 'success', //
2: 'danger', //
3: 'warning', // 退
4: 'info' //
4: 'info' //
}
return statusMap[status] || 'info'
},
@ -239,12 +240,12 @@ export default {
1: '已通过',
2: '已拒绝',
3: '退回修改',
4: '永久隐藏'
4: '取消发布'
}
return statusText[status] || '未知'
},
getTypeTagType(type) {
return type === 1 ? 'success' : 'primary'
return type === 1 ? 'success' : type === 2 ? 'primary' : type === 3 ? 'warning' : 'info'
},
async handleApprove(row) {
try {
@ -291,7 +292,7 @@ export default {
async handlePermanentlyHide(row) {
try {
await supplyDemandSave({ id: row.id, status: 4 })
this.$message.success('已永久隐藏')
this.$message.success('已取消发布')
this.getList()
} catch (e) {
this.$message.error('操作失败')

Loading…
Cancel
Save