Bladeren bron

Merge branch 'master' of http://git.derkj.com:9095/waibao/NN2025081602

* 'master' of http://git.derkj.com:9095/waibao/NN2025081602:
  修复问题
  10.28 按照文档修复内容: 1. 专升本、对口类科目专属属性attr_type 1: "文化基础", 2: '19专业大类' 2. 单招类提醒科目专属属性attr_type 3: '职业适应性测试视频课' 3. 修复高校管理,编辑新增数据问题 4. 智能回答管理增加下载模板功能 10.29 文档修复内容 1. 修改智能回复内容,去除作者必填
  no message
  no message
  增加题型
wesmiler 5 maanden geleden
bovenliggende
commit
a75533f872

+ 1 - 0
.gitignore

@@ -9,3 +9,4 @@ addons/admin/node_modules/
 addons/min-exam/
 .idea/
 sql/xb_interact.sql
+addons/

+ 2 - 1
addons/admin/src/config/setting.js

@@ -5,7 +5,8 @@ export default {
   version: "1.0",
   name: "后台管理系统", // 项目名称
   loginName: "后台登录", // 项目名称
-  baseURL: "http://123.57.137.186:8090/", // 本地接口地址
+  // baseURL: "http://123.57.137.186:8090/", // 本地接口地址
+  baseURL: "https://api.gxlmcs.com/", // 本地接口地址
   // chatURL: 'ws://127.0.4.82:8660',  // 聊天地址
   chatURL: "ws://api.bzfengsheng.cn/chat", // 聊天地址
   //   baseURL: "https://api.bzfengsheng.cn", // 测试接口地址

+ 78 - 30
addons/admin/src/styles/eleadmin/common.scss

@@ -24,7 +24,12 @@ textarea {
   padding: 15px;
 }
 
-h1, h2, h3, h4, h5, h6 {
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
   color: $--color-text-primary;
   font-size: 26px;
   font-weight: 500;
@@ -86,56 +91,69 @@ h6 {
   text-decoration: line-through;
 }
 
-.ele-text-small, .ele-text-small > .el-progress__text {
+.ele-text-small,
+.ele-text-small>.el-progress__text {
   font-size: 12px !important;
 }
 
 .ele-bg-primary {
-  background-color: $--color-primary !important;;
+  background-color: $--color-primary !important;
+  ;
 }
 
 .ele-bg-success {
-  background-color: $--color-success !important;;
+  background-color: $--color-success !important;
+  ;
 }
 
 .ele-bg-warning {
-  background-color: $--color-warning !important;;
+  background-color: $--color-warning !important;
+  ;
 }
 
 .ele-bg-danger {
-  background-color: $--color-danger !important;;
+  background-color: $--color-danger !important;
+  ;
 }
 
 .ele-bg-info {
-  background-color: $--color-info !important;;
+  background-color: $--color-info !important;
+  ;
 }
 
 .ele-bg-white {
-  background-color: $--color-white !important;;
+  background-color: $--color-white !important;
+  ;
 }
 
 .ele-bg-base {
-  background-color: $--background-color-base !important;;
+  background-color: $--background-color-base !important;
+  ;
 }
 
 .ele-border-primary {
-  border-color: $--color-primary !important;;
+  border-color: $--color-primary !important;
+  ;
 }
 
 .ele-border-success {
-  border-color: $--color-success !important;;
+  border-color: $--color-success !important;
+  ;
 }
 
 .ele-border-warning {
-  border-color: $--color-warning !important;;
+  border-color: $--color-warning !important;
+  ;
 }
 
 .ele-border-danger {
-  border-color: $--color-danger !important;;
+  border-color: $--color-danger !important;
+  ;
 }
 
 .ele-border-info {
-  border-color: $--color-info !important;;
+  border-color: $--color-info !important;
+  ;
 }
 
 .ele-pull-left {
@@ -184,7 +202,8 @@ h6 {
   width: 100% !important;
 }
 
-.ele-btn-icon, .ele-btn-icon.is-round {
+.ele-btn-icon,
+.ele-btn-icon.is-round {
   padding-left: 12px;
   padding-right: 12px;
 
@@ -199,11 +218,22 @@ h6 {
   }
 }
 
-.el-button + .el-link, .el-button + .el-dropdown, .el-button + .ele-action,
-.el-link + .el-button, .el-link + .el-link, .el-link + .el-dropdown, .el-link + .ele-action,
-.el-dropdown + .el-button, .el-dropdown + .el-link, .el-dropdown + .el-dropdown, .el-dropdown + .ele-action,
-.ele-action + .el-button, .ele-action + .el-link, .ele-action + .el-dropdown, .ele-action + .ele-action,
-.el-tag + .el-tag {
+.el-button+.el-link,
+.el-button+.el-dropdown,
+.el-button+.ele-action,
+.el-link+.el-button,
+.el-link+.el-link,
+.el-link+.el-dropdown,
+.el-link+.ele-action,
+.el-dropdown+.el-button,
+.el-dropdown+.el-link,
+.el-dropdown+.el-dropdown,
+.el-dropdown+.ele-action,
+.ele-action+.el-button,
+.ele-action+.el-link,
+.ele-action+.el-dropdown,
+.ele-action+.ele-action,
+.el-tag+.el-tag {
   margin-left: 10px;
 }
 
@@ -238,12 +268,12 @@ s {
     align-items: flex-end;
   }
 
-  & > .ele-cell-content {
+  &>.ele-cell-content {
     flex: 1;
     box-sizing: border-box;
   }
 
-  & > * + .ele-cell-content {
+  &>*+.ele-cell-content {
     padding-left: 10px;
   }
 
@@ -298,11 +328,11 @@ s {
 }
 
 @media screen and (min-width: 768px) {
-  .ele-admin-sidebar + .ele-admin-body .ele-bottom-tool {
+  .ele-admin-sidebar+.ele-admin-body .ele-bottom-tool {
     padding-left: $--size-side-width + 15px;
   }
 
-  .ele-layout-collapse .ele-admin-sidebar + .ele-admin-body .ele-bottom-tool {
+  .ele-layout-collapse .ele-admin-sidebar+.ele-admin-body .ele-bottom-tool {
     padding-left: $--size-side-width-collapse + 15px;
   }
 }
@@ -313,7 +343,8 @@ s {
 }
 
 /** 水平搜索表单 */
-.ele-form-search .el-form-item, .ele-form-search .ele-form-actions {
+.ele-form-search .el-form-item,
+.ele-form-search .ele-form-actions {
   margin-bottom: 15px;
 }
 
@@ -366,7 +397,8 @@ s {
   &.ele-form-search {
     padding-bottom: 0;
 
-    .el-form-item, .ele-form-actions {
+    .el-form-item,
+    .ele-form-actions {
       margin-bottom: 10px;
     }
   }
@@ -388,22 +420,22 @@ s {
 
 /** 滚动条 */
 @media screen and (min-width: 768px) {
-  :not(html,body)::-webkit-scrollbar {
+  :not(html, body)::-webkit-scrollbar {
     width: 8px;
     height: 8px;
     background: transparent;
   }
 
-  :not(html,body)::-webkit-scrollbar-track {
+  :not(html, body)::-webkit-scrollbar-track {
     background: transparent;
   }
 
-  :not(html,body)::-webkit-scrollbar-thumb {
+  :not(html, body)::-webkit-scrollbar-thumb {
     border-radius: 4px;
     background-color: $--scrollbar-background-color;
   }
 
-  :not(html,body)::-webkit-scrollbar-thumb:hover {
+  :not(html, body)::-webkit-scrollbar-thumb:hover {
     background-color: $--scrollbar-hover-background-color;
   }
 
@@ -432,4 +464,20 @@ s {
   &:hover {
     overflow: auto;
   }
+}
+
+/* 禁用按钮样式 - 去掉背景和边框 */
+button.action-btn:disabled,
+button.edit-btn:disabled,
+button.delete-btn:disabled {
+  background-color: transparent !important;
+  border: none !important;
+  cursor: not-allowed;
+  opacity: 0.5;
+}
+
+/* 兼容其他按钮类 */
+button[disabled] {
+  background-color: transparent !important;
+  border: none !important;
 }

+ 32 - 25
addons/admin/src/views/exam/component/ExcelImport.vue

@@ -65,31 +65,32 @@
                 <template slot="default">
                     <p><strong>Excel格式要求:</strong></p>
                     CREATE TABLE `lev_ad` (
-                        `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-                        `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '广告标题',
-                        `position` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '广告位置:1-主页轮播图,2-会员中心占位图',
-                        `cover` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '广告图片',
-                        `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '广告访问:1-站内链接,2-站外链接,3-其他小程序,4-微信视频号',
-                        `description` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '广告描述',
-                        `url` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '广告链接/小程序链接/视频号链接',
-                        `link_type` tinyint(1) DEFAULT '1' COMMENT '跳转类型:1-默认,2-小程序',
-                        `mp_appid` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '跳转小程序APPID',
-                        `width` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '广告宽度',
-                        `height` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '广告高度',
-                        `start_time` int(10) unsigned NOT NULL COMMENT '开始时间',
-                        `end_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '结束时间',
-                        `view_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '广告点击次数',
-                        `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态:1在用 2停用',
-                        `sort` smallint(5) unsigned NOT NULL DEFAULT '125' COMMENT '排序',
-                        `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '添加时间',
-                        `update_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
-                        `mark` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '有效标识(1正常 0删除)',
-                        PRIMARY KEY (`id`) USING BTREE,
-                        KEY `title` (`title`) USING BTREE,
-                        KEY `ad_sort_id` (`position`) USING BTREE
-                      ) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='广告管理表';                    <p>• 第一行必须是标题行,支持以下列名:题目类型、题目、选项A~F(或答案A~F)、正确答案、解析、分数</p>
+                    `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+                    `title` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '广告标题',
+                    `position` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '广告位置:1-主页轮播图,2-会员中心占位图',
+                    `cover` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '广告图片',
+                    `type` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '广告访问:1-站内链接,2-站外链接,3-其他小程序,4-微信视频号',
+                    `description` varchar(150) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '广告描述',
+                    `url` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '广告链接/小程序链接/视频号链接',
+                    `link_type` tinyint(1) DEFAULT '1' COMMENT '跳转类型:1-默认,2-小程序',
+                    `mp_appid` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '跳转小程序APPID',
+                    `width` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '广告宽度',
+                    `height` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '广告高度',
+                    `start_time` int(10) unsigned NOT NULL COMMENT '开始时间',
+                    `end_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '结束时间',
+                    `view_num` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '广告点击次数',
+                    `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态:1在用 2停用',
+                    `sort` smallint(5) unsigned NOT NULL DEFAULT '125' COMMENT '排序',
+                    `create_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '添加时间',
+                    `update_time` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
+                    `mark` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '有效标识(1正常 0删除)',
+                    PRIMARY KEY (`id`) USING BTREE,
+                    KEY `title` (`title`) USING BTREE,
+                    KEY `ad_sort_id` (`position`) USING BTREE
+                    ) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+                    ROW_FORMAT=DYNAMIC COMMENT='广告管理表'; <p>• 第一行必须是标题行,支持以下列名:题目类型、题目、选项A~F(或答案A~F)、正确答案、解析、分数</p>
                     <p>• 列顺序可以任意排列,系统会自动识别各列</p>
-                    <p>• 题目类型:单选题、多选题、判断题、填空题、问答题</p>
+                    <p>• 题目类型:单选题、多选题、判断题、填空题、问答题、写作题、应用题、计算题、证明题、解答题、论述题</p>
                     <p>• 正确答案:单选题填选项字母(A/B/C/D/E/F),多选题用逗号分隔(A,B),判断题填对/错,填空题填答案,问答题可留空</p>
                     <p>• 分数:每道题的分值,建议单选题2-5分,多选题5-10分,判断题1-2分,填空题3-8分,问答题10-20分</p>
                     <p>• 支持富文本格式</p>
@@ -211,7 +212,13 @@ export default {
                 ['多选题', '这是一道多选题示例?', '选项A内容', '选项B内容', '选项C内容', '选项D内容', '', '', 'A,B', '这是多选题的解析', '10'],
                 ['判断题', '这是一道判断题示例?', '', '', '', '', '', '', '对', '这是判断题的解析', '2'],
                 ['填空题', '这是一道填空题示例,答案是___', '', '', '', '', '', '', '答案内容', '这是填空题的解析', '8'],
-                ['问答题', '这是一道问答题示例?', '', '', '', '', '', '', '', '这是问答题的解析', '15']
+                ['问答题', '这是一道问答题示例?', '', '', '', '', '', '', '', '这是问答题的解析', '15'],
+                ['写作题', '这是一道写作题示例?', '', '', '', '', '', '', '', '这是写作题的解析', '20'],
+                ['应用题', '这是一道应用题示例?', '', '', '', '', '', '', '', '这是应用题的解析', '25'],
+                ['计算题', '这是一道计算题示例?', '', '', '', '', '', '', '', '这是计算题的解析', '30'],
+                ['证明题', '这是一道证明题示例?', '', '', '', '', '', '', '', '这是证明题的解析', '35'],
+                ['解答题', '这是一道解答题示例?', '', '', '', '', '', '', '', '这是解答题的解析', '40'],
+                ['论述题', '这是一道论述题示例?', '', '', '', '', '', '', '', '这是论述题的解析', '45']
             ];
 
             // 创建工作簿

+ 22 - 7
addons/admin/src/views/exam/component/SubjectManager.vue

@@ -14,9 +14,10 @@
                         </template>
                     </el-table-column>
                     <el-table-column prop="attr_type" label="类别属性" width="120"
-                        v-if="defaultType === 2 || defaultType === 3">
+                        v-if="defaultType === 1 || defaultType === 2 || defaultType === 3">
                         <template slot-scope="{ row }">
-                            <el-tag type="success" v-if="row.attr_type">{{ attrTypeOptions[row.attr_type] || '未知'
+                            <el-tag type="success" v-if="row.attr_type">{{ currentAttrTypeOptions[row.attr_type] ||
+                                attrTypeOptions[row.attr_type] || '未知'
                                 }}</el-tag>
                             <span v-else>-</span>
                         </template>
@@ -57,9 +58,10 @@
                     <uploadImage :limit="1" v-model="formData.icon" @upload-success="onImageUploadSuccess">
                     </uploadImage>
                 </el-form-item>
-                <el-form-item label="类别属性" prop="attr_type" v-if="defaultType === 2 || defaultType === 3">
+                <el-form-item label="类别属性" prop="attr_type"
+                    v-if="defaultType === 1 || defaultType === 2 || defaultType === 3">
                     <el-select v-model="formData.attr_type" placeholder="请选择类别属性" clearable>
-                        <el-option v-for="(label, key) in attrTypeOptions" :key="key" :label="label"
+                        <el-option v-for="(label, key) in currentAttrTypeOptions" :key="key" :label="label"
                             :value="Number(key)" />
                     </el-select>
                 </el-form-item>
@@ -106,7 +108,7 @@ export default {
                 id: null,
                 subject_name: '',
                 type: this.defaultType,
-                attr_type: null,
+                attr_type: this.defaultType === 1 ? 3 : null,
                 description: '',
                 icon: '',
                 sort: 0,
@@ -116,7 +118,20 @@ export default {
                 subject_name: [{ required: true, message: '请输入科目名称', trigger: 'blur' }],
                 type: [{ required: true, message: '请选择所属栏目', trigger: 'change' }]
             },
-            attrTypeOptions: { 1: "文化基础", 2: '19专业大类' },
+            attrTypeOptions: { 1: "文化基础", 2: '19专业大类', 3: '职业适应性测试视频课' },
+        }
+    },
+    computed: {
+        // 根据默认类型动态获取类别属性选项
+        currentAttrTypeOptions() {
+            if (this.defaultType === 1) {
+                // 单招:只显示职业适应性测试视频课
+                return { 3: '职业适应性测试视频课' };
+            } else if (this.defaultType === 2 || this.defaultType === 3) {
+                // 对口和专升本:显示文化基础和19专业大类
+                return { 1: "文化基础", 2: '19专业大类' };
+            }
+            return {};
         }
     },
     watch: {
@@ -197,7 +212,7 @@ export default {
                 id: null,
                 subject_name: '',
                 type: this.defaultType, // 使用父组件传入默认栏目
-                attr_type: this.defaultType === 1 ? 0 : null, // 单招默认为0,对口和专升本为null
+                attr_type: this.defaultType === 1 ? 3 : null, // 单招默认为3(职业适应性测试视频课),对口和专升本为null
                 description: '',
                 icon: '',
                 sort: 0,

+ 19 - 5
addons/admin/src/views/exam/component/TopicManager.vue

@@ -89,11 +89,8 @@
             <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
                 <el-form-item label="题目类型" prop="topic_type">
                     <el-select v-model="form.topic_type" placeholder="请选择题型" @change="changeTopicType">
-                        <el-option label="单选题" value="单选题"></el-option>
-                        <el-option label="多选题" value="多选题"></el-option>
-                        <el-option label="判断题" value="判断题"></el-option>
-                        <el-option label="填空题" value="填空题"></el-option>
-                        <el-option label="简答题" value="简答题"></el-option>
+                        <el-option v-for="type in topicTypes" :key="type.value" :label="type.label" :value="type.value">
+                        </el-option>
                     </el-select>
                 </el-form-item>
 
@@ -249,6 +246,7 @@ export default {
         return {
             loading: false,
             topics: [],
+            topicTypes: [], // 题目类型列表
             showForm: false,
             isEdit: false,
             editingIndex: -1,
@@ -296,6 +294,9 @@ export default {
             imageDialogImage: ''
         };
     },
+    mounted() {
+        this.loadTopicTypes();
+    },
     watch: {
         visible(val) {
             if (val) {
@@ -306,6 +307,19 @@ export default {
         },
     },
     methods: {
+        async loadTopicTypes() {
+            try {
+                const res = await this.$http.get('/topics/topicTypes');
+                if (res.data.code === 0) {
+                    this.topicTypes = res.data.data;
+                } else {
+                    this.$message.error(res.data.msg);
+                }
+            } catch (error) {
+                this.$message.error('加载题目类型失败');
+                console.error(error);
+            }
+        },
         async loadTopics() {
             this.loading = true;
             try {

+ 39 - 14
addons/admin/src/views/subject/subject.vue

@@ -20,7 +20,7 @@
                     <el-col :md="6" :sm="12">
                         <el-form-item label="类别属性">
                             <el-select v-model="table.where.attr_type" placeholder="请选择类别属性" clearable>
-                                <el-option v-for="(label, key) in attrTypeOptions" :key="key" :label="label"
+                                <el-option v-for="(label, key) in searchAttrTypeOptions" :key="key" :label="label"
                                     :value="key" />
                             </el-select>
                         </el-form-item>
@@ -77,7 +77,7 @@
                     <el-table-column prop="attr_type" label="类别属性" width="120" v-if="showAttrTypeColumn">
                         <template slot-scope="{ row }">
                             <el-tag type="success" v-if="row.attr_type">{{ attrTypeOptions[row.attr_type] || '未知'
-                            }}</el-tag>
+                                }}</el-tag>
                             <span v-else>-</span>
                         </template>
                     </el-table-column>
@@ -120,7 +120,7 @@
                 </el-form-item>
                 <el-form-item label="类别属性" prop="attr_type" v-if="showAttrTypeField">
                     <el-select v-model="formData.attr_type" placeholder="请选择类别属性" clearable>
-                        <el-option v-for="(label, key) in attrTypeOptions" :key="key" :label="label"
+                        <el-option v-for="(label, key) in currentAttrTypeOptions" :key="key" :label="label"
                             :value="Number(key)" />
                     </el-select>
                 </el-form-item>
@@ -155,7 +155,7 @@ export default {
                 where: { keyword: "", type: null, attr_type: null, status: null }
             },
             choose: [],
-            formData: { id: null, subject_name: "", type: 1, attr_type: null, description: "", icon: "", sort: 0, status: 1 },
+            formData: { id: null, subject_name: "", type: 1, attr_type: 3, description: "", icon: "", sort: 0, status: 1 },
             isEdit: false,
             formVisible: false,
             formRules: {
@@ -163,7 +163,7 @@ export default {
                 type: [{ required: true, message: "请选择所属栏目", trigger: "change" }]
             },
             typeOptions: { 1: "单招", 2: "对口", 3: "专升本" },
-            attrTypeOptions: { 1: "文化基础", 2: '19专业大类' },
+            attrTypeOptions: { 1: "文化基础", 2: '19专业大类', 3: '职业适应性测试视频课' },
             permissionMap: {
                 delete: "sys:subject:delete",
                 edit: "sys:subject:edit",
@@ -174,23 +174,48 @@ export default {
     },
     computed: {
         ...mapGetters(["permission"]),
-        // 是否显示类别属性列(只有对口和专升本才显示)
+        // 是否显示类别属性列
         showAttrTypeColumn() {
-            return this.table.where.type === 2 || this.table.where.type === 3;
+            // 所有类型都显示类别属性列
+            return this.table.where.type === 1 || this.table.where.type === 2 || this.table.where.type === 3;
         },
-        // 是否显示类别属性字段(只有对口和专升本才显示)
+        // 是否显示类别属性字段
         showAttrTypeField() {
-            return this.formData.type === 2 || this.formData.type === 3;
+            // 所有类型都显示类别属性字段
+            return this.formData.type === 1 || this.formData.type === 2 || this.formData.type === 3;
+        },
+        // 根据当前表单类型动态获取类别属性选项
+        currentAttrTypeOptions() {
+            if (this.formData.type === 1) {
+                // 单招:只显示职业适应性测试视频课
+                return { 3: '职业适应性测试视频课' };
+            } else if (this.formData.type === 2 || this.formData.type === 3) {
+                // 对口和专升本:显示文化基础和19专业大类
+                return { 1: "文化基础", 2: '19专业大类' };
+            }
+            return {};
+        },
+        // 搜索表单的类别属性选项(根据搜索的类型)
+        searchAttrTypeOptions() {
+            if (this.table.where.type === 1) {
+                // 单招:只显示职业适应性测试视频课
+                return { 3: '职业适应性测试视频课' };
+            } else if (this.table.where.type === 2 || this.table.where.type === 3) {
+                // 对口和专升本:显示文化基础和19专业大类
+                return { 1: "文化基础", 2: '19专业大类' };
+            }
+            // 未选择类型时显示所有
+            return this.attrTypeOptions;
         }
     },
     watch: {
         // 监听类型变化,自动设置 attr_type
         'formData.type'(newType) {
             if (newType === 1) {
-                // 单招时设置为0
-                this.formData.attr_type = 0;
+                // 单招时默认设置为职业适应性测试视频课
+                this.formData.attr_type = 3;
             } else if (newType === 2 || newType === 3) {
-                // 对口和专升本时设置为null(可选)
+                // 对口和专升本时清空,让用户自己选择
                 this.formData.attr_type = null;
             }
         }
@@ -206,13 +231,13 @@ export default {
                 this.formData = {
                     ...row,
                     type: Number(row.type),
-                    attr_type: row.attr_type ? Number(row.attr_type) : (Number(row.type) === 1 ? 0 : null),
+                    attr_type: row.attr_type ? Number(row.attr_type) : (Number(row.type) === 1 ? 3 : null),
                     description: row.description || "",
                     icon: row.icon || ""
                 }; // 转成数字
             } else {
                 this.isEdit = false;
-                this.formData = { id: null, subject_name: "", type: 1, attr_type: 0, description: "", icon: "", sort: 0, status: 1 };
+                this.formData = { id: null, subject_name: "", type: 1, attr_type: 3, description: "", icon: "", sort: 0, status: 1 };
             }
             this.formVisible = true;
         },

+ 1 - 1
addons/admin/src/views/system/ad/banner.vue

@@ -1,5 +1,5 @@
 <template>
-  <list defaultPosition="1" :permissionMap="permissionMap"></list>
+  <list :defaultPosition="1" :permissionMap="permissionMap"></list>
 </template>
 
 <script>

+ 6 - 5
addons/admin/src/views/system/ad/components/list.vue

@@ -232,15 +232,16 @@ export default {
     defaultPosition: {
       handler(newPosition) {
         this.table.where.position = newPosition;
-        this.$refs.table.reload();
+        // 确保refs存在后再调用reload
+        this.$nextTick(() => {
+          if (this.$refs.table) {
+            this.$refs.table.reload();
+          }
+        });
       },
       immediate: true
     }
   },
-  mounted() {
-    // 在这里初始化数据
-    this.table.where.position = this.defaultPosition;
-  },
   methods: {
     /* 显示编辑 */
     edit(row) {

+ 47 - 13
addons/admin/src/views/system/article/components/ArticleForm.vue

@@ -1,6 +1,6 @@
 <template>
-    <el-dialog :title="formData.id ? '编辑' : '创建'" :visible.sync="visible" width="1000px" :destroy-on-close="true"
-        custom-class="ele-dialog-form" :lock-scroll="false" @closed="resetForm">
+    <el-dialog :title="formData.id ? '编辑' : '创建'" :visible.sync="dialogVisible" width="1000px"
+        custom-class="ele-dialog-form" :lock-scroll="false" @closed="handleClosed">
         <el-form :model="formData" ref="formRef" :rules="rules" label-width="100px">
             <el-row :gutter="15">
                 <el-col :sm="12">
@@ -12,7 +12,7 @@
                     <el-form-item label="标题:" prop="title">
                         <el-input v-model="formData.title" placeholder="请输入标题" clearable />
                     </el-form-item>
-                    <el-form-item label="作者:" prop="author">
+                    <el-form-item label="作者:" prop="author" v-if="!isSmartReply">
                         <el-input v-model="formData.author" placeholder="请输入作者" clearable />
                     </el-form-item>
                     <el-form-item label="状态:" prop="status">
@@ -34,7 +34,7 @@
         </el-form>
 
         <div slot="footer">
-            <el-button @click="visible = false">取消</el-button>
+            <el-button @click="closeDialog">取消</el-button>
             <el-button type="primary" @click="save">保存</el-button>
         </div>
     </el-dialog>
@@ -61,15 +61,40 @@ export default {
                 { id: 4, name: 'VIP购买协议' },
                 { id: 9, name: '智能问答' },
             ],
-            rules: {
-                title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-                author: [{ required: true, message: '请输入标题', trigger: 'blur' }],
-                type: [{ required: true, message: '请选择类型', trigger: 'blur' }],
-                content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
-            },
         }
     },
     computed: {
+        // 弹窗显示状态(处理prop的双向绑定)
+        dialogVisible: {
+            get() {
+                return this.visible;
+            },
+            set(val) {
+                this.$emit('update:visible', val);
+            }
+        },
+        // 判断是否为智能问答类型
+        isSmartReply() {
+            const currentType = this.defaultType || this.formData.type;
+            const b = currentType == 9;
+            console.log('isSmartReply', b, this.defaultType)
+            return b;
+        },
+        // 动态验证规则
+        rules() {
+            const baseRules = {
+                title: [{ required: true, message: '请输入标题', trigger: 'blur' }],
+                type: [{ required: true, message: '请选择类型', trigger: 'blur' }],
+                content: [{ required: true, message: '内容不能为空', trigger: 'blur' }],
+            };
+
+            // 智能问答不需要验证作者
+            if (!this.isSmartReply) {
+                baseRules.author = [{ required: true, message: '请输入作者', trigger: 'blur' }];
+            }
+
+            return baseRules;
+        },
         editorConfig() {
             return {
                 menubar: false,
@@ -85,9 +110,18 @@ export default {
         },
     },
     methods: {
+        closeDialog() {
+            this.$emit('update:visible', false);
+        },
+        handleClosed() {
+            // 对话框关闭后重置表单
+            this.resetForm();
+        },
         resetForm() {
-            this.$refs.formRef && this.$refs.formRef.resetFields()
-            this.formData = Object.assign({}, this.defaultData)
+            if (this.$refs.formRef) {
+                this.$refs.formRef.resetFields();
+            }
+            this.formData = Object.assign({}, this.defaultData);
         },
         save() {
             this.$refs.formRef.validate(valid => {
@@ -99,7 +133,7 @@ export default {
                         if (res.data.code === 0) {
                             this.$message.success(res.data.msg)
                             this.$emit('success')
-                            this.$emit('update:visible', false) // ✅ 让父组件把 visible 改成 false
+                            this.closeDialog()
                         } else {
                             this.$message.error(res.data.msg)
                         }

+ 74 - 10
addons/admin/src/views/system/article/components/ExcelImportDialog.vue

@@ -1,11 +1,29 @@
 <template>
     <el-dialog :visible.sync="visible" title="导入 Excel" width="800px">
-        <el-upload :action="uploadUrl" :before-upload="beforeUpload" :on-change="handleFileChange" accept=".xls,.xlsx"
-            :show-file-list="false" :auto-upload="false">
-            <el-button slot="trigger" type="primary" icon="el-icon-upload2">选择 Excel 文件</el-button>
-        </el-upload>
+        <div class="import-header">
+            <el-upload :action="uploadUrl" :before-upload="beforeUpload" :on-change="handleFileChange"
+                accept=".xls,.xlsx" :show-file-list="false" :auto-upload="false">
+                <el-button slot="trigger" type="primary" icon="el-icon-upload2">选择 Excel 文件</el-button>
+            </el-upload>
 
-        <el-button type="primary" :disabled="!file" @click="handleImport" class="mt-10">开始导入</el-button>
+            <el-button type="success" icon="el-icon-download" @click="downloadTemplate">下载导入模板</el-button>
+        </div>
+
+        <div class="import-actions">
+            <el-button type="primary" :disabled="!file" @click="handleImport">开始导入</el-button>
+        </div>
+
+        <!-- 模板说明 -->
+        <div class="template-tips">
+            <el-alert type="info" :closable="false" show-icon>
+                <div slot="title">
+                    <p><strong>模板格式说明:</strong></p>
+                    <p>• 第一行:标题、内容(固定表头)</p>
+                    <p>• 从第二行开始填写数据</p>
+                    <p>• 标题:必填,内容:选填</p>
+                </div>
+            </el-alert>
+        </div>
 
         <!-- 预览区域 -->
         <div v-if="previewData.length" class="excel-preview">
@@ -66,6 +84,36 @@ export default {
             this.file = null;
             this.previewData = [];
         },
+        // 下载导入模板
+        downloadTemplate() {
+            // 创建模板数据
+            const templateData = [
+                ['标题', '内容'],
+                ['示例标题1', '这是示例内容1'],
+                ['示例标题2', '这是示例内容2'],
+            ];
+
+            // 创建工作簿
+            const wb = XLSX.utils.book_new();
+            const ws = XLSX.utils.aoa_to_sheet(templateData);
+
+            // 设置列宽
+            ws['!cols'] = [
+                { wch: 30 },  // 标题列宽
+                { wch: 50 },  // 内容列宽
+            ];
+
+            // 将工作表添加到工作簿
+            XLSX.utils.book_append_sheet(wb, ws, '导入模板');
+
+            // 生成文件名(带时间戳)
+            const fileName = `智能回答导入模板_${new Date().getTime()}.xlsx`;
+
+            // 下载文件
+            XLSX.writeFile(wb, fileName);
+
+            this.$message.success('模板下载成功');
+        },
         beforeUpload(file) {
             const isExcel =
                 file.type.includes("excel") || file.name.endsWith(".xls") || file.name.endsWith(".xlsx");
@@ -156,8 +204,28 @@ export default {
 </script>
 
 <style scoped>
+.import-header {
+    display: flex;
+    gap: 10px;
+    margin-bottom: 15px;
+    align-items: center;
+}
+
+.import-actions {
+    margin-bottom: 15px;
+}
+
 .el-upload {
-    width: 100%;
+    flex: 1;
+}
+
+.template-tips {
+    margin: 15px 0;
+}
+
+.template-tips p {
+    margin: 5px 0;
+    line-height: 1.6;
 }
 
 .excel-preview {
@@ -178,10 +246,6 @@ export default {
     overflow: hidden;
 }
 
-.mt-10 {
-    margin-top: 10px;
-}
-
 .dialog-footer {
     display: flex;
     justify-content: flex-end;

+ 14 - 7
addons/admin/src/views/system/article/components/list.vue

@@ -34,13 +34,13 @@
               <el-button @click="reloadTable()">
                 重置
               </el-button>
-              <el-button v-if="permission.includes(permissionMap['add'])" type="primary" icon="el-icon-plus" size="small"
-                @click="openCreate">
+              <el-button v-if="permission.includes(permissionMap['add'])" type="primary" icon="el-icon-plus"
+                size="small" @click="openCreate">
                 创建
               </el-button>
               <!-- 导入按钮,仅在 defaultType 为 9 时可见 -->
-              <el-button v-if="defaultType == 9 && permission.includes(permissionMap['add'])" type="success" icon="el-icon-upload2"
-                size="small" @click="openImportDialog">
+              <el-button v-if="defaultType == 9 && permission.includes(permissionMap['add'])" type="success"
+                icon="el-icon-upload2" size="small" @click="openImportDialog">
                 导入
               </el-button>
             </div>
@@ -65,7 +65,7 @@
           </el-table-column>
           <el-table-column prop="status" label="发布" :resizable="false" min-width="120">
             <template slot-scope="{row}">
-              <el-switch v-model="row.status" @change="editStatus(row)" :active-value="1" :inactive-value="2" />
+              <el-switch v-model="row.status" @change="handleStatusChange(row)" :active-value="1" :inactive-value="2" />
             </template>
           </el-table-column>
           <el-table-column label="操作" width="130px" align="center" :resizable="false" fixed="right">
@@ -254,21 +254,28 @@ export default {
         });
       }
     },
+    /* 处理状态切换 */
+    handleStatusChange(row) {
+      this.editStatus(row);
+    },
     /* 更改状态 */
     editStatus(row) {
       this.$message.closeAll()
       const loading = this.$loading({ lock: true });
-      let params = Object.assign({}, row);
+      let params = { id: row.id, status: row.status };
       this.$http.post('/article/status', params).then(res => {
         loading.close();
         if (res.data.code === 0) {
           this.$message({ type: 'success', message: res.data.msg });
         } else {
-          row.status = !row.status ? 2 : 1;
+          // 失败时恢复原状态
+          row.status = row.status === 1 ? 2 : 1;
           this.$message.error(res.data.msg);
         }
       }).catch(e => {
         loading.close();
+        // 失败时恢复原状态
+        row.status = row.status === 1 ? 2 : 1;
         this.$message.error(e.message);
       });
     },

+ 0 - 0
addons/admin/src/views/system/institution.vue


+ 31 - 3
addons/admin/src/views/system/institution/index.vue

@@ -192,9 +192,37 @@ export default {
             this.$refs.table.reload();
         },
         /* 显示编辑 */
-        edit(row) {
-            this.editForm = Object.assign({ status: 1, link_type: 1, sort: 0 }, row);
-            this.showEdit = true;
+        async edit(row) {
+            // 获取完整数据,确保所有字段都存在
+            const loading = this.$loading({ lock: true });
+            try {
+                const res = await this.$http.get(`/institutions/info?id=${row.id}`);
+                console.log('Response:', res.data.data.data);
+                loading.close();
+                if (res.data.code === 0 && res.data.data.data) {
+                    const data = res.data.data.data;
+                    console.log('Institution data:', data);
+                    this.editForm = {
+                        id: data.id || null,
+                        name: data.name || '',
+                        code: data.code || '',
+                        thumb: data.thumb || '',
+                        link_url: data.link_url || '',
+                        link_type: data.link_type || 1,
+                        mp_appid: data.mp_appid || '',
+                        description: data.description || '',
+                        sort: data.sort || 0,
+                        status: data.status !== undefined ? data.status : 1
+                    };
+                    console.log('EditForm:', this.editForm);
+                    this.showEdit = true;
+                } else {
+                    this.$message.error(res.data.msg || '获取数据失败');
+                }
+            } catch (e) {
+                loading.close();
+                this.$message.error(e.message || '获取数据失败');
+            }
         },
         /* 重置表单 */
         resetForm() {

+ 1 - 1
app/Http/Controllers/Admin/InstitutionController.php

@@ -60,7 +60,7 @@ class InstitutionController extends Backend
         $id = request()->input("id", 0);
         $info = [];
         if ($id) {
-            $info = $this->service->getInfo($id);
+            $info = $this->service->info();
         }
         return message(MESSAGE_OK, true, $info);
     }

+ 25 - 0
app/Http/Controllers/Admin/TopicController.php

@@ -1,4 +1,5 @@
 <?php
+
 namespace App\Http\Controllers\Admin;
 
 use App\Services\Exam\TopicService;
@@ -32,4 +33,28 @@ class TopicController extends Backend
         return $this->service->sort($param);
     }
 
+    /**
+     * 获取题目类型列表
+     */
+    public function getTopicTypes()
+    {
+        $types = [
+            ['label' => '单选题', 'value' => '单选题'],
+            ['label' => '多选题', 'value' => '多选题'],
+            ['label' => '判断题', 'value' => '判断题'],
+            ['label' => '填空题', 'value' => '填空题'],
+            ['label' => '简答题', 'value' => '简答题'],
+            ['label' => '写作题', 'value' => '写作题'],
+            ['label' => '应用题', 'value' => '应用题'],
+            ['label' => '计算题', 'value' => '计算题'],
+            ['label' => '证明题', 'value' => '证明题'],
+            ['label' => '解答题', 'value' => '解答题'],
+            ['label' => '论述题', 'value' => '论述题'],
+        ];
+        return response()->json([
+            'code' => 0,
+            'msg' => '获取成功',
+            'data' => $types
+        ]);
+    }
 }

+ 1 - 1
app/Models/InstitutionModel.php

@@ -55,6 +55,6 @@ class InstitutionModel extends BaseModel
 
     public function setThumbAttribute($value)
     {
-        return $value ? get_image_path($value) : '';
+        $this->attributes['thumb'] = $value ? get_image_path($value) : '';
     }
 }

+ 6 - 7
app/Services/Api/ExamService.php

@@ -191,9 +191,9 @@ class ExamService extends BaseService
             return $datas;
         }
 
-        $prefix = env('DB_PREFIX','lev_');
+        $prefix = env('DB_PREFIX', 'lev_');
         $query = $this->getQuery($params);
-        $list = $query->with(['error'])->whereRaw("{$prefix}a.accurate_count < {$prefix}a.answer_count")->where('b.id','>',0)
+        $list = $query->with(['error'])->whereRaw("{$prefix}a.accurate_count < {$prefix}a.answer_count")->where('b.id', '>', 0)
             ->select(['a.id', 'a.user_id', 'a.paper_id', 'a.score', 'a.accurate_count', 'a.answer_count', 'b.name', 'b.type', 'b.scene_type', 'b.topic_count', 'b.score_total', 'b.is_charge', 'a.create_time', 'a.answer_times', 'a.status'])
             ->orderBy('a.create_time', 'desc')
             ->paginate($pageSize > 0 ? $pageSize : 9999999);
@@ -202,7 +202,7 @@ class ExamService extends BaseService
             foreach ($list['data'] as &$item) {
                 $item['error_count'] = $item['answer_count'] - $item['accurate_count'];
                 $item['create_time'] = $item['create_time'] ? datetime($item['create_time'], 'Y-m-d H.i.s') : '';
-                $item['error_id'] = isset($item['error']) && isset($item['error']['topic_id'])? $item['error']['topic_id'] : 0;
+                $item['error_id'] = isset($item['error']) && isset($item['error']['topic_id']) ? $item['error']['topic_id'] : 0;
             }
         }
 
@@ -407,7 +407,7 @@ class ExamService extends BaseService
         $rid = isset($params['rid']) ? $params['rid'] : 0;
         $tid = isset($params['tid']) ? $params['tid'] : 0;
         $isSubmit = isset($params['is_submit']) ? $params['is_submit'] : 1;
-        $answer = isset($params['answer']) && $params['answer']? str_replace("\n",'\n', $params['answer']) : '';
+        $answer = isset($params['answer']) && $params['answer'] ? str_replace("\n", '\n', $params['answer']) : '';
         $answerImage = isset($params['answer_image']) ? $params['answer_image'] : '';
         $answerType = isset($params['answer_type']) && $params['answer_type'] ? $params['answer_type'] : 2;
         $submitType = isset($params['submit_type']) && $params['submit_type'] ? $params['submit_type'] : 1; // 答题模式
@@ -444,7 +444,7 @@ class ExamService extends BaseService
         $topicInfo = ExamTopicModel::where(['id' => $tid, 'paper_id' => $paperId, 'status' => 1, 'mark' => 1])->first();
         $topicType = isset($topicInfo['topic_type']) ? trim($topicInfo['topic_type']) : '';
         $topicScore = isset($topicInfo['score']) ? intval($topicInfo['score']) : 0;
-        $topicName = isset($topicInfo['topic_name']) && $topicInfo['topic_name']? str_replace("\n",'\n', $topicInfo['topic_name']) : '';
+        $topicName = isset($topicInfo['topic_name']) && $topicInfo['topic_name'] ? str_replace("\n", '\n', $topicInfo['topic_name']) : '';
         $topicShowType = isset($topicInfo['show_type']) ? $topicInfo['show_type'] : 1;
         $correctAnswer = isset($topicInfo['correct_answer']) ? $topicInfo['correct_answer'] : '';
         if (empty($topicInfo) || empty($topicType) || empty($topicName)) {
@@ -548,7 +548,7 @@ class ExamService extends BaseService
             } else {
                 $topicData['accurate'] = 0;
             }
-//      }else if (in_array($topicType, ['简答题','计算题','阅读理解'])){
+            //      }else if (in_array($topicType, ['简答题','计算题','阅读理解'])){
         } else {
             // 图片答案AI验证
             if ($answerType == 1) {
@@ -724,5 +724,4 @@ class ExamService extends BaseService
 
         return $data;
     }
-
 }

+ 15 - 15
app/Services/Common/InstitutionService.php

@@ -64,6 +64,10 @@ class InstitutionService extends BaseService
                 // 格式化时间
                 $item['create_time'] = $item['create_time'] ? datetime($item['create_time'], 'Y-m-d H:i:s') : '';
                 $item['update_time'] = $item['update_time'] ? datetime($item['update_time'], 'Y-m-d H:i:s') : '';
+                // 处理封面图片URL
+                if (isset($item['thumb']) && $item['thumb']) {
+                    $item['thumb'] = get_image_url($item['thumb']);
+                }
                 // 格式化状态
                 $item['status_text'] = $item['status'] == 1 ? '正常' : '不显示';
                 // 格式化链接类型
@@ -142,24 +146,12 @@ class InstitutionService extends BaseService
             return message('院校代码已存在', false);
         }
 
-        // 处理封面图片
+        // 处理封面图片路径(前端传递的是完整 URL,需要转换为相对路径)
         if (!empty($data['thumb'])) {
             $data['thumb'] = get_image_path($data['thumb']);
         }
 
-        // 确保所有必填字段都存在
-        $data = [
-            'name' => $data['name'] ?? '',
-            'code' => $data['code'] ?? '',
-            'thumb' => $data['thumb'] ?? '',
-            'link_url' => $data['link_url'] ?? '',
-            'link_type' => $data['link_type'] ?? 1,
-            'mp_appid' => $data['mp_appid'] ?? '',
-            'description' => $data['description'] ?? '',
-            'sort' => $data['sort'] ?? 0,
-            'status' => $data['status'] ?? 1,
-        ];
-
+        // 直接传递给 parent::edit,确保包含 id 字段用于判断新增/编辑
         return parent::edit($data);
     }
 
@@ -173,7 +165,15 @@ class InstitutionService extends BaseService
         $id = request()->input("id", 0);
         $info = [];
         if ($id) {
-            $info = $this->model->getInfo($id);
+            // 查询记录
+            $record = $this->model->where('id', $id)->where('mark', 1)->first();
+            if ($record) {
+                $info = $record->toArray();
+                // 处理封面图片URL(确保返回完整URL)
+                if (isset($info['thumb']) && $info['thumb']) {
+                    $info['thumb'] = get_image_url($info['thumb']);
+                }
+            }
         }
         return message(MESSAGE_OK, true, $info);
     }

+ 1 - 0
routes/web.php

@@ -244,6 +244,7 @@ Route::post('/topics/edit', [TopicController::class, 'edit']);
 Route::post('/topics/delete', [TopicController::class, 'delete']);
 Route::post('/topics/status', [TopicController::class, 'status']);
 Route::post('/topics/sort', [TopicController::class, 'sort']);
+Route::get('/topics/topicTypes', [TopicController::class, 'getTopicTypes']);
 
 // videos
 Route::get('/videos/index', [VideoController::class, 'index']);