yangg 1 tháng trước cách đây
mục cha
commit
600d9bfd7c

+ 17 - 0
src/views/questionBank/list/api.ts

@@ -100,3 +100,20 @@ export function GetQuestionTypeList() {
 	});
 }
 
+export const BatchUpdateCompetencyTags = (data: any) => {
+	return request({
+		url: '/api/system/question/batch_update_competency_tags',
+		method: 'post',
+		data
+	});
+}
+
+// 获取胜任力标签列表
+export function GetCompetencyList(params: any) {
+	return request({
+		url: '/api/system/competency/list',
+		method: 'get',
+		params: {...params, tenant_id: 1}
+	});
+}
+

+ 195 - 0
src/views/questionBank/list/components/BatchCompetencyTagsDialog.vue

@@ -0,0 +1,195 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="批量绑定胜任力标签"
+    width="60%"
+  >
+    <el-form :model="form" label-width="120px">
+      <el-form-item label="胜任力标签">
+        <el-table :data="form.competency_tags" border style="width: 100%">
+          <el-table-column label="标签" width="200">
+            <template #default="{ row }">
+              <el-select 
+                v-model="row.id"
+                filterable
+                placeholder="请选择胜任力标签"
+                style="width: 100%"
+              >
+                <el-option
+                  v-for="item in competencyOptions"
+                  :key="item.id"
+                  :label="item.name"
+                  :value="item.id"
+                />
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column label="权重" width="100">
+            <template #default="{ row }">
+              <div class="weight-input">
+                <el-input-number 
+                  v-model="row.weight" 
+                  :min="0" 
+                  :max="100"
+                  :step="1" 
+                  :precision="0"
+                  :controls="false"
+                  style="width: calc(100% - 20px)"
+                />
+                <span class="percent-sign">%</span>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="重要性" width="100">
+            <template #default="{ row }">
+              <el-input-number 
+                v-model="row.importance" 
+                :min="1"
+                :controls="false"
+                style="width: 100%"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="备注">
+            <template #default="{ row }">
+              <el-input 
+                v-model="row.remark" 
+                placeholder="请输入备注"
+              />
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="80" fixed="right">
+            <template #default="{ $index }">
+              <el-button type="danger" link @click="removeTag($index)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <div style="margin-top: 10px">
+          <el-button type="primary" @click="addTag">添加标签</el-button>
+        </div>
+      </el-form-item>
+     <!--  <el-form-item label="清除现有标签">
+        <el-switch v-model="form.clear_existing" />
+      </el-form-item> -->
+    </el-form>
+    <template #footer>
+      <el-button @click="dialogVisible = false">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">确定</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script lang="ts" setup>
+import { ref, reactive, onMounted } from 'vue'
+import { successMessage } from '/@/utils/message'
+import * as api from '../api'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+  crudExpose: {
+    type: Object,
+    required: true
+  }
+})
+
+const dialogVisible = ref(false)
+const selectedQuestions = ref<number[]>([])
+const competencyOptions = ref<Array<any>>([])
+
+const form = reactive({
+  competency_tags: [] as any[],
+  /* clear_existing: true */
+})
+
+// 获取胜任力标签列表
+const fetchCompetencyList = async () => {
+  try {
+    const res = await api.GetCompetencyList({
+      page: 1,
+      limit: 1000
+    })
+    if (res.code === 2000 && res.data?.items) {
+      competencyOptions.value = res.data.items
+    }
+  } catch (error) {
+    console.error('获取胜任力标签列表失败:', error)
+  }
+}
+
+const open = async (selection: any[]) => {
+  selectedQuestions.value = selection.map(item => item.id)
+  // 打开对话框时获取最新的胜任力标签列表
+  await fetchCompetencyList()
+  dialogVisible.value = true
+}
+
+const addTag = () => {
+  form.competency_tags.push({
+    id: undefined,
+    weight: 100,
+    importance: 1,
+    remark: ''
+  })
+}
+
+const removeTag = (index: number) => {
+  form.competency_tags.splice(index, 1)
+}
+
+const handleSubmit = async () => {
+  // 验证是否选择了标签
+  if (form.competency_tags.some(tag => !tag.id)) {
+    ElMessage.warning('请选择胜任力标签')
+    return
+  }
+
+  try {
+    // 转换权重为小数形式
+    const formattedTags = form.competency_tags.map(tag => ({
+      ...tag,
+      weight: tag.weight // 将百分比转换为小数
+    }))
+
+    const res = await api.BatchUpdateCompetencyTags({
+      question_ids: selectedQuestions.value,
+      competency_tags: formattedTags, // 使用转换后的数据
+      tenant_id: 1
+    })
+    
+    if (res.code === 2000) {
+      successMessage('批量更新胜任力标签成功')
+      dialogVisible.value = false
+      // 刷新列表
+      props.crudExpose.doRefresh()
+      // 重置表单
+      form.competency_tags = []
+    }
+  } catch (error) {
+    console.error('批量更新胜任力标签失败', error)
+  }
+}
+
+// 组件挂载时获取胜任力标签列表
+onMounted(() => {
+  fetchCompetencyList()
+})
+
+defineExpose({
+  open
+})
+</script>
+
+<style scoped>
+.el-input-number {
+  width: 100%;
+}
+
+.weight-input {
+  display: flex;
+  align-items: center;
+}
+
+.percent-sign {
+  margin-left: 4px;
+}
+</style> 

+ 8 - 0
src/views/questionBank/list/components/api.ts

@@ -119,3 +119,11 @@ export function setRoleUsers(roleId: string | number | undefined, data: object)
 		data,
 	});
 }
+
+export function GetCompetencyList(query:any) {
+	return request({
+		url: '/api/system/competency/list',
+		method: 'get',
+		params: {...query,tenant_id:1},
+	});
+}

+ 68 - 0
src/views/questionBank/list/crud.tsx

@@ -276,6 +276,23 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 							context.openBatchCategoryDialog(selection);
 						},
 					},
+					batchBindCompetencyTags: {
+						text: '批量绑定胜任力标签',
+						type: 'primary',
+						size: 'small',
+						show: true,
+						order: 4,
+						click: () => {
+							const selection = context.selectedRows || [];
+							
+							if (!selection || selection.length === 0) {
+								warningMessage('请先选择要操作的题目');
+								return;
+							}
+							// 打开批量绑定胜任力标签对话框
+							context.openBatchCompetencyTagsDialog(selection);
+						},
+					},
 				},
 			},
 			rowHandle: {
@@ -578,6 +595,57 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 						helper: '选择题目关联的标签,可多选'
 					}
 				},
+				competency_tags:{
+					title: '胜任力标签',
+					search: { 
+						show: true,
+						size: 'small',
+						col:{ span:3 },
+					},
+					type: 'dict-select',
+					column: {
+						minWidth: 150,
+						component: {
+							name: 'fs-component',
+							render: ({ row }: { row: any }) => {
+								if (!row.competency_tags || row.competency_tags.length === 0) return <span>-</span>;
+								
+								return (
+									<div style="display: flex; flex-wrap: wrap; gap: 4px;">
+										{row.competency_tags.map((tag: any) => (
+											<el-tag
+												key={tag.id}
+												type="warning"
+												effect="plain"
+												size="mini"
+											>
+												{tag.name}
+											</el-tag>
+										))}
+									</div>
+								);
+							}
+						}
+					},
+					dict: dict({
+						getData: async () => {
+							const res = await api.GetCompetencyList({page:1, limit:1000, tenant_id:1});
+							return res.data.items;
+						},
+						label: 'name',    
+						value: 'id'
+					}),
+					form: {
+						component: {
+							props: {
+								multiple: true,
+								filterable: true,
+								placeholder: '请选择胜任力标签'
+							}
+						},
+						helper: '选择题目关联的胜任力标签,可多选'
+					}
+				},
 				recommended_duration: {
 					title: '建议时长(秒)',
 					search: { show: false },

+ 6 - 0
src/views/questionBank/list/index.vue

@@ -4,6 +4,7 @@
 		<PermissionDrawerCom />
 		<BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
 		<BatchCategoryDialog ref="batchCategoryDialogRef" :crudExpose="crudExpose" />
+		<BatchCompetencyTagsDialog ref="batchCompetencyTagsDialogRef" :crudExpose="crudExpose" />
 	</fs-page>
 </template>
 
@@ -20,9 +21,11 @@ import { successMessage } from '../../../utils/message';
 const PermissionDrawerCom = defineAsyncComponent(() => import('./components/RoleDrawer.vue'));
 const BatchTagsDialog = defineAsyncComponent(() => import('./components/BatchTagsDialog.vue'));
 const BatchCategoryDialog = defineAsyncComponent(() => import('./components/BatchCategoryDialog.vue'));
+const BatchCompetencyTagsDialog = defineAsyncComponent(() => import('./components/BatchCompetencyTagsDialog.vue'));
 
 const batchTagsDialogRef = ref();
 const batchCategoryDialogRef = ref();
+const batchCompetencyTagsDialogRef = ref();
 
 const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
 const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单
@@ -43,6 +46,9 @@ const { crudBinding, crudRef, crudExpose } = useFs({
 		openBatchCategoryDialog: (selection: any) => {
 			batchCategoryDialogRef.value.open(selection);
 		},
+		openBatchCompetencyTagsDialog: (selection: any) => {
+			batchCompetencyTagsDialogRef.value.open(selection);
+		},
 		selectedRows: [] // 存储选中的行
 	},
 });