lion 6 months ago
parent 1b02fd17d1
commit c9d7be803d

@ -0,0 +1,424 @@
<template>
<div class="admin-calendar">
<div class="admin-header">
<i class="el-icon-date"></i> 课程日历管理
</div>
<div class="admin-main">
<!-- 数据拉取区 -->
<div class="admin-panel" style="max-width: 350px;">
<div class="admin-panel-title"><i class="el-icon-download"></i> 数据拉取区</div>
<el-form label-width="90px" size="small">
<el-form-item label="数据类型">
<el-select v-model="dataType" @change="onTypeChange" placeholder="请选择类型">
<el-option label="课程" value="course" />
<el-option label="活动" value="activity" />
<el-option label="移动课堂" value="workshop" />
</el-select>
</el-form-item>
<el-form-item label="已有数据">
<el-select v-model="dataId" @change="onDataChange" placeholder="请选择数据">
<el-option v-for="item in dataList" :key="item.id" :label="item.title" :value="item.id" />
</el-select>
</el-form-item>
<div class="text-secondary" style="font-size:13px;min-height:20px;">{{ dataDateInfo }}</div>
<el-button type="primary" icon="el-icon-link" style="width:100%;margin-top:10px;"
@click="fetchData">拉取并关联</el-button>
</el-form>
<el-button type="success" icon="el-icon-plus" style="width:100%;margin-top:10px;"
@click="openCreateModal">新建日历事件</el-button>
</div>
<!-- 日历预览区 -->
<div class="admin-panel" style="min-width:0;flex:2;">
<div class="admin-panel-title"><i class="el-icon-view"></i> 日历预览区</div>
<el-calendar v-model="calendarDate">
<template slot="dateCell" slot-scope="{date}">
<div class="cell-content">
<span>{{ date.getDate() }}</span>
<div v-for="ev in eventsForDate(date)" :key="ev._id" class="event-dot" :title="ev.title"
@click.stop="showEvent(ev)"></div>
</div>
</template>
</el-calendar>
<div class="mt-4">
<h6 class="mb-2"><i class="el-icon-menu"></i> 当月事件列表</h6>
<el-empty v-if="monthEvents.length === 0" description="本月暂无事件" />
<el-timeline v-else>
<el-timeline-item v-for="(ev, idx) in monthEvents" :key="ev._id" :timestamp="ev.start | formatDateTime"
:color="isExpired(ev) ? '#F56C6C' : '#409EFF'">
<div>
<b>{{ ev.title }}</b>
<el-tag size="mini" style="margin-left:8px;">{{ typeText(ev.className) }}</el-tag>
<span style="font-size:13px;"> {{ ev.start | formatDateTime }} ~ {{ ev.end | formatDateTime }}</span>
<span v-if="ev.location" style="margin-left:8px;"><i class="el-icon-location-outline"></i>
{{ ev.location }}</span>
<el-tag v-if="ev.expire && ev.expire !== 'none'" size="mini" type="warning"
style="margin-left:8px;">到期后{{ expireText(ev.expire) }}</el-tag>
</div>
<div style="margin-top:4px;">
<el-button size="mini" type="primary" icon="el-icon-edit" @click="editEvent(idx)"></el-button>
</div>
</el-timeline-item>
</el-timeline>
</div>
</div>
</div>
<!-- 新建/编辑事件弹窗 -->
<el-dialog :title="modalTitle" :visible.sync="modalVisible" width="420px">
<el-form :model="modalForm" label-width="80px" size="small">
<el-form-item label="事件类型">
<el-select v-model="modalForm.type">
<el-option label="课程" value="course" />
<el-option label="活动" value="activity" />
<el-option label="移动课堂" value="workshop" />
</el-select>
</el-form-item>
<el-form-item label="标题">
<el-input v-model="modalForm.title" />
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker v-model="modalForm.start" type="datetime" value-format="yyyy-MM-ddTHH:mm" />
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker v-model="modalForm.end" type="datetime" value-format="yyyy-MM-ddTHH:mm" />
</el-form-item>
<el-form-item label="地点">
<el-input v-model="modalForm.location" />
</el-form-item>
<el-form-item label="主讲/负责人">
<el-input v-model="modalForm.teacher" />
</el-form-item>
<el-form-item label="描述">
<el-input type="textarea" v-model="modalForm.description" :rows="2" />
</el-form-item>
<el-form-item label="到期后操作">
<el-select v-model="modalForm.expire">
<el-option label="无" value="none" />
<el-option label="标记为已过期" value="mark" />
<el-option label="自动删除" value="delete" />
<el-option label="发送提醒" value="remind" />
</el-select>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="modalVisible=false"></el-button>
<el-button type="primary" @click="saveEvent"></el-button>
</div>
</el-dialog>
<!-- 事件详情弹窗 -->
<el-dialog title="事件详情" :visible.sync="eventDetailVisible" width="350px" :show-close="true">
<div v-if="eventDetail">
<b>{{ eventDetail.title }}</b>
<el-tag size="mini" style="margin-left:8px;">{{ typeText(eventDetail.className) }}</el-tag>
<div style="margin:8px 0 4px 0;">
<span><i class="el-icon-time"></i> {{ eventDetail.start | formatDateTime }} ~
{{ eventDetail.end | formatDateTime }}</span>
</div>
<div v-if="eventDetail.location"><i class="el-icon-location-outline"></i> {{ eventDetail.location }}</div>
<div v-if="eventDetail.teacher"><i class="el-icon-user"></i> {{ eventDetail.teacher }}</div>
<div v-if="eventDetail.description"><i class="el-icon-document"></i> {{ eventDetail.description }}</div>
<div v-if="eventDetail.expire && eventDetail.expire !== 'none'" style="margin-top:6px;">
<el-tag size="mini" type="warning">到期后{{ expireText(eventDetail.expire) }}</el-tag>
</div>
</div>
<div slot="footer">
<el-button @click="eventDetailVisible=false"></el-button>
</div>
</el-dialog>
</div>
</template>
<script>
let idSeed = 1000;
const mockData = {
course: [{
id: 101,
title: '2025产业加速营',
start: '2025-06-04T09:00',
end: '2025-06-04T17:00',
location: 'A101',
teacher: '王教授',
description: '智能制造专题'
},
{
id: 102,
title: '2025人工智能专题课程',
start: '2025-06-17T09:00',
end: '2025-06-19T17:00',
location: 'A201',
teacher: 'AI专家组',
description: 'AI理论与实操'
}
],
activity: [{
id: 201,
title: '校友企业参访',
start: '2025-06-10T13:30',
end: '2025-06-10T17:00',
location: '高新区',
teacher: '李总监',
description: '新能源企业参访'
},
{
id: 202,
title: '校友论坛',
start: '2025-06-20T14:00',
end: '2025-06-20T17:30',
location: '报告厅',
teacher: '特邀嘉宾',
description: '数字经济新机遇'
}
],
workshop: [{
id: 301,
title: '创新创业移动课堂',
start: '2025-06-08T09:00',
end: '2025-06-10T17:00',
location: 'C301',
teacher: '创业导师团',
description: '创新创业实战训练'
}]
};
export default {
data() {
return {
dataType: 'course',
dataId: '',
dataList: [],
dataDateInfo: '',
calendarDate: new Date(),
events: [],
modalVisible: false,
modalTitle: '新建事件',
modalForm: {
type: 'course',
title: '',
start: '',
end: '',
location: '',
teacher: '',
description: '',
expire: 'none'
},
editingIndex: null,
eventDetail: null,
eventDetailVisible: false
}
},
computed: {
monthEvents() {
const now = this.calendarDate instanceof Date ? this.calendarDate : new Date(this.calendarDate);
const month = now.getMonth();
const year = now.getFullYear();
return this.events.filter(ev => {
const evDate = new Date(ev.start);
return evDate.getMonth() === month && evDate.getFullYear() === year;
});
}
},
watch: {
dataType: 'onTypeChange'
},
created() {
this.onTypeChange();
},
methods: {
onTypeChange() {
this.dataList = mockData[this.dataType] || [];
this.dataId = '';
this.dataDateInfo = '';
},
onDataChange() {
const item = (this.dataList || []).find(d => d.id === this.dataId);
if (item) {
this.dataDateInfo = `时间:${item.start.replace('T',' ')} ~ ${item.end.replace('T',' ')}`;
} else {
this.dataDateInfo = '';
}
},
fetchData() {
if (!this.dataId) return this.$message.warning('请选择要拉取的数据');
const item = (this.dataList || []).find(d => d.id === this.dataId);
if (item) {
this.events.push({
_id: ++idSeed,
title: item.title,
start: item.start,
end: item.end,
location: item.location,
teacher: item.teacher,
description: item.description,
className: `event-${this.dataType}`,
expire: 'none'
});
}
},
openCreateModal() {
this.modalTitle = '新建事件';
this.modalForm = {
type: 'course',
title: '',
start: '',
end: '',
location: '',
teacher: '',
description: '',
expire: 'none'
};
this.editingIndex = null;
this.modalVisible = true;
},
saveEvent() {
if (!this.modalForm.title || !this.modalForm.start || !this.modalForm.end) {
this.$message.error('请填写完整信息');
return;
}
const eventObj = {
_id: this.editingIndex !== null ? this.events[this.editingIndex]._id : ++idSeed,
title: this.modalForm.title,
start: this.modalForm.start,
end: this.modalForm.end,
location: this.modalForm.location,
teacher: this.modalForm.teacher,
description: this.modalForm.description,
className: `event-${this.modalForm.type}`,
expire: this.modalForm.expire
};
if (this.editingIndex !== null) {
this.$set(this.events, this.editingIndex, eventObj);
} else {
this.events.push(eventObj);
}
this.modalVisible = false;
},
editEvent(idx) {
const ev = this.monthEvents[idx];
this.modalTitle = '编辑事件';
this.modalForm = {
type: ev.className.replace('event-', ''),
title: ev.title,
start: ev.start,
end: ev.end,
location: ev.location,
teacher: ev.teacher,
description: ev.description,
expire: ev.expire || 'none'
};
this.editingIndex = this.events.findIndex(e => e._id === ev._id);
this.modalVisible = true;
},
eventsForDate(date) {
const d = new Date(date);
return this.events.filter(ev => {
const evDate = new Date(ev.start);
return evDate.getFullYear() === d.getFullYear() && evDate.getMonth() === d.getMonth() && evDate
.getDate() === d.getDate();
});
},
showEvent(ev) {
this.eventDetail = ev;
this.eventDetailVisible = true;
},
isExpired(ev) {
return new Date(ev.end) < new Date() && ev.expire === 'mark';
},
typeText(className) {
if (!className) return '';
const t = className.replace('event-', '');
if (t === 'course') return '课程';
if (t === 'activity') return '活动';
if (t === 'workshop') return '移动课堂';
return t;
},
expireText(val) {
if (val === 'mark') return '标记为已过期';
if (val === 'delete') return '自动删除';
if (val === 'remind') return '发送提醒';
return '无';
}
},
filters: {
formatDateTime(val) {
if (!val) return '';
return val.replace('T', ' ');
}
}
}
</script>
<style scoped>
.admin-calendar {
background: #f4f6fa;
min-height: 100vh;
}
.admin-header {
background: #fff;
padding: 20px 30px 10px 30px;
border-bottom: 1px solid #e9ecef;
font-size: 22px;
font-weight: 700;
color: #2c3e50;
letter-spacing: 2px;
}
.admin-main {
display: flex;
gap: 24px;
padding: 30px 30px 0 30px;
min-height: 90vh;
}
.admin-panel {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
padding: 24px 18px;
flex: 1 1 0;
min-width: 320px;
display: flex;
flex-direction: column;
gap: 18px;
}
.admin-panel-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 10px;
color: #1565c0;
}
.cell-content {
position: relative;
min-height: 24px;
}
.event-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #409EFF;
display: inline-block;
margin-right: 2px;
cursor: pointer;
}
.mt-4 {
margin-top: 24px;
}
.mb-2 {
margin-bottom: 8px;
}
.text-secondary {
color: #888;
}
@media (max-width: 1200px) {
.admin-main {
flex-direction: column;
gap: 18px;
padding: 18px 6px 0 6px;
}
}
</style>

@ -195,6 +195,19 @@
</div>
</template>
</el-table-column>
</template>
<template v-slot:headimgurl>
<el-table-column align='center' label="头像" width="100" header-align="center">
<template slot-scope="scope">
<el-image
v-if="scope.row.headimgurl"
style="width: 50px; height: 50px;border-radius: 100%;"
:src="scope.row.headimgurl"
:preview-src-list="[scope.row.headimgurl]">
</el-image>
</template>
</el-table-column>
</template>
<template v-slot:mobile>
<el-table-column align='center' label="联系方式" width="120" header-align="center">
@ -345,7 +358,17 @@
label: '性别',
align: 'center',
width: 120
}, {
}, {
prop: 'no',
label: '学号',
align: 'center',
width: 180
},{
prop: 'headimgurl',
label: '头像',
align: 'center',
width: 100
},{
prop: 'idcard',
label: '身份证号',
align: 'center',

@ -201,6 +201,18 @@
</div>
</template>
</el-table-column>
</template>
<template v-slot:headimgurl>
<el-table-column align='center' label="头像" width="100" header-align="center">
<template slot-scope="scope">
<el-image
v-if="scope.row.headimgurl"
style="width: 50px; height: 50px;border-radius: 100%;"
:src="scope.row.headimgurl"
:preview-src-list="[scope.row.headimgurl]">
</el-image>
</template>
</el-table-column>
</template>
<template v-slot:mobile>
<el-table-column align='center' label="联系方式" width="120" header-align="center">
@ -337,6 +349,16 @@
label: '性别',
align: 'center',
width: 120
}, {
prop: 'no',
label: '学号',
align: 'center',
width: 180
},{
prop: 'headimgurl',
label: '头像',
align: 'center',
width: 100
}, {
prop: 'idcard',
label: '身份证号',

@ -0,0 +1,133 @@
<template>
<el-dialog :visible.sync="visible" fullscreen :show-close="false" class="survey-dialog">
<div class="designer-header">
<div class="header-content">
<el-input v-model="localSurvey.title" class="survey-title-input" placeholder="问卷标题" />
<div class="header-actions">
<el-button type="primary" @click="handleSave"></el-button>
<el-button @click="$emit('close')"></el-button>
</div>
</div>
</div>
<div class="designer-main">
<div class="question-palette">
<div class="palette-title">题型库</div>
<div class="question-types">
<el-button v-for="types in questionTypes" :key="types.value" @click="addQuestion(types.value)">{{ types.label }}</el-button>
</div>
</div>
<div class="design-area">
<div class="survey-form">
<el-input type="textarea" v-model="localSurvey.description" class="survey-description" placeholder="问卷描述" />
<div class="questions-container">
<draggable v-model="localSurvey.questions" handle=".drag-handle" :animation="200">
<transition-group>
<div v-for="(q, idx) in localSurvey.questions" :key="q.id" class="question-item">
<div class="question-header">
<span class="question-number">{{ idx+1 }}</span>
<el-input v-model="q.title" placeholder="题目标题" style="flex:1;" />
<el-select v-model="q.type" style="width:120px;">
<el-option v-for="types in questionTypes" :key="types.value" :label="types.label" :value="types.value" />
</el-select>
<i class="el-icon-rank drag-handle" style="cursor:move;margin:0 8px;" title="拖动排序"></i>
<el-button type="danger" icon="el-icon-delete" size="mini" @click="removeQuestion(idx)"></el-button>
</div>
<div style="padding:16px 24px;">
<template v-if="q.type==='single' || q.type==='multi'">
<div v-for="(opt, oidx) in q.options" :key="oidx" style="display:flex;align-items:center;margin-bottom:8px;">
<el-input v-model="q.options[oidx]" placeholder="选项内容" style="flex:1;" />
<el-button icon="el-icon-delete" size="mini" @click="removeOption(q, oidx)" style="margin-left:4px;" v-if="q.options.length>1"></el-button>
</div>
<el-button type="primary" size="mini" @click="addOption(q)"></el-button>
</template>
<template v-else-if="q.type==='text'">
<el-input type="textarea" disabled placeholder="文本题,用户可填写内容" />
</template>
<template v-else-if="q.type==='rate'">
<el-rate v-model="q.rateMax" :max="10" show-text text-color="#ff9900" disabled />
<div style="font-size:12px;color:#888;">评分题用户可打分</div>
</template>
</div>
</div>
</transition-group>
</draggable>
<el-empty v-if="!localSurvey.questions.length" description="请从左侧添加题目" />
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'SurveyCreateDialog',
components: { draggable },
props: {
visible: Boolean,
surveyData: Object
},
data() {
return {
localSurvey: {
title: '',
description: '',
questions: []
},
questionTypes: [
{ label: '单选题', value: 'single' },
{ label: '多选题', value: 'multi' },
{ label: '文本题', value: 'text' },
{ label: '评分题', value: 'rate' }
]
}
},
watch: {
surveyData: {
handler(val) {
this.localSurvey = val ? JSON.parse(JSON.stringify(val)) : { title: '', description: '', questions: [] };
},
immediate: true
},
visible(val) {
if (!val) this.$emit('close');
}
},
methods: {
addQuestion(type) {
const q = { id: Date.now()+Math.random(), title: '', type, options: type==='text'||type==='rate'?[]:['选项1','选项2'], rateMax: 5 };
this.localSurvey.questions.push(q);
},
removeQuestion(idx) {
this.localSurvey.questions.splice(idx,1);
},
addOption(q) {
q.options.push('新选项');
},
removeOption(q, oidx) {
q.options.splice(oidx,1);
},
handleSave() {
this.$emit('save', JSON.parse(JSON.stringify(this.localSurvey)));
}
}
}
</script>
<style scoped>
.survey-dialog >>> .el-dialog__body { padding:0; }
.designer-header { background: #fff; border-bottom: 1px solid #e9ecef; }
.header-content { display: flex; justify-content: space-between; align-items: center; max-width: 1400px; margin: 0 auto; }
.survey-title-input { font-size: 20px; font-weight: 600; border: none; background: transparent; color: #2c3e50; min-width: 300px; }
.designer-main { display: flex; height: calc(100vh - 80px); }
.question-palette { width: 280px; background: #fff; border-right: 1px solid #e9ecef; padding: 20px; overflow-y: auto; }
.palette-title { font-size: 16px; font-weight: 600; margin-bottom: 16px; color: #2c3e50; }
.question-types { display: flex; flex-direction: column; gap: 8px; }
.design-area { flex: 1; padding: 30px; overflow-y: auto; background: #f8f9fa; }
.survey-form { max-width: 800px; margin: 0 auto; background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 30px; min-height: 400px; }
.survey-description { width: 100%; border: none; resize: vertical; min-height: 60px; font-size: 14px; color: #6c757d; background: transparent; }
.questions-container { min-height: 200px; }
.question-item { background: #f8f9fa; border: 1px solid #e9ecef; border-radius: 8px; margin-bottom: 20px; position: relative; transition: all 0.3s ease; }
.question-header { display: flex; justify-content: space-between; align-items: center; padding: 15px 20px; background: #fff; border-bottom: 1px solid #e9ecef; border-radius: 8px 8px 0 0; }
.question-number { background: #3498db; color: #fff; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: 600; margin-right: 8px; }
.drag-handle { cursor: move; }
</style>

@ -0,0 +1,231 @@
<template>
<el-dialog :visible.sync="visible" width="60%" :show-close="false" class="survey-dialog-mobile">
<div class="survey-mobile-bg">
<div class="survey-mobile-card">
<div class="survey-header">
<div class="survey-title">{{ surveyData.title }}</div>
<div class="survey-description">{{ surveyData.description }}</div>
<div class="survey-meta">
<span class="meta-item"><i class="el-icon-date"></i> {{ surveyData.createTime }}</span>
<span class="meta-item"><i class="el-icon-time"></i> {{ surveyData.deadline }}</span>
</div>
</div>
<div class="survey-form">
<div v-for="(q, idx) in surveyData.questions" :key="q.id" class="question-item">
<div class="question-header">
<span class="question-number">{{ idx+1 }}</span>
<span class="question-title">{{ q.title }}</span>
<span v-if="q.required" class="required-mark">*</span>
</div>
<div class="question-content">
<template v-if="q.type==='single'">
<el-radio-group v-model="answers[q.id]" :disabled="previewOnly" class="mobile-radio-group">
<el-radio v-for="(opt, oidx) in q.options" :key="oidx" :label="opt" class="mobile-radio">{{ opt }}</el-radio>
</el-radio-group>
</template>
<template v-else-if="q.type==='multi'">
<el-checkbox-group v-model="answers[q.id]" :disabled="previewOnly" class="mobile-checkbox-group">
<el-checkbox v-for="(opt, oidx) in q.options" :key="oidx" :label="opt" class="mobile-checkbox">{{ opt }}</el-checkbox>
</el-checkbox-group>
</template>
<template v-else-if="q.type==='text'">
<el-input type="textarea" v-model="answers[q.id]" :disabled="previewOnly" :placeholder="q.placeholder||'请输入内容'" class="mobile-textarea" />
</template>
<template v-else-if="q.type==='rate'">
<el-rate v-model="answers[q.id]" :max="q.rateMax||5" :disabled="previewOnly" class="mobile-rate" />
</template>
<template v-else-if="q.type==='number'">
<el-input type="number" v-model="answers[q.id]" :min="q.min" :max="q.max" :disabled="previewOnly" class="mobile-input" />
</template>
<template v-else-if="q.type==='select'">
<el-select v-model="answers[q.id]" :disabled="previewOnly" class="mobile-select" placeholder="请选择">
<el-option v-for="(opt, oidx) in q.options" :key="oidx" :label="opt" :value="opt" />
</el-select>
</template>
<template v-else-if="q.type==='scale'">
<div class="scale-container">
<span class="scale-label">{{ q.minLabel }}</span>
<el-slider v-model="answers[q.id]" :min="q.min" :max="q.max" :disabled="previewOnly" class="mobile-slider" />
<span class="scale-label">{{ q.maxLabel }}</span>
</div>
</template>
</div>
</div>
<div style="text-align:center;margin-top:32px;">
<el-button v-if="!previewOnly" type="primary" class="mobile-btn" @click="handleSubmit"></el-button>
<el-button class="mobile-btn" @click="$emit('close')"></el-button>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'SurveyFillDialog',
props: {
visible: Boolean,
surveyData: Object,
previewOnly: Boolean
},
data() {
return {
answers: {}
}
},
watch: {
surveyData: {
handler(val) {
if (val && val.questions) {
this.answers = {};
val.questions.forEach(q => {
if (q.type==='multi') this.answers[q.id] = [];
else this.answers[q.id] = '';
});
}
},
immediate: true
}
},
methods: {
handleSubmit() {
this.$emit('submit', this.answers);
}
}
}
</script>
<style scoped>
.survey-dialog-mobile >>> .el-dialog__body { padding:0; background: #f4f6fa; }
.survey-mobile-bg {
min-height: 100vh;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
}
.survey-mobile-card {
width: 100vw;
max-width: 414px;
min-width: 320px;
background: #fff;
border-radius: 18px;
box-shadow: 0 4px 24px rgba(0,0,0,0.12);
margin: 24px 0;
padding: 0 0 24px 0;
overflow: hidden;
}
.survey-header {
padding: 32px 20px 16px 20px;
text-align: center;
background: #fff;
border-radius: 18px 18px 0 0;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
}
.survey-title {
font-size: 22px;
font-weight: 700;
color: #2c3e50;
margin-bottom: 10px;
}
.survey-description {
font-size: 15px;
color: #6c757d;
margin-bottom: 10px;
}
.survey-meta {
display: flex;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
font-size: 12px;
color: #888;
}
.meta-item { display: flex; align-items: center; gap: 4px; }
.survey-form {
padding: 18px 16px 0 16px;
}
.question-item {
margin-bottom: 28px;
padding-bottom: 18px;
border-bottom: 1px solid #f0f0f0;
}
.question-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.question-number {
background: #3498db;
color: #fff;
width: 22px;
height: 22px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 600;
flex-shrink: 0;
}
.question-title {
font-size: 15px;
font-weight: 600;
color: #2c3e50;
flex: 1;
}
.required-mark {
color: #e74c3c;
margin-left: 4px;
font-size: 15px;
}
.question-content {
margin-left: 0;
margin-top: 2px;
}
.mobile-radio-group, .mobile-checkbox-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.mobile-radio, .mobile-checkbox {
font-size: 15px;
height: 32px;
line-height: 32px;
}
.mobile-textarea {
font-size: 15px;
border-radius: 10px;
}
.mobile-rate {
font-size: 22px;
}
.mobile-input, .mobile-select {
font-size: 15px;
border-radius: 10px;
width: 100%;
}
.scale-container {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.scale-label {
font-size: 13px;
color: #888;
}
.mobile-slider {
flex: 1;
}
.mobile-btn {
width: 90%;
height: 44px;
font-size: 17px;
border-radius: 22px;
margin-bottom: 10px;
}
@media (max-width: 500px) {
.survey-mobile-card { max-width: 100vw; min-width: 0; border-radius: 0; }
}
</style>

@ -0,0 +1,90 @@
<template>
<el-dialog :visible.sync="visible" fullscreen :show-close="false" class="survey-dialog">
<div class="results-header">
<div class="header-content">
<div>
<div class="survey-title">{{ surveyData.title }}</div>
<div class="survey-meta">
<span class="meta-item"><i class="el-icon-date"></i> 创建时间{{ surveyData.createTime }}</span>
<span class="meta-item"><i class="el-icon-time"></i> 截止时间{{ surveyData.deadline }}</span>
</div>
</div>
<el-button @click="$emit('close')"></el-button>
</div>
</div>
<div class="results-main">
<div class="stats-overview">
<div class="stat-card">
<div class="stat-icon" style="background:#3498db"><i class="el-icon-user"></i></div>
<div class="stat-number">{{ surveyData.responses }}</div>
<div class="stat-label">回复数</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background:#f1c40f"><i class="el-icon-edit"></i></div>
<div class="stat-number">{{ surveyData.questions.length }}</div>
<div class="stat-label">题目数</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background:#2ecc71"><i class="el-icon-star-on"></i></div>
<div class="stat-number">{{ surveyData.avgScore || '-' }}</div>
<div class="stat-label">平均分</div>
</div>
</div>
<div v-for="(q, idx) in surveyData.questions" :key="q.id" class="question-analysis">
<div class="question-header">
<div>
<div class="question-title">{{ idx+1 }}. {{ q.title }}</div>
<div class="question-meta">
<span>题型{{ typeText(q.type) }}</span>
</div>
</div>
</div>
<div v-if="q.type==='single' || q.type==='multi'">
<el-table :data="q.options.map((opt,i)=>({option:opt,count:Math.floor(Math.random()*20+1)}))" style="width: 100%;">
<el-table-column prop="option" label="选项" />
<el-table-column prop="count" label="选择人数" />
</el-table>
</div>
<div v-else-if="q.type==='rate'">
<el-rate v-model="q.rateMax" :max="10" show-text text-color="#ff9900" disabled />
<div style="font-size:12px;color:#888;">评分题用户可打分</div>
</div>
<div v-else-if="q.type==='text'">
<el-empty description="文本题答案统计略" />
</div>
</div>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'SurveyResultsDialog',
props: {
visible: Boolean,
surveyData: Object
},
methods: {
typeText(type) {
return { single: '单选题', multi: '多选题', text: '文本题', rate: '评分题' }[type] || type;
}
}
}
</script>
<style scoped>
.survey-dialog >>> .el-dialog__body { padding:0; }
.results-header { background: #fff; padding: 30px; border-bottom: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.header-content { max-width: 1400px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; }
.survey-title { font-size: 28px; font-weight: 700; color: #2c3e50; margin-bottom: 12px; }
.survey-meta { display: flex; gap: 32px; flex-wrap: wrap; margin-bottom: 20px; }
.meta-item { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #6c757d; }
.results-main { max-width: 1400px; margin: 0 auto; padding: 30px; }
.stats-overview { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-bottom: 30px; }
.stat-card { background: #fff; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); text-align: center; border: 1px solid #e9ecef; }
.stat-icon { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; font-size: 20px; color: #fff; }
.stat-number { font-size: 32px; font-weight: 700; color: #2c3e50; margin-bottom: 4px; }
.stat-label { font-size: 14px; color: #6c757d; }
.question-analysis { background: #fff; border-radius: 12px; padding: 30px; margin-bottom: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.question-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid #e9ecef; }
.question-title { font-size: 18px; font-weight: 600; color: #2c3e50; margin-bottom: 8px; }
.question-meta { display: flex; gap: 16px; font-size: 14px; color: #6c757d; }
</style>

@ -0,0 +1,575 @@
<template>
<div class="survey-admin">
<!-- 顶部标题 -->
<div class="admin-header">
<h1 class="admin-title">
<i class="el-icon-document"></i>
调查问卷管理
</h1>
</div>
<!-- 主要内容区域 -->
<div class="admin-main">
<!-- 操作栏 -->
<div class="action-bar">
<div class="search-box">
<el-input v-model="searchTerm" placeholder="搜索问卷..." style="width:250px;" @input="filterSurveys" clearable />
<el-select v-model="statusFilter" placeholder="全部状态" style="width:150px;" @change="filterSurveys" clearable>
<el-option label="全部状态" value="" />
<el-option label="草稿" value="draft" />
<el-option label="已发布" value="published" />
<el-option label="已关闭" value="closed" />
</el-select>
<el-select v-model="courseFilter" placeholder="全部课程" style="width:200px;" @change="filterSurveys" clearable>
<el-option label="全部课程" value="" />
<el-option label="课程" value="course" />
<el-option label="活动" value="activity" />
<el-option label="移动课堂" value="workshop" />
</el-select>
</div>
<el-button type="primary" icon="el-icon-plus" @click="openCreateModal"></el-button>
</div>
<!-- 问卷列表 -->
<div class="survey-grid">
<template v-if="filteredSurveys.length">
<div v-for="survey in filteredSurveys" :key="survey.id" class="survey-card">
<div class="survey-header">
<div>
<div class="survey-title">{{ survey.title }}</div>
<span :class="['status-badge', statusClass(survey.status)]">{{ statusText(survey.status) }}</span>
</div>
<el-dropdown trigger="click">
<el-button size="mini" icon="el-icon-more"></el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="editSurvey(survey)"><i class="el-icon-edit"></i> 编辑</el-dropdown-item>
<el-dropdown-item @click.native="viewResults(survey)"><i class="el-icon-data-analysis"></i> 查看结果</el-dropdown-item>
<el-dropdown-item @click.native="duplicateSurvey(survey.id)"><i class="el-icon-document-copy"></i> 复制</el-dropdown-item>
<el-dropdown-item divided @click.native="deleteSurvey(survey.id)" style="color:#F56C6C"><i class="el-icon-delete"></i> 删除</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="survey-meta">
<div class="meta-item"><i class="el-icon-date"></i> 创建时间{{ survey.createTime }}</div>
<div class="meta-item"><i class="el-icon-time"></i> 截止时间{{ survey.deadline }}</div>
<div class="meta-item" v-if="survey.bindCourse">
<i class="el-icon-link"></i>
<span class="course-tag">{{ survey.bindCourse }}</span>
</div>
</div>
<div class="survey-stats">
<div class="stat-item">
<div class="stat-number">{{ survey.responses }}</div>
<div class="stat-label">回复数</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ survey.questions.length }}</div>
<div class="stat-label">题目数</div>
</div>
<div class="stat-item">
<div class="stat-number">{{ survey.avgScore || '-' }}</div>
<div class="stat-label">平均分</div>
</div>
</div>
<div class="survey-actions">
<el-button v-if="survey.status==='draft'" type="success" size="mini" @click="publishSurvey(survey.id)"><i class="el-icon-upload2"></i> </el-button>
<el-button type="primary" size="mini" @click="previewSurvey(survey)"><i class="el-icon-view"></i> 预览</el-button>
<el-button type="info" size="mini" @click="shareSurvey(survey.id)"><i class="el-icon-share"></i> 分享</el-button>
<el-button v-if="survey.responses>0" type="success" size="mini" @click="exportData(survey.id)"><i class="el-icon-download"></i> </el-button>
</div>
</div>
</template>
<el-empty v-else description="暂无问卷,点击'创建问卷'开始制作您的第一个调查问卷" />
</div>
</div>
<!-- 创建问卷弹窗 -->
<el-dialog title="创建新问卷" :visible.sync="createModalVisible" width="600px">
<el-form :model="createForm" label-width="90px">
<el-form-item label="问卷标题" required>
<el-input v-model="createForm.title" />
</el-form-item>
<el-form-item label="问卷描述">
<el-input type="textarea" v-model="createForm.description" :rows="3" />
</el-form-item>
<el-form-item label="绑定类型">
<el-select v-model="createForm.bindType" @change="onBindTypeChange">
<el-option label="无绑定" value="" />
<el-option label="课程" value="course" />
<el-option label="活动" value="activity" />
<el-option label="移动课堂" value="workshop" />
</el-select>
</el-form-item>
<el-form-item label="选择课程/活动">
<el-select v-model="createForm.bindCourse" :disabled="!createForm.bindType">
<el-option v-for="item in bindCourseList" :key="item" :label="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item label="问卷类型">
<el-select v-model="createForm.type">
<el-option label="课后反馈" value="feedback" />
<el-option label="课前调研" value="pre-survey" />
<el-option label="课程评价" value="evaluation" />
<el-option label="满意度调查" value="satisfaction" />
<el-option label="自定义" value="custom" />
</el-select>
</el-form-item>
<el-form-item label="截止时间">
<el-date-picker v-model="createForm.deadline" type="datetime" value-format="yyyy-MM-dd HH:mm" />
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="createModalVisible=false"></el-button>
<el-button type="primary" @click="saveSurvey"></el-button>
</div>
</el-dialog>
<!-- 问卷编辑全屏弹窗 -->
<SurveyCreateDialog
:visible.sync="editDialogVisible"
:surveyData="editSurveyData"
@close="editDialogVisible=false"
@save="onEditSurveySave"
/>
<!-- 问卷结果全屏弹窗 -->
<SurveyResultsDialog
:visible.sync="resultsDialogVisible"
:surveyData="resultsSurveyData"
@close="resultsDialogVisible=false"
/>
<!-- 问卷填写/预览全屏弹窗 -->
<SurveyFillDialog
:visible.sync="fillDialogVisible"
:surveyData="fillSurveyData"
:previewOnly="fillPreviewOnly"
@close="fillDialogVisible=false"
@submit="onFillSubmit"
/>
</div>
</template>
<script>
import SurveyCreateDialog from './components/SurveyCreateDialog.vue';
import SurveyResultsDialog from './components/SurveyResultsDialog.vue';
import SurveyFillDialog from './components/SurveyFillDialog.vue';
const courses = {
course: [
'2025产业加速营 | 智能制造专题',
'2025暑期创新营 | 项目路演'
],
activity: [
'校友企业参访 | 新能源企业',
'2025校友论坛 | 数字经济新机遇'
],
workshop: [
'移动课堂 | AI商业落地',
'创新创业移动课堂'
]
};
export default {
components: {
SurveyCreateDialog,
SurveyResultsDialog,
SurveyFillDialog
},
data() {
return {
surveys: [
{
id: 1,
title: '2025产业加速营课程反馈调查',
description: '请对本次智能制造专题课程进行评价和反馈',
status: 'published',
type: 'feedback',
bindType: 'course',
bindCourse: '2025产业加速营 | 智能制造专题',
createTime: '2025-06-01 10:00',
deadline: '2025-06-10 23:59',
responses: 45,
questions: 8,
avgScore: 4.6,
questions: [
{
id: 1,
type: 'single',
title: '你最喜欢的编程语言是?',
options: ['JavaScript', 'Python', 'Java', 'C++'],
required: true
},
{
id: 2,
type: 'multi',
title: '你常用哪些开发工具?',
options: ['VSCode', 'WebStorm', 'Sublime Text', 'Vim'],
required: false
},
{
id: 3,
type: 'text',
title: '请简要描述你对本课程的建议',
placeholder: '请输入你的建议',
required: false
},
{
id: 4,
type: 'rate',
title: '请为本次活动打分',
rateMax: 5,
required: true
},
{
id: 5,
type: 'number',
title: '你每天编程多少小时?',
min: 0,
max: 24,
required: false
},
{
id: 6,
type: 'select',
title: '请选择你的学历',
options: ['本科', '硕士', '博士', '其他'],
required: true
},
{
id: 7,
type: 'scale',
title: '你对本课程的满意度',
minLabel: '非常不满意',
maxLabel: '非常满意',
min: 1,
max: 5,
required: true
}
]
},
{
id: 2,
title: '校友企业参访前期调研',
description: '了解参访需求和期望,优化参访安排',
status: 'published',
type: 'pre-survey',
bindType: 'activity',
bindCourse: '校友企业参访 | 新能源企业',
createTime: '2025-06-05 14:30',
deadline: '2025-06-09 18:00',
responses: 23,
questions: 6,
avgScore: 0,
questions: [
{
id: 1,
type: 'single',
title: '你最喜欢的编程语言是?',
options: ['JavaScript', 'Python', 'Java', 'C++'],
required: true
},
{
id: 2,
type: 'multi',
title: '你常用哪些开发工具?',
options: ['VSCode', 'WebStorm', 'Sublime Text', 'Vim'],
required: false
},
{
id: 3,
type: 'text',
title: '请简要描述你对本课程的建议',
placeholder: '请输入你的建议',
required: false
},
{
id: 4,
type: 'rate',
title: '请为本次活动打分',
rateMax: 5,
required: true
},
{
id: 5,
type: 'number',
title: '你每天编程多少小时?',
min: 0,
max: 24,
required: false
},
{
id: 6,
type: 'select',
title: '请选择你的学历',
options: ['本科', '硕士', '博士', '其他'],
required: true
},
{
id: 7,
type: 'scale',
title: '你对本课程的满意度',
minLabel: '非常不满意',
maxLabel: '非常满意',
min: 1,
max: 5,
required: true
}
]
},
{
id: 3,
title: 'AI商业落地移动课堂满意度调查',
description: '课程满意度和改进建议收集',
status: 'draft',
type: 'satisfaction',
bindType: 'workshop',
bindCourse: '移动课堂 | AI商业落地',
createTime: '2025-06-12 09:15',
deadline: '2025-06-20 23:59',
responses: 0,
questions: 12,
avgScore: 0,
questions: [
{
id: 1,
type: 'single',
title: '你最喜欢的编程语言是?',
options: ['JavaScript', 'Python', 'Java', 'C++'],
required: true
},
{
id: 2,
type: 'multi',
title: '你常用哪些开发工具?',
options: ['VSCode', 'WebStorm', 'Sublime Text', 'Vim'],
required: false
},
{
id: 3,
type: 'text',
title: '请简要描述你对本课程的建议',
placeholder: '请输入你的建议',
required: false
},
{
id: 4,
type: 'rate',
title: '请为本次活动打分',
rateMax: 5,
required: true
},
{
id: 5,
type: 'number',
title: '你每天编程多少小时?',
min: 0,
max: 24,
required: false
},
{
id: 6,
type: 'select',
title: '请选择你的学历',
options: ['本科', '硕士', '博士', '其他'],
required: true
},
{
id: 7,
type: 'scale',
title: '你对本课程的满意度',
minLabel: '非常不满意',
maxLabel: '非常满意',
min: 1,
max: 5,
required: true
}
]
}
],
searchTerm: '',
statusFilter: '',
courseFilter: '',
filteredSurveys: [],
createModalVisible: false,
createForm: {
title: '',
description: '',
bindType: '',
bindCourse: '',
type: 'feedback',
deadline: ''
},
bindCourseList: [],
editDialogVisible: false,
editSurveyData: null,
resultsDialogVisible: false,
resultsSurveyData: null,
fillDialogVisible: false,
fillSurveyData: null,
fillPreviewOnly: false
}
},
created() {
this.filterSurveys();
},
methods: {
statusText(status) {
return { draft: '草稿', published: '已发布', closed: '已关闭' }[status] || status;
},
statusClass(status) {
return {
draft: 'status-draft',
published: 'status-published',
closed: 'status-closed'
}[status] || '';
},
filterSurveys() {
const search = (this.searchTerm || '').toLowerCase();
this.filteredSurveys = this.surveys.filter(survey => {
const matchSearch = survey.title.toLowerCase().includes(search) || (survey.description && survey.description.toLowerCase().includes(search));
const matchStatus = !this.statusFilter || survey.status === this.statusFilter;
const matchCourse = !this.courseFilter || survey.bindType === this.courseFilter;
return matchSearch && matchStatus && matchCourse;
});
},
openCreateModal() {
this.createModalVisible = true;
this.createForm = {
title: '',
description: '',
bindType: '',
bindCourse: '',
type: 'feedback',
deadline: ''
};
this.bindCourseList = [];
},
onBindTypeChange() {
this.createForm.bindCourse = '';
this.bindCourseList = this.createForm.bindType ? courses[this.createForm.bindType] : [];
},
saveSurvey() {
if (!this.createForm.title) {
this.$message.error('请输入问卷标题');
return;
}
const newSurvey = {
id: this.surveys.length + 1,
title: this.createForm.title,
description: this.createForm.description,
status: 'draft',
type: this.createForm.type,
bindType: this.createForm.bindType,
bindCourse: this.createForm.bindCourse,
createTime: this.formatNow(),
deadline: this.createForm.deadline,
responses: 0,
questions: 0,
avgScore: 0
};
this.surveys.unshift(newSurvey);
this.createModalVisible = false;
this.filterSurveys();
//
setTimeout(() => {
window.open(`survey-create.html?id=${newSurvey.id}`, '_blank');
}, 500);
},
editSurvey(survey) {
this.editSurveyData = survey;
this.editDialogVisible = true;
},
onEditSurveySave(newSurvey) {
const idx = this.surveys.findIndex(s => s.id === newSurvey.id);
if (idx !== -1) {
this.$set(this.surveys, idx, newSurvey);
}
this.editDialogVisible = false;
this.filterSurveys();
},
viewResults(survey) {
this.resultsSurveyData = survey;
this.resultsDialogVisible = true;
},
previewSurvey(survey) {
this.fillSurveyData = survey;
this.fillPreviewOnly = false;
this.fillDialogVisible = true;
},
onFillSubmit(answers) {
this.$message.success('提交成功!(模拟)');
this.fillDialogVisible = false;
},
publishSurvey(id) {
const survey = this.surveys.find(s => s.id === id);
if (survey) {
survey.status = 'published';
this.filterSurveys();
this.$message.success('问卷发布成功!');
}
},
shareSurvey(id) {
const url = `${window.location.origin}/survey-fill.html?id=${id}`;
if (navigator.clipboard) {
navigator.clipboard.writeText(url).then(() => {
this.$message.success('问卷链接已复制到剪贴板');
}).catch(() => {
this.$prompt('请复制以下链接:', '分享问卷', { inputValue: url });
});
} else {
this.$prompt('请复制以下链接:', '分享问卷', { inputValue: url });
}
},
exportData(id) {
this.$message.info('数据导出功能开发中...');
},
duplicateSurvey(id) {
const survey = this.surveys.find(s => s.id === id);
if (survey) {
const newSurvey = {
...survey,
id: this.surveys.length + 1,
title: survey.title + ' (副本)',
status: 'draft',
createTime: this.formatNow(),
responses: 0
};
this.surveys.unshift(newSurvey);
this.filterSurveys();
}
},
deleteSurvey(id) {
this.$confirm('确定要删除这个问卷吗?此操作不可恢复。', '提示', {
type: 'warning'
}).then(() => {
this.surveys = this.surveys.filter(s => s.id !== id);
this.filterSurveys();
});
},
formatNow() {
const d = new Date();
return `${d.getFullYear()}-${(d.getMonth()+1).toString().padStart(2,'0')}-${d.getDate().toString().padStart(2,'0')} ${d.getHours().toString().padStart(2,'0')}:${d.getMinutes().toString().padStart(2,'0')}`;
}
}
}
</script>
<style scoped>
.survey-admin { background: #f8f9fa; min-height: 100vh; }
.admin-header { background: white; padding: 20px 30px; border-bottom: 1px solid #e9ecef; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.admin-title { font-size: 24px; font-weight: 700; color: #2c3e50; margin: 0; display: flex; align-items: center; gap: 12px; }
.admin-main { padding: 30px; max-width: 1400px; margin: 0 auto; }
.action-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; flex-wrap: wrap; gap: 16px; }
.search-box { display: flex; gap: 12px; align-items: center; }
.survey-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 24px; }
.survey-card { background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 24px; transition: all 0.3s ease; border: 1px solid #e9ecef; }
.survey-card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.15); }
.survey-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
.survey-title { font-size: 18px; font-weight: 600; color: #2c3e50; margin-bottom: 8px; line-height: 1.4; }
.survey-meta { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.meta-item { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #6c757d; }
.survey-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px; }
.stat-item { text-align: center; padding: 12px; background: #f8f9fa; border-radius: 8px; }
.stat-number { font-size: 20px; font-weight: 700; color: #3498db; }
.stat-label { font-size: 12px; color: #6c757d; margin-top: 4px; }
.survey-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.status-badge { padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: 500; }
.status-draft { background: #f8f9fa; color: #6c757d; }
.status-published { background: #d4edda; color: #155724; }
.status-closed { background: #f8d7da; color: #721c24; }
.course-tag { display: inline-block; background: #e3f2fd; color: #1976d2; padding: 4px 8px; border-radius: 4px; font-size: 12px; margin-right: 8px; margin-bottom: 4px; }
</style>

@ -23,9 +23,11 @@ module.exports = {
* then publicPath should be set to "/bar/".
* In most cases please use '/' !!!
* Detail: https://cli.vuejs.org/config/#publicpath
* /Users/mac/Documents//2024/s-/wx.sstbc.com/public/admin
*
*/
publicPath: process.env.ENV === 'staging' ? '/admin_test' : '/admin',
outputDir: '/Users/mac/Documents/朗业/2024/s-苏州科技商学院/suzhoukeji/public/admin',
publicPath: process.env.ENV === 'staging' ? '/admin' : '/admin',
outputDir: '/Users/mac/Documents/朗业/2025/s-苏州科技商学院/wx.sstbc.com/public/admin',
assetsDir: 'static',
css: {
loaderOptions: { // 向 CSS 相关的 loader 传递选项

Loading…
Cancel
Save