crud.tsx 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596
  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, watch } from 'vue';
  7. import { ElMessage } from 'element-plus';
  8. // 在文件顶部添加全局接口声明
  9. declare global {
  10. interface Window {
  11. competencyTagsOptions?: any[];
  12. }
  13. }
  14. /**
  15. *
  16. * @param crudExpose:index传递过来的示例
  17. * @param context:index传递过来的自定义参数
  18. * @returns
  19. */
  20. export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
  21. const pageRequest = async (query: any) => {
  22. // 如果存在 question 搜索值,将其赋值给 keyword
  23. console.log('query', query);
  24. if (query.question) {
  25. query.keyword = query.question;
  26. delete query.question_tag;
  27. delete query.question; // 删除原有的 question 参数
  28. }
  29. const params = {
  30. ...query,
  31. question_category: query.category||query.question_category,
  32. /* question_tag: query.tag, */
  33. tenant_id: 1
  34. }
  35. console.log('params', params);
  36. return await api.GetList(params);
  37. };
  38. const editRequest = async ({ form, row }: EditReq) => {
  39. form.id = row.id;
  40. // 处理胜任力标签配置
  41. if (form.competency_tags && form.competency_tags.length > 0 && form.competency_tags_config) {
  42. form.competency_tags = form.competency_tags_config.map((config: any) => ({
  43. id: config.id,
  44. weight: config.weight || 100,
  45. importance: config.importance || 1,
  46. remark: config.remark || ''
  47. }));
  48. } else {
  49. form.competency_tags_config = [];
  50. }
  51. // 调用更新题目的接口
  52. const editResult = await api.UpdateObj(form);
  53. // 如果更新成功且题目形式为0(开放题)或5,异步触发视频生成
  54. if (editResult.code === 2000 && (form.question_form === 0 || form.question_form === 5)) {
  55. // 使用 setTimeout 将视频生成请求放入下一个事件循环
  56. /* setTimeout(async () => {
  57. try {
  58. // 构造视频生成请求参数
  59. const videoParams = {
  60. video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
  61. text: form.question,
  62. question_id: form.id,
  63. guidance_scale: 2.0,
  64. inference_steps: 20,
  65. seed: 1247
  66. };
  67. // 调用视频生成接口
  68. const videoResult = await fetch(`${import.meta.env.VITE_API_URL_MINLONG}/queue/process/text/`, {
  69. method: 'POST',
  70. headers: {
  71. 'Content-Type': 'application/json'
  72. },
  73. body: JSON.stringify(videoParams)
  74. });
  75. if (!videoResult.ok) {
  76. console.warn('数字人视频生成请求失败:', await videoResult.text());
  77. ElMessage({
  78. type: 'warning',
  79. message: '数字人视频生成请求失败,但不影响题目的更新'
  80. });
  81. } else {
  82. ElMessage({
  83. type: 'success',
  84. message: '题目已更新,数字人视频正在重新生成中,请稍后查看'
  85. });
  86. }
  87. } catch (error) {
  88. console.error('调用数字人视频生成接口失败:', error);
  89. ElMessage({
  90. type: 'warning',
  91. message: '数字人视频生成失败,但题目已成功更新'
  92. });
  93. }
  94. }, 0); */
  95. }
  96. return editResult;
  97. };
  98. const delRequest = async ({ row }: DelReq) => {
  99. return await api.DelObj(row.id);
  100. };
  101. const addRequest = async ({ form }: AddReq) => {
  102. // 处理胜任力标签配置
  103. if (form.competency_tags && form.competency_tags.length > 0 && form.competency_tags_config) {
  104. form.competency_tags = form.competency_tags_config.map((config: any) => ({
  105. id: config.id,
  106. weight: config.weight || 100,
  107. importance: config.importance || 1,
  108. remark: config.remark || ''
  109. }));
  110. } else {
  111. form.competency_tags_config = [];
  112. }
  113. // 根据题目类型调用不同的接口
  114. let addResult;
  115. if (form.question_form === 6) { // 填空题
  116. addResult = await api.AddFillQuestion(form);
  117. } else {
  118. addResult = await api.AddObj(form);
  119. }
  120. // 如果添加成功且题目形式为0(开放题)或5,异步触发视频生成
  121. if (addResult.code === 2000 && (form.question_form === 0 || form.question_form === 5)) {
  122. // 使用 setTimeout 将视频生成请求放入下一个事件循环
  123. /* setTimeout(async () => {
  124. try {
  125. // 构造视频生成请求参数
  126. const videoParams = {
  127. video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
  128. text: form.question,
  129. question_id: addResult.data.id,
  130. guidance_scale: 2.0,
  131. inference_steps: 20,
  132. seed: 1247
  133. };
  134. // 调用视频生成接口http://192.168.66.101:7861
  135. const videoResult = await fetch(`${import.meta.env.VITE_API_URL_MINLONG}/queue/process/text/`, {
  136. method: 'POST',
  137. headers: {
  138. 'Content-Type': 'application/json'
  139. },
  140. body: JSON.stringify(videoParams)
  141. });
  142. if (!videoResult.ok) {
  143. console.warn('数字人视频生成请求失败:', await videoResult.text());
  144. ElMessage({
  145. type: 'warning',
  146. message: '数字人视频生成请求失败,但不影响题目的保存'
  147. });
  148. } else {
  149. ElMessage({
  150. type: 'success',
  151. message: '题目已保存,数字人视频正在生成中,请稍后查看'
  152. });
  153. }
  154. } catch (error) {
  155. console.error('调用数字人视频生成接口失败:', error);
  156. ElMessage({
  157. type: 'warning',
  158. message: '数字人视频生成失败,但题目已成功保存'
  159. });
  160. }
  161. }, 0); */
  162. }
  163. return addResult;
  164. };
  165. /* // 修改分类和标签数据获取方式
  166. const categoryOptions = ref<Array<{value: number, label: string}>>([]);
  167. const tagOptions = ref<Array<{value: number, label: string}>>([]);
  168. // 获取分类数据
  169. const fetchCategories = async () => {
  170. try {
  171. const res = await api.GetcategoryList({page:1, limit:1000, tenant_id:1});
  172. if (res.code === 0 && res.data && res.data.items) {
  173. categoryOptions.value = res.data.items;
  174. }
  175. } catch (error) {
  176. console.error('获取分类数据失败', error);
  177. }
  178. };
  179. // 获取标签数据
  180. const fetchTags = async (categoryId?: number) => {
  181. try {
  182. const params = {
  183. page: 1,
  184. limit: 1000,
  185. tenant_id: 1
  186. };
  187. if (categoryId) {
  188. params.category_id = categoryId;
  189. }
  190. const res = await api.GetTagList(params);
  191. if (res.code === 0 && res.data && res.data.items) {
  192. tagOptions.value = res.data.items;
  193. }
  194. } catch (error) {
  195. console.error('获取标签数据失败', error);
  196. }
  197. }; */
  198. // 初始化时获取分类数据
  199. /* onMounted(() => {
  200. fetchCategories();
  201. fetchTags();
  202. }); */
  203. // 添加批量更新标签的方法
  204. const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
  205. try {
  206. const res = await api.BatchUpdateTags({
  207. question_ids: questionIds,
  208. tags: tagIds,
  209. tenant_id: 1
  210. });
  211. if (res.code === 0) {
  212. successMessage('批量更新标签成功');
  213. // 刷新列表
  214. crudExpose.doRefresh();
  215. }
  216. } catch (error) {
  217. console.error('批量更新标签失败', error);
  218. }
  219. };
  220. // 添加批量更新分类的方法
  221. const batchUpdateCategory = async (questionIds: number[], categoryId: number) => {
  222. try {
  223. const res = await api.BatchUpdateCategory({
  224. question_ids: questionIds,
  225. category_id: categoryId,
  226. tenant_id: "1"
  227. });
  228. if (res.code === 0) {
  229. successMessage('批量更新分类成功');
  230. // 刷新列表
  231. crudExpose.doRefresh();
  232. }
  233. } catch (error) {
  234. console.error('批量更新分类失败', error);
  235. }
  236. };
  237. return {
  238. crudOptions: {
  239. toolbar:{
  240. buttons:{
  241. search:{show:false},
  242. // 刷新按钮
  243. refresh:{show:false},
  244. // 紧凑模式
  245. compact:{show:false},
  246. // 导出按钮
  247. export:{
  248. text: '导出',
  249. type: 'primary',
  250. size: 'small',
  251. icon: 'upload',
  252. circle: false,
  253. display: true,
  254. show:false
  255. },
  256. // 列设置按钮
  257. columns:{
  258. show:false
  259. },
  260. }
  261. },
  262. request: {
  263. pageRequest,
  264. addRequest,
  265. editRequest,
  266. delRequest,
  267. },
  268. pagination: {
  269. show: true,
  270. },
  271. // 添加搜索表单配置
  272. search: {
  273. col: { span: 3 },
  274. show: true,
  275. autoSearch:false,
  276. buttons: {
  277. search: {
  278. size: 'small', // 设置查询按钮大小为small
  279. },
  280. reset: {
  281. size: 'small', // 设置重置按钮大小为small
  282. }
  283. }
  284. },
  285. actionbar: {
  286. buttons: {
  287. add: {
  288. size: 'small',
  289. show: auth('role:Create'),
  290. },
  291. // 添加批量绑定标签按钮
  292. batchBindTags: {
  293. text: '批量绑定标签',
  294. type: 'primary',
  295. size: 'small',
  296. show: true,
  297. order: 2,
  298. click: () => {
  299. // 使用存储的选中行
  300. const selection = context.selectedRows || [];
  301. console.log('选中的行:', selection);
  302. if (!selection || selection.length === 0) {
  303. warningMessage('请先选择要操作的题目');
  304. return;
  305. }
  306. // 打开批量绑定标签对话框
  307. context.openBatchTagsDialog(selection);
  308. },
  309. },
  310. // 添加批量设置分类按钮
  311. batchSetCategory: {
  312. text: '批量设置分类',
  313. type: 'primary',
  314. show: true,
  315. size: 'small',
  316. order: 3,
  317. click: () => {
  318. // 使用存储的选中行
  319. const selection = context.selectedRows || [];
  320. console.log('选中的行:', selection);
  321. if (!selection || selection.length === 0) {
  322. warningMessage('请先选择要操作的题目');
  323. return;
  324. }
  325. // 打开批量设置分类对话框
  326. context.openBatchCategoryDialog(selection);
  327. },
  328. },
  329. batchBindCompetencyTags: {
  330. text: '批量绑定胜任力标签',
  331. type: 'primary',
  332. size: 'small',
  333. show: true,
  334. order: 4,
  335. click: () => {
  336. const selection = context.selectedRows || [];
  337. if (!selection || selection.length === 0) {
  338. warningMessage('请先选择要操作的题目');
  339. return;
  340. }
  341. // 打开批量绑定胜任力标签对话框
  342. context.openBatchCompetencyTagsDialog(selection);
  343. },
  344. },
  345. },
  346. },
  347. rowHandle: {
  348. //固定右侧
  349. fixed: 'right',
  350. width: 259,
  351. buttons: {
  352. view: {
  353. show: true,
  354. size: 'small',
  355. icon:"View",
  356. type: 'text',
  357. },
  358. edit: {
  359. show: auth('role:Update'),
  360. size: 'small',
  361. icon:"Edit",
  362. type: 'text',
  363. },
  364. remove: {
  365. show: auth('role:Delete'),
  366. size: 'small',
  367. type: 'text',
  368. icon:"Delete",
  369. },
  370. // 添加生成数字人视频按钮
  371. /* generateVideo: {
  372. text: '生成视频',
  373. type: 'text',
  374. size: 'small',
  375. show: ({ row }) => {
  376. // 只在题目形式为开放题(0)或追加型开放题(5)时显示
  377. return row.question_form === 0 || row.question_form === 5;
  378. },
  379. click: async ({ row }) => {
  380. try {
  381. // 构造视频生成请求参数
  382. const videoParams = {
  383. video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
  384. text: row.question,
  385. question_id: row.id,
  386. guidance_scale: 2.0,
  387. inference_steps: 20,
  388. seed: 1247
  389. };
  390. // 调用视频生成接口
  391. const videoResult = await fetch('http://192.168.66.101:7861/queue/process/text/', {
  392. method: 'POST',
  393. headers: {
  394. 'Content-Type': 'application/json'
  395. },
  396. body: JSON.stringify(videoParams)
  397. });
  398. if (!videoResult.ok) {
  399. console.warn('数字人视频生成请求失败:', await videoResult.text());
  400. ElMessage({
  401. type: 'error',
  402. message: '视频生成请求失败'
  403. });
  404. } else {
  405. ElMessage({
  406. type: 'info',
  407. message: '数字人视频正在生成中...'
  408. });
  409. }
  410. } catch (error) {
  411. console.error('调用数字人视频生成接口失败:', error);
  412. ElMessage({
  413. type: 'error',
  414. message: '视频生成请求失败'
  415. });
  416. }
  417. }
  418. } */
  419. },
  420. },
  421. form: {
  422. col: { span: 24 },
  423. labelWidth: '100px',
  424. wrapper: {
  425. is: 'el-dialog',
  426. width: '600px',
  427. },
  428. },
  429. columns: {
  430. _selection: {
  431. title: '选择',
  432. form: { show: false },
  433. column: {
  434. type: 'selection',
  435. align: 'center',
  436. width: 50,
  437. fixed: 'left',
  438. columnSetDisabled: true,
  439. },
  440. },
  441. id: {
  442. title: 'ID',
  443. column: { show: true ,width:80,},
  444. search: { show: false },
  445. form: { show: false },
  446. },
  447. question: {
  448. title: '题目内容',
  449. search: {
  450. show: true,
  451. size: 'small',
  452. col: { span: 3 },
  453. component: {
  454. name: 'el-input',
  455. props: {
  456. placeholder: '请输入题目内容搜索',
  457. clearable: true
  458. }
  459. }
  460. },
  461. column: {
  462. showOverflowTooltip: true,
  463. width: 300,
  464. sortable: 'custom',
  465. },
  466. form: {
  467. rules: [{ required: true, message: '题目内容必填' }],
  468. component: {
  469. placeholder: '请输入题目内容',
  470. },
  471. },
  472. },
  473. competency_tags:{
  474. title: '胜任力标签',
  475. search: {
  476. show: true,
  477. size: 'small',
  478. component: {
  479. props: {
  480. multiple: false,
  481. filterable: true,
  482. }
  483. },
  484. col:{ span:3 },
  485. },
  486. type: 'dict-select',
  487. column: {
  488. minWidth: 230,
  489. component: {
  490. name: 'fs-component',
  491. render: ({ row }: { row: any }) => {
  492. if (!row.competency_tags || row.competency_tags.length === 0) return <span>-</span>;
  493. const displayTags = row.competency_tags.slice(0, 2);
  494. const remainingCount = row.competency_tags.length - 2;
  495. return (
  496. <div style="display: flex; gap: 4px; align-items: center;">
  497. {displayTags.map((tag: any) => (
  498. <el-tag
  499. key={tag.id}
  500. type="warning"
  501. effect="plain"
  502. size="mini"
  503. style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
  504. title={tag.name}
  505. >
  506. {tag.name.length > 4 ? tag.name.slice(0, 4) + '...' : tag.name}
  507. </el-tag>
  508. ))}
  509. {remainingCount > 0 && (
  510. <el-tooltip
  511. placement="top"
  512. effect="light"
  513. popper-class="tag-tooltip"
  514. >
  515. {{
  516. default: () => (
  517. <el-tag
  518. type="info"
  519. effect="plain"
  520. size="mini"
  521. >
  522. +{remainingCount}
  523. </el-tag>
  524. ),
  525. content: () => (
  526. <div>
  527. <div style="font-weight: bold; margin-bottom: 5px">剩余{remainingCount}个标签:</div>
  528. {row.competency_tags.slice(2).map((tag: any) => (
  529. <div key={tag.id} style="margin: 3px 0">{tag.name}</div>
  530. ))}
  531. </div>
  532. )
  533. }}
  534. </el-tooltip>
  535. )}
  536. </div>
  537. );
  538. }
  539. }
  540. },
  541. dict: dict({
  542. getData: async () => {
  543. const res = await api.GetCompetencyList({page:1, limit:100, tenant_id:1});
  544. window.competencyTagsOptions = res.data.items || [];
  545. return res.data.items;
  546. },
  547. label: 'name',
  548. value: 'id'
  549. }),
  550. form: {
  551. component: {
  552. props: {
  553. multiple: true,
  554. filterable: true,
  555. placeholder: '请选择胜任力标签'
  556. },
  557. value: compute(({ form }) => {
  558. if (!form.competency_tags) return [];
  559. console.log('form.competency_tags', form.competency_tags);
  560. // 如果是数组但元素是对象,提取id
  561. if (Array.isArray(form.competency_tags)) {
  562. const ids = form.competency_tags.map((tag: any) => {
  563. if (typeof tag === 'object' && tag !== null) {
  564. // 同时更新 competency_tags_config
  565. if (!form.competency_tags_config) {
  566. form.competency_tags_config = [];
  567. form.competency_tags = [];
  568. }
  569. // 检查配置是否已存在
  570. const existingConfig = form.competency_tags_config.find(
  571. (config: any) => config.id === tag.id
  572. );
  573. if (!existingConfig) {
  574. form.competency_tags_config.push({
  575. id: tag.id,
  576. competency_name: tag.name,
  577. weight: tag.weight || 100,
  578. importance: tag.importance || 1,
  579. remark: ''
  580. });
  581. form.competency_tags.push(tag.id);
  582. }
  583. return tag.id;
  584. }
  585. return tag;
  586. });
  587. return ids;
  588. }
  589. return form.competency_tags;
  590. }),
  591. onValueChange: ({ value, form }: { value: any[], form: any }) => {
  592. // 保存选中的ID数组
  593. form.competency_tags = value;
  594. // 同步更新competency_tags_config
  595. if (!form.competency_tags_config) {
  596. form.competency_tags_config = [];
  597. }
  598. // 获取当前已有的配置ID
  599. const existingConfigIds = form.competency_tags_config.map((config: any) => config.id);
  600. // 从全局变量获取胜任力标签选项数据
  601. const competencyOptions = window.competencyTagsOptions || [];
  602. // 添加新选择的标签配置
  603. value.forEach((tagId: number) => {
  604. if (!existingConfigIds.includes(tagId)) {
  605. // 查找标签信息
  606. const tagInfo = competencyOptions.find((tag: any) => tag.id === tagId);
  607. if (tagInfo) {
  608. form.competency_tags_config.push({
  609. id: tagId,
  610. competency_name: tagInfo.name,
  611. weight: tagInfo.weight || 100,
  612. importance: tagInfo.importance || 1,
  613. remark: ''
  614. });
  615. }
  616. }
  617. });
  618. // 移除已不存在的标签配置
  619. form.competency_tags_config = form.competency_tags_config.filter(
  620. (config: any) => value.includes(config.id)
  621. );
  622. }
  623. },
  624. helper: '选择题目关联的胜任力标签,可多选'
  625. }
  626. },
  627. competency_tags_config: {
  628. title: '胜任力配置',
  629. search: { show: false },
  630. column: { show: false },
  631. form: {
  632. show: compute(({ form }) => {
  633. // 只有当选择了胜任力标签时才显示配置
  634. return form && form.competency_tags && form.competency_tags.length > 0;
  635. }),
  636. component: {
  637. name: 'el-card',
  638. children: {
  639. default: ({ form }: { form: any }) => {
  640. // 确保competency_tags_config数组已初始化
  641. if (!form.competency_tags_config) {
  642. form.competency_tags_config = [];
  643. }
  644. // 使用ref标记是否已经处理过,避免重复处理
  645. const processed = ref(false);
  646. // 使用onMounted确保只在组件挂载时执行一次
  647. onMounted(() => {
  648. if (!processed.value && form.competency_tags && form.competency_tags.length > 0) {
  649. updateCompetencyConfig();
  650. processed.value = true;
  651. }
  652. });
  653. // 监听competency_tags变化,但避免无限循环
  654. watch(() => [...(form.competency_tags || [])], (newTags, oldTags) => {
  655. // 只有当标签真正变化时才更新配置
  656. if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
  657. updateCompetencyConfig();
  658. }
  659. });
  660. // 抽取更新配置的逻辑到单独的函数
  661. const updateCompetencyConfig = () => {
  662. // 获取当前已有的配置ID
  663. const existingConfigIds = form.competency_tags_config.map((config: any) => config.id);
  664. // 创建新的配置数组
  665. const newConfigs = [...form.competency_tags_config];
  666. // 从全局变量获取胜任力标签选项数据
  667. const competencyOptions = window.competencyTagsOptions || [];
  668. // 添加新选择的标签配置
  669. form.competency_tags.forEach((tagId: number) => {
  670. if (!existingConfigIds.includes(tagId)) {
  671. // 查找标签名称
  672. const tagName = competencyOptions.find((tag: any) => tag.id === tagId)?.name || '未知标签';
  673. // 添加新配置
  674. newConfigs.push({
  675. id: tagId,
  676. competency_name: tagName,
  677. weight: 100, // 默认权重为100%
  678. importance: 1, // 默认重要性为1
  679. remark: '' // 默认备注为空
  680. });
  681. }
  682. });
  683. // 移除已不存在的标签配置
  684. const filteredConfigs = newConfigs.filter(
  685. (config: any) => form.competency_tags.includes(config.id)
  686. );
  687. // 一次性更新配置数组
  688. form.competency_tags_config = filteredConfigs;
  689. };
  690. // 渲染表格
  691. return (
  692. <div>
  693. <el-table border style="width: 100%" data={form.competency_tags_config}>
  694. <el-table-column label="标签" width="200">
  695. {{
  696. default: ({ row }: { row: any }) => (
  697. <span>{row.competency_name}</span>
  698. )
  699. }}
  700. </el-table-column>
  701. <el-table-column label="权重" width="100">
  702. {{
  703. default: ({ row }: { row: any }) => (
  704. <div class="weight-input" style="display: flex; align-items: center;">
  705. <el-input-number
  706. v-model={row.weight}
  707. min={0}
  708. max={100}
  709. step={1}
  710. precision={0}
  711. controls={false}
  712. style="width: calc(100% - 20px)"
  713. />
  714. <span style="margin-left: 4px;">%</span>
  715. </div>
  716. )
  717. }}
  718. </el-table-column>
  719. <el-table-column label="级别" width="100">
  720. {{
  721. default: ({ row }: { row: any }) => (
  722. <el-input-number
  723. v-model={row.importance}
  724. min={1}
  725. controls={false}
  726. style="width: 100%"
  727. />
  728. )
  729. }}
  730. </el-table-column>
  731. {/* <el-table-column label="备注">
  732. {{
  733. default: ({ row }: { row: any }) => (
  734. <el-input
  735. v-model={row.remark}
  736. placeholder="请输入备注"
  737. />
  738. )
  739. }}
  740. </el-table-column> */}
  741. </el-table>
  742. </div>
  743. );
  744. }
  745. }
  746. },
  747. helper: '配置每个胜任力标签的权重、重要性和备注'
  748. }
  749. },
  750. /* question_type: {
  751. title: '问题类型',
  752. search: { show: true },
  753. type: 'dict-select',
  754. column: {
  755. minWidth: 100,
  756. },
  757. dict: dict({
  758. data: [
  759. { value: 1, label: '专业技能' },
  760. { value: 2, label: '通用能力' },
  761. { value: 3, label: '个人特质' }
  762. ]
  763. }),
  764. form: {
  765. rules: [{ required: true, message: '问题类型必填' }],
  766. component: {
  767. placeholder: '请选择问题类型',
  768. },
  769. helper: '选择问题的类型分类',
  770. },
  771. }, */
  772. question_form: {
  773. title: '题目形式',
  774. search: { show: true,
  775. size: 'small',
  776. col:{ span:3},
  777. },
  778. type: 'dict-select',
  779. column: {
  780. minWidth: 120,
  781. },
  782. dict: dict({
  783. // 使用API方式获取数据
  784. getData: async () => {
  785. const res = await api.GetQuestionTypeList();
  786. // 返回question_forms数组
  787. return res.data.question_forms || [];
  788. },
  789. label: 'label',
  790. value: 'value'
  791. }),
  792. form: {
  793. rules: [{ required: true, message: '题目形式必填' }],
  794. component: {
  795. placeholder: '请选择题目形式',
  796. onChange: ({ form }: { form: any }) => {
  797. // 重置相关字段
  798. if (form.question_form === 1) {
  799. // 单选题
  800. form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
  801. } else if (form.question_form === 2) {
  802. // 多选题
  803. form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
  804. } else if (form.question_form === 4) {
  805. // 得分题 - 初始化五个评分选项
  806. form.options = [
  807. { option_text: '无', score: 0, sort: 1 },
  808. { option_text: '轻度', score: 1, sort: 2 },
  809. { option_text: '中度', score: 2, sort: 3 },
  810. { option_text: '偏重', score: 3, sort: 4 },
  811. { option_text: '严重', score: 4, sort: 5 }
  812. ];
  813. // 初始化评分说明
  814. if (!form.scoring_reference) {
  815. form.scoring_reference = '该评分反映了症状表现的频率与严重程度。';
  816. }
  817. } else if (form.question_form === 6) {
  818. // 填空题
  819. form.blank_answer_template = [
  820. { blank_index: 1, correct_answer: '', score: 5 }
  821. ];
  822. // 初始化评分说明
  823. if (!form.scoring_reference) {
  824. form.scoring_reference = '根据答案的准确性评分';
  825. }
  826. } else {
  827. // 开放问题
  828. form.options = [];
  829. }
  830. }
  831. },
  832. helper: '选择题目的形式:开放问题、单选题、多选题、填空题、色盲题或追加型开放问题',
  833. },
  834. },
  835. position_types: {
  836. title: '适用职位',
  837. search: { show: true,
  838. size: 'small',
  839. col:{ span:3}, },
  840. type: 'dict-select',
  841. column: {
  842. minWidth: 120,
  843. maxTagCount: 1
  844. },
  845. dict: dict({
  846. getData: async () => {
  847. const res = await api.GetPositionList({page:1,limit:50,tenant_id:1});
  848. console.log(res.data);
  849. return res.data || [];
  850. },
  851. label: 'title',
  852. value: 'id'
  853. }),
  854. form: {
  855. component: {
  856. /* name: 'el-select', */
  857. props: {
  858. multiple: true,
  859. filterable: true,
  860. placeholder: '请选择适用职位类型',
  861. /* options: [
  862. { value: '0', label: '技术' },
  863. { value: '1', label: '管理' },
  864. ] */
  865. }
  866. },
  867. helper: '选择题目适用的职位类型,可多选'
  868. }
  869. },
  870. category_id: {
  871. title: '分类',
  872. search: { show: true,
  873. size: 'small',
  874. col:{ span:2}, },
  875. type: 'dict-select',
  876. column: {
  877. minWidth: 120,
  878. component: {
  879. // 自定义渲染组件,显示彩色标签
  880. // name: 'fs-component',
  881. render: ({ row }: { row: any }) => {
  882. if (!row.category || row.category.length === 0) return <span>-</span>;
  883. return (
  884. <el-tag
  885. key={row.category.id}
  886. type="warning"
  887. effect="plain"
  888. size="mini"
  889. style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
  890. >
  891. {row.category.name.length > 4 ? row.category.name.slice(0, 4) + '...' : row.category.name}
  892. </el-tag>
  893. );
  894. }
  895. }
  896. },
  897. dict: dict({
  898. // 使用API方式获取数据
  899. getData: async () => {
  900. const res = await api.GetcategoryList({page:1, limit:50, tenant_id:1});
  901. return res.data.items;
  902. },
  903. label: 'name',
  904. value: 'id'
  905. }),
  906. form: {
  907. // rules: [{ required: true, message: '题目分类必填' }],
  908. component: {
  909. placeholder: '请选择题目分类',
  910. /* onChange: ({ value }: { value: number }) => {
  911. // 当分类变化时,重新获取对应的标签
  912. fetchTags(value);
  913. } */
  914. },
  915. helper: '选择所属的分类'
  916. },
  917. },
  918. question_tags:{
  919. title: '标签',
  920. search: {
  921. show: true,
  922. size: 'small',
  923. col:{ span:3 },
  924. component: {
  925. props: {
  926. multiple: false, // 在搜索表单中使用单选模式
  927. filterable: true,
  928. placeholder: '请选择标签搜索'
  929. }
  930. }
  931. },
  932. type: 'dict-select',
  933. column: {
  934. show: false,
  935. minWidth: 220,
  936. },
  937. dict: dict({
  938. // 使用API方式获取数据
  939. getData: async () => {
  940. const res = await api.GetTagList({page:1, limit:50, tenant_id:1});
  941. return res.data.items;
  942. },
  943. label: 'name',
  944. value: 'id'
  945. }),
  946. form: {
  947. component: {
  948. props: {
  949. multiple: true, // 在编辑表单中保持多选
  950. filterable: true,
  951. placeholder: '请选择标签'
  952. }
  953. },
  954. helper: '选择题目关联的标签,可多选'
  955. }
  956. },
  957. tags:{
  958. title: '标签',
  959. search: {
  960. show: false,
  961. size: 'small',
  962. col:{ span:3},
  963. component: {
  964. props: {
  965. multiple: false, // 在搜索表单中使用单选模式
  966. filterable: true,
  967. placeholder: '请选择标签搜索'
  968. }
  969. }
  970. },
  971. type: 'dict-select',
  972. column: {
  973. minWidth: 220,
  974. component: {
  975. name: 'fs-component',
  976. render: ({ row }: { row: any }) => {
  977. if (!row.tags || row.tags.length === 0) return <span>-</span>;
  978. const displayTags = row.tags.slice(0, 2); // 只取前两个标签
  979. const remainingCount = row.tags.length - 2; // 计算剩余标签数量
  980. return (
  981. <div style="display: flex; gap: 4px; align-items: center;">
  982. {displayTags.map((tag: any) => (
  983. <el-tag
  984. key={tag.id}
  985. type="warning"
  986. effect="plain"
  987. size="mini"
  988. style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
  989. title={tag.name}
  990. >
  991. {tag.name.length > 4 ? tag.name.slice(0, 4) + '...' : tag.name}
  992. </el-tag>
  993. ))}
  994. {remainingCount > 0 && (
  995. <el-tooltip
  996. placement="top"
  997. effect="light"
  998. popper-class="tag-tooltip"
  999. >
  1000. {{
  1001. default: () => (
  1002. <el-tag
  1003. type="info"
  1004. effect="plain"
  1005. size="mini"
  1006. >
  1007. +{remainingCount}
  1008. </el-tag>
  1009. ),
  1010. content: () => (
  1011. <div>
  1012. <div style="font-weight: bold; margin-bottom: 5px">剩余{remainingCount}个标签:</div>
  1013. {row.tags.slice(2).map((tag: any) => (
  1014. <div key={tag.id} style="margin: 3px 0">{tag.name}</div>
  1015. ))}
  1016. </div>
  1017. )
  1018. }}
  1019. </el-tooltip>
  1020. )}
  1021. </div>
  1022. );
  1023. }
  1024. }
  1025. },
  1026. dict: dict({
  1027. // 使用API方式获取数据
  1028. getData: async () => {
  1029. const res = await api.GetTagList({page:1, limit:50, tenant_id:1});
  1030. return res.data.items;
  1031. },
  1032. label: 'name',
  1033. value: 'id'
  1034. }),
  1035. form: {
  1036. component: {
  1037. props: {
  1038. multiple: true, // 在编辑表单中保持多选
  1039. filterable: true,
  1040. placeholder: '请选择标签'
  1041. }
  1042. },
  1043. helper: '选择题目关联的标签,可多选'
  1044. }
  1045. },
  1046. recommended_duration: {
  1047. title: '建议时长(秒)',
  1048. search: { show: false },
  1049. column: {
  1050. minWidth: 100,
  1051. },
  1052. form: {
  1053. value: 60,
  1054. component: {
  1055. name: 'el-input-number',
  1056. props: {
  1057. min: 10,
  1058. max: 600,
  1059. step: 10
  1060. }
  1061. },
  1062. helper: '建议回答此题目的时长,单位为秒'
  1063. }
  1064. },
  1065. /* difficulty: {
  1066. title: '难度等级',
  1067. search: { show: true },
  1068. type: 'dict-select',
  1069. column: {
  1070. minWidth: 80,
  1071. },
  1072. dict: dict({
  1073. data: [
  1074. { value: 1, label: '初级' },
  1075. { value: 2, label: '中级' },
  1076. { value: 3, label: '高级' }
  1077. ]
  1078. }),
  1079. form: {
  1080. rules: [{ required: true, message: '难度等级必填' }],
  1081. helper: '选择题目的难度级别'
  1082. },
  1083. }, */
  1084. is_system: {
  1085. title: '系统题目',
  1086. search: { show: false },
  1087. type: 'dict-select',
  1088. column: {
  1089. width: 100,
  1090. /* component: {
  1091. name: 'fs-component',
  1092. render: ({ row }: { row: any }) => {
  1093. return row.is_system ?
  1094. <el-tag type="success" size="small">是</el-tag> :
  1095. <el-tag type="info" size="small">否</el-tag>;
  1096. }
  1097. } */
  1098. },
  1099. dict: dict({
  1100. data: [
  1101. { value: true, label: '是' },
  1102. { value: false, label: '否' }
  1103. ]
  1104. }),
  1105. form: {
  1106. value: false,
  1107. component: {
  1108. placeholder: '请选择是否为系统题目'
  1109. },
  1110. helper: '是否为系统预设题目'
  1111. }
  1112. },
  1113. status: {
  1114. title: '状态',
  1115. search: { show: true,
  1116. size: 'small',
  1117. col:{ span:2},},
  1118. type: 'dict-select',
  1119. column: {
  1120. width: 80,
  1121. },
  1122. dict: dict({
  1123. data: [
  1124. { value: 0, label: '停用' },
  1125. { value: 1, label: '启用' }
  1126. ]
  1127. }),
  1128. form: {
  1129. value: 1,
  1130. rules: [{ required: true, message: '状态必填' }],
  1131. component: {
  1132. placeholder: '请选择状态',
  1133. },
  1134. helper: '题目的启用状态'
  1135. },
  1136. },
  1137. // 添加红线问题字段
  1138. is_required_correct: {
  1139. title: '红线问题',
  1140. search: { show: false,
  1141. size: 'small',
  1142. col:{ span:2}, },
  1143. type: 'dict-select',
  1144. column: {
  1145. width: 100,
  1146. component: {
  1147. name: 'fs-component',
  1148. render: ({ row }: { row: any }) => {
  1149. // 只有单选题和多选题才显示红线问题状态
  1150. if (row.question_form !== 1 && row.question_form !== 2) {
  1151. return <span>-</span>;
  1152. }
  1153. return row.is_required_correct ?
  1154. <el-tag type="danger" size="small">是</el-tag> :
  1155. <el-tag type="info" size="small">否</el-tag>;
  1156. }
  1157. }
  1158. },
  1159. dict: dict({
  1160. data: [
  1161. { value: true, label: '是' },
  1162. { value: false, label: '否' }
  1163. ]
  1164. }),
  1165. form: {
  1166. value: false,
  1167. show: compute(({ form }) => {
  1168. // 只有单选题和多选题才显示红线问题选项
  1169. return form && (form.question_form === 1 || form.question_form === 2);
  1170. }),
  1171. component: {
  1172. placeholder: '请选择是否为红线问题'
  1173. },
  1174. helper: '红线问题是指必须回答正确的题目,否则可能导致面试失败'
  1175. }
  1176. },
  1177. sort: {
  1178. title: '排序',
  1179. search: { show: false },
  1180. column: {
  1181. width: 80,
  1182. },
  1183. form: {
  1184. component: {
  1185. name: 'el-input-number',
  1186. props: {
  1187. min: 1,
  1188. max: 999
  1189. }
  1190. },
  1191. helper: '题目的排序值,值越小排序越靠前'
  1192. }
  1193. },
  1194. option_items: {
  1195. title: '选项列表',
  1196. search: { show: false },
  1197. column: { show: false },
  1198. form: {
  1199. show: compute(({ form }) => {
  1200. return form && (form.question_form === 1 || form.question_form === 2 || form.question_form === 4);
  1201. }),
  1202. component: {
  1203. name: 'el-card',
  1204. children: {
  1205. default: ({ form }: { form: any }) => {
  1206. // 确保options数组已初始化
  1207. if (!form.options) {
  1208. form.options = [];
  1209. }
  1210. // 心理评估题的选项渲染
  1211. if (form.question_form === 4) {
  1212. return (
  1213. <div>
  1214. <div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
  1215. <span style="flex: 1;">选项内容</span>
  1216. <span style="width: 120px; text-align: center;">分值</span>
  1217. </div>
  1218. {form.options.map((option: any, index: number) => (
  1219. <div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
  1220. <el-input
  1221. v-model={option.option_text}
  1222. placeholder="请输入选项内容"
  1223. style="flex: 1; margin-right: 10px;"
  1224. />
  1225. <el-input-number
  1226. v-model={option.score}
  1227. min={0}
  1228. max={10}
  1229. style="width: 120px;"
  1230. placeholder="分值"
  1231. />
  1232. </div>
  1233. ))}
  1234. <div style="display: flex; justify-content: space-between; margin-top: 10px;">
  1235. <el-button
  1236. type="primary"
  1237. onClick={() => {
  1238. if (!form.options) {
  1239. form.options = [];
  1240. }
  1241. const sort = form.options.length > 0 ?
  1242. Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
  1243. form.options.push({ option_text: '', score: 0, sort });
  1244. }}
  1245. >
  1246. 添加选项
  1247. </el-button>
  1248. {form.options.length > 2 && (
  1249. <el-button
  1250. type="danger"
  1251. onClick={() => {
  1252. form.options.pop();
  1253. }}
  1254. >
  1255. 删除最后一项
  1256. </el-button>
  1257. )}
  1258. </div>
  1259. </div>
  1260. );
  1261. }
  1262. // 单选题和多选题的选项渲染(原有逻辑)
  1263. return (
  1264. <div>
  1265. <div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
  1266. <span style="flex: 1;">选项内容</span>
  1267. <span style="width: 80px; text-align: center;">是否正确</span>
  1268. </div>
  1269. {form.options.map((option: any, index: number) => (
  1270. <div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
  1271. <el-input
  1272. v-model={option.option_text}
  1273. placeholder="请输入选项内容"
  1274. style="flex: 1; margin-right: 10px;"
  1275. />
  1276. <el-tooltip
  1277. content="设置为正确答案"
  1278. placement="top"
  1279. effect="light"
  1280. >
  1281. <div
  1282. onClick={() => {
  1283. if (form.question_form === 1) {
  1284. // 单选题:只能有一个正确答案
  1285. form.options.forEach((item: any, idx: number) => {
  1286. item.is_correct = (idx === index);
  1287. });
  1288. } else {
  1289. // 多选题:可以有多个正确答案
  1290. option.is_correct = !option.is_correct;
  1291. }
  1292. }}
  1293. style="cursor: pointer; width: 80px; text-align: center;"
  1294. >
  1295. {form.question_form === 1 ? (
  1296. // 单选题使用单选按钮
  1297. <el-radio
  1298. modelValue={option.is_correct}
  1299. label={true}
  1300. />
  1301. ) : (
  1302. // 多选题使用复选框
  1303. <el-checkbox
  1304. modelValue={option.is_correct}
  1305. />
  1306. )}
  1307. </div>
  1308. </el-tooltip>
  1309. </div>
  1310. ))}
  1311. <div style="display: flex; justify-content: space-between; margin-top: 10px;">
  1312. <el-button
  1313. type="primary"
  1314. onClick={() => {
  1315. // 确保options数组已初始化
  1316. if (!form.options) {
  1317. form.options = [];
  1318. }
  1319. // 添加新选项时自动设置排序值
  1320. const sort = form.options.length > 0 ?
  1321. Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
  1322. form.options.push({ option_text: '', is_correct: false, sort });
  1323. }}
  1324. >
  1325. 添加选项
  1326. </el-button>
  1327. {form.options.length > 2 && (
  1328. <el-button
  1329. type="danger"
  1330. onClick={() => {
  1331. form.options.pop();
  1332. }}
  1333. >
  1334. 删除最后一项
  1335. </el-button>
  1336. )}
  1337. </div>
  1338. </div>
  1339. )
  1340. }
  1341. }
  1342. },
  1343. helper: compute(({ form }) => {
  1344. if (form.question_form === 1) {
  1345. return '添加单选题的选项,并标记正确答案(只能有一个正确答案)';
  1346. } else if (form.question_form === 2) {
  1347. return '添加多选题的选项,并标记正确答案(可以有多个正确答案)';
  1348. } else if (form.question_form === 4) {
  1349. return '添加心理评估题的选项,并设置每个选项的分值';
  1350. }
  1351. return '';
  1352. })
  1353. }
  1354. },
  1355. scoring_reference: {
  1356. title: '评分标准',
  1357. search: { show: false },
  1358. column: { show: false },
  1359. form: {
  1360. component: {
  1361. name: 'el-input',
  1362. type: 'textarea',
  1363. rows: 4,
  1364. placeholder: compute(({ form }) => {
  1365. if (form && form.question_form === 4) {
  1366. return '请输入评分标准说明';
  1367. }
  1368. return '请输入评分标准说明';
  1369. })
  1370. },
  1371. helper: compute(({ form }) => {
  1372. if (form && form.question_form === 4) {
  1373. return '评分标准说明,例如:【团队协作能力】1.优秀:xxxxxx。2.xxxx。3.一般:xxxxx。';
  1374. }
  1375. return '评分标准说明,例如:【团队协作能力】1.优秀:xxxxxx。2.xxxx。3.一般:xxxxx。';
  1376. })
  1377. },
  1378. },
  1379. digital_human_video_url:{
  1380. title: '视频是否生成',
  1381. search: { show: false },
  1382. column: {
  1383. width: 120,
  1384. component: {
  1385. name: 'fs-component',
  1386. render: ({ row }: { row: any }) => {
  1387. return row.digital_human_video_url ?
  1388. <el-tag type="success" size="small">是</el-tag> :
  1389. <el-tag type="info" size="small">否</el-tag>;
  1390. }
  1391. }
  1392. },
  1393. form: {
  1394. show: false // 在表单中不显示此字段,通常由系统自动生成
  1395. }
  1396. },
  1397. blank_answer_template: {
  1398. title: '填空答案模板',
  1399. search: { show: false },
  1400. column: { show: false },
  1401. form: {
  1402. show: compute(({ form }) => {
  1403. return form && form.question_form === 6;
  1404. }),
  1405. component: {
  1406. name: 'el-card',
  1407. children: {
  1408. default: ({ form }: { form: any }) => {
  1409. // 确保blank_answer_template数组已初始化
  1410. if (!form.blank_answer_template) {
  1411. form.blank_answer_template = [{ blank_index: 1, correct_answer: '', score: 5 }];
  1412. }
  1413. // 计算题目中的填空数量
  1414. const blankCount = (form.question || '').split('___').length - 1;
  1415. // 如果填空数量变化,更新模板
  1416. if (blankCount > 0 && form.blank_answer_template.length !== blankCount) {
  1417. form.blank_answer_template = Array.from({ length: blankCount }, (_, i) => ({
  1418. blank_index: i + 1,
  1419. correct_answer: '',
  1420. score: 5
  1421. }));
  1422. }
  1423. return (
  1424. <div>
  1425. <div class="blank-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
  1426. <span style="width: 80px;">填空序号</span>
  1427. <span style="flex: 1;">正确答案</span>
  1428. <span style="width: 120px; text-align: center;">分值</span>
  1429. </div>
  1430. {form.blank_answer_template.map((blank: any, index: number) => (
  1431. <div class="blank-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
  1432. <span style="width: 80px;">{blank.blank_index}</span>
  1433. <el-input
  1434. v-model={blank.correct_answer}
  1435. placeholder="请输入正确答案"
  1436. style="flex: 1; margin: 0 10px;"
  1437. />
  1438. <el-input-number
  1439. v-model={blank.score}
  1440. min={0}
  1441. max={100}
  1442. style="width: 120px;"
  1443. placeholder="分值"
  1444. />
  1445. </div>
  1446. ))}
  1447. <div style="color: #666; font-size: 12px; margin-top: 10px;">
  1448. 提示:在题目中使用"___"(三个下划线)表示填空位置,系统会自动识别填空数量
  1449. </div>
  1450. </div>
  1451. );
  1452. }
  1453. }
  1454. },
  1455. helper: '设置每个填空的正确答案和分值'
  1456. }
  1457. }
  1458. },
  1459. // 确保表格配置正确
  1460. table: {
  1461. selection: true,
  1462. onSelectionChange: (selection: any[]) => {
  1463. // 存储选中的行到一个全局变量中
  1464. context.selectedRows = selection;
  1465. },
  1466. },
  1467. },
  1468. };
  1469. };
  1470. // 导出批量更新标签方法,供组件使用
  1471. export const useBatchUpdateTags = (crudExpose: any) => {
  1472. const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
  1473. try {
  1474. const res = await api.BatchUpdateTags({
  1475. question_ids: questionIds,
  1476. tags: tagIds,
  1477. tenant_id: 1
  1478. });
  1479. if (res.code === 0) {
  1480. successMessage('批量更新标签成功');
  1481. // 刷新列表
  1482. crudExpose.doRefresh();
  1483. }
  1484. } catch (error) {
  1485. console.error('批量更新标签失败', error);
  1486. }
  1487. };
  1488. return { batchUpdateTags };
  1489. };
  1490. // 导出批量更新分类方法,供组件使用
  1491. export const useBatchUpdateCategory = (crudExpose: any) => {
  1492. const batchUpdateCategory = async (questionIds: number[], categoryId: number) => {
  1493. try {
  1494. const res = await api.BatchUpdateCategory({
  1495. question_ids: questionIds,
  1496. category_id: categoryId,
  1497. tenant_id: "1"
  1498. });
  1499. if (res.code === 0) {
  1500. successMessage('批量更新分类成功');
  1501. // 刷新列表
  1502. crudExpose.doRefresh();
  1503. }
  1504. } catch (error) {
  1505. console.error('批量更新分类失败', error);
  1506. }
  1507. };
  1508. return { batchUpdateCategory };
  1509. };
  1510. // 导出批量更新胜任力标签方法,供组件使用
  1511. export const useBatchUpdateCompetencyTags = (crudExpose: any) => {
  1512. const batchUpdateCompetencyTags = async (questionIds: number[], competencyTags: any[]) => {
  1513. try {
  1514. const res = await api.BatchUpdateCompetencyTags({
  1515. question_ids: questionIds,
  1516. competency_tags: competencyTags,
  1517. tenant_id: 1
  1518. });
  1519. if (res.code === 0) {
  1520. successMessage('批量更新胜任力标签成功');
  1521. // 刷新列表
  1522. crudExpose.doRefresh();
  1523. }
  1524. } catch (error) {
  1525. console.error('批量更新胜任力标签失败', error);
  1526. }
  1527. };
  1528. return { batchUpdateCompetencyTags };
  1529. };