crud.tsx 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992
  1. import { CreateCrudOptionsProps, CreateCrudOptionsRet, AddReq, DelReq, EditReq, dict, compute } from '@fast-crud/fast-crud';
  2. import * as api from './api';
  3. import { dictionary } from '/@/utils/dictionary';
  4. import { successMessage, warningMessage } from '../../../utils/message';
  5. import { auth } from '/@/utils/authFunction';
  6. import { ref, onMounted } from 'vue';
  7. import { ElMessage } from 'element-plus';
  8. /**
  9. *
  10. * @param crudExpose:index传递过来的示例
  11. * @param context:index传递过来的自定义参数
  12. * @returns
  13. */
  14. export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
  15. const pageRequest = async (query: any) => {
  16. return await api.GetList(query);
  17. };
  18. const editRequest = async ({ form, row }: EditReq) => {
  19. form.id = row.id;
  20. // 调用更新题目的接口
  21. const editResult = await api.UpdateObj(form);
  22. // 如果更新成功且题目形式为0(开放题)或5,调用数字人视频生成接口
  23. /* if (editResult.code === 2000 && (form.question_form === 0 || form.question_form === 5)) {
  24. try {
  25. // 构造视频生成请求参数
  26. const videoParams = {
  27. video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
  28. text: form.question, // 使用题目内容作为文本
  29. question_id: form.id, // 使用编辑的题目ID
  30. guidance_scale: 2.0, // 默认值
  31. inference_steps: 20, // 默认值
  32. seed: 1247
  33. };
  34. // 调用视频生成接口
  35. const videoResult = await fetch('http://192.168.66.101:7861/queue/process/text/', {
  36. method: 'POST',
  37. headers: {
  38. 'Content-Type': 'application/json'
  39. },
  40. body: JSON.stringify(videoParams)
  41. });
  42. if (!videoResult.ok) {
  43. console.warn('数字人视频生成请求失败:', await videoResult.text());
  44. } else {
  45. // 只在成功时显示提示
  46. ElMessage({
  47. type: 'info',
  48. message: '题目已更新,数字人视频正在重新生成中...'
  49. });
  50. }
  51. } catch (error) {
  52. console.error('调用数字人视频生成接口失败:', error);
  53. // 这里我们只记录错误,不影响题目更新的成功状态
  54. }
  55. } */
  56. return editResult;
  57. };
  58. const delRequest = async ({ row }: DelReq) => {
  59. return await api.DelObj(row.id);
  60. };
  61. const addRequest = async ({ form }: AddReq) => {
  62. // 先调用添加题目的接口
  63. const addResult = await api.AddObj(form);
  64. // 如果添加成功且题目形式为0(开放题)或5,调用数字人视频生成接口
  65. /* if (addResult.code === 2000 && (form.question_form === 0 || form.question_form === 5)) {
  66. try {
  67. // 构造视频生成请求参数
  68. const videoParams = {
  69. video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
  70. text: form.question, // 使用题目内容作为文本
  71. question_id: addResult.data.id, // 使用新创建的题目ID
  72. guidance_scale: 2.0, // 默认值
  73. inference_steps: 20, // 默认值
  74. seed: 1247
  75. };
  76. // 调用视频生成接口
  77. const videoResult = await fetch('http://192.168.66.101:7861/queue/process/text/', {
  78. method: 'POST',
  79. headers: {
  80. 'Content-Type': 'application/json'
  81. },
  82. body: JSON.stringify(videoParams)
  83. });
  84. if (!videoResult.ok) {
  85. console.warn('数字人视频生成请求失败:', await videoResult.text());
  86. } else {
  87. // 只在成功时显示提示
  88. ElMessage({
  89. type: 'info',
  90. message: '题目已创建,数字人视频正在生成中...'
  91. });
  92. }
  93. } catch (error) {
  94. console.error('调用数字人视频生成接口失败:', error);
  95. // 这里我们只记录错误,不影响题目创建的成功状态
  96. }
  97. } */
  98. return addResult;
  99. };
  100. /* // 修改分类和标签数据获取方式
  101. const categoryOptions = ref<Array<{value: number, label: string}>>([]);
  102. const tagOptions = ref<Array<{value: number, label: string}>>([]);
  103. // 获取分类数据
  104. const fetchCategories = async () => {
  105. try {
  106. const res = await api.GetcategoryList({page:1, limit:1000, tenant_id:1});
  107. if (res.code === 0 && res.data && res.data.items) {
  108. categoryOptions.value = res.data.items;
  109. }
  110. } catch (error) {
  111. console.error('获取分类数据失败', error);
  112. }
  113. };
  114. // 获取标签数据
  115. const fetchTags = async (categoryId?: number) => {
  116. try {
  117. const params = {
  118. page: 1,
  119. limit: 1000,
  120. tenant_id: 1
  121. };
  122. if (categoryId) {
  123. params.category_id = categoryId;
  124. }
  125. const res = await api.GetTagList(params);
  126. if (res.code === 0 && res.data && res.data.items) {
  127. tagOptions.value = res.data.items;
  128. }
  129. } catch (error) {
  130. console.error('获取标签数据失败', error);
  131. }
  132. }; */
  133. // 初始化时获取分类数据
  134. /* onMounted(() => {
  135. fetchCategories();
  136. fetchTags();
  137. }); */
  138. // 添加批量更新标签的方法
  139. const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
  140. try {
  141. const res = await api.BatchUpdateTags({
  142. question_ids: questionIds,
  143. tags: tagIds,
  144. tenant_id: 1
  145. });
  146. if (res.code === 0) {
  147. successMessage('批量更新标签成功');
  148. // 刷新列表
  149. crudExpose.doRefresh();
  150. }
  151. } catch (error) {
  152. console.error('批量更新标签失败', error);
  153. }
  154. };
  155. // 添加批量更新分类的方法
  156. const batchUpdateCategory = async (questionIds: number[], categoryId: number) => {
  157. try {
  158. const res = await api.BatchUpdateCategory({
  159. question_ids: questionIds,
  160. category_id: categoryId,
  161. tenant_id: "1"
  162. });
  163. if (res.code === 0) {
  164. successMessage('批量更新分类成功');
  165. // 刷新列表
  166. crudExpose.doRefresh();
  167. }
  168. } catch (error) {
  169. console.error('批量更新分类失败', error);
  170. }
  171. };
  172. return {
  173. crudOptions: {
  174. toolbar:{
  175. buttons:{
  176. search:{show:false},
  177. // 刷新按钮
  178. refresh:{show:false},
  179. // 紧凑模式
  180. compact:{show:false},
  181. // 导出按钮
  182. export:{
  183. text: '导出',
  184. type: 'primary',
  185. size: 'small',
  186. icon: 'upload',
  187. circle: false,
  188. display: true
  189. },
  190. // 列设置按钮
  191. columns:{
  192. show:false
  193. },
  194. }
  195. },
  196. request: {
  197. pageRequest,
  198. addRequest,
  199. editRequest,
  200. delRequest,
  201. },
  202. pagination: {
  203. show: true,
  204. },
  205. // 添加搜索表单配置
  206. search: {
  207. show: true,
  208. buttons: {
  209. search: {
  210. size: 'small', // 设置查询按钮大小为small
  211. },
  212. reset: {
  213. size: 'small', // 设置重置按钮大小为small
  214. }
  215. }
  216. },
  217. actionbar: {
  218. buttons: {
  219. add: {
  220. size: 'small',
  221. show: auth('role:Create'),
  222. },
  223. // 添加批量绑定标签按钮
  224. batchBindTags: {
  225. text: '批量绑定标签',
  226. type: 'primary',
  227. size: 'small',
  228. show: true,
  229. order: 2,
  230. click: () => {
  231. // 使用存储的选中行
  232. const selection = context.selectedRows || [];
  233. console.log('选中的行:', selection);
  234. if (!selection || selection.length === 0) {
  235. warningMessage('请先选择要操作的题目');
  236. return;
  237. }
  238. // 打开批量绑定标签对话框
  239. context.openBatchTagsDialog(selection);
  240. },
  241. },
  242. // 添加批量设置分类按钮
  243. batchSetCategory: {
  244. text: '批量设置分类',
  245. type: 'primary',
  246. show: true,
  247. size: 'small',
  248. order: 3,
  249. click: () => {
  250. // 使用存储的选中行
  251. const selection = context.selectedRows || [];
  252. console.log('选中的行:', selection);
  253. if (!selection || selection.length === 0) {
  254. warningMessage('请先选择要操作的题目');
  255. return;
  256. }
  257. // 打开批量设置分类对话框
  258. context.openBatchCategoryDialog(selection);
  259. },
  260. },
  261. },
  262. },
  263. rowHandle: {
  264. //固定右侧
  265. fixed: 'right',
  266. width: 259,
  267. buttons: {
  268. view: {
  269. show: true,
  270. size: 'small',
  271. icon:"View",
  272. type: 'text',
  273. },
  274. edit: {
  275. show: auth('role:Update'),
  276. size: 'small',
  277. icon:"Edit",
  278. type: 'text',
  279. },
  280. remove: {
  281. show: auth('role:Delete'),
  282. size: 'small',
  283. type: 'text',
  284. icon:"Delete",
  285. },
  286. /* permission: {
  287. type: 'primary',
  288. text: '权限配置',
  289. show: auth('role:Permission'),
  290. click: (clickContext: any): void => {
  291. const { row } = clickContext;
  292. context.RoleDrawer.handleDrawerOpen(row);
  293. context.RoleMenuBtn.setState([]);
  294. context.RoleMenuField.setState([]);
  295. },
  296. }, */
  297. },
  298. },
  299. form: {
  300. col: { span: 24 },
  301. labelWidth: '100px',
  302. wrapper: {
  303. is: 'el-dialog',
  304. width: '600px',
  305. },
  306. },
  307. columns: {
  308. _selection: {
  309. title: '选择',
  310. form: { show: false },
  311. column: {
  312. type: 'selection',
  313. align: 'center',
  314. width: 50,
  315. fixed: 'left',
  316. columnSetDisabled: true,
  317. },
  318. },
  319. id: {
  320. title: 'ID',
  321. column: { show: true ,width:80,},
  322. search: { show: false },
  323. form: { show: false },
  324. },
  325. question: {
  326. title: '题目内容',
  327. search: {
  328. show: true,
  329. size: 'small',
  330. col:{ span:3},
  331. },
  332. column: {
  333. showOverflowTooltip: true, // 超出显示提示
  334. width: 300 ,// 固定列宽
  335. sortable: 'custom',
  336. },
  337. form: {
  338. rules: [{ required: true, message: '题目内容必填' }],
  339. component: {
  340. placeholder: '请输入题目内容',
  341. },
  342. },
  343. },
  344. /* question_type: {
  345. title: '问题类型',
  346. search: { show: true },
  347. type: 'dict-select',
  348. column: {
  349. minWidth: 100,
  350. },
  351. dict: dict({
  352. data: [
  353. { value: 1, label: '专业技能' },
  354. { value: 2, label: '通用能力' },
  355. { value: 3, label: '个人特质' }
  356. ]
  357. }),
  358. form: {
  359. rules: [{ required: true, message: '问题类型必填' }],
  360. component: {
  361. placeholder: '请选择问题类型',
  362. },
  363. helper: '选择问题的类型分类',
  364. },
  365. }, */
  366. question_form: {
  367. title: '题目形式',
  368. search: { show: true,
  369. size: 'small',
  370. col:{ span:3},
  371. },
  372. type: 'dict-select',
  373. column: {
  374. minWidth: 100,
  375. },
  376. dict: dict({
  377. // 使用API方式获取数据
  378. getData: async () => {
  379. const res = await api.GetQuestionTypeList();
  380. // 返回question_forms数组
  381. return res.data.question_forms || [];
  382. },
  383. label: 'label',
  384. value: 'value'
  385. }),
  386. form: {
  387. rules: [{ required: true, message: '题目形式必填' }],
  388. component: {
  389. placeholder: '请选择题目形式',
  390. onChange: ({ form }: { form: any }) => {
  391. // 重置相关字段
  392. if (form.question_form === 1) {
  393. // 单选题
  394. form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
  395. } else if (form.question_form === 2) {
  396. // 多选题
  397. form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
  398. } else if (form.question_form === 4) {
  399. // 得分题 - 初始化五个评分选项
  400. form.options = [
  401. { option_text: '无', score: 0, sort: 1 },
  402. { option_text: '轻度', score: 1, sort: 2 },
  403. { option_text: '中度', score: 2, sort: 3 },
  404. { option_text: '偏重', score: 3, sort: 4 },
  405. { option_text: '严重', score: 4, sort: 5 }
  406. ];
  407. // 初始化评分说明
  408. if (!form.scoring_reference) {
  409. form.scoring_reference = '该评分反映了症状表现的频率与严重程度。';
  410. }
  411. } else {
  412. // 开放问题
  413. form.options = [];
  414. }
  415. }
  416. },
  417. helper: '选择题目的形式:开放问题、单选题、多选题、色盲题或追加型开放问题',
  418. },
  419. },
  420. position_types: {
  421. title: '适用职位',
  422. search: { show: true,
  423. size: 'small',
  424. col:{ span:3}, },
  425. type: 'dict-select',
  426. column: {
  427. minWidth: 120,
  428. },
  429. dict: dict({
  430. getData: async () => {
  431. const res = await api.GetPositionList({page:1,limit:1000,tenant_id:1});
  432. console.log(res.data);
  433. return res.data || [];
  434. },
  435. label: 'title',
  436. value: 'id'
  437. }),
  438. form: {
  439. component: {
  440. /* name: 'el-select', */
  441. props: {
  442. multiple: true,
  443. filterable: true,
  444. placeholder: '请选择适用职位类型',
  445. /* options: [
  446. { value: '0', label: '技术' },
  447. { value: '1', label: '管理' },
  448. ] */
  449. }
  450. },
  451. helper: '选择题目适用的职位类型,可多选'
  452. }
  453. },
  454. category_id: {
  455. title: '分类',
  456. search: { show: true,
  457. size: 'small',
  458. col:{ span:2}, },
  459. type: 'dict-select',
  460. column: {
  461. minWidth: 120,
  462. component: {
  463. // 自定义渲染组件,显示彩色标签
  464. // name: 'fs-component',
  465. render: ({ row }: { row: any }) => {
  466. if (!row.category || row.category.length === 0) return <span>-</span>;
  467. return (
  468. <el-tag
  469. key={row.category.id}
  470. type="warning"
  471. effect="plain"
  472. size="mini"
  473. >
  474. {row.category.name}
  475. </el-tag>
  476. );
  477. }
  478. }
  479. },
  480. dict: dict({
  481. // 使用API方式获取数据
  482. getData: async () => {
  483. const res = await api.GetcategoryList({page:1, limit:1000, tenant_id:1});
  484. return res.data.items;
  485. },
  486. label: 'name',
  487. value: 'id'
  488. }),
  489. form: {
  490. // rules: [{ required: true, message: '题目分类必填' }],
  491. component: {
  492. placeholder: '请选择题目分类',
  493. /* onChange: ({ value }: { value: number }) => {
  494. // 当分类变化时,重新获取对应的标签
  495. fetchTags(value);
  496. } */
  497. },
  498. helper: '选择所属的分类'
  499. },
  500. },
  501. tags:{
  502. title: '标签',
  503. search: {
  504. show: true,
  505. size: 'small',
  506. col:{ span:3},
  507. component: {
  508. props: {
  509. multiple: false, // 在搜索表单中使用单选模式
  510. filterable: true,
  511. placeholder: '请选择标签搜索'
  512. }
  513. }
  514. },
  515. type: 'dict-select',
  516. column: {
  517. minWidth: 150,
  518. component: {
  519. // 自定义渲染组件,显示彩色标签
  520. name: 'fs-component',
  521. render: ({ row }: { row: any }) => {
  522. if (!row.tags || row.tags.length === 0) return <span>-</span>;
  523. return (
  524. <div style="display: flex; flex-wrap: wrap; gap: 4px;">
  525. {row.tags.map((tag: any) => (
  526. <el-tag
  527. key={tag.id}
  528. type="warning"
  529. effect="plain"
  530. size="mini"
  531. >
  532. {tag.name}
  533. </el-tag>
  534. ))}
  535. </div>
  536. );
  537. }
  538. }
  539. },
  540. dict: dict({
  541. // 使用API方式获取数据
  542. getData: async () => {
  543. const res = await api.GetTagList({page:1, limit:1000, tenant_id:1});
  544. return res.data.items;
  545. },
  546. label: 'name',
  547. value: 'id'
  548. }),
  549. form: {
  550. component: {
  551. props: {
  552. multiple: true, // 在编辑表单中保持多选
  553. filterable: true,
  554. placeholder: '请选择标签'
  555. }
  556. },
  557. helper: '选择题目关联的标签,可多选'
  558. }
  559. },
  560. recommended_duration: {
  561. title: '建议时长(秒)',
  562. search: { show: false },
  563. column: {
  564. minWidth: 100,
  565. },
  566. form: {
  567. value: 60,
  568. component: {
  569. name: 'el-input-number',
  570. props: {
  571. min: 10,
  572. max: 600,
  573. step: 10
  574. }
  575. },
  576. helper: '建议回答此题目的时长,单位为秒'
  577. }
  578. },
  579. /* difficulty: {
  580. title: '难度等级',
  581. search: { show: true },
  582. type: 'dict-select',
  583. column: {
  584. minWidth: 80,
  585. },
  586. dict: dict({
  587. data: [
  588. { value: 1, label: '初级' },
  589. { value: 2, label: '中级' },
  590. { value: 3, label: '高级' }
  591. ]
  592. }),
  593. form: {
  594. rules: [{ required: true, message: '难度等级必填' }],
  595. helper: '选择题目的难度级别'
  596. },
  597. }, */
  598. is_system: {
  599. title: '系统题目',
  600. search: { show: false },
  601. type: 'dict-select',
  602. column: {
  603. width: 100,
  604. /* component: {
  605. name: 'fs-component',
  606. render: ({ row }: { row: any }) => {
  607. return row.is_system ?
  608. <el-tag type="success" size="small">是</el-tag> :
  609. <el-tag type="info" size="small">否</el-tag>;
  610. }
  611. } */
  612. },
  613. dict: dict({
  614. data: [
  615. { value: true, label: '是' },
  616. { value: false, label: '否' }
  617. ]
  618. }),
  619. form: {
  620. value: false,
  621. component: {
  622. placeholder: '请选择是否为系统题目'
  623. },
  624. helper: '是否为系统预设题目'
  625. }
  626. },
  627. status: {
  628. title: '状态',
  629. search: { show: true,
  630. size: 'small',
  631. col:{ span:2},},
  632. type: 'dict-select',
  633. column: {
  634. width: 80,
  635. },
  636. dict: dict({
  637. data: [
  638. { value: 0, label: '停用' },
  639. { value: 1, label: '启用' }
  640. ]
  641. }),
  642. form: {
  643. value: 1,
  644. rules: [{ required: true, message: '状态必填' }],
  645. component: {
  646. placeholder: '请选择状态',
  647. },
  648. helper: '题目的启用状态'
  649. },
  650. },
  651. // 添加红线问题字段
  652. is_required_correct: {
  653. title: '红线问题',
  654. search: { show: true,
  655. size: 'small',
  656. col:{ span:2}, },
  657. type: 'dict-select',
  658. column: {
  659. width: 100,
  660. component: {
  661. name: 'fs-component',
  662. render: ({ row }: { row: any }) => {
  663. // 只有单选题和多选题才显示红线问题状态
  664. if (row.question_form !== 1 && row.question_form !== 2) {
  665. return <span>-</span>;
  666. }
  667. return row.is_required_correct ?
  668. <el-tag type="danger" size="small">是</el-tag> :
  669. <el-tag type="info" size="small">否</el-tag>;
  670. }
  671. }
  672. },
  673. dict: dict({
  674. data: [
  675. { value: true, label: '是' },
  676. { value: false, label: '否' }
  677. ]
  678. }),
  679. form: {
  680. value: false,
  681. show: compute(({ form }) => {
  682. // 只有单选题和多选题才显示红线问题选项
  683. return form && (form.question_form === 1 || form.question_form === 2);
  684. }),
  685. component: {
  686. placeholder: '请选择是否为红线问题'
  687. },
  688. helper: '红线问题是指必须回答正确的题目,否则可能导致面试失败'
  689. }
  690. },
  691. sort: {
  692. title: '排序',
  693. search: { show: false },
  694. column: {
  695. width: 80,
  696. },
  697. form: {
  698. component: {
  699. name: 'el-input-number',
  700. props: {
  701. min: 1,
  702. max: 999
  703. }
  704. },
  705. helper: '题目的排序值,值越小排序越靠前'
  706. }
  707. },
  708. option_items: {
  709. title: '选项列表',
  710. search: { show: false },
  711. column: { show: false },
  712. form: {
  713. show: compute(({ form }) => {
  714. return form && (form.question_form === 1 || form.question_form === 2 || form.question_form === 4);
  715. }),
  716. component: {
  717. name: 'el-card',
  718. children: {
  719. default: ({ form }: { form: any }) => {
  720. // 确保options数组已初始化
  721. if (!form.options) {
  722. form.options = [];
  723. }
  724. // 心理评估题的选项渲染
  725. if (form.question_form === 4) {
  726. return (
  727. <div>
  728. <div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
  729. <span style="flex: 1;">选项内容</span>
  730. <span style="width: 120px; text-align: center;">分值</span>
  731. </div>
  732. {form.options.map((option: any, index: number) => (
  733. <div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
  734. <el-input
  735. v-model={option.option_text}
  736. placeholder="请输入选项内容"
  737. style="flex: 1; margin-right: 10px;"
  738. />
  739. <el-input-number
  740. v-model={option.score}
  741. min={0}
  742. max={10}
  743. style="width: 120px;"
  744. placeholder="分值"
  745. />
  746. </div>
  747. ))}
  748. <div style="display: flex; justify-content: space-between; margin-top: 10px;">
  749. <el-button
  750. type="primary"
  751. onClick={() => {
  752. if (!form.options) {
  753. form.options = [];
  754. }
  755. const sort = form.options.length > 0 ?
  756. Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
  757. form.options.push({ option_text: '', score: 0, sort });
  758. }}
  759. >
  760. 添加选项
  761. </el-button>
  762. {form.options.length > 2 && (
  763. <el-button
  764. type="danger"
  765. onClick={() => {
  766. form.options.pop();
  767. }}
  768. >
  769. 删除最后一项
  770. </el-button>
  771. )}
  772. </div>
  773. </div>
  774. );
  775. }
  776. // 单选题和多选题的选项渲染(原有逻辑)
  777. return (
  778. <div>
  779. <div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
  780. <span style="flex: 1;">选项内容</span>
  781. <span style="width: 80px; text-align: center;">是否正确</span>
  782. </div>
  783. {form.options.map((option: any, index: number) => (
  784. <div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
  785. <el-input
  786. v-model={option.option_text}
  787. placeholder="请输入选项内容"
  788. style="flex: 1; margin-right: 10px;"
  789. />
  790. <el-tooltip
  791. content="设置为正确答案"
  792. placement="top"
  793. effect="light"
  794. >
  795. <div
  796. onClick={() => {
  797. if (form.question_form === 1) {
  798. // 单选题:只能有一个正确答案
  799. form.options.forEach((item: any, idx: number) => {
  800. item.is_correct = (idx === index);
  801. });
  802. } else {
  803. // 多选题:可以有多个正确答案
  804. option.is_correct = !option.is_correct;
  805. }
  806. }}
  807. style="cursor: pointer; width: 80px; text-align: center;"
  808. >
  809. {form.question_form === 1 ? (
  810. // 单选题使用单选按钮
  811. <el-radio
  812. modelValue={option.is_correct}
  813. label={true}
  814. />
  815. ) : (
  816. // 多选题使用复选框
  817. <el-checkbox
  818. modelValue={option.is_correct}
  819. />
  820. )}
  821. </div>
  822. </el-tooltip>
  823. </div>
  824. ))}
  825. <div style="display: flex; justify-content: space-between; margin-top: 10px;">
  826. <el-button
  827. type="primary"
  828. onClick={() => {
  829. // 确保options数组已初始化
  830. if (!form.options) {
  831. form.options = [];
  832. }
  833. // 添加新选项时自动设置排序值
  834. const sort = form.options.length > 0 ?
  835. Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
  836. form.options.push({ option_text: '', is_correct: false, sort });
  837. }}
  838. >
  839. 添加选项
  840. </el-button>
  841. {form.options.length > 2 && (
  842. <el-button
  843. type="danger"
  844. onClick={() => {
  845. form.options.pop();
  846. }}
  847. >
  848. 删除最后一项
  849. </el-button>
  850. )}
  851. </div>
  852. </div>
  853. )
  854. }
  855. }
  856. },
  857. helper: compute(({ form }) => {
  858. if (form.question_form === 1) {
  859. return '添加单选题的选项,并标记正确答案(只能有一个正确答案)';
  860. } else if (form.question_form === 2) {
  861. return '添加多选题的选项,并标记正确答案(可以有多个正确答案)';
  862. } else if (form.question_form === 4) {
  863. return '添加心理评估题的选项,并设置每个选项的分值';
  864. }
  865. return '';
  866. })
  867. }
  868. },
  869. scoring_reference: {
  870. title: '评分标准',
  871. search: { show: false },
  872. column: { show: false },
  873. form: {
  874. component: {
  875. name: 'el-input',
  876. type: 'textarea',
  877. rows: 4,
  878. placeholder: compute(({ form }) => {
  879. if (form && form.question_form === 4) {
  880. return '请输入评分标准说明';
  881. }
  882. return '请输入评分标准说明';
  883. })
  884. },
  885. helper: compute(({ form }) => {
  886. if (form && form.question_form === 4) {
  887. return '评分标准说明,例如:该评分反映了症状表现的频率与严重程度。';
  888. }
  889. return '题目的评分标准';
  890. })
  891. },
  892. },
  893. digital_human_video_url:{
  894. title: '视频是否生成',
  895. search: { show: false },
  896. column: {
  897. width: 120,
  898. component: {
  899. name: 'fs-component',
  900. render: ({ row }: { row: any }) => {
  901. return row.digital_human_video_url ?
  902. <el-tag type="success" size="small">是</el-tag> :
  903. <el-tag type="info" size="small">否</el-tag>;
  904. }
  905. }
  906. },
  907. form: {
  908. show: false // 在表单中不显示此字段,通常由系统自动生成
  909. }
  910. }
  911. },
  912. // 确保表格配置正确
  913. table: {
  914. selection: true,
  915. onSelectionChange: (selection: any[]) => {
  916. // 存储选中的行到一个全局变量中
  917. context.selectedRows = selection;
  918. },
  919. },
  920. },
  921. };
  922. };
  923. // 导出批量更新标签方法,供组件使用
  924. export const useBatchUpdateTags = (crudExpose: any) => {
  925. const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
  926. try {
  927. const res = await api.BatchUpdateTags({
  928. question_ids: questionIds,
  929. tags: tagIds,
  930. tenant_id: 1
  931. });
  932. if (res.code === 0) {
  933. successMessage('批量更新标签成功');
  934. // 刷新列表
  935. crudExpose.doRefresh();
  936. }
  937. } catch (error) {
  938. console.error('批量更新标签失败', error);
  939. }
  940. };
  941. return { batchUpdateTags };
  942. };
  943. // 导出批量更新分类方法,供组件使用
  944. export const useBatchUpdateCategory = (crudExpose: any) => {
  945. const batchUpdateCategory = async (questionIds: number[], categoryId: number) => {
  946. try {
  947. const res = await api.BatchUpdateCategory({
  948. question_ids: questionIds,
  949. category_id: categoryId,
  950. tenant_id: "1"
  951. });
  952. if (res.code === 0) {
  953. successMessage('批量更新分类成功');
  954. // 刷新列表
  955. crudExpose.doRefresh();
  956. }
  957. } catch (error) {
  958. console.error('批量更新分类失败', error);
  959. }
  960. };
  961. return { batchUpdateCategory };
  962. };