crud.tsx 46 KB

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