qaz 2 tygodni temu
rodzic
commit
bcbbf81593
91 zmienionych plików z 292 dodań i 24425 usunięć
  1. 1 1
      .env.development
  2. 0 2
      src/App.vue
  3. 0 30
      src/router/route.ts
  4. 1 1
      src/theme/app.scss
  5. 2 2
      src/theme/element.scss
  6. 0 88
      src/views/JobApplication/list/api.ts
  7. 0 116
      src/views/JobApplication/list/components/BatchStatusDialog.vue
  8. 0 153
      src/views/JobApplication/list/components/index.vue
  9. 0 564
      src/views/JobApplication/list/crud.tsx
  10. 0 379
      src/views/JobApplication/list/index.vue
  11. 0 2548
      src/views/JobApplication/report/index.vue
  12. 0 3889
      src/views/JobApplication/report/report.vue
  13. 0 619
      src/views/JobApplication/report/topic.vue
  14. 0 312
      src/views/digitalHuman/detail/index.vue
  15. 0 47
      src/views/digitalHuman/list/api.ts
  16. 0 192
      src/views/digitalHuman/list/components/EditDialog.vue
  17. 0 440
      src/views/digitalHuman/list/crud.tsx
  18. 0 511
      src/views/digitalHuman/list/index.vue
  19. 0 9
      src/views/interface/index.ts
  20. 0 64
      src/views/position/Jobcompetency/api.ts
  21. 0 320
      src/views/position/Jobcompetency/crud.tsx
  22. 0 31
      src/views/position/Jobcompetency/index.vue
  23. 0 624
      src/views/position/create/index.vue
  24. 0 3832
      src/views/position/detail/index.vue
  25. 0 66
      src/views/position/list/api.ts
  26. 0 121
      src/views/position/list/components/BatchTagsDialog.vue
  27. 0 76
      src/views/position/list/components/RoleDrawer.vue
  28. 0 186
      src/views/position/list/components/RoleMenuBtn.vue
  29. 0 118
      src/views/position/list/components/RoleMenuField.vue
  30. 0 81
      src/views/position/list/components/RoleMenuTree.vue
  31. 0 35
      src/views/position/list/components/RoleUsers.vue
  32. 0 129
      src/views/position/list/components/api.ts
  33. 0 630
      src/views/position/list/crud.tsx
  34. 0 128
      src/views/position/list/index.vue
  35. 0 37
      src/views/position/list/stores/RoleDrawerStores.ts
  36. 0 24
      src/views/position/list/stores/RoleMenuBtnStores.ts
  37. 0 24
      src/views/position/list/stores/RoleMenuFieldStores.ts
  38. 0 21
      src/views/position/list/stores/RoleMenuTreeStores.ts
  39. 0 24
      src/views/position/list/stores/RoleUsersStores.ts
  40. 0 99
      src/views/position/list/types.ts
  41. 0 55
      src/views/questionBank/classList/api.ts
  42. 0 292
      src/views/questionBank/classList/crud.tsx
  43. 0 25
      src/views/questionBank/classList/index.vue
  44. 0 55
      src/views/questionBank/labelList/api.ts
  45. 0 285
      src/views/questionBank/labelList/crud.tsx
  46. 0 25
      src/views/questionBank/labelList/index.vue
  47. 0 119
      src/views/questionBank/list/api.ts
  48. 0 109
      src/views/questionBank/list/components/BatchCategoryDialog.vue
  49. 0 195
      src/views/questionBank/list/components/BatchCompetencyTagsDialog.vue
  50. 0 120
      src/views/questionBank/list/components/BatchTagsDialog.vue
  51. 0 76
      src/views/questionBank/list/components/RoleDrawer.vue
  52. 0 186
      src/views/questionBank/list/components/RoleMenuBtn.vue
  53. 0 118
      src/views/questionBank/list/components/RoleMenuField.vue
  54. 0 81
      src/views/questionBank/list/components/RoleMenuTree.vue
  55. 0 35
      src/views/questionBank/list/components/RoleUsers.vue
  56. 0 129
      src/views/questionBank/list/components/api.ts
  57. 0 1467
      src/views/questionBank/list/crud.tsx
  58. 0 63
      src/views/questionBank/list/index.vue
  59. 0 37
      src/views/questionBank/list/stores/RoleDrawerStores.ts
  60. 0 24
      src/views/questionBank/list/stores/RoleMenuBtnStores.ts
  61. 0 24
      src/views/questionBank/list/stores/RoleMenuFieldStores.ts
  62. 0 21
      src/views/questionBank/list/stores/RoleMenuTreeStores.ts
  63. 0 24
      src/views/questionBank/list/stores/RoleUsersStores.ts
  64. 0 99
      src/views/questionBank/list/types.ts
  65. 0 118
      src/views/questionBank/positionList/api.ts
  66. 0 172
      src/views/questionBank/positionList/components/AsideTable/crud.ts
  67. 0 68
      src/views/questionBank/positionList/components/AsideTable/index.vue
  68. 0 115
      src/views/questionBank/positionList/components/DocumentFormCom/index.vue
  69. 0 128
      src/views/questionBank/positionList/components/DocumentTreeCom/index.vue
  70. 0 689
      src/views/questionBank/positionList/crud.tsx
  71. 0 1686
      src/views/questionBank/positionList/index.vue
  72. 22 0
      src/views/system/dept/components/DeptUserCom/crud.tsx
  73. 5 5
      src/views/system/dept/components/DeptUserCom/index.vue
  74. 2 2
      src/views/system/dept/index.vue
  75. 22 0
      src/views/system/dictionary/crud.tsx
  76. 22 0
      src/views/system/downloadCenter/crud.tsx
  77. 23 0
      src/views/system/fileList/crud.tsx
  78. 2 2
      src/views/system/home/index.vue
  79. 22 0
      src/views/system/log/loginLog/crud.tsx
  80. 22 0
      src/views/system/log/operationLog/crud.tsx
  81. 28 29
      src/views/system/login/component/account.vue
  82. 23 0
      src/views/system/menu/components/MenuButtonCom/crud.tsx
  83. 23 0
      src/views/system/messageCenter/crud.tsx
  84. 23 0
      src/views/system/role/crud.tsx
  85. 23 0
      src/views/system/user/crud.tsx
  86. 3 3
      src/views/system/user/index.vue
  87. 23 0
      src/views/system/whiteList/crud.tsx
  88. 0 84
      src/views/talent/list/api.ts
  89. 0 153
      src/views/talent/list/components/index.vue
  90. 0 555
      src/views/talent/list/crud.tsx
  91. 0 379
      src/views/talent/list/index.vue

+ 1 - 1
.env.development

@@ -6,7 +6,7 @@ ENV = 'development'
 # 本地环境接口地址 121.36.251.245
 
 
-VITE_API_URL = 'http://192.168.100.101:9084'
+VITE_API_URL = 'http://192.168.100.187:9084'
 # VITE_API_URL = 'https://backend.qicai321.com'
 VITE_API_WX_URL='https://api.weixin.qq.com/'
 VITE_API_URL_MINLONG='http://117.185.80.170:7861'

+ 0 - 2
src/App.vue

@@ -5,7 +5,6 @@
 		<LockScreen v-if="themeConfig.isLockScreen" />
 		<Setings ref="setingsRef" v-show="themeConfig.lockScreenTime > 1" />
 		<CloseFull v-if="!themeConfig.isLockScreen" />
-<!--		<Upgrade v-if="getVersion" />-->
 	</el-config-provider>
 </template>
 
@@ -25,7 +24,6 @@ import setIntroduction from '/@/utils/setIconfont';
 const LockScreen = defineAsyncComponent(() => import('/@/layout/lockScreen/index.vue'));
 const Setings = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/setings.vue'));
 const CloseFull = defineAsyncComponent(() => import('/@/layout/navBars/breadcrumb/closeFull.vue'));
-const Upgrade = defineAsyncComponent(() => import('/@/layout/upgrade/index.vue'));
 
 // 定义变量内容
 const { messages, locale } = useI18n();

+ 0 - 30
src/router/route.ts

@@ -31,20 +31,6 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
 			isKeepAlive: true,
 		},
 		children: [],
-	},
-	{
-		path: '/personal',
-		name: 'personal',
-		component: () => import('/@/views/system/personal/index.vue'),
-		meta: {
-			title: 'message.router.personal',
-			isLink: '',
-			isHide: false,
-			isKeepAlive: true,
-			isAffix: false,
-			isIframe: false,
-			icon: 'iconfont icon-gerenzhongxin',
-		},
 	}
 ];
 
@@ -103,21 +89,5 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 		meta: {
 			title: 'message.router.personal'
 		},
-	},
-	{
-		path: '/JobApplication/report/report',
-		name: 'report',
-		component: () => import('/@/views/JobApplication/report/report.vue'),
-		meta: {
-			title: '面试报告'
-		},
-	},
-	{
-		path: '/topic',
-		name: 'reportTopic',
-		component: () => import('/@/views/JobApplication/report/topic.vue'),
-		meta: {
-			title: '面试问题'
-		},
 	}
 ];

+ 1 - 1
src/theme/app.scss

@@ -32,7 +32,7 @@ body,
 	width: 100%;
 	height: 100%;
 	/* font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; */
-	font-family: "Golos Text", sans-serif;
+	font-family:Avenir, Helvetica, Arial, sans-serif;// "Golos Text", sans-serif;
 	font-weight: 400;
 	-webkit-font-smoothing: antialiased;
 	-webkit-tap-highlight-color: transparent;

+ 2 - 2
src/theme/element.scss

@@ -86,7 +86,7 @@
 	height: 56px !important;
 	line-height: 56px !important;
 	margin: 0 0 0 5px;
-	font-size: 11px;
+	font-size: 12px;
 }
 .el-menu-item,
 .el-sub-menu__title {
@@ -206,7 +206,7 @@
 	}
 }
 .layout-container .layout-aside .el-scrollbar__view{
-	margin-top: 33px;
+	/* margin-top: 33px; */
 }
 
 /* Tabs 标签页

+ 0 - 88
src/views/JobApplication/list/api.ts

@@ -1,88 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-
-export const apiPrefix = '/api/system/job_applications/';
-export function GetList(query: any) {
-	return request({
-		url: apiPrefix,
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: apiPrefix + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: apiPrefix,
-		method: 'post',
-		data: obj,
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: apiPrefix + obj.id + '/',
-		method: 'put',
-		data: obj,
-	});
-}
-
-export function DelObj(id: DelReq) {
-	return request({
-		url:`/api/system/job_applications/${id}/?tenant_id=1`,
-		method: 'delete',
-		data: { id },
-	});
-}
-export function GetPermission() {
-    return request({
-        url: apiPrefix,
-        method: 'get',
-    });
-}
-
-export function BulkUpdateStatus(data: {
-	application_ids: number[];
-	new_status: number;
-	note?: string;
-	tenant_id: string;
-}) {
-	return request({
-		url: '/api/system/job_applications/bulk_update_status/',
-		method: 'post',
-		data
-	});
-}
-
-export function updateBatchStatus(data: {
-	application_ids: number[];
-	new_status: number;
-	note?: string;
-	tenant_id: string;
-}) {
-	return request({
-		url: '/api/system/job/batch_update_application_status/',
-		method: 'post',
-		data
-	});
-}
-
-/* export function updateBatchTags(data) {
-	return request({
-		url: '/job-application/batch-update-tags',
-		method: 'post',
-		data
-	});
-} */
-
-export function getApplicationStatusSummary() {
-	return request({
-		url: '/api/system/job/application_status_summary/?tenant_id=1',
-		method: 'get',
-	});
-}

+ 0 - 116
src/views/JobApplication/list/components/BatchStatusDialog.vue

@@ -1,116 +0,0 @@
-<template>
-  <el-dialog
-    v-model="dialogVisible"
-    title="批量修改申请状态"
-    width="500px"
-    :close-on-click-modal="false"
-    @closed="handleClosed"
-  >
-    <el-form :model="form" label-width="100px" ref="formRef" :rules="rules">
-      <el-form-item label="新状态" prop="new_status">
-        <el-select v-model="form.new_status" placeholder="请选择新状态" style="width: 100%">
-          <el-option :value="0" label="待面试" />
-          <el-option :value="1" label="已通知面试" />
-          <el-option :value="2" label="已面试" />
-          <el-option :value="3" label="已录用" />
-          <el-option :value="4" label="已拒绝" />
-        </el-select>
-      </el-form-item>
-      <el-form-item label="备注" prop="note">
-        <el-input
-          v-model="form.note"
-          type="textarea"
-          placeholder="请输入备注信息"
-          :rows="3"
-        />
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <el-button @click="dialogVisible = false">取消</el-button>
-      <el-button type="primary" @click="handleSubmit" :loading="loading">确认</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive } from 'vue';
-import { ElMessage, FormInstance } from 'element-plus';
-import { updateBatchStatus } from '../api';
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-const formRef = ref<FormInstance>();
-
-const form = reactive({
-  new_status: undefined as number | undefined,
-  note: '',
-  application_ids: [] as number[]
-});
-
-const rules = {
-  new_status: [{ required: true, message: '请选择新状态', trigger: 'change' }]
-};
-
-const emit = defineEmits(['success']);
-const props = defineProps({
-  crudExpose: {
-    type: Object,
-    required: true
-  }
-});
-
-// 打开对话框
-const open = (selection: any[]) => {
-  dialogVisible.value = true;
-  form.application_ids = selection.map(item => item.id);
-  form.new_status = undefined;
-  form.note = '';
-};
-
-// 提交表单
-const handleSubmit = async () => {
-  if (!formRef.value) return;
-  
-  await formRef.value.validate(async (valid) => {
-    if (!valid) return;
-    
-    loading.value = true;
-    try {
-      const data = {
-        application_ids: form.application_ids,
-        new_status: form.new_status as number,
-        note: form.note,
-        tenant_id: '1'
-      };
-      
-      const res = await updateBatchStatus(data);
-      
-      if (res.code === 2000) {
-        ElMessage.success('批量修改状态成功');
-        dialogVisible.value = false;
-        emit('success');
-        props.crudExpose.doRefresh();
-      } else {
-        ElMessage.error(res.msg || '操作失败');
-      }
-    } catch (error) {
-      console.error('批量修改状态失败:', error);
-      ElMessage.error('操作失败,请重试');
-    } finally {
-      loading.value = false;
-    }
-  });
-};
-
-// 关闭对话框时重置表单
-const handleClosed = () => {
-  if (formRef.value) {
-    formRef.value.resetFields();
-  }
-};
-
-// 暴露方法给父组件
-defineExpose({
-  open
-});
-</script> 

+ 0 - 153
src/views/JobApplication/list/components/index.vue

@@ -1,153 +0,0 @@
-<template>
-  <el-dialog
-    v-model="dialogVisible"
-    title="批量操作"
-    width="500px"
-    :close-on-click-modal="false"
-    :close-on-press-escape="false"
-  >
-    <el-form :model="form" label-width="100px">
-     <!--  <el-form-item label="操作类型">
-        <el-select v-model="form.operationType" placeholder="请选择操作类型" style="width: 100%">
-          <el-option label="修改状态" value="status"></el-option>
-          <el-option label="添加标签" value="tags"></el-option>
-        </el-select>
-      </el-form-item> -->
-
-      <el-form-item v-if="form.operationType === 'status'" label="状态">
-        <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
-          <el-option label="待处理" :value="0"></el-option>
-          <el-option label="已通知面试" :value="1"></el-option>
-          <el-option label="已面试" :value="2"></el-option>
-          <el-option label="已录用" :value="3"></el-option>
-          <el-option label="已拒绝" :value="4"></el-option>
-        </el-select>
-      </el-form-item>
-
-      <el-form-item v-if="form.operationType === 'tags'" label="标签">
-        <el-select
-          v-model="form.tags"
-          multiple
-          filterable
-          allow-create
-          default-first-option
-          placeholder="请选择或创建标签"
-          style="width: 100%"
-        >
-          <el-option
-            v-for="tag in tagOptions"
-            :key="tag.value"
-            :label="tag.label"
-            :value="tag.value"
-          ></el-option>
-        </el-select>
-      </el-form-item>
-    </el-form>
-
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
-      </span>
-    </template>
-  </el-dialog>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive } from 'vue';
-import { ElMessage } from 'element-plus';
-import { updateBatchStatus } from '../api';
-
-const props = defineProps({
-  crudExpose: {
-    type: Object,
-    required: true
-  }
-});
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-const selectedItems = ref([]);
-
-const form = reactive({
-  operationType: 'status',
-  status: undefined,
-  tags: []
-});
-
-// 标签选项,可以根据实际需求从API获取
-const tagOptions = ref([
-  { value: '优质候选人', label: '优质候选人' },
-  { value: '有经验', label: '有经验' },
-  { value: '应届毕业生', label: '应届毕业生' },
-  { value: '技能熟练', label: '技能熟练' }
-]);
-
-// 打开对话框
-const open = (selection: any) => {
-  if (!selection || selection.length === 0) {
-    ElMessage.warning('请至少选择一条记录');
-    return;
-  }
-  
-  selectedItems.value = selection;
-  dialogVisible.value = true;
-  
-  // 重置表单
-  form.operationType = 'status';
-  form.status = undefined;
-  form.tags = [];
-};
-
-// 提交处理
-const handleSubmit = async () => {
-  
-  if (form.operationType === 'tags' && (!form.tags || form.tags.length === 0)) {
-    ElMessage.warning('请至少选择一个标签');
-    return;
-  }
-  
-  try {
-    loading.value = true;
-    const application_ids = selectedItems.value.map((item: any) => item.id);
-    
-    if (form.operationType === 'status') {
-      await updateBatchStatus({
-        application_ids,
-        new_status: form.status,
-        tenant_id: 1
-      });
-      ElMessage.success('批量更新状态成功');
-    } /* else if (form.operationType === 'tags') {
-      await updateBatchTags({
-        ids,
-        tags: form.tags
-      });
-      ElMessage.success('批量更新标签成功');
-    } */
-    
-    // 关闭对话框
-    dialogVisible.value = false;
-    
-    // 刷新列表
-    props.crudExpose.doRefresh();
-  } catch (error) {
-    console.error('批量操作失败:', error);
-    ElMessage.error('操作失败,请重试');
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 暴露方法给父组件
-defineExpose({
-  open
-});
-</script>
-
-<style scoped>
-.dialog-footer {
-  display: flex;
-  justify-content: flex-end;
-}
-</style>

+ 0 - 564
src/views/JobApplication/list/crud.tsx

@@ -1,564 +0,0 @@
-import * as api from './api';
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage,warningMessage } from '/@/utils/message';
-import { auth } from '/@/utils/authFunction';
-import tableSelector from '/@/components/tableSelector/index.vue';
-import { shallowRef, h, createVNode, render } from 'vue';
-import { useRouter } from 'vue-router';
-import { ElDialog, ElDescriptions, ElDescriptionsItem, ElTabs, ElTabPane, ElTable, ElTableColumn, ElButton } from 'element-plus';
-import axios from 'axios';
-
-export const createCrudOptions = function ({ crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter();
-	
-	const pageRequest = async (query: any) => {
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj(row.id);
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	/**
-	 * 懒加载
-	 * @param row
-	 * @returns {Promise<unknown>}
-	 */
-	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
-		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
-			resolve(res.data);
-		});
-	};
-
-	// 添加获取用户信息的方法
-	const getUserProfile = async (userId: number) => {
-		try {
-			const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/system/wechat/user/profile/get?user_id=${userId}&tenant_id=1`);
-			return response.data;
-		} catch (error) {
-			console.error('获取用户信息失败:', error);
-			return null;
-		}
-	};
-
-	// 显示用户信息弹窗
-	const showUserProfileDialog = async (userId: number) => {
-		const profileData = await getUserProfile(userId);
-		if (!profileData || profileData.code !== 2000) {
-			warningMessage('获取用户信息失败');
-			return;
-		}
-		console.log(profileData);
-
-		// 创建弹窗内容
-		const { user_info = {}, profile = {}, educations = [], family_members = [], trainings = [], work_experiences = [] } = profileData.data || {};
-
-		// 创建一个容器元素
-		const container = document.createElement('div');
-		
-		// 创建对话框内容
-		const dialogContent = createVNode(
-			ElDialog,
-			{
-				title: '个人信息详情',
-				width: '70%',
-				modelValue: true,
-				'onUpdate:modelValue': (val: boolean) => {
-					if (!val) {
-						// 关闭对话框时销毁组件
-						render(null, container);
-						document.body.removeChild(container);
-					}
-				},
-				beforeClose: () => {
-					render(null, container);
-					document.body.removeChild(container);
-				}
-			},
-			{
-				default: () => createVNode(ElTabs, { type: 'border-card' }, {
-					default: () => [
-						// 基本信息标签页
-						createVNode(ElTabPane, { label: '基本信息' }, {
-							default: () => createVNode(ElDescriptions, { column: 3, border: true }, {
-								default: () => [
-									createVNode(ElDescriptionsItem, { label: '姓名' }, { default: () => user_info?.name || '-' }),
-									createVNode(ElDescriptionsItem, { label: '电话' }, { default: () => user_info?.phone || '-' }),
-									createVNode(ElDescriptionsItem, { label: '年龄' }, { default: () => user_info?.age || '-' }),
-									createVNode(ElDescriptionsItem, { label: '出生日期' }, { default: () => user_info?.birth_date || '-' }),
-									createVNode(ElDescriptionsItem, { label: '性别' }, { default: () => user_info?.gender_name || '-' }),
-									createVNode(ElDescriptionsItem, { label: '身份证号' }, { default: () => user_info?.id_card || '-' }),
-									createVNode(ElDescriptionsItem, { label: '政治面貌' }, { default: () => profile?.political_status || '-' }),
-									createVNode(ElDescriptionsItem, { label: '民族' }, { default: () => profile?.ethnicity || '-' }),
-									createVNode(ElDescriptionsItem, { label: '身高' }, { default: () => (profile?.height ? profile.height + 'cm' : '-') }),
-									createVNode(ElDescriptionsItem, { label: '体重' }, { default: () => (profile?.weight ? profile.weight + 'kg' : '-') }),
-									createVNode(ElDescriptionsItem, { label: '籍贯' }, { default: () => profile?.native_place || '-' }),
-									createVNode(ElDescriptionsItem, { label: '户口所在地' }, { default: () => profile?.household_location || '-' }),
-									createVNode(ElDescriptionsItem, { label: '现居地址' }, { default: () => profile?.current_address || '-' }),
-									createVNode(ElDescriptionsItem, { label: '婚姻状况' }, { default: () => profile?.marital_status_name || '-' }),
-									createVNode(ElDescriptionsItem, { label: '是否有子女' }, { default: () => (profile?.has_children ? '是' : '否') }),
-									createVNode(ElDescriptionsItem, { label: '期望薪资' }, { default: () => profile?.expected_salary || '-' }),
-									createVNode(ElDescriptionsItem, { label: '紧急联系人' }, { default: () => profile?.emergency_contact || '-' }),
-									createVNode(ElDescriptionsItem, { label: '紧急联系电话' }, { default: () => profile?.emergency_phone || '-' }),
-									createVNode(ElDescriptionsItem, { label: '特长' }, { default: () => profile?.specialties || '-' }),
-									createVNode(ElDescriptionsItem, { label: '人生格言' }, { default: () => profile?.life_motto || '-' }),
-									createVNode(ElDescriptionsItem, { label: '招聘来源' }, { default: () => profile?.recruitment_source_name || '-' }),
-									createVNode(ElDescriptionsItem, { label: '招聘来源详情' }, { default: () => profile?.recruitment_source_detail || '-' }),
-								]
-							})
-						}),
-						
-						// 教育经历标签页
-						createVNode(ElTabPane, { label: '教育经历' }, {
-							default: () => educations && educations.length > 0 
-								? createVNode(ElTable, { data: educations, border: true, stripe: true }, {
-									default: () => [
-										createVNode(ElTableColumn, { prop: 'education_type_name', label: '学历类型' }),
-										createVNode(ElTableColumn, { prop: 'degree_name', label: '学位' }),
-										createVNode(ElTableColumn, { prop: 'school_name', label: '学校名称' }),
-										createVNode(ElTableColumn, { prop: 'major', label: '专业' }),
-										createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
-										createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
-									]
-								})
-								: createVNode('div', { style: 'text-align: center; padding: 20px;' }, { default: () => '暂无教育经历数据' })
-						}),
-						
-						// 家庭成员标签页
-						createVNode(ElTabPane, { label: '家庭成员' }, {
-							default: () => family_members && family_members.length > 0
-								? createVNode(ElTable, { data: family_members, border: true, stripe: true }, {
-									default: () => [
-										createVNode(ElTableColumn, { prop: 'relation', label: '关系' }),
-										createVNode(ElTableColumn, { prop: 'name', label: '姓名' }),
-										createVNode(ElTableColumn, { prop: 'workplace', label: '工作单位' }),
-										createVNode(ElTableColumn, { prop: 'position', label: '职位' }),
-										createVNode(ElTableColumn, { prop: 'phone', label: '联系电话' }),
-									]
-								})
-								: createVNode('div', { style: 'text-align: center; padding: 20px;' }, { default: () => '暂无家庭成员数据' })
-						}),
-						
-						// 工作经历标签页
-						createVNode(ElTabPane, { label: '工作经历' }, {
-							default: () => work_experiences && work_experiences.length > 0
-								? createVNode(ElTable, { data: work_experiences, border: true, stripe: true }, {
-									default: () => [
-										createVNode(ElTableColumn, { prop: 'company_name', label: '公司名称' }),
-										createVNode(ElTableColumn, { prop: 'department', label: '部门' }),
-										createVNode(ElTableColumn, { prop: 'position', label: '职位' }),
-										createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
-										createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
-										createVNode(ElTableColumn, { prop: 'monthly_salary', label: '月薪' }),
-										createVNode(ElTableColumn, { prop: 'company_size', label: '公司规模' }),
-										createVNode(ElTableColumn, { prop: 'supervisor_name', label: '主管姓名' }),
-										createVNode(ElTableColumn, { prop: 'supervisor_phone', label: '主管电话' }),
-										createVNode(ElTableColumn, { prop: 'resignation_reason', label: '离职原因' }),
-									]
-								})
-								: createVNode('div', { style: 'text-align: center; padding: 20px;' }, { default: () => '暂无工作经历数据' })
-						}),
-						
-						// 培训经历标签页已被注释掉,保持原样
-					]
-				}),
-				footer: () => createVNode(ElButton, {
-					onClick: () => {
-						render(null, container);
-						document.body.removeChild(container);
-					}
-				}, { default: () => '关闭' })
-			}
-		);
-
-		// 将容器添加到body
-		document.body.appendChild(container);
-		
-		// 渲染对话框到容器
-		render(dialogContent, container);
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						show: false,//auth('area:Create'),
-					},
-					// 添加批量操作按钮
-					// 添加批量绑定标签按钮
-					/* batchBindTags: {
-						text: '批量绑定标签',
-						type: 'primary',
-						show: true,
-						order: 2,
-						click: () => {
-							// 使用正确的方法获取选中行
-							const selection = context.selectedRows || [];
-							console.log('选中的行:', selection);
-							
-							if (!selection || selection.length === 0) {
-								warningMessage('请先选择要操作的申请');
-								return;
-							}
-							// 打开批量绑定标签对话框
-							context.openBatchTagsDialog(selection);
-						},
-					}, */
-					// 添加批量修改状态按钮
-					batchUpdateStatus: {
-						text: '批量修改状态',
-						type: 'primary',
-						show: true,
-						order: 1,
-						size: 'small',
-						click: () => {
-							const selection = context.selectedRows || [];
-							
-							if (!selection || selection.length === 0) {
-								warningMessage('请先选择要操作的申请');
-								return;
-							}
-							// 打开批量修改状态对话框
-							context.openBatchStatusDialog(selection);
-						},
-					},
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 280, // 增加宽度以容纳新按钮
-				buttons: {
-					view: {
-						iconRight: 'view',
-						show: true,
-						type: 'text',
-						click: ({ row }) => {
-							// 在新窗口中打开报告详情页面,确保使用正确的哈希路由格式
-							console.log(row)
-							const baseUrl = window.location.origin;
-							const url = `${baseUrl}/#/JobApplication/report/report?id=${row.id}&tenant_id=${1}&application_id=${row.applicant}`;
-							window.open(url, '_blank');
-						}
-					},
-					profile: { // 添加查看个人信息按钮
-						text: '查看个人信息',
-						iconRight: 'User',
-						type: 'text',
-						show: true,
-						order: 1,
-						click: ({ row }) => {
-							showUserProfileDialog(row.applicant);
-						}
-					},
-					reanalyze: { // 添加重新分析按钮
-						text: '重新分析',
-						iconRight: 'Refresh',
-						type: 'text',
-						show: compute(({ row }) => {
-							return row.status === 2; // 只在状态为"已面试"时显示
-						}),
-						order: 2,
-						click: async ({ row }) => {
-							try {
-								const response = await axios.post(`${import.meta.env.VITE_API_URL}/api/system/job/trigger_comprehensive_analysis`, {
-									application_id: row.id,
-									force_trigger: true
-								});
-								if (response.data.code === 200) {
-									successMessage('已触发重新分析');
-									// 刷新当前页面数据
-									crudExpose.doRefresh();
-								} else {
-									warningMessage(response.data.message || '触发重新分析失败');
-								}
-							} catch (error) {
-								console.error('触发重新分析失败:', error);
-								warningMessage('触发重新分析失败');
-							}
-						}
-					},
-					edit: {
-						iconRight: 'Edit',
-						type: 'text',
-						show: false,//auth('area:Update'),
-					},
-					remove: {
-						iconRight: 'Delete',
-						type: 'text',
-						show: auth('area:Delete'),
-					},
-				},
-			},
-			pagination: {
-				show: true,
-			},
-			table: {
-				selection: true,
-				onSelectionChange: (selection: any[]) => {
-					// 存储选中的行到一个全局变量中
-					context.selectedRows = selection;
-				},
-			},
-			search: {
-				show: true,
-				layout: 'auto',
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				},
-				resetBtn: {
-					show: true,
-					click: () => {
-						// 重置搜索时,也重置树的选中状态
-						const treeRef = document.querySelector('.el-tree');
-						if (treeRef) {
-							// 尝试清除当前高亮
-							const highlightNode = treeRef.querySelector('.is-current');
-							if (highlightNode) {
-								highlightNode.classList.remove('is-current');
-							}
-						}
-					}
-				}
-			},
-			columns: {
-				_selection: {
-					title: '选择',
-					form: { show: false },
-					column: {
-						type: 'selection',
-						align: 'center',
-						width: 50,
-						fixed: 'left',
-						columnSetDisabled: true,
-					},
-				},
-				/* _index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				}, */
-				id:{
-					title: 'ID',
-					form: { show: false },
-					column: {
-					
-						align: 'center',
-						width: '70px',}
-				},
-				report_id: {
-					title: '报告编号',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入报告编号',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 150,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '报告编号必填项' },
-						],
-					},
-				},
-				applicant_name: {
-					title: '申请人姓名',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入申请人姓名',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '申请人姓名必填项' },
-						],
-					},
-				},
-				applicant_phone: {
-					title: '申请人电话',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入申请人电话',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 130,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '申请人电话必填项' },
-						],
-					},
-				},
-				position_title: {
-					title: '申请职位',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入申请职位',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '申请职位必填项' },
-						],
-					},
-				},
-				status: {
-					title: '申请状态',
-					search: {
-						show: true,
-						placeholder: '请选择申请状态',
-						size: 'small',
-						col:{ span:3},
-						/* component: {
-							name: 'el-select',
-							
-						}, */
-					},
-					type: 'dict-select',
-					column: {
-						minWidth: 100,
-					},
-					dict: dict({
-						data: [
-							{ value: 0, label: '待面试' },
-							{ value: 1, label: '已通知面试' },
-							{ value: 2, label: '已面试' },
-							{ value: 3, label: '已录用' },
-							{ value: 4, label: '已拒绝' },
-						],
-					}),
-				},
-				comprehensive_score: {
-					title: '综合评分',
-					type: 'number',
-					column: {
-						minWidth: 100,
-					},
-				},
-				hire_recommendation: {
-					title: '录用建议',
-					type: 'textarea',
-					column: {
-						minWidth: 150,
-						showOverflowTooltip: true,
-					},
-				},
-				/* resume_url: {
-					title: '简历链接',
-					type: 'link',
-					column: {
-						minWidth: 100,
-						component: {
-							name: 'fs-button',
-							type: 'primary',
-							link: true,
-							text: '查看简历',
-							click: compute(({ row }) => {
-								return () => {
-									if (row.resume_url) {
-										window.open(row.resume_url);
-									}
-								};
-							}),
-						},
-						show: compute(({ row }) => {
-							return !!row.resume_url;
-						}),
-					},
-				}, */
-				create_datetime: {
-					title: '创建时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false,
-					},
-				},
-				update_datetime: {
-					title: '更新时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false,
-					},
-				},
-			},
-			
-		},
-	};
-};

+ 0 - 379
src/views/JobApplication/list/index.vue

@@ -1,379 +0,0 @@
-<template>
-	<fs-page>
-		<div class="job-application-container">
-			<div class="sidebar">
-				<div class="tree-container">
-					<!-- 全部 -->
-					<div class="tree-item" :class="{ active: activeNode === 'all' }" @click="handleNodeClick({ type: 'all' })">
-						<div class="item-content">
-							<el-icon><Grid /></el-icon>
-							<span>全部</span>
-						</div>
-						<div class="item-count">{{ totalCount }}人</div>
-					</div>
-					
-					<!-- 待安排 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-1' }" @click="handleNodeClick({ type: 'status', value: 0, id: 'status-1' })">
-						<div class="item-content">
-							<el-icon><Clock /></el-icon>
-							<span>待面试</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[1]">{{ statusCounts[1] }}</div>
-					</div>
-					
-					<!-- 推进中 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-2' }" @click="handleNodeClick({ type: 'status', value: 2, id: 'status-2' })">
-						<div class="item-content">
-							<el-icon><ArrowRight /></el-icon>
-							<span>已面试</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[2]">{{ statusCounts[2] }}</div>
-					</div>
-					
-					<!-- 已入职 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-3' }" @click="handleNodeClick({ type: 'status', value: 3, id: 'status-3' })">
-						<div class="item-content">
-							<el-icon><Check /></el-icon>
-							<span>已录用</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[3]">{{ statusCounts[3] }}</div>
-					</div>
-					
-					<!-- 待召回 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-4' }" @click="handleNodeClick({ type: 'status', value: 4, id: 'status-4' })">
-						<div class="item-content">
-							<el-icon><RefreshRight /></el-icon>
-							<span>已拒绝</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[4]">{{ statusCounts[4] }}</div>
-					</div>
-					
-					<!-- 职位分类 -->
-					<div class="category-title" @click="togglePositionList">
-						<el-icon><Briefcase /></el-icon>
-						<span>职位</span>
-						<el-icon class="expand-icon" :class="{ 'is-expanded': showPositionList }">
-							<ArrowRight />
-						</el-icon>
-					</div>
-					
-					<!-- 职位列表 -->
-					<div v-if="showPositionList" class="position-list">
-						<!-- 全部职位 -->
-						<div class="tree-item" :class="{ active: activeNode === 'position-all' }" @click="handleNodeClick({ type: 'all', id: 'position-all' })">
-							<div class="item-content">
-								<span>全部</span>
-							</div>
-						</div>
-						
-						<!-- 职位列表项 -->
-						<div 
-							v-for="position in positions" 
-							:key="'position-' + position.id" 
-							class="tree-item" 
-							:class="{ active: activeNode === 'position-' + position.id }"
-							@click="handleNodeClick({ type: 'position', value: position.title, id: 'position-' + position.id })"
-						>
-							<div class="item-content">
-								<el-icon><ArrowRight /></el-icon>
-								<span>{{ position.title }}</span>
-							</div>
-							<div class="item-count" v-if="position.count">{{ position.count }}</div>
-						</div>
-					</div>
-				</div>
-			</div>
-			<div class="content">
-				<fs-crud ref="crudRef" v-bind="crudBinding">
-					<!-- 可以添加自定义插槽,如果需要 -->
-				</fs-crud>
-			</div>
-		</div>
-		<BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
-		<BatchStatusDialog ref="batchStatusDialogRef" :crudExpose="crudExpose" @success="handleBatchStatusSuccess" />
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="areas">
-import { onMounted, ref, reactive } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { GetPermission } from './api';
-import { handleColumnPermission } from '/@/utils/columnPermission';
-import { Grid, Clock, ArrowRight, Check, RefreshRight, Briefcase } from '@element-plus/icons-vue';
-import BatchTagsDialog from './components/index.vue';
-import BatchStatusDialog from './components/BatchStatusDialog.vue';
-import { getApplicationStatusSummary } from './api';
-
-const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ 
-	createCrudOptions,
-	context: {
-		openBatchTagsDialog: (selection: any) => {
-			batchTagsDialogRef.value.open(selection);
-		},
-		openBatchStatusDialog: (selection: any) => {
-			batchStatusDialogRef.value.open(selection);
-		},
-		selectedRows: [] // 存储选中的行
-	}
-});
-
-const batchTagsDialogRef = ref();
-const batchStatusDialogRef = ref();
-
-// 状态计数
-const totalCount = ref(0);
-const statusCounts = reactive<Record<number, number>>({
-	1: 0, // 待面试
-	2: 0, // 已面试
-	3: 0, // 已录用
-	4: 0  // 已拒绝
-});
-
-// 职位列表
-const showPositionList = ref(true);
-const positions = ref<Array<{id: number|string, title: string, count?: number}>>([]);
-
-// 获取职位列表
-const fetchPositions = async () => {
-	try {
-		const res = await fetch(`${import.meta.env.VITE_API_URL}/api/system/job/list?page=1&limit=100&tenant_id=1`);
-		const data = await res.json();
-		
-		if (data.code === 2000 && data.data ) {
-			positions.value = data.data.map((item: any) => ({
-				id: item.id,
-				title: item.title || item.name || item.job_name,
-				count: 0
-			}));
-		} else {
-			console.error('获取职位列表失败:', data.msg || '未知错误');
-		}
-	} catch (error) {
-		console.error('获取职位列表异常:', error);
-		// 设置默认职位,以防接口失败
-		positions.value = [{ id: 1, title: '流水线操作工', count: 0 }];
-	}
-};
-
-// 获取申请状态统计数据
-const fetchStatusSummary = async () => {
-	try {
-		const res = await getApplicationStatusSummary();
-		const data = res;
-		console.log(data)
-		if (data.code === 2000 && data.data) {
-			totalCount.value = data.data.total || 0;
-			
-			// 更新状态计数
-			if (data.data.status_data && Array.isArray(data.data.status_data)) {
-				data.data.status_data.forEach((item: any) => {
-					// 注意:API返回的status可能与前端定义的不完全一致,需要映射
-					const statusMap: Record<number, number> = {
-						0: 1, // API中的0对应前端的1(待面试)
-						2: 2, // 已面试
-						3: 3, // 已录用
-						4: 4  // 已拒绝
-					};
-					
-					const frontendStatus = statusMap[item.status];
-					if (frontendStatus && frontendStatus in statusCounts) {
-						statusCounts[frontendStatus] = item.count;
-					}
-				});
-			} else {
-				// 如果没有status_data,则使用单独的字段
-				statusCounts[1] = data.data.pending || 0;
-				statusCounts[2] = data.data.interviewed || 0;
-				statusCounts[3] = data.data.hired || 0;
-				statusCounts[4] = data.data.rejected || 0;
-			}
-		} else {
-			console.error('获取申请状态统计失败:', data.message || '未知错误');
-		}
-	} catch (error) {
-		console.error('获取申请状态统计异常:', error);
-	}
-};
-
-// 切换职位列表显示
-const togglePositionList = () => {
-	showPositionList.value = !showPositionList.value;
-};
-
-// 当前激活的节点
-const activeNode = ref('all');
-
-// 处理树节点点击
-const handleNodeClick = (data: any) => {
-	activeNode.value = data.id || 'all';
-	
-	if (data.type === 'all') {
-		try {
-			// 尝试重置表单,如果方法存在的话
-			const searchRef = crudExpose.getSearchRef();
-			if (searchRef && typeof searchRef.resetFields === 'function') {
-				searchRef.resetFields();
-			}
-		} catch (error) {
-			console.warn('重置表单失败:', error);
-		}
-		
-		// 无论如何都执行搜索,确保数据刷新
-		crudExpose.doSearch({
-			form: {}
-		});
-	} else if (data.type === 'status') {
-		// 按状态筛选
-		crudExpose.doSearch({
-			form: {
-				status: data.value
-			}
-		});
-	} else if (data.type === 'position') {
-		// 按职位筛选
-		crudExpose.doSearch({
-			form: {
-				position_title: data.value
-			}
-		});
-	}
-};
-
-// 更新计数
-const updateCounts = (data: any) => {
-	if (!data || !data.records) return;
-	
-	// 统计各职位数量
-	// 重置职位计数
-	positions.value.forEach(position => {
-		position.count = 0;
-	});
-	
-	// 统计职位数量
-	data.records.forEach((record: any) => {
-		if (record.position_title) {
-			const position = positions.value.find(p => p.title === record.position_title);
-			if (position) {
-				position.count = (position.count || 0) + 1;
-			}
-		}
-	});
-};
-
-// 处理批量状态修改成功
-const handleBatchStatusSuccess = () => {
-	// 刷新数据和状态统计
-	crudExpose.doRefresh();
-	fetchStatusSummary();
-};
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 获取职位列表
-	await fetchPositions();
-	
-	// 获取申请状态统计
-	await fetchStatusSummary();
-	
-	// 设置列权限
-	const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	
-	// 添加数据加载后的回调,用于更新职位计数
-	if (newOptions && newOptions.crudOptions && newOptions.crudOptions.request) {
-		const originalPageRequest = newOptions.crudOptions.request.pageRequest;
-		newOptions.crudOptions.request.pageRequest = async (query: any) => {
-			const res = await originalPageRequest(query);
-			updateCounts(res.data);
-			return res;
-		};
-	}
-	
-	// 重置crudBinding
-	resetCrudOptions(newOptions);
-	
-	// 刷新
-	crudExpose.doRefresh();
-});
-</script>
-
-<style scoped>
-.job-application-container {
-	display: flex;
-	height: 100%;
-}
-
-.sidebar {
-	margin-top: 10px;
-	width: 200px;
-	margin-right: 5px;
-	flex-shrink: 0;
-	background-color: #fff;
-	border-right: 1px solid #ebeef5;
-	border-radius: 10px;
-}
-
-.tree-container {
-	padding: 8px 0;
-}
-
-.tree-item {
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
-	padding: 10px 16px;
-	cursor: pointer;
-	transition: background-color 0.3s;
-}
-
-.tree-item:hover {
-	background-color: #f5f7fa;
-}
-
-.tree-item.active {
-	background-color: #f0f7ff;
-	color: #409eff;
-	border-right: 2px solid #409eff;
-}
-
-.item-content {
-	display: flex;
-	align-items: center;
-	gap: 8px;
-}
-
-.item-count {
-	font-size: 12px;
-	color: #909399;
-}
-
-.category-title {
-	display: flex;
-	align-items: center;
-	padding: 12px 16px;
-	margin-top: 8px;
-	font-weight: 500;
-	border-top: 1px solid #ebeef5;
-	border-bottom: 1px solid #ebeef5;
-	gap: 8px;
-	cursor: pointer;
-}
-
-.expand-icon {
-	margin-left: auto;
-	transition: transform 0.3s;
-}
-
-.expand-icon.is-expanded {
-	transform: rotate(90deg);
-}
-
-.position-list {
-	padding-left: 8px;
-}
-
-.content {
-	flex: 1;
-	overflow: hidden;
-}
-</style>

+ 0 - 2548
src/views/JobApplication/report/index.vue

@@ -1,2548 +0,0 @@
-<script setup lang="ts">
-import { ref, onMounted, computed } from 'vue'
-import { message } from 'ant-design-vue'
-import type { FormInstance } from 'ant-design-vue'
-import { useRoute } from 'vue-router'
-import { ElMessage } from 'element-plus'
-import { Share, Download, Printer, ArrowUp, User } from '@element-plus/icons-vue'
-
-interface CandidateInfo {
-  name: string
-  idNumber: string
-  phoneNumber: string
-  score: number
-  availabilityPeriod: string
-  onboardingTime: string
-  suggestedSalary: string
-  salaryRange:string 
-  dimensions: {
-    teamwork: string
-    learningAbility: string
-    attention: string
-    workAdaptability: string
-    serviceAwareness: string
-  }
-  dimensionDetails: {
-    teamwork: string
-    learningAbility: string
-    attention: string
-    workAdaptability: string
-    serviceAwareness: string
-  }
-  duvAnalysis: {
-    title: string
-    content: string
-    score: string
-    type: 'positive' | 'negative' | 'neutral'
-  }[]
-  interviewRecord: {
-    question: string
-    answer: string
-    analysis: string
-    score: string
-    videoUrl?: string
-    thumbnail?: string
-    question_form?: number
-    is_correct?: boolean | string
-    question_image_url?: string
-    options?: {
-      id: number
-      option_text: string
-      is_correct: boolean
-      sort: number
-    }[]
-    answer_data?: {
-      text_answer: string
-      selected_options: number[]
-      selected_option_details: {
-        id: number
-        is_correct: boolean
-        option_text: string
-      }[]
-    }
-  }[]
-  videoRecords: {
-    category: string
-    videos: {
-      url: string
-      thumbnail: string
-      description: string
-    }[]
-  }[]
-  additionalInfo: {
-    idVerification: string
-    phoneVerification: string
-    leftHandGestures: {
-      passed: boolean
-      images: string[]
-    }
-    rightHandGestures: {
-      passed: boolean
-      images: string[]
-    }
-    faceVerification: {
-      passed: boolean
-      images: string[]
-    }
-    comprehensiveVerification: string
-    handVerification: string
-    handBrightness: string
-    handIntegrity: string
-    handPoseRecognition: string
-  }
-  strengths?: string[]
-  weaknesses?: string[]
-  hireRecommendation?: string
-  hireReason?: string
-}
-
-interface UserProfile {
-  user_info: {
-    id: number;
-    name: string;
-    phone: string;
-    age: number;
-    birth_date: string;
-    gender: number;
-    gender_name: string;
-    professional_skills: string | null;
-    id_card: string;
-    status: number;
-    examine: string;
-    source: string;
-    create_datetime: string;
-    update_datetime: string;
-  };
-  profile: {
-    political_status: string;
-    ethnicity: string;
-    height: string;
-    weight: string;
-    native_place: string;
-    household_location: string;
-    current_address: string;
-    female_status: number;
-    female_status_name: string;
-    marital_status: number;
-    marital_status_name: string;
-    has_children: boolean;
-    expected_salary: string;
-    emergency_contact: string;
-    emergency_phone: string;
-    specialties: string;
-    life_motto: string;
-    recruitment_source: number;
-    recruitment_source_name: string;
-    recruitment_source_detail: string;
-  };
-  educations: Array<{
-    id: number;
-    education_type: number;
-    education_type_name: string;
-    degree: number;
-    degree_name: string;
-    start_date: string;
-    end_date: string;
-    school_name: string;
-    major: string;
-    create_datetime: string;
-    update_datetime: string;
-  }>;
-  family_members: Array<{
-    id: number;
-    relation: string;
-    name: string;
-    workplace: string;
-    position: string;
-    phone: string;
-    create_datetime: string;
-    update_datetime: string;
-  }>;
-  trainings: Array<{
-    id: number;
-    training_name: string;
-    institution: string;
-    start_date: string;
-    end_date: string;
-    description: string;
-    certificate: string;
-    create_datetime: string;
-    update_datetime: string;
-  }>;
-  work_experiences: Array<{
-    id: number;
-    company_name: string;
-    start_date: string;
-    end_date: string;
-    department: string;
-    position: string;
-    company_size: number;
-    monthly_salary: string;
-    supervisor_name: string;
-    supervisor_phone: string;
-    job_description: string | null;
-    achievements: string | null;
-    resignation_reason: string | null;
-    create_datetime: string;
-    update_datetime: string;
-  }>;
-}
-
-// 首先添加类型定义
-interface PhotoResult {
-  url: string;
-  status: string;
-  photo_id: number;
-  detections: any[];
-  description: string;
-  photo_index: number;
-  analysis_model: string;
-  part_verification: {
-    explanation: string;
-    is_correct_part: boolean;
-  } | null;
-  analysis_timestamp: string;
-  overall_assessment: string;
-}
-
-interface VisualAnalysisResults {
-  status: string;
-  timestamp: string;
-  total_photos: number;
-  photo_results: PhotoResult[];
-  application_id: number;
-  failed_analyses: number;
-  successful_analyses: number;
-  verification_summary: {
-    correct_parts: number;
-    total_verified: number;
-    incorrect_parts: number;
-    all_parts_correct: boolean;
-  };
-}
-
-// 修改 apiData 的类型定义
-interface ApiData {
-  applicant?: any;
-  application?: any;
-  position?: any;
-  interview_progress?: any[];
-  posture_photos?: any[];
-  scoring_summary?: {
-    total_score_obtained: number;
-    max_possible_score: number;
-    answered_scoring_questions_count: number;
-    scoring_details: any[];
-  };
-  required_questions_summary?: {
-    all_correct: boolean;
-    failed_questions_details: any[];
-  };
-  visual_analysis_results?: VisualAnalysisResults;
-}
-
-const candidateInfo = ref<CandidateInfo>({
-  name: '',
-  idNumber: '',
-  phoneNumber: '',
-  score: 0,
-  availabilityPeriod: '',
-  onboardingTime: '',
-  suggestedSalary: '',
-  salaryRange: '',
-  dimensions: {
-    teamwork: '欠佳',
-    learningAbility: '优秀',
-    attention: '欠佳',
-    workAdaptability: '优秀',
-    serviceAwareness: '中等'
-  },
-  dimensionDetails: {
-    teamwork: '',
-    learningAbility: '',
-    attention: '',
-    workAdaptability: '',
-    serviceAwareness: ''
-  },
-  duvAnalysis: [
-    {
-      title: '',
-      content: '',
-      score: '',
-      type: 'neutral'
-    },
-    {
-      title: '',
-      content: '',
-      score: '',
-      type: 'neutral'
-    }
-  ],
-  interviewRecord: [
-    {
-      question: '',
-      answer: '',
-      analysis: '',
-      score: '',
-      videoUrl: '',
-      thumbnail: '',
-      question_form: 0,
-      is_correct: false,
-      question_image_url: '',
-      options: [],
-      answer_data: {
-        text_answer: '',
-        selected_options: [],
-        selected_option_details: []
-      }
-    },
-    {
-      question: '',
-      answer: '',
-      analysis: '',
-      score: '',
-      videoUrl: '',
-      thumbnail: '',
-      question_form: 0,
-      is_correct: false,
-      question_image_url: '',
-      options: [],
-      answer_data: {
-        text_answer: '',
-        selected_options: [],
-        selected_option_details: []
-      }
-    }
-  ],
-  videoRecords: [
-    {
-      category: '服务态度测评',
-      videos: [
-        {
-          url: '',
-          thumbnail: '',
-          description: ''
-        },
-        {
-          url: '',
-          thumbnail: '',
-          description: ''
-        }
-      ]
-    }
-  ],
-  additionalInfo: {
-    idVerification: '通过',
-    phoneVerification: '通过',
-    leftHandGestures: {
-      passed: true,
-      images: [
-     
-      ]
-    },
-    rightHandGestures: {
-      passed: true,
-      images: [
-       
-      ]
-    },
-    faceVerification: {
-      passed: true,
-      images: [
-       
-      ]
-    },
-    comprehensiveVerification: '通过',
-    handVerification: '通过',
-    handBrightness: '通过',
-    handIntegrity: '通过',
-    handPoseRecognition: '通过'
-  },
-  strengths: [],
-  weaknesses: [],
-  hireRecommendation: '',
-  hireReason: ''
-})
-
-// 修改 apiData 的声明
-const apiData = ref<ApiData | null>(null);
-
-const loading = ref<boolean>(true)
-const error = ref<string>('')
-
-const route = useRoute()
-
-const fetchApplicationDetail = async () => {
-  loading.value = true
-  try {
-    // 从Vue Router获取参数
-    const id = route.query.id as string || '1'
-    const tenant_id = route.query.tenant_id as string || '1'
-    const application_id = route.query.application_id as string || '1'
-    
-    console.log('Route params:', { id, tenant_id, application_id })
-    
-    // 使用正确的参数调用API
-    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application_detail?tenant_id=${tenant_id}&application_id=${id || application_id}`)
-    const result = await response.json()
-    
-    if (result.code === 2000) {
-      apiData.value = result.data
-      console.log(result.data)
-      updateCandidateInfo(result.data)
-    } else {
-      error.value = result.msg || '获取数据失败'
-    }
-  } catch (err) {
-    console.error('获取申请详情失败:', err)
-    error.value = '获取数据失败,请稍后重试'
-  } finally {
-    loading.value = false
-  }
-}
-
-const updateCandidateInfo = (data: any) => {
-  if (!data) return
-  
-  const { applicant, application, position, interview_progress, posture_photos } = data
-  
-  // 更新基本信息
-  candidateInfo.value.name = applicant?.name || candidateInfo.value.name
-  candidateInfo.value.phoneNumber = applicant?.phone || candidateInfo.value.phoneNumber
-  candidateInfo.value.idNumber = applicant?.id_card || candidateInfo.value.idNumber
-  
-  // 添加薪资相关信息处理
-  if (position) {
-    // 如果API返回了薪资范围,使用API数据
-    if (position.salary_range) {
-      candidateInfo.value.salaryRange = position.salary_range || '面议'
-      /* candidateInfo.value.salaryRange.max = position.salary_range || '面议' */
-    } else {
-      // 否则使用默认值
-      candidateInfo.value.salaryRange = '面议'
-    }
-    
-    // 根据候选人评分计算建议薪资
-    if (position.salary_range && typeof position.salary_range === 'string') {
-      // 尝试从薪资范围字符串中提取最小值和最大值
-      // 支持多种格式:'5000-8000'、'5,000-8,000'、'5000元-8000元'等
-      let min = 0, max = 0;
-      
-      if (position.salary_range.includes('-')) {
-        // 处理范围格式 (例如 "5000-8000")
-        const parts = position.salary_range.split('-');
-        min = parseInt(parts[0].trim().replace(/[^\d]/g, ''), 10);
-        max = parseInt(parts[1].trim().replace(/[^\d]/g, ''), 10);
-      } else if (position.salary_range.includes('~')) {
-        // 处理波浪线格式 (例如 "5000~8000")
-        const parts = position.salary_range.split('~');
-        min = parseInt(parts[0].trim().replace(/[^\d]/g, ''), 10);
-        max = parseInt(parts[1].trim().replace(/[^\d]/g, ''), 10);
-      } else if (position.salary_range.includes('至')) {
-        // 处理中文格式 (例如 "5000至8000")
-        const parts = position.salary_range.split('至');
-        min = parseInt(parts[0].trim().replace(/[^\d]/g, ''), 10);
-        max = parseInt(parts[1].trim().replace(/[^\d]/g, ''), 10);
-      }
-      
-      // 如果成功解析出最小值和最大值,则根据分数计算建议薪资
-      if (!isNaN(min) && !isNaN(max) && min > 0 && max > 0 && max > min) {
-        // 薪资计算逻辑
-        // 1. 分数低于60分:给予最低薪资
-        // 2. 分数在60-80分之间:给予中等薪资(最低薪资 + 薪资范围的1/3)
-        // 3. 分数在80分以上:给予最高薪资
-        console.log("score",application.comprehensive_score)
-        const score = application.comprehensive_score;
-        console.log(score)
-        let suggested = min; // 默认给最低薪资
-        
-        if (score >= 80) {
-          // 80分以上给最高薪资
-          console.log(max)
-          suggested = max;
-        } else if (score >= 60) {
-          // 60-80分给中等薪资(最低薪资 + 薪资范围的1/3)
-          suggested = min + Math.floor((max - min) / 3);
-        }else{
-          // 低于60分保持最低薪资
-          suggested = 0;
-        }
-        
-        // 格式化薪资显示
-        if (suggested >= 10000) {
-          // 如果大于等于10000,显示为"x.x万"
-          const inWan = (suggested / 10000).toFixed(1);
-          candidateInfo.value.suggestedSalary = `${inWan}万`;
-        } else {
-          // 否则直接显示数字
-          candidateInfo.value.suggestedSalary = `${suggested}`;
-        }
-      } else {
-        // 如果无法解析薪资范围,则直接使用原始值
-        candidateInfo.value.suggestedSalary = position.salary_range;
-      }
-    } else {
-      // 如果没有薪资范围信息,设为面议
-      candidateInfo.value.suggestedSalary = '面议';
-    }
-  } else {
-    // 如果没有职位信息,设置默认值
-    candidateInfo.value.salaryRange = '面议';
-    candidateInfo.value.suggestedSalary = '面议';
-  }
-  
-  // 更新综合分析
-  if (application?.comprehensive_analysis) {
-    // 更新综合评分
-    if (application.comprehensive_analysis.comprehensive_score !== null && 
-        application.comprehensive_analysis.comprehensive_score !== undefined) {
-      candidateInfo.value.score = application.comprehensive_analysis.comprehensive_score
-    }
-    
-    // 更新优缺点分析
-    if (application.comprehensive_analysis.video_analysis_data) {
-      const videoData = application.comprehensive_analysis.video_analysis_data
-      
-      // 更新优点
-      if (videoData.strengths && videoData.strengths.length > 0) {
-        candidateInfo.value.strengths = videoData.strengths.filter((s: string) => 
-          s && !s.includes('无法从响应中提取')
-        )
-      }
-      
-      // 更新缺点
-      if (videoData.weaknesses && videoData.weaknesses.length > 0) {
-        candidateInfo.value.weaknesses = videoData.weaknesses.filter((w: string) => 
-          w && !w.includes('无法从响应中提取')
-        )
-      }
-      
-      // 更新录用建议
-      if (application.comprehensive_analysis.hire_recommendation) {
-        candidateInfo.value.hireRecommendation = application.comprehensive_analysis.hire_recommendation
-      }
-      
-      // 更新录用理由
-      if (application.comprehensive_analysis.hire_reason) {
-        candidateInfo.value.hireReason = application.comprehensive_analysis.hire_reason
-      }
-    }
-  }
-  
-  // 更新能力维度评分
-  if (application?.ai_capability_scores) {
-    const dimensionMapping: Record<string, keyof typeof candidateInfo.value.dimensions> = {
-      '专业性': 'workAdaptability',
-      '沟通能力': 'teamwork',
-      '技术匹配度': 'learningAbility',
-      '解决问题能力': 'attention',
-      '服务意识': 'serviceAwareness'
-    }
-    
-    Object.entries(application.ai_capability_scores).forEach(([key, value]) => {
-      const mappedKey = dimensionMapping[key]
-      if (mappedKey && typeof value === 'number') {
-        let rating = '中等'
-        if (value >= 80) rating = '优秀'
-        else if (value < 65) rating = '欠佳'
-        
-        candidateInfo.value.dimensions[mappedKey] = rating
-      }
-    })
-  } else {
-    // 如果没有AI能力评分,根据面试题目分析生成维度评分
-    const dimensionScores: Record<string, number[]> = {
-      teamwork: [],
-      learningAbility: [],
-      attention: [],
-      workAdaptability: [],
-      serviceAwareness: []
-    }
-    
-    interview_progress?.forEach((q: any) => {
-      if (q.video_answer && q.video_answer.ai_score) {
-        // 根据问题类型和内容分配到不同维度
-        if (q.question_text.includes('团队') || q.question_text.includes('合作')) {
-          dimensionScores.teamwork.push(q.video_answer.ai_score)
-        }
-        if (q.question_text.includes('学习') || q.question_text.includes('技能')) {
-          dimensionScores.learningAbility.push(q.video_answer.ai_score)
-        }
-        if (q.question_text.includes('细致') || q.question_text.includes('严谨') || q.question_text.includes('注意')) {
-          dimensionScores.attention.push(q.video_answer.ai_score)
-        }
-        if (q.question_text.includes('适应') || q.question_text.includes('工作')) {
-          dimensionScores.workAdaptability.push(q.video_answer.ai_score)
-        }
-        if (q.question_text.includes('服务') || q.question_text.includes('客户')) {
-          dimensionScores.serviceAwareness.push(q.video_answer.ai_score)
-        }
-      }
-    })
-    
-    // 计算每个维度的平均分并转换为评级
-    Object.entries(dimensionScores).forEach(([key, scores]) => {
-      if (scores.length > 0) {
-        const avgScore = scores.reduce((sum, score) => sum + score, 0) / scores.length
-        let rating = '中等'
-        if (avgScore >= 80) rating = '优秀'
-        else if (avgScore < 65) rating = '欠佳'
-        
-        candidateInfo.value.dimensions[key as keyof typeof candidateInfo.value.dimensions] = rating
-      }
-    })
-  }
-  
-  // 更新维度详情
-  const dimensionDetailsMapping: Record<string, string> = {
-    teamwork: '候选人在团队协作方面的表现',
-    learningAbility: '候选人的学习能力和接受新知识的速度',
-    attention: '候选人对细节的关注程度和工作严谨性',
-    workAdaptability: '候选人适应工作环境和要求的能力',
-    serviceAwareness: '候选人的服务意识和客户导向思维'
-  }
-  
-  Object.keys(candidateInfo.value.dimensionDetails).forEach(key => {
-    const dimension = key as keyof typeof candidateInfo.value.dimensionDetails
-    const rating = candidateInfo.value.dimensions[dimension]
-    let detail = dimensionDetailsMapping[dimension] || ''
-    
-    if (rating === '优秀') {
-      detail += '表现优秀,符合岗位要求。'
-    } else if (rating === '中等') {
-      detail += '表现一般,基本符合岗位要求。'
-    } else {
-      detail += '表现欠佳,需要进一步提升。'
-    }
-    
-    candidateInfo.value.dimensionDetails[dimension] = detail
-  })
-  
-  // 更新面试记录
-  if (interview_progress && interview_progress.length > 0) {
-    candidateInfo.value.interviewRecord = interview_progress
-      .filter((q: any) => q.video_answer || q.answer_data)
-      .map((q: any) => {
-        const record: InterviewRecord = {
-          question: q.question_text || '未提供问题',
-          answer: '',
-          analysis: '',
-          score: '',
-          question_form: q.question_form,
-          options: q.options,
-          answer_data: q.answer_data
-        }
-
-        // 处理视频答案
-        if (q.video_answer) {
-          record.answer = q.video_answer.transcript || ''
-          record.analysis = q.video_answer.ai_analysis?.comment || ''
-          record.score = q.video_answer.ai_score ? `${q.video_answer.ai_score}分` : ''
-          record.videoUrl = q.video_answer.video_url || ''
-        }
-
-        // 处理选择题答案
-        if (q.answer_data) {
-          record.answer = q.answer_data.text_answer || ''
-          record.is_correct = q.answer_data.selected_option_details?.some((opt: any) => opt.is_correct) || false
-        }
-
-        // 处理图片题
-        if (q.question_form === 3) {
-          record.question_image_url = q.question_image_url || ''
-        }
-
-        return record
-      })
-      // 按题目类型排序:视频题(0) -> 图片题(3) -> 选择题(1,2,4)
-      .sort((a, b) => {
-        // 定义题型优先级
-        const getPriority = (questionForm: number) => {
-          if (questionForm === 0) return 0  // 视频题最优先
-          if (questionForm === 3) return 1  // 图片题次之
-          return 2  // 其他题型最后
-        }
-        
-        return getPriority(a.question_form || 0) - getPriority(b.question_form || 0)
-      })
-  }
-  
-  // 更新DUV分析
-  if (application?.visual_analysis_results && application.visual_analysis_results.photo_results) {
-    // 从photo_results中收集所有detections
-    const allDetections: any[] = []
-    
-    application.visual_analysis_results.photo_results.forEach((photo: any) => {
-      if (photo.detections && photo.detections.length > 0) {
-        photo.detections.forEach((detection: any) => {
-          allDetections.push({
-            title: detection.feature || '特征分析',
-            content: detection.location ? 
-              `在${detection.location}发现${detection.feature}${detection.description ? ',' + detection.description : ''}` : 
-              detection.feature + (detection.description ? ',' + detection.description : ''),
-            score: detection.confidence >= 0.8 ? '确认' : '疑似',
-            type: detection.description && detection.description.includes('影响') ? 'negative' : 'neutral'
-          })
-        })
-      }
-    })
-    
-    // 如果有检测结果,更新DUV分析
-    if (allDetections.length > 0) {
-      candidateInfo.value.duvAnalysis = allDetections
-    } else {
-      // 如果没有检测结果,提供默认值
-      candidateInfo.value.duvAnalysis = [
-        {
-          title: '未发现特殊特征',
-          content: '未在照片中检测到特殊特征',
-          score: '正常',
-          type: 'positive'
-        }
-      ]
-    }
-  } else if (application?.visual_analysis_results && application.visual_analysis_results.detections) {
-    // 兼容旧版API格式
-    candidateInfo.value.duvAnalysis = application.visual_analysis_results.detections.map((detection: any) => ({
-      title: detection.feature || '特征分析',
-      content: detection.location ? `在${detection.location}发现${detection.feature}` : detection.feature,
-      score: detection.confidence >= 0.8 ? '确认' : '疑似',
-      type: 'neutral'
-    }))
-  } else {
-    // 如果没有视觉分析结果,提供默认值
-    candidateInfo.value.duvAnalysis = [
-      {
-        title: '未进行DUV分析',
-        content: '未提供DUV分析数据',
-        score: '未知',
-        type: 'neutral'
-      }
-    ]
-  }
-  
-  // 更新手势验证和人脸验证图片
-  if (posture_photos && posture_photos.length > 0) {
-    const leftHandPhotos = posture_photos.filter((p: any) => 
-      p.description.includes('left_') || p.description.includes('左手')
-    ).map((p: any) => p.photo_url)
-    
-    if (leftHandPhotos.length > 0) {
-      candidateInfo.value.additionalInfo.leftHandGestures.images = leftHandPhotos
-    }
-    
-    const rightHandPhotos = posture_photos.filter((p: any) => 
-      p.description.includes('right_') || p.description.includes('右手')
-    ).map((p: any) => p.photo_url)
-    
-    if (rightHandPhotos.length > 0) {
-      candidateInfo.value.additionalInfo.rightHandGestures.images = rightHandPhotos
-    }
-    
-    const facePhotos = posture_photos.filter((p: any) => 
-      p.description.includes('面部') || 
-      (!p.description.includes('left_') && 
-       !p.description.includes('right_') &&
-       !p.description.includes('左手') && 
-       !p.description.includes('右手'))
-    ).map((p: any) => p.photo_url)
-    
-    if (facePhotos.length > 0) {
-      candidateInfo.value.additionalInfo.faceVerification.images = facePhotos
-    }
-  }
-  
-  // 更新视频记录
-  if (interview_progress && interview_progress.length > 0) {
-    const videosByCategory: Record<string, any[]> = {}
-    
-    interview_progress.forEach((q: any) => {
-      if (q.video_answer && q.video_answer.video_url) {
-        const category = q.question_type_display || '面试视频'
-        if (!videosByCategory[category]) {
-          videosByCategory[category] = []
-        }
-        
-        videosByCategory[category].push({
-          url: q.video_answer.video_url,
-          thumbnail: '/images/video-placeholder.jpg',
-          description: q.question_text
-        })
-      }
-    })
-    
-    candidateInfo.value.videoRecords = Object.entries(videosByCategory).map(([category, videos]) => ({
-      category,
-      videos
-    }))
-  }
-  
-  // 更新入职相关信息
-  candidateInfo.value.availabilityPeriod = '无特殊情况'
-  candidateInfo.value.onboardingTime = '随时可以入职'
-}
-
-onMounted(() => {
-  console.log('完整URL:', window.location.href)
-  console.log('路径:', window.location.pathname)
-  console.log('查询字符串:', window.location.search)
-  console.log('哈希:', window.location.hash)
-  
-  fetchApplicationDetail()
-  
-  // 确保所有主要部分都有正确的ID
-  setTimeout(() => {
-    console.log('检查页面ID:');
-    ['comprehensive-assessment', 'image-analysis', 'answer-records', 'other-info'].forEach(id => {
-      const element = document.getElementById(id);
-      console.log(`ID ${id} 存在:`, !!element);
-    });
-  }, 1000);
-  
-  // 添加滚动监听
-  window.addEventListener('scroll', () => {
-    const sections = ['comprehensive-assessment', 'image-analysis', 'answer-records', 'other-info'];
-    
-    // 找到当前在视口中的部分
-    for (const sectionId of sections) {
-      const element = document.getElementById(sectionId);
-      if (element) {
-        const rect = element.getBoundingClientRect();
-        if (rect.top <= 150 && rect.bottom >= 150) {
-          setActiveTab(sectionId);
-          break;
-        }
-      }
-    }
-  }, { passive: true });
-})
-
-const evaluationScore = ref<number>(0)
-const evaluationComments = ref<string>('')
-const formRef = ref<FormInstance>()
-
-const handleSubmit = async () => {
-  try {
-    await formRef.value?.validate()
-    message.success('评估已提交')
-  } catch (error) {
-    message.error('请完成所有必填项')
-  }
-}
-
-const reportTop = ref<HTMLElement | null>(null)
-
-const scrollToTop = () => {
-  console.log('尝试滚动到顶部')
-  
-  // 使用ref引用直接滚动到顶部元素
-  if (reportTop.value) {
-    reportTop.value.scrollIntoView({ behavior: 'smooth', block: 'start' })
-    console.log('使用ref滚动到顶部')
-    return
-  }
-  
-  // 如果ref不可用,尝试通过ID查找元素
-  const topElement = document.getElementById('report-top')
-  if (topElement) {
-    topElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
-    console.log('使用ID滚动到顶部')
-    return
-  }
-  
-  // 如果以上方法都失败,使用之前的备用方法
-  // ... 之前的代码 ...
-}
-
-const handleShare = () => {
-  ElMessage.success('分享链接已复制')
-}
-
-const handleDownload = async () => {
-  try {
-    // 从路由获取参数
-    const id = route.query.id as string || '1'
-    const tenant_id = route.query.tenant_id as string || '1'
-    const application_id = route.query.application_id as string || '1'
-    
-    // 显示下载开始的提示
-    ElMessage.success('报告下载中...')
-    
-    // 调用下载接口
-    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/system/job/export_application_pdf?application_id=${id || application_id}&tenant_id=${tenant_id}`)
-    const result = await response.json()
-    
-    if (result.code === 2000) {
-      // 下载任务创建成功,提示用户去下载中心查看
-      ElMessage.success('下载任务已创建,请前往下载中心查看')
-    } else {
-      // 处理错误情况
-      ElMessage.error(result.msg || '下载任务创建失败')
-    }
-  } catch (error) {
-    console.error('创建下载任务失败:', error)
-    ElMessage.error('创建下载任务失败,请稍后重试')
-  }
-}
-
-// 添加类型定义
-const originalButtonDisplay: string[] = []
-
-// 修改打印函数
-const handlePrint = () => {
-  // 保存当前页面滚动位置
-  const scrollPosition = window.scrollY
-  
-  // 临时修改样式以确保所有内容都能打印
-  const originalOverflow = document.body.style.overflow
-  const originalHeight = document.body.style.height
-  
-  document.body.style.overflow = 'visible'
-  document.body.style.height = 'auto'
-  
-  // 隐藏不需要打印的元素
-  const buttonsToHide = document.querySelectorAll('.fixed')
-  
-  buttonsToHide.forEach((button, index) => {
-    // 添加类型断言
-    const buttonElement = button as HTMLElement
-    originalButtonDisplay[index] = buttonElement.style.display
-    buttonElement.style.display = 'none'
-  })
-  
-  // 延迟执行打印,确保样式已应用
-  setTimeout(() => {
-    window.print()
-    
-    // 打印完成后恢复原始样式
-    document.body.style.overflow = originalOverflow
-    document.body.style.height = originalHeight
-    
-    // 恢复隐藏的按钮
-    buttonsToHide.forEach((button, index) => {
-      // 添加类型断言
-      const buttonElement = button as HTMLElement
-      buttonElement.style.display = originalButtonDisplay[index]
-    })
-    
-    // 恢复滚动位置
-    window.scrollTo(0, scrollPosition)
-    
-    ElMessage.success('打印完成')
-  }, 300)
-}
-
-// 添加base64编码的内联图片作为fallback
-const fallbackImageBase64Ref = ref('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgZmlsbD0iI2VlZWVlZSIvPjxwYXRoIGQ9Ik03NSA2NUExMCAxMCAwIDAgMSA4NSA3NUExMCAxMCAwIDAgMSA3NSA4NUExMCAxMCAwIDAgMSA2NSA3NUExMCAxMCAwIDAgMSA3NSA2NVoiIGZpbGw9IiNhYWFhYWEiLz48cGF0aCBkPSJNMTIwIDEzNUw5MCAxMDVMNjAgMTM1TDYwIDE0MEwxNDAgMTQwTDE0MCAxMzVMMTIwIDExNVoiIGZpbGw9IiNhYWFhYWEiLz48L3N2Zz4=')
-
-// 简化的图片错误处理函数
-const handleImageError = (event: Event) => {
-  const target = event.target as HTMLImageElement
-  if (target) {
-    target.src = fallbackImageBase64Ref.value
-    target.onerror = null // 防止无限循环
-  }
-}
-
-// 修改视频加载和第一帧捕获的函数
-const captureVideoFirstFrame = (videoElement: HTMLVideoElement) => {
-  // 创建一个一次性的事件监听器,在视频可以播放时捕获第一帧
-  const captureFrame = () => {
-    try {
-      // 确保视频已加载足够的数据
-      if (videoElement.readyState >= 2) {
-        // 创建canvas元素
-        const canvas = document.createElement('canvas')
-        canvas.width = videoElement.videoWidth || 320
-        canvas.height = videoElement.videoHeight || 240
-        
-        // 在canvas上绘制视频当前帧
-        const ctx = canvas.getContext('2d')
-        if (ctx) {
-          // 确保视频当前时间设置为开始位置
-          videoElement.currentTime = 0.1
-          
-          // 绘制视频帧到canvas
-          ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height)
-          
-          // 将canvas转换为图片URL
-          const thumbnailUrl = canvas.toDataURL('image/jpeg')
-          
-          // 设置为视频的poster属性
-          videoElement.poster = thumbnailUrl
-          
-          // 清理
-          videoElement.removeEventListener('loadeddata', captureFrame)
-        }
-      }
-    } catch (error) {
-      console.error('捕获视频第一帧失败:', error)
-      // 设置默认缩略图
-      videoElement.poster = fallbackImageBase64Ref.value
-    }
-  }
-  
-  // 添加事件监听器
-  videoElement.addEventListener('loadeddata', captureFrame)
-  
-  // 如果视频已经加载,立即尝试捕获
-  if (videoElement.readyState >= 2) {
-    captureFrame()
-  }
-  
-  // 添加错误处理
-  videoElement.addEventListener('error', () => {
-    console.error('视频加载失败')
-    videoElement.poster = fallbackImageBase64Ref.value
-  })
-  
-  // 设置超时,确保即使视频加载缓慢也能显示默认缩略图
-  setTimeout(() => {
-    if (!videoElement.poster || videoElement.poster === '') {
-      videoElement.poster = fallbackImageBase64Ref.value
-    }
-  }, 3000)
-}
-
-// 视频加载完成后的处理函数
-const handleVideoLoaded = (event: Event) => {
-  const videoElement = event.target as HTMLVideoElement
-  if (videoElement) {
-    // 确保视频已经加载元数据
-    if (videoElement.readyState >= 1) {
-      captureVideoFirstFrame(videoElement)
-    } else {
-      // 如果元数据尚未加载,添加元数据加载事件
-      videoElement.addEventListener('loadedmetadata', () => {
-        captureVideoFirstFrame(videoElement)
-      })
-    }
-  }
-}
-
-// 添加视频错误处理函数
-const handleVideoError = (event: Event) => {
-  const videoElement = event.target as HTMLVideoElement
-  if (videoElement) {
-    videoElement.poster = fallbackImageBase64Ref.value
-    console.error('视频加载错误')
-  }
-}
-
-// 修改 DUV 得分解释函数
-const getDuvScoreInterpretation = (score: number | undefined): string => {
-  if (score === undefined) return '未进行心理测评或数据缺失'
-  
-  if (score <= 20) {
-    return '心理状态健康,情绪稳定、认知正常。候选人在心理测评中表现出稳定的情绪状态和健康的心理特质,适合岗位要求。'
-  } else if (score <= 35) {
-    return '轻微焦虑或负面情绪,仍具备岗位适应力。候选人在某些方面可能存在轻微的心理压力,但总体上仍保持稳定,基本符合岗位要求。'
-  } else if (score <= 55) {
-    return '存在一定情绪波动或压力反应。候选人在测评中表现出一定程度的心理压力或情绪波动,建议进一步面谈评估。'
-  } else if (score <= 70) {
-    return '心理压力较大,可能影响工作稳定性。候选人在测评中表现出较明显的心理压力或情绪不稳定性,建议慎重录用或考虑调岗。'
-  } else {
-    return '可能存在较严重心理或精神问题。候选人在测评中表现出明显的心理或情绪问题,不建议录用。'
-  }
-}
-
-// 更新 DUV 得分显示的 CSS 类判断逻辑
-const getDuvScoreClass = (score: number | undefined): string => {
-  if (score === undefined) return 'text-gray-500'
-  
-  if (score <= 20) {
-    return 'text-green-500' // 优秀
-  } else if (score <= 35) {
-    return 'text-blue-500' // 良好
-  } else if (score <= 55) {
-    return 'text-yellow-500' // 一般
-  } else if (score <= 70) {
-    return 'text-orange-500' // 偏差
-  } else {
-    return 'text-red-500' // 严重异常
-  }
-}
-
-// 获取 DUV 得分等级
-const getDuvScoreLevel = (score: number | undefined): string => {
-  if (score === undefined) return '未知'
-  
-  if (score <= 20) {
-    return '优秀'
-  } else if (score <= 35) {
-    return '良好'
-  } else if (score <= 55) {
-    return '一般'
-  } else if (score <= 70) {
-    return '偏差'
-  } else {
-    return '严重异常'
-  }
-}
-
-// 获取录用建议
-const getDuvHireRecommendation = (score: number | undefined): string => {
-  if (score === undefined) return '无法给出建议'
-  
-  if (score <= 20) {
-    return '建议优先录取'
-  } else if (score <= 35) {
-    return '可考虑录取'
-  } else if (score <= 55) {
-    return '建议进一步面谈评估'
-  } else if (score <= 70) {
-    return '慎重录用或建议调岗'
-  } else {
-    return '不建议录用'
-  }
-}
-
-// 添加安全获取分数值的辅助函数
-const getSafeScoreValue = (score: number | undefined): number => {
-  return score !== undefined ? score : 0
-}
-
-const getSafeMaxScoreValue = (maxScore: number | undefined): number => {
-  return maxScore !== undefined ? maxScore : 210
-}
-
-// 用户个人信息状态
-const userProfileData = ref<UserProfile | null>(null);
-const userProfileDialogVisible = ref(false);
-const userProfileLoading = ref(false);
-
-// 获取用户个人信息
-const fetchUserProfile = async () => {
-  userProfileLoading.value = true;
-  try {
-    // 从应用数据中获取用户ID和租户ID
-    const userId = apiData.value?.applicant?.id || route.query.user_id || '30';
-    const tenantId = route.query.tenant_id || '1';
-    
-    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/system/wechat/user/profile/get?user_id=${userId}&tenant_id=${tenantId}`);
-    const result = await response.json();
-    
-    if (result.code === 2000) {
-      userProfileData.value = result.data;
-      userProfileDialogVisible.value = true;
-    } else {
-      ElMessage.error(result.msg || '获取用户信息失败');
-    }
-  } catch (error) {
-    console.error('获取用户个人信息失败:', error);
-    ElMessage.error('获取用户信息失败,请稍后重试');
-  } finally {
-    userProfileLoading.value = false;
-  }
-};
-
-// 格式化日期显示
-const formatDate = (dateString: string | null | undefined): string => {
-  if (!dateString) return '未设置';
-  
-  try {
-    const date = new Date(dateString);
-    return date.toLocaleDateString('zh-CN');
-  } catch (e) {
-    return dateString;
-  }
-};
-
-// 处理查看个人信息按钮点击
-const handleViewProfile = () => {
-  fetchUserProfile();
-};
-
-const handleTabClick = (tab: any) => {
-  console.log('点击标签:', tab.props.name);
-  
-  // 尝试多种方法找到目标元素
-  const targetId = tab.props.name;
-  const element = document.getElementById(targetId);
-  
-  if (element) {
-    // 找到滚动容器 - 可能是document.body, document.documentElement, 或特定容器
-    const scrollContainer = document.querySelector('.max-w-4xl') || 
-                           document.documentElement || 
-                           document.body;
-    
-    // 计算元素相对于滚动容器的位置
-    const containerRect = scrollContainer.getBoundingClientRect();
-    const elementRect = element.getBoundingClientRect();
-    const relativeTop = elementRect.top - containerRect.top;
-    
-    // 滚动容器
-    if (scrollContainer === document.documentElement || scrollContainer === document.body) {
-      window.scrollTo({
-        top: window.scrollY + relativeTop - 100, // 减去100px的偏移
-        behavior: 'smooth'
-      });
-    } else {
-      scrollContainer.scrollTo({
-        top: scrollContainer.scrollTop + relativeTop - 100,
-        behavior: 'smooth'
-      });
-    }
-    
-    console.log('执行滚动:', window.scrollY + relativeTop - 100);
-  } else {
-    console.error('未找到目标元素:', targetId);
-  }
-};
-
-// 添加一个备用的滚动函数,可以通过按钮直接调用
-/* const scrollToSection = (sectionId: string) => {
-  console.log('尝试滚动到部分:', sectionId);
-  const element = document.getElementById(sectionId);
-  if (element) {
-    element.scrollIntoView({ behavior: 'smooth', block: 'start' });
-    setTimeout(() => {
-      window.scrollBy({ top: -100, behavior: 'smooth' });
-    }, 100);
-  }
-}; */
-
-// 添加到methods部分或直接在setup中定义
-const setActiveTab = (sectionId: string) => {
-  // 移除所有标签的active类
-  document.querySelectorAll('.tab-item').forEach(tab => {
-    tab.classList.remove('active');
-  });
-  
-  // 根据sectionId找到对应的标签并添加active类
-  const tabMap = {
-    'comprehensive-assessment': 0,
-    'image-analysis': 1,
-    'answer-records': 2,
-    'other-info': 3
-  };
-  
-  const index = tabMap[sectionId];
-  if (index !== undefined) {
-    document.querySelectorAll('.tab-item')[index].classList.add('active');
-  }
-};
-
-// 修改scrollToSection函数
-const scrollToSection = (sectionId: string) => {
-  console.log('尝试滚动到部分:', sectionId);
-  const element = document.getElementById(sectionId);
-  if (element) {
-    element.scrollIntoView({ behavior: 'smooth', block: 'start' });
-    setTimeout(() => {
-      window.scrollBy({ top: -100, behavior: 'smooth' });
-      setActiveTab(sectionId); // 设置激活的标签
-    }, 100);
-  }
-};
-
-// 修改获取手部照片的方法
-const getHandPhotos = (hand: 'left' | 'right'): PhotoResult[] => {
-  console.log(apiData.value)
-  if (!apiData.value?.application?.visual_analysis_results?.photo_results) {
-    console.log('No photo results available');
-    return Array(3).fill({
-      url: '',
-      description: `${hand === 'left' ? '左手' : '右手'}手势`,
-      part_verification: null
-    });
-  }
-
-  const photos = apiData.value.application.visual_analysis_results.photo_results.filter((photo: PhotoResult) => {
-    const description = photo.description.toLowerCase();
-    const searchTerm = hand === 'left' ? '左手' : '右手';
-    const matches = description.includes(searchTerm);
-    console.log(`Checking photo ${photo.photo_id}: "${description}" for "${searchTerm}" - ${matches}`);
-    return matches;
-  });
-
-  console.log(`Found ${photos.length} ${hand} hand photos`);
-
-  // 确保始终返回3张图片的数组
-  const result = [...photos];
-  while (result.length < 3) {
-    result.push({
-      url: '',
-      status: 'empty',
-      photo_id: -result.length,
-      detections: [],
-      description: `${hand === 'left' ? '左手' : '右手'}手势`,
-      photo_index: -result.length,
-      analysis_model: '',
-      part_verification: null,
-      analysis_timestamp: '',
-      overall_assessment: ''
-    });
-  }
-
-  return result;
-};
-
-// 在script setup部分添加以下函数
-const getScoreLevel = (score: number): string => {
-  if (score >= 90) return '优秀';
-  if (score >= 80) return '良好';
-  if (score >= 70) return '中等';
-  if (score >= 60) return '及格';
-  return '较差';
-}
-
-// 添加一个计算属性来获取competency_scores
-const competencyScores = computed(() => {
-  return apiData.value?.application?.comprehensive_analysis?.competency_scores || {};
-})
-
-// 添加一个函数来获取标签的背景色
-const getTagColorClass = (tagName: string): string => {
-  const colorMap: Record<string, string> = {
-    '沟通表达能力': 'bg-blue-100 text-blue-800',
-    '工作经历匹配度': 'bg-green-100 text-green-800', 
-    '操作技能水平': 'bg-yellow-100 text-yellow-800',
-    '质量意识': 'bg-indigo-100 text-indigo-800',
-    '安全意识': 'bg-pink-100 text-pink-800',
-    '团队协作能力': 'bg-purple-100 text-purple-800',
-    '问题解决能力': 'bg-orange-100 text-orange-800',
-    '学习能力': 'bg-red-100 text-red-800',
-    '初级统筹能力': 'bg-teal-100 text-teal-800'
-  };
-  return colorMap[tagName] || 'bg-gray-100 text-gray-800';
-}
-</script>
-
-<template>
-  <!-- 修改最外层容器的类,添加背景样式 -->
-  <div class="min-h-screen bg-gradient-custom">
-    <div class="max-w-4xl mx-auto p-6 relative overflow-y-auto" style="height: 100vh;">
-      <!-- 添加顶部锚点 -->
-      <div id="report-top" ref="reportTop"></div>
-      
-      <!-- 加载状态 -->
-      <a-spin :spinning="loading" tip="加载中...">
-        <!-- 页面标题 -->
-        <div class="bg-[#1d1e3a] text-white p-4 rounded-t-lg">
-          <div class="flex justify-between items-center">
-            <div class="text-sm">No.{{ apiData?.application?.report_id || candidateInfo.idNumber }}</div>
-            <div class="text-sm">{{ apiData?.application?.interview_duration_formatted || new Date().toLocaleString() }}</div>
-          </div>
-        </div>
-
-        <div v-if="error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
-          <strong class="font-bold">错误!</strong>
-          <span class="block sm:inline">{{ error }}</span>
-        </div>
-
-        <div v-else class="bg-white rounded-b-lg shadow-lg p-6">
-          <!-- 头部信息 -->
-          <div class="flex justify-between items-center mb-8 pb-4 border-b">
-            <div class="flex items-center space-x-4">
-              <div class="w-20 h-20 bg-gray-200 rounded-lg overflow-hidden">
-                <img 
-                  :src="candidateInfo.additionalInfo.faceVerification.images[0]" 
-                  alt="头像" 
-                  class="w-full h-full object-cover"
-                  @error="handleImageError"
-                >
-              </div>
-              <div>
-                <div class="flex items-center gap-2">
-                  <h1 class="text-2xl font-bold">{{ candidateInfo.name }}</h1>
-                  <el-button size="small" style="color: #1d1e3a; text-decoration: underline;" type="text" @click="handleViewProfile">
-                    个人信息
-                  </el-button>
-                </div>
-                <p class="text-gray-600">{{ candidateInfo.phoneNumber }}</p>
-                <p v-if="apiData?.position" class="text-gray-500 text-sm">
-                  应聘职位: {{ apiData.position.title }} - {{ apiData.position.department }}
-                </p>
-              </div>
-            </div>
-            <div class="text-right">
-              <div class="text-4xl font-bold text-yellow-500">{{ candidateInfo.score }}<span class="text-base ml-1">分</span></div>
-              <div class="text-sm text-yellow-500">
-                {{ candidateInfo.score >= 70 ? '推荐 👍' : candidateInfo.score >= 60 ? '待定 🤔' : '不推荐 👎' }}
-              </div>
-            </div>
-          </div>
-
-          <!-- 基本信息 -->
-          <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
-            <div>
-              <label class="text-gray-600">身份证号</label>
-              <div>{{ candidateInfo.idNumber }}</div>
-            </div>
-           <!--  <div>
-              <label class="text-gray-600">入职之后,您缺岗的班次时间段是?</label>
-              <div>{{ candidateInfo.availabilityPeriod }}</div>
-            </div>
-            <div>
-              <label class="text-gray-600">请问您通过面试之后多久可以入职?</label>
-              <div>{{ candidateInfo.onboardingTime }}</div>
-            </div> -->
-            <div>
-              <label class="text-gray-600">薪资范围</label>
-              <div>{{ candidateInfo.salaryRange }}</div>
-            </div>
-            <div>
-              <label class="text-gray-600">建议薪资</label>
-              <div class="font-semibold text-blue-600">{{ candidateInfo.suggestedSalary }}</div>
-            </div>
-          </div>
-
-          <!-- 在标签导航下方添加备用导航按钮 -->
-          <!-- <div class="backup-navigation mt-4 mb-6 flex flex-wrap gap-2">
-            <el-button size="small" @click="scrollToSection('comprehensive-assessment')">
-              综合评估
-            </el-button>
-            <el-button size="small" @click="scrollToSection('image-analysis')">
-              图片分析
-            </el-button>
-            <el-button size="small" @click="scrollToSection('answer-records')">
-              回答记录
-            </el-button>
-            <el-button size="small" @click="scrollToSection('other-info')">
-              其他信息
-            </el-button>
-          </div> -->
-
-          <!-- 将原来的标签导航移到更上层,并添加固定定位 -->
-          <div class="sticky-tabs">
-            <div class="custom-tabs">
-              <div class="tab-item active" @click="scrollToSection('comprehensive-assessment')">
-                <span>综合评估</span>
-              </div>
-              <div class="tab-item" @click="scrollToSection('image-analysis')">
-                <span>图片分析评估</span>
-              </div>
-              <div class="tab-item" @click="scrollToSection('answer-records')">
-                <span>回答记录</span>
-              </div>
-              <div class="tab-item" @click="scrollToSection('other-info')">
-                <span>其他信息</span>
-              </div>
-            </div>
-          </div>
-
-          <!-- 为每个主要章节添加id -->
-          <div id="comprehensive-assessment" class="mb-8 pt-4 border-t border-transparent">
-            <!-- 1. 综合评估 -->
-            <div class="mb-8">
-              <h1 class="font-bold mb-6">1. 综合评估</h1>
-
-              <div class="space-y-4">
-                <!-- 为每个部分添加 id -->
-                <div id="hire-recommendation" class="border-b pb-4">
-                  <!-- 录用建议部分的现有代码 -->
-                  <div class=" pb-4" style="font-size: 16px;">
-                    <div class="flex items-center justify-between mb-2">
-                      <span class="text-gray-600">录用建议</span>
-                      <span :class="{
-                        'text-green-500': candidateInfo.hireRecommendation?.includes('推荐'),
-                        'text-red-500': candidateInfo.hireRecommendation?.includes('不推荐'),
-                        'text-yellow-500': !candidateInfo.hireRecommendation?.includes('推荐') && !candidateInfo.hireRecommendation?.includes('不推荐')
-                      }">{{ candidateInfo.hireRecommendation || '无建议' }}</span>
-                    </div>
-                    <p class="text-gray-600 text-sm" style="font-size: 14px;">{{ candidateInfo.hireReason || '无详细说明' }}</p>
-                  </div>
-                </div>
-                  <!-- 替换原有的评分详情部分 -->
-                <div id="red-line" class="border-b pb-4">
-                  <!-- 红线题检测部分的现有代码 -->
-                  <div class="flex items-center justify-between mb-2">
-                    <span class="text-gray-600" style="color: red;">红线题检测结果</span>
-                    <span :class="{
-                      'text-green-500': apiData?.required_questions_summary?.all_correct,
-                      'text-red-500': apiData?.required_questions_summary && !apiData.required_questions_summary.all_correct
-                    }">
-                      {{ apiData?.required_questions_summary?.all_correct ? '全部正确' : '存在错误,建议不录用' }}
-                    </span>
-                  </div>
-                  <div v-if="apiData?.required_questions_summary && !apiData.required_questions_summary.all_correct" 
-                       class="mt-2 border-l-4 border-red-500 pl-3 bg-red-50 p-2 rounded">
-                    <p class="text-red-700 font-medium mb-1">未通过的红线题:</p>
-                    <ul v-if="apiData.required_questions_summary.failed_questions_details.length > 0" 
-                        class="list-disc pl-5 text-gray-600 text-sm">
-                      <li v-for="(question, index) in apiData.required_questions_summary.failed_questions_details" 
-                          :key="'failed-'+index">
-                        {{ question.question_text || `问题 ${index + 1}` }}
-                      </li>
-                    </ul>
-                    <p v-else class="text-gray-600 text-sm">未提供详细信息</p>
-                  </div>
-                </div>
-                
-                <div id="strengths" v-if="candidateInfo.strengths && candidateInfo.strengths.length > 0" class="border-b pb-4">
-                  <h3 class="font-semibold mb-2">优点</h3>
-                  <ul class="list-disc pl-5 text-gray-600 text-sm">
-                    <li v-for="(strength, index) in candidateInfo.strengths" :key="'strength-'+index">
-                      {{ strength }}
-                    </li>
-                  </ul>
-                </div>
-                
-                <div id="weaknesses" v-if="candidateInfo.weaknesses && candidateInfo.weaknesses.length > 0" class="border-b pb-4">
-                  <h3 class="font-semibold mb-2">需改进的地方</h3>
-                  <ul class="list-disc pl-5 text-gray-600 text-sm">
-                    <li v-for="(weakness, index) in candidateInfo.weaknesses" :key="'weakness-'+index">
-                      {{ weakness }}
-                    </li>
-                  </ul>
-                </div>
-                
-                <div id="duv-analysis" class="border-b pb-4">
-                  <!-- DUV心理测评部分的现有代码 -->
-                  <div class="flex items-center justify-between mt-4 mb-2">
-                    <span class="text-gray-600">DUV 心理测评得分</span>
-                    <span :class="getDuvScoreClass(apiData?.scoring_summary?.total_score_obtained)">
-                      {{ getSafeScoreValue(apiData?.scoring_summary?.total_score_obtained) }}
-                      <span class="ml-2 text-sm">({{ getDuvScoreLevel(apiData?.scoring_summary?.total_score_obtained) }})</span>
-                    </span>
-                  </div>
-                  <p class="text-gray-600 text-sm">
-                    {{ getDuvScoreInterpretation(apiData?.scoring_summary?.total_score_obtained) }}
-                  </p>
-                  <p class="text-sm mt-1" :class="getDuvScoreClass(apiData?.scoring_summary?.total_score_obtained)">
-                    <strong>录用建议:</strong> {{ getDuvHireRecommendation(apiData?.scoring_summary?.total_score_obtained) }}
-                  </p>
-                </div>
-
-                <!-- 替换原有的评分详情部分 -->
-                <div id="score-calculation" class="border-b pb-4">
-                    <div class="rounded-lg space-y-3">
-                      <!-- 动态渲染competency_scores中的标签 -->
-                      <div v-for="(score, tagId) in competencyScores" 
-                           :key="tagId"
-                           class="flex items-start space-x-2" 
-                           style="font-size: 14px;">
-                        <div :class="[getTagColorClass(score.tag_name), 'px-2 py-1 rounded text-sm whitespace-nowrap']" 
-                             style="font-size: 14px;">
-                          {{ score.tag_name }}
-                        </div>
-                        <div class="flex-1">
-                          <p class="text-gray-700">
-                            {{ getScoreLevel(score.normalized_score) }}
-                           <!--  <span class="text-sm text-gray-500 ml-2">({{ score.normalized_score }}分)</span> -->
-                          </p>
-                        </div>
-                      </div>
-                      <!-- 人脸一致性 (保留原有的) -->
-                    <div class="flex items-start space-x-2" style="font-size: 14px;">
-                      <div style="font-size: 14px;" class="bg-purple-100 text-purple-800 px-2 py-1 rounded text-sm whitespace-nowrap">人脸一致性</div>
-                      <div class="flex-1">
-                        <p class="text-gray-700">{{ apiData?.application?.comprehensive_analysis?.score_calculation?.face_consistency_score || '暂无数据' }}</p>
-                      </div>
-                    </div>
-                    </div>
-                  </div>
-              </div>
-            </div>
-          </div>
-
-          <div id="image-analysis" class="mb-8 pt-4 border-t border-transparent">
-            <h2 class=" font-bold mb-6">2. 图片分析评估</h2>
-            <div class="space-y-4">
-             
-              
-              <div v-for="(item, index) in candidateInfo.duvAnalysis" :key="index" class="border-b pb-4">
-                <div class="flex items-center justify-between mb-2">
-                  <span class="text-gray-600">{{ item.title }}</span>
-                  <span :class="{
-                    'text-green-500': item.type === 'positive',
-                    'text-red-500': item.type === 'negative',
-                    'text-yellow-500': item.type === 'neutral'
-                  }">{{ item.score }}</span>
-                </div>
-                <p class="text-gray-600 text-sm">{{ item.content }}</p>
-              </div>
-            </div>
-          </div>
-
-          <div id="answer-records" class="mb-8 pt-4 border-t border-transparent">
-            <h2 class="font-bold mb-6">3. 回答记录</h2>
-            <div class="space-y-4">
-              <div v-for="(record, index) in candidateInfo.interviewRecord" :key="index" class="bg-gray-50 rounded-lg p-4">
-                <!-- 题目标题部分 -->
-                 
-                <div class="mb-4">
-                  <div class="flex items-start space-x-2">
-                    <div class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm">第{{ index + 1 }}题</div>
-                    <div class="flex-1">
-                      <h3 class="font-semibold text-gray-800">{{ record.question }}</h3>
-                    </div>
-                  </div>
-
-                  <!-- 选择题布局 -->
-                  <div v-if="record.question_form === 1 || record.question_form === 2 || record.question_form === 4" class="mt-4">
-                    <div class="bg-white p-4 rounded-lg">
-                      <!-- 显示选项 -->
-                      <div v-if="record.options && record.options.length > 0" class="space-y-3">
-                        <div v-for="(option, optIndex) in record.options" :key="optIndex"
-                             :class="[
-                               'p-3 rounded border',
-                               record.answer_data?.selected_option_details?.some(
-                                 (selected: { id: number }) => selected.id === option.id
-                               )
-                                 ? 'border-blue-300 bg-blue-50'
-                                 : 'border-gray-200'
-                             ]">
-                          <div class="flex items-center justify-between">
-                            <div class="flex-1">
-                              <span class="text-gray-700">{{ option.option_text }}</span>
-                            </div>
-                            <div class="flex items-center space-x-2">
-                              <!-- 只在非问卷调查题(question_form !== 4)时显示正确答案和对错状态 -->
-                              <template v-if="record.question_form !== 4">
-                                <span v-if="option.is_correct" class="text-green-500">✓ 正确答案</span>
-                                <span v-if="record.answer_data?.selected_option_details?.some(
-                                        (selected: { id: number }) => selected.id === option.id
-                                      )"
-                                      :class="option.is_correct ? 'text-green-500' : 'text-red-500'">
-                                  {{ option.is_correct ? '(已选 - 正确)' : '(已选 - 错误)' }}
-                                </span>
-                              </template>
-                              <!-- 问卷调查题只显示是否选中 -->
-                              <template v-else>
-                                <span v-if="record.answer_data?.selected_option_details?.some(
-                                        (selected: { id: number }) => selected.id === option.id
-                                      )"
-                                      class="text-blue-500">
-                                  (已选)
-                                </span>
-                              </template>
-                            </div>
-                          </div>
-                        </div>
-                      </div>
-                      
-                      <!-- 显示答案状态 -->
-                      <div class="mt-4 flex justify-between items-center">
-                        <div class="text-gray-600">
-                          候选人: {{ record.answer_data?.selected_option_details?.map(
-                            (opt: { option_text: string }) => opt.option_text
-                          ).join(', ') || '未作答' }}
-                        </div>
-                        <!-- 只在非问卷调查题时显示对错状态 -->
-                        <div v-if="record.question_form !== 4">
-                          <span :class="[
-                            'px-3 py-1 rounded-full text-sm',
-                            record.answer_data?.selected_option_details?.every(
-                              (opt: { is_correct: boolean }) => opt.is_correct
-                            )
-                              ? 'bg-green-100 text-green-800'
-                              : 'bg-red-100 text-red-800'
-                          ]">
-                            {{ record.answer_data?.selected_option_details?.every(
-                                 (opt: { is_correct: boolean }) => opt.is_correct
-                               ) ? '正确' : '错误' }}
-                          </span>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-
-                  <!-- 视频问答题布局 -->
-                  <div v-else-if="record.question_form === 0" class="mt-4 flex space-x-4">
-                   
-                    
-                    <!-- 右侧答案和评分 -->
-                    <div class="w-2/3">
-                      <div class="bg-white p-4 rounded-lg">
-                        <div class="mb-4">
-                          <div class="flex items-start space-x-2">
-                            <div class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">候选人</div>
-                            <div class="flex-1">
-                              <p class="text-gray-700">{{ record.answer }}</p>
-                            </div>
-                          </div>
-                        </div>
-                        
-                        <div>
-                          <div class="flex items-start space-x-2">
-                            <div class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-sm">AI评分</div>
-                            <div class="flex-1">
-                              <p class="text-gray-600 m-0">{{ record.analysis }}</p>
-                              <div class="mt-1 text-blue-500">得分:{{ record.score }}</div>
-                            </div>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                     <!-- 左侧视频播放器 -->
-                    <div class="w-1/3">
-                      <div class="video-container">
-                        <video
-                          class="w-full h-full object-cover rounded-lg"
-                          controls
-                          :src="record.videoUrl"
-                          preload="metadata"
-                          :poster="fallbackImageBase64Ref"
-                          @loadeddata="handleVideoLoaded"
-                          @error="handleVideoError"
-                        >
-                          您的浏览器不支持视频播放。
-                        </video>
-                      </div>
-                    </div>
-                  </div>
-
-                  <!-- 图片题布局 -->
-                  <div v-else-if="record.question_form === 3" class="mt-4">
-                    <div class="grid grid-cols-2 gap-4">
-                      <!-- 左侧图片显示 -->
-                      <div class="bg-white p-4 rounded-lg">
-                        <div class="aspect-w-16 aspect-h-9 rounded-lg overflow-hidden bg-gray-100">
-                          <img 
-                            :src="record.question_image_url" 
-                            alt="题目图片" 
-                            class="w-full h-full object-contain"
-                            @error="handleImageError"
-                          >
-                        </div>
-                      </div>
-                      
-                      <!-- 右侧选项和答案 -->
-                      <div class="bg-white p-4 rounded-lg">
-                        <!-- 显示选项 -->
-                        <div v-if="record.options && record.options.length > 0" class="space-y-3">
-                          <div v-for="(option, optIndex) in record.options" :key="optIndex"
-                               :class="[
-                                 'p-3 rounded border',
-                                 record.answer_data?.selected_option_details?.some(
-                                   (selected: { id: number }) => selected.id === option.id
-                                 )
-                                   ? 'border-blue-300 bg-blue-50'
-                                   : 'border-gray-200'
-                               ]">
-                            <div class="flex items-center justify-between">
-                              <div class="flex-1">
-                                <span class="text-gray-700">{{ option.option_text }}</span>
-                              </div>
-                              <div class="flex items-center space-x-2">
-                                <span v-if="option.is_correct" class="text-green-500">✓ 正确答案</span>
-                                <span v-if="record.answer_data?.selected_option_details?.some(
-                                        (selected: { id: number }) => selected.id === option.id
-                                      )"
-                                      :class="option.is_correct ? 'text-green-500' : 'text-red-500'">
-                                  {{ option.is_correct ? '(已选 - 正确)' : '(已选 - 错误)' }}
-                                </span>
-                              </div>
-                            </div>
-                          </div>
-                        </div>
-                        
-                        <!-- 显示答案状态 -->
-                        <div class="mt-4 flex justify-between items-center">
-                          <div class="text-gray-600">
-                            答案: {{ record.answer_data?.selected_option_details?.map(
-                              (opt: { option_text: string }) => opt.option_text
-                            ).join(', ') || '未作答' }}
-                          </div>
-                          <div>
-                            <span :class="[
-                              'px-3 py-1 rounded-full text-sm',
-                              record.answer_data?.selected_option_details?.every(
-                                (opt: { is_correct: boolean }) => opt.is_correct
-                              )
-                                ? 'bg-green-100 text-green-800'
-                                : 'bg-red-100 text-red-800'
-                            ]">
-                              {{ record.answer_data?.selected_option_details?.every(
-                                   (opt: { is_correct: boolean }) => opt.is_correct
-                                 ) ? '正确' : '错误' }}
-                            </span>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-
-          <div id="other-info" class="mb-8 pt-4 border-t border-transparent">
-            <h2 class="font-bold mb-6">4. 其他信息</h2>
-            <div class="space-y-6">
-              <!-- 验证状态列表 -->
-              <div class="space-y-2">
-                <div class="flex items-center space-x-2">
-                  <span class="text-gray-600">身份认证:</span>
-                  <span class="text-green-500">{{ candidateInfo.additionalInfo.idVerification }}</span>
-                </div>
-                <div class="flex items-center space-x-2">
-                  <span class="text-gray-600">手机号实名认证:</span>
-                  <span class="text-green-500">{{ candidateInfo.additionalInfo.phoneVerification }}</span>
-                </div>
-                <!-- <div class="flex items-center space-x-2">
-                  <span class="text-gray-600">综合A类评估:</span>
-                  <span class="text-green-500">{{ candidateInfo.additionalInfo.comprehensiveVerification }}</span>
-                </div>
-                <div class="flex items-center space-x-2">
-                  <span class="text-gray-600">综合B类评估:</span>
-                  <span class="text-green-500">{{ candidateInfo.additionalInfo.comprehensiveVerification }}</span>
-                </div> -->
-              </div>
-
-              <!-- 手部综合检测 -->
-              <div class="space-y-4">
-                <div class="flex items-center space-x-2">
-                  <span class="text-gray-600">手部综合检测:</span>
-                  <span class="text-green-500">{{ candidateInfo.additionalInfo.handVerification }}</span>
-                </div>
-                
-                <!-- 手部检测详细信息 -->
-               <!--  <div class="pl-4 space-y-2">
-                  <div class="flex items-center space-x-2">
-                    <span class="text-gray-600">手指灵活度:</span>
-                    <span class="text-green-500">{{ candidateInfo.additionalInfo.handBrightness }}</span>
-                  </div>
-                  <div class="flex items-center space-x-2">
-                    <span class="text-gray-600">手指完整度:</span>
-                    <span class="text-green-500">{{ candidateInfo.additionalInfo.handIntegrity }}</span>
-                  </div>
-                  <div class="flex items-center space-x-2">
-                    <span class="text-gray-600">手部姿态纹身:</span>
-                    <span class="text-green-500">{{ candidateInfo.additionalInfo.handPoseRecognition }}</span>
-                  </div>
-                </div> -->
-
-                <!-- 手势验证图片展示 -->
-                <div class="grid grid-cols-2 gap-8 mt-4">
-                  <!-- 左手验证结果 -->
-                  <div>
-                    <h4 class="text-gray-600 mb-2">左手手势验证</h4>
-                    <div class="grid grid-cols-3 gap-2">
-                      <div v-for="(photo, index) in getHandPhotos('left')" 
-                           :key="index"
-                           class="relative aspect-square rounded-lg overflow-hidden bg-gray-100"
-                           :class="{'border-2': photo.part_verification, 
-                                   'border-green-500': photo.part_verification?.is_correct_part,
-                                   'border-red-500': photo.part_verification && !photo.part_verification.is_correct_part}"
-                      >
-                        <!-- 使用 el-tooltip 包裹内容 -->
-                        <el-tooltip
-                          v-if="photo.part_verification && !photo.part_verification.is_correct_part"
-                          :content="photo.part_verification.explanation || '未提供失败原因'"
-                          placement="top"
-                          effect="dark"
-                          :show-after="200"
-                        >
-                          <div class="w-full h-full">
-                            <img :src="photo.url" 
-                                 :alt="photo.description" 
-                                 class="w-full h-full object-cover"
-                                 @error="handleImageError">
-                            
-                            <!-- 底部状态显示 -->
-                            <div v-if="photo.part_verification"
-                                 class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                                 :class="{
-                                   'bg-green-500 bg-opacity-90': photo.part_verification.is_correct_part,
-                                   'bg-red-500 bg-opacity-90': !photo.part_verification.is_correct_part
-                                 }">
-                              {{ photo.part_verification.is_correct_part ? '通过' : '未通过' }}
-                            </div>
-                          </div>
-                        </el-tooltip>
-                        
-                        <!-- 如果不需要显示tooltip,直接显示图片 -->
-                        <template v-else>
-                          <img :src="photo.url" 
-                               :alt="photo.description" 
-                               class="w-full h-full object-cover"
-                               @error="handleImageError">
-                          
-                          <!-- 底部状态显示 -->
-                          <div v-if="photo.part_verification"
-                               class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                               :class="{
-                                 'bg-green-500 bg-opacity-90': photo.part_verification.is_correct_part,
-                                 'bg-red-500 bg-opacity-90': !photo.part_verification.is_correct_part
-                               }">
-                            {{ photo.part_verification.is_correct_part ? '通过' : '未通过' }}
-                          </div>
-                        </template>
-                      </div>
-                    </div>
-                  </div>
-
-                  <!-- 右手验证结果 -->
-                  <div>
-                    <h4 class="text-gray-600 mb-2">右手手势验证</h4>
-                    <div class="grid grid-cols-3 gap-2">
-                      <div v-for="(photo, index) in getHandPhotos('right')" 
-                           :key="index"
-                           class="relative aspect-square rounded-lg overflow-hidden bg-gray-100"
-                           :class="{'border-2': photo.part_verification, 
-                                   'border-green-500': photo.part_verification?.is_correct_part,
-                                   'border-red-500': photo.part_verification && !photo.part_verification.is_correct_part}"
-                      >
-                        <!-- 使用 el-tooltip 包裹内容 -->
-                        <el-tooltip
-                          v-if="photo.part_verification && !photo.part_verification.is_correct_part"
-                          :content="photo.part_verification.explanation || '未提供失败原因'"
-                          placement="top"
-                          effect="dark"
-                          :show-after="200"
-                        >
-                          <div class="w-full h-full">
-                            <img :src="photo.url" 
-                                 :alt="photo.description" 
-                                 class="w-full h-full object-cover"
-                                 @error="handleImageError">
-                            
-                            <!-- 底部状态显示 -->
-                            <div v-if="photo.part_verification"
-                                 class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                                 :class="{
-                                   'bg-green-500 bg-opacity-90': photo.part_verification.is_correct_part,
-                                   'bg-red-500 bg-opacity-90': !photo.part_verification.is_correct_part
-                                 }">
-                              {{ photo.part_verification.is_correct_part ? '通过' : '未通过' }}
-                            </div>
-                          </div>
-                        </el-tooltip>
-                        
-                        <!-- 如果不需要显示tooltip,直接显示图片 -->
-                        <template v-else>
-                          <img :src="photo.url" 
-                               :alt="photo.description" 
-                               class="w-full h-full object-cover"
-                               @error="handleImageError">
-                          
-                          <!-- 底部状态显示 -->
-                          <div v-if="photo.part_verification"
-                               class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                               :class="{
-                                 'bg-green-500 bg-opacity-90': photo.part_verification.is_correct_part,
-                                 'bg-red-500 bg-opacity-90': !photo.part_verification.is_correct_part
-                               }">
-                            {{ photo.part_verification.is_correct_part ? '通过' : '未通过' }}
-                          </div>
-                        </template>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-
-              <!-- 换人检测 -->
-              <div>
-                <h3 class="font-semibold mb-4">人脸验证: </h3>
-                <div class="grid grid-cols-3 md:grid-cols-6 gap-2">
-                  <div v-for="(image, index) in candidateInfo.additionalInfo.faceVerification.images" 
-                       :key="index"
-                       class="aspect-square rounded-lg overflow-hidden bg-gray-100">
-                    <img :src="image" 
-                         alt="人脸验证" 
-                         class="w-full h-full object-cover"
-                         @error="handleImageError">
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </a-spin>
-
-      <!-- 悬浮按钮 (Element Plus 版本) -->
-      <div class="fixed right-8 bottom-24 flex flex-col space-y-4">
-        <el-button
-          type="primary"
-          style="margin-left: 12px;"
-          circle
-          @click="handleViewProfile"
-        >
-          <el-icon><User /></el-icon>
-        </el-button>
-        <el-button
-          type="primary"
-          style="margin-left: 12px;"
-          circle
-          @click="handleShare"
-        >
-          <el-icon><Share /></el-icon>
-        </el-button>
-        <el-button
-          type="primary"
-          circle
-          @click="handleDownload"
-        >
-          <el-icon><Download /></el-icon>
-        </el-button>
-        <el-button
-          type="primary"
-          circle
-          @click="handlePrint"
-        >
-          <el-icon><Printer /></el-icon>
-        </el-button>
-        <el-button
-          type="primary"
-          circle
-          @click="scrollToTop"
-        >
-          <el-icon><ArrowUp /></el-icon>
-        </el-button>
-      </div>
-    </div>
-  </div>
-
-  <!-- 用户个人信息弹窗 -->
-  <el-dialog
-    v-model="userProfileDialogVisible"
-    title="个人详细信息"
-    width="80%"
-    :close-on-click-modal="false"
-    class="user-profile-dialog"
-  >
-    <el-scrollbar height="70vh">
-      <el-spin :spinning="userProfileLoading">
-        <div v-if="userProfileData" class="user-profile-content">
-          <!-- 基本信息 -->
-          <div class="profile-section">
-            <h3 class="section-title">基本信息</h3>
-            <el-descriptions :column="3" border>
-              <el-descriptions-item label="姓名">{{ userProfileData.user_info.name }}</el-descriptions-item>
-              <el-descriptions-item label="手机号">{{ userProfileData.user_info.phone }}</el-descriptions-item>
-              <el-descriptions-item label="年龄">{{ userProfileData.user_info.age }}岁</el-descriptions-item>
-              <el-descriptions-item label="出生日期">{{ formatDate(userProfileData.user_info.birth_date) }}</el-descriptions-item>
-              <el-descriptions-item label="性别">{{ userProfileData.user_info.gender_name }}</el-descriptions-item>
-              <el-descriptions-item label="身份证号">{{ userProfileData.user_info.id_card }}</el-descriptions-item>
-            </el-descriptions>
-          </div>
-          
-          <!-- 个人资料 -->
-          <div class="profile-section">
-            <h3 class="section-title">个人资料</h3>
-            <el-descriptions :column="3" border>
-              <el-descriptions-item label="政治面貌">{{ userProfileData.profile.political_status }}</el-descriptions-item>
-              <el-descriptions-item label="民族">{{ userProfileData.profile.ethnicity }}</el-descriptions-item>
-              <el-descriptions-item label="身高">{{ userProfileData.profile.height }}cm</el-descriptions-item>
-              <el-descriptions-item label="体重">{{ userProfileData.profile.weight }}kg</el-descriptions-item>
-              <el-descriptions-item label="籍贯">{{ userProfileData.profile.native_place }}</el-descriptions-item>
-              <el-descriptions-item label="户口所在地">{{ userProfileData.profile.household_location }}</el-descriptions-item>
-              <el-descriptions-item label="现居地址">{{ userProfileData.profile.current_address }}</el-descriptions-item>
-              <el-descriptions-item label="婚姻状况">{{ userProfileData.profile.marital_status_name }}</el-descriptions-item>
-              <el-descriptions-item label="是否有子女">{{ userProfileData.profile.has_children ? '是' : '否' }}</el-descriptions-item>
-              <el-descriptions-item label="期望薪资">{{ userProfileData.profile.expected_salary }}</el-descriptions-item>
-              <el-descriptions-item label="紧急联系人">{{ userProfileData.profile.emergency_contact }}</el-descriptions-item>
-              <el-descriptions-item label="紧急联系电话">{{ userProfileData.profile.emergency_phone }}</el-descriptions-item>
-              <el-descriptions-item label="特长">{{ userProfileData.profile.specialties }}</el-descriptions-item>
-              <el-descriptions-item label="人生格言">{{ userProfileData.profile.life_motto }}</el-descriptions-item>
-              <el-descriptions-item label="招聘来源">{{ userProfileData.profile.recruitment_source_name }}</el-descriptions-item>
-            </el-descriptions>
-          </div>
-          
-          <!-- 教育经历 -->
-          <div class="profile-section" v-if="userProfileData.educations && userProfileData.educations.length > 0">
-            <h3 class="section-title">教育经历</h3>
-            <div v-for="(edu, index) in userProfileData.educations" :key="`edu-${index}`" class="mb-4">
-              <el-descriptions :column="3" border>
-                <el-descriptions-item label="学历类型">{{ edu.education_type_name }}</el-descriptions-item>
-                <el-descriptions-item label="学历">{{ edu.degree_name }}</el-descriptions-item>
-                <el-descriptions-item label="时间段">{{ formatDate(edu.start_date) }} 至 {{ formatDate(edu.end_date) }}</el-descriptions-item>
-                <el-descriptions-item label="学校名称">{{ edu.school_name }}</el-descriptions-item>
-                <el-descriptions-item label="专业">{{ edu.major }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-          </div>
-          
-          <!-- 工作经历 -->
-          <div class="profile-section" v-if="userProfileData.work_experiences && userProfileData.work_experiences.length > 0">
-            <h3 class="section-title">工作经历</h3>
-            <div v-for="(work, index) in userProfileData.work_experiences" :key="`work-${index}`" class="mb-4">
-              <el-descriptions :column="3" border>
-                <el-descriptions-item label="公司名称">{{ work.company_name }}</el-descriptions-item>
-                <el-descriptions-item label="时间段">{{ formatDate(work.start_date) }} 至 {{ formatDate(work.end_date) }}</el-descriptions-item>
-                <el-descriptions-item label="部门">{{ work.department }}</el-descriptions-item>
-                <el-descriptions-item label="职位">{{ work.position }}</el-descriptions-item>
-                <el-descriptions-item label="公司规模">{{ work.company_size }}人</el-descriptions-item>
-                <el-descriptions-item label="月薪">{{ work.monthly_salary }}</el-descriptions-item>
-                <el-descriptions-item label="主管姓名">{{ work.supervisor_name }}</el-descriptions-item>
-                <el-descriptions-item label="主管电话">{{ work.supervisor_phone }}</el-descriptions-item>
-                <el-descriptions-item label="工作描述" :span="3">{{ work.job_description || '无' }}</el-descriptions-item>
-                <el-descriptions-item label="工作成就" :span="3">{{ work.achievements || '无' }}</el-descriptions-item>
-                <el-descriptions-item label="离职原因" :span="3">{{ work.resignation_reason || '无' }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-          </div>
-          
-          <!-- 培训经历 -->
-          <div class="profile-section" v-if="userProfileData.trainings && userProfileData.trainings.length > 0">
-            <h3 class="section-title">培训经历</h3>
-            <div v-for="(training, index) in userProfileData.trainings" :key="`training-${index}`" class="mb-4">
-              <el-descriptions :column="3" border>
-                <el-descriptions-item label="培训名称">{{ training.training_name }}</el-descriptions-item>
-                <el-descriptions-item label="培训机构">{{ training.institution }}</el-descriptions-item>
-                <el-descriptions-item label="时间段">{{ formatDate(training.start_date) }} 至 {{ formatDate(training.end_date) }}</el-descriptions-item>
-                <el-descriptions-item label="培训描述" :span="3">{{ training.description }}</el-descriptions-item>
-                <el-descriptions-item label="证书">{{ training.certificate }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-          </div>
-          
-          <!-- 家庭成员 -->
-          <div class="profile-section" v-if="userProfileData.family_members && userProfileData.family_members.length > 0">
-            <h3 class="section-title">家庭成员</h3>
-            <div v-for="(member, index) in userProfileData.family_members" :key="`family-${index}`" class="mb-4">
-              <el-descriptions :column="3" border>
-                <el-descriptions-item label="关系">{{ member.relation }}</el-descriptions-item>
-                <el-descriptions-item label="姓名">{{ member.name }}</el-descriptions-item>
-                <el-descriptions-item label="联系电话">{{ member.phone }}</el-descriptions-item>
-                <el-descriptions-item label="工作单位">{{ member.workplace }}</el-descriptions-item>
-                <el-descriptions-item label="职位">{{ member.position }}</el-descriptions-item>
-              </el-descriptions>
-            </div>
-          </div>
-        </div>
-        <div v-else class="empty-data">
-          <el-empty description="暂无个人信息数据"></el-empty>
-        </div>
-      </el-spin>
-    </el-scrollbar>
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="userProfileDialogVisible = false">关闭</el-button>
-      </span>
-    </template>
-  </el-dialog>
-</template>
-
-<style scoped>
-/* 修改背景渐变样式,使用蓝色小精灵的配色 */
-.bg-gradient-custom {
-  background: linear-gradient(180deg, #0066cc 0%, #3399ff 100%);
-  min-height: 100vh;
-  width: 100%;
-  position: relative;
-  overflow-y: auto;
-  scrollbar-width: none;
-  -ms-overflow-style: none;
-}
-
-/* 修改星星背景效果,使其更符合蓝色主题 */
-.bg-gradient-custom::before {
-  content: '';
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background-image: 
-    radial-gradient(2px 2px at 20px 30px, #ffffff30 50%, transparent 100%),
-    radial-gradient(2px 2px at 40px 70px, #ffffff30 50%, transparent 100%),
-    radial-gradient(2px 2px at 50px 160px, #ffffff30 50%, transparent 100%),
-    radial-gradient(2px 2px at 90px 40px, #ffffff30 50%, transparent 100%),
-    radial-gradient(2px 2px at 130px 80px, #ffffff30 50%, transparent 100%),
-    radial-gradient(2px 2px at 160px 120px, #ffffff30 50%, transparent 100%);
-  background-repeat: repeat;
-  background-size: 200px 200px;
-  opacity: 0.6;
-  z-index: 0;
-  animation: twinkle 4s infinite alternate;
-}
-
-/* 添加闪烁动画效果 */
-@keyframes twinkle {
-  0% {
-    opacity: 0.4;
-  }
-  100% {
-    opacity: 0.7;
-  }
-}
-
-/* 修改页面标题区域样式 */
-:deep(.bg-\[#1d1e3a\]) {
-  background-color: #004d99 !important; /* 深蓝色标题背景 */
-}
-
-/* 修改按钮悬浮效果 */
-.el-button--primary {
-  background-color: #0066cc !important;
-  border-color: #0066cc !important;
-}
-
-.el-button--primary:hover {
-  background-color: #3399ff !important;
-  border-color: #3399ff !important;
-  transform: scale(1.05);
-  transition: all 0.3s ease;
-}
-
-/* 修改内容区域的样式 */
-.max-w-4xl {
-  position: relative;
-  overflow-y: visible;
-  min-height: 100%;
-  z-index: 1;
-  scrollbar-width: none;
-  -ms-overflow-style: none;
-}
-
-.max-w-4xl::-webkit-scrollbar {
-  display: none;
-}
-
-.video-container {
-  position: relative;
-  width: 100%;
-  max-width: 240px;
-  aspect-ratio: 3/4;
-  margin: 0 auto;
-  background: #000;
-  border-radius: 12px;
-  overflow: hidden;
-}
-
-.ant-form-item {
-  margin-bottom: 24px;
-}
-
-.fixed {
-  z-index: 100;
-}
-
-.ant-btn-circle {
-  width: 40px;
-  height: 40px;
-  padding: 0;
-  border-radius: 50%;
-}
-
-/* 修改滚动相关样式 */
-html, body {
-  height: 100%;
-  scroll-behavior: smooth;
-  overflow-y: auto !important; /* 强制允许垂直滚动 */
-}
-
-/* 确保主容器可以正常滚动 */
-.max-w-4xl {
-  position: relative;
-  overflow-y: visible;
-  min-height: 100%;
-}
-
-/* 添加以下样式确保内容可以正常滚动 */
-body {
-  margin: 0;
-  padding: 0;
-  overflow-y: auto !important;
-}
-
-#app {
-  height: auto;
-  min-height: 100vh;
-  overflow: visible;
-}
-
-/* 确保滚动容器不会被阻止滚动 */
-.ant-layout, 
-.ant-layout-content,
-.el-scrollbar__wrap,
-.el-scrollbar__view {
-  overflow-y: auto !important;
-}
-
-@media (max-width: 768px) {
-  .max-w-4xl {
-    padding: 1rem;
-  }
-  
-  .grid-cols-3 {
-    grid-template-columns: 1fr;
-  }
-  
-  .fixed {
-    right: 1rem;
-  }
-}
-
-/* 打印样式 */
-@media print {
-  /* 打印时隐藏悬浮按钮 */
-  .fixed {
-    display: none !important;
-  }
-  
-  /* 确保内容完整显示 */
-  .max-w-4xl {
-    max-width: 100% !important;
-    padding: 0 !important;
-    margin: 0 !important;
-  }
-  
-  /* 调整页面边距 */
-  @page {
-    margin: 1cm;
-  }
-  
-  /* 确保背景色和图片打印 */
-  * {
-    -webkit-print-color-adjust: exact !important;
-    color-adjust: exact !important;
-    print-color-adjust: exact !important;
-  }
-  
-  /* 避免视频容器在打印时出现问题 */
-  .video-container {
-    page-break-inside: avoid;
-    break-inside: avoid;
-  }
-  
-  /* 确保每个主要部分在新页面开始 */
-  h2.text-xl {
-    page-break-before: always;
-    break-before: always;
-  }
-  
-  /* 第一个标题不需要分页 */
-  h2.text-xl:first-of-type {
-    page-break-before: avoid;
-    break-before: avoid;
-  }
-
-  .bg-gradient-custom {
-    background: none !important;
-  }
-  
-  .bg-gradient-custom::before {
-    display: none !important;
-  }
-}
-
-/* 确保内容区域可以正常滚动 */
-.max-w-4xl {
-  position: relative;
-  overflow-y: auto;
-  height: 100%;
-  padding-bottom: 100px; /* 为底部按钮留出空间 */
-}
-
-/* 添加用户个人信息弹窗样式 */
-.user-profile-content {
-  padding: 0 16px;
-}
-
-.profile-section {
-  margin-bottom: 24px;
-}
-
-.section-title {
-  font-size: 18px;
-  font-weight: 600;
-  margin-bottom: 16px;
-  padding-bottom: 8px;
-  border-bottom: 1px solid #ebeef5;
-  color: #0066cc;
-}
-
-:deep(.el-descriptions__label) {
-  background-color: #f5f7fa;
-  font-weight: 500;
-}
-
-:deep(.el-descriptions__content) {
-  padding: 12px 16px;
-}
-
-.empty-data {
-  padding: 40px 0;
-  display: flex;
-  justify-content: center;
-}
-
-/* 确保弹窗内容可以滚动 */
-:deep(.el-dialog__body) {
-  padding: 16px 0;
-  max-height: 70vh;
-  overflow: hidden;
-}
-
-/* 修改弹窗标题样式 */
-:deep(.el-dialog__header) {
-  padding: 16px 20px;
-  background-color: #0066cc;
-  margin-right: 0;
-}
-
-:deep(.el-dialog__title) {
-  color: white;
-  font-weight: 600;
-}
-
-:deep(.el-dialog__headerbtn .el-dialog__close) {
-  color: white;
-}
-
-/* 修改弹窗底部样式 */
-:deep(.el-dialog__footer) {
-  padding: 12px 20px;
-  border-top: 1px solid #ebeef5;
-}
-
-/* 添加标签样式 */
-:deep(.el-tabs__nav-wrap) {
-  background-color: white;
-  border-radius: 8px;
-  padding: 4px;
-}
-
-:deep(.el-tabs__item) {
-  color: #606266;
-  font-size: 14px;
-  padding: 0 20px;
-  height: 36px;
-  line-height: 36px;
-  transition: all 0.3s;
-}
-
-:deep(.el-tabs__item.is-active) {
-  color: #0066cc;
-  font-weight: 500;
-}
-
-:deep(.el-tabs__active-bar) {
-  background-color: #0066cc;
-}
-
-/* 确保内容区域有足够的滚动空间 */
-.space-y-4 > div {
-  scroll-margin-top: 120px; /* 增加滚动边距,避免被固定导航遮挡 */
-}
-
-/* 主要章节的滚动定位调整 */
-#comprehensive-assessment,
-#image-analysis,
-#answer-records,
-#other-info {
-  scroll-margin-top: 120px; /* 增加滚动边距 */
-  padding-top: 20px; /* 添加顶部内边距,使内容更清晰可见 */
-  border-top: 1px solid transparent; /* 添加透明边框,帮助滚动定位 */
-}
-
-/* 打印时隐藏固定导航 */
-@media print {
-  .sticky-tabs {
-    display: none !important;
-  }
-}
-
-/* 确保固定导航不会干扰滚动 */
-.sticky-tabs {
-  position: sticky;
-  top: -24px;
-  z-index: 10;
-  background-color: white;
-  padding: 10px 0;
-  margin-bottom: 20px;
-  /* box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); */
-}
-
-/* 自定义标签样式 - 匹配图片中的深色标签 */
-:deep(.custom-tabs) {
-  background-color: #1d1e3a;
-  border-radius: 0;
-}
-
-:deep(.custom-tabs .el-tabs__header) {
-  margin: 0;
-  border: none;
-  background-color: #1d1e3a;
-}
-
-:deep(.custom-tabs .el-tabs__nav) {
-  border: none;
-  background-color: #1d1e3a;
-}
-
-:deep(.custom-tabs .el-tabs__item) {
-  color: white;
-  font-size: 14px;
-  padding: 0 20px;
-  height: 40px;
-  line-height: 40px;
-  border: none;
-  background-color: transparent;
-}
-
-:deep(.custom-tabs .el-tabs__item.is-active) {
-  color: white;
-  font-weight: 500;
-  background-color: #4e4e8b;
-}
-
-:deep(.custom-tabs .el-tabs__item:hover) {
-  color: white;
-  background-color: #4e4e8b;
-}
-
-:deep(.custom-tabs .el-tabs__nav-wrap::after) {
-  display: none;
-}
-
-.sticky-tabs {
-  position: sticky;
-  top: -25px;
-  z-index: 10;
-  margin-bottom: 20px;
-  
-}
-
-.custom-tabs {
-  display: flex;
-  background-color: #1d1e3a;
-  width: 100%;
-  overflow-x: auto;
-  white-space: nowrap;
-  border-radius: 5px;
-}
-
-.tab-item {
-  padding: 0 20px;
-  height: 40px;
-  line-height: 40px;
-  color: white;
-  font-size: 14px;
-  cursor: pointer;
-  text-align: center;
-  transition: background-color 0.3s;
-  width: 25%;
-  /* flex: 1; */
-  min-width: max-content;
-}
-
-.tab-item:hover {
-  background-color: #4e4e8b;
-}
-
-.tab-item.active {
-  background-color: #4e4e8b;
-  font-weight: 500;
-}
-
-/* 确保在移动设备上也能正常滚动 */
-@media (max-width: 768px) {
-  .custom-tabs {
-    justify-content: flex-start;
-  }
-  
-  .tab-item {
-    flex: 0 0 auto;
-  }
-}
-
-/* 添加新的样式 */
-.group {
-  position: relative;
-}
-
-.group:hover .group-hover\:opacity-100 {
-  opacity: 1;
-}
-
-.transition-opacity {
-  transition: opacity 0.2s ease-in-out;
-}
-</style>

+ 0 - 3889
src/views/JobApplication/report/report.vue

@@ -1,3889 +0,0 @@
-<template>
-  <div class="outer-container" :class="{'mobile': isMobile}">
-    
-
-    <!-- 添加加载状态和错误提示 -->
-    <div v-if="loading" class="loading-state">
-      加载中...
-    </div>
-    
-    <div v-else-if="error" class="error-state">
-      {{ error }}
-    </div>
-
-    <div v-else class="report-container">
-      <!-- 添加顶部标识栏 -->
-    <div class="report-header">
-      <div class="header-left">
-        <img src="/@/assets/qcLogo.png" class="cc-icon" alt="cc icon">
-        AI面试结果检验报告
-      </div>
-      <div class="header-right">
-        <div>报告生成时间:{{ reportTime }}</div>
-        <div class="report-id">报告ID:{{ reportId }}</div>
-        <!-- 添加导出按钮 -->
-      </div>
-    </div>
-
-      <!-- 头部信息 -->
-      <div class="header">
-        <!-- 头像部分 -->
-        <div class="header-left">
-          <img class="avatar" :src="userInfo.avatar" alt="用户头像">
-        </div>
-        <!-- 信息部分 -->
-        <div class="header-right">
-          <!-- 左侧信息 -->
-          <div class="left-section">
-            <div class="info-row">
-              <span class="label">姓名:</span>
-              <span class="value">{{ userInfo.name }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">面试时间:</span>
-              <span class="value">{{ userInfo.interviewTime }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">面试时长:</span>
-              <span class="value">{{ userInfo.duration }}</span>
-            </div>
-          </div>
-          <!-- 右侧信息 -->
-          <div class="right-section">
-            <div class="info-row">
-              <span class="label">实名认证:</span>
-              <span class="value verified">已核验</span>
-            </div>
-            <div class="info-row">
-              <span class="label">身份证号:</span>
-              <span class="value">{{ userInfo.idNumber }}</span>
-            </div>
-            <div class="info-row">
-              <span class="label">手机号码:</span>
-              <span class="value">{{ userInfo.phone }}</span>
-            </div>
-          </div>
-          <!-- 印章 -->
-          <div class="stamp">
-            <img v-if="userInfo.pass < 60" src="/@/assets/notPass.png" alt="不通过印章">
-            <img v-else src="/@/assets/pass.png" alt="通过印章">
-          </div>
-        </div>
-      </div>
-
-      <!-- 修改导航选项卡部分 -->
-      <div 
-        class="nav-tabs"
-        :class="{ 'fixed': isNavFixed }"
-        ref="navTabs"
-      >
-        <div 
-          v-for="tab in tabs" 
-          :key="tab.id"
-          class="tab-item"
-          :class="{ 
-            'active': activeTab === tab.id,
-            'link-tab': tab.isLink,
-            'info-tab': tab.isInfo 
-          }"
-          @click="handleTabClick(tab)"
-        >
-          {{ tab.name }}
-          <!-- 添加链接图标 -->
-          <i v-if="tab.isLink" class="el-icon-link"></i>
-          <!-- 添加信息图标 -->
-          <i v-if="tab.isInfo" class="el-icon-info"></i>
-        </div>
-      </div>
-      
-      <!-- 修改占位元素 -->
-      <div 
-        class="nav-placeholder"
-        v-if="isNavFixed"
-        :style="{ height: navHeight + 'px' }"
-      ></div>
-
-      <!-- 各个内容区域添加ref标识 -->
-      <div ref="comprehensive" class="evaluation-result pdfRef">
-        <div class="score-section">
-          <div class="score-section-left">
-            <div class="score-title">综合评价</div>
-            <div class="score-value">
-              <span class="number">{{userInfo.pass}}</span>
-              <span class="unit">分</span>
-              <p class="status-tag" v-if="userInfo.pass < 60">不通过</p>
-              <p class="status-tag" style='background:#67C23A;' v-else>通过</p>
-            </div>
-            <div class="detection-results">
-              <span class="detection-item">
-                身份认证 
-                <span :class="userInfo.idNumber ? 'status-normal' : 'status-warning'">
-                  {{ userInfo.idNumber ? '无异常' : '有异常' }}
-                </span>
-              </span>
-              <span class="separator">/</span>
-              <span class="detection-item">
-                手机号实名认证 
-                <span :class="userInfo.phone ? 'status-normal' : 'status-warning'">
-                  {{ userInfo.phone ? '无异常' : '有异常' }}
-                </span>
-              </span>
-              <span class="separator">/</span>
-              <span class="detection-item">
-                手部综合检测 <span class="status-normal" :style="userInfo.visual_analysis_done ? '':'color:#f56c6c;'"> {{ userInfo.visual_analysis_done ? '无异常' : '有异常' }}</span>
-              </span>
-            </div>
-          </div>
-          <div class="fail-reason">
-            <div class="reason-header">
-            <!--   <i class="el-icon-warning"></i> -->
-              <span>录用建议:{{ hireRecommendation || '正在分析中' }}</span>
-            </div>
-            <div class="reason-content">
-              {{ failReason }}
-            </div>
-          </div>
-          <div class="reason-content redline-section">
-            <div class="redline-header">
-              <span class="redline-title">红线题检测结果</span>
-              <span :class="{
-                'status-normal': redLineQuestions.allCorrect,
-                'status-warning': !redLineQuestions.allCorrect
-              }">
-                {{ redLineQuestions.allCorrect ? '全部正确' : '存在错误,建议不录用' }}
-              </span>
-            </div>
-
-            <!-- 添加未通过题目的详细信息 -->
-            <div v-if="!redLineQuestions.allCorrect && redLineQuestions.failedQuestions.length > 0"
-                 class="failed-questions">
-              <div class="failed-title">未通过的红线题:</div>
-              <div class="failed-list">
-                <div v-for="(question, index) in redLineQuestions.failedQuestions" 
-                     :key="'failed-'+index"
-                     class="failed-item">
-                  <span class="bullet">•</span>
-                  <span class="question-text">{{ question.question_text || `问题 ${index + 1}` }}</span>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-        <!-- 能力雷达图 -->
-        <div class="radar-chart">
-          <v-chart 
-            ref="radarChart"
-            class="radar" 
-            :option="radarOption" 
-            autoresize 
-          />
-        </div>
-      </div>
-
-      <div ref="ability" class="ability-quality pdfRef">
-        <h3 style="font-size: 16px; font-weight: bold;">能力素质 <span style="font-size: 14px; font-weight: normal;">综合得分<span class="score-text">{{ totalScore || '正在分析中' }}</span>{{ totalScore ? '/100 分' : '' }}</span> </h3>
-        
-        <div class="quality-details">
-          <div class="quality-item">
-            <div class="quality-left">
-              <div class="icon-box">
-                <div class="icon advantage-icon">
-                  <img src="/@/assets/yous.png" alt="优势展现">
-                </div>
-                <h5>优势展现</h5>
-              </div>
-              <div class="content">
-                <template v-if="qualityDetails.strengths && qualityDetails.strengths.length">
-                  <p v-for="(strength, index) in qualityDetails.strengths" 
-                     :key="index"
-                     v-html="formatTextWithBrackets(getChineseAnalysis(strength))">
-                  </p>
-                </template>
-                <p v-else>正在分析中</p>
-              </div>
-            </div>
-            
-            <div class="quality-right">
-              <div class="icon-box">
-                <div class="icon suggestion-icon" style="position: relative;">
-                  <img src="/@/assets/que.png" alt="发展建议">
-                  <img style="width: 10px; height: 10px; position: absolute; top: 32%; left: 5px;" src="/@/assets/info.png" alt="">
-                </div>
-                <h5>发展建议</h5>
-              </div>
-              <div class="content">
-                <template v-if="qualityDetails.weaknesses && qualityDetails.weaknesses.length">
-                  <p v-for="(weakness, index) in qualityDetails.weaknesses" 
-                     :key="index"
-                     v-html="formatTextWithBrackets2(getChineseAnalysis(weakness))">
-                  </p>
-                </template>
-                <p v-else>正在分析中</p>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <div>
-        <div class="detailed-evaluation">
-          <div class="line-info">
-            <div class="line-info-left">低</div>
-            <div class="line-info-center">中</div>
-            <div class="line-info-right">高</div>
-          </div>
-          <div class="line-chart">
-            <div class="line-chart-left"></div>
-            <div class="line_left"></div>
-            <div class="line_center"></div>
-            <div class="line_right"></div>
-          </div>
-          <div class="ability-scores">
-            <div v-for="(item, index) in abilityItems" :key="index" class="ability-item">
-              <span class="ability-name">{{ item.tag_name }}</span>
-              <div class="bar-chart-container">
-                <v-chart 
-                  :option="getBarChartOption(item)"
-                  :autoresize="true"
-                  style="width: 100%; height: 30px;"
-                />
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <div ref="cheat" class="detection-section">
-        <div class="detection-section-header">
-            <div class="section-title">作弊检测</div>
-            <div class="status-tag normal">无异常</div>
-        </div>
-     <div class="detection-section-content">
-        <div class="detection-items">
-            <div class="item">
-            <span class="status normal">无风险</span>
-            <span class="separator">/</span>
-            <span class="desc">转移、替考:检测无异常</span>
-            </div>
-            <div class="item">
-            <span class="status normal">无风险</span>
-            <span class="separator">/</span>
-            <span class="desc">读稿:检测无异常</span>
-            </div>
-            <div class="item">
-            <span class="status normal">无风险</span>
-            <span class="separator">/</span>
-            <span class="desc">录屏/切屏:检测无异常</span>
-            </div>
-        </div>
-
-        <div class="risk-standards">
-            <div class="standards-title">风险等级标准</div>
-            <div class="standards-content">
-            <div class="standard-item">
-                <span class="level">无异常:</span>
-                <span class="desc">候选人提供的信息真实/无切屏录屏行为/无互联网查询行为/无互联网传行为</span>
-            </div>
-            <div class="standard-item">
-                <span class="level">低风险:</span>
-                <span class="desc">候选人提供信息真实/可切屏录屏行为/可互联网查询行为/无互联网传行为</span>
-            </div>
-            <div class="standard-item">
-                <span class="level">高风险:</span>
-                <span class="desc">面试者与候选人为一不同人/可切屏录屏行为/可互联网查询行为/过程存在传输行为</span>
-            </div>
-            </div>
-        
-        </div>
-    </div>
-    </div>
-
-    <div ref="psychology" class="psychology-section">
-      <div class="section-title">心理检测</div>
-     <!--  -->
-      <div class="psychology_box">
-        <div class="psychology_body">
-          <div class="psychology-desc">
-            {{ psychologyData.description }}
-          </div>
-          <div class="psychology-recommendation">
-            <span class="label">录用建议:</span>
-            <span class="value">{{ psychologyData.recommendation }}</span>
-          </div>
-        </div>
-        <div class="duv-score">
-          <!-- <span class="score" :class="getPsychologyScoreClass">{{ psychologyData.score }}</span>
-          <span>分</span> -->
-          <span class="risk-level" :class="getPsychologyScoreClass">{{ getPsychologyRiskLevel }}</span>
-        </div>
-      </div>
-    </div>
-
-    <div ref="body" class="body-detection-section">
-      <div class="detection-section-header">
-        <div class="section-title">肢体检测</div>
-        <el-button 
-          type="primary" 
-          link
-          @click="showBodyVerification = true"
-        >
-          查看肢体核验结果
-        </el-button>
-      </div>
-       <!-- 手部检测结果说明 -->
-  <div class="detection-results mt-6">
-    <div class="grid grid-cols-2 gap-4">
-      <!-- 左手检测结果 -->
-      <template v-for="(photo, index) in getHandPhotos('left')" :key="'left-'+index">
-        <div class="detection-item" style="background-color: #f8f8f8;">
-          <span class="position" style="color: #6c6c6c;">{{ getPositionLabel('left', index) }}</span>
-          <span class="status" :class="{
-            'status-normal': photo.part_verification?.is_correct_part,
-            'status-error': !photo.part_verification?.is_correct_part
-          }">
-            {{ photo.part_verification?.is_correct_part ? '动作检测无异常' : '动作检测异常' }}
-          </span>
-          <span class="description text-gray-500 text-sm">
-            {{ photo.part_verification?.is_correct_part ? getDefaultDescription('left', index) : photo.part_verification?.explanation || '检测未通过' }}
-          </span>
-        </div>
-      </template>
-
-      <!-- 右手检测结果 -->
-      <template v-for="(photo, index) in getHandPhotos('right')" :key="'right-'+index">
-        <div class="detection-item" style="background-color: #f8f8f8;">
-          <span class="position"  style="color: #6c6c6c;">{{ getPositionLabel('right', index) }}</span>
-          <span class="status" :class="{
-            'status-normal': photo.part_verification?.is_correct_part,
-            'status-error': !photo.part_verification?.is_correct_part
-          }">
-            {{ photo.part_verification?.is_correct_part ? '动作检测无异常' : '动作检测异常' }}
-          </span>
-          <span class="description text-gray-500 text-sm">
-            {{ photo.part_verification?.is_correct_part ? getDefaultDescription('right', index) : photo.part_verification?.explanation || '检测未通过' }}
-          </span>
-        </div>
-      </template>
-    </div>
-  </div>
-    </div>
-
-    <div ref="answers" class="report-footer">
-      <!-- 添加问答记录展示区域 -->
-      <div class="qa-section">
-        <h3 class="section-title">问答记录</h3>
-        <div class="qa-content">
-          <div  class="space-y-4">
-            <div v-for="(record, index) in interviewRecords" :key="index" class=" rounded-lg p-4"  v-show="record.question_form === 0">
-              <!-- 题目标题部分 -->
-              <div class="mb-4" v-if="record.question_form === 0">
-                <div class="flex items-start  space-x-2 w-2/3" >
-                  <div class="px-2 py-1 rounded text-sm" style="font-size: 14px; font-weight: 600; font-family: 微软雅黑;">第{{ index + 1 }}题:</div>
-                <!--   <div class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm">{{ getQuestionTypeName(record.question_form || 0) }}</div> -->
-                  <div class="flex-1">
-                    <span class="text-gray-800 color-black " style="font-size: 14px;">{{ record.question }}</span>
-                  </div>
-                </div>
-                <!-- 视频问答题布局 || record.question_form === 5-->
-                <div v-if="record.question_form === 0" class="mt-4">
-                  <!-- 原有的视频问答题布局 -->
-                  <div class="flex space-x-4">
-                    <!-- 右侧答案和评分 -->
-                    <div class="w-2/3">
-                      <div class="bg-white p-4 rounded-xl">
-                        <div class="">
-                          <div class="flex flex-col space-x-2">
-                            <div class="px-2 py-1 rounded text-sm" id="candidate-answer" style="font-size: 14px;">候选人回答:</div>
-                            <div class="flex-1">
-                              <p class="text-gray-700 mb-0" style="font-size: 14px; line-height: 20px;  color: #808080;">{{ record.answer }}</p>
-                            </div>
-                          </div>
-                        </div>
-                        
-                        <div>
-                        </div>
-                      </div>
-                    </div>
-                    <!-- 左侧视频播放器 -->
-                    <div class="w-1/3">
-                      <div class="video-container">
-                        <video
-                          class="w-full h-full object-cover rounded-lg"
-                          controls
-                          :src="record.video_url"
-                          preload="metadata"
-                          :poster="fallbackImageBase64Ref"
-                          @loadeddata="handleVideoLoaded"
-                          @error="handleVideoError"
-                        >
-                          您的浏览器不支持视频播放。
-                        </video>
-                      </div>
-                    </div>
-                  </div>
-                  
-                  <!-- 追问题列表 -->
-                  <div v-if="record.follow_up_questions && record.follow_up_questions.length > 0" 
-                       class="mt-2  border-gray-200">
-                    <div v-for="(followUp, fIndex) in record.follow_up_questions" 
-                         :key="fIndex" 
-                         class="">
-                      <div class="flex items-start space-x-2">
-                       
-                        <div class="flex-1">
-                         
-                          <!-- 追问题的视频和答案布局 -->
-                          <div class="mt-2">
-                            <div class="flex space-x-4">
-                              <!-- 右侧答案和评分 -->
-                              <div class="w-2/3">
-                                <div class=" p-4 rounded-xl bg-white">
-                                  <div class="">
-                                    <div class="flex items-start space-x-2">
-                                        <div class="bg-purple-100 text-purple-800 px-2 py-1 rounded text-sm" style="font-size: 14px;background-color: #fdd3bd; color:#f7681d;">追问题</div>
-                                      <!-- <div class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">候选人</div> -->
-                                      <div class="flex-1">
-                                        <span class="font-medium text-gray-800" style="font-size: 14px;">{{ followUp.question }}</span>
-                                        <p class="text-gray-700 mb-0" style="font-size: 14px; line-height: 20px;  color: #808080;">{{ followUp.answer }}</p>
-                                      </div>
-                                    </div>
-                                  </div>
-                                  
-                                  <div>
-                                    <!-- <div class="flex items-start space-x-2">
-                                      <div class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-sm">AI评分</div>
-                                      <div class="flex-1">
-                                        <p class="text-gray-600 m-0">{{ followUp.analysis }}</p>
-                                        <div class="mt-1 text-blue-500">得分:{{ followUp.score }}</div>
-                                      </div>
-                                    </div> -->
-                                  </div>
-                                </div>
-                              </div>
-                              <!-- 左侧视频播放器 -->
-                              <div v-if="followUp.video_url" class="w-1/3">
-                                <div class="video-container">
-                                  <video
-                                    class="w-full h-full object-cover rounded-lg"
-                                    controls
-                                    :src="followUp.video_url"
-                                    preload="metadata"
-                                    :poster="fallbackImageBase64Ref"
-                                    @loadeddata="handleVideoLoaded"
-                                    @error="handleVideoError"
-                                  >
-                                    您的浏览器不支持视频播放。
-                                  </video>
-                                </div>
-                              </div>
-                            </div>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                  <!-- ai评分 -->
-                  <div class="flex flex-col items-start bg-[#f8f8f8] p-4 rounded-lg">
-                    <div class="flex items-center">
-                      <div class="text-[14px] text-[#000] mr-2">本题评分:</div>
-                      <div class="text-[#409EFF] font-medium">{{ record.score }}分</div>
-                    </div>
-                    <div class="flex ml-0">
-                      <div class="text-[14px] text-[#f7681d] mr-2">整体分析:</div>
-                      <div class="analysis-content flex-1">
-                        <p class="text-[14px] text-[#666666] m-0 ml-4 analysis-text">
-                          {{ formatAnalysis(record.analysis) }}
-                        </p>
-                      </div>
-                    </div>
-                    <div class="flex items-center">
-                      <div class="text-[14px] text-[#666666] mr-2">考核点:</div>
-                      <div class="analysis-content flex-1">
-                        <div class="assessment-points">
-                          <!-- 从分析文本中提取的考核点 -->
-                          <template v-for="(point, index) in extractAssessmentPoints(record.analysis)" :key="index">
-                            <div class="assessment-item">
-                              <span class="point-name">{{ point.name }}</span>
-                             <!--  <span class="point-score" :class="getScoreClass(point.score)">{{ point.score }}</span> -->
-                            </div>
-                            <!-- 添加分隔符,除了最后一项 -->
-                            <span v-if="index < extractAssessmentPoints(record.analysis).length - 1" class="separator">/</span>
-                          </template>
-                        </div>
-                      </div>
-                      <div class="progress-bar" style="margin-left: 20px;width: 180px; font-size: 12px;color: #808080;">
-                        <el-progress 
-                          :percentage="Number(record.score)" 
-                          :stroke-width="6"
-                          :format="formats"
-                          :color="getProgressColor(record.score)"
-                          class="custom-progress"
-                          style="font-size: 12px;color: #808080;"
-                        />
-                      </div>
-                    </div>
-                  </div>
-                </div>
-
-                
-              </div>
-            </div>
-          </div>
-          <!-- 追问题列表 -->
-          <div v-if="followUpQuestions && followUpQuestions.length > 0" 
-               class="mt-2  border-gray-200">
-            <div v-for="(followUp, fIndex) in [followUpQuestions[followUpQuestions.length - 1]]" 
-                 :key="fIndex" 
-                 class="">
-                 <div class="flex items-start  space-x-2 pb-4" >
-                 <!--  {{ JSON.stringify(followUp.user_text) }} -->
-                  <div class="px-2 py-1 rounded text-sm" style="font-size: 14px; font-weight: 600; font-family: 微软雅黑;">候选人提问:</div>
-                  <div class="flex-1">
-                    <span class="text-gray-800 color-black " style="font-size: 14px;">{{ followUp.user_text }}</span>
-                  </div>
-                </div>
-                <div class="bg-white p-4 rounded-xl mb-4">
-                  <div class="">
-                    <div class="flex flex-col space-x-2">
-                      <div class="px-2 py-1 rounded text-sm" id="candidate-answer" style="font-size: 14px;">智能体回答:</div>
-                      <div class="flex-1">
-                        <p class="text-gray-700 mb-0" style="font-size: 14px; line-height: 20px;  color: #808080;">{{ followUp.ai_reply }}</p>
-                      </div>
-                    </div>
-                  </div>
-                  
-                  <div>
-                  </div>
-                </div>
-                 <!-- <div class="flex-1">
-                    <span class="text-gray-800 color-black " style="font-size: 14px;">{{ followUp.ai_reply }}</span>
-                  </div> -->
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <!-- 将导出按钮移到这里,添加fixed-btn类 -->
-    <el-button
-          type="primary"
-          circle
-           class="export-btn fixed-btn"
-          :loading="exporting"
-          @click="exportPDF"
-        >
-          <el-icon><Download /></el-icon>
-        </el-button>
- <!--  <el-button 
-    type="primary"
-    size="small"
-    @click="exportPDF"
-    :loading="exporting"
-    class="export-btn fixed-btn"
-  >
-    <i class="el-icon-download"></i>
-    {{ exporting ? '导出中...' : '导出PDF' }}
-  </el-button> -->
-</div>
-
-<!-- 添加个人详细信息弹窗 -->
-<el-dialog
-  title="个人详细信息"
-  v-model="showInfoDialog"
-  width="60%"
-  :before-close="handleDialogClose"
-  class="info-dialog"
->
-  <div class="info-content">
-    <div class="info-section">
-      <h4>基本信息</h4>
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="label">姓名:</span>
-          <span class="value">{{ userProfile.name }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">性别:</span>
-          <span class="value">{{ userProfile.gender }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">年龄:</span>
-          <span class="value">{{ userProfile.age }}岁</span>
-        </div>
-        <div class="info-item">
-          <span class="label">身份证:</span>
-          <span class="value">{{ userProfile.idCard }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">民族:</span>
-          <span class="value">{{ userProfile.ethnicity }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">政治面貌:</span>
-          <span class="value">{{ userProfile.politicalStatus }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">婚姻状况:</span>
-          <span class="value">{{ userProfile.maritalStatus }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">身高:</span>
-          <span class="value">{{ userProfile.height }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">体重:</span>
-          <span class="value">{{ userProfile.weight }}</span>
-        </div>
-      </div>
-    </div>
-    
-    <div class="info-section">
-      <h4>联系方式</h4>
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="label">手机号码:</span>
-          <span class="value">{{ userProfile.phone }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">籍贯:</span>
-          <span class="value">{{ userProfile.nativePlace }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">现居地址:</span>
-          <span class="value">{{ userProfile.address }}</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="info-section">
-      <h4>教育背景</h4>
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="label">学历:</span>
-          <span class="value">{{ userProfile.education }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">毕业院校:</span>
-          <span class="value">{{ userProfile.schoolName }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">专业:</span>
-          <span class="value">{{ userProfile.major }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">毕业时间:</span>
-          <span class="value">{{ userProfile.graduationDate }}</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="info-section">
-      <h4>工作信息</h4>
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="label">当前公司:</span>
-          <span class="value">{{ userProfile.currentCompany }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">所在部门:</span>
-          <span class="value">{{ userProfile.department }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">当前职位:</span>
-          <span class="value">{{ userProfile.position }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">工作经验:</span>
-          <span class="value">{{ userProfile.experience }}</span>
-        </div>
-        <div class="info-item">
-          <span class="label">期望薪资:</span>
-          <span class="value">{{ userProfile.expectedSalary }}</span>
-        </div>
-      </div>
-    </div>
-
-    <div class="info-section">
-      <h4>其他信息</h4>
-      <div class="info-grid">
-        <div class="info-item">
-          <span class="label">特长爱好:</span>
-          <span class="value">{{ userProfile.specialties }}</span>
-        </div>
-      </div>
-    </div>
-  </div>
-</el-dialog>
-
-<!-- 添加肢体核验结果弹窗 -->
-<el-dialog
-  title="肢体核验结果"
-  v-model="showBodyVerification"
-  width="80%"
-  :before-close="handleBodyVerificationClose"
-  class="body-verification-dialog"
->
-  <!-- 将原来的手势验证内容移到这里 -->
-  <div class="hand-verification">
-    <div class="grid grid-cols-2 gap-8">
-      <!-- 左手验证结果 -->
-      <div>
-        <h4 class="text-gray-600 mb-2">左手手势验证</h4>
-        <div class="grid grid-cols-3 gap-2">
-          <div v-for="(photo, index) in getHandPhotos('left')" 
-               :key="index"
-               class="relative aspect-square rounded-lg overflow-hidden bg-gray-100"
-               :class="{'border-2': photo.part_verification, 
-                       'border-green-500': photo.part_verification?.is_correct_part,
-                       'border-red-500': photo.part_verification && !photo.part_verification.is_correct_part}"
-          >
-            <!-- 使用 el-tooltip 包裹内容 -->
-            <el-tooltip
-              v-if="photo.part_verification && !photo.part_verification.is_correct_part"
-              :content="photo.part_verification.explanation || '未提供失败原因'"
-              placement="top"
-              effect="dark"
-              :show-after="200"
-            >
-              <div class="w-full h-full">
-                <img :src="photo.url" 
-                     :alt="photo.description" 
-                     class="w-full h-full object-cover"
-                     @error="handleImageError">
-                
-                <!-- 底部状态显示 -->
-                <div class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                     :class="{
-                       'bg-green-500 bg-opacity-90': photo.part_verification?.is_correct_part,
-                       'bg-red-500 bg-opacity-90': photo.part_verification && !photo.part_verification.is_correct_part
-                     }">
-                  {{ photo.part_verification?.is_correct_part ? '通过' : '未通过' }}
-                </div>
-              </div>
-            </el-tooltip>
-            <template v-else>
-                <img :src="photo.url" 
-                    :alt="photo.description" 
-                    class="w-full h-full object-cover"
-                    @error="handleImageError">
-                
-                <!-- 底部状态显示 -->
-                <div v-if="photo.part_verification"
-                    class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                    :class="{
-                        'bg-green-500 bg-opacity-90': photo.part_verification.is_correct_part,
-                        'bg-red-500 bg-opacity-90': !photo.part_verification.is_correct_part
-                    }">
-                {{ photo.part_verification.is_correct_part ? '通过' : '未通过' }}
-                </div>
-            </template>
-          </div>
-        </div>
-      </div>
-
-      <!-- 右手验证结果 -->
-      <div>
-        <h4 class="text-gray-600 mb-2">右手手势验证</h4>
-        <div class="grid grid-cols-3 gap-2">
-          <div v-for="(photo, index) in getHandPhotos('right')" 
-               :key="index"
-               class="relative aspect-square rounded-lg overflow-hidden bg-gray-100"
-               :class="{'border-2': photo.part_verification, 
-                       'border-green-500': photo.part_verification?.is_correct_part,
-                       'border-red-500': photo.part_verification && !photo.part_verification.is_correct_part}"
-          >
-            <!-- 使用 el-tooltip 包裹内容 -->
-            <el-tooltip
-              v-if="photo.part_verification && !photo.part_verification.is_correct_part"
-              :content="photo.part_verification.explanation || '未提供失败原因'"
-              placement="top"
-              effect="dark"
-              :show-after="200"
-            >
-              <div class="w-full h-full">
-                <img :src="photo.url" 
-                     :alt="photo.description" 
-                     class="w-full h-full object-cover"
-                     @error="handleImageError">
-                
-                <!-- 底部状态显示 -->
-                <div class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                     :class="{
-                       'bg-green-500 bg-opacity-90': photo.part_verification?.is_correct_part,
-                       'bg-red-500 bg-opacity-90': photo.part_verification && !photo.part_verification.is_correct_part
-                     }">
-                  {{ photo.part_verification?.is_correct_part ? '通过' : '未通过' }}
-                </div>
-              </div>
-            </el-tooltip>
-            <template v-else>
-                      <img :src="photo.url" 
-                           :alt="photo.description" 
-                           class="w-full h-full object-cover"
-                           @error="handleImageError">
-                          
-                          <!-- 底部状态显示 -->
-                          <div v-if="photo.part_verification"
-                               class="absolute bottom-0 left-0 right-0 py-1 px-2 text-xs text-center text-white z-10"
-                               :class="{
-                                 'bg-green-500 bg-opacity-90': photo.part_verification.is_correct_part,
-                                 'bg-red-500 bg-opacity-90': !photo.part_verification.is_correct_part
-                               }">
-                            {{ photo.part_verification.is_correct_part ? '通过' : '未通过' }}
-                          </div>
-                        </template>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-
- 
-</el-dialog>
-</div>
-</template>
-
-<script>
-import { use } from 'echarts/core'
-import { CanvasRenderer } from 'echarts/renderers'
-import { RadarChart } from 'echarts/charts'
-import { TitleComponent, TooltipComponent, LegendComponent } from 'echarts/components'
-import VChart from 'vue-echarts'
-import { BarChart } from 'echarts/charts'
-import { GridComponent } from 'echarts/components'
-// 引入html2canvas和jspdf
-import html2canvas from 'html2canvas'
-import jsPDF from 'jspdf'
-import { Share, Download, Printer, ArrowUp, User } from '@element-plus/icons-vue'
-import { ElMessage } from 'element-plus'
-
-use([
-  CanvasRenderer,
-  RadarChart,
-  TitleComponent,
-  TooltipComponent,
-  LegendComponent,
-  BarChart,
-  GridComponent
-])
-
-export default {
-  name: 'JobApplicationReport',
-  components: {
-    VChart
-  },
-  data() {
-    return {
-      userInfo: {
-        avatar: '/@/assets/default-avatar.png',
-        name: '张某三',
-        interviewTime: '2021-06-30 18:00',
-        duration: '18分 12秒',
-        idNumber: '24893119999999999',
-        phone: '14893725981',
-        pass: 60,
-        visual_analysis_done:false
-      },
-      totalScore: 52.0,
-      abilityItems: [],
-      isPassed: false,
-      hireRecommendation:'',
-      failReason: '候选人答题过程中存在非正常表现和应答,同时,可行性较为不足,候选人表现出来的综合能力也不符合岗位要求的行为和动机表现,在特定学习方面,候选人未能呈现出明显的优势和发展潜质。',
-      qualityDetails: {
-        strengths: [],
-        weaknesses: []
-      },
-      activeTab: 'comprehensive',
-      tabs: [
-        { id: 'comprehensive', name: '综合评价' },
-        { id: 'ability', name: '能力素质' },
-        { id: 'cheat', name: '作弊检测' },
-        { id: 'psychology', name: '心理检测' },
-        { id: 'body', name: '肢体检测' },
-        { id: 'personal-info', name: '个人详细信息', isInfo: true },
-        { id: 'answers', name: '问答记录' },
-        { id: 'other-answers', name: '其余答题详情', isLink: true }
-      ],
-      isNavFixed: false,
-      navHeight: 0,
-      navOffsetTop: 0,
-      loading: false,
-      error: null,
-      // 分析结果中英文对照表
-      analysisMap: {
-        'clear_expression': '表达清晰',
-        'logical_thinking': '逻辑思维能力强',
-        'professional_knowledge': '专业知识扎实',
-        'communication_skills': '沟通能力出色',
-        'problem_solving': '问题解决能力强',
-        'team_collaboration': '团队协作意识好',
-        'learning_ability': '学习能力强',
-        'lack_experience': '经验不足',
-        'weak_professional': '专业知识有待提升',
-        'poor_expression': '表达能力需要提高',
-        'weak_logic': '逻辑思维需要加强',
-        'weak_communication': '沟通技巧需要改进',
-        'weak_teamwork': '团队协作意识不足',
-        'weak_learning': '学习能力有待提升'
-      },
-      psychologyData: {
-        score: 35,
-        description: '轻微焦虑或负面情绪,仍具备岗位适应力。候选人在某些方面可能存在轻微的心理压力,但总体上仍保持稳定,基本符合岗位要求。',
-        recommendation: '可考虑录取'
-      },
-      redLineQuestions: {
-        allCorrect: false,
-        failedQuestions: []
-      },
-      // 修改手势验证数据结构
-      handVerificationData: {
-        left: [],
-        right: []
-      },
-      // 添加新的数据属性
-      reportTime: '2025.5.19',
-      reportId: '2489372598',
-      isMobile: false,
-      showInfoDialog: false,
-      userProfile: {
-        name: '',
-        gender: '',
-        age: '',
-        education: '',
-        phone: '',
-        email: '',
-        address: '',
-        position: '',
-        experience: '',
-        expectedSalary: '',
-        idCard: '',
-        height: '',
-        weight: '',
-        nativePlace: '',
-        politicalStatus: '',
-        ethnicity: '',
-        maritalStatus: '',
-        specialties: '',
-        schoolName: '',
-        major: '',
-        graduationDate: '',
-        currentCompany: '',
-        department: '',
-      },
-      showBodyVerification: false, // 添加控制弹窗显示的数据
-      competencyMap: {
-        'communication_cooperation': '团队协作能力',
-        'learning_ability': '应变能力',
-        'service_awareness': '服务意识',
-        'customer_insight': '客户需求洞察力',
-        'position_matching': '应变能力',
-        'communication_expression': '沟通表达能力'
-      },
-      // 修改雷达图数据结构
-      radarData: {
-        indicator: [
-          { name: '团队协作能力', value: 50, max: 100 },
-          { name: '应变能力', value: 50, max: 100 },
-          { name: '服务意识', value: 50, max: 100 },
-          { name: '客户需求洞察力', value: 50, max: 100 },
-          { name: '应变能力', value: 50, max: 100 },
-          { name: '沟通表达能力', value: 50, max: 100 }
-        ],
-        values: [50, 50, 50, 50, 50, 50]  // 初始值设为0
-      },
-      exporting: false, // 添加导出状态控制
-      radarChartInstance: null,
-      interviewRecords: [], // 存储问答记录
-      followUpQuestions: [], // 存储追问题
-      noTableHeight: 0
-    }
-  },
-  async created() {
-    await this.fetchApplicationDetail()
-  },
-  mounted() {
-    // 获取导航栏的初始位置和高度
-    this.$nextTick(() => {
-      const navElement = this.$refs.navTabs
-      if (navElement) {
-        this.navHeight = navElement.offsetHeight
-        this.navOffsetTop = navElement.offsetTop
-        
-        // 添加滚动监听
-        window.addEventListener('scroll', this.handleScroll)
-      }
-    })
-    // 检测设备类型
-    this.checkDevice();
-    // 监听窗口大小变化
-    window.addEventListener('resize', this.checkDevice);
-    // 添加获取图表实例的ref
-    this.$nextTick(() => {
-      // 保存图表实例的引用
-      this.radarChartInstance = this.$refs.radarChart
-    })
-    // 等待组件渲染完成后保存图表实例
-    this.$nextTick(() => {
-      if (this.$refs.radarChart) {
-        this.radarChartInstance = this.$refs.radarChart
-      }
-    })
-  },
-  beforeDestroy() {
-    // 移除滚动监听
-    window.removeEventListener('scroll', this.handleScroll)
-    // 移除监听器
-    window.removeEventListener('resize', this.checkDevice);
-  },
-  computed: {
-    radarOption() {
-      return {
-        radar: {
-          indicator: this.radarData.indicator,
-          center: ['50%', '50%'],
-          radius: '65%',
-          splitNumber: 4,
-          shape: 'circle',
-          axisName: {
-            color: '#666',
-            fontSize: 12,
-            formatter: (name) => {
-              const index = this.radarData.indicator.findIndex(item => item.name === name);
-              const value = this.radarData.values[index];
-              // 添加中文释义
-              const nameMappings = {
-                '团队协作能力': '团队协作',
-                '应变能力': '应变能力',
-                '服务意识': '服务意识',
-                '客户需求洞察力': '需求洞察',
-                '沟通表达能力': '沟通表达'
-              };
-              const displayName = nameMappings[name] || name;
-              return `${value}\n${displayName}`;
-            }
-          },
-          nameGap: 15,
-          axisLabel: {
-            show: false
-          },
-          splitArea: {
-            show: true,
-            areaStyle: {
-              color: ['#fff']
-            }
-          },
-          splitLine: {
-            show: true,
-            lineStyle: {
-              color: '#E7E9ED',
-              type: 'solid',
-              width: 1
-            }
-          },
-          axisLine: {
-            show: true,
-            lineStyle: {
-              color: '#E7E9ED',
-              width: 1
-            }
-          }
-        },
-        series: [{
-          type: 'radar',
-          symbol: 'none',
-          itemStyle: {
-            normal: {
-              lineStyle: {
-                color: '#c1dafc',
-                width: 2
-              },
-              areaStyle: {
-                type: 'default',
-                color: 'rgba(193, 218, 252, 0.6)',
-                shadowBlur: 10,
-                shadowColor: 'rgba(193, 218, 252, 0.5)',
-                shadowOffsetY: 5
-              }
-            }
-          },
-          data: [{
-            value: this.radarData.values
-          }]
-        }]
-      }
-    },
-    // 添加新的计算属性
-    getPsychologyScoreClass() {
-      const score = this.psychologyData.score;
-      if (score <= 20) return 'low-risk';
-      if (score <= 55) return 'medium-risk';
-      return 'high-risk';
-    },
-    
-    getPsychologyRiskLevel() {
-      const score = this.psychologyData.score;
-      if (score <= 20) return '低风险';
-      if (score <= 55) return '中风险';
-      return '高风险';
-    }
-  },
-  methods: {
-    async fetchApplicationDetail() {
-      try {
-        const application_id = this.$route.query.id || this.$route.params.id
-        console.log(application_id)
-        this.loading = true
-        const response = await fetch(
-          `${import.meta.env.VITE_API_URL}/api/job/application_detail?tenant_id=${1}&application_id=${application_id}`
-        )
-
-        if (!response.ok) {
-          throw new Error('获取数据失败')
-        }
-
-        const data = await response.json()
-        
-        if (data.code === 2000) {
-          // 添加数据安全检查
-          const applicationData = data.data || {}
-          this.reportTime = applicationData.application?.update_datetime || ''
-          // 更新用户信息
-          this.userInfo = {
-            avatar: this.getFacePhoto(applicationData.posture_photos) || '/@/assets/default-avatar.png',
-            name: applicationData.applicant?.name || '未知',
-            interviewTime: applicationData.application?.interview_completion_time || '-',
-            duration: applicationData.application?.interview_duration_formatted || '-',
-            idNumber: applicationData.applicant?.id_card || '-',
-            phone: applicationData.applicant?.phone || '-',
-            pass: applicationData.application?.comprehensive_score || 0,
-            visual_analysis_done:applicationData.application.analysis_status.visual_analysis_done
-          }
-          this.reportId = applicationData.application?.report_id || ''
-          // 处理手势验证照片数据
-          if (applicationData.posture_photos && Array.isArray(applicationData.posture_photos)) {
-            // 分类处理左右手照片
-            const leftHandPhotos = applicationData.posture_photos.filter(photo => 
-              photo?.description?.includes('left_') || photo?.description?.includes('左手')
-            ) || []
-            
-            const rightHandPhotos = applicationData.posture_photos.filter(photo => 
-              photo?.description?.includes('right_') || photo?.description?.includes('右手')
-            ) || []
-
-            // 更新左手照片数据
-            this.handVerificationData.left = leftHandPhotos.map(photo => ({
-              url: photo?.photo_url || '/@/assets/default-image.png',
-              description: this.getPhotoDescription(photo?.description || ''),
-              part_verification: {
-                is_correct_part: photo?.part_verification?.is_correct_part ?? true,
-                explanation: photo?.part_verification?.explanation || '手势验证通过'
-              }
-            }))
-
-            // 更新右手照片数据
-            this.handVerificationData.right = rightHandPhotos.map(photo => ({
-              url: photo?.photo_url || '/@/assets/default-image.png',
-              description: this.getPhotoDescription(photo?.description || ''),
-              part_verification: {
-                is_correct_part: photo?.part_verification?.is_correct_part ?? true,
-                explanation: photo?.part_verification?.explanation || '手势验证通过'
-              }
-            }))
-          }
-
-          // 更新其他数据,添加空值检查
-          if (applicationData.application) {
-            this.totalScore = applicationData.application.comprehensive_score || 0
-            this.hireRecommendation = applicationData.application.hire_recommendation || ''
-            this.failReason = applicationData.application.hire_reason || ''
-          }
-
-          // 更新能力标签得分数据,添加空值检查
-          const videoAnalysisData = applicationData.application?.comprehensive_analysis?.video_analysis_data
-          if (videoAnalysisData?.competency_scores) {
-            this.abilityItems = Object.entries(videoAnalysisData.competency_scores)
-              .map(([key, item]) => ({
-                tag_name: this.getCompetencyName(key) || item.tag_name || key,
-                weighted_score: Math.round(item.weighted_score || 0)
-              }))
-          } else {
-            // 设置默认的能力项
-            this.abilityItems = [
-              { tag_name: '团队协作能力', weighted_score: 0 },
-              { tag_name: '应变能力', weighted_score: 0 },
-              { tag_name: '服务意识', weighted_score: 0 },
-              { tag_name: '客户需求洞察力', weighted_score: 0 },
-              { tag_name: '沟通表达能力', weighted_score: 0 }
-            ]
-          }
-
-          // 更新优势和劣势数据,添加空值检查
-          if (videoAnalysisData) {
-            this.qualityDetails = {
-              strengths: videoAnalysisData.strengths || [],
-              weaknesses: videoAnalysisData.weaknesses || []
-            }
-          } else {
-            this.qualityDetails = {
-              strengths: [],
-              weaknesses: []
-            }
-          }
-
-          // 更新红线题数据
-          if (applicationData.required_questions_summary) {
-            this.redLineQuestions = {
-              allCorrect: applicationData.required_questions_summary.all_correct || false,
-              failedQuestions: applicationData.required_questions_summary.failed_questions_details || []
-            }
-          }
-
-          // 获取心理测评分数
-          const psychologyScore = applicationData.scoring_summary?.total_score_obtained
-
-          // 更新心理检测组件数据
-          this.psychologyData = {
-            score: psychologyScore || 0,
-            description: this.getPsychologyDescription(psychologyScore),
-            recommendation: this.getHireRecommendation(psychologyScore)
-          }
-
-          // 更新雷达图数据,添加空值检查
-          if (videoAnalysisData?.competency_scores) {
-            this.updateRadarData(videoAnalysisData.competency_scores)
-          } else {
-            // 设置默认的雷达图数据
-            this.radarData = {
-              indicator: [
-                { name: '团队协作能力', max: 100 },
-                { name: '应变能力', max: 100 },
-                { name: '服务意识', max: 100 },
-                { name: '客户需求洞察力', max: 100 },
-                { name: '沟通表达能力', max: 100 }
-              ],
-              values: [0, 0, 0, 0, 0]
-            }
-          }
-          // 更新追问题列表
-          this.followUpQuestions = applicationData.follow_up_records.map(item => {
-            let parsedUserText = null;
-            try {
-              // 尝试解析user_text字符串为对象
-              parsedUserText = JSON.parse(item.user_text.replace(/'/g, '"'));
-              console.log(parsedUserText)
-            } catch (error) {
-              console.error('Failed to parse user_text:', error);
-            }
-            
-            // 获取text值并去除双引号
-            const textValue = parsedUserText?.text || '暂无提问';
-            const cleanText = textValue.replace(/^"|"$/g, ''); // 去除开头和结尾的双引号
-            
-            return {
-              ...item,
-              user_text: cleanText
-            }
-          });
-          console.log(this.followUpQuestions)
-          // 添加获取问答记录的逻辑
-          if (data.data?.interview_progress) {
-            this.interviewRecords = this.processInterviewRecords(data.data.interview_progress)
-            console.log(this.interviewRecords)
-          }
-          
-        }
-      } catch (error) {
-        console.error('获取应用详情失败:', error)
-        this.error = error.message
-      } finally {
-        this.loading = false
-      }
-    },
-    formats(percentage) {
-      console.log(percentage)
-      return `${percentage}分`
-    },
-    format(percentage) {
-      return `${percentage}分`
-    },
-    scrollToSection(tabId) {
-      // 如果是答题记录标签,则进行路由跳转
-      if (tabId === 'other-answers') {
-        // 假设当前路由中有 applicationId 参数
-        const applicationId = this.$route.query.id || this.$route.params.id
-        const baseUrl = window.location.origin;
-        // 使用window.open在新标签页打开
-        window.open(`${baseUrl}/#/topic?applicationId=${applicationId}`, '_blank')
-        return
-      }
-
-      // 其他标签的原有滚动逻辑
-      this.activeTab = tabId
-      const element = this.$refs[tabId]
-      if (element) {
-        element.scrollIntoView({ behavior: 'smooth' })
-      }
-    },
-    handleScroll() {
-      const scrollTop = window.pageYOffset || document.documentElement.scrollTop
-      
-      // 更新固定状态
-      this.isNavFixed = scrollTop > this.navOffsetTop
-
-      // 更新当前激活的标签
-      this.updateActiveTab(scrollTop)
-    },
-    updateActiveTab(scrollTop) {
-      const navHeight = this.navHeight
-      
-      // 获取所有section的位置
-      const sections = this.tabs.map(tab => {
-        const element = this.$refs[tab.id]
-        return {
-          id: tab.id,
-          top: element.getBoundingClientRect().top + window.pageYOffset - navHeight - 20
-        }
-      })
-
-      // 找到当前滚动位置对应的section
-      for (let i = sections.length - 1; i >= 0; i--) {
-        if (scrollTop >= sections[i].top) {
-          this.activeTab = sections[i].id
-          break
-        }
-      }
-    },
-    // 获取分析结果的中文释义
-    getChineseAnalysis(key) {
-      return this.analysisMap[key] || key
-    },
-    // 添加获取心理状态描述的方法
-    getPsychologyDescription(score) {
-      if (score === undefined) return '未进行心理测评或数据缺失'
-      
-      if (score <= 20) {
-        return '心理状态健康,情绪稳定、认知正常。候选人在心理测评中表现出稳定的情绪状态和健康的心理特质,适合岗位要求。'
-      } else if (score <= 35) {
-        return '轻微焦虑或负面情绪,仍具备岗位适应力。候选人在某些方面可能存在轻微的心理压力,但总体上仍保持稳定,基本符合岗位要求。'
-      } else if (score <= 55) {
-        return '存在一定情绪波动或压力反应。候选人在测评中表现出一定程度的心理压力或情绪波动,建议进一步面谈评估。'
-      } else if (score <= 70) {
-        return '心理压力较大,可能影响工作稳定性。候选人在测评中表现出较明显的心理压力或情绪不稳定性,建议慎重录用或考虑调岗。'
-      } else {
-        return '可能存在较严重心理或精神问题。候选人在测评中表现出明显的心理或情绪问题,不建议录用。'
-      }
-    },
-    // 添加获取录用建议的方法
-    getHireRecommendation(score) {
-      if (score === undefined) return '无法给出建议'
-      
-      if (score <= 20) {
-        return '建议优先录取'
-      } else if (score <= 35) {
-        return '可考虑录取'
-      } else if (score <= 55) {
-        return '建议进一步面谈评估'
-      } else if (score <= 70) {
-        return '慎重录用或建议调岗'
-      } else {
-        return '不建议录用'
-      }
-    },
-    // 添加照片描述转换方法
-    getPhotoDescription(description) {
-      const descriptionMap = {
-        'left_palm': '左手手心',
-        'left_back': '左手手背',
-        'left_fist': '左手腕拳',
-        'right_palm': '右手手心',
-        'right_back': '右手手背',
-        'right_fist': '右手腕拳',
-        // 添加中文描述的映射
-        '左手手心': '左手手心',
-        '左手手背': '左手手背',
-        '左手腕拳': '左手腕拳',
-        '右手手心': '右手手心',
-        '右手手背': '右手手背',
-        '右手腕拳': '右手腕拳'
-      }
-      return descriptionMap[description] || description
-    },
-    // 修改获取手部照片的方法
-    getHandPhotos(hand) {
-      const photos = this.handVerificationData[hand] || []
-      return photos.map(photo => ({
-        url: photo.url,
-        status: 'normal',
-        photo_id: 1,
-        detections: [],
-        description: photo.description,
-        photo_index: 1,
-        analysis_model: '',
-        part_verification: photo.part_verification,
-        analysis_timestamp: '',
-        overall_assessment: '正常'
-      }))
-    },
-    // 图片错误处理函数保持不变
-    handleImageError(event) {
-      const imgElement = event.target
-      if (imgElement) {
-        imgElement.src = '/@/assets/default-image.png'
-      }
-    },
-    checkDevice() {
-      this.isMobile = window.innerWidth <= 768;
-    },
-    async handleTabClick(tab) {
-      if (tab.isInfo) {
-        // 打开弹窗前获取最新的用户信息
-        await this.fetchUserProfile();
-        this.showInfoDialog = true;
-      } else if (tab.isLink) {
-        this.scrollToSection(tab.id);
-      } else {
-        this.scrollToSection(tab.id);
-      }
-    },
-    handleDialogClose() {
-      this.showInfoDialog = false;
-    },
-    async fetchUserProfile() {
-      try {
-        const userId = this.$route.query.application_id;
-        const tenantId = 1;
-        
-        const response = await fetch(
-          `${import.meta.env.VITE_API_URL}/api/system/wechat/user/profile/get?user_id=${userId}&tenant_id=${tenantId}`
-        );
-
-        if (!response.ok) {
-          throw new Error('获取用户信息失败');
-        }
-
-        const data = await response.json();
-        
-        if (data.success && data.profile) {
-          const basicInfo = data.profile.basic_info;
-          const education = data.profile.educations[0] || {};
-          const workExp = data.profile.work_experiences[0] || {};
-
-          // 更新用户详细信息
-          this.userProfile = {
-            name: basicInfo.name || this.userInfo.name,
-            gender: basicInfo.gender === 1 ? '男' : '女',
-            age: basicInfo.age || '',
-            education: this.getEducationLevel(education.degree),
-            phone: basicInfo.phone || this.userInfo.phone,
-            email: '',  // API中没有邮箱字段
-            address: basicInfo.current_address || '',
-            position: workExp.position || '',
-            experience: this.formatWorkExperience(workExp),
-            expectedSalary: basicInfo.expected_salary ? `${basicInfo.expected_salary}元/月` : '',
-            idCard: basicInfo.id_card || '',
-            height: basicInfo.height ? `${basicInfo.height}cm` : '',
-            weight: basicInfo.weight ? `${basicInfo.weight}kg` : '',
-            nativePlace: basicInfo.native_place || '',
-            politicalStatus: basicInfo.political_status || '',
-            ethnicity: basicInfo.ethnicity || '',
-            maritalStatus: this.getMaritalStatus(basicInfo.marital_status),
-            specialties: basicInfo.specialties || '',
-            schoolName: education.school_name || '',
-            major: education.major || '',
-            graduationDate: education.end_date || '',
-            currentCompany: workExp.company_name || '',
-            department: workExp.department || '',
-          }
-        }
-      } catch (error) {
-        console.error('获取用户详细信息失败:', error);
-      }
-    },
-    getEducationLevel(degree) {
-      const degrees = {
-        1: '专科',
-        2: '本科',
-        3: '硕士',
-        4: '博士'
-      };
-      return degrees[degree] || '未知';
-    },
-    formatWorkExperience(workExp) {
-      if (!workExp.start_date) return '无工作经验';
-      const start = new Date(workExp.start_date);
-      const end = workExp.end_date ? new Date(workExp.end_date) : new Date();
-      const years = Math.floor((end - start) / (365 * 24 * 60 * 60 * 1000));
-      return `${years}年`;
-    },
-    getMaritalStatus(status) {
-      return status === 0 ? '未婚' : '已婚';
-    },
-    getPositionLabel(hand, index) {
-      // 根据照片的类型来确定位置标签
-      const photoType = this.getPhotoType(hand, index);
-      const positions = {
-        left: {
-          palm: '①左手手心',
-          back: '②左手手背',
-          fist: '③左手腕拳'
-        },
-        right: {
-          palm: '④右手手心',
-          back: '⑤右手手背',
-          fist: '⑥右手腕拳'
-        }
-      };
-      return positions[hand]?.[photoType] || `${hand === 'left' ? '左' : '右'}手部位${index + 1}`;
-    },
-    getDefaultDescription(hand, index) {
-      // 根据照片的类型来确定描述
-      const photoType = this.getPhotoType(hand, index);
-      const descriptions = {
-        left: {
-          palm: '手掌纹理清晰,活动自然',
-          back: '手背皮肤正常,关节灵活',
-          fist: '腕关节活动度正常'
-        },
-        right: {
-          palm: '手掌纹理清晰,活动自然',
-          back: '手背皮肤正常,关节灵活',
-          fist: '腕关节活动度正常'
-        }
-      };
-      return descriptions[hand]?.[photoType] || '手部动作正常';
-    },
-    // 新增方法:根据照片信息确定类型
-    getPhotoType(hand, index) {
-      const photo = this.handVerificationData[hand]?.[index];
-      if (!photo) return null;
-
-      // 从照片描述中提取类型
-      const description = photo.description?.toLowerCase() || '';
-      
-      if (description.includes('palm') || description.includes('手心')) {
-        return 'palm';
-      } else if (description.includes('back') || description.includes('手背')) {
-        return 'back';
-      } else if (description.includes('fist') || description.includes('腕拳')) {
-        return 'fist';
-      }
-      
-      // 如果无法从描述中确定类型,尝试从验证结果中获取
-      if (photo.part_verification?.part_type) {
-        return photo.part_verification.part_type;
-      }
-      
-      // 根据索引返回默认类型
-      const defaultTypes = ['palm', 'back', 'fist'];
-      return defaultTypes[index] || 'unknown';
-    },
-    // 添加关闭弹窗的方法
-    handleBodyVerificationClose() {
-      this.showBodyVerification = false
-    },
-    // 添加新方法:获取能力名称的中文释义
-    getCompetencyName(key) {
-      const competencyMap = {
-        'communication_cooperation': '团队协作能力',
-        'learning_ability': '应变能力',
-        'service_awareness': '服务意识',
-        'customer_insight': '客户需求洞察力',
-        'position_matching': '岗位匹配度',
-        'communication_expression': '沟通表达能力',
-        // 可以根据需要添加更多映射
-      }
-      return competencyMap[key]
-    },
-    // 修改更新雷达图数据的方法
-    updateRadarData(competencyScores) {
-      if (!competencyScores) return
-      
-      const processedData = Object.entries(competencyScores)
-        .map(([key, item]) => ({
-          name: this.getCompetencyName(key) || item.tag_name || key,
-          score: Math.round(item.weighted_score || 0)
-        }))
-        .sort((a, b) => b.score - a.score)
-        .slice(0, 6)
-
-      this.radarData = {
-        indicator: processedData.map(item => ({
-          name: item.name,
-          max: 100
-        })),
-        values: processedData.map(item => item.score)
-      }
-    },
-    // 添加检查图表实例的方法
-    async ensureChartRendered() {
-      let attempts = 0
-      const maxAttempts = 5
-      
-      while (!this.$refs.radarChart?.chart && attempts < maxAttempts) {
-        await new Promise(resolve => setTimeout(resolve, 200))
-        attempts++
-      }
-      
-      return this.$refs.radarChart?.chart
-    },
-    getFacePhoto(photos) {
-      if (!photos || !Array.isArray(photos)) {
-        return null;
-      }
-
-      // 定义面部照片的中文和英文关键词
-      const faceKeywords = ['face', 'facial', '面部', '人脸', '头像'];
-      
-      // 查找包含面部关键词的照片
-      const facePhoto = photos.find(photo => {
-        const description = (photo?.description || '').toLowerCase();
-        return faceKeywords.some(keyword => description.includes(keyword));
-      });
-
-      // 如果找到面部照片则返回URL,否则返回数组第一张照片的URL
-      return facePhoto?.photo_url || photos[0]?.photo_url;
-    },
-    // 获取题型名称
-    getQuestionTypeName(questionForm) {
-      const typeMap = {
-        0: '视频问答题',
-        1: '单选题',
-        2: '多选题',
-        3: '图片选择题',
-        4: '问卷题',
-        5: '开放问答题'
-      }
-      return typeMap[questionForm] || '未知题型'
-    },
-    // 处理问答记录数据
-    processInterviewRecords(records) {
-      if (!records || !Array.isArray(records)) return []
-      
-      // 过滤并处理记录,只保留 question_form 为 0 或 5 的记录
-      const mainQuestions = records
-        .filter(q => (q.question_form === 0 || q.question_form === 5) && (q.video_answer || q.answer_data))
-        .map(q => ({
-          question: q.question_text || '未提供问题',
-          answer: q.video_answer?.transcript || q.answer_data?.text_answer || '',
-          analysis: q.video_answer?.ai_analysis?.comment || '',
-          score: q.video_answer?.ai_score ? `${q.video_answer.ai_score}` : '',
-          question_form: q.question_form,
-          question_id: q.question_id,
-          parent_question_id: q.parent_question_id,
-          follow_up_questions: [],
-          video_url: q.video_answer.video_url
-        }))
-
-      // 处理追问题,仍然只处理 question_form 为 5 的追问
-      records
-        .filter(q => q.question_form === 5 && q.parent_question_id)
-        .forEach(followUp => {
-          const parentQuestion = mainQuestions.find(
-            q => q.question_id === followUp.parent_question_id
-          )
-          if (parentQuestion) {
-            parentQuestion.follow_up_questions.push({
-              question: followUp.question_text || '未提供问题',
-              answer: followUp.video_answer?.transcript || '',
-              analysis: followUp.video_answer?.ai_analysis?.comment || '',
-              score: followUp.video_answer?.ai_score ? `${followUp.video_answer.ai_score}分` : '',
-              question_form: followUp.question_form,
-              question_id: followUp.question_id,
-              parent_question_id: followUp.parent_question_id,
-              follow_up_questions: [],
-              video_url: followUp.video_answer.video_url
-            })
-          }
-        })
-
-      return mainQuestions
-    },
-    getProgressColor(score) {
-      if (score >= 80) {
-        return [{
-          color: '#00b050', // 深绿色起点
-          percentage: 0
-        }, {
-          color: '#6aff05', // 浅绿色终点
-          percentage: 100
-        }]
-      } else {
-        return [{
-          color: '#f7681d', // 橙色起点
-          percentage: 0
-        }, {
-          color: '#fdd1bb', // 浅橙色终点
-          percentage: 100
-        }]
-      }
-    },
-    // 添加格式化分析文本的方法
-    formatAnalysis(analysis) {
-      if (!analysis) return '';
-      // 移除开头的换行符,并确保每个评价项之间只有一个换行
-      return analysis
-        .replace(/^\s+/, '') // 移除开头的空白
-        .replace(/【/g, '\n【') // 在评价项前添加换行
-        .replace(/^\n/, ''); // 移除第一个换行符
-    },
-    extractAssessmentPoints(analysis) {
-      if (!analysis) return [];
-      // 改进的正则表达式,更精确地匹配考核点格式
-      const pointsRegex = /【([^】]+)】评价:([^【]+)(?=【|$)/g;
-      const points = [];
-      let match;
-      
-      while ((match = pointsRegex.exec(analysis)) !== null) {
-        const name = match[1].trim();
-        const fullDetail = match[2].trim();
-        
-        // 提取评分等级,保持原有逻辑
-        let score = '';
-        if (fullDetail.includes('优秀')) score = '优秀';
-        else if (fullDetail.includes('良好')) score = '良好';
-        else if (fullDetail.includes('一般')) score = '一般';
-        else if (fullDetail.includes('较差')) score = '较差';
-        else score = '一般';
-        
-        // 去掉评分等级,保留详细描述
-        const detail = fullDetail.replace(/(优秀|良好|一般|较差),/, '').trim();
-        
-        points.push({
-          name,
-          score,
-          detail
-        });
-      }
-      
-      return points;
-    },
-    getScoreClass(score) {
-      switch (score) {
-        case '优秀':
-          return 'score-excellent';
-        case '良好':
-          return 'score-good';
-        case '一般':
-          return 'score-average';
-        case '较差':
-          return 'score-poor';
-        default:
-          return 'score-average';
-      }
-    },
-    // 添加新的计算属性
-    getPsychologyScoreClass() {
-      const score = this.psychologyData.score;
-      if (score <= 20) return 'low-risk';
-      if (score <= 55) return 'medium-risk';
-      return 'high-risk';
-    },
-    
-    getPsychologyRiskLevel() {
-      const score = this.psychologyData.score;
-      if (score <= 20) return '低风险';
-      if (score <= 55) return '中风险';
-      return '高风险';
-    },
-    async exportPDF() {
-      try {
-        this.exporting = true
-        
-        // 获取要导出的内容区域
-        const element = document.querySelector('.report-container')
-        if (!element) {
-          throw new Error('未找到要导出的内容')
-        }
-
-        // 设置导出选项
-        const options = {
-          allowTaint: false,
-          useCORS: true,
-          scale: 2,
-          logging: false,
-          backgroundColor: '#ffffff',
-          height: element.scrollHeight,
-          windowHeight: element.scrollHeight,
-          imageTimeout: 0,
-          onclone: (doc) => {
-            // 添加打印样式
-            const style = doc.createElement('style')
-            style.innerHTML = `
-              @media print {
-                body {
-                  -webkit-print-color-adjust: exact !important;
-                  print-color-adjust: exact !important;
-                }
-                .report-container {
-                  width: 100% !important;
-                  margin: 0 !important;
-                  padding: 20px !important;
-                  display: block !important;
-                }
-                .report-header {
-                  display: flex !important;
-                  justify-content: space-between !important;
-                  align-items: flex-end !important;
-                  height: 80px !important;
-                  margin-bottom: 30px !important;
-                }
-                 :v-deep(.status-tag),
-                .status-tag[style*="background"],
-                span[class="status-tag"] {
-                  background: none !important;
-                  background-color: transparent !important;
-                  -webkit-print-color-adjust: exact !important;
-                  print-color-adjust: exact !important;
-                  color: #000000 !important;
-                  border: 1px solid #000000 !important;
-                }
-                /* 确保内联样式不会覆盖我们的设置 */
-                span[style*="background"].status-tag {
-                  background: none !important;
-                  background-color: transparent !important;
-                }
-                .header {
-                  display: flex !important;
-                  gap: 20px !important;
-                }
-                .header-right {
-                  display: flex !important;
-                  position: relative !important;
-                }
-                .nav-tabs {
-                  display: flex !important;
-                  gap: 32px !important;
-                  justify-content: flex-start !important;
-                  align-items: center !important;
-                }
-                .evaluation-result {
-                  display: flex !important;
-                  justify-content: space-between !important;
-                  align-items: flex-start !important;
-                }
-                .score-section {
-                  display: flex !important;
-                  flex-direction: column !important;
-                  gap: 24px !important;
-                }
-                .score-section-left {
-                  display: flex !important;
-                  justify-content: flex-start !important;
-                  align-items: center !important;
-                  gap: 16px !important;
-                }
-                .detection-results {
-                  display: flex !important;
-                  align-items: center !important;
-                  gap: 8px !important;
-                }
-                .ability-quality {
-                  display: block !important;
-                }
-                .quality-details {
-                  display: block !important;
-                }
-                .quality-item {
-                  display: flex !important;
-                  gap: 24px !important;
-                }
-                .psychology_box {
-                  display: flex !important;
-                  flex-direction: row !important;
-                  gap: 16px !important;
-                  justify-content: space-between !important;
-                  align-items: center !important;
-                }
-                .detection-section-content {
-                  display: flex !important;
-                  justify-content: space-between !important;
-                  gap: 16px !important;
-                }
-                .fixed-btn {
-                  display: none !important;
-                }
-                
-                /* 确保所有flex容器在打印时保持flex布局 */
-                [class*="flex"] {
-                  display: flex !important;
-                }
-                
-                /* 确保图表容器保持正确的尺寸 */
-                .radar-chart {
-                  width: 500px !important;
-                  height: 350px !important;
-                }
-                
-                /* 确保进度条样式正确 */
-                .el-progress-bar__outer {
-                  height: 8px !important;
-                  background-color: #EBEEF5 !important;
-                }
-                
-                .el-progress-bar__inner {
-                  transition: none !important;
-                }
-                :v-deep(.el-progress__text){
-                  display: none !important;
-                }
-                
-                /* 确保文字样式正确 
-                .text-[14px] {
-                  font-size: 14px !important;
-                  line-height: 16px !important;
-                }*/
-                
-                /* 确保边距和间距正确 */
-                .mt-6 { margin-top: 1.5rem !important; }
-                .mb-4 { margin-bottom: 1rem !important; }
-                .p-4 { padding: 1rem !important; }
-                
-                /* 确保圆角样式正确 */
-                .rounded-lg { border-radius: 0.5rem !important; }
-                
-                /* 确保背景色正确 */
-                .bg-white { background-color: #ffffff !important; }
-                .bg-gray-50 { background-color: #F9FAFB !important; }
-              }
-            `
-            doc.head.appendChild(style)
-            const allElements = doc.querySelectorAll('*')
-            allElements.forEach(el => {
-              el.style.visibility = 'visible'
-              el.style.opacity = '1'
-            })
-          }
-        }
-
-        // 生成画布
-        const canvas = await html2canvas(element, options)
-
-        // 计算PDF尺寸(使用A4比例)
-        const imgWidth = 595.28 
-        const imgHeight = 841.89
-        const pageHeight = imgHeight
-        const imgData = canvas.toDataURL('image/jpeg', 1.0)
-
-        // 创建PDF
-        const pdf = new jsPDF({
-          orientation: 'p',
-          unit: 'pt',
-          format: 'a4',
-          compress: true
-        })
-
-        // 计算页数
-        const pageCount = Math.ceil(canvas.height * imgWidth / canvas.width / pageHeight)
-
-        // 逐页添加内容
-        for (let i = 0; i < pageCount; i++) {
-          if (i > 0) {
-            pdf.addPage()
-          }
-          
-          const position = -i * pageHeight
-          
-          pdf.addImage(
-            imgData,
-            'JPEG',
-            0,
-            position,
-            imgWidth,
-            canvas.height * imgWidth / canvas.width,
-            '',
-            'FAST'
-          )
-        }
-
-        // 保存文件
-        pdf.save(`AI面试结果检验报告_${this.userInfo.name || '未知'}_${this.reportId}.pdf`)
-
-      } catch (error) {
-        console.error('导出PDF失败:', error)
-        ElMessage.error('导出PDF失败,请稍后重试')
-      } finally {
-        this.exporting = false
-      }
-    },
-
-    // 添加辅助方法
-    getFooterElement(remainingHeight, fillingHeight = 0) {
-      const newNode = document.createElement('div')
-      newNode.style.background = '#ffffff'
-      newNode.style.width = 'calc(100% + 8px)'
-      newNode.style.marginLeft = '-4px'
-      newNode.style.marginBottom = '0px'
-      newNode.classList.add('divRemove')
-      newNode.style.height = (remainingHeight + fillingHeight) + 'px'
-      return newNode
-    },
-
-    isSplit(nodes, index, pageHeight) {
-      if (!nodes[index + 1]) return false
-      
-      if (nodes[index].localName !== 'tr') {
-        this.noTableHeight += nodes[index].clientHeight
-      }
-
-      if (nodes[index].localName !== 'tr') {
-        return nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight && 
-               nodes[index + 1] && 
-               nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight
-      } else {
-        return nodes[index].offsetTop + nodes[index].offsetHeight + this.noTableHeight < pageHeight && 
-               nodes[index + 1] && 
-               nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight + this.noTableHeight > pageHeight
-      }
-    },
-    getBarChartOption(item) {
-      const color = item.weighted_score >= 80 ? 
-        [{
-          offset: 0,
-          color: '#00b050'
-        }, {
-          offset: 1,
-          color: '#6aff05'
-        }] : 
-        [{
-          offset: 0,
-          color: '#f7681d'
-        }, {
-          offset: 1,
-          color: '#fdd1bb'
-        }];
-
-      return {
-        grid: {
-          left: 0,
-          right: 40,
-          top: 0,
-          bottom: 0,
-          containLabel: false
-        },
-        xAxis: {
-          type: 'value',
-          show: false,
-          max: 100
-        },
-        yAxis: {
-          type: 'category',
-          show: false
-        },
-        series: [{
-          type: 'bar',
-          data: [item.weighted_score],
-          barWidth: 8,
-          label: {
-            show: true,
-            position: 'right',
-            formatter: '{c}分',
-            fontSize: 12,
-            color: '#808080'
-          },
-          itemStyle: {
-            borderRadius: [0, 4, 4, 0],
-            color: {
-              type: 'linear',
-              x: 0,
-              y: 0,
-              x2: 1,
-              y2: 0,
-              colorStops: color
-            }
-          }
-        }]
-      };
-    },
-    formatTextWithBrackets(text) {
-      if (!text) return '';
-      return text.replace(/【(.*?)】/g, '<span  style="color:#81b337; font-weight: 500;">【$1】</span>');
-    },
-    formatTextWithBrackets2(text) {
-      if (!text) return '';
-      return text.replace(/【(.*?)】/g, '<span  style="color:#fb752f; font-weight: 500;">【$1】</span>');
-    },
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.outer-container {
-  position: relative;
-  width: 100%;
-  height: 100vh;
-  overflow-y: auto;
-  background: #fff;
-}
-
-.report-container {
-  /* padding: 30px; */
-  margin: 0 auto 30px;
-  width: 1200px;
-  background: #fff;
-
-  .header {
-    display: flex;
-    gap: 20px;
-    background: #fff;
-    border-radius: 8px;
-
-    .header-left {
-      .avatar {
-        width: 150px;
-        height: 150px;
-        border-radius: 50%;
-        object-fit: cover;
-      }
-    }
-
-    .header-right {
-      flex: 1;
-      display: flex;
-      position: relative;
-      background: #f8f8f8;
-      padding: 20px;
-      border-radius: 8px;
-
-      .left-section,
-      .right-section {
-        flex: 1;
-
-        .info-row {
-          margin-bottom: 12px;
-          display: flex;
-          align-items: center;
-          
-          &:last-child {
-            margin-bottom: 0;
-          }
-
-          .label {
-            width: 80px;
-            color: #666;
-            font-size: 14px;
-          }
-
-          .value {
-            color: #333;
-            font-size: 14px;
-
-            &.verified {
-              color: #67C23A;
-            }
-          }
-        }
-      }
-
-      .stamp {
-        position: absolute;
-        right: 20px;
-        top: 50%;
-        transform: translateY(-50%);
-
-        img {
-          width: 150px;
-          height: 150px;
-          opacity: 0.8;
-        }
-      }
-    }
-  }
-
-  .evaluation-result {
-    border-radius: 8px;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-top: 20px;
-    background: #f8f8f8;
-    .score-section {
-      width: 65%;
-      display: flex;
-      flex-direction: column;
-      gap: 24px;
-      padding: 24px;
-      background: #f8f8f8;
-      border-radius: 4px;
-
-      .score-section-left {
-        flex: 1;
-        display: flex;
-        justify-content: flex-start;
-        align-items: center;
-        gap: 16px;
-        white-space: nowrap; // 添加此行防止换行
-        
-        .score-title {
-          font-size: 16px;
-          font-weight: bold;
-          color: #333;
-          min-width: fit-content; // 添加此行确保标题不会被压缩
-        }
-
-        .score-value {
-          display: flex;
-          align-items: baseline;
-          min-width: fit-content; // 添加此行确保分数部分不会被压缩
-          
-          .number {
-            font-size: 14px;
-            font-weight: bold;
-            color: #F56C6C;
-          }
-
-          .unit {
-            margin: 0 8px;
-            font-size: 14px;
-            color: #333;
-          }
-
-          .status-tag {
-            //padding: 2px 8px;
-            font-size: 12px;
-            color: #fff;
-            background: #F56C6C;  // 默认背景色
-            border-radius: 2px;
-
-            // 添加打印时的特殊样式
-            @media print {
-              background: none !important;  // 移除打印时的背景色
-              color: #F56C6C !important;   // 使用对应的文字颜色
-              border: 1px solid #F56C6C;   // 添加边框来区分
-
-              &[style*="background:#67C23A"] {
-                background: none !important;
-                color: #67C23A !important;
-                border: 1px solid #67C23A;
-              }
-            }
-          }
-        }
-
-        .detection-results {
-          display: flex;
-          align-items: center;
-          gap: 8px;
-          font-size: 14px;
-          color: #333;
-          min-width: fit-content; // 添加此行确保检测结果不会被压缩
-
-          .detection-item {
-            display: flex;
-            align-items: center;
-            gap: 4px;
-            white-space: nowrap; // 添加此行防止换行
-            
-            .status-normal {
-              color: #67C23A;
-            }
-
-            .status-warning {
-              color: #F56C6C;
-            }
-          }
-
-          .separator {
-            color: #DCDFE6;
-          }
-        }
-      }
-
-      .fail-reason {
-        flex: 1;
-        text-align: left;
-        padding: 16px;
-        border-radius: 4px;
-
-        .reason-header {
-          display: flex;
-          align-items: center;
-          gap: 8px;
-          margin-bottom: 12px;
-          color: #F56C6C;
-          font-size: 14px;
-
-          i {
-            font-size: 16px;
-          }
-        }
-
-        .reason-content {
-          margin-top: 16px;
-          font-size: 14px;
-
-          .label {
-            color: #333;
-            font-weight: 500;
-          }
-
-          .status-normal {
-            color: #67C23A;
-          }
-
-          .status-warning {
-            color: #F56C6C;
-          }
-
-          .failed-questions {
-            margin-top: 12px;
-            padding: 12px;
-            background-color: #FEF0F0;
-            border-radius: 4px;
-            border-left: 4px solid #F56C6C;
-
-            .failed-title {
-              color: #F56C6C;
-              font-weight: 500;
-              margin-bottom: 8px;
-            }
-
-            .failed-list {
-            text-align: left;
-              list-style-type: disc;
-              padding-left: 20px;
-              color: #606266;
-
-              li {
-                margin-bottom: 4px;
-                
-                &:last-child {
-                  margin-bottom: 0;
-                }
-              }
-            }
-          }
-        }
-      }
-    }
-
-    .radar-chart {
-      width: 500px;
-      height: 350px;
-     /*  background: #fff; */
-      border-radius: 8px;
-      /* border: 1px solid #EBEEF5; */
-      padding: 20px;
-     /*  margin-right: 20px; */
-      
-      .radar {
-        width: 100%;
-        height: 100%;
-      }
-
-      :deep(.echarts-tooltip) {
-        display: none; // 隐藏tooltip
-      }
-    }
-  }
-
-  .ability-quality {
-    margin-top: 30px;
-    background: #f8f8f8;
-    padding: 20px;
-    border-radius: 8px;
-    
-    h3 {
-      font-size: 16px;
-      color: #333;
-      margin: 0;
-      padding-bottom: 16px;
- /*      border-bottom: 1px solid #eee; */
-
-      .score-text {
-        font-size: 14px;
-        color: #f56c6c;
-        font-weight: normal;
-        margin-left: 10px;
-      }
-    }
-
-    .quality-details {
-      margin-top: 20px;
-
-      .quality-item {
-        display: flex;
-      
-        gap: 24px;
-        
-        .quality-left, .quality-right {
-            .icon-box{
-                display: flex;
-                align-items: center;
-                .icon {
-                    width: 32px;
-                    height: 32px;
-                    border-radius: 4px;
-                    flex-shrink: 0;
-                    margin-right: 12px;
-                } 
-                h5{
-                    margin-bottom: 0;
-                }
-            }
-            .content{
-                p{
-                  .bracket-content {
-                    color: #409EFF;
-                    font-weight: 500;
-                  }
-                }
-            }
-            
-           flex: 1;
-                display: flex;
-                align-items: center;
-                gap: 12px;
-          
-
-          .advantage-icon {
-            /* background: #67C23A; */
-          }
-
-          .suggestion-icon {
-            /* background: #E6A23C; */
-          }
-
-          .content {
-            flex: 1;
-            
-            h4 {
-              margin: 0 0 12px 0;
-              font-size: 14px;
-              color: #333;
-            }
-
-            p {
-              margin: 0;
-              font-size: 12px;
-              line-height: 1.6;
-              color: #666;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  .detailed-evaluation {
-    padding: 20px;
-    border-radius: 8px;
-    margin-top: 30px;
-    background: #f8f8f8;
-    position: relative;
-    .line-info {
-      color: #9b9b9b;
-      .line-info-left {
-        position: absolute;
-        left: 36%;
-        top: 0;
-      }
-      .line-info-center {
-        position: absolute;
-        left: 63%;
-        top: 0;
-      }
-      .line-info-right {  
-        position: absolute;
-        left: 72%;
-        top: 0;
-      }
-    }
-    .line-chart {
-      .line-chart-left {
-        width: 1px;
-        height: 91%;
-        border-left: 1px solid #e1e1e1;
-        position: absolute;
-        left: 12%;
-    margin-left: 16px;
-      }
-        .line_left {
-          width: 1px;
-          height: 90%;
-          border-left: 1px dashed #e1e1e1;
-          position: absolute;
-          left: 59%;
-          margin-left: 7px;
-          /* top: 0; */
-
-        }
-        .line_center {
-          width: 1px;
-          height: 90%;
-          border-left: 1px dashed #e1e1e1;
-          position: absolute;
-          left: 69%;
-          margin-left: -7px;
-        }
-        .line_right {
-          width: 1px;
-          height: 90%;
-          border-left: 1px dashed #e1e1e1;
-          position: absolute;
-          left: 76%;
-          margin-left: -6px;
-        }
-      }
-
-    .ability-scores {
-      .ability-item {
-        display: flex;
-        align-items: center;
-        margin-bottom: 20px;
-        position: relative;
-        min-height: 30px;
-
-        .ability-name {
-          min-width: 120px;
-          margin-right: 20px;
-          font-size: 14px;
-          color: #333;
-          white-space: nowrap;
-          display: flex;
-          align-items: center;
-          height: 30px; // 固定高度以确保垂直居中
-        }
-
-        .bar-chart-container {
-          flex: 1;
-          margin-right: 45px;
-          display: flex;
-          align-items: center;
-          height: 30px; // 固定高度以确保垂直居中
-
-          :deep(.echarts) {
-            height: 100% !important;
-          }
-        }
-      }
-    }
-  }
-
-  .detection-section,
-  .psychology-section,
-  .body-detection-section {
-    .detection-section-header{
-      display: flex;
-    align-items: flex-start;
-    justify-content: space-between;
-    }
-    margin-top: 20px;
-    background: #f8f8f8;
-    padding: 20px;
-    border-radius: 8px;
-
-    .section-title {
-      font-size: 16px;
-      font-weight: bold;
-      color: #333;
-      margin-bottom: 16px;
-    }
-
-    .status-tag {
-      display: inline-block;
-      padding: 2px 8px;
-      border-radius: 2px;
-      font-size: 12px;
-      
-      &.normal {
-        font-size: 14px;
-        /* background: #67C23A; */
-        color: #67C23A;
-      }
-    }
-  }
-  .detection-section-content{
-   display: flex;
-   justify-content: space-between;
-   gap: 16px;
-  .detection-items {
-    margin: 16px 0;
-
-    .item {
-      margin-bottom: 8px;
-      font-size: 14px;
-
-      .status {
-        &.normal {
-          color: #67C23A;
-        }
-      }
-
-      .separator {
-        margin: 0 8px;
-        color: #DCDFE6;
-      }
-
-      .desc {
-        color: #606266;
-      }
-    }
-  }
-
-  .risk-standards {
-    margin-top: 16px;
-    
-    .standards-title {
-      font-size: 14px;
-      color: #333;
-      margin-bottom: 12px;
-    }
-
-    .standard-item {
-      margin-bottom: 8px;
-      font-size: 14px;
-      
-      .level {
-        color: #333;
-      }
-      
-      .desc {
-        color: #606266;
-      }
-    }
-  }
-
-  }
-  .psychology-section {
-    .psychology_box{
-      display: flex;
-    flex-direction: row;
-    gap: 16px;
-    justify-content: space-between;
-    align-items: center;
-    }
-    .duv-score {
-      position: relative;
-      
-      .score {
-        font-size: 48px;
-        font-weight: bold;
-        
-        &.low-risk {
-          color: #67C23A;
-        }
-        
-        &.medium-risk {
-          color: #E6A23C;
-        }
-        
-        &.high-risk {
-          color: #F56C6C;
-        }
-      }
-
-      .risk-level {
-    margin-top: 0 !important;
-        border-radius: 12px;
-        font-size: 12px;
-        font-weight: normal;
-        
-        &.low-risk {
-          
-          color: #67C23A;
-        }
-        
-        &.medium-risk {
-          
-          color: #E6A23C;
-        }
-        
-        &.high-risk {
-          
-          color: #F56C6C;
-        }
-      }
-    }
-
-    .psychology-desc {
-      font-size: 14px;
-      color: #606266;
-      line-height: 1.6;
-    }
-
-    .psychology-recommendation {
-      margin-top: 16px;
-      font-size: 14px;
-      
-      .label {
-        color: #333;
-        font-weight: 500;
-      }
-      
-      .value {
-        color: #409EFF;
-      }
-    }
-  }
-
-  .body-detection-section {
-    .hand-verification {
-      .grid {
-        @apply gap-4;
-      }
-      
-      .aspect-square {
-        @apply relative;
-        padding-top: 100%;
-        
-        img {
-          @apply absolute inset-0 w-full h-full object-cover;
-        }
-      }
-    }
-
-    .detection-results {
-      .detection-item {
-        @apply p-4 bg-gray-50 rounded-lg;
-        
-        .position {
-          @apply font-medium text-gray-700 mr-2;
-        }
-        
-        .status {
-          font-weight: 500;
-          
-          &.status-normal {
-            color: #67C23A;  // 绿色
-          }
-          
-          &.status-error {
-            color: #F56C6C;  // 红色
-          }
-        }
-        
-        .description {
-          @apply block mt-1;
-        }
-      }
-    }
-  }
-
-  .nav-tabs {
-    position: sticky;
-    top: 0;
-    display: flex;
-    gap: 32px;
-    margin: 20px 0;
-    padding: 0 20px;
-    border: none;
-    margin-bottom: 2px;
-   /*  border-bottom: 1px solid #EBEEF5; */
-    background: #fff;
-    z-index: 100;
-    width: 100%;
-    box-sizing: border-box;
-    transition: all 0.3s ease;
-
-    &.fixed {
-      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
-    }
-
-    .tab-item {
-      position: relative;
-      padding: 5px 22px;
-      font-size: 14px;
-      color: #606266;
-      cursor: pointer;
-      transition: all 0.3s;
-      border-radius: 15px;
-
-      &:hover {
-        color: #409EFF;
-        background: #f2f3f8;
-      }
-
-      &.active {
-        color: #409EFF;
-        font-weight: 500;
-        background: #f2f3f8;
-
-        &::after {
-          display: none;
-        }
-      }
-
-      &.link-tab {
-        display: flex;
-        align-items: center;
-        gap: 4px;
-        
-        i {
-          font-size: 12px;
-        }
-
-        &:hover {
-          color: #409EFF;
-          text-decoration: underline;
-        }
-      }
-
-      &.info-tab {
-        display: flex;
-        align-items: center;
-        gap: 4px;
-        
-        i {
-          font-size: 14px;
-        }
-
-        &:hover {
-          color: #409EFF;
-        }
-      }
-    }
-  }
-
-  // 修改占位元素样式
-  .nav-placeholder {
-    margin: 20px 0;
-    transition: height 0.3s ease;
-  }
-}
-
-.loading-state,
-.error-state {
-  text-align: center;
-  padding: 20px;
-  color: #666;
-}
-
-.error-state {
-  color: #F56C6C;
-}
-
-.redline-section {
-  margin-top: 16px;
-  padding: 16px;
-  background-color: #FFF6F6;
-  border-radius: 4px;
-
-  .redline-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 12px;
-
-    .redline-title {
-      color: #F56C6C;
-      font-size: 14px;
-      font-weight: 500;
-    }
-
-    .status-normal {
-      color: #67C23A;
-      font-size: 14px;
-    }
-
-    .status-warning {
-      color: #F56C6C;
-      font-size: 14px;
-    }
-  }
-
-  .failed-questions {
-    text-align: left !important;
-    .failed-title {
-    text-align: left;
-      color: #606266;
-      font-size: 14px;
-      margin-bottom: 8px;
-    }
-
-    .failed-list {
-    text-align: left;
-      padding-left: 8px;
-      
-      .failed-item {
-        display: flex;
-        align-items: flex-start;
-        margin-bottom: 8px;
-        color: #606266;
-        font-size: 14px;
-        line-height: 1.5;
- text-align: left;
-        &:last-child {
-          margin-bottom: 0;
-        }
-
-        .bullet {
-          color: #F56C6C;
-          margin-right: 8px;
-          font-size: 16px;
-          line-height: 1;
-        }
-
-        .question-text {
-          flex: 1;
-        }
-      }
-    }
-  }
-}
-
-.hand-verification {
-  .grid {
-    @apply gap-4;
-  }
-  
-  .aspect-square {
-    @apply relative;
-    padding-top: 100%;
-    
-    img {
-      @apply absolute inset-0 w-full h-full object-cover;
-    }
-  }
-}
-
-.detection-results {
-  .detection-item {
-    @apply p-4 rounded-lg;
-    
-    .position {
-      @apply font-medium text-gray-700 mr-2;
-    }
-    
-    .status {
-      font-weight: 500;
-      
-      &.status-normal {
-        color: #67C23A;  // 绿色
-      }
-      
-      &.status-error {
-        color: #F56C6C;  // 红色
-      }
-    }
-    
-    .description {
-      @apply block mt-1;
-    }
-  }
-}
-
-// 添加一些额外的样式来处理图片加载失败的情况
-.aspect-square {
-  background: #f5f5f5;
-  
-  img {
-    &[src="/@/assets/default-image.png"] {
-      object-fit: contain;
-      padding: 20%;
-    }
-  }
-}
-
-// 添加顶部标识栏样式
-.report-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-end;
-  height: 80px;
-  padding: 5px 24px;
-  background: url("/@/assets/report.png") no-repeat center center;
-  background-size: 100% 100%;
-  color: #fff;
-  position: relative;
-  width: 1200px;
-    margin: 0 auto;
-    margin-bottom: 30px;
-    border-radius: 5px;
-  .header-left {
-    position: absolute;
-    left: 0;
-    bottom: 0;
-    display: flex;
-    color: #000;
-    padding: 5px 40px;
-    background-color: #fff;
-    align-items: center;
-    font-size: 16px;
-    font-weight: 500;
-    border-top-right-radius: 60px;
-    .cc-icon {
-      width: 21px;
-      height: 12px;
-      margin-right: 8px;
-    }
-  }
-
-  .header-right {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    width: 100%;
-    gap: 24px;
-    font-size: 14px;
-    opacity: 0.8;
-    margin-left: 260px;
-    span {
-      white-space: nowrap;
-    }
-
-    .report-id {
-      opacity: 0.8;
-    }
-  }
-}
-
-// 添加媒体查询和响应式样式
-@media screen and (max-width: 768px) {
-  .outer-container {
-    padding: 0;
-  }
-
-  .report-header {
-    height: auto;
-    padding: 12px 16px;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 8px;
-
-    .header-right {
-      flex-direction: column;
-      gap: 4px;
-      width: 100%;
-    }
-  }
-
-  .report-container {
-    padding: 16px;
-
-    .header {
-      flex-direction: column;
-      gap: 16px;
-
-      .header-left {
-        .avatar {
-          width: 100px;
-          height: 100px;
-          margin: 0 auto;
-          display: block;
-        }
-      }
-
-      .header-right {
-        flex-direction: column;
-        padding: 16px;
-
-        .left-section,
-        .right-section {
-          width: 100%;
-        }
-
-        .stamp {
-          position: static;
-          transform: none;
-          margin-top: 16px;
-          text-align: center;
-
-          img {
-            width: 80px;
-            height: 80px;
-          }
-        }
-      }
-    }
-
-    .nav-tabs {
-      gap: 16px;
-      padding: 0 12px;
-      overflow-x: auto;
-      white-space: nowrap;
-      -webkit-overflow-scrolling: touch;
-
-      &::-webkit-scrollbar {
-        display: none;
-      }
-
-      .tab-item {
-        padding: 8px 0;
-        font-size: 13px;
-      }
-    }
-
-    .evaluation-result {
-      .score-section {
-        padding: 16px;
-
-        .score-section-left {
-          flex-direction: column;
-          align-items: flex-start;
-          gap: 12px;
-
-          .detection-results {
-            flex-direction: column;
-            gap: 8px;
-
-            .separator {
-              display: none;
-            }
-          }
-        }
-      }
-    }
-
-    .ability-quality {
-      .quality-details {
-        .quality-item {
-          flex-direction: column;
-          gap: 20px;
-        }
-      }
-    }
-
-    .detailed-evaluation {
-     
-      .ability-scores {
-        .ability-item {
-          flex-direction: column;
-          gap: 8px;
-          margin-bottom: 16px;
-
-          .ability-name {
-            width: 100%;
-          }
-        }
-      }
-    }
-
-    .detection-section-content {
-      flex-direction: column;
-      gap: 20px;
-    }
-
-    // 手势验证部分响应式处理
-    .hand-verification {
-      .grid {
-        grid-template-columns: 1fr !important;
-        gap: 16px;
-
-        h4 {
-          margin-top: 16px;
-        }
-      }
-    }
-  }
-
-  // 修改心理检测部分在移动端的样式
-  .psychology-section {
-    .duv-score {
-      flex-wrap: wrap;
-      
-      .score {
-        font-size: 20px;
-      }
-    }
-  }
-
-  // 修改红线题部分在移动端的样式
-  .redline-section {
-    padding: 12px;
-
-    .failed-questions {
-      .failed-list {
-        .failed-item {
-          margin-bottom: 12px;
-        }
-      }
-    }
-  }
-}
-
-// 添加暗黑模式支持1
-/* @media (prefers-color-scheme: dark) {
-  .report-container {
-    background: #1a1a1a;
-    color: #e5e5e5;
-
-    .header-right {
-      background: #2a2a2a;
-    }
-
-    .nav-tabs {
-      background: #1a1a1a;
-      border-bottom-color: #333;
-
-      .tab-item {
-        color: #e5e5e5;
-
-        &:hover,
-        &.active {
-          color: #409EFF;
-        }
-      }
-    }
-
-    .evaluation-result,
-    .ability-quality,
-    .detailed-evaluation,
-    .detection-section,
-    .psychology-section,
-    .body-detection-section {
-      background: #2a2a2a;
-    }
-
-    .status-normal {
-      color: #95d475;
-    }
-
-    .status-warning {
-      color: #ff7875;
-    }
-  }
-} */
-
-.info-dialog {
-  :deep(.el-dialog__header) {
-    padding: 20px;
-    border-bottom: 1px solid #EBEEF5;
-  }
-
-  :deep(.el-dialog__body) {
-    padding: 24px;
-  }
-
-  .info-content {
-    .info-section {
-      margin-bottom: 24px;
-
-      &:last-child {
-        margin-bottom: 0;
-      }
-
-      h4 {
-        font-size: 16px;
-        color: #333;
-        margin: 0 0 16px 0;
-        padding-left: 10px;
-        border-left: 3px solid #409EFF;
-      }
-
-      .info-grid {
-        display: grid;
-        grid-template-columns: repeat(2, 1fr);
-        gap: 16px;
-
-        .info-item {
-          display: flex;
-          align-items: center;
-
-          .label {
-            width: 80px;
-            color: #666;
-            font-size: 14px;
-          }
-
-          .value {
-            flex: 1;
-            color: #333;
-            font-size: 14px;
-          }
-        }
-      }
-    }
-  }
-}
-
-// 添加移动端适配
-@media screen and (max-width: 768px) {
-  .info-dialog {
-    :deep(.el-dialog) {
-      width: 90% !important;
-      margin: 20px auto !important;
-    }
-
-    .info-content {
-      .info-section {
-        .info-grid {
-          grid-template-columns: 1fr;
-        }
-      }
-    }
-  }
-}
-
-// 添加弹窗相关样式
-.body-verification-dialog {
-  :deep(.el-dialog__header) {
-    padding: 20px;
-    border-bottom: 1px solid #EBEEF5;
-  }
-
-  :deep(.el-dialog__body) {
-    padding: 24px;
-  }
-
-  // 在弹窗中保持原有的手势验证样式
-  .hand-verification {
-    .grid {
-      @apply gap-4;
-    }
-    
-    .aspect-square {
-      @apply relative;
-      padding-top: 100%;
-      
-      img {
-        @apply absolute inset-0 w-full h-full object-cover;
-      }
-    }
-  }
-
-  .detection-results {
-    .detection-item {
-      @apply p-4 bg-gray-50 rounded-lg;
-      
-      .position {
-        @apply font-medium text-gray-700 mr-2;
-      }
-      
-      .status {
-        @apply text-green-500;
-      }
-      
-      .description {
-        @apply block mt-1;
-      }
-    }
-  }
-}
-
-// 添加移动端适配
-@media screen and (max-width: 768px) {
-  .body-verification-dialog {
-    :deep(.el-dialog) {
-      width: 95% !important;
-      margin: 10px auto !important;
-    }
-
-    .hand-verification {
-      .grid {
-        grid-template-columns: 1fr !important;
-        gap: 16px;
-      }
-    }
-
-    .detection-results {
-      .grid {
-        grid-template-columns: 1fr !important;
-      }
-    }
-  }
-}
-
-// 确保在移动端也能正常显示
-@media screen and (max-width: 768px) {
-  .radar-chart {
-    width: 100%;
-    margin-right: 0;
-    margin-top: 20px;
-  }
-}
-
-// 修改心理测评部分的样式
-.psychology-section {
-  .psychology_box {
-    display: flex;
-    flex-direction: row;
-    gap: 16px;
-    justify-content: space-between;
-    align-items: center;
-
-    .psychology_body {
-      flex: 1;
-    }
-
-    .duv-score {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      width: 100px;
-      height: 100px;
-      background: #fff;
-      border-radius: 50%;
-      color: #81b337;
-      gap: 8px;
-      span {
-        &:first-child {
-          font-size: 14px;
-          color: #666;
-        }
-
-        &.score {
-         
-          font-size: 48px;
-          font-weight: bold;
-          color: #81b337;
-        }
-
-        &:last-child {
-           margin-top: 20px;
-          font-weight: bold;
-          font-size: 20px;
-          color: #81b337;
-        }
-      }
-    }
-  }
-
-  .psychology-desc {
-    font-size: 14px;
-    color: #606266;
-    line-height: 1.6;
-    margin-bottom: 16px;
-  }
-
-  .psychology-recommendation {
-    font-size: 14px;
-    
-    .label {
-      color: #333;
-      font-weight: 500;
-    }
-    
-    .value {
-      color: #409EFF;
-    }
-  }
-}
-
-// 添加移动端适配
-@media screen and (max-width: 768px) {
-  .psychology-section {
-    .psychology_box {
-      flex-direction: column;
-      
-      .duv-score {
-        width: 100%;
-        padding: 16px;
-      }
-    }
-  }
-}
-
-// 修改导出按钮样式
-.export-btn.fixed-btn {
-  position: fixed;
-  right: 40px;
-  bottom: 40px;
-  z-index: 999;
-  padding: 12px; // 修改为固定的padding值
-  width: 40px; // 添加固定宽度
-  height: 40px; // 添加固定高度
-  border-radius: 50%; // 改为圆形
-  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-  transition: all 0.3s ease;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  
-  // 修改图标样式
-  :deep(.el-icon) {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    font-size: 16px;
-  }
-  
-  &:hover {
-    transform: translateY(-2px);
-    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
-  }
-  
-  &:active {
-    transform: translateY(0);
-  }
-}
-
-// 移动端适配
-@media screen and (max-width: 768px) {
-  .export-btn.fixed-btn {
-    right: 20px;
-    bottom: 20px;
-    width: 36px; // 移动端稍微小一点
-    height: 36px;
-    padding: 8px;
-  }
-}
-
-// 修改header-right样式以适应新增按钮
-.report-header {
-  .header-right {
-    display: flex;
-    align-items: center;
-    gap: 16px;
-    
-    .export-btn {
-      margin-left: auto; // 将按钮推到最右侧
-    }
-  }
-}
-
-// 添加移动端适配
-@media screen and (max-width: 768px) {
-  .report-header {
-    .header-right {
-      flex-wrap: wrap;
-      
-      .export-btn {
-        margin-left: 0;
-        margin-top: 8px;
-        width: 100%;
-      }
-    }
-  }
-}
-
-// 添加问答记录相关样式
-.qa-section {
-  margin-top: 30px;
-  padding: 20px;
-  background: #f8f8f8;
-  border-radius: 8px;
-
-  .section-title {
-    font-size: 16px;
-    font-weight: bold;
-    color: #333;
-    margin-bottom: 20px;
-  }
-
-  .qa-content {
-    .qa-item {
-      margin-bottom: 24px;
-      
-      &:last-child {
-        margin-bottom: 0;
-      }
-    }
-
-    .question-block {
-      background: #fff;
-      border-radius: 8px;
-      padding: 16px;
-      margin-bottom: 16px;
-
-      .question-header {
-        display: flex;
-        gap: 8px;
-        margin-bottom: 12px;
-
-        .question-tag {
-          padding: 2px 8px;
-          background: #e6f7ff;
-          color: #1890ff;
-          border-radius: 4px;
-          font-size: 12px;
-        }
-
-        .question-type {
-          padding: 2px 8px;
-          background: #f5f5f5;
-          color: #666;
-          border-radius: 4px;
-          font-size: 12px;
-        }
-      }
-
-      h4 {
-        font-size: 14px;
-        color: #333;
-        margin-bottom: 12px;
-      }
-    }
-
-    .answer-content {
-      p {
-        color: #666;
-        font-size: 14px;
-        line-height: 1.6;
-        margin-bottom: 8px;
-      }
-
-      .score-info {
-        color: #1890ff;
-        font-size: 14px;
-
-        .analysis {
-          color: #666;
-          margin-top: 4px;
-        }
-      }
-    }
-
-    .follow-up-questions {
-      padding-left: 24px;
-      border-left: 2px solid #e8e8e8;
-
-      .follow-up-item {
-        background: #fff;
-        border-radius: 8px;
-        padding: 16px;
-        margin-bottom: 12px;
-
-        &:last-child {
-          margin-bottom: 0;
-        }
-
-        .follow-up-header {
-          margin-bottom: 12px;
-
-          .follow-up-tag {
-            padding: 2px 8px;
-            background: #f6f3ff;
-            color: #722ed1;
-            border-radius: 4px;
-            font-size: 12px;
-          }
-        }
-
-        h4 {
-          font-size: 14px;
-          color: #333;
-          margin-bottom: 12px;
-        }
-      }
-    }
-  }
-}
-.video-container {
-  position: relative;
-  width: 100%;
-  max-width: 126px;
-  /* aspect-ratio: 3/4; */
-  height: 150px;
-  margin: 0 auto;
-  background: #000;
-  border-radius: 12px;
-  overflow: hidden;
-}
-// 移动端适配
-@media screen and (max-width: 768px) {
-  .qa-section {
-    padding: 16px;
-
-    .qa-content {
-      .follow-up-questions {
-        padding-left: 16px;
-      }
-    }
-  }
-}
-#candidate-answer{
-  margin-left: 15px;
-  position: relative;
-  padding-left: 12px;  // 为竖条留出空间
-  &::before {
-    content: '';
-    position: absolute;
-    left: -7px;
-    top: 50%;
-    transform: translateY(-50%);
-    width: 3px;
-    height: 16px;
-    background: #2a3dae;  // 竖条颜色
-    border-radius: 2px;
-  }
-}
-
-// 添加媒体查询以处理移动端显示
-@media screen and (max-width: 768px) {
-  .score-section-left {
-    flex-wrap: wrap; // 在移动端允许换行
-    white-space: normal; // 在移动端允许换行
-    
-    .detection-results {
-      width: 100%;
-      flex-wrap: wrap;
-      
-      .detection-item {
-        width: 100%;
-        justify-content: flex-start;
-      }
-      
-      .separator {
-        display: none; // 在移动端隐藏分隔符
-      }
-    }
-  }
-}
-
-// 添加自定义进度条样式
-:deep(.custom-progress) {
-  .el-progress-bar__outer {
-    border-radius: 3px;
-    overflow: hidden;
-  }
-  
-  .el-progress__text {
-    font-size: 12px !important;
-    color: #808080 !important;
-    margin-left: 5px !important;
-  }
-  
-  .el-progress-bar__inner {
-    border-radius: 3px;
-    position: relative;
-    &::after {
-      content: '';
-      position: absolute;
-      right: 0;
-      top: 0;
-      height: 100%;
-      width: 6px;
-      background: inherit;
-      filter: brightness(115%);
-      border-radius: 0 3px 3px 0;
-    }
-  }
-}
-
-// 修改分析文本的样式
-/* .text-[14px] {
-  white-space: pre-wrap; // 保留换行和空格
-  word-wrap: break-word; // 允许长单词换行
-  word-break: break-all; // 在任意字符间换行
-  line-height: 1.6; // 增加行高
-  padding: 8px 0; // 增加上下内边距
-} */
-
-.analysis-content {
-  .analysis-text {
-    white-space: pre-line; // 只保留换行符
-    line-height: 1.6;
-    margin: 0;
-    padding: 0;
-  }
-}
-
-.assessment-points {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 8px;
-  align-items: center;
-  
-  .assessment-item {
-    display: flex;
-    align-items: center;
-    gap: 4px;
-    
-    .point-name {
-      color: #333;
-      font-weight: 500;
-    }
-
-    .point-score {
-      font-size: 12px;
-      padding: 2px 6px;
-      border-radius: 4px;
-      
-      &.score-excellent {
-        background-color: #e6f7ff;
-        color: #1890ff;
-      }
-      
-      &.score-good {
-        background-color: #f6ffed;
-        color: #52c41a;
-      }
-      
-      &.score-average {
-        background-color: #fff7e6;
-        color: #fa8c16;
-      }
-      
-      &.score-poor {
-        background-color: #fff1f0;
-        color: #f5222d;
-      }
-    }
-  }
-
-  .separator {
-    color: #666666;
-    margin: 0 4px;
-  }
-}
-
-.bar-chart-container {
-  flex: 1;
-  margin-right: 45px;
-}
-
-.ability-scores {
-  .ability-item {
-    display: flex;
-    align-items: center;
-    margin-bottom: 20px;
-    position: relative;
-    min-height: 30px;
-
-    .ability-name {
-      min-width: 120px;
-      margin-right: 20px;
-      font-size: 14px;
-      color: #333;
-      white-space: nowrap;
-      display: flex;
-      align-items: center;
-      height: 30px; // 固定高度以确保垂直居中
-    }
-
-    .bar-chart-container {
-      flex: 1;
-      margin-right: 45px;
-      display: flex;
-      align-items: center;
-      height: 30px; // 固定高度以确保垂直居中
-
-      :deep(.echarts) {
-        height: 100% !important;
-      }
-    }
-  }
-}
-
-// 修改导出PDF时的样式处理
-.detailed-evaluation {
-  .ability-scores {
-    .ability-item {
-      @media print {
-        display: flex !important;
-        align-items: center !important;
-        min-height: 30px !important;
-
-        .ability-name {
-          display: flex !important;
-          align-items: center !important;
-          height: 30px !important;
-        }
-
-        .bar-chart-container {
-          display: flex !important;
-          align-items: center !important;
-          height: 30px !important;
-        }
-      }
-    }
-  }
-}
-
-// 确保在导出时保持样式
-:deep(.custom-progress) {
-  @media print {
-    .el-progress-bar__outer {
-      height: 8px !important;
-    }
-
-    .el-progress__text {
-      position: absolute !important;
-      right: -45px !important;
-      top: 50% !important;
-      transform: translateY(-50%) !important;
-      line-height: 1 !important;
-    }
-  }
-}
-</style>

+ 0 - 619
src/views/JobApplication/report/topic.vue

@@ -1,619 +0,0 @@
-<script setup lang="ts">
-// 导入必要的组件和类型
-import { ref, onMounted } from 'vue'
-import { useRoute } from 'vue-router'
-
-// 定义面试记录的接口
-interface InterviewRecord {
-  question: string
-  answer: string
-  analysis: string
-  score: string
-  videoUrl?: string
-  thumbnail?: string
-  question_form?: number
-  is_correct?: boolean | string
-  question_image_url?: string
-  parent_question_id?: number
-  follow_up_questions?: InterviewRecord[]
-  question_id?: number
-  options?: {
-    id: number
-    option_text: string
-    is_correct: boolean
-    sort: number
-  }[]
-  answer_data?: {
-    text_answer: string
-    selected_options: number[]
-    selected_option_details: {
-      id: number
-      is_correct: boolean
-      option_text: string
-    }[]
-  }
-}
-
-// 定义响应式数据
-const route = useRoute()
-const interviewRecords = ref<InterviewRecord[]>([])
-const loading = ref(true)
-const error = ref('')
-
-// 添加base64编码的内联图片作为fallback
-const fallbackImageBase64Ref = ref('data:image/svg+xml;base64,PHN2ZyB...')
-
-// 图片错误处理函数
-const handleImageError = (event: Event) => {
-  const target = event.target as HTMLImageElement
-  if (target) {
-    target.src = fallbackImageBase64Ref.value
-    target.onerror = null
-  }
-}
-
-// 视频相关处理函数
-const handleVideoLoaded = (event: Event) => {
-  const videoElement = event.target as HTMLVideoElement
-  if (videoElement) {
-    if (videoElement.readyState >= 1) {
-      captureVideoFirstFrame(videoElement)
-    } else {
-      videoElement.addEventListener('loadedmetadata', () => {
-        captureVideoFirstFrame(videoElement)
-      })
-    }
-  }
-}
-
-const handleVideoError = (event: Event) => {
-  const videoElement = event.target as HTMLVideoElement
-  if (videoElement) {
-    videoElement.poster = fallbackImageBase64Ref.value
-    console.error('视频加载错误')
-  }
-}
-
-// 获取面试记录数据
-const fetchInterviewRecords = async () => {
-  loading.value = true
-  try {
-    const id = route.query.id as string || '1'
-    const tenant_id = route.query.tenant_id as string || '1'
-    const application_id = route.query.applicationId as string || '1'
-    
-    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application_detail?tenant_id=${1}&application_id=${application_id}`)
-    const result = await response.json()
-    
-    if (result.code === 2000 && result.data?.interview_progress) {
-      // 处理面试记录
-      const processedRecords = result.data.interview_progress
-        .filter((q: any) => q.video_answer || q.answer_data)
-        .map((q: any) => {
-          const record: InterviewRecord = {
-            question: q.question_text || '未提供问题',
-            answer: '',
-            analysis: '',
-            score: '',
-            question_form: q.question_form,
-            parent_question_id: q.parent_question_id,
-            options: q.options,
-            answer_data: q.answer_data,
-            follow_up_questions: [],
-            question_id: q.question_id
-          }
-
-          if (q.video_answer) {
-            record.answer = q.video_answer.transcript || ''
-            record.analysis = q.video_answer.ai_analysis?.comment || ''
-            record.score = q.video_answer.ai_score ? `${q.video_answer.ai_score}分` : ''
-            record.videoUrl = q.video_answer.video_url || ''
-          }
-
-          if (q.answer_data) {
-            record.answer = q.answer_data.text_answer || ''
-            record.is_correct = q.answer_data.selected_option_details?.some((opt: any) => opt.is_correct) || false
-          }
-
-          if (q.question_form === 3) {
-            record.question_image_url = q.question_image_url || ''
-          }
-
-          return record
-        })
-
-      // 将记录分为主题和追问两组
-      const mainQuestions: InterviewRecord[] = []
-      const followUpQuestions: InterviewRecord[] = []
-      
-      processedRecords.forEach((record: InterviewRecord) => {
-        if (record.question_form === 5 && record.parent_question_id) {
-          followUpQuestions.push(record)
-        } else {
-          mainQuestions.push(record)
-        }
-      })
-
-      // 对主题进行排序
-      const sortedMainQuestions = mainQuestions.sort((a: InterviewRecord, b: InterviewRecord) => {
-        const getPriority = (questionForm: number) => {
-          switch (questionForm) {
-            case 0: return 1;  // 视频问答题最优先
-            case 5: return 2;  // 开放问答题次优先
-            case 1: return 3;  // 单选题
-            case 2: return 4;  // 多选题
-            case 3: return 5;  // 图片选择题
-            case 4: return 6;  // 问卷题
-            default: return 99;
-          }
-        }
-        return getPriority(a.question_form || -1) - getPriority(b.question_form || -1)
-      })
-
-      // 将追问题数组倒序
-      const reversedFollowUps = [...followUpQuestions].reverse()
-
-      // 将倒序后的追问题添加到对应的父问题中
-      reversedFollowUps.forEach(followUp => {
-        const parentQuestion = sortedMainQuestions.find(
-          q => q.question_form === 0 && q.question_id === followUp.parent_question_id
-        )
-        if (parentQuestion) {
-          // 如果follow_up_questions还没有初始化,先初始化为空数组
-          if (!parentQuestion.follow_up_questions) {
-            parentQuestion.follow_up_questions = []
-          }
-          // 使用unshift而不是push,这样新的追问题会被添加到数组开头
-          parentQuestion.follow_up_questions.unshift(followUp)
-        }
-      })
-
-      // 设置最终结果
-      interviewRecords.value = sortedMainQuestions
-    } else {
-      error.value = result.msg || '获取数据失败'
-    }
-  } catch (err) {
-    console.error('获取面试记录失败:', err)
-    error.value = '获取数据失败,请稍后重试'
-  } finally {
-    loading.value = false
-  }
-}
-
-// 在script setup部分添加以下函数
-const captureVideoFirstFrame = (videoElement: HTMLVideoElement) => {
-  // 创建一个一次性的事件监听器,在视频可以播放时捕获第一帧
-  const captureFrame = () => {
-    try {
-      // 确保视频已加载足够的数据
-      if (videoElement.readyState >= 2) {
-        // 创建canvas元素
-        const canvas = document.createElement('canvas')
-        canvas.width = videoElement.videoWidth || 320
-        canvas.height = videoElement.videoHeight || 240
-        
-        // 在canvas上绘制视频当前帧
-        const ctx = canvas.getContext('2d')
-        if (ctx) {
-          // 确保视频当前时间设置为开始位置
-          videoElement.currentTime = 0.1
-          
-          // 绘制视频帧到canvas
-          ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height)
-          
-          // 将canvas转换为图片URL
-          const thumbnailUrl = canvas.toDataURL('image/jpeg')
-          
-          // 设置为视频的poster属性
-          videoElement.poster = thumbnailUrl
-          
-          // 清理
-          videoElement.removeEventListener('loadeddata', captureFrame)
-        }
-      }
-    } catch (error) {
-      console.error('捕获视频第一帧失败:', error)
-      // 设置默认缩略图
-      videoElement.poster = fallbackImageBase64Ref.value
-    }
-  }
-  
-  // 添加事件监听器
-  videoElement.addEventListener('loadeddata', captureFrame)
-  
-  // 如果视频已经加载,立即尝试捕获
-  if (videoElement.readyState >= 2) {
-    captureFrame()
-  }
-  
-  // 添加错误处理
-  videoElement.addEventListener('error', () => {
-    console.error('视频加载失败')
-    videoElement.poster = fallbackImageBase64Ref.value
-  })
-  
-  // 设置超时,确保即使视频加载缓慢也能显示默认缩略图
-  setTimeout(() => {
-    if (!videoElement.poster || videoElement.poster === '') {
-      videoElement.poster = fallbackImageBase64Ref.value
-    }
-  }, 3000)
-}
-
-// 在script setup中添加题型名称映射函数
-const getQuestionTypeName = (questionForm: number): string => {
-  const typeMap: Record<number, string> = {
-    0: '视频问答题(AI面试官提问)',
-    1: '单选题(知识检测)',
-    2: '多选题(综合选择)',
-    3: '图片选择题(场景判断)',
-    4: '问卷题(个性测评)',
-    5: '开放问答题(追问互动)'
-  }
-  return typeMap[questionForm] || '未知题型'
-}
-
-onMounted(() => {
-  fetchInterviewRecords()
-})
-</script>
-
-<template>
-  <div class="page-container">
-    <div class="content-container">
-      <div class="max-w-4xl mx-auto p-6">
-        <!-- 加载状态 -->
-        <a-spin :spinning="loading" tip="加载中...">
-          <!-- 错误提示 -->
-          <div v-if="error" class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
-            <strong class="font-bold">错误!</strong>
-            <span class="block sm:inline">{{ error }}</span>
-          </div>
-
-          <!-- 暂无数据提示 -->
-          <div v-else-if="!loading && (!interviewRecords || interviewRecords.length === 0)" 
-               class="flex flex-col items-center justify-center py-12 bg-gray-50 rounded-lg">
-            <svg class="w-16 h-16 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
-              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 
-                    d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
-              </path>
-            </svg>
-            <h3 class="mt-4 text-lg font-medium text-gray-900">暂无面试记录</h3>
-            <p class="mt-1 text-sm text-gray-500">当前没有任何面试记录数据</p>
-          </div>
-
-          <!-- 面试记录列表 -->
-          <div v-else-if="interviewRecords.length > 0" class="space-y-4">
-            <div v-for="(record, index) in interviewRecords" :key="index" class="bg-gray-50 rounded-lg p-4">
-              <!-- 题目标题部分 -->
-              <div class="mb-4">
-                <div class="flex items-start space-x-2">
-                  <div class="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm">第{{ index + 1 }}题</div>
-                  <div class="bg-gray-100 text-gray-800 px-2 py-1 rounded text-sm">{{ getQuestionTypeName(record.question_form || 0) }}</div>
-                  <div class="flex-1">
-                    <h3 class="font-semibold text-gray-800">{{ record.question }}</h3>
-                  </div>
-                </div>
-
-                <!-- 选择题布局 -->
-                <div v-if="record.question_form === 1 || record.question_form === 2 || record.question_form === 4" class="mt-4">
-                  <div class="bg-white p-4 rounded-lg">
-                    <!-- 显示选项 -->
-                    <div v-if="record.options && record.options.length > 0" class="space-y-3">
-                      <div v-for="(option, optIndex) in record.options" :key="optIndex"
-                           :class="[
-                             'p-3 rounded border',
-                             record.answer_data?.selected_option_details?.some(
-                               selected => selected.id === option.id
-                             )
-                               ? 'border-blue-300 bg-blue-50'
-                               : 'border-gray-200'
-                           ]">
-                        <div class="flex items-center justify-between">
-                          <div class="flex-1">
-                            <span class="text-gray-700">{{ option.option_text }}</span>
-                          </div>
-                          <div class="flex items-center space-x-2">
-                            <template v-if="record.question_form !== 4">
-                              <span v-if="option.is_correct" class="text-green-500">✓ 正确答案</span>
-                              <span v-if="record.answer_data?.selected_option_details?.some(
-                                      selected => selected.id === option.id
-                                    )"
-                                        :class="option.is_correct ? 'text-green-500' : 'text-red-500'">
-                                {{ option.is_correct ? '(已选 - 正确)' : '(已选 - 错误)' }}
-                              </span>
-                            </template>
-                            <template v-else>
-                              <span v-if="record.answer_data?.selected_option_details?.some(
-                                      selected => selected.id === option.id
-                                    )"
-                                        class="text-blue-500">
-                                (已选)
-                              </span>
-                            </template>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                    
-                    <!-- 显示答案状态 -->
-                    <div class="mt-4 flex justify-between items-center">
-                      <div class="text-gray-600">
-                        候选人: {{ record.answer_data?.selected_option_details?.map(
-                          opt => opt.option_text
-                        ).join(', ') || '未作答' }}
-                      </div>
-                      <div v-if="record.question_form !== 4">
-                        <span :class="[
-                          'px-3 py-1 rounded-full text-sm',
-                          record.answer_data?.selected_option_details?.every(
-                            opt => opt.is_correct
-                          )
-                            ? 'bg-green-100 text-green-800'
-                            : 'bg-red-100 text-red-800'
-                        ]">
-                          {{ record.answer_data?.selected_option_details?.every(
-                               opt => opt.is_correct
-                             ) ? '正确' : '错误' }}
-                        </span>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-
-                <!-- 视频问答题布局 -->
-                <div v-else-if="record.question_form === 0 || record.question_form === 5" class="mt-4">
-                  <!-- 原有的视频问答题布局 -->
-                  <div class="flex space-x-4">
-                    <!-- 右侧答案和评分 -->
-                    <div class="w-2/3">
-                      <div class="bg-white p-4 rounded-lg">
-                        <div class="mb-4">
-                          <div class="flex items-start space-x-2">
-                            <div class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">候选人</div>
-                            <div class="flex-1">
-                              <p class="text-gray-700">{{ record.answer }}</p>
-                            </div>
-                          </div>
-                        </div>
-                        
-                        <div>
-                          <div class="flex items-start space-x-2">
-                            <div class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-sm">AI评分</div>
-                            <div class="flex-1">
-                              <p class="text-gray-600 m-0">{{ record.analysis }}</p>
-                              <div class="mt-1 text-blue-500">得分:{{ record.score }}</div>
-                            </div>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                    <!-- 左侧视频播放器 -->
-                    <div class="w-1/3">
-                      <div class="video-container">
-                        <video
-                          class="w-full h-full object-cover rounded-lg"
-                          controls
-                          :src="record.videoUrl"
-                          preload="metadata"
-                          :poster="fallbackImageBase64Ref"
-                          @loadeddata="handleVideoLoaded"
-                          @error="handleVideoError"
-                        >
-                          您的浏览器不支持视频播放。
-                        </video>
-                      </div>
-                    </div>
-                  </div>
-                  
-                  <!-- 追问题列表 -->
-                  <div v-if="record.follow_up_questions && record.follow_up_questions.length > 0" 
-                       class="mt-4 pl-8 border-l-2 border-gray-200">
-                    <div v-for="(followUp, fIndex) in record.follow_up_questions" 
-                         :key="fIndex" 
-                         class="mb-4">
-                      <div class="flex items-start space-x-2">
-                        <div class="bg-purple-100 text-purple-800 px-2 py-1 rounded text-sm">追问题</div>
-                        <div class="flex-1">
-                          <h4 class="font-medium text-gray-800">{{ followUp.question }}</h4>
-                          <!-- 追问题的视频和答案布局 -->
-                          <div class="mt-2">
-                            <div class="flex space-x-4">
-                              <!-- 右侧答案和评分 -->
-                              <div class="w-2/3">
-                                <div class="bg-white p-4 rounded-lg">
-                                  <div class="mb-4">
-                                    <div class="flex items-start space-x-2">
-                                      <div class="bg-green-100 text-green-800 px-2 py-1 rounded text-sm">候选人</div>
-                                      <div class="flex-1">
-                                        <p class="text-gray-700">{{ followUp.answer }}</p>
-                                      </div>
-                                    </div>
-                                  </div>
-                                  
-                                  <div>
-                                    <div class="flex items-start space-x-2">
-                                      <div class="bg-yellow-100 text-yellow-800 px-2 py-1 rounded text-sm">AI评分</div>
-                                      <div class="flex-1">
-                                        <p class="text-gray-600 m-0">{{ followUp.analysis }}</p>
-                                        <div class="mt-1 text-blue-500">得分:{{ followUp.score }}</div>
-                                      </div>
-                                    </div>
-                                  </div>
-                                </div>
-                              </div>
-                              <!-- 左侧视频播放器 -->
-                              <div v-if="followUp.videoUrl" class="w-1/3">
-                                <div class="video-container">
-                                  <video
-                                    class="w-full h-full object-cover rounded-lg"
-                                    controls
-                                    :src="followUp.videoUrl"
-                                    preload="metadata"
-                                    :poster="fallbackImageBase64Ref"
-                                    @loadeddata="handleVideoLoaded"
-                                    @error="handleVideoError"
-                                  >
-                                    您的浏览器不支持视频播放。
-                                  </video>
-                                </div>
-                              </div>
-                            </div>
-                          </div>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-
-                <!-- 图片题布局 -->
-                <div v-else-if="record.question_form === 3" class="mt-4">
-                  <div class="grid grid-cols-2 gap-4">
-                    <!-- 左侧图片显示 -->
-                    <div class="bg-white p-4 rounded-lg">
-                      <div class="aspect-w-16 aspect-h-9 rounded-lg overflow-hidden bg-gray-100">
-                        <img 
-                          :src="record.question_image_url" 
-                          alt="题目图片" 
-                          class="w-full h-full object-contain"
-                          @error="handleImageError"
-                        >
-                      </div>
-                    </div>
-                    
-                    <!-- 右侧选项和答案 -->
-                    <div class="bg-white p-4 rounded-lg">
-                      <!-- 显示选项 -->
-                      <div v-if="record.options && record.options.length > 0" class="space-y-3">
-                        <div v-for="(option, optIndex) in record.options" :key="optIndex"
-                             :class="[
-                               'p-3 rounded border',
-                               record.answer_data?.selected_option_details?.some(
-                                 selected => selected.id === option.id
-                               )
-                                 ? 'border-blue-300 bg-blue-50'
-                                 : 'border-gray-200'
-                             ]">
-                          <div class="flex items-center justify-between">
-                            <div class="flex-1">
-                              <span class="text-gray-700">{{ option.option_text }}</span>
-                            </div>
-                            <div class="flex items-center space-x-2">
-                              <span v-if="option.is_correct" class="text-green-500">✓ 正确答案</span>
-                              <span v-if="record.answer_data?.selected_option_details?.some(
-                                      selected => selected.id === option.id
-                                    )"
-                                        :class="option.is_correct ? 'text-green-500' : 'text-red-500'">
-                                {{ option.is_correct ? '(已选 - 正确)' : '(已选 - 错误)' }}
-                              </span>
-                            </div>
-                          </div>
-                        </div>
-                      </div>
-                      
-                      <!-- 显示答案状态 -->
-                      <div class="mt-4 flex justify-between items-center">
-                        <div class="text-gray-600">
-                          答案: {{ record.answer_data?.selected_option_details?.map(
-                            opt => opt.option_text
-                          ).join(', ') || '未作答' }}
-                        </div>
-                        <div>
-                          <span :class="[
-                            'px-3 py-1 rounded-full text-sm',
-                            record.answer_data?.selected_option_details?.every(
-                              opt => opt.is_correct
-                            )
-                              ? 'bg-green-100 text-green-800'
-                              : 'bg-red-100 text-red-800'
-                          ]">
-                            {{ record.answer_data?.selected_option_details?.every(
-                                 opt => opt.is_correct
-                               ) ? '正确' : '错误' }}
-                          </span>
-                        </div>
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </a-spin>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style scoped>
-.video-container {
-  position: relative;
-  width: 100%;
-  max-width: 240px;
-  aspect-ratio: 3/4;
-  margin: 0 auto;
-  background: #000;
-  border-radius: 12px;
-  overflow: hidden;
-}
-
-/* 确保内容区域可以正常滚动 */
-.max-w-4xl {
-  position: relative;
-  height: auto;
-  padding-bottom: 2rem;
-}
-
-/* 打印样式 */
-@media print {
-  /* 确保内容完整显示 */
-  .max-w-4xl {
-    max-width: 100% !important;
-    padding: 0 !important;
-    margin: 0 !important;
-  }
-  
-  /* 调整页面边距 */
-  @page {
-    margin: 1cm;
-  }
-  
-  /* 确保背景色和图片打印 */
-  * {
-    -webkit-print-color-adjust: exact !important;
-    color-adjust: exact !important;
-    print-color-adjust: exact !important;
-  }
-  
-  /* 避免视频容器在打印时出现问题 */
-  .video-container {
-    page-break-inside: avoid;
-    break-inside: avoid;
-  }
-}
-
-/* 响应式布局 */
-@media (max-width: 768px) {
-  .max-w-4xl {
-    padding: 1rem;
-  }
-  
-  .grid-cols-3 {
-    grid-template-columns: 1fr;
-  }
-}
-
-/* 添加新的样式 */
-.page-container {
-  height: 100vh;
-  overflow-y: auto;
-  background-color: white;
-}
-
-.content-container {
-  max-width: 64rem; /* 4xl = 64rem */
-  margin: 0 auto;
-  padding: 1.5rem;
-}
-</style>

+ 0 - 312
src/views/digitalHuman/detail/index.vue

@@ -1,312 +0,0 @@
-<template>
-  <fs-page>
-    <div class="digital-human-detail">
-      <!-- 顶部操作栏 -->
-      <div class="detail-header">
-        <el-button @click="goBack" icon="ArrowLeft">返回</el-button>
-        <h2 class="detail-title">{{ digitalHuman.name || '数字人详情' }}</h2>
-        <div class="detail-actions">
-          <el-button type="primary" @click="openEditForm" v-if="canEdit">编辑</el-button>
-        </div>
-      </div>
-
-      <!-- 详情内容区 -->
-      <div class="detail-content" v-loading="loading">
-        <el-row :gutter="20">
-          <el-col :span="6">
-            <div class="avatar-container">
-              <img :src="digitalHuman.avatar_url" alt="数字人头像" class="avatar-image" v-if="digitalHuman.avatar_url">
-              <div class="avatar-placeholder" v-else>暂无头像</div>
-            </div>
-          </el-col>
-          <el-col :span="18">
-            <el-descriptions :column="2" border>
-              <el-descriptions-item label="名称">{{ digitalHuman.name }}</el-descriptions-item>
-              <el-descriptions-item label="状态">
-                <el-tag :type="digitalHuman.status === 1 ? 'success' : 'danger'">
-                  {{ digitalHuman.status === 1 ? '启用' : '禁用' }}
-                </el-tag>
-              </el-descriptions-item>
-              <el-descriptions-item label="语音ID">{{ digitalHuman.config?.voice_id }}</el-descriptions-item>
-              <el-descriptions-item label="风格">{{ digitalHuman.config?.style }}</el-descriptions-item>
-              <el-descriptions-item label="创建人">{{ digitalHuman.creator_name }}</el-descriptions-item>
-              <el-descriptions-item label="创建时间">{{ digitalHuman.create_datetime }}</el-descriptions-item>
-              <el-descriptions-item label="更新时间">{{ digitalHuman.update_datetime }}</el-descriptions-item>
-              <el-descriptions-item label="描述" :span="2">{{ digitalHuman.description }}</el-descriptions-item>
-            </el-descriptions>
-          </el-col>
-        </el-row>
-      </div>
-    </div>
-
-    <!-- 编辑表单弹窗 -->
-    <el-dialog
-      v-model="showEditForm"
-      title="编辑数字人"
-      width="500px"
-      :close-on-click-modal="false"
-      :destroy-on-close="true"
-    >
-      <el-form ref="formRef" :model="formData" label-width="100px" class="form-content">
-        <el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
-          <el-input v-model="formData.name" placeholder="请输入数字人名称"></el-input>
-        </el-form-item>
-        
-        <el-form-item label="描述" prop="description">
-          <el-input v-model="formData.description" type="textarea" placeholder="请输入描述"></el-input>
-        </el-form-item>
-        
-        <el-form-item label="头像" prop="avatar_url" :rules="[{ required: true, message: '请上传头像', trigger: 'change' }]">
-          <el-upload
-            class="avatar-uploader"
-            :action="`${apiBaseUrl}/api/system/admin_upload/`"
-            :headers="{
-              'Authorization': `JWT ${Session.get('token') || ''}`
-            }"
-            :data="{
-              tenant_id: '1'
-            }"
-            :show-file-list="false"
-            :on-success="handleAvatarSuccess"
-            :before-upload="beforeAvatarUpload">
-            <img v-if="formData.avatar_url" :src="formData.avatar_url" class="avatar" />
-            <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
-          </el-upload>
-        </el-form-item>
-        
-        <el-form-item label="状态" prop="status">
-          <el-switch v-model="formData.status" :active-value="1" :inactive-value="0"></el-switch>
-        </el-form-item>
-        
-        <el-form-item label="语音ID" prop="config.voice_id">
-          <el-select v-model="formData.config.voice_id" placeholder="请选择语音">
-            <el-option label="小明(高级)" value="xiaoming_voice_premium"></el-option>
-            <el-option label="小红" value="xiaohong_voice"></el-option>
-            <!-- 可以根据实际情况添加更多选项 -->
-          </el-select>
-        </el-form-item>
-        
-        <el-form-item label="风格" prop="config.style">
-          <el-select v-model="formData.config.style" placeholder="请选择风格">
-            <el-option label="专业" value="professional"></el-option>
-            <el-option label="友好" value="friendly"></el-option>
-            <el-option label="严肃" value="serious"></el-option>
-            <!-- 可以根据实际情况添加更多选项 -->
-          </el-select>
-        </el-form-item>
-      </el-form>
-      
-      <template #footer>
-        <div class="dialog-footer">
-          <el-button @click="closeEditForm">取消</el-button>
-          <el-button type="primary" @click="submitEditForm">保存</el-button>
-        </div>
-      </template>
-    </el-dialog>
-  </fs-page>
-</template>
-
-<script lang="ts" setup name="digitalHumanDetail">
-import { onMounted, ref, reactive } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import { Plus, ArrowLeft } from '@element-plus/icons-vue';
-import { Session } from '/@/utils/storage';
-import { GetObj, UpdateObj } from '../list/api';
-import { auth } from '/@/utils/authFunction';
-
-const apiBaseUrl = import.meta.env.VITE_API_URL;
-const route = useRoute();
-const router = useRouter();
-const loading = ref(true);
-const digitalHuman = ref({});
-const canEdit = ref(auth('digitalHuma:Update'));
-
-// 编辑表单相关
-const showEditForm = ref(false);
-const formRef = ref();
-const formData = reactive({
-  id: '',
-  name: '',
-  description: '',
-  avatar_url: '',
-  tenant_id: 1,
-  status: 1,
-  config: {
-    voice_id: 'xiaoming_voice_premium',
-    style: 'professional'
-  }
-});
-
-// 获取数字人详情
-const getDigitalHumanDetail = async () => {
-  try {
-    loading.value = true;
-    const id = route.query.id;
-    if (!id) {
-      ElMessage.error('缺少数字人ID');
-      goBack();
-      return;
-    }
-    
-    const res = await GetObj(id as string);
-    digitalHuman.value = res.data;
-    loading.value = false;
-  } catch (error) {
-    console.error('获取数字人详情失败', error);
-    ElMessage.error('获取数字人详情失败');
-    loading.value = false;
-  }
-};
-
-// 返回列表页
-const goBack = () => {
-  router.push('/digitalHuman/list');
-};
-
-// 打开编辑表单
-const openEditForm = () => {
-  showEditForm.value = true;
-  // 填充表单数据
-  Object.assign(formData, {
-    id: digitalHuman.value.id,
-    name: digitalHuman.value.name,
-    description: digitalHuman.value.description,
-    avatar_url: digitalHuman.value.avatar_url,
-    tenant_id: digitalHuman.value.tenant_id || 1,
-    status: digitalHuman.value.status,
-    config: {
-      voice_id: digitalHuman.value.config?.voice_id || 'xiaoming_voice_premium',
-      style: digitalHuman.value.config?.style || 'professional'
-    }
-  });
-};
-
-// 关闭编辑表单
-const closeEditForm = () => {
-  showEditForm.value = false;
-};
-
-// 头像上传成功处理
-const handleAvatarSuccess = (res: any, file: any) => {
-  // 根据实际接口返回格式调整
-  console.log(res);
-  formData.avatar_url = res.url;
-};
-
-// 头像上传前验证
-const beforeAvatarUpload = (file: any) => {
-  const isImage = file.type.startsWith('image/');
-  const isLt2M = file.size / 1024 / 1024 < 2;
-
-  if (!isImage) {
-    ElMessage.error('上传头像图片只能是图片格式!');
-  }
-  if (!isLt2M) {
-    ElMessage.error('上传头像图片大小不能超过 2MB!');
-  }
-  return isImage && isLt2M;
-};
-
-// 提交编辑表单
-const submitEditForm = async () => {
-  try {
-    await formRef.value.validate();
-    await UpdateObj(formData);
-    ElMessage.success('更新成功');
-    closeEditForm();
-    getDigitalHumanDetail(); // 刷新详情
-  } catch (e) {
-    console.error(e);
-    ElMessage.error('更新失败');
-  }
-};
-
-onMounted(() => {
-  getDigitalHumanDetail();
-});
-</script>
-
-<style scoped>
-.digital-human-detail {
-  padding: 20px;
-}
-
-.detail-header {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-}
-
-.detail-title {
-  flex: 1;
-  margin: 0 20px;
-  font-size: 20px;
-}
-
-.detail-content {
-  background-color: #fff;
-  padding: 20px;
-  border-radius: 4px;
-}
-
-.avatar-container {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  height: 100%;
-}
-
-.avatar-image {
-  width: 200px;
-  height: 200px;
-  object-fit: cover;
-  border-radius: 4px;
-}
-
-.avatar-placeholder {
-  width: 200px;
-  height: 200px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  background-color: #f5f7fa;
-  color: #909399;
-  border-radius: 4px;
-}
-
-/* 编辑表单样式 */
-.avatar-uploader {
-  text-align: center;
-  border: 1px dashed #d9d9d9;
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-  transition: border-color 0.3s;
-}
-
-.avatar-uploader:hover {
-  border-color: #409EFF;
-}
-
-.avatar-uploader-icon {
-  font-size: 28px;
-  color: #8c939d;
-  width: 100px;
-  height: 100px;
-  line-height: 100px;
-  text-align: center;
-}
-
-.avatar {
-  width: 100px;
-  height: 100px;
-  display: block;
-  object-fit: cover;
-}
-
-.dialog-footer {
-  text-align: right;
-}
-</style> 

+ 0 - 47
src/views/digitalHuman/list/api.ts

@@ -1,47 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-
-export const apiPrefix = '/api/system/digital_human/';
-export function GetList(query: UserPageQuery) {
-	return request({
-		url: apiPrefix,
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: apiPrefix + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: apiPrefix,
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: apiPrefix + obj.id + '/',
-		method: 'put',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function DelObj(id: DelReq) {
-	return request({
-		url: apiPrefix + id + '/',
-		method: 'delete',
-		data: { id, tenant_id: 1 },
-	});
-}
-export function GetPermission() {
-    return request({
-        url: apiPrefix,
-        method: 'get',
-    });
-}

+ 0 - 192
src/views/digitalHuman/list/components/EditDialog.vue

@@ -1,192 +0,0 @@
-<template>
-  <el-dialog
-    v-model="visible"
-    :title="title"
-    width="600px"
-    :close-on-click-modal="false"
-    @closed="handleClosed"
-  >
-    <el-form
-      ref="formRef"
-      :model="form"
-      :rules="rules"
-      label-width="100px"
-    >
-      <el-form-item label="数字人名称" prop="name">
-        <el-input v-model="form.name" placeholder="请输入数字人名称" />
-      </el-form-item>
-
-      <el-form-item label="描述" prop="description">
-        <el-input
-          v-model="form.description"
-          type="textarea"
-          :rows="3"
-          placeholder="请输入描述"
-        />
-      </el-form-item>
-
-      <el-form-item label="头像" prop="avatar_url">
-        <el-upload
-          class="avatar-uploader"
-          action="/api/system/admin_upload/"
-          :headers="uploadHeaders"
-          :show-file-list="false"
-          :before-upload="beforeAvatarUpload"
-          :on-success="handleAvatarSuccess"
-        >
-          <img v-if="form.avatar_url" :src="form.avatar_url" class="avatar" />
-          <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
-        </el-upload>
-      </el-form-item>
-
-      <el-form-item label="状态" prop="status">
-        <el-select v-model="form.status" placeholder="请选择状态">
-          <el-option label="启用" :value="1" />
-          <el-option label="禁用" :value="0" />
-        </el-select>
-      </el-form-item>
-    </el-form>
-
-    <template #footer>
-      <el-button @click="visible = false">取 消</el-button>
-      <el-button type="primary" @click="handleSubmit">确 定</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, watch } from 'vue';
-import { Plus } from '@element-plus/icons-vue';
-import type { FormInstance } from 'element-plus';
-import { ElMessage } from 'element-plus';
-import { UpdateObj } from '../api';
-
-const props = defineProps({
-  modelValue: Boolean,
-  rowData: {
-    type: Object,
-    default: () => ({})
-  }
-});
-
-const emit = defineEmits(['update:modelValue', 'success']);
-
-const visible = ref(props.modelValue);
-const formRef = ref<FormInstance>();
-const title = ref('编辑数字人');
-
-// 表单数据
-const form = reactive({
-  id: undefined,
-  name: '',
-  description: '',
-  avatar_url: '',
-  status: 1,
-});
-
-// 表单校验规则
-const rules = {
-  name: [{ required: true, message: '请输入数字人名称', trigger: 'blur' }],
-  avatar_url: [{ required: true, message: '请上传头像', trigger: 'change' }],
-  status: [{ required: true, message: '请选择状态', trigger: 'change' }],
-};
-
-// 上传相关
-const uploadHeaders = {
-  Authorization: `JWT ${localStorage.getItem('token') || ''}`
-};
-
-const beforeAvatarUpload = (file: File) => {
-  const isImage = file.type.startsWith('image/');
-  const isLt2M = file.size / 1024 / 1024 < 2;
-
-  if (!isImage) {
-    ElMessage.error('只能上传图片文件!');
-    return false;
-  }
-  if (!isLt2M) {
-    ElMessage.error('图片大小不能超过 2MB!');
-    return false;
-  }
-  return true;
-};
-
-const handleAvatarSuccess = (res: any) => {
-  form.avatar_url = res.data.url;
-};
-
-// 初始化表单数据
-const initForm = () => {
-  Object.assign(form, props.rowData);
-};
-
-// 提交表单
-const handleSubmit = async () => {
-  if (!formRef.value) return;
-  
-  await formRef.value.validate(async (valid) => {
-    if (valid) {
-      try {
-        await UpdateObj(form);
-        ElMessage.success('更新成功');
-        emit('success');
-        visible.value = false;
-      } catch (error) {
-        console.error('更新失败:', error);
-        ElMessage.error('更新失败');
-      }
-    }
-  });
-};
-
-// 关闭弹窗时重置表单
-const handleClosed = () => {
-  if (formRef.value) {
-    formRef.value.resetFields();
-  }
-  emit('update:modelValue', false);
-};
-
-// 监听 visible 变化
-watch(() => props.modelValue, (val) => {
-  visible.value = val;
-  if (val) {
-    initForm();
-  }
-});
-
-watch(() => visible.value, (val) => {
-  emit('update:modelValue', val);
-});
-</script>
-
-<style scoped>
-.avatar-uploader {
-  border: 1px dashed #d9d9d9;
-  border-radius: 6px;
-  cursor: pointer;
-  position: relative;
-  overflow: hidden;
-  width: 100px;
-  height: 100px;
-}
-
-.avatar-uploader:hover {
-  border-color: #409EFF;
-}
-
-.avatar-uploader-icon {
-  font-size: 28px;
-  color: #8c939d;
-  width: 100px;
-  height: 100px;
-  text-align: center;
-  line-height: 100px;
-}
-
-.avatar {
-  width: 100px;
-  height: 100px;
-  display: block;
-}
-</style> 

+ 0 - 440
src/views/digitalHuman/list/crud.tsx

@@ -1,440 +0,0 @@
-import * as api from './api';
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage } from '/@/utils/message';
-import { auth } from '/@/utils/authFunction';
-import tableSelector from '/@/components/tableSelector/index.vue';
-import { shallowRef, getCurrentInstance } from 'vue';
-import { useRouter } from 'vue-router';
-import { Plus } from '@element-plus/icons-vue';
-
-interface DigitalHumanForm {
-	id?: number;
-	name: string;
-	description?: string;
-	avatar_url: string;
-	template_video_url?: string;
-	status: number;
-	config: {
-		style: string;
-		voice_id: string;
-	} | string;
-	creator_name?: string;
-	create_datetime?: string;
-	update_datetime?: string;
-}
-
-export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter();
-	
-	const pageRequest = async (query: UserPageQuery) => {
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj(row.id);
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	/**
-	 * 懒加载
-	 * @param row
-	 * @returns {Promise<unknown>}
-	 */
-	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
-		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
-			resolve(res.data);
-		});
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						show: true,
-						size: 'small',
-						click: () => {
-							crudExpose.emit('add-digital-human');
-						}
-					},
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 300,
-				buttons: {
-					view: {
-						iconRight: 'view',
-						show: false,
-						type: 'text',
-						click: ({ row }) => {
-							// 查看数字人详情
-							// router.push({
-							// 	path: '/digitalHuman/detail',
-							// 	query: { id: row.id }
-							// });
-						}
-					},
-					edit: {
-						iconRight: 'Edit',
-						type: 'text',
-						show: false,
-						/* click: ({ row }) => {
-							console.log(crudExpose);
-					
-							//crudExpose.emit('edit-digital-human', row);
-						} */
-					},
-					remove: {
-						iconRight: 'Delete',
-						type: 'text',
-						show: false//	auth('digitalHuma:Delete'),
-					},
-				},
-			},
-			pagination: {
-				show: true,
-			},
-			table: {
-				rowKey: 'id',
-			},
-			search: {
-				show: true,
-				layout: 'auto',
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				}
-			},
-			columns: {
-				_index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				},
-				name: {
-					title: '数字人名称',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入数字人名称',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 150,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '数字人名称为必填项' },
-						],
-					},
-				},
-				description: {
-					title: '描述',
-					search: {
-						show: false,
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'textarea',
-					column: {
-						minWidth: 200,
-						showOverflowTooltip: true,
-					},
-					form: {
-						component: {
-							span: 24,
-						},
-					},
-				},
-				avatar_url: {
-					title: '头像',
-					type: 'cropper-uploader',
-					form: {
-						component: {
-							uploader: {
-								action: '/api/system/admin_upload/',
-								multiple: false,
-								limit: 1,
-								accept: '.jpg,.png,.jpeg',
-								headers: () => {
-									return {
-										'Authorization': `JWT ${localStorage.getItem('token') || ''}`
-									}
-								},
-								data: {
-									tenant_id: '1'
-								},
-								onSuccessHandle(res: any) {
-									// 返回上传后的URL
-									return res.data.url;
-								},
-								beforeUpload(file: File) {
-									const isImage = file.type.startsWith('image/');
-									const isLt2M = file.size / 1024 / 1024 < 2;
-									
-									if (!isImage) {
-										this.$message.error('只能上传图片文件!');
-										return false;
-									}
-									if (!isLt2M) {
-										this.$message.error('图片大小不能超过 2MB!');
-										return false;
-									}
-									return true;
-								}
-							},
-							slots: {
-								default: ({modelValue}) => {
-									return modelValue ? 
-										<img src={modelValue} class="avatar" style="width:100px;height:100px;"/> : 
-										<el-icon class="avatar-uploader-icon"><Plus /></el-icon>
-								}
-							}
-						},
-						rules: [
-							{ required: true, message: '请上传头像' }
-						]
-					},
-					/* column: {
-						component: {
-							name: 'fs-image',
-							style: {width: '50px', height: '50px'},
-							props: {
-								src: (row) => row.avatar_url,
-								preview: true,
-								fit: 'cover'
-							}
-						}
-					} */
-				},
-				template_video_url: {
-					title: '模板视频',
-					type: 'file-uploader',
-					form: {
-						component: {
-							uploader: {
-								action: '/api/system/admin_upload/',
-								multiple: false,
-								limit: 1,
-								accept: '.mp4,.mov,.avi',
-								headers: () => {
-									return {
-										'Authorization': `JWT ${localStorage.getItem('token') || ''}`
-									}
-								},
-								data: {
-									tenant_id: '1'
-								},
-								onSuccessHandle(res: any) {
-									return res.data.url;
-								},
-								beforeUpload(file: File) {
-									const isVideo = file.type.startsWith('video/');
-									const isLt50M = file.size / 1024 / 1024 < 50;
-									
-									if (!isVideo) {
-										this.$message.error('只能上传视频文件!');
-										return false;
-									}
-									if (!isLt50M) {
-										this.$message.error('视频大小不能超过 50MB!');
-										return false;
-									}
-									return true;
-								}
-							},
-							placeholder: '请上传模板视频'
-						}
-					},
-					column: {
-						minWidth: 180,
-						
-					}
-				},
-				status: {
-					title: '状态',
-					search: {
-						show: true,
-						component: {
-							name: 'el-select',
-							placeholder: '请选择状态',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'dict-select',
-					column: {
-						minWidth: 100,
-					},
-					dict: dict({
-						data: [
-							{ value: 1, label: '启用' },
-							{ value: 0, label: '禁用' },
-						],
-					}),
-				},
-				/* config: {
-					title: '配置信息',
-					type: 'text',
-					column: {
-						minWidth: 150,
-						formatter: (row) => {
-							if (row.config) {
-								try {
-									const config = typeof row.config === 'string' ? JSON.parse(row.config) : row.config;
-									return `风格: ${config.style || '-'}, 语音ID: ${config.voice_id || '-'}`;
-								} catch (e) {
-									return '配置格式错误';
-								}
-							}
-							return '-';
-						},
-						showOverflowTooltip: true,
-					},
-					form: {
-						component: {
-							name: 'el-card',
-							header: '配置信息',
-							children: {
-								style: {
-									title: '风格设置',
-									value: 'professional',
-									component: {
-										name: 'el-select',
-										options: [
-											{ value: 'professional', label: '专业' },
-											{ value: 'friendly', label: '友好' },
-											{ value: 'serious', label: '严肃' },
-											{ value: 'casual', label: '随意' }
-										]
-									}
-								},
-								voice_id: {
-									title: '语音设置',
-									value: 'xiaoming_voice_premium',
-									component: {
-										name: 'el-select',
-										options: [
-											{ value: 'xiaoming_voice_premium', label: '小明(高级)' },
-											{ value: 'xiaohong_voice_premium', label: '小红(高级)' },
-											{ value: 'xiaohong_voice', label: '小红' }
-										]
-									}
-								}
-							}
-						},
-						valueResolve: (form: DigitalHumanForm) => {
-							// 将配置对象转换为JSON字符串
-							if (form.config && typeof form.config === 'object') {
-								form.config = JSON.stringify(form.config);
-							}
-							return form;
-						},
-						valueBuilder: (form: DigitalHumanForm) => {
-							// 将JSON字符串转换为对象
-							if (form.config && typeof form.config === 'string') {
-								try {
-									form.config = JSON.parse(form.config);
-								} catch (e) {
-									console.error('配置解析错误:', e);
-									form.config = { style: 'professional', voice_id: 'xiaoming_voice_premium' };
-								}
-							} else if (!form.config) {
-								form.config = { style: 'professional', voice_id: 'xiaoming_voice_premium' };
-							}
-						}
-					}
-				}, */
-				creator_name: {
-					title: '创建人',
-					type: 'text',
-					column: {
-						minWidth: 120,
-					},
-					form: {
-						show: false,
-					},
-				},
-				create_datetime: {
-					title: '创建时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false,
-					},
-				},
-				update_datetime: {
-					title: '更新时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false,
-					},
-				},
-			/* 	operation: {
-					title: '操作',
-					type: 'button',
-					width: '200px',
-					buttons: {
-						edit: {
-							show: false
-						},
-					},
-					columnSlot: true
-				} */
-			},
-		},
-	};
-};

+ 0 - 511
src/views/digitalHuman/list/index.vue

@@ -1,511 +0,0 @@
-<template>
-	<fs-page>
-		<!-- 添加一个容器来分割页面 -->
-		<div class="digital-human-container">
-			<!-- 左侧列表区域 -->
-			<div class="list-area">
-				<fs-crud ref="crudRef" v-bind="crudBinding" @add-digital-human="openAddForm" @edit-digital-human="openEditForm">
-					<!-- 自定义行操作按钮 -->
-					<template #cell-rowHandle-middle="scope">
-						<!-- 查看按钮 -->
-						<el-button 
-							size="small" 
-							type="text" 
-							@click="openViewForm(scope.row)"
-						>
-							查看<el-icon><View /></el-icon>
-						</el-button>
-						
-						<!-- 编辑按钮 -->
-						<el-button 
-							size="small" 
-							type="text" 
-							@click="openEditForm(scope.row)"
-						>
-							编辑<el-icon><Edit /></el-icon>
-						</el-button>
-						
-						<!-- 删除按钮 -->
-						<el-button 
-							size="small" 
-							type="text" 
-							@click="handleDelete(scope.row)"
-							class="text-danger"
-						>
-							删除<el-icon><Delete /></el-icon>
-						</el-button>
-					</template>
-				</fs-crud>
-			</div>
-			
-			<!-- 将侧边栏表单改为弹窗 -->
-			<el-dialog
-				v-model="showForm"
-				:title="getDialogTitle()"
-				width="500px"
-				:close-on-click-modal="false"
-				:destroy-on-close="true"
-			>
-				<!-- 查看模式使用 el-descriptions 展示数据 -->
-				<el-descriptions v-if="formMode === 'view'" :column="1" border>
-					<el-descriptions-item label="名称">{{ formData.name }}</el-descriptions-item>
-					<el-descriptions-item label="头像">
-						<img v-if="formData.avatar_url" :src="formData.avatar_url" class="avatar" />
-						<span v-else>无头像</span>
-					</el-descriptions-item>
-					<el-descriptions-item label="模板视频">
-						<video 
-							v-if="formData.template_video_url" 
-							:src="formData.template_video_url" 
-							class="video-preview" 
-							controls>
-						</video>
-						<span v-else>无视频</span>
-					</el-descriptions-item>
-					<el-descriptions-item label="状态">
-						<el-tag :type="formData.status === 1 ? 'success' : 'info'">
-							{{ formData.status === 1 ? '启用' : '禁用' }}
-						</el-tag>
-					</el-descriptions-item>
-					<el-descriptions-item label="语音ID">
-						{{ getVoiceName(formData.config.voice_id) }}
-					</el-descriptions-item>
-					<el-descriptions-item label="风格">
-						{{ getStyleName(formData.config.style) }}
-					</el-descriptions-item>
-					<el-descriptions-item label="描述">{{ formData.description || '无' }}</el-descriptions-item>
-				</el-descriptions>
-
-				<!-- 添加/编辑表单内容 -->
-				<el-form v-else ref="formRef" :model="formData" label-width="100px" class="form-content">
-					<el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
-						<el-input v-model="formData.name" placeholder="请输入数字人名称"></el-input>
-					</el-form-item>
-					<el-form-item label="头像" prop="avatar_url" :rules="[{ required: true, message: '请上传头像', trigger: 'change' }]">
-						<el-upload
-							class="avatar-uploader"
-							:action="`${apiBaseUrl}/api/system/admin_upload/`"
-							:headers="{
-								'Authorization': `JWT ${Session.get('token') || ''}`
-							}"
-							:data="{
-								tenant_id: '1'
-							}"
-							:show-file-list="false"
-							:on-success="handleAvatarSuccess"
-							:before-upload="beforeAvatarUpload">
-							<img v-if="formData.avatar_url" :src="formData.avatar_url" class="avatar" />
-							<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
-						</el-upload>
-					</el-form-item>
-					<el-form-item label="模板视频" prop="template_video_url">
-						<el-upload
-							class="video-uploader"
-							:action="`${apiBaseUrl}/api/system/admin_upload/`"
-							:headers="{
-								'Authorization': `JWT ${Session.get('token') || ''}`
-							}"
-							:data="{
-								tenant_id: '1'
-							}"
-							:show-file-list="false"
-							:on-success="handleVideoSuccess"
-							:before-upload="beforeVideoUpload"
-							accept="video/*">
-							<video 
-								v-if="formData.template_video_url" 
-								:src="formData.template_video_url" 
-								class="video-preview" 
-								controls>
-							</video>
-							<div v-else class="video-uploader-placeholder">
-								<el-icon class="video-uploader-icon"><Plus /></el-icon>
-								<div>点击上传视频</div>
-							</div>
-						</el-upload>
-					</el-form-item>
-					<el-form-item label="状态" prop="status">
-						<el-switch v-model="formData.status" :active-value="1" :inactive-value="0"></el-switch>
-					</el-form-item>
-					
-					<el-form-item label="语音ID" prop="config.voice_id">
-						<el-select v-model="formData.config.voice_id" placeholder="请选择语音">
-							<el-option label="小明(高级)" value="xiaoming_voice_premium"></el-option>
-							<el-option label="小红" value="xiaohong_voice"></el-option>
-							<!-- 可以根据实际情况添加更多选项 -->
-						</el-select>
-					</el-form-item>
-					
-					<el-form-item label="风格" prop="config.style">
-						<el-select v-model="formData.config.style" placeholder="请选择风格">
-							<el-option label="专业" value="professional"></el-option>
-							<el-option label="友好" value="friendly"></el-option>
-							<el-option label="严肃" value="serious"></el-option>
-							<!-- 可以根据实际情况添加更多选项 -->
-						</el-select>
-					</el-form-item>
-					<el-form-item label="描述" prop="description">
-						<el-input v-model="formData.description" type="textarea" placeholder="请输入描述"></el-input>
-					</el-form-item>
-					
-				</el-form>
-				
-				<template #footer>
-					<div class="dialog-footer">
-						<el-button @click="closeForm">{{ formMode === 'view' ? '关闭' : '取消' }}</el-button>
-						<el-button v-if="formMode !== 'view'" type="primary" @click="submitForm">提交</el-button>
-					</div>
-				</template>
-			</el-dialog>
-		</div>
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="areas">
-import { onMounted, ref, reactive } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { GetPermission, AddObj, UpdateObj, DelObj } from './api';
-import * as api from './api';  // Import the full api module
-import { handleColumnPermission } from '/@/utils/columnPermission';
-import { ElMessage, ElMessageBox } from 'element-plus';
-import { Close, Plus, Edit, Delete, View } from '@element-plus/icons-vue';
-import { Session } from '/@/utils/storage';
-
-const apiBaseUrl = import.meta.env.VITE_API_URL;
-
-const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
-
-// 表单模式:add 或 edit
-const formMode = ref('add');
-const showForm = ref(false);
-const defaultFormData = {
-	name: '',
-	description: '',
-	avatar_url: '',
-	status: 1,
-	template_video_url: '',
-	config: {
-		voice_id: 'xiaoming_voice_premium',
-		style: 'professional'
-	}
-};
-
-const formData = ref({...defaultFormData});
-
-// Add formRef reference
-const formRef = ref();
-
-// 打开添加表单
-const openAddForm = () => {
-	formMode.value = 'add';
-	formData.value = {...defaultFormData};
-	showForm.value = true;
-};
-
-// 打开编辑表单
-const openEditForm = (row: any) => {
-	console.log('编辑行数据:', row);
-	formMode.value = 'edit';
-	// 深拷贝行数据
-	const rowData = JSON.parse(JSON.stringify(row));
-	
-	// 确保 config 是对象格式
-	if (typeof rowData.config === 'string') {
-		try {
-			rowData.config = JSON.parse(rowData.config);
-		} catch (e) {
-			console.error('解析config失败:', e);
-			rowData.config = {...defaultFormData.config};
-		}
-	} else if (!rowData.config) {
-		rowData.config = {...defaultFormData.config};
-	}
-	
-	formData.value = rowData;
-	showForm.value = true;
-};
-
-// 打开查看表单(只读模式)
-const openViewForm = (row: any) => {
-	console.log('查看行数据:', row);
-	formMode.value = 'view';
-	// 深拷贝行数据
-	const rowData = JSON.parse(JSON.stringify(row));
-	
-	// 确保 config 是对象格式
-	if (typeof rowData.config === 'string') {
-		try {
-			rowData.config = JSON.parse(rowData.config);
-		} catch (e) {
-			console.error('解析config失败:', e);
-			rowData.config = {...defaultFormData.config};
-		}
-	} else if (!rowData.config) {
-		rowData.config = {...defaultFormData.config};
-	}
-	
-	formData.value = rowData;
-	showForm.value = true;
-};
-
-// 关闭表单
-const closeForm = () => {
-	showForm.value = false;
-};
-
-// 头像上传成功处理
-const handleAvatarSuccess = (res: any, file: any) => {
-	// 根据实际接口返回格式调整
-	console.log(res);
-	formData.value.avatar_url = res.url;
-};
-
-// 头像上传前验证
-const beforeAvatarUpload = (file: any) => {
-	const isImage = file.type.startsWith('image/');
-	const isLt2M = file.size / 1024 / 1024 < 2;
-
-	if (!isImage) {
-		ElMessage.error('上传头像图片只能是图片格式!');
-	}
-	if (!isLt2M) {
-		ElMessage.error('上传头像图片大小不能超过 2MB!');
-	}
-	return isImage && isLt2M;
-};
-
-// 视频上传成功处理
-const handleVideoSuccess = (res: any, file: any) => {
-	// 根据实际接口返回格式调整
-	console.log(res);
-	formData.value.template_video_url = res.url;
-};
-
-// 视频上传前验证
-const beforeVideoUpload = (file: any) => {
-	const isVideo = file.type.startsWith('video/');
-	const isLt50M = file.size / 1024 / 1024 < 50;
-
-	if (!isVideo) {
-		ElMessage.error('请上传视频格式文件!');
-	}
-	if (!isLt50M) {
-		ElMessage.error('上传视频大小不能超过 50MB!');
-	}
-	return isVideo && isLt50M;
-};
-
-// 提交表单
-const submitForm = async () => {
-	try {
-		await formRef.value.validate();
-		
-		// 处理配置对象
-		const submitData = {...formData.value};
-		if (submitData.config && typeof submitData.config === 'object') {
-			// 确保 config 符合类型要求
-			const config = {
-				voice_id: submitData.config.voice_id || '',
-				style: submitData.config.style || ''
-			};
-			// 将config转为字符串
-			submitData.config = JSON.stringify(config) as any; // 使用类型断言解决类型错误
-		}
-		
-		// 在发送请求前,确保config是字符串类型
-		if (formMode.value === 'add') {
-			await AddObj(submitData);
-			ElMessage.success('添加成功');
-		} else {
-			await UpdateObj(submitData);
-			ElMessage.success('更新成功');
-		}
-		
-		closeForm();
-		crudExpose.doRefresh();
-	} catch (error) {
-		console.error('表单提交错误:', error);
-		ElMessage.error('提交失败,请检查表单信息');
-	}
-};
-
-// 处理删除
-const handleDelete = (row: any) => {
-	ElMessageBox.confirm(
-		`确定要删除数字人 "${row.name}" 吗?`,
-		'删除确认',
-		{
-			confirmButtonText: '确定',
-			cancelButtonText: '取消',
-			type: 'warning',
-		}
-	)
-		.then(async () => {
-			try {
-				await DelObj(row.id);
-				ElMessage.success('删除成功');
-				crudExpose.doRefresh();
-			} catch (error) {
-				console.error('删除失败:', error);
-				ElMessage.error('删除失败,请稍后重试');
-			}
-		})
-		.catch(() => {
-			// 用户取消删除操作
-		});
-};
-
-// Define the fetchData function
-const fetchData = () => {
-	crudExpose.doRefresh();
-};
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 设置列权限
-	const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	
-	// 配置新增按钮,点击时打开右侧表单而不是弹窗
-	if (newOptions.actionbar?.buttons?.add) {
-		newOptions.actionbar.buttons.add.click = () => {
-			openAddForm();
-		};
-	}
-	
-	//重置crudBinding
-	resetCrudOptions(newOptions);
-	// 刷新
-	crudExpose.doRefresh();
-});
-
-// 获取对话框标题
-const getDialogTitle = () => {
-	if (formMode.value === 'add') return '新增数字人';
-	if (formMode.value === 'edit') return '编辑数字人';
-	return '查看数字人';
-};
-
-// 语音ID映射
-const voiceOptions = [
-	{ label: '小明(高级)', value: 'xiaoming_voice_premium' },
-	{ label: '小红', value: 'xiaohong_voice' }
-];
-
-// 风格映射
-const styleOptions = [
-	{ label: '专业', value: 'professional' },
-	{ label: '友好', value: 'friendly' },
-	{ label: '严肃', value: 'serious' }
-];
-
-// 获取语音名称
-const getVoiceName = (voiceId: string) => {
-	const option = voiceOptions.find(item => item.value === voiceId);
-	return option ? option.label : voiceId;
-};
-
-// 获取风格名称
-const getStyleName = (style: string) => {
-	const option = styleOptions.find(item => item.value === style);
-	return option ? option.label : style;
-};
-</script>
-
-<style scoped>
-.digital-human-container {
-	display: flex;
-	height: 100%;
-}
-
-.list-area {
-	flex: 1;
-	overflow: auto;
-}
-
-/* 移除侧边栏相关样式,添加弹窗相关样式 */
-.avatar-uploader {
-	text-align: center;
-	border: 1px dashed #d9d9d9;
-	border-radius: 6px;
-	cursor: pointer;
-	position: relative;
-	overflow: hidden;
-	transition: border-color 0.3s;
-}
-
-.avatar-uploader:hover {
-	border-color: #409EFF;
-}
-
-.avatar-uploader-icon {
-	font-size: 28px;
-	color: #8c939d;
-	width: 100px;
-	height: 100px;
-	line-height: 100px;
-	text-align: center;
-}
-
-.avatar {
-	width: 100px;
-	height: 100px;
-	display: block;
-	object-fit: cover;
-}
-
-.dialog-footer {
-	text-align: right;
-}
-
-.video-uploader {
-	text-align: center;
-	border: 1px dashed #d9d9d9;
-	border-radius: 6px;
-	cursor: pointer;
-	position: relative;
-	overflow: hidden;
-	transition: border-color 0.3s;
-}
-
-.video-uploader:hover {
-	border-color: #409EFF;
-}
-
-.video-uploader-icon {
-	font-size: 28px;
-	color: #8c939d;
-	width: 100px;
-	height: 100px;
-	line-height: 100px;
-	text-align: center;
-}
-
-.video-preview {
-	width: 200px;
-	height: auto;
-	max-height: 150px;
-	display: block;
-	object-fit: contain;
-}
-
-.video-uploader-placeholder {
-	text-align: center;
-	padding: 20px;
-	border: 1px dashed #d9d9d9;
-	border-radius: 6px;
-}
-
-/* 添加删除按钮的危险样式 */
-.text-danger {
-	color: #F56C6C;
-}
-.text-danger:hover {
-	color: #f78989;
-}
-
-/* 添加查看模式的样式 */
-.el-descriptions {
-	margin-bottom: 20px;
-}
-</style>

+ 0 - 9
src/views/interface/index.ts

@@ -1,9 +0,0 @@
-/*
- *
- * 后端API接口响应数据
- */
-interface APIResponseData {
-	code?: number;
-	data: [];
-	msg?: string;
-}

+ 0 - 64
src/views/position/Jobcompetency/api.ts

@@ -1,64 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-
-export const apiPrefix = '/api/system/competency/list';
-export function GetList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/competency/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: apiPrefix + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: '/api/system/competency/create',
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: '/api/system/competency/update',
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function DelObj(data:any) {
-	return request({
-		url:`/api/system/competency/delete`,
-		method: 'post',
-		data: {...data,tenant_id:1},
-	});
-}
-export function GetPermission(query: UserPageQuery) {
-    return request({
-        url: '/api/system/competency/list',
-        method: 'get',
-        params: {...query,tenant_id:1},
-    });
-}
-
-export function GetcategoryList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/question_category/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-
-export function GetPositionList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/job/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}

+ 0 - 320
src/views/position/Jobcompetency/crud.tsx

@@ -1,320 +0,0 @@
-import * as api from './api';
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage } from '/@/utils/message';
-import { auth } from '/@/utils/authFunction';
-import tableSelector from '/@/components/tableSelector/index.vue';
-import { shallowRef } from 'vue';
-import { useRouter } from 'vue-router';
-
-export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter();
-	
-	const pageRequest = async (query: any) => {
-		try {
-			return await api.GetList(query);
-		} catch (error) {
-			console.error('Failed to fetch list:', error);
-			return { data: { items: [], total: 0 } }; // 返回空数据而不是抛出错误
-		}
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj({id:row.id});
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	/**
-	 * 懒加载
-	 * @param row
-	 * @returns {Promise<unknown>}
-	 */
-	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
-		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
-			resolve(res.data);
-		}).catch(error => {
-			console.error('Failed to load content:', error);
-			resolve([]); // 出错时返回空数组
-		});
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						show: true, // 允许添加标签类别
-					},
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 200,
-				buttons: {
-					view: {
-						show: false, // 移除查看按钮,因为标签类别不需要详情页
-					},
-					edit: {
-						iconRight: 'Edit',
-						type: 'text',
-						show: true, // 允许编辑标签类别
-					},
-					remove: {
-						iconRight: 'Delete',
-						type: 'text',
-						show: true, // 允许删除标签类别
-					},
-				},
-			},
-			pagination: {
-				show: true,
-			},
-			table: {
-				rowKey: 'id',
-			},
-			search: {
-				show: true,
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				}
-			},
-			columns: {
-				/* _index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				}, */
-				id: {
-					title: 'ID',
-					form: { show: false },
-					column: {
-						show: true,
-					},
-				},
-				name: {
-					title: '标签名称',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入标签名称',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '标签名称为必填项' },
-						],
-					},
-				},
-				positions: {
-					title: '关联职位',
-					type: 'dict-select',
-					column: {
-						minWidth: 150,
-						component: {
-							vModel: 'modelValue',
-							multiple: true,
-							// 自定义渲染组件,显示彩色标签
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								if (!row.positions || row.positions.length === 0) return <span>-</span>;
-								
-								return (
-									<div style="display: flex; flex-wrap: wrap; gap: 4px;">
-										{row.positions.map((tag: any) => (
-											<el-tag
-												key={tag.id}
-												type="warning"
-												effect="plain"
-												size="mini"
-											>
-												{tag.title}
-											</el-tag>
-										))}
-									</div>
-								);
-							}
-						},
-					},
-					dict: dict({
-						getData: async () => {
-							const res = await api.GetPositionList({page:1,limit:1000,tenant_id:1});
-							return res.data || [];
-						},
-						label: 'title',
-						value: 'id'
-					}),
-					form: {
-						component: {
-							multiple: true,
-							filterable: true,
-							placeholder: '请选择关联职位',
-						},
-						/* rules: [
-							{ required: true, message: '关联职位为必填项' },
-						], */
-					},
-				},
-				/* icon: {
-					title: '图标',
-					type: 'input',
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						component: {
-							placeholder: '请输入图标名称',
-						},
-					},
-				},
-				color: {
-					title: '标签颜色',
-					type: 'colorpicker',
-					column: {
-						minWidth: 100,
-						component: {
-							name: 'fs-color-block',
-						},
-					},
-					form: {
-						value: '#52c41a', // 默认颜色
-					},
-				}, */
-				sort: {
-					title: '排序',
-					type: 'number',
-					column: {
-						minWidth: 80,
-					},
-					form: {
-						value: 1, // 默认排序值
-					},
-				},
-				
-				/* questions_count: {
-					title: '问题数量',
-					type: 'number',
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动计算
-					},
-				}, */
-				create_datetime: {
-					title: '创建时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动生成
-					},
-				},
-				update_datetime: {
-					title: '更新时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动更新
-					},
-				},
-				is_general: {
-					title: '通用胜任力',
-					type: 'dict-select',
-					column: {
-						minWidth: 90,
-						component:{
-							render: ({ row }: { row: any }) => {
-								return <span>{row.is_general ? '通用' : '非通用'}</span>;
-							}
-						}
-					},
-					dict: dict({
-						data: [
-							{ value: true, label: '通用' },
-							{ value: false, label: '非通用' },
-						],
-					}),
-					form: {
-						value: true, // 默认启用
-						component: {
-							name: 'el-switch',
-						},
-					},
-				},
-				description: {
-					title: '描述',
-					search: {
-						show: false,
-						component: {
-							placeholder: '请输入描述',
-						},
-					},
-					type: 'textarea',
-					column: {
-						minWidth: 150,
-						showOverflowTooltip: true,
-					},
-					form: {
-						component: {
-							span: 24,
-							placeholder: '请输入标签描述',
-						},
-					},
-				},
-			},
-		},
-	};
-};

+ 0 - 31
src/views/position/Jobcompetency/index.vue

@@ -1,31 +0,0 @@
-<template>
-	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="areas">
-import { onMounted } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { GetPermission } from './api';
-import { handleColumnPermission } from '/@/utils/columnPermission';
-
-const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	try {
-		// 设置列权限
-		const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-		// 重置crudBinding
-		resetCrudOptions(newOptions);
-		// 刷新
-		crudExpose.doRefresh();
-	} catch (error) {
-		console.error('Failed to get permissions:', error);
-		// 即使权限获取失败,也允许页面加载
-		crudExpose.doRefresh();
-	}
-});
-</script>

+ 0 - 624
src/views/position/create/index.vue

@@ -1,624 +0,0 @@
-<template>
-  <div class="position-create-container">
-    <div class="header-container">
-      <page-header title="新增职位" @back="goBack" />
-    <!--   <div class="header-right">应用至人力资源-对外</div> -->
-    </div>
-    
-    <el-card class="form-card">
-      <div class="section-title">
-        <div class="section-line"></div>
-        <span>职位详情</span>
-      </div>
-      
-      <el-form 
-        ref="formRef" 
-        :model="formData" 
-        :rules="rules" 
-        label-width="120px"
-        label-position="right"
-      >
-        <el-form-item label="职位名称" prop="title">
-          <el-input v-model="formData.title" placeholder="请输入职位名称" maxlength="20" show-word-limit />
-        </el-form-item>
-        
-        <el-form-item label="职位类别" prop="job_category">
-          <el-select v-model="formData.job_category" placeholder="请选择职位类别">
-            <el-option label="技术" value="技术" />
-            <el-option label="产品" value="产品" />
-            <el-option label="设计" value="设计" />
-            <el-option label="运营" value="运营" />
-            <el-option label="市场" value="市场" />
-            <el-option label="销售" value="销售" />
-            <el-option label="行政" value="行政" />
-            <el-option label="财务" value="财务" />
-            <el-option label="人力资源" value="人力资源" />
-            <el-option label="法务" value="法务" />
-            <el-option label="其他" value="其他" />
-          </el-select>
-        </el-form-item>
-        
-        <el-form-item label="职位性质" prop="job_type_display">
-          <el-select v-model="formData.job_type_display" placeholder="请选择职位性质">
-            <el-option label="全职" value="全职" />
-            <el-option label="兼职" value="兼职" />
-            <el-option label="实习" value="实习" />
-            <el-option label="其他" value="其他" />
-          </el-select>
-        </el-form-item>
-        
-       <!--  <el-form-item label="工作城市" prop="city">
-          <el-select v-model="formData.city" placeholder="请选择工作城市">
-            <el-option label="北京" value="北京" />
-            <el-option label="上海" value="上海" />
-            <el-option label="广州" value="广州" />
-            <el-option label="深圳" value="深圳" />
-            <el-option label="杭州" value="杭州" />
-            <el-option label="成都" value="成都" />
-            <el-option label="武汉" value="武汉" />
-            <el-option label="西安" value="西安" />
-            <el-option label="南京" value="南京" />
-            <el-option label="其他" value="其他" />
-          </el-select>
-        </el-form-item> -->
-        
-        <el-form-item label="工作地点" prop="location">
-          <el-cascader
-            v-model="formData.location"
-            :options="locationOptions"
-            :props="{ 
-              expandTrigger: 'hover',
-              checkStrictly: false
-            }"
-            style="width: 100%"
-            placeholder="请选择工作地点"
-            clearable
-            @change="handleLocationChange"
-          />
-        </el-form-item>
-        
-        <el-form-item label="门牌号码" prop="address_detail">
-          <el-input v-model="formData.address_detail" placeholder="请输入详细地址" />
-        </el-form-item>
-        
-        <el-form-item label="福利待遇" prop="benefits">
-          <el-select v-model="formData.benefits" placeholder="请选择福利待遇" multiple>
-            <el-option label="五险一金" value="五险一金" />
-            <el-option label="年终奖" value="年终奖" />
-            <el-option label="带薪年假" value="带薪年假" />
-            <el-option label="定期体检" value="定期体检" />
-            <el-option label="节日福利" value="节日福利" />
-            <el-option label="员工旅游" value="员工旅游" />
-            <el-option label="弹性工作" value="弹性工作" />
-            <el-option label="免费班车" value="免费班车" />
-            <el-option label="餐补" value="餐补" />
-            <el-option label="交通补贴" value="交通补贴" />
-          </el-select>
-        </el-form-item>
-        
-        <el-form-item label="职位要求" prop="requirements">
-          <div class="editor-container">
-            <div ref="quillEditor" class="quill-editor"></div>
-          </div>
-        </el-form-item>
-        
-        <!-- 添加职位描述字段 -->
-        <el-form-item label="职位描述" prop="description">
-          <div class="editor-container">
-            <div ref="descriptionEditor" class="quill-editor"></div>
-          </div>
-        </el-form-item>
-        
-        <!-- 添加职位薪资字段 -->
-        <el-form-item label="职位薪资" prop="salary_range">
-          <div class="salary-range-container">
-            <el-select v-model="formData.salary_type" placeholder="薪资类型" class="salary-type-select">
-              <el-option label="月薪" value="月薪" />
-              <el-option label="年薪" value="年薪" />
-              <el-option label="日薪" value="日薪" />
-              <el-option label="时薪" value="时薪" />
-            </el-select>
-            <el-input v-model="formData.start_salary" placeholder="起始薪资" class="salary-input" />
-            <span class="salary-separator">-</span>
-            <el-input v-model="formData.end_salary" placeholder="最高薪资" class="salary-input" />
-          </div>
-        </el-form-item>
-        
-        <!-- 添加职位条件字段 -->
-        <el-form-item label="职位条件">
-          <div class="job-conditions-container">
-            <el-select v-model="formData.work_experience_required" placeholder="工作经验" class="condition-select">
-              <el-option label="不限" value="不限" />
-              <el-option label="应届毕业生" value="应届毕业生" />
-              <el-option label="1年以下" value="1年以下" />
-              <el-option label="1-3年" value="1-3年" />
-              <el-option label="3-5年" value="3-5年" />
-              <el-option label="5-10年" value="5-10年" />
-              <el-option label="10年以上" value="10年以上" />
-            </el-select>
-            <el-select v-model="formData.education_required" placeholder="学历要求" class="condition-select">
-              <el-option label="不限" value="不限" />
-              <el-option label="高中" value="高中" />
-              <el-option label="大专" value="大专" />
-              <el-option label="本科" value="本科" />
-              <el-option label="硕士" value="硕士" />
-              <el-option label="博士" value="博士" />
-            </el-select>
-          </div>
-        </el-form-item>
-        
-        <el-form-item label="胜任力标签" prop="competency_tags">
-          <el-select
-            v-model="formData.competency_tags"
-            multiple
-            placeholder="请选择胜任力标签"
-            style="width: 100%"
-          >
-            <el-option
-              v-for="tag in competencyTags"
-              :key="tag.id"
-              :label="tag.name"
-              :value="tag.id"
-            >
-            </el-option>
-          </el-select>
-        </el-form-item>
-        
-        <el-form-item label="所属部门" prop="department">
-          <el-input v-model="formData.department" placeholder="请输入所属部门" />
-        </el-form-item>
-        
-        <el-form-item label="职位状态" prop="job_type">
-          <el-select v-model="formData.job_type" placeholder="请选择职位状态">
-            <el-option label="未发布" :value="0" />
-            <el-option label="已发布" :value="1" />
-            <el-option label="已结束" :value="2" />
-          </el-select>
-        </el-form-item>
-        
-        <el-form-item label="截止日期" prop="end_date">
-          <el-date-picker
-            v-model="formData.end_date"
-            type="datetime"
-            placeholder="请选择截止日期"
-            value-format="YYYY-MM-DD HH:mm:ss"
-          />
-        </el-form-item>
-        
-        <el-form-item>
-          <el-button type="primary" @click="submitForm">保存</el-button>
-          <el-button @click="goBack">取消</el-button>
-        </el-form-item>
-      </el-form>
-    </el-card>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
-import { useRouter } from 'vue-router';
-import { ElMessage } from 'element-plus';
-import * as api from '../list/api';
-import type { FormInstance } from 'element-plus';
-import Quill from 'quill';
-import 'quill/dist/quill.snow.css'; // 引入样式
-import axios from 'axios';
-
-const router = useRouter();
-const formRef = ref<FormInstance>();
-
-// 添加胜任力标签接口定义
-interface CompetencyTag {
-  id: number;
-  name: string;
-  description: string;
-}
-
-// 在 PositionFormData 接口中添加胜任力字段
-interface PositionFormData {
-  title: string;
-  job_category: string;
-  job_type_display: string;
-  city: string;
-  location: string[];
-  address_detail: string;
-  benefits: string[];
-  requirements: string;
-  job_type?: number;
-  salary_type: string;
-  start_salary: string;
-  end_salary: string;
-  work_experience_required: string;
-  education_required: string;
-  salary_range?: string;
-  department?: string;
-  end_date?: string;
-  description?: string;
-  province: string;
-  district: string;
-  location_str?: string;
-  status: number;
-  competency_tags: number[]; // 存储选中的胜任力标签ID
-}
-
-// 表单数据
-const formData = reactive<PositionFormData>({
-  title: '',
-  job_category: '',
-  job_type_display: '',
-  city: '',
-  location: [],
-  address_detail: '',
-  benefits: [],
-  requirements: '',
-  job_type: 0,
-  salary_type: '月薪',
-  start_salary: '',
-  end_salary: '',
-  work_experience_required: '',
-  education_required: '',
-  salary_range: '',
-  department: '',
-  end_date: '',
-  description: '',
-  province: '',
-  district: '',
-  location_str: '',
-  status: 0,
-  competency_tags: [],
-});
-
-// 表单验证规则
-const rules = {
-  title: [{ required: true, message: '职位名称必填', trigger: 'blur' }],
-  job_category: [{ required: true, message: '职位类别必填', trigger: 'change' }],
-  job_type_display: [{ required: true, message: '职位性质必填', trigger: 'change' }],
-  location: [{ required: true, message: '工作地点必填', trigger: 'change' }],
-  department: [{ required: true, message: '所属部门必填', trigger: 'blur' }],
-  job_type: [{ required: true, message: '职位状态必填', trigger: 'change' }],
-  end_date: [{ required: true, message: '截止日期必填', trigger: 'change' }],
-  requirements: [{ required: true, message: '职位要求必填', trigger: 'blur' }],
-  description: [{ required: true, message: '职位描述必填', trigger: 'blur' }],
-  salary_range: [{ required: true, message: '薪资范围必填', trigger: 'blur' }],
-  competency_tags: [
-    { required: true, message: '请选择胜任力标签', trigger: 'change' }
-  ],
-};
-
-// 添加对描述编辑器的引用
-const quillEditor = ref<HTMLElement | null>(null);
-const descriptionEditor = ref<HTMLElement | null>(null);
-let editor: any = null;
-let descEditor: any = null;
-
-// 工作地点选项
-const locationOptions = [
-  {
-    value: '上海市',
-    label: '上海市',
-    children: [
-      {
-        value: '浦东新区',
-        label: '浦东新区',
-        children: [
-          { value: '张江', label: '张江' },
-          { value: '金桥', label: '金桥' },
-          { value: '陆家嘴', label: '陆家嘴' }
-        ]
-      },
-      {
-        value: '徐汇区',
-        label: '徐汇区',
-        children: [
-          { value: '漕河泾', label: '漕河泾' },
-          { value: '徐家汇', label: '徐家汇' }
-        ]
-      },
-    ]
-  },
-  {
-    value: '北京市',
-    label: '北京市',
-    children: [
-      {
-        value: '海淀区',
-        label: '海淀区',
-        children: [
-          { value: '中关村', label: '中关村' },
-          { value: '上地', label: '上地' }
-        ]
-      },
-      {
-        value: '朝阳区',
-        label: '朝阳区',
-        children: [
-          { value: 'CBD', label: 'CBD' },
-          { value: '望京', label: '望京' }
-        ]
-      },
-    ]
-  },
-];
-
-// 处理地点选择变化
-const handleLocationChange = (value: any) => {
-  console.log('地址选择变化:', value);
-  if (value && Array.isArray(value) && value.length > 0) {
-    formData.province = value[0];
-    formData.city = value[1] || '';
-    formData.district = value[2] || '';
-  }
-};
-
-// 在 setup 中添加胜任力相关的响应式数据
-const competencyTags = ref<CompetencyTag[]>([]);
-const competencyDescriptions = ref<Record<number, string>>({});
-
-// 添加获取胜任力标签的方法
-const fetchCompetencyTags = async () => {
-  try {
-      const response = await api.GetCompetencyList({page:1,pageSize:1000});
-    console.log('response', response);
-    competencyTags.value = response.data.items;
-    // 构建描述映射
-   /*  competencyTags.value.forEach(tag => {
-      competencyDescriptions.value[tag.id] = tag.description;
-    }); */
-  } catch (error) {
-    console.error('获取胜任力标签失败:', error);
-    ElMessage.error('获取胜任力标签失败');
-  }
-};
-
-onMounted(() => {
-  // 初始化职位要求编辑器
-  if (quillEditor.value) {
-    editor = new Quill(quillEditor.value, {
-      theme: 'snow',
-      modules: {
-        toolbar: [
-          ['bold', 'italic', 'underline', 'strike'],
-          ['blockquote', 'code-block'],
-          [{ 'header': 1 }, { 'header': 2 }],
-          [{ 'list': 'ordered' }, { 'list': 'bullet' }],
-          [{ 'script': 'sub' }, { 'script': 'super' }],
-          [{ 'indent': '-1' }, { 'indent': '+1' }],
-          [{ 'direction': 'rtl' }],
-          [{ 'size': ['small', false, 'large', 'huge'] }],
-          [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
-          [{ 'color': [] }, { 'background': [] }],
-          [{ 'font': [] }],
-          [{ 'align': [] }],
-          ['clean']
-        ]
-      },
-      placeholder: '请输入职位要求'
-    });
-    
-    // 设置初始内容
-    if (formData.requirements) {
-      editor.clipboard.dangerouslyPasteHTML(formData.requirements);
-    }
-    
-    // 监听内容变化
-    editor.on('text-change', () => {
-      formData.requirements = editor.root.innerHTML;
-    });
-  }
-  
-  // 初始化职位描述编辑器
-  if (descriptionEditor.value) {
-    descEditor = new Quill(descriptionEditor.value, {
-      theme: 'snow',
-      modules: {
-        toolbar: [
-          ['bold', 'italic', 'underline', 'strike'],
-          ['blockquote', 'code-block'],
-          [{ 'header': 1 }, { 'header': 2 }],
-          [{ 'list': 'ordered' }, { 'list': 'bullet' }],
-          [{ 'script': 'sub' }, { 'script': 'super' }],
-          [{ 'indent': '-1' }, { 'indent': '+1' }],
-          [{ 'direction': 'rtl' }],
-          [{ 'size': ['small', false, 'large', 'huge'] }],
-          [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
-          [{ 'color': [] }, { 'background': [] }],
-          [{ 'font': [] }],
-          [{ 'align': [] }],
-          ['clean']
-        ]
-      },
-      placeholder: '请输入职位描述'
-    });
-    
-    // 设置初始内容
-    if (formData.description) {
-      descEditor.clipboard.dangerouslyPasteHTML(formData.description);
-    }
-    
-    // 监听内容变化
-    descEditor.on('text-change', () => {
-      formData.description = descEditor.root.innerHTML;
-    });
-  }
-  
-  // 在 onMounted 中调用获取胜任力标签
-  fetchCompetencyTags();
-});
-
-onBeforeUnmount(() => {
-  editor = null;
-  descEditor = null;
-});
-
-// 返回列表页
-const goBack = () => {
-  router.push('/position/list');
-};
-
-// 提交表单
-const submitForm = async () => {
-  if (!formRef.value) return;
-  
-  // 确保获取最新内容
-  if (editor) {
-    formData.requirements = editor.root.innerHTML;
-  }
-  
-  if (descEditor) {
-    formData.description = descEditor.root.innerHTML;
-  }
-  
-  // 在验证前设置薪资范围
-  if (formData.salary_type && formData.start_salary && formData.end_salary) {
-    formData.salary_range = `${formData.salary_type} ${formData.start_salary}-${formData.end_salary}`;
-  }
-  
-  await formRef.value.validate(async (valid: boolean, fields: any) => {
-    if (valid) {
-      try {
-        const formToSubmit = { ...formData };
-        
-        // 处理地址信息
-        if (Array.isArray(formToSubmit.location) && formToSubmit.location.length > 0) {
-          formToSubmit.province = formToSubmit.location[0];
-          formToSubmit.city = formToSubmit.location[1] || '';
-          formToSubmit.district = formToSubmit.location[2] || '';
-          // 使用location_str字段存储字符串形式的位置
-          formToSubmit.location_str = formToSubmit.location.join(' ');
-        }
-        
-        // 处理胜任力标签数据
-        const apiData = {
-          ...formToSubmit,
-          competency_tags: formToSubmit.competency_tags,
-          // ... other fields
-        };
-        
-        const response = await api.AddObj(apiData);
-        ElMessage.success('职位添加成功');
-        router.push(`/position/detail?id=${response.data.id}`);
-      } catch (error) {
-        console.error('保存失败:', error);
-        ElMessage.error('保存失败,请重试');
-      }
-    } else {
-      console.log('验证失败:', fields);
-    }
-  });
-};
-</script>
-
-<style scoped>
-.position-create-container {
-  padding: 20px;
-}
-
-.form-card {
-  margin: 0 auto;
-  margin-top: 20px;
-  width: 40%;
-
-}
-
-.section-title {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-  font-weight: bold;
-  color: #333;
-}
-
-.section-line {
-  width: 4px;
-  height: 16px;
-  background-color: #67C23A;
-  margin-right: 8px;
-}
-
-.editor-container {
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-}
-
-.editor-toolbar {
-  display: flex;
-  padding: 8px;
-  border-bottom: 1px solid #dcdfe6;
-  background-color: #f5f7fa;
-}
-
-.toolbar-btn {
-  margin-right: 5px;
-  padding: 4px 8px;
-  background: none;
-  border: none;
-  cursor: pointer;
-  border-radius: 3px;
-}
-
-.toolbar-btn:hover {
-  background-color: #e6e6e6;
-}
-
-.toolbar-btn.active {
-  background-color: #ffecd1;
-}
-
-.toolbar-select {
-  margin-right: 5px;
-  padding: 4px;
-  border: 1px solid #dcdfe6;
-  border-radius: 3px;
-}
-
-.header-container {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.header-right {
-  color: #909399;
-  font-size: 14px;
-}
-
-.editable-content {
-  min-height: 200px;
-  padding: 10px;
-  border: 1px solid #dcdfe6;
-  border-top: none;
-  overflow-y: auto;
-  background-color: #fff;
-}
-
-.quill-editor {
-  height: 300px;
-}
-
-.salary-range-container {
-  display: flex;
-  align-items: center;
-}
-
-.salary-type-select {
-  width: 100px;
-  margin-right: 10px;
-}
-
-.salary-input {
-  width: 120px;
-}
-
-.salary-separator {
-  margin: 0 10px;
-}
-
-.job-conditions-container {
-  display: flex;
-  gap: 20px;
-}
-
-.condition-select {
-  width: 200px;
-}
-</style> 

+ 0 - 3832
src/views/position/detail/index.vue

@@ -1,3832 +0,0 @@
-<template>
-  <div class="position-detail-container">
-    <div class="header-container">
-      <page-header title="测试" @back="goBack" />
-      <div class="header-actions">
-        <el-button type="primary" @click="handleEdit">编辑</el-button>
-        <el-button @click="handleShare">分享职位</el-button>
-      </div>
-    </div>
-    
-    <div class="detail-content">
-      <!-- 左侧详情信息 -->
-      <el-card class="detail-card">
-        <div class="section-title">
-          <div class="section-line"></div>
-          <span>职位详情</span>
-        </div>
-        
-        <div class="detail-item">
-          <div class="detail-label">职位名称</div>
-          <div class="detail-value" v-if="!isEditingTitle">
-            {{ positionData.title }}
-            <el-button type="text" class="edit-title-btn" @click="startEditTitle">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-input 
-              v-model="editingTitle" 
-              placeholder="请输入职位名称" 
-              maxlength="20"
-              show-word-limit
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditTitle">取消</el-button>
-              <el-button size="small" type="primary" @click="saveTitle">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 工作地点 -->
-        <div class="detail-item">
-          <div class="detail-label">工作地点</div>
-          <div class="detail-value" v-if="!isEditingLocation">
-            {{ positionData.location || '暂无' }}
-            <el-button type="text" class="edit-title-btn" @click="startEditLocation">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-input 
-              v-model="editingLocation" 
-              placeholder="请输入工作地点" 
-              maxlength="50"
-              show-word-limit
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditLocation">取消</el-button>
-              <el-button size="small" type="primary" @click="saveLocation">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 职位薪资 -->
-        <div class="detail-item">
-          <div class="detail-label">职位薪资</div>
-          <div class="detail-value" v-if="!isEditingSalary">
-            {{ positionData.salary_range || '暂无' }}
-            <el-button type="text" class="edit-title-btn" @click="startEditSalary">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-input 
-              v-model="editingSalary" 
-              placeholder="请输入职位薪资" 
-              maxlength="30"
-              show-word-limit
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditSalary">取消</el-button>
-              <el-button size="small" type="primary" @click="saveSalary">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 工作经验 -->
-        <div class="detail-item">
-          <div class="detail-label">工作经验</div>
-          <div class="detail-value" v-if="!isEditingExperience">
-            {{ positionData.work_experience_required || '暂无' }}
-            <el-button type="text" class="edit-title-btn" @click="startEditExperience">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-select v-model="editingExperience" placeholder="请选择工作经验" style="width: 100%">
-              <el-option label="不限" value="不限" />
-              <el-option label="应届毕业生" value="应届毕业生" />
-              <el-option label="1年以下" value="1年以下" />
-              <el-option label="1-3年" value="1-3年" />
-              <el-option label="3-5年" value="3-5年" />
-              <el-option label="5-10年" value="5-10年" />
-              <el-option label="10年以上" value="10年以上" />
-            </el-select>
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditExperience">取消</el-button>
-              <el-button size="small" type="primary" @click="saveExperience">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 学历要求 -->
-        <div class="detail-item">
-          <div class="detail-label">学历要求</div>
-          <div class="detail-value" v-if="!isEditingEducation">
-            {{ positionData.education_required || '暂无' }}
-            <el-button type="text" class="edit-title-btn" @click="startEditEducation">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-select v-model="editingEducation" placeholder="请选择学历要求" style="width: 100%">
-              <el-option label="不限" value="不限" />
-              <el-option label="高中" value="高中" />
-              <el-option label="大专" value="大专" />
-              <el-option label="本科" value="本科" />
-              <el-option label="硕士" value="硕士" />
-              <el-option label="博士" value="博士" />
-            </el-select>
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditEducation">取消</el-button>
-              <el-button size="small" type="primary" @click="saveEducation">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <!-- 所属部门 -->
-        <div class="detail-item">
-          <div class="detail-label">所属部门</div>
-          <div class="detail-value" v-if="!isEditingDepartment">
-            {{ positionData.department || '暂无' }}
-            <el-button type="text" class="edit-title-btn" @click="startEditDepartment">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-input 
-              v-model="editingDepartment" 
-              placeholder="请输入所属部门" 
-              maxlength="30"
-              show-word-limit
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditDepartment">取消</el-button>
-              <el-button size="small" type="primary" @click="saveDepartment">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <div class="detail-item">
-          <div class="detail-label">职位状态</div>
-          <div class="detail-value">
-            <el-tag :type="getStatusType(positionData.job_type)">
-              {{ getStatusText(positionData.job_type) }}
-            </el-tag>
-          </div>
-        </div>
-        
-        <div class="detail-item">
-          <div class="detail-label">截止日期</div>
-          <div class="detail-value" v-if="!isEditingEndDate">
-            {{ positionData.end_date || '暂无' }}
-            <el-button type="text" class="edit-title-btn" @click="startEditEndDate">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-date-picker
-              v-model="editingEndDate"
-              type="date"
-              placeholder="请选择截止日期"
-              format="YYYY-MM-DD"
-              value-format="YYYY-MM-DD"
-              style="width: 100%"
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditEndDate">取消</el-button>
-              <el-button size="small" type="primary" @click="saveEndDate">保存</el-button>
-            </div>
-          </div>
-        </div>
-           
-        <div class="detail-item">
-          <div class="detail-label">职位要求</div>
-          <div class="detail-value html-content" v-if="!isEditingRequirements" v-html="positionData.requirements"></div>
-          <el-button type="text" class="edit-title-btn" @click="startEditRequirements" v-if="!isEditingRequirements">
-            <el-icon><Edit /></el-icon>
-          </el-button>
-          <div class="title-edit-container" v-else>
-            <el-input
-              v-model="editingRequirements"
-              type="textarea"
-              rows="6"
-              placeholder="请输入职位要求"
-              maxlength="1000"
-              show-word-limit
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditRequirements">取消</el-button>
-              <el-button size="small" type="primary" @click="saveRequirements">保存</el-button>
-            </div>
-          </div>
-        </div>
-        
-        <div class="detail-item">
-          <div class="detail-label">职位描述</div>
-          <div class="detail-value html-content" v-if="!isEditingDescription" v-html="positionData.description"></div>
-          <el-button type="text" class="edit-title-btn" @click="startEditDescription" v-if="!isEditingDescription">
-            <el-icon><Edit /></el-icon>
-          </el-button>
-          <div class="title-edit-container" v-else>
-            <el-input
-              v-model="editingDescription"
-              type="textarea"
-              rows="6"
-              placeholder="请输入职位描述"
-              maxlength="1000"
-              show-word-limit
-            />
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditDescription">取消</el-button>
-              <el-button size="small" type="primary" @click="saveDescription">保存</el-button>
-            </div>
-          </div>
-        </div>
-
-        <!-- 在 detail-item 列表中添加能力标签部分 -->
-        <div class="detail-item">
-          <div class="detail-label">能力标签</div>
-          <div class="detail-value" v-if="!isEditingCompetency">
-            <el-tag
-              v-for="tag in positionData.competency_tags"
-              :key="tag.id"
-              effect="plain"
-              class="competency-tag"
-            >
-              {{ tag.name }}
-            </el-tag>
-            <el-button type="text" class="edit-title-btn" @click="startEditCompetency">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-          </div>
-          <div class="title-edit-container" v-else>
-            <el-select
-              v-model="selectedCompetencies"
-              multiple
-              filterable
-              placeholder="请选择能力标签"
-              style="width: 100%"
-            
-            >
-              <el-option
-                v-for="tag in competencyTags"
-                :key="tag.id"
-                :label="tag.name"
-                :value="tag.id"
-              >
-              </el-option>
-            </el-select>
-            <div class="title-edit-actions">
-              <el-button size="small" @click="cancelEditCompetency">取消</el-button>
-              <el-button size="small" type="primary" @click="saveCompetency">保存</el-button>
-            </div>
-          </div>
-        </div>
-      </el-card>
-      
-      <!-- 右侧状态信息 -->
-      <div class="status-cards">
-        <el-card class="status-card">
-          <div class="section-title">
-            <div class="section-line"></div>
-            <span>职位状态</span>
-          </div>
-          <div class="status-switch">
-            <el-switch
-              v-model="positionStatus"
-              inactive-text=""
-              @change="handleStatusChange"
-            />
-            <span class="status-text">{{ positionStatus ? '开启职位' : '关闭职位' }}</span>
-          </div>
-        </el-card>
-        
-        <el-card class="status-card">
-          <div class="section-title">
-            <div class="section-line"></div>
-            <span>职位用途</span>
-            <el-button type="text" class="edit-btn" @click="handleEditUsage">编辑</el-button>
-          </div>
-          <div class="usage-content">
-            <div class="usage-label">职位用途</div>
-            <div class="usage-value">正式招聘</div>
-          </div>
-        </el-card>
-        
-        <el-card class="status-card">
-          <div class="section-title">
-            <div class="section-line"></div>
-            <span>招聘流程</span>
-            <el-button type="text" class="edit-btn" @click="handleEditProcess">设置流程</el-button>
-          </div>
-          <div class="process-steps">
-            <div 
-              v-for="(step, index) in recruitmentProcess" 
-              :key="step.id" 
-              class="process-step" 
-              :class="{ 'disabled': !step.active }"
-            >
-              <div class="step-number">{{ index + 1 }}</div>
-              <div class="step-content">
-                <div class="step-title">{{ step.name }}</div>
-                <div class="step-desc">{{ step.description }}</div>
-              </div>
-            </div>
-          </div>
-        </el-card>
-      </div>
-    </div>
-
-    <!-- 添加招聘流程编辑对话框 -->
-    <el-dialog
-      title="设置招聘流程"
-      v-model="showProcessDialog"
-      width="500px"
-      :close-on-click-modal="false"
-    >
-      <div class="process-dialog-content">
-        <div v-for="(step, index) in recruitmentProcess" :key="step.id">
-          <!-- 添加在每个步骤上方的加号按钮 -->
-          <div class="add-process-btn-top" v-if="index === 0 || true">
-            <el-button type="text" @click="showStepOptions(index, $event)">
-              <el-icon><Plus /></el-icon>
-            </el-button>
-          </div>
-          
-          <div 
-            class="process-edit-item"
-            :class="{ 'highlight': step.active }"
-          >
-            <div class="process-number">{{ index + 1 }}</div>
-            <div class="process-edit-content">
-              <div>{{ step.name }}</div>
-              <div class="process-edit-desc">{{ step.description }}</div>
-            </div>
-            <el-button 
-              type="danger" 
-              size="small" 
-              plain 
-              class="delete-btn"
-              @click="deleteProcessStep(index)"
-            >
-              删除
-            </el-button>
-          </div>
-        </div>
-        
-        <!-- 最后一个步骤下方的加号按钮 -->
-        <div class="add-process-btn">
-          <el-button type="text" @click="showStepOptions(recruitmentProcess.length, $event)">
-            <el-icon><Plus /></el-icon>
-          </el-button>
-        </div>
-      </div>
-      
-      <!-- 添加选项菜单 -->
-      <div 
-        v-if="showOptionsMenu" 
-        class="process-options-menu"
-        :style="{ top: menuPosition.top, left: menuPosition.left }"
-        @click.stop
-      >
-        <div 
-          v-for="option in processStepOptions" 
-          :key="option.value"
-          class="process-option-item"
-          @click="addSelectedStepType(option.value, option.label)"
-        >
-          {{ option.label }}
-        </div>
-      </div>
-      
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelEditProcess">取消</el-button>
-          <el-button type="primary" @click="saveProcess">确定</el-button>
-        </span>
-      </template>
-    </el-dialog>
-    
-    <!-- AI视频配置对话框 -->
-    <el-drawer
-      title="AI视频"
-      v-model="showAIVideoDialog"
-      direction="rtl"
-      size="60%"
-      :close-on-click-modal="false"
-      class="ai-video-drawer"
-      :destroy-on-close="false"
-    >
-      <!-- 步骤指示器 -->
-      <div class="ai-video-steps">
-        <div class="step-item" :class="{ 'active': currentStep === 1 }" @click="jumpToStep(1)">
-          <div class="step-number">1</div>
-          <div class="step-title">考察能力</div>
-        </div>
-        <div class="step-line" :class="{ 'active-line': currentStep >= 2 }"></div>
-        <div class="step-item" :class="{ 'active': currentStep === 2 }" @click="jumpToStep(2)">
-          <div class="step-number">2</div>
-          <div class="step-title">面试题目</div>
-        </div>
-        <div class="step-line" :class="{ 'active-line': currentStep >= 3 }"></div>
-        <div class="step-item" :class="{ 'active': currentStep === 3 }" @click="jumpToStep(3)">
-          <div class="step-number">3</div>
-          <div class="step-title">系统设置</div>
-        </div>
-      </div>
-
-      <!-- 步骤1:考察能力 -->
-      <div v-if="currentStep === 1" class="ai-video-content">
-        <div class="ai-video-options">
-          <p class="option-tip">请选择要优先使用的考察能力,也可以点击"下一步"直接出题</p>
-          
-          <div class="new-feature-tip">
-            <span class="new-tag">NEW</span>
-            AI根据岗位信息生成的能力,创建完步骤后,会自动将选择的新智能能力加入到该职位的考察能力列表中。
-          </div>
-          
-          <div class="ability-options">
-
-            <el-checkbox v-model="aiVideoOptions.qualityControl" label="质量控制">
-              <div class="ability-label">质量控制</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.teamwork" label="团队协作">
-              <div class="ability-label">团队协作</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.problemSolving" label="问题解决">
-              <div class="ability-label">问题解决</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.dataAnalysis" label="问题分析">
-              <div class="ability-label">问题分析</div>
-            </el-checkbox>
-            
-           <!--  <el-checkbox v-model="aiVideoOptions.taskManagement" label="分析问题">
-              <div class="ability-label">分析问题</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.productivity" label="生产效率">
-              <div class="ability-label">生产效率</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.manufacturingSkills" label="制造制作技能">
-              <div class="ability-label">制造制作技能</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.customerService" label="客户服务技能">
-              <div class="ability-label">客户服务技能</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.resistanceToPressure" label="抗压能力-客户服务">
-              <div class="ability-label">抗压能力-客户服务</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.workDefinition" label="工作稳定-宣誓">
-              <div class="ability-label">工作稳定-宣誓</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.selfIntroduction" label="自我介绍-宣誓">
-              <div class="ability-label">自我介绍-宣誓</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.detailOrientation" label="细节关注">
-              <div class="ability-label">细节关注</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.logicalThinking" label="清晰逻辑思维">
-              <div class="ability-label">清晰逻辑思维</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.safetyAwareness" label="安全意识">
-              <div class="ability-label">安全意识</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.customerServiceAwareness" label="客户服务意识">
-              <div class="ability-label">客户服务意识</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.timeManagement" label="时间管理">
-              <div class="ability-label">时间管理</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.teamCooperation" label="团队合作">
-              <div class="ability-label">团队合作</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.timeManagementSkills" label="时间管理能力">
-              <div class="ability-label">时间管理能力</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.productEvaluation" label="品鉴评估能力">
-              <div class="ability-label">品鉴评估能力</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.basicQualifications" label="基本素质">
-              <div class="ability-label">基本素质</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.networkDeployment" label="网位布署">
-              <div class="ability-label">网位布署</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.remoteCooperation" label="泡通协作">
-              <div class="ability-label">泡通协作</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.executionAbility" label="执行能力">
-              <div class="ability-label">执行能力</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.selfDiscipline" label="自驱力">
-              <div class="ability-label">自驱力</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.managementAbility" label="管理能力">
-              <div class="ability-label">管理能力</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.customerOrientation" label="客户导向">
-              <div class="ability-label">客户导向</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.stability" label="稳定性">
-              <div class="ability-label">稳定性</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.salesSkills" label="销售技巧">
-              <div class="ability-label">销售技巧</div>
-            </el-checkbox>
-            
-            <el-checkbox v-model="aiVideoOptions.responseAbility" label="应变能力">
-              <div class="ability-label">应变能力</div>
-            </el-checkbox> -->
-          </div>
-        </div>
-      </div>
-
-      <!-- 步骤2:面试题目 -->
-      <div v-if="currentStep === 2" class="ai-video-content">
-        <div class="interview-settings">
-          <div class="interview-header">
-            <div class="interview-duration">
-              <span>预计面试时长: </span>
-              <span class="duration-value">0秒</span>
-              <span class="duration-tip">拖动试题卡片可改变试题顺序</span>
-            </div>
-            
-            <div class="interview-actions">
-              <el-button type="primary" plain class="import-btn">
-                <el-icon><Document /></el-icon>
-                导入题目
-              </el-button>
-              <el-button type="success" plain class="custom-btn">
-                <el-icon><Plus /></el-icon>
-                自定义题目
-              </el-button>
-            </div>
-          </div>
-          
-          <div class="interview-mode">
-            <span>面试问答方式: </span>
-            <span class="mode-value">传统读题模式</span>
-            <el-button type="primary" plain size="small" class="customize-btn">定制面试间</el-button>
-          </div>
-          
-          <div class="interview-questions-container">
-            <div class="no-questions">
-              <p>暂无题目</p>
-              <p class="ai-tip">点击"AI自动组题"按钮</p>
-              <p class="ai-tip">可根据您选择的能力类型一键组题</p>
-              <el-button type="success" class="auto-generate-btn">AI自动组题</el-button>
-            </div>
-            
-            <div class="question-list">
-              <div v-for="(question, index) in chatQuestions" :key="index" class="question-card">
-                <div class="question-header">
-                  <div class="question-title">
-                    <span class="question-number">题目{{ index + 1 }}</span>
-                    <span>{{ question.title }}</span>
-                  </div>
-                  <div class="question-actions">
-                    <div class="weight-control">
-                      <span class="weight-label">权重占比:</span>
-                      <el-input-number 
-                        v-model="question.weight" 
-                        :min="0" 
-                        :max="100"
-                        :step="1"
-                        size="small"
-                        controls-position="right"
-                      >
-                        <template #suffix>%</template>
-                      </el-input-number>
-                    </div>
-                    <div class="answer-limit">
-                      <span class="answer-label">最多回答次数</span>
-                      <el-select v-model="question.maxAnswers" size="small" placeholder="请选择">
-                        <el-option label="1次" :value="1" />
-                        <el-option label="2次" :value="2" />
-                        <el-option label="3次" :value="3" />
-                      </el-select>
-                    </div>
-                    <el-button type="text" @click="editQuestion(index)">
-                      <el-icon><Edit /></el-icon>
-                    </el-button>
-                    <el-button type="text" @click="deleteQuestion(index)">
-                      <el-icon><Delete /></el-icon>
-                    </el-button>
-                  </div>
-                </div>
-                
-                <div class="question-info">
-                  <div class="info-item">
-                    <span class="info-label">考察能力:</span>
-                    <span>{{ question.ability }}</span>
-                  </div>
-                  <div class="info-item">
-                    <span class="info-label">面试内容:</span>
-                    <span>{{ question.content }}</span>
-                  </div>
-                  <div class="info-item">
-                    <span class="info-label">对话目标:</span>
-                    <span>{{ question.target }}</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-          
-          <div class="multi-dimension-questions">
-            <div class="dimension-title">多维度能力测评题</div>
-            <div class="dimension-list">
-              <div v-for="ability in selectedAbilities" :key="ability.value" class="dimension-item">
-                <div class="dimension-header">
-                  <span>{{ ability.label }}</span>
-                  <span class="new-tag" v-if="ability.isNew">NEW</span>
-                  <el-icon class="expand-icon"><ArrowDown /></el-icon>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 步骤3:系统设置 -->
-      <div v-if="currentStep === 3" class="ai-video-content">
-        <div class="system-settings">
-          <!-- 排名区间设置 -->
-          <div class="setting-section">
-            <div class="section-header">排名区间设置</div>
-            <div class="rank-settings">
-              <div class="rank-range">
-                <span>排名前</span>
-                <div class="range-inputs">
-                  <el-input v-model="rankSettings.min" size="small" class="percent-input">
-                    <template #suffix>%</template>
-                  </el-input>
-                  <span>-</span>
-                  <el-input v-model="rankSettings.max" size="small" class="percent-input">
-                    <template #suffix>%</template>
-                  </el-input>
-                </div>
-              </div>
-              
-              <div class="score-display">
-                <span>分数</span>
-                <div class="score-range">{{ rankSettings.minScore }} - {{ rankSettings.maxScore }} 分</div>
-              </div>
-            </div>
-            
-            <el-button type="primary" plain class="add-rank-btn">
-              <el-icon><Plus /></el-icon>
-              添加排名区间
-            </el-button>
-          </div>
-          
-          <!-- AI视频有效期 -->
-          <div class="setting-section">
-            <div class="section-header">AI视频有效期</div>
-            <div class="validity-setting">
-              <el-switch v-model="validityEnabled" />
-              <span class="validity-label">开启AI视频有效期</span>
-            </div>
-            <div class="validity-tip">暂未设置,设置后候选人超过有效期则不可进入</div>
-          </div>
-          
-          <!-- AI模型版本 -->
-          <div class="setting-section">
-            <div class="section-header">AI模型版本</div>
-            <el-select v-model="aiModelVersion" placeholder="请选择" class="model-select">
-              <el-option label="标准版" value="standard"></el-option>
-            </el-select>
-          </div>
-          
-          <!-- 评分AI -->
-          <div class="setting-section">
-            <div class="section-header">评分AI</div>
-            <!-- 评分AI设置内容 -->
-          </div>
-        </div>
-      </div>
-      
-      <template #footer>
-        <div class="drawer-footer">
-          <el-button v-if="currentStep > 1" @click="prevStep">上一步</el-button>
-          <el-button @click="cancelAIVideoConfig">取消</el-button>
-          <el-button v-if="currentStep < 3" type="primary" @click="nextStep">下一步</el-button>
-          <el-button v-else type="success" @click="finishConfig">新增</el-button>
-        </div>
-      </template>
-    </el-drawer>
-
-    <!-- 在 template 中添加视频宣讲弹窗 -->
-    <el-dialog
-      title="新增视频宣讲"
-      v-model="showVideoLectureDialog"
-      width="35%"
-      :close-on-click-modal="false"
-      class="video-lecture-dialog"
-    >
-      <div class="video-lecture-form">
-       <!--  <div class="form-item">
-          <div class="form-label">步骤名称</div>
-          <el-input 
-            v-model="videoLectureForm.title" 
-            placeholder="请输入步骤名称"
-            maxlength="30"
-            show-word-limit
-          />
-        </div> -->
-        
-        <div class="form-item">
-          <div class="form-label">上传视频</div>
-          <el-upload
-            class="video-uploader"
-            action="#"
-            :auto-upload="false"
-            :show-file-list="false"
-            accept="video/*"
-          >
-            <div class="upload-area">
-              <el-icon><Plus /></el-icon>
-              <div class="upload-text">上传文件大小不超过500M</div>
-            </div>
-          </el-upload>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">视频简介</div>
-          <el-input
-            v-model="videoLectureForm.description"
-            type="textarea"
-            rows="4"
-            placeholder="视频简介会显示在视频下方的标题下方"
-            maxlength="200"
-            show-word-limit
-          />
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">播放设置</div>
-          <el-radio-group v-model="videoLectureForm.broadcastType">
-            <el-radio label="required">必须看完</el-radio>
-            <el-radio label="optional">看过即可</el-radio>
-          </el-radio-group>
-        </div>
-      </div>
-      
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelVideoLecture">取消</el-button>
-          <el-button type="primary" @click="confirmVideoLecture">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- AI实时对话抽屉 -->
-    <el-drawer
-      title="AI实时对话"
-      v-model="showAIChatDialog"
-      direction="rtl"
-      size="60%"
-      :close-on-click-modal="false"
-      class="ai-chat-drawer"
-      :destroy-on-close="false"
-    >
-      <div class="ai-chat-content">
-        <!-- AI实时对话的配置内容 -->
-        <div class="chat-settings">
-          <div class="section-title">
-            <div class="section-line"></div>
-            <span>对话设置</span>
-          </div>
-          
-          <!-- 添加按钮组 -->
-          <div class="chat-actions">
-            <el-button type="primary" plain @click="handleAddQuestion">
-              <el-icon><Plus /></el-icon>
-              添加问题
-            </el-button>
-            <el-button type="success" plain @click="handleImportQuestion">
-              <el-icon><Document /></el-icon>
-              导入问题
-            </el-button>
-          </div>
-
-          <!-- 添加问题列表容器 -->
-          <div class="chat-questions">
-            <div v-if="chatQuestions.length === 0" class="empty-questions">
-              <p>暂无问题</p>
-              <p class="tip-text">点击"添加问题"或"导入问题"添加面试问题</p>
-            </div>
-            <div v-else class="question-list">
-              <div v-for="(question, index) in chatQuestions" :key="index" class="question-card">
-                <div class="question-header">
-                  <div class="question-title">
-                    <span class="question-number">题目{{ index + 1 }}</span>
-                    <span>{{ question.title }}</span>
-                  </div>
-                  <div class="question-actions">
-                    <div class="weight-control">
-                      <span class="weight-label">权重占比:</span>
-                      <el-input-number 
-                        v-model="question.weight" 
-                        :min="0" 
-                        :max="100"
-                        :step="1"
-                        size="small"
-                        controls-position="right"
-                      >
-                        <template #suffix>%</template>
-                      </el-input-number>
-                    </div>
-                    <div class="answer-limit">
-                      <span class="answer-label">最多回答次数</span>
-                      <el-select v-model="question.maxAnswers" size="small" placeholder="请选择">
-                        <el-option label="1次" :value="1" />
-                        <el-option label="2次" :value="2" />
-                        <el-option label="3次" :value="3" />
-                      </el-select>
-                    </div>
-                    <el-button type="text" @click="editQuestion(index)">
-                      <el-icon><Edit /></el-icon>
-                    </el-button>
-                    <el-button type="text" @click="deleteQuestion(index)">
-                      <el-icon><Delete /></el-icon>
-                    </el-button>
-                  </div>
-                </div>
-                
-                <div class="question-info">
-                  <div class="info-item">
-                    <span class="info-label">考察能力:</span>
-                    <span>{{ question.ability }}</span>
-                  </div>
-                  <div class="info-item">
-                    <span class="info-label">面试内容:</span>
-                    <span>{{ question.content }}</span>
-                  </div>
-                  <div class="info-item">
-                    <span class="info-label">对话目标:</span>
-                    <span>{{ question.target }}</span>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-      
-      <template #footer>
-        <div class="drawer-footer">
-          <el-button @click="cancelAIChatConfig">取消</el-button>
-          <el-button type="primary" @click="handleAIChatConfig">确认</el-button>
-        </div>
-      </template>
-    </el-drawer>
-
-    <!-- 在 template 中添加问题编辑弹框 -->
-    <el-dialog
-      title="添加问题"
-      v-model="showQuestionDialog"
-      width="500px"
-      :close-on-click-modal="false"
-      class="question-dialog"
-    >
-      <div class="question-form">
-        <div class="form-item">
-          <div class="form-label">
-            标题<span class="required">*</span>
-          </div>
-          <el-input 
-            v-model="questionForm.title"
-            placeholder="请输入标题"
-            maxlength="15"
-            show-word-limit
-          />
-        </div>
-
-        <div class="form-item">
-          <div class="form-label">考察能力</div>
-          <el-select
-            v-model="questionForm.ability"
-            placeholder="请选择考察能力"
-            class="full-width"
-          >
-            <el-option label="深入考察抗压能力" value="pressure" />
-            <el-option label="候选人回答中包含一个详细的案例" value="case" />
-            <el-option label="获取候选人明确的毕业院校和专业" value="education" />
-          </el-select>
-        </div>
-
-        <div class="form-item">
-          <div class="form-label">
-            面试内容<span class="required">*</span>
-          </div>
-          <el-input
-            v-model="questionForm.content"
-            type="textarea"
-            rows="4"
-            placeholder="请输入面试内容"
-            maxlength="300"
-            show-word-limit
-          />
-        </div>
-
-        <div class="form-item">
-          <div class="form-label">对话目标</div>
-          <el-input
-            v-model="questionForm.target"
-            type="textarea"
-            rows="3"
-            placeholder="输入你想在对话中达成的目标,示例:
-• 深入考察抗压能力
-• 候选人回答中包含一个详细的案例
-• 获取候选人明确的毕业院校和专业"
-            maxlength="100"
-            show-word-limit
-          />
-        </div>
-      </div>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelAddQuestion">取消</el-button>
-          <el-button type="primary" @click="confirmAddQuestion">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 修改资料收集对话框模板 -->
-    <el-dialog
-      title="资料收集"
-      v-model="showDataCollectionDialog"
-      width="500px"
-      :close-on-click-modal="false"
-      class="data-collection-dialog"
-    >
-      <div class="data-collection-form">
-        <div class="question-list">
-          <div v-for="(question, index) in questionTypes" :key="index" class="question-item">
-            <!-- 问题标题栏 -->
-            <div class="question-header">
-              <div class="question-title">
-                <span>{{ question.label }}</span>
-              </div>
-              <div class="question-type">
-                <span>问答类</span>
-                <el-icon><ArrowDown /></el-icon>
-              </div>
-            </div>
-            
-            <!-- 问题内容区 -->
-            <div class="question-content">
-              <div class="user-answer">用户回答</div>
-              
-              <!-- 操作按钮区 -->
-              <div class="question-actions">
-                <div class="action-left">
-                  <el-switch v-model="question.isVisible" />
-                  <span class="action-text">{{ question.isVisible ? '显示' : '隐藏' }}</span>
-                </div>
-                <div class="action-right">
-                  <el-icon class="action-icon"><Document /></el-icon>
-                  <el-icon class="action-icon"><Delete /></el-icon>
-                </div>
-              </div>
-              
-              <!-- 设置区域 -->
-              <div class="question-settings">
-                <div class="setting-item">
-                  <span>开启评分</span>
-                  <el-switch v-model="question.enableScoring" />
-                </div>
-                <div class="setting-item">
-                  <span>必答</span>
-                  <el-switch v-model="question.isRequired" />
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
-
-        <!-- 添加问题按钮 -->
-        <div class="add-question">
-          <el-button type="primary" plain @click="handleAddQuestion">
-            <el-icon><Plus /></el-icon>
-            添加问题
-          </el-button>
-        </div>
-      </div>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelDataCollection">取消</el-button>
-          <el-button type="primary" @click="confirmDataCollection">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 添加简历收集对话框 -->
-    <el-dialog
-      title="简历收集"
-      v-model="showResumeCollectionDialog"
-      width="500px"
-      :close-on-click-modal="false"
-      class="resume-collection-dialog"
-    >
-      <div class="resume-collection-form">
-        <div class="form-item">
-          <div class="form-label">步骤名称</div>
-          <el-input 
-            v-model="resumeCollectionForm.title" 
-            placeholder="请输入步骤名称"
-            maxlength="30"
-            show-word-limit
-          />
-        </div>
-        
-        <!-- <div class="form-item">
-          <div class="form-label">必填项</div>
-          <el-switch v-model="resumeCollectionForm.isRequired" />
-          <span class="switch-label">{{ resumeCollectionForm.isRequired ? '开启' : '关闭' }}</span>
-        </div>
-        
-        <div class="resume-preview">
-          <div class="preview-title">简历收集预览</div>
-          <div class="preview-content">
-            <div class="preview-item">
-              <div class="item-label">姓名</div>
-              <div class="item-input"></div>
-              <div class="item-required" v-if="resumeCollectionForm.isRequired">*</div>
-            </div>
-            <div class="preview-item">
-              <div class="item-label">手机号</div>
-              <div class="item-input"></div>
-              <div class="item-required" v-if="resumeCollectionForm.isRequired">*</div>
-            </div>
-            <div class="preview-item">
-              <div class="item-label">邮箱</div>
-              <div class="item-input"></div>
-              <div class="item-required" v-if="resumeCollectionForm.isRequired">*</div>
-            </div>
-            <div class="preview-item">
-              <div class="item-label">上传简历</div>
-              <div class="item-upload">
-                <el-icon><Plus /></el-icon>
-              </div>
-              <div class="item-required" v-if="resumeCollectionForm.isRequired">*</div>
-            </div>
-          </div>
-        </div> -->
-      </div>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelResumeCollection">取消</el-button>
-          <el-button type="primary" @click="confirmResumeCollection">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 添加代码测试对话框 -->
-    <el-dialog
-      title="添加代码笔试步骤"
-      v-model="showCodeTestDialog"
-      width="500px"
-      :close-on-click-modal="false"
-      class="code-test-dialog"
-    >
-      <div class="code-test-form">
-        <div class="form-item">
-          <div class="form-label">步骤名称</div>
-          <el-input 
-            v-model="codeTestForm.title" 
-            placeholder="请输入步骤名称"
-            maxlength="30"
-            show-word-limit
-          />
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">自动评判试卷</div>
-          <el-switch v-model="codeTestForm.autoJudge" />
-          <span class="switch-label">{{ codeTestForm.autoJudge ? '开启' : '关闭' }}</span>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">添加考试试卷</div>
-          <div class="test-paper-selector">
-            <div class="empty-paper" v-if="!codeTestForm.selectedPaper">
-              <el-button type="primary" plain @click="handleSelectPaper">
-                <el-icon><Plus /></el-icon>
-                添加考卷
-              </el-button>
-              <p class="tip-text">多选时,考生会随机分配到一套试卷</p>
-            </div>
-            <div class="selected-paper" v-else>
-              <div class="paper-info">
-                <div class="paper-title">{{ codeTestForm.selectedPaper.title }}</div>
-                <div class="paper-meta">
-                  <span>{{ codeTestForm.selectedPaper.questionCount }}道题</span>
-                  <span>{{ codeTestForm.selectedPaper.duration }}分钟</span>
-                  <span>{{ codeTestForm.selectedPaper.difficulty }}</span>
-                </div>
-              </div>
-              <div class="paper-actions">
-                <el-button type="text" @click="handleViewPaper">查看</el-button>
-                <el-button type="text" @click="handleChangePaper">更换</el-button>
-                <el-button type="text" @click="handleRemovePaper">删除</el-button>
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-      
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelCodeTest">取消</el-button>
-          <el-button type="primary" @click="confirmCodeTest">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 添加打字测试对话框 -->
-    <el-drawer
-      title="添加打字测试步骤"
-      v-model="showTypingTestDialog"
-      size="60%"
-      :close-on-click-modal="false"
-      class="typing-test-dialog"
-      :destroy-on-close="false"
-    >
-      <div class="typing-test-form">
-        <div class="form-item">
-          <div class="form-label">选择文章</div>
-          <div class="article-selection">
-            <el-button type="primary" size="small" class="add-article-btn">
-              <el-icon><Plus /></el-icon>
-              添加文章
-            </el-button>
-          </div>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">候选人选择文章方式</div>
-          <el-radio-group v-model="typingTestForm.selectionMode">
-            <el-radio label="random">随机抽取</el-radio>
-            <el-radio label="self">候选人自主选择</el-radio>
-          </el-radio-group>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">测试时间</div>
-          <div class="time-input-container">
-            <el-input v-model="typingTestForm.duration" class="time-input" />
-            <span class="time-unit">分钟</span>
-          </div>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">登录界面预览图(建议尺寸1024*768像素,最大不要超过5M)</div>
-          <el-upload
-            class="preview-uploader"
-            action="#"
-            :auto-upload="false"
-            :show-file-list="false"
-            accept="image/*"
-          >
-            <div class="upload-area">
-              <el-icon><Plus /></el-icon>
-              <div class="upload-text">添加图片</div>
-            </div>
-          </el-upload>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">注意事项内容(在登录界面显示)</div>
-          <el-input
-            v-model="typingTestForm.notes"
-            type="textarea"
-            rows="4"
-            placeholder="请输入注意事项内容"
-            maxlength="300"
-            show-word-limit
-          />
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">测试页面标题</div>
-          <el-input 
-            v-model="typingTestForm.pageTitle" 
-            placeholder="测试页面标题"
-          />
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">视频监控</div>
-          <el-checkbox v-model="typingTestForm.videoMonitoring">视频监控</el-checkbox>
-        </div>
-        
-        <div class="form-item">
-          <div class="form-label">通过标准</div>
-          <div class="criteria-options">
-            <el-checkbox v-model="typingTestForm.passCriteria.speed">速度</el-checkbox>
-            <el-checkbox v-model="typingTestForm.passCriteria.accuracy">正确率</el-checkbox>
-          </div>
-        </div>
-      </div>
-      
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="cancelTypingTest">取消</el-button>
-          <el-button type="primary" @click="confirmTypingTest">保存</el-button>
-        </span>
-      </template>
-    </el-drawer>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref, reactive, onMounted, computed } from 'vue';
-import { useRouter, useRoute } from 'vue-router';
-import { ElMessage, ElMessageBox, ElDrawer } from 'element-plus';
-import * as api from '../list/api';
-import { Plus, Document, ArrowDown, Edit, Delete, InfoFilled } from '@element-plus/icons-vue';
-import { GetList } from '../../questionBank/classList/api';
-import { GetCompetencyList } from '../list/api';
-
-// 添加 ChatQuestion 接口定义
-interface ChatQuestion {
-  title: string;
-  ability: string;
-  content: string;
-  target: string;
-  weight: number;
-  maxAnswers: number;
-}
-
-// 添加 Paper 接口定义
-interface Paper {
-  id: number;
-  title: string;
-  questionCount: number;
-  duration: number;
-  difficulty: string;
-}
-
-const router = useRouter();
-const route = useRoute();
-const positionId = ref(route.params.id as string);
-const positionStatus = ref(false);
-
-// 职位数据
-const positionData = reactive({
-  title: '',
-  job_category: '',
-  job_type_display: '',
-  city: '',
-  location: [],
-  location_str: '',
-  address_detail: '',
-  benefits: [],
-  requirements: '',
-  job_type: 0,
-  salary_range: '',
-  department: '',
-  end_date: '',
-  description: '',
-  work_experience_required: '',
-  education_required: '',
-  competency_tags: []
-});
-
-// 招聘流程数据 - 修改为普通数组而非 ref 对象
-const recruitmentProcess = reactive([
-  { id: 1, name: '待核验', description: '待核验', active: true },
-  { id: 2, name: 'AI视频', description: 'AI视频', active: true },
-  { id: 3, name: '已通过', description: '已通过', active: true },
-  { id: 4, name: '已淘汰', description: '已淘汰', active: false }
-]);
-
-// 是否显示流程编辑对话框
-const showProcessDialog = ref(false);
-
-// 定义可选的流程步骤类型
-const processStepOptions = [
-  { label: 'AI视频', value: 'ai_video' },
-  { label: '视频宣讲', value: 'video_presentation' },
-  { label: 'AI实时对话', value: 'ai_chat' },
-  { label: '资料收集', value: 'data_collection' },
-  { label: '简历收集', value: 'resume_collection' },
-  { label: '代码测试', value: 'code_test' },
-  { label: '打字测试', value: 'typing_test' }
-];
-
-// 当前选择的添加位置
-const currentAddIndex = ref(-1);
-// 是否显示选项菜单
-const showOptionsMenu = ref(false);
-// 选项菜单的位置
-const menuPosition = reactive({ top: '0px', left: '0px' });
-
-// AI视频对话框显示状态
-const showAIVideoDialog = ref(false);
-
-// AI视频配置选项
-const aiVideoOptions = reactive({
-  qualityControl: true,
-  teamwork: false,
-  problemSolving: false,
-  dataAnalysis: false,
-  taskManagement: false,
-  productivity: false,
-  manufacturingSkills: false,
-  customerService: false,
-  resistanceToPressure: false,
-  workDefinition: false,
-  selfIntroduction: false,
-  detailOrientation: false,
-  logicalThinking: false,
-  safetyAwareness: false,
-  customerServiceAwareness: false,
-  timeManagement: false,
-  teamCooperation: false,
-  timeManagementSkills: false,
-  productEvaluation: false,
-  basicQualifications: false,
-  networkDeployment: false,
-  remoteCooperation: false,
-  executionAbility: false,
-  selfDiscipline: false,
-  managementAbility: false,
-  customerOrientation: false,
-  stability: false,
-  salesSkills: false,
-  responseAbility: false
-});
-
-// 当前步骤
-const currentStep = ref(1);
-
-// 选中的能力列表
-const selectedAbilities = computed(() => {
-  return Object.entries(aiVideoOptions)
-    .filter(([_, value]) => value)
-    .map(([key, _]) => {
-      const label = getAbilityLabel(key);
-      return {
-        value: key,
-        label,
-        isNew: ['teamwork', 'problemSolving', 'qualityControl'].includes(key)
-      };
-    });
-});
-
-// 获取能力标签
-const getAbilityLabel = (key: string) => {
-  const abilityMap: Record<string, string> = {
-    qualityControl: '质量控制',
-    teamwork: '团队协作',
-    problemSolving: '问题解决',
-    dataAnalysis: '问题分析',
-    taskManagement: '分析问题',
-    productivity: '生产效率',
-    manufacturingSkills: '制造制作技能',
-    customerService: '客户服务技能',
-    resistanceToPressure: '抗压能力-客户服务',
-    workDefinition: '工作稳定-宣誓',
-    selfIntroduction: '自我介绍-宣誓',
-    detailOrientation: '细节关注',
-    logicalThinking: '清晰逻辑思维',
-    safetyAwareness: '安全意识',
-    customerServiceAwareness: '客户服务意识',
-    timeManagement: '时间管理',
-    teamCooperation: '团队合作',
-    timeManagementSkills: '时间管理能力',
-    productEvaluation: '品鉴评估能力',
-    basicQualifications: '基本素质',
-    networkDeployment: '网位布署',
-    remoteCooperation: '泡通协作',
-    executionAbility: '执行能力',
-    selfDiscipline: '自驱力',
-    managementAbility: '管理能力',
-    customerOrientation: '客户导向',
-    stability: '稳定性',
-    salesSkills: '销售技巧',
-    responseAbility: '应变能力'
-  };
-  return abilityMap[key] || key;
-};
-
-// 排名设置
-const rankSettings = reactive({
-  min: 0,
-  max: 100,
-  minScore: 0.00,
-  maxScore: 0.00
-});
-
-// AI模型版本
-const aiModelVersion = ref('standard');
-
-// 是否启用有效期
-const validityEnabled = ref(false);
-
-// 在 script setup 部分添加以下响应式变量
-const showVideoLectureDialog = ref(false); // 控制视频宣讲弹窗显示
-const videoLectureForm = reactive({
-  title: '', // 步骤名称
-  videoFile: null, // 视频文件
-  description: '', // 视频简介
-  broadcastType: 'required', // 播放设置:必须看完/看过即可
-});
-
-// 在 script setup 部分添加新的响应式变量
-const showAIChatDialog = ref(false); // 控制AI实时对话抽屉显示
-
-// 添加 AI 实时对话配置相关的响应式变量
-const chatDuration = ref(15); // 默认15分钟
-const maxQuestions = ref(5); // 默认5个问题
-
-// 在 script setup 部分添加以下响应式变量
-const showQuestionDialog = ref(false); // 控制添加问题弹框显示
-const questionForm = reactive({
-  title: '', // 标题
-  ability: '', // 考察能力
-  content: '', // 面试内容
-  target: '', // 对话目标
-  weight: 100, // 权重占比,默认100%
-  maxAnswers: 1 // 最多回答次数,默认1次
-});
-
-// 在 script setup 中添加以下响应式变量
-const showDataCollectionDialog = ref(false); // 控制资料收集对话框显示
-const dataCollectionForm = reactive({
-  isRequired: false, // 开启评分开关
-  questions: [] // 问题列表
-});
-
-// 修改问题类型定义
-interface QuestionType {
-  label: string;
-  value: string;
-  isVisible?: boolean;
-  enableScoring?: boolean;
-  isRequired?: boolean;
-}
-
-// 定义问题列表
-const questionTypes = ref<QuestionType[]>([
-  { 
-    label: 'Q1', 
-    value: 'q1',
-    isVisible: true,
-    enableScoring: false,
-    isRequired: false
-  },
-  { 
-    label: '请问你的毕业院校是哪里?', 
-    value: 'school',
-    isVisible: true,
-    enableScoring: false,
-    isRequired: true
-  }
-]);
-
-// 添加简历收集相关的状态和方法
-const showResumeCollectionDialog = ref(false);
-const resumeCollectionForm = reactive({
-  title: '简历收集',
-  isRequired: true,
-  steps: 4, // 默认步骤数
-});
-
-// 显示简历收集弹窗
-const showResumeCollectionConfig = () => {
-  showResumeCollectionDialog.value = true;
-};
-
-// 取消简历收集配置
-const cancelResumeCollection = () => {
-  showResumeCollectionDialog.value = false;
-  // 重置表单
-  Object.assign(resumeCollectionForm, {
-    title: '简历收集',
-    isRequired: true,
-    steps: 4
-  });
-};
-
-// 确认添加简历收集
-const confirmResumeCollection = () => {
-  // 创建新的简历收集步骤
-  const newStep = {
-    id: Date.now(),
-    name: resumeCollectionForm.title,
-    description: '简历收集',
-    active: true,
-    type: 'resume_collection',
-    config: {
-      ...resumeCollectionForm
-    }
-  };
-  
-  // 添加到流程中
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  
-  // 关闭弹窗
-  showResumeCollectionDialog.value = false;
-  
-  // 提示成功
-  ElMessage.success('简历收集步骤已添加');
-  
-  // 重置表单
-  cancelResumeCollection();
-};
-
-// 获取职位详情
-const getPositionDetail = async () => {
-  try {
-    const id = route.query.id;
-    if (!id) {
-      ElMessage.error('职位ID不存在');
-      return;
-    }
-    
-    // 修复类型错误,创建符合InfoReq类型的对象
-    const idValue = Array.isArray(id) ? id[0] : id;
-    // 创建一个符合API要求的请求对象
-    const infoReq = id
-    const response = await api.GetObj(id); // 使用 infoReq 而不是 id
-    if (response && response.data) {
-      Object.assign(positionData, response.data);
-      // 设置职位状态
-      positionStatus.value = positionData.job_type === 1;
-    }
-  } catch (error) {
-    console.error('获取职位详情失败:', error);
-    ElMessage.error('获取职位详情失败');
-  }
-};
-
-// 返回列表页
-const goBack = () => {
-  router.push('/position/list');
-};
-
-// 编辑职位
-const handleEdit = () => {
-  router.push(`/position/edit/${positionId.value}`);
-};
-
-// 分享职位
-const handleShare = () => {
-  ElMessage.success('分享链接已复制到剪贴板');
-};
-
-// 修改职位状态
-const handleStatusChange = async (value: boolean) => {
-  const id = route.query.id;
-  try {
-    const newStatus = value ? 1 : 0;
-    await api.UpdateObj({
-      id: id,
-      job_type: newStatus
-    });
-    positionData.job_type = newStatus;
-    ElMessage.success(`职位已${value ? '开启' : '关闭'}`);
-  } catch (error) {
-    console.error('修改状态失败:', error);
-    ElMessage.error('修改状态失败');
-    // 恢复原状态
-    positionStatus.value = !value;
-  }
-};
-
-// 编辑职位用途
-const handleEditUsage = () => {
-  ElMessage.info('编辑职位用途功能开发中');
-};
-
-// 编辑招聘流程
-const handleEditProcess = () => {
-  showProcessDialog.value = true;
-};
-
-// 保存招聘流程
-const saveProcess = () => {
-  showProcessDialog.value = false;
-  ElMessage.success('招聘流程已更新');
-};
-
-// 取消编辑
-const cancelEditProcess = () => {
-  showProcessDialog.value = false;
-};
-
-// 删除流程步骤
-const deleteProcessStep = (index: number) => {
-  recruitmentProcess.splice(index, 1);
-};
-
-// 在特定位置添加流程步骤
-const addProcessStepAt = (index: number) => {
-  if (recruitmentProcess.length >= 6) {
-    ElMessage.warning('最多只能添加6个流程步骤');
-    return;
-  }
-  
-  const newStep = {
-    id: Date.now(),
-    name: '新步骤',
-    description: '新步骤',
-    active: true
-  };
-  
-  recruitmentProcess.splice(index, 0, newStep);
-};
-
-// 修改原有的添加方法,调用新方法
-const addProcessStep = () => {
-  addProcessStepAt(recruitmentProcess.length);
-};
-
-// 获取状态文本
-const getStatusText = (status: number) => {
-  const statusMap: Record<number, string> = {
-    0: '未发布',
-    1: '已发布',
-    2: '已结束'
-  };
-  return statusMap[status] || '未知状态';
-};
-
-// 获取状态类型
-const getStatusType = (status: number) => {
-  const typeMap: Record<number, string> = {
-    0: 'info',
-    1: 'success',
-    2: 'danger'
-  };
-  return typeMap[status] || 'info';
-};
-
-// 获取职位性质文本
-const getJobTypeText = (type: number) => {
-  const typeMap: Record<number, string> = {
-    0: '全职',
-    1: '兼职',
-    2: '实习',
-    3: '其他'
-  };
-  return typeMap[type] || '未知';
-};
-
-// 显示选项菜单
-const showStepOptions = (index: number, event: MouseEvent) => {
-  currentAddIndex.value = index;
-  showOptionsMenu.value = true;
-  
-  // 计算菜单位置 - 获取按钮元素
-  const button = event.currentTarget as HTMLElement;
-  // 获取对话框元素,作为相对定位的参考
-  const dialogContent = button.closest('.process-dialog-content');
-  
-  if (dialogContent) {
-    // 获取按钮和对话框的位置信息
-    const buttonRect = button.getBoundingClientRect();
-    const dialogRect = dialogContent.getBoundingClientRect();
-    
-    // 计算相对于对话框的位置
-    const top = buttonRect.bottom - dialogRect.top + 5; // 按钮下方5px
-    const left = buttonRect.left - dialogRect.left;
-    
-    // 设置菜单位置
-    menuPosition.top = `${top}px`;
-    menuPosition.left = `${left}px`;
-  }
-  
-  // 阻止事件冒泡
-  event.stopPropagation();
-};
-
-// 修改添加选定类型的步骤方法
-const addSelectedStepType = (type: string, label: string) => {
-  if (type === 'ai_video') {
-    showAIVideoDialog.value = true;
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 添加 AI 实时对话类型的处理
-  if (type === 'ai_chat') {
-    showAIChatDialog.value = true;
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 添加视频宣讲类型的处理
-  if (type === 'video_presentation') {
-    showVideoLectureConfig();
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 添加资料收集类型的处理
-  if (type === 'data_collection') {
-    showDataCollectionDialog.value = true;
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 添加简历收集类型的处理
-  if (type === 'resume_collection') {
-    showResumeCollectionConfig();
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 添加代码测试类型的处理
-  if (type === 'code_test') {
-    showCodeTestConfig();
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 添加打字测试类型的处理
-  if (type === 'typing_test') {
-    showTypingTestDialog.value = true;
-    showOptionsMenu.value = false;
-    return;
-  }
-  
-  // 其他类型的处理保持不变
-  if (recruitmentProcess.length >= 6) {
-    ElMessage.warning('最多只能添加6个流程步骤');
-    return;
-  }
-  
-  const newStep = {
-    id: Date.now(),
-    name: label,
-    description: label,
-    active: true,
-    type: type
-  };
-  
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  showOptionsMenu.value = false;
-};
-
-// 下一步
-const nextStep = () => {
-  if (currentStep.value < 3) {
-    currentStep.value++;
-  } else {
-    finishConfig();
-  }
-};
-
-// 上一步
-const prevStep = () => {
-  if (currentStep.value > 1) {
-    currentStep.value--;
-  }
-};
-
-// 完成配置
-const finishConfig = () => {
-  // 创建新的AI视频步骤
-  const newStep = {
-    id: Date.now(),
-    name: 'AI视频',
-    description: `已选择${selectedAbilities.value.length}项能力测评`,
-    active: true,
-    type: 'ai_video',
-    abilities: selectedAbilities.value.map(a => a.value)
-  };
-  
-  // 添加到流程中
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  
-  // 关闭对话框
-  showAIVideoDialog.value = false;
-  
-  // 重置当前步骤
-  currentStep.value = 1;
-  
-  // 提示成功
-  ElMessage.success('AI视频步骤已添加');
-};
-
-// 取消AI视频配置
-const cancelAIVideoConfig = () => {
-  showAIVideoDialog.value = false;
-  currentStep.value = 1; // 重置步骤
-};
-
-// 跳转到指定步骤
-const jumpToStep = (step: number) => {
-  // 确保步骤在有效范围内
-  if (step >= 1 && step <= 3) {
-    currentStep.value = step;
-  }
-};
-
-// 显示视频宣讲配置弹窗
-const showVideoLectureConfig = () => {
-  showVideoLectureDialog.value = true;
-};
-
-// 取消视频宣讲配置
-const cancelVideoLecture = () => {
-  showVideoLectureDialog.value = false;
-  // 重置表单
-  Object.assign(videoLectureForm, {
-    title: '',
-    videoFile: null,
-    description: '',
-    broadcastType: 'required'
-  });
-};
-
-// 确认添加视频宣讲
-const confirmVideoLecture = () => {
-  // 创建新的视频宣讲步骤
-  const newStep = {
-    id: Date.now(),
-    name: videoLectureForm.title || '视频宣讲',
-    description: videoLectureForm.description || '视频宣讲',
-    active: true,
-    type: 'video_presentation',
-    config: {
-      ...videoLectureForm
-    }
-  };
-  
-  // 添加到流程中
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  
-  // 关闭弹窗
-  showVideoLectureDialog.value = false;
-  
-  // 提示成功
-  ElMessage.success('视频宣讲步骤已添加');
-  
-  // 重置表单
-  cancelVideoLecture();
-};
-
-// 添加 AI 实时对话相关方法
-const handleAIChatConfig = () => {
-  // 这里添加确认配置的逻辑
-  const newStep = {
-    id: Date.now(),
-    name: 'AI实时对话',
-    description: 'AI实时对话',
-    active: true,
-    type: 'ai_chat'
-  };
-  
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  showAIChatDialog.value = false;
-  ElMessage.success('AI实时对话步骤已添加');
-};
-
-const cancelAIChatConfig = () => {
-  showAIChatDialog.value = false;
-};
-
-// 添加问题列表数据
-const chatQuestions = ref<ChatQuestion[]>([]);
-
-// 添加问题处理方法
-const handleAddQuestion = () => {
-  showQuestionDialog.value = true;
-};
-
-// 取消添加问题
-const cancelAddQuestion = () => {
-  showQuestionDialog.value = false;
-  // 重置表单
-  Object.assign(questionForm, {
-    title: '',
-    ability: '',
-    content: '',
-    target: '',
-    weight: 100,
-    maxAnswers: 1
-  });
-};
-
-// 确认添加问题
-const confirmAddQuestion = () => {
-  if (!questionForm.title || !questionForm.content) {
-    ElMessage.warning('请填写必填项');
-    return;
-  }
-
-  // 创建新问题对象
-  const newQuestion = {
-    title: questionForm.title,
-    ability: questionForm.ability,
-    content: questionForm.content,
-    target: questionForm.target,
-    weight: questionForm.weight,
-    maxAnswers: questionForm.maxAnswers
-  };
-
-  // 添加到问题列表
-  chatQuestions.value.push(newQuestion);
-  
-  // 关闭弹框
-  showQuestionDialog.value = false;
-  
-  // 重置表单
-  cancelAddQuestion();
-  
-  // 提示成功
-  ElMessage.success('问题添加成功');
-};
-
-// 导入问题处理方法
-const handleImportQuestion = () => {
-  ElMessage.info('导入问题功能开发中');
-};
-
-// 在 script setup 中添加
-const editQuestion = (index: number) => {
-  // 编辑问题的逻辑
-  const question = chatQuestions.value[index];
-  Object.assign(questionForm, question);
-  showQuestionDialog.value = true;
-};
-
-const deleteQuestion = (index: number) => {
-  ElMessageBox.confirm('确认删除该问题?', '提示', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
-    type: 'warning'
-  }).then(() => {
-    chatQuestions.value.splice(index, 1);
-    ElMessage.success('删除成功');
-  });
-};
-
-// 取消资料收集配置
-const cancelDataCollection = () => {
-  showDataCollectionDialog.value = false;
-  // 重置表单
-  Object.assign(dataCollectionForm, {
-    isRequired: false,
-    questions: []
-  });
-};
-
-// 确认添加资料收集
-const confirmDataCollection = () => {
-  if (dataCollectionForm.questions.length === 0) {
-    ElMessage.warning('请至少选择一个问题');
-    return;
-  }
-
-  // 创建新的资料收集步骤
-  const newStep = {
-    id: Date.now(),
-    name: '资料收集',
-    description: `已选择${dataCollectionForm.questions.length}个问题`,
-    active: true,
-    type: 'data_collection',
-    config: {
-      ...dataCollectionForm,
-      questions: dataCollectionForm.questions.map(q => {
-        // 修复 find 方法的使用
-        const question = questionTypes.value.find(type => type.value === q);
-        return {
-          type: q,
-          title: question?.label || '',
-          required: dataCollectionForm.isRequired
-        };
-      })
-    }
-  };
-
-  // 添加到流程中
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-
-  // 关闭弹窗
-  showDataCollectionDialog.value = false;
-
-  // 提示成功
-  ElMessage.success('资料收集步骤已添加');
-
-  // 重置表单
-  cancelDataCollection();
-};
-
-// 使用 onMounted 和 onUnmounted 钩子
-onMounted(() => {
-  getPositionDetail();
-  getCompetencyList();
-});
-
-// 添加代码测试相关的状态和方法
-const showCodeTestDialog = ref(false);
-const codeTestForm = reactive({
-  title: '代码笔试',
-  autoJudge: true,
-  selectedPaper: null as Paper | null,
-});
-
-// 显示代码测试弹窗
-const showCodeTestConfig = () => {
-  showCodeTestDialog.value = true;
-};
-
-// 取消代码测试配置
-const cancelCodeTest = () => {
-  showCodeTestDialog.value = false;
-  // 重置表单
-  Object.assign(codeTestForm, {
-    title: '代码笔试',
-    autoJudge: true,
-    selectedPaper: null
-  });
-};
-
-// 确认添加代码测试
-const confirmCodeTest = () => {
-  if (!codeTestForm.selectedPaper) {
-    ElMessage.warning('请添加考试试卷');
-    return;
-  }
-  
-  // 创建新的代码测试步骤
-  const newStep = {
-    id: Date.now(),
-    name: codeTestForm.title,
-    description: `${codeTestForm.selectedPaper.title}`,
-    active: true,
-    type: 'code_test',
-    config: {
-      ...codeTestForm
-    }
-  };
-  
-  // 添加到流程中
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  
-  // 关闭弹窗
-  showCodeTestDialog.value = false;
-  
-  // 提示成功
-  ElMessage.success('代码笔试步骤已添加');
-  
-  // 重置表单
-  cancelCodeTest();
-};
-
-// 选择试卷
-const handleSelectPaper = () => {
-  // 模拟选择试卷
-  codeTestForm.selectedPaper = {
-    id: 1,
-    title: '前端开发工程师笔试',
-    questionCount: 4,
-    duration: 60,
-    difficulty: '中等难度'
-  } as Paper;
-};
-
-// 查看试卷
-const handleViewPaper = () => {
-  ElMessage.info('查看试卷功能开发中');
-};
-
-// 更换试卷
-const handleChangePaper = () => {
-  ElMessage.info('更换试卷功能开发中');
-};
-
-// 删除试卷
-const handleRemovePaper = () => {
-  codeTestForm.selectedPaper = null;
-};
-
-// 添加打字测试对话框
-const showTypingTestDialog = ref(false);
-const typingTestForm = reactive({
-  title: '', // 步骤名称
-  duration: 5, // 测试时间,默认5分钟
-  content: '', // 测试页面标题
-  notes: '', // 注意事项内容
-  videoMonitoring: false, // 是否启用视频监控
-  passCriteria: {
-    speed: false, // 是否通过速度标准
-    accuracy: true, // 是否通过正确率标准,默认选中
-  },
-  selectionMode: 'random', // 文章选择方式:random-随机抽取,self-候选人自主选择
-  pageTitle: '', // 测试页面标题
-  previewImage: null, // 登录界面预览图
-  articles: [] // 文章列表
-});
-
-// 取消打字测试配置
-const cancelTypingTest = () => {
-  showTypingTestDialog.value = false;
-  // 重置表单
-  Object.assign(typingTestForm, {
-    title: '',
-    duration: 5,
-    content: '',
-    notes: '',
-    videoMonitoring: false,
-    passCriteria: {
-      speed: false,
-      accuracy: true,
-    },
-    selectionMode: 'random',
-    pageTitle: '',
-    previewImage: null,
-    articles: []
-  });
-};
-
-// 确认添加打字测试
-const confirmTypingTest = () => {
-  // 创建新的打字测试步骤
-  const newStep = {
-    id: Date.now(),
-    name: typingTestForm.title,
-    description: `打字测试 ${typingTestForm.duration}分钟`,
-    active: true,
-    type: 'typing_test',
-    config: {
-      ...typingTestForm
-    }
-  };
-  
-  // 添加到流程中
-  recruitmentProcess.splice(currentAddIndex.value, 0, newStep);
-  
-  // 关闭弹窗
-  showTypingTestDialog.value = false;
-  
-  // 提示成功
-  ElMessage.success('打字测试步骤已添加');
-  
-  // 重置表单
-  cancelTypingTest();
-};
-
-// 添加职位名称编辑功能
-const isEditingTitle = ref(false);
-const editingTitle = ref('');
-
-// 开始编辑职位名称
-const startEditTitle = () => {
-  editingTitle.value = positionData.title;
-  isEditingTitle.value = true;
-};
-
-// 取消编辑职位名称
-const cancelEditTitle = () => {
-  isEditingTitle.value = false;
-};
-
-// 保存编辑后的职位名称
-const saveTitle = async () => {
-  if (!editingTitle.value.trim()) {
-    ElMessage.warning('职位名称不能为空');
-    return;
-  }
-  
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      title: editingTitle.value.trim()
-    });
-    
-    // 更新本地数据
-    positionData.title = editingTitle.value.trim();
-    isEditingTitle.value = false;
-    ElMessage.success('职位名称已更新');
-  } catch (error) {
-    console.error('更新职位名称失败:', error);
-    ElMessage.error('更新职位名称失败');
-  }
-};
-
-// 工作地点编辑
-const isEditingLocation = ref(false);
-const editingLocation = ref('');
-
-// 开始编辑工作地点
-const startEditLocation = () => {
-  editingLocation.value = positionData.location || '';
-  isEditingLocation.value = true;
-};
-
-// 取消编辑工作地点
-const cancelEditLocation = () => {
-  isEditingLocation.value = false;
-};
-
-// 保存工作地点
-const saveLocation = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      location: editingLocation.value.trim()
-    });
-    
-    // 更新本地数据
-    positionData.location = editingLocation.value.trim();
-    isEditingLocation.value = false;
-    ElMessage.success('工作地点已更新');
-  } catch (error) {
-    console.error('更新工作地点失败:', error);
-    ElMessage.error('更新工作地点失败');
-  }
-};
-
-// 职位薪资编辑
-const isEditingSalary = ref(false);
-const editingSalary = ref('');
-
-// 开始编辑职位薪资
-const startEditSalary = () => {
-  editingSalary.value = positionData.salary_range || '';
-  isEditingSalary.value = true;
-};
-
-// 取消编辑职位薪资
-const cancelEditSalary = () => {
-  isEditingSalary.value = false;
-};
-
-// 保存职位薪资
-const saveSalary = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      salary_range: editingSalary.value.trim()
-    });
-    
-    // 更新本地数据
-    positionData.salary_range = editingSalary.value.trim();
-    isEditingSalary.value = false;
-    ElMessage.success('职位薪资已更新');
-  } catch (error) {
-    console.error('更新职位薪资失败:', error);
-    ElMessage.error('更新职位薪资失败');
-  }
-};
-
-// 工作经验编辑
-const isEditingExperience = ref(false);
-const editingExperience = ref('');
-
-// 开始编辑工作经验
-const startEditExperience = () => {
-  editingExperience.value = positionData.work_experience_required || '';
-  isEditingExperience.value = true;
-};
-
-// 取消编辑工作经验
-const cancelEditExperience = () => {
-  isEditingExperience.value = false;
-};
-
-// 保存工作经验
-const saveExperience = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      work_experience_required: editingExperience.value
-    });
-    
-    // 更新本地数据
-    positionData.work_experience_required = editingExperience.value;
-    isEditingExperience.value = false;
-    ElMessage.success('工作经验已更新');
-  } catch (error) {
-    console.error('更新工作经验失败:', error);
-    ElMessage.error('更新工作经验失败');
-  }
-};
-
-// 学历要求编辑
-const isEditingEducation = ref(false);
-const editingEducation = ref('');
-
-// 开始编辑学历要求
-const startEditEducation = () => {
-  editingEducation.value = positionData.education_required || '';
-  isEditingEducation.value = true;
-};
-
-// 取消编辑学历要求
-const cancelEditEducation = () => {
-  isEditingEducation.value = false;
-};
-
-// 保存学历要求
-const saveEducation = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      education_required: editingEducation.value
-    });
-    
-    // 更新本地数据
-    positionData.education_required = editingEducation.value;
-    isEditingEducation.value = false;
-    ElMessage.success('学历要求已更新');
-  } catch (error) {
-    console.error('更新学历要求失败:', error);
-    ElMessage.error('更新学历要求失败');
-  }
-};
-
-// 所属部门编辑
-const isEditingDepartment = ref(false);
-const editingDepartment = ref('');
-
-// 开始编辑所属部门
-const startEditDepartment = () => {
-  editingDepartment.value = positionData.department || '';
-  isEditingDepartment.value = true;
-};
-
-// 取消编辑所属部门
-const cancelEditDepartment = () => {
-  isEditingDepartment.value = false;
-};
-
-// 保存所属部门
-const saveDepartment = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      department: editingDepartment.value.trim()
-    });
-    
-    // 更新本地数据
-    positionData.department = editingDepartment.value.trim();
-    isEditingDepartment.value = false;
-    ElMessage.success('所属部门已更新');
-  } catch (error) {
-    console.error('更新所属部门失败:', error);
-    ElMessage.error('更新所属部门失败');
-  }
-};
-
-// 截止日期编辑
-const isEditingEndDate = ref(false);
-const editingEndDate = ref('');
-
-// 开始编辑截止日期
-const startEditEndDate = () => {
-  editingEndDate.value = positionData.end_date || '';
-  isEditingEndDate.value = true;
-};
-
-// 取消编辑截止日期
-const cancelEditEndDate = () => {
-  isEditingEndDate.value = false;
-};
-
-// 保存截止日期
-const saveEndDate = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      end_date: editingEndDate.value
-    });
-    
-    // 更新本地数据
-    positionData.end_date = editingEndDate.value;
-    isEditingEndDate.value = false;
-    ElMessage.success('截止日期已更新');
-  } catch (error) {
-    console.error('更新截止日期失败:', error);
-    ElMessage.error('更新截止日期失败');
-  }
-};
-
-// 职位要求编辑
-const isEditingRequirements = ref(false);
-const editingRequirements = ref('');
-
-// 开始编辑职位要求
-const startEditRequirements = () => {
-  editingRequirements.value = positionData.requirements || '';
-  isEditingRequirements.value = true;
-};
-
-// 取消编辑职位要求
-const cancelEditRequirements = () => {
-  isEditingRequirements.value = false;
-};
-
-// 保存职位要求
-const saveRequirements = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      requirements: editingRequirements.value
-    });
-    
-    // 更新本地数据
-    positionData.requirements = editingRequirements.value;
-    isEditingRequirements.value = false;
-    ElMessage.success('职位要求已更新');
-  } catch (error) {
-    console.error('更新职位要求失败:', error);
-    ElMessage.error('更新职位要求失败');
-  }
-};
-
-// 职位描述编辑
-const isEditingDescription = ref(false);
-const editingDescription = ref('');
-
-// 开始编辑职位描述
-const startEditDescription = () => {
-  editingDescription.value = positionData.description || '';
-  isEditingDescription.value = true;
-};
-
-// 取消编辑职位描述
-const cancelEditDescription = () => {
-  isEditingDescription.value = false;
-};
-
-// 保存职位描述
-const saveDescription = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      description: editingDescription.value
-    });
-    
-    // 更新本地数据
-    positionData.description = editingDescription.value;
-    isEditingDescription.value = false;
-    ElMessage.success('职位描述已更新');
-  } catch (error) {
-    console.error('更新职位描述失败:', error);
-    ElMessage.error('更新职位描述失败');
-  }
-};
-
-// 添加能力标签相关的响应式变量
-const competencyTags = ref([]);
-const isEditingCompetency = ref(false);
-const selectedCompetencies = ref([]);
-
-// 获取能力标签列表
-const getCompetencyList = async () => {
-  try {
-    const response = await api.GetCompetencyList({page:1,pageSize:1000});
-    if (response && response.data) {
-      competencyTags.value = response.data.items;
-    }
-  } catch (error) {
-    console.error('获取能力标签列表失败:', error);
-    ElMessage.error('获取能力标签列表失败');
-  }
-};
-
-// 开始编辑能力标签
-const startEditCompetency = () => {
-  selectedCompetencies.value = positionData.competency_tags?.map(tag => tag.id) || [];
-  isEditingCompetency.value = true;
-};
-
-// 取消编辑能力标签
-const cancelEditCompetency = () => {
-  isEditingCompetency.value = false;
-};
-
-// 保存能力标签
-const saveCompetency = async () => {
-  try {
-    const id = route.query.id;
-    await api.UpdateObj({
-      id: id,
-      competency_tags: selectedCompetencies.value
-    });
-    
-    // 更新本地数据
-    positionData.competency_tags = competencyTags.value.filter(tag => 
-      selectedCompetencies.value.includes(tag.id)
-    );
-    isEditingCompetency.value = false;
-    ElMessage.success('能力标签已更新');
-  } catch (error) {
-    console.error('更新能力标签失败:', error);
-    ElMessage.error('更新能力标签失败');
-  }
-};
-</script>
-
-<style scoped>
-.position-detail-container {
-  padding: 20px;
-  width: 80% !important;
-  margin: 0 auto;
-}
-
-.header-container {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
-}
-
-.header-actions {
-  display: flex;
-  gap: 10px;
-}
-
-.detail-content {
-  display: flex;
-  gap: 20px;
-}
-
-.detail-card {
-  flex: 2;
-}
-
-.status-cards {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  gap: 20px;
-}
-
-.section-title {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-  font-weight: bold;
-  color: #333;
-  position: relative;
-}
-
-.section-line {
-  width: 4px;
-  height: 16px;
-  background-color: #67C23A;
-  margin-right: 8px;
-}
-
-.edit-btn {
-  position: absolute;
-  right: 0;
-  color: #67C23A;
-}
-
-.detail-item {
-  display: flex;
-  margin-bottom: 20px;
-  line-height: 1.5;
-}
-
-.detail-label {
-  width: 120px;
-  color: #606266;
-  font-weight: 500;
-}
-
-.detail-value {
-  flex: 1;
-  color: #303133;
-}
-
-.html-content {
-  max-height: 300px;
-  overflow-y: auto;
-  padding: 10px;
- /*  background-color: #f9f9f9; */
-  border-radius: 4px;
-}
-
-.benefit-tag {
-  margin-right: 8px;
-  margin-bottom: 8px;
-}
-
-.status-switch {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
-
-.status-text {
-  font-size: 14px;
-  color: #303133;
-}
-
-.usage-content {
-  display: flex;
-  align-items: center;
-  margin-top: 10px;
-}
-
-.usage-label {
-  width: 80px;
-  color: #606266;
-}
-
-.usage-value {
-  color: #303133;
-  font-weight: 500;
-}
-
-.process-steps {
-  margin-top: 20px;
-}
-
-.process-step {
-  display: flex;
-  align-items: flex-start;
-  margin-bottom: 15px;
-}
-
-.step-number {
-  width: 24px;
-  height: 24px;
-  border-radius: 50%;
-  background-color: #67C23A;
-  color: white;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 10px;
-  font-size: 14px;
-}
-
-.step-content {
-  flex: 1;
-}
-
-.step-title {
-  font-weight: 500;
-  color: #303133;
-  margin-bottom: 4px;
-}
-
-.step-desc {
-  font-size: 12px;
-  color: #909399;
-}
-
-.process-step.disabled .step-number {
-  background-color: #909399;
-}
-
-.process-step.disabled .step-title,
-.process-step.disabled .step-desc {
-  color: #C0C4CC;
-}
-
-/* 招聘流程编辑对话框样式 */
-.process-dialog-content {
-  max-height: 400px;
-  overflow-y: auto;
-}
-
-.process-edit-item {
-  display: flex;
-  align-items: center;
-  padding: 15px;
-  border-radius: 4px;
-  margin-bottom: 10px;
-  background-color: #f5f7fa;
-  position: relative;
-}
-
-.process-edit-item.highlight {
-  background-color: #f0f9eb;
-  border: 1px solid #e1f3d8;
-}
-
-.process-number {
-  width: 24px;
-  height: 24px;
-  border-radius: 50%;
-  background-color: #67C23A;
-  color: white;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 15px;
-}
-
-.process-edit-content {
-  flex: 1;
-}
-
-.process-edit-desc {
-  font-size: 12px;
-  color: #909399;
-  margin-top: 4px;
-}
-
-.delete-btn {
-  position: absolute;
-  right: 10px;
-  top: 50%;
-  transform: translateY(-50%);
-}
-
-/* 添加在每个步骤上方的加号按钮样式 */
-.add-process-btn-top {
-  display: flex;
-  justify-content: center;
-  margin-bottom: 5px;
-}
-
-.add-process-btn-top .el-button {
-  color: #67C23A;
-  padding: 5px;
-}
-
-/* 确保原有的底部加号按钮样式保持不变 */
-.add-process-btn {
-  display: flex;
-  justify-content: center;
-  margin-top: 15px;
-}
-
-.add-process-btn .el-button {
-  color: #67C23A;
-}
-
-/* 流程步骤选项菜单 */
-.process-options-menu {
-  position: absolute;
-  background-color: white;
-  border-radius: 4px;
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
-  padding: 8px 0;
-  z-index: 2000;
-  max-height: 300px;
-  overflow-y: auto;
-  width: 150px;
-}
-
-.process-option-item {
-  padding: 8px 16px;
-  cursor: pointer;
-  font-size: 14px;
-  color: #606266;
-}
-
-.process-option-item:hover {
-  background-color: #f5f7fa;
-  color: #67C23A;
-}
-
-/* AI视频抽屉样式 */
-.ai-video-drawer {
-  --el-drawer-padding-primary: 0;
-}
-
-.ai-video-steps {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 20px 0;
-  border-bottom: 1px solid #e4e7ed;
-  background-color: #fff;
-}
-
-.step-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  color: #909399;
-  position: relative;
-  z-index: 1;
-  cursor: pointer;
-  transition: all 0.3s;
-}
-
-.step-item:hover {
-  color: #67C23A;
-}
-
-.step-item:hover .step-number {
-  border-color: #67C23A;
-}
-
-.step-number {
-  width: 30px;
-  height: 30px;
-  border-radius: 50%;
-  background-color: #f5f7fa;
-  color: #909399;
-  border: 1px solid #dcdfe6;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 8px;
-  font-weight: bold;
-}
-
-.step-item.active .step-number {
-  background-color: #67C23A;
-  color: white;
-  border-color: #67C23A;
-}
-
-.step-line {
-  width: 100px;
-  height: 1px;
-  background-color: #e4e7ed;
-  margin: 0 15px;
-  position: relative;
-  top: -15px;
-}
-
-.step-line.active-line {
-  background-color: #67C23A;
-}
-
-.step-title {
-  font-size: 14px;
-}
-
-.ai-video-content {
-  padding: 20px;
-  height: calc(100% - 120px); /* 减去header和footer高度 */
-  overflow-y: auto;
-}
-
-.drawer-footer {
-  padding: 10px 20px;
-  text-align: right;
-  border-top: 1px solid #e4e7ed;
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  background-color: white;
-}
-
-/* 步骤1:考察能力样式 */
-.option-tip {
-  margin-bottom: 15px;
-  color: #606266;
-}
-
-.new-feature-tip {
-  background-color: #f0f9eb;
-  padding: 10px;
-  border-radius: 4px;
-  margin-bottom: 20px;
-  font-size: 14px;
-  color: #606266;
-  line-height: 1.5;
-}
-
-.new-tag {
-  display: inline-block;
-  background-color: #f56c6c;
-  color: white;
-  padding: 2px 6px;
-  border-radius: 4px;
-  font-size: 12px;
-  margin-right: 5px;
-}
-
-.ability-options {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
-  gap: 15px;
-}
-
-.ability-label {
-  font-size: 14px;
-  color: #606266;
-}
-
-/* 步骤2:面试题目样式 */
-.interview-settings {
-  width: 100%;
-}
-
-.interview-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
-}
-
-.interview-duration {
-  display: flex;
-  align-items: center;
-}
-
-.duration-value {
-  font-weight: bold;
-  margin: 0 10px;
-}
-
-.duration-tip {
-  color: #909399;
-  font-size: 12px;
-}
-
-.interview-mode {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20px;
-}
-
-.mode-value {
-  font-weight: bold;
-  margin: 0 10px;
-}
-
-.interview-questions-container {
-  min-height: 200px;
-  border: 1px dashed #dcdfe6;
-  border-radius: 4px;
-  margin-bottom: 20px;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-
-.no-questions {
-  text-align: center;
-  color: #909399;
-}
-
-.ai-tip {
-  font-size: 12px;
-  color: #c0c4cc;
-  margin: 5px 0;
-}
-
-.auto-generate-btn {
-  margin-top: 15px;
-}
-
-.multi-dimension-questions {
-  margin-top: 30px;
-}
-
-.dimension-title {
-  font-weight: bold;
-  margin-bottom: 15px;
-}
-
-.dimension-list {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-
-.dimension-item {
-  border: 1px solid #dcdfe6;
-  border-radius: 4px;
-  padding: 10px;
-}
-
-.dimension-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-
-.expand-icon {
-  cursor: pointer;
-}
-
-/* 步骤3:系统设置样式 */
-.system-settings {
-  width: 100%;
-}
-
-.setting-section {
-  margin-bottom: 30px;
-  border-bottom: 1px solid #ebeef5;
-  padding-bottom: 20px;
-}
-
-.section-header {
-  font-weight: bold;
-  color: #303133;
-  margin-bottom: 15px;
-  position: relative;
-  padding-left: 10px;
-}
-
-.section-header::before {
-  content: '';
-  position: absolute;
-  left: 0;
-  top: 50%;
-  transform: translateY(-50%);
-  width: 4px;
-  height: 16px;
-  background-color: #67C23A;
-}
-
-.rank-settings {
-  display: flex;
-  align-items: center;
-  margin-bottom: 15px;
-}
-
-.rank-range {
-  display: flex;
-  align-items: center;
-  margin-right: 30px;
-}
-
-.range-inputs {
-  display: flex;
-  align-items: center;
-  margin-left: 10px;
-}
-
-.percent-input {
-  width: 80px;
-}
-
-.score-display {
-  display: flex;
-  align-items: center;
-}
-
-.score-range {
-  margin-left: 10px;
-  color: #67C23A;
-}
-
-.add-rank-btn {
-  margin-top: 10px;
-}
-
-.validity-setting {
-  display: flex;
-  align-items: center;
-  margin-bottom: 10px;
-}
-
-.validity-label {
-  margin-left: 10px;
-}
-
-.validity-tip {
-  font-size: 12px;
-  color: #909399;
-  margin-top: 5px;
-}
-
-.model-select {
-  width: 200px;
-}
-
-@media (max-width: 768px) {
-  .ability-options {
-    grid-template-columns: repeat(2, 1fr);
-  }
-  
-  .step-line {
-    width: 50px;
-  }
-}
-
-@media (max-width: 480px) {
-  .ability-options {
-    grid-template-columns: 1fr;
-  }
-  
-  .interview-header {
-    flex-direction: column;
-    align-items: flex-start;
-  }
-  
-  .interview-actions {
-    margin-top: 10px;
-  }
-  
-  .rank-settings {
-    flex-direction: column;
-    align-items: flex-start;
-  }
-  
-  .rank-range {
-    margin-bottom: 10px;
-  }
-}
-
-/* 视频宣讲弹窗样式 */
-.video-lecture-form {
-  padding: 20px 0;
-}
-
-.form-item {
-  margin-bottom: 20px;
-}
-
-.form-label {
-  font-weight: 500;
-  margin-bottom: 8px;
-  color: #606266;
-}
-
-.video-uploader {
-  width: 100%;
-}
-
-.upload-area {
-  width: 100%;
-  height: 120px;
-  border: 1px dashed #d9d9d9;
-  border-radius: 6px;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  transition: border-color 0.3s;
-}
-
-.upload-area:hover {
-  border-color: #67C23A;
-}
-
-.upload-text {
-  color: #909399;
-  font-size: 12px;
-  margin-top: 8px;
-}
-
-.el-icon {
-  font-size: 24px;
-  color: #909399;
-}
-
-/* AI实时对话抽屉样式 */
-.ai-chat-drawer {
-  --el-drawer-padding-primary: 0;
-}
-
-.ai-chat-content {
-  padding: 20px;
-  height: calc(100% - 120px);
-  overflow-y: auto;
-}
-
-.chat-settings {
-  margin-bottom: 30px;
-}
-
-.chat-actions {
-  display: flex;
-  gap: 10px;
-  margin: 20px 0;
-}
-
-.chat-questions {
-  margin-top: 20px;
-  min-height: 200px;
-  border: 1px dashed #dcdfe6;
-  border-radius: 4px;
-}
-
-.empty-questions {
-  height: 200px;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  color: #909399;
-}
-
-.tip-text {
-  font-size: 12px;
-  color: #c0c4cc;
-  margin-top: 8px;
-}
-
-.question-list {
-  padding: 16px;
-  background: #f5f7fa;
-  border-radius: 8px;
-  min-height: 200px;
-}
-
-.question-card {
-  background: #fff;
-  border-radius: 8px;
-  padding: 20px;
-  margin-bottom: 16px;
-  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
-}
-
-.question-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20px;
-  flex-wrap: wrap;
-  gap: 10px;
-}
-
-.question-title {
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
-
-.question-number {
-  color: #333;
-  font-weight: 500;
-}
-
-.question-actions {
-  display: flex;
-  align-items: center;
-  gap: 20px;
-}
-
-.weight-control, .answer-limit {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-}
-
-.weight-label, .answer-label {
-  color: #606266;
-  font-size: 14px;
-  white-space: nowrap;
-}
-
-:deep(.el-input-number) {
-  width: 100px;
-}
-
-:deep(.el-select) {
-  width: 120px;
-}
-
-.question-info {
-  background: #f8f9fa;
-  padding: 15px;
-  border-radius: 4px;
-}
-
-.info-item {
-  margin-bottom: 12px;
-  line-height: 1.6;
-}
-
-.info-item:last-child {
-  margin-bottom: 0;
-}
-
-.info-label {
-  color: #909399;
-  margin-right: 8px;
-  font-weight: 500;
-}
-
-/* 问题编辑弹框样式 */
-.question-form {
-  padding: 20px 0;
-}
-
-.form-item {
-  margin-bottom: 20px;
-}
-
-.form-label {
-  font-weight: 500;
-  margin-bottom: 8px;
-  color: #606266;
-  display: flex;
-  align-items: center;
-}
-
-.required {
-  color: #f56c6c;
-  margin-left: 4px;
-}
-
-.full-width {
-  width: 100%;
-}
-
-:deep(.el-input__count) {
-  background: transparent;
-}
-
-:deep(.el-textarea__count) {
-  background: transparent;
-}
-
-/* 资料收集对话框样式 */
-.data-collection-form {
-  padding: 20px;
-}
-
-/* 表单项样式 */
-.form-item {
-  margin-bottom: 24px;
-}
-
-/* 表单标签样式 */
-.form-label {
-  font-size: 14px;
-  color: #333;
-  font-weight: 500;
-  margin-bottom: 12px;
-  display: flex;
-  align-items: center;
-}
-
-/* 必填星号样式 */
-.required {
-  color: #ff4d4f;
-  margin-left: 4px;
-  font-size: 14px;
-}
-
-/* 开关样式 */
-:deep(.el-switch) {
-  margin-top: 8px;
-}
-
-/* 问题列表容器样式 */
-.question-list {
-  margin-top: 12px;
- /*  border: 1px solid #e4e7ed; */
-  border-radius: 4px;
-  padding: 16px;
-/*   background-color: #f8f9fa; */
-}
-
-/* 问题项样式 */
-.question-item {
-  margin-bottom: 16px;
-  display: flex;
-  align-items: center;
-  flex-direction: column;
-}
-
-/* 复选框组样式 */
-:deep(.el-checkbox-group) {
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-}
-
-/* 复选框样式 */
-:deep(.el-checkbox) {
-  margin-right: 0;
-  height: auto;
-}
-
-/* 复选框标签样式 */
-:deep(.el-checkbox__label) {
-  font-size: 14px;
-  color: #606266;
-}
-
-.data-collection-dialog {
-  .data-collection-form {
-    padding: 0;
-  }
-
-  .question-list {
-    padding: 0;
-  }
-
-  .question-item {
-    border: 1px solid #DCDFE6;
-    border-radius: 4px;
-    margin-bottom: 16px;
-    overflow: hidden;
-  }
-
-  .question-header {
-    background: #F5F7FA;
-    padding: 12px 16px;
-    border-bottom: 1px solid #DCDFE6;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-  }
-
-  .question-title {
-    font-size: 14px;
-    color: #303133;
-  }
-
-  .question-type {
-    display: flex;
-    align-items: center;
-    gap: 4px;
-    color: #909399;
-    font-size: 14px;
-    cursor: pointer;
-
-    .el-icon {
-      font-size: 12px;
-    }
-  }
-
-  .question-content {
-    padding: 16px;
-  }
-
-  .user-answer {
-    color: #909399;
-    font-size: 14px;
-    margin-bottom: 16px;
-  }
-
-  .question-actions {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    margin-bottom: 16px;
-    padding-bottom: 16px;
-    border-bottom: 1px solid #EBEEF5;
-  }
-
-  .action-left {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-
-    .action-text {
-      font-size: 14px;
-      color: #606266;
-    }
-  }
-
-  .action-right {
-    display: flex;
-    gap: 16px;
-
-    .action-icon {
-      font-size: 16px;
-      color: #909399;
-      cursor: pointer;
-
-      &:hover {
-        color: #409EFF;
-      }
-    }
-  }
-
-  .question-settings {
-    display: flex;
-    gap: 24px;
-  }
-
-  .setting-item {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    
-    span {
-      font-size: 14px;
-      color: #606266;
-    }
-  }
-
-  .add-question {
-    margin-top: 24px;
-    text-align: center;
-  }
-}
-
-/* 简历收集对话框样式 */
-.resume-collection-dialog {
-  .resume-collection-form {
-    padding: 20px 0;
-  }
-
-  .switch-label {
-    margin-left: 10px;
-    font-size: 14px;
-    color: #606266;
-  }
-
-  .resume-preview {
-    margin-top: 20px;
-    border: 1px solid #e4e7ed;
-    border-radius: 4px;
-  }
-
-  .preview-title {
-    padding: 10px 15px;
-    background-color: #f5f7fa;
-    border-bottom: 1px solid #e4e7ed;
-    font-weight: 500;
-    color: #606266;
-  }
-
-  .preview-content {
-    padding: 15px;
-  }
-
-  .preview-item {
-    display: flex;
-    align-items: center;
-    margin-bottom: 15px;
-  }
-
-  .item-label {
-    width: 80px;
-    color: #606266;
-  }
-
-  .item-input {
-    flex: 1;
-    height: 32px;
-    background-color: #f5f7fa;
-    border: 1px solid #dcdfe6;
-    border-radius: 4px;
-  }
-
-  .item-upload {
-    flex: 1;
-    height: 60px;
-    background-color: #f5f7fa;
-    border: 1px dashed #dcdfe6;
-    border-radius: 4px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-  }
-
-  .item-required {
-    color: #f56c6c;
-    margin-left: 5px;
-  }
-}
-
-/* 添加代码测试对话框样式 */
-.code-test-dialog {
-  .code-test-form {
-    padding: 20px 0;
-  }
-
-  .switch-label {
-    margin-left: 10px;
-    font-size: 14px;
-    color: #606266;
-  }
-
-  .test-paper-selector {
-    margin-top: 10px;
-  }
-
-  .empty-paper {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    padding: 30px;
-    border: 1px dashed #dcdfe6;
-    border-radius: 4px;
-    background-color: #f8f9fa;
-  }
-
-  .tip-text {
-    margin-top: 10px;
-    font-size: 12px;
-    color: #909399;
-  }
-
-  .selected-paper {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 15px;
-    border: 1px solid #dcdfe6;
-    border-radius: 4px;
-    background-color: #f8f9fa;
-  }
-
-  .paper-info {
-    flex: 1;
-  }
-
-  .paper-title {
-    font-weight: 500;
-    color: #303133;
-    margin-bottom: 5px;
-  }
-
-  .paper-meta {
-    display: flex;
-    gap: 15px;
-    font-size: 12px;
-    color: #909399;
-  }
-
-  .paper-actions {
-    display: flex;
-    gap: 10px;
-  }
-}
-
-/* 添加打字测试对话框样式 */
-.typing-test-dialog {
-  .typing-test-form {
-    padding: 20px;
-  }
-
-  .form-item {
-    margin-bottom: 24px;
-    position: relative;
-  }
-
-  .form-label {
-    font-weight: 500;
-    margin-bottom: 12px;
-    color: #606266;
-    font-size: 14px;
-  }
-
-  .article-selection {
-    margin-top: 10px;
-  }
-
-  .add-article-btn {
-    display: flex;
-    align-items: center;
-    gap: 5px;
-    font-size: 13px;
-  }
-
-  .time-input-container {
-    display: flex;
-    align-items: center;
-  }
-
-  .time-input {
-    width: 120px;
-  }
-
-  .time-unit {
-    margin-left: 10px;
-    color: #606266;
-  }
-
-  .preview-uploader {
-    width: 100px;
-  }
-
-  .upload-area {
-    width: 100px;
-    height: 100px;
-    border: 1px dashed #d9d9d9;
-    border-radius: 6px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    transition: border-color 0.3s;
-  }
-
-  .upload-area:hover {
-    border-color: #67C23A;
-  }
-
-  .upload-text {
-    color: #909399;
-    font-size: 12px;
-    margin-top: 8px;
-  }
-
-  .notes-counter {
-    position: absolute;
-    right: 10px;
-    bottom: -20px;
-    font-size: 12px;
-    color: #909399;
-  }
-
-  .criteria-options {
-    display: flex;
-    gap: 20px;
-  }
-  
-  /* 添加以下新样式 */
-  :deep(.el-radio) {
-    margin-right: 30px;
-  }
-  
-  :deep(.el-radio__label) {
-    font-size: 14px;
-  }
-  
-  :deep(.el-textarea) {
-    margin-bottom: 5px;
-  }
-  
-  :deep(.el-checkbox__label) {
-    font-size: 14px;
-  }
-  
-  :deep(.el-input) {
-    width: auto;
-  }
-}
-
-.title-edit-container {
-  display: flex;
-  align-items: flex-start;
-    gap: 10px;
-    flex-direction: column;
-}
-
-.edit-title-btn {
-  color: #67C23A;
-}
-
-/* 职位名称编辑相关样式 */
-.edit-title-btn {
-  margin-left: 10px;
-  font-size: 14px;
-  color: #409EFF;
-}
-
-.title-edit-container {
-  flex: 1;
-}
-
-.title-edit-actions {
-  display: flex;
-  justify-content: flex-end;
-  margin-top: 10px;
-  gap: 10px;
-}
-
-/* 确保在编辑模式下输入框有足够的宽度 */
-.title-edit-container :deep(.el-input) {
-  width: 100%;
-}
-
-.competency-tag {
-  margin-right: 8px;
-  margin-bottom: 8px;
-}
-
-.info-icon {
-  margin-left: 4px;
-  font-size: 14px;
-  color: #909399;
-  float: right;
-}
-
-:deep(.el-checkbox-group) {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 12px;
-}
-
-:deep(.el-checkbox) {
-  margin-right: 0;
-}
-</style> 

+ 0 - 66
src/views/position/list/api.ts

@@ -1,66 +0,0 @@
-import { request } from '/@/utils/service';
-import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-import { Session } from '/@/utils/storage';
-
-export const apiPrefix = '/api/system/job/create';
-
-export function GetPermission() {
-	return request({
-		url: apiPrefix + 'field_permission/',
-		method: 'get',
-	});
-}
-
-export function GetList(query: PageQuery) {
-	return request({
-		url: '/api/system/job/list',
-		method: 'get',
-		params: {...query,tenant_id:'1'},/* :Session.get('tenant_id').tenant_id ||  */
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: "/api/system/job/detail?id=" + id+"&tenant_id=1",
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: "/api/system/job/create",
-		method: 'post',
-		data: {...obj,tenant_id: '1'},/* Session.get('tenant_id').tenant_id || */
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: "/api/system/job/update",/* + obj.id+'/' */
-		method: 'post',
-		data: {...obj,tenant_id: '1'},
-	});
-}
-
-export function DelObj(id: DelReq) {
-	return request({
-		url: '/api/system/job/delete',/* + id + '/' */
-		method: 'delete',
-		data: { id,tenant_id: '1' },
-	});
-}
-
-export function BatchUpdateTags(data: any) {
-	return request({
-		url: '/api/system/competency/batch_bind',
-		method: 'post',
-		data
-	});
-}
-
-export function GetCompetencyList(query:any) {
-	return request({
-		url: '/api/system/competency/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}

+ 0 - 121
src/views/position/list/components/BatchTagsDialog.vue

@@ -1,121 +0,0 @@
-<template>
-  <el-dialog
-    v-model="dialogVisible"
-    title="批量绑定标签"
-    width="500px"
-    :close-on-click-modal="false"
-    @closed="handleClosed"
-  >
-    <el-form :model="form" label-width="100px">
-      <el-form-item label="选中职位">
-        <div class="selected-count">已选择 {{ selectedQuestions.length }} 个职位</div>
-      </el-form-item>
-      <el-form-item label="选择标签" required>
-        <el-select
-          v-model="form.tags"
-          multiple
-          filterable
-          placeholder="请选择要绑定的标签"
-          style="width: 100%"
-        >
-          <el-option
-            v-for="item in tagOptions"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <el-button @click="dialogVisible = false">取消</el-button>
-      <el-button type="primary" @click="handleConfirm" :loading="loading">确认绑定</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
-import { GetTagList } from './api';
-import { useBatchUpdateTags } from '../crud';
-import { successMessage, warningMessage } from '../../../../utils/message';
-
-const props = defineProps({
-  crudExpose: {
-    type: Object,
-    required: true
-  }
-});
-
-const { batchUpdateTags } = useBatchUpdateTags(props.crudExpose);
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-const selectedQuestions = ref<any[]>([]);
-const tagOptions = ref<any[]>([]);
-
-const form = reactive({
-  tags: [] as number[]
-});
-
-// 获取标签列表
-const fetchTags = async () => {
-  try {
-    const res = await GetTagList({ page: 1, limit: 1000, tenant_id: 1 });
-    console.log('res', res);
-    if (res.code === 2000 && res.data && res.data.items) {
-      tagOptions.value = res.data.items;
-    }
-  } catch (error) {
-    console.error('获取标签数据失败', error);
-  }
-};
-
-// 打开对话框
-const open = (selection: any[]) => {
-  selectedQuestions.value = selection;
-  dialogVisible.value = true;
-  form.tags = [];
-};
-
-// 确认绑定
-const handleConfirm = async () => {
-  if (form.tags.length === 0) {
-    warningMessage('请至少选择一个标签');
-    return;
-  }
-
-  loading.value = true;
-  try {
-    // 提取所有选中题目的ID
-    const questionIds = selectedQuestions.value.map(item => item.id);
-    await batchUpdateTags(questionIds, form.tags);
-    dialogVisible.value = false;
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 对话框关闭后的处理
-const handleClosed = () => {
-  form.tags = [];
-  selectedQuestions.value = [];
-};
-
-// 初始化时获取标签数据
-onMounted(() => {
-  fetchTags();
-});
-
-// 暴露方法给父组件
-defineExpose({
-  open
-});
-</script>
-
-<style scoped>
-.selected-count {
-  font-size: 14px;
-  color: #606266;
-}
-</style> 

+ 0 - 76
src/views/position/list/components/RoleDrawer.vue

@@ -1,76 +0,0 @@
-<template>
-	<el-drawer
-		v-model="RoleDrawer.drawerVisible"
-		title="权限配置"
-		direction="rtl"
-		size="80%"
-		:close-on-click-modal="false"
-		:before-close="RoleDrawer.handleDrawerClose"
-		:destroy-on-close="true"
-	>
-		<template #header>
-			<div>
-				当前授权角色:
-				<el-tag style="margin-right: 20px">{{ RoleDrawer.roleName }}</el-tag>
-				授权人员:
-				<el-button size="small" :icon="UserFilled" @click="handleUsers">{{ RoleDrawer.users.length }}</el-button>
-			</div>
-		</template>
-		<splitpanes class="default-theme" style="height: 100%">
-			<pane min-size="20" size="22">
-				<div class="pane-box">
-					<MenuTreeCom />
-				</div>
-			</pane>
-			<pane min-size="20">
-				<div class="pane-box">
-					<el-tabs v-model="activeName" class="demo-tabs">
-						<el-tab-pane label="接口权限" name="first"><MenuBtnCom /></el-tab-pane>
-						<el-tab-pane label="列字段权限" name="second"><MenuFieldCom /></el-tab-pane>
-					</el-tabs>
-				</div>
-			</pane>
-		</splitpanes>
-	</el-drawer>
-
-	<el-dialog v-model="dialogVisible" title="授权用户" width="700px" :close-on-click-modal="false">
-		<RoleUsersCom />
-	</el-dialog>
-</template>
-
-<script setup lang="ts">
-import { Splitpanes, Pane } from 'splitpanes';
-import 'splitpanes/dist/splitpanes.css';
-import { UserFilled } from '@element-plus/icons-vue';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { defineAsyncComponent, ref } from 'vue';
-import { RoleUsersStores } from '../stores/RoleUsersStores';
-
-const MenuTreeCom = defineAsyncComponent(() => import('./RoleMenuTree.vue'));
-const MenuBtnCom = defineAsyncComponent(() => import('./RoleMenuBtn.vue'));
-const MenuFieldCom = defineAsyncComponent(() => import('./RoleMenuField.vue'));
-const RoleUsersCom = defineAsyncComponent(() => import('./RoleUsers.vue'));
-const RoleDrawer = RoleDrawerStores(); // 抽屉参数
-const RoleUsers = RoleUsersStores(); // 角色-用户
-const activeName = ref('first');
-
-const dialogVisible = ref(false);
-
-const handleUsers = () => {
-	dialogVisible.value = true;
-	RoleUsers.get_all_users(); // 获取所有用户
-	RoleUsers.set_right_users(RoleDrawer.$state.users); // 设置已选中用户
-};
-</script>
-
-<style lang="scss" scoped>
-.pane-box {
-	width: 100vw; /* 视口宽度 */
-	height: 100vh; /* 视口高度 */
-	max-width: 100%; /* 确保不超过父元素的宽度 */
-	max-height: 100%; /* 确保不超过父元素的高度 */
-	overflow: auto; /* 当内容超出容器尺寸时显示滚动条 */
-	padding: 10px;
-	background-color: #fff;
-}
-</style>

+ 0 - 186
src/views/position/list/components/RoleMenuBtn.vue

@@ -1,186 +0,0 @@
-<template>
-	<div class="pccm-item" v-if="RoleMenuBtn.$state.length > 0">
-		<div class="menu-form-alert">配置操作功能接口权限,配置数据权限点击小齿轮</div>
-		<el-checkbox v-for="btn in RoleMenuBtn.$state" :key="btn.id" v-model="btn.isCheck" @change="handleCheckChange(btn)">
-			<div class="btn-item">
-				{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
-				<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(btn)">
-					<el-icon>
-						<Setting />
-					</el-icon>
-				</span>
-			</div>
-		</el-checkbox>
-	</div>
-	<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false" :before-close="handleDialogClose">
-		<div class="pc-dialog">
-			<el-select v-model="selectBtn.data_range" @change="handlePermissionRangeChange" placeholder="请选择">
-				<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
-			</el-select>
-			<el-tree-select
-				v-show="selectBtn.data_range === 4"
-				node-key="id"
-				v-model="selectBtn.dept"
-				:props="defaultTreeProps"
-				:data="deptData"
-				multiple
-				check-strictly
-				:render-after-expand="false"
-				show-checkbox
-				class="dialog-tree"
-			/>
-		</div>
-		<template #footer>
-			<div>
-				<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
-				<el-button @click="handleDialogClose"> 取消</el-button>
-			</div>
-		</template>
-	</el-dialog>
-</template>
-
-<script setup lang="ts">
-import { computed, onMounted, ref } from 'vue';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleMenuBtnStores } from '../stores/RoleMenuBtnStores';
-import { RoleMenuTreeStores } from '../stores/RoleMenuTreeStores';
-import { RoleMenuBtnType } from '../types';
-import { getRoleToDeptAll, setRoleMenuBtn, setRoleMenuBtnDataRange } from './api';
-import XEUtils from 'xe-utils';
-import { ElMessage } from 'element-plus';
-const RoleDrawer = RoleDrawerStores(); // 角色-菜单
-const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
-const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
-const dialogVisible = ref(false);
-// 选中的按钮
-const selectBtn = ref<RoleMenuBtnType>({
-	id: 0,
-	menu_btn_pre_id: 0,
-	/** 是否选中 */
-	isCheck: false,
-	/** 按钮名称 */
-	name: '',
-	/** 数据权限范围 */
-	data_range: 0,
-	dept: [],
-});
-/**
- * 数据权限范围
- */
-const dataPermissionRange = ref([
-	{ label: '仅本人数据权限', value: 0 },
-	{ label: '本部门及以下数据权限', value: 1 },
-	{ label: '本部门数据权限', value: 2 },
-	{ label: '全部数据权限', value: 3 },
-	{ label: '自定数据权限', value: 4 },
-]);
-/**
- * 自定义数据权限的部门树配置
- */
-const defaultTreeProps = {
-	children: 'children',
-	label: 'name',
-	value: 'id',
-};
-
-/**
- * 自定数据权限下拉选择事件
- */
-const handlePermissionRangeChange = async (val: number) => {
-	if (val < 4) {
-		selectBtn.value.dept = [];
-	}
-};
-/**
- * 格式化按钮数据范围
- */
-const formatDataRange = computed(() => {
-	return function (datarange: number) {
-		const datarangeitem = XEUtils.find(dataPermissionRange.value, (item: any) => {
-			if (item.value === datarange) {
-				return item.label;
-			}
-		});
-		return datarangeitem.label;
-	};
-});
-/**
- * 勾选按钮
- */
-const handleCheckChange = async (btn: RoleMenuBtnType) => {
-	const put_data = {
-		isCheck: btn.isCheck,
-		roleId: RoleDrawer.roleId,
-		menuId: RoleMenuTree.id,
-		btnId: btn.id,
-	};
-	const { data, msg } = await setRoleMenuBtn(put_data);
-	RoleMenuBtn.updateState(data);
-	ElMessage({ message: msg, type: 'success' });
-};
-
-/**
- * 按钮-数据范围确定
- */
-const handleDialogConfirm = async () => {
-	const { data, msg } = await setRoleMenuBtnDataRange(selectBtn.value);
-	selectBtn.value = data;
-	dialogVisible.value = false;
-	ElMessage({ message: msg, type: 'success' });
-};
-/**
- * 数据范围关闭
- */
-const handleDialogClose = () => {
-	dialogVisible.value = false;
-};
-
-/**
- * 齿轮点击
- */
-const handleSettingClick = async (btn: RoleMenuBtnType) => {
-	selectBtn.value = btn;
-	dialogVisible.value = true;
-};
-
-/**
- * 部门数据
- *
- */
-const deptData = ref<number[]>([]);
-// 页面打开后获取列表数据
-onMounted(async () => {
-	const res = await getRoleToDeptAll({ role: RoleDrawer.roleId, menu_button: selectBtn.value.id });
-	const depts = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
-	deptData.value = depts;
-});
-</script>
-
-<style lang="scss" scoped>
-.pccm-item {
-	margin-bottom: 10px;
-	.menu-form-alert {
-		color: #fff;
-		line-height: 24px;
-		padding: 8px 16px;
-		margin-bottom: 20px;
-		border-radius: 4px;
-		background-color: var(--el-color-primary);
-	}
-}
-// .el-checkbox {
-// 	width: 200px;
-// }
-.btn-item {
-	display: flex;
-	align-items: center;
-	justify-content: center; /* 水平居中 */
-	.el-icon {
-		margin-left: 5px;
-	}
-}
-.dialog-tree {
-	width: 100%;
-	margin-top: 20px;
-}
-</style>

+ 0 - 118
src/views/position/list/components/RoleMenuField.vue

@@ -1,118 +0,0 @@
-<template>
-	<div class="pccm-item" v-if="RoleMenuField.$state.length > 0">
-		<div class="menu-form-alert">
-			<el-button size="small" @click="handleSaveField">保存 </el-button>
-			配置数据列字段权限
-		</div>
-
-		<ul class="columns-list">
-			<li class="columns-head">
-				<div class="width-txt">
-					<span>字段</span>
-				</div>
-				<div v-for="(head, hIndex) in RoleMenuFieldHeader.$state" :key="hIndex" class="width-check">
-					<el-checkbox v-model="head.checked" @change="handleColumnChange($event, head.value, head.disabled)">
-						<span>{{ head.label }}</span>
-					</el-checkbox>
-				</div>
-			</li>
-			<div class="columns-content">
-				<li v-for="(c_item, c_index) in RoleMenuField.$state" :key="c_index" class="columns-item">
-					<div class="width-txt">{{ c_item.title }}</div>
-					<div v-for="(col, cIndex) in RoleMenuFieldHeader.$state" :key="cIndex" class="width-check">
-						<el-checkbox v-model="c_item[col.value]" class="ci-checkout" :disabled="c_item[col.disabled]"></el-checkbox>
-					</div>
-				</li>
-			</div>
-		</ul>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { ElMessage } from 'element-plus';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleMenuFieldStores, RoleMenuFieldHeaderStores } from '../stores/RoleMenuFieldStores';
-import { setRoleMenuField } from './api';
-const RoleMenuField = RoleMenuFieldStores();
-const RoleMenuFieldHeader = RoleMenuFieldHeaderStores();
-const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
-/** 全选 */
-const handleColumnChange = (val: boolean, btnType: string, disabledType: string) => {
-	for (const iterator of RoleMenuField.$state) {
-		iterator[btnType] = iterator[disabledType] ? iterator[btnType] : val;
-	}
-};
-const handleSaveField = async () => {
-	const res = await setRoleMenuField(RoleDrawer.$state.roleId, RoleMenuField.$state);
-	ElMessage({ message: res.msg, type: 'success' });
-};
-</script>
-
-<style lang="scss" scoped>
-.pccm-item {
-	margin-bottom: 10px;
-	.menu-form-alert {
-		color: #fff;
-		line-height: 24px;
-		padding: 8px 16px;
-		margin-bottom: 20px;
-		border-radius: 4px;
-		background-color: var(--el-color-primary);
-	}
-	.menu-form-btn {
-		margin-left: 10px;
-		height: 40px;
-		padding: 8px 16px;
-		margin-bottom: 20px;
-	}
-
-	.btn-item {
-		display: flex;
-		align-items: center;
-
-		span {
-			margin-left: 5px;
-		}
-	}
-
-	.columns-list {
-		.width-txt {
-			width: 200px;
-		}
-
-		.width-check {
-			width: 100px;
-		}
-
-		.width-icon {
-			cursor: pointer;
-		}
-
-		.columns-head {
-			display: flex;
-			align-items: center;
-			padding: 6px 0;
-			border-bottom: 1px solid #ebeef5;
-			box-sizing: border-box;
-
-			span {
-				font-weight: 900;
-			}
-		}
-		.columns-content {
-			max-height: calc(100vh - 240px); /* 视口高度 */
-			overflow-y: auto; /* 当内容超出高度时显示垂直滚动条 */
-			.columns-item {
-				display: flex;
-				align-items: center;
-				padding: 6px 0;
-				box-sizing: border-box;
-
-				.ci-checkout {
-					height: auto !important;
-				}
-			}
-		}
-	}
-}
-</style>

+ 0 - 81
src/views/position/list/components/RoleMenuTree.vue

@@ -1,81 +0,0 @@
-<template>
-	<el-tree
-		ref="treeRef"
-		:data="menuData"
-		:props="defaultTreeProps"
-		:default-checked-keys="menuDefaultCheckedKeys"
-		@check-change="handleMenuChange"
-		@node-click="handleMenuClick"
-		node-key="id"
-		check-strictly
-		highlight-current
-		show-checkbox
-		default-expand-all
-	>
-	</el-tree>
-</template>
-
-<script setup lang="ts">
-import { onMounted, ref } from 'vue';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleMenuTreeStores } from '../stores/RoleMenuTreeStores';
-import { RoleMenuBtnStores } from '../stores/RoleMenuBtnStores';
-import { RoleMenuFieldStores } from '../stores/RoleMenuFieldStores';
-import { RoleMenuFieldHeaderStores } from '../stores/RoleMenuFieldStores';
-import { getRoleMenu, getRoleMenuBtnField, setRoleMenu } from './api';
-import { ElMessage } from 'element-plus';
-import XEUtils from 'xe-utils';
-import { RoleMenuTreeType } from '../types';
-
-const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
-const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
-const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
-const RoleMenuField = RoleMenuFieldStores(); // 角色-菜单-列字段
-const RoleMenuFieldHeader = RoleMenuFieldHeaderStores();// 角色-菜单-列字段
-const menuData = ref<RoleMenuTreeType[]>([]); // 菜单列表数据
-const menuDefaultCheckedKeys = ref<(number | string | undefined)[]>([]); // 默认选中的菜单列表
-// 菜单配置
-const defaultTreeProps = {
-	children: 'children',
-	label: 'name',
-	value: 'id',
-};
-
-/**
- * 菜单复选框选中
- * @param node:当前节点的 Node 对象
- * @param checked:布尔值,表示当前节点是否被选中
- */
-const handleMenuChange = (node: any, checked: boolean) => {
-	setRoleMenu({ roleId: RoleDrawer.roleId, menuId: node.id, isCheck: checked }).then((res: any) => {
-		ElMessage({ message: res.msg, type: 'success' });
-	});
-};
-/**
- * 菜单点击事件
- */
-const handleMenuClick = async (selectNode: RoleMenuTreeType) => {
-	if (!selectNode.is_catalog) {
-		RoleMenuTree.setRoleMenuTree(selectNode); // 更新当前选中的菜单
-		// 获取当前菜单的按钮列表
-		const { data } = await getRoleMenuBtnField({
-			roleId: RoleDrawer.roleId,
-			menuId: selectNode.id,
-		});
-		RoleMenuBtn.setState(data.menu_btn); // 更新按钮列表
-		RoleMenuField.setState(data.menu_field); // 更新列字段列表
-	} else {
-		RoleMenuBtn.setState([]); // 更新按钮列表
-		RoleMenuField.setState([]); // 更新列字段列表
-	}
-	RoleMenuFieldHeader.$reset()
-};
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	menuData.value = await getRoleMenu({ roleId: RoleDrawer.roleId });
-	menuDefaultCheckedKeys.value = XEUtils.toTreeArray(menuData.value)
-		.filter((i) => i.isCheck)
-		.map((i) => i.id);
-});
-</script>

+ 0 - 35
src/views/position/list/components/RoleUsers.vue

@@ -1,35 +0,0 @@
-<template>
-	<el-transfer
-		v-model="RoleUsers.$state.right_users"
-		filterable
-		:titles="['未授权用户', '已授权用户']"
-		:data="RoleUsers.$state.all_users"
-		:props="{
-			key: 'id',
-			label: 'name',
-		}"
-		@change="handleChange"
-	/>
-</template>
-
-<script lang="ts" setup>
-import { ElMessage } from 'element-plus';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleUsersStores } from '../stores/RoleUsersStores';
-import { setRoleUsers } from './api';
-const RoleDrawer = RoleDrawerStores(); // 抽屉参数
-const RoleUsers = RoleUsersStores(); // 角色-用户
-
-/**
- *
- * @param value 当前右侧选中的用户
- * @param direction 移动的方向
- * @param movedKeys 移动的用户
- */
-const handleChange = (value: number[] | string[], direction: 'left' | 'right', movedKeys: string[] | number[]) => {
-	setRoleUsers(RoleDrawer.$state.roleId, { direction, movedKeys }).then((res:any) => {
-		RoleDrawer.set_state(res.data)
-		ElMessage({ message: res.msg, type: 'success' });
-	});
-};
-</script>

+ 0 - 129
src/views/position/list/components/api.ts

@@ -1,129 +0,0 @@
-import { request } from '/@/utils/service';
-import XEUtils from 'xe-utils';
-/**
- * 获取 角色-菜单
- * @param query
- */
-export function getRoleMenu(query: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/get_role_menu/',
-		method: 'get',
-		params: query,
-	}).then((res: any) => {
-		return XEUtils.toArrayTree(res.data, { key: 'id', parentKey: 'parent', children: 'children', strict: false });
-	});
-}
-/**
- * 设置 角色-菜单
- * @param data
- * @returns
- */
-export function setRoleMenu(data: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/set_role_menu/',
-		method: 'put',
-		data,
-	});
-}
-/**
- * 获取 角色-菜单-按钮-列字段
- * @param query
- */
-export function getRoleMenuBtnField(query: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/get_role_menu_btn_field/',
-		method: 'get',
-		params: query,
-	});
-}
-
-/**
- * 设置 角色-菜单-按钮
- * @param data
- */
-export function setRoleMenuBtn(data: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/set_role_menu_btn/',
-		method: 'put',
-		data,
-	});
-}
-
-/**
- * 设置 角色-菜单-列字段
- * @param data
- */
-export function setRoleMenuField(roleId: string | number | undefined, data: object) {
-	return request({
-		url: `/api/system/role_menu_button_permission/${roleId}/set_role_menu_field/`,
-		method: 'put',
-		data,
-	});
-}
-
-/**
- * 设置 角色-菜单-按钮-数据权限
- * @param query
- * @returns
- */
-export function setRoleMenuBtnDataRange(data: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/set_role_menu_btn_data_range/',
-		method: 'put',
-		data,
-	});
-}
-
-/**
- * 获取当前用户角色下所能授权的部门
- * @param query
- * @returns
- */
-export function getRoleToDeptAll(query: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/role_to_dept_all/',
-		method: 'get',
-		params: query,
-	});
-}
-
-/**
- * 获取所有用户
- * @param query
- * @returns
- */
-export function getAllUsers() {
-	return request({
-		url: '/api/system/user/',
-		method: 'get',
-		params: { limit: 999 },
-	}).then((res: any) => {
-		return XEUtils.map(res.data, (item: any) => {
-			return {
-				id: item.id,
-				name: item.name,
-			};
-		});
-	});
-}
-
-/**
- * 设置角色-用户
- * @param query
- * @returns
- */
-export function setRoleUsers(roleId: string | number | undefined, data: object) {
-	return request({
-		url: `/api/system/role/${roleId}/set_role_users/`,
-		method: 'put',
-		data,
-	});
-}
-
-export function GetTagList(query: any) {
-	return request({
-		url: '/api/system/competency/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}

+ 0 - 630
src/views/position/list/crud.tsx

@@ -1,630 +0,0 @@
-import { CreateCrudOptionsProps, CreateCrudOptionsRet, AddReq, DelReq, EditReq, dict, compute } from '@fast-crud/fast-crud';
-import * as api from './api';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage, warningMessage } from '../../../utils/message';
-import { auth } from '/@/utils/authFunction';
-
-/* interface PositionContext {
-	router: any;
-	selectedRows: any[];
-	openBatchTagsDialog: (selection: any[]) => void;
-} */
-
-/**
- *
- * @param crudExpose:index传递过来的示例
- * @param context:index传递过来的自定义参数
- * @returns
- */
-export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const pageRequest = async (query: any) => {
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj(row.id);
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			pagination: {
-				show: true,
-			},
-			search: {
-				show: true,
-				buttons: {
-					search: {
-						size: 'small',
-						col:{ span:3},
-					},
-					reset: {
-						size: 'small',
-						col:{ span:3},
-					},
-				},
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						size: 'small',
-						show: auth('role:Create'),
-						click: () => {
-							context.router.push('/position/create');
-						}
-					},
-					// 添加批量绑定标签按钮
-					batchBindTags: {
-						text: '批量绑定标签',
-						type: 'primary',
-						size: 'small',
-						show: true,
-						order: 2,
-						click: () => {
-							// 使用存储的选中行
-							const selection = context.selectedRows || [];
-							console.log('选中的行:', selection);
-							
-							if (!selection || selection.length === 0) {
-								warningMessage('请先选择要操作的职位');
-								return;
-							}
-							console.log('context', context);
-							// 打开批量绑定标签对话框
-							context.openBatchTagsDialog(selection);
-						},
-					},
-					// addDialog: {
-					// 	text: '快速添加',
-					// 	icon: 'Plus',
-					// 	show: auth('role:Create'),
-					// 	order: 2
-					// },
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 320,
-				buttons: {
-					view: {
-						show: true,
-						icon:"View",
-						type:"text"
-					},
-					edit: {
-						show: auth('role:Update'),
-						icon:"Edit",
-						type:"text",
-						click: (ctx) => {
-							context.router.push(`/position/detail?id=${ctx.row.id}`);
-						}
-					},
-					remove: {
-						show: auth('role:Delete'),
-						icon:"Delete",
-						type:"text"
-					},
-					qrcode: {
-						text: '小程序码',
-						type: "text",
-						icon: '',
-						click: (ctx) => {
-							context.generateQRCode(ctx.row);
-						},
-						order: 4
-					},
-					
-					/* permission: {
-						type: 'primary',
-						text: '权限配置',
-						show: auth('role:Permission'),
-						click: (clickContext: any): void => {
-							const { row } = clickContext;
-							context.RoleDrawer.handleDrawerOpen(row);
-							context.RoleMenuBtn.setState([]);
-							context.RoleMenuField.setState([]);
-						},
-					}, */
-				},
-			},
-			form: {
-				col: { span: 24 },
-				labelWidth: '100px',
-				wrapper: {
-					is: 'el-dialog',
-					width: '600px',
-				},
-			},
-			table: {
-				onSelectionChange: (selection: any[]) => {
-					context.selectedRows = selection;
-					console.log('选择变化:', selection);
-				}
-			},
-			columns: {
-				_selection: {
-					title: '选择',
-					form: { show: false },
-					column: {
-						type: 'selection',
-						align: 'center',
-						width: 50,
-						fixed: 'left',
-						columnSetDisabled: true,
-					},
-				},
-				/* _index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				}, */
-				id: {
-					title: 'ID',
-					column: { show: false },
-					search: { show: false },
-					form: { show: false },
-				},
-				title: {
-					title: '职位名称',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						minWidth: 120,
-						sortable: 'custom',
-					},
-					form: {
-						rules: [{ required: true, message: '角色名称必填' }],
-						component: {
-							placeholder: '请输入角色名称',
-						},
-					},
-				},
-				/* key: {
-					title: '权限标识',
-					search: { show: false },
-					column: {
-						minWidth: 120,
-						sortable: 'custom',
-						columnSetDisabled: true,
-					},
-					form: {
-						rules: [{ required: true, message: '权限标识必填' }],
-						component: {
-							placeholder: '输入权限标识',
-						},
-					},
-					valueBuilder(context) {
-						const { row, key } = context;
-						return row[key];
-					},
-				},
-				sort: {
-					title: '排序',
-					search: { show: false },
-					type: 'number',
-					column: {
-						minWidth: 90,
-						sortable: 'custom',
-					},
-					form: {
-						rules: [{ required: true, message: '排序必填' }],
-						value: 1,
-					},
-				},*/
-				job_type: {
-					title: '职位类型',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					type: 'dict-select',
-					column: {
-						width: 100,
-					},
-					dict: dict({
-						data: [
-							{ value:0, label: '全职' },
-							{ value:1, label: '兼职' },
-							{ value: 2, label: '实习' },
-							{ value: 3, label: "其他" }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '职位类型必填' }],
-						component: {
-							placeholder: '职位类型',
-						},
-					},
-				},
-				competency_tags: {
-					title: '胜任力标签',
-					search: { 
-						show: true,
-						size: 'small',
-						col: { span: 3 },
-						component: {
-							props: {
-								multiple: false, // 在搜索表单中使用单选模式
-								filterable: true,
-							}
-						},
-					},
-					type: 'dict-select',
-					column: {
-						minWidth: 180,
-						component: {
-							// 自定义渲染组件,显示彩色标签
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								if (!row.competency_tags || row.competency_tags.length === 0) return <span>-</span>;
-								
-								const displayTags = row.competency_tags.slice(0, 2); // 只取前两个标签
-								const remainingCount = row.competency_tags.length - 2; // 计算剩余标签数量
-								
-								return (
-									<div style="display: flex; gap: 4px; align-items: center;">
-										{displayTags.map((tag: any) => (
-											<el-tag
-												key={tag.id}
-												type="warning"
-												effect="plain"
-												size="mini"
-												style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
-												title={tag.name}
-											>
-												{tag.name.length > 4 ? tag.name.slice(0, 4) + '...' : tag.name}
-											</el-tag>
-										))}
-									{remainingCount > 0 && (
-											<el-tooltip
-												placement="top"
-												effect="light"
-												popper-class="tag-tooltip"
-											>
-												{{
-													default: () => (
-														<el-tag
-															type="info"
-															effect="plain"
-															size="mini"
-														>
-															+{remainingCount}
-														</el-tag>
-													),
-													content: () => (
-														<div>
-															<div style="font-weight: bold; margin-bottom: 5px">剩余{remainingCount}个标签:</div>
-															{row.competency_tags.slice(2).map((tag: any) => (
-																<div key={tag.id} style="margin: 3px 0">{tag.name}</div>
-															))}
-														</div>
-													)
-												}}
-											</el-tooltip>
-										)}
-									</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: {
-						rules: [{ required: true, message: '胜任力标签必填' }],
-					},
-				},
-				salary_range: {
-					title: '薪资范围',
-					search: { show: false,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						rules: [{ required: true, message: '薪资范围必填' }],
-						component: {
-							placeholder: '请输入薪资范围',
-						},
-					},
-				},
-				location: {
-					title: '工作地点',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						minWidth: 120,
-						formatter: ({ row }) => {
-							if (Array.isArray(row.location)) {
-								return row.location.join(' ');
-							}
-							if (row.province && row.city) {
-								return row.province + ' ' + row.city + (row.district ? ' ' + row.district : '');
-							}
-							return row.location || '';
-						}
-					},
-					form: {
-						component: {
-							name: 'el-cascader',
-							props: {
-								options: [
-									{
-										value: '上海市',
-										label: '上海市',
-										children: [
-											{
-												value: '浦东新区',
-												label: '浦东新区',
-												children: [
-													{ value: '张江', label: '张江' },
-													{ value: '金桥', label: '金桥' },
-													{ value: '陆家嘴', label: '陆家嘴' }
-												]
-											},
-											{
-												value: '徐汇区',
-												label: '徐汇区',
-												children: [
-													{ value: '漕河泾', label: '漕河泾' },
-													{ value: '徐家汇', label: '徐家汇' }
-												]
-											},
-											// 其他区域...
-										]
-									},
-									{
-										value: '北京市',
-										label: '北京市',
-										children: [
-											{
-												value: '海淀区',
-												label: '海淀区',
-												children: [
-													{ value: '中关村', label: '中关村' },
-													{ value: '上地', label: '上地' }
-												]
-											},
-											{
-												value: '朝阳区',
-												label: '朝阳区',
-												children: [
-													{ value: 'CBD', label: 'CBD' },
-													{ value: '望京', label: '望京' }
-												]
-											},
-											// 其他区域...
-										]
-									},
-									// 其他省市...
-								],
-								props: {
-									expandTrigger: 'hover',
-									checkStrictly: false
-								},
-								placeholder: '请选择工作地点',
-								clearable: true,
-								showAllLevels: true
-							},
-							on: {
-								change: (value) => {
-									console.log('地址选择变化:', value);
-								}
-							}
-						},
-						valueResolve: (form: any) => {
-							if (form.location && Array.isArray(form.location) && form.location.length > 0) {
-								form.province = form.location[0];
-								form.city = form.location[1] || '';
-								form.district = form.location[2] || '';
-								form.location = form.location.join(' ');
-							}
-						},
-						valueBuilder: (form: any) => {
-							if (form.province && form.city) {
-								form.location = [form.province, form.city];
-								if (form.district) {
-									form.location.push(form.district);
-								}
-							} else if (typeof form.location === 'string' && form.location) {
-								const parts = form.location.split(' ');
-								if (parts.length > 0) {
-									form.location = parts;
-								}
-							}
-						}
-					},
-				},
-				department: {
-					title: '所属部门',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						rules: [{ required: true, message: '所属部门必填' }],
-						component: {
-							placeholder: '请输入所属部门',
-						},
-					},
-				},
-				status: {
-					title: '状态',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					type: 'dict-select',
-					column: {
-						minWidth: 100,
-					},
-					dict: dict({
-						data: [
-							{ value: 0, label: '未发布' },
-							{ value: 1, label: '已发布' },
-							{ value: 2, label: '已结束' }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '职位类型必填' }],
-					},
-				},
-				end_date: {
-					title: '截止日期',
-					type: 'datetime',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						minWidth: 150,
-						sortable: 'custom',
-					},
-					form: {
-						rules: [{ required: true, message: '截止日期必填' }],
-						component: {
-							type: 'datetime',
-							valueFormat: 'YYYY-MM-DD HH:mm:ss',
-							placeholder: '请选择截止日期',
-						},
-					},
-				},
-				requirements: {
-					title: '职位要求',
-					search: { show: false,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						showOverflowTooltip: true,
-						width: 120,
-						sortable: 'custom',
-						/* minWidth: 120, */
-						formatter: ({ row }) => {
-							// 创建一个临时元素来解析HTML
-							const div = document.createElement('div');
-							div.innerHTML = row.requirements || '';
-							// 返回纯文本内容
-							return div.textContent || div.innerText || '';
-						}
-					},
-					form: {
-						component: {
-							type: 'textarea',
-							rows: 4,
-						},
-					},
-				},
-				description: {
-					title: '职位描述',
-					search: { show: false },
-					column: {
-						showOverflowTooltip: true,
-						width: 120,
-						sortable: 'custom',
-						/* minWidth: 120, */
-						formatter: ({ row }) => {
-							const div = document.createElement('div');
-							div.innerHTML = row.description || '';
-							return div.textContent || div.innerText || '';
-						}
-					},
-					form: {
-						component: {
-							type: 'textarea',
-							rows: 4,
-						},
-					},
-				},
-				province: {
-					title: '省份',
-					column: { show: false },
-					form: { show: false }
-				},
-				city: {
-					title: '城市',
-					column: { show: false },
-					form: { show: false }
-				},
-				district: {
-					title: '区域',
-					column: { show: false },
-					form: { show: false }
-				},
-			},
-		},
-	};
-};
-export const useBatchUpdateTags = (crudExpose: any) => {
-	const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
-		try {
-			const res = await api.BatchUpdateTags({
-				position_ids: questionIds,
-				tag_ids: tagIds,
-				tenant_id: 1
-			});
-			if (res.code === 2000) {
-				successMessage('批量更新标签成功');
-				// 刷新列表
-				crudExpose.doRefresh();
-			}
-		} catch (error) {
-			console.error('批量更新标签失败', error);
-		}
-	};
-	
-	return { batchUpdateTags };
-};

+ 0 - 128
src/views/position/list/index.vue

@@ -1,128 +0,0 @@
-<template>
-	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
-		<PermissionDrawerCom />
-		<!-- 小程序二维码对话框 -->
-		<el-dialog v-model="qrCodeVisible" title="小程序二维码" width="400px" align-center>
-			<div class="qrcode-container">
-				<img v-if="qrCodeUrl" :src="qrCodeUrl" alt="小程序二维码" />
-				<div v-else class="loading">生成中...</div>
-			</div>
-		</el-dialog>
-		<BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="role">
-import { defineAsyncComponent, onMounted, ref, reactive } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions, useBatchUpdateTags } from './crud';
-import { RoleDrawerStores } from './stores/RoleDrawerStores';
-import { RoleMenuBtnStores } from './stores/RoleMenuBtnStores';
-import { RoleMenuFieldStores } from './stores/RoleMenuFieldStores';
-import { RoleUsersStores } from './stores/RoleUsersStores';
-import { ElMessage } from 'element-plus';
-import axios from 'axios';
-import { useRouter } from 'vue-router';
-import { successMessage } from '../../../utils/message';
-import BatchTagsDialog from './components/BatchTagsDialog.vue';
-
-const PermissionDrawerCom = defineAsyncComponent(() => import('./components/RoleDrawer.vue'));
-
-// 小程序二维码相关状态
-const qrCodeVisible = ref(false);
-const qrCodeUrl = ref('');
-
-const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
-const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单
-const RoleMenuField = RoleMenuFieldStores();// 角色-菜单-字段
-const RoleUsers = RoleUsersStores();// 角色-用户
-
-// 获取路由实例
-const router = useRouter();
-
-const batchTagsDialogRef = ref();
-
-const selectedRows = ref([]);
-
-// 生成小程序二维码
-async function generateQRCode(row: { id: string | number }) {
-	try {
-		qrCodeVisible.value = true;
-		qrCodeUrl.value = ''; // 清空之前的二维码
-		
-		// 调用系统API获取二维码
-		const response = await axios.post(`${import.meta.env.VITE_API_URL}/api/system/wechat/qrcode`, {
-			scene: `${row.id}`,
-			page: 'pages/index/index',
-			width: 430,
-			auto_color: false,
-			is_hyaline: false
-		}, {
-			responseType: 'blob' // 接收二进制数据
-		});
-		
-		// 将二进制数据转换为URL
-		const blob = new Blob([response.data], { type: 'image/png' });
-		qrCodeUrl.value = URL.createObjectURL(blob);
-	} catch (error) {
-		console.error('生成小程序码失败:', error);
-		ElMessage.error('生成小程序二维码失败,请稍后重试');
-		qrCodeVisible.value = false;
-	}
-}
-
-// 创建 crud 配置
-const { crudBinding, crudRef, crudExpose } = useFs({
-	createCrudOptions,
-	context: { 
-		RoleDrawer, 
-		RoleMenuBtn, 
-		RoleMenuField,
-		generateQRCode,
-		router,
-		$message: {
-			warning: (msg: string) => successMessage(msg)
-		},
-		selectedRows: selectedRows,
-		openBatchTagsDialog: (selection: any) => {
-		batchTagsDialogRef.value.open(selection);
-	}
-	},
-});
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 刷新
-	crudExpose.doRefresh();
-	// 获取全部用户
-	RoleUsers.get_all_users();
-	
-	// 获取标签列表
-	/* await fetchTags(); */
-});
-</script>
-
-<style scoped>
-.qrcode-container {
-	display: flex;
-	justify-content: center;
-	align-items: center;
-	min-height: 300px;
-}
-.qrcode-container img {
-	max-width: 100%;
-}
-.loading {
-	font-size: 16px;
-	color: #909399;
-}
-.dialog-footer {
-	margin-top: 20px;
-	text-align: right;
-}
-/* 添加标签选择的样式 */
-.el-select-dropdown__item {
-	padding: 8px 12px;
-}
-</style>

+ 0 - 37
src/views/position/list/stores/RoleDrawerStores.ts

@@ -1,37 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleDrawerType } from '../types';
-/**
- * 权限配置:抽屉
- */
-const initialState: RoleDrawerType = {
-	drawerVisible: false,
-	roleId: undefined,
-	roleName: undefined,
-	users: [],
-};
-
-export const RoleDrawerStores = defineStore('RoleDrawerStores', {
-	state: (): RoleDrawerType => ({
-		...initialState,
-	}),
-	actions: {
-		/**
-		 * 打开权限修改抽屉
-		 */
-		handleDrawerOpen(row: any) {
-			this.drawerVisible = true;
-			this.set_state(row);
-		},
-		set_state(row: any) {
-			this.roleName = row.name;
-			this.roleId = row.id;
-			this.users = row.users;
-		},
-		/**
-		 * 关闭权限修改抽屉
-		 */
-		handleDrawerClose() {
-			Object.assign(this.$state, initialState);
-		},
-	},
-});

+ 0 - 24
src/views/position/list/stores/RoleMenuBtnStores.ts

@@ -1,24 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleMenuBtnType } from '../types';
-/**
- * 权限配置:角色-菜单-按钮
- */
-
-export const RoleMenuBtnStores = defineStore('RoleMenuBtnStores', {
-	state: (): RoleMenuBtnType[] => [],
-	actions: {
-		/**
-		 * 初始化
-		 */
-		setState(data: RoleMenuBtnType[]) {
-			this.$state = data;
-			this.$state.length = data.length;
-		},
-		updateState(data: RoleMenuBtnType) {
-			const index = this.$state.findIndex((item) => item.id === data.id);
-			if (index !== -1) {
-				this.$state[index] = data;
-			}
-		},
-	},
-});

+ 0 - 24
src/views/position/list/stores/RoleMenuFieldStores.ts

@@ -1,24 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleMenuFieldType, RoleMenuFieldHeaderType } from '../types';
-/**
- * 权限配置:角色-菜单-列字段
- */
-
-export const RoleMenuFieldStores = defineStore('RoleMenuFieldStores', {
-	state: (): RoleMenuFieldType[] => [],
-	actions: {
-		/** 重置 */
-		setState(data: RoleMenuFieldType[]) {
-			this.$state = data;
-			this.$state.length = data.length;
-		},
-	},
-});
-
-export const RoleMenuFieldHeaderStores = defineStore('RoleMenuFieldHeaderStores', {
-	state: (): RoleMenuFieldHeaderType[] => [
-		{ value: 'is_create', label: '新增可见', disabled: 'disabled_create', checked: false },
-		{ value: 'is_update', label: '编辑可见', disabled: 'disabled_update', checked: false },
-		{ value: 'is_query', label: '列表可见', disabled: 'disabled_query', checked: false },
-	],
-});

+ 0 - 21
src/views/position/list/stores/RoleMenuTreeStores.ts

@@ -1,21 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleMenuTreeType } from '../types';
-/**
- * 权限抽屉:角色-菜单
- */
-
-export const RoleMenuTreeStores = defineStore('RoleMenuTreeStores', {
-	state: (): RoleMenuTreeType => ({
-		id: 0,
-		parent: 0,
-		name: '',
-		isCheck: false,
-		is_catalog: false,
-	}),
-	actions: {
-		/** 赋值 */
-		setRoleMenuTree(data: RoleMenuTreeType) {
-			this.$state = data;
-		},
-	},
-});

+ 0 - 24
src/views/position/list/stores/RoleUsersStores.ts

@@ -1,24 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleUsersType } from '../types';
-import { getAllUsers } from '../components/api';
-import XEUtils from 'xe-utils';
-/**
- * 权限抽屉:角色-用户
- */
-
-export const RoleUsersStores = defineStore('RoleUsersStores', {
-	state: (): RoleUsersType => ({
-		all_users: [],
-		right_users: [],
-	}),
-	actions: {
-		get_all_users() {
-			getAllUsers().then((res: any) => {
-				this.$state.all_users = res;
-			});
-		},
-		set_right_users(users: any) {
-			this.$state.right_users = XEUtils.map(users, (item: any) => item.id);
-		},
-	},
-});

+ 0 - 99
src/views/position/list/types.ts

@@ -1,99 +0,0 @@
-/**角色列表数据类型 */
-export interface RoleItemType {
-	id: string | number;
-	modifier_name: string;
-	creator_name: string;
-	create_datetime: string;
-	update_datetime: string;
-	description: string;
-	modifier: string;
-	dept_belong_id: string;
-	name: string;
-	key: string;
-	sort: number;
-	status: boolean;
-	admin: boolean;
-	creator: string;
-}
-
-export interface UsersType {
-	id: string | number;
-	name: string;
-}
-export interface RoleUsersType {
-	all_users: UsersType[];
-	right_users: UsersType[];
-}
-
-/**
- * 权限配置 抽屉组件参数数据类型
- */
-export interface RoleDrawerType {
-	/** 是否显示抽屉*/
-	drawerVisible: boolean;
-	/** 角色id*/
-	roleId: string | number | undefined;
-	/** 角色名称*/
-	roleName: string | undefined;
-	/** 用户*/
-	users: UsersType[];
-}
-
-/**
- * 菜单数据类型
- */
-export interface RoleMenuTreeType {
-	id: number | string | undefined;
-	/** 父级id */
-	parent: number | string | undefined;
-	name: string;
-	/** 是否选中 */
-	isCheck: boolean;
-	/** 是否是目录 */
-	is_catalog: boolean;
-}
-/**
- * 菜单-按钮数据类型
- */
-export interface RoleMenuBtnType {
-	id: string | number;
-	menu_btn_pre_id: string | number;
-	/** 是否选中 */
-	isCheck: boolean;
-	/** 按钮名称 */
-	name: string;
-	/** 数据权限范围 */
-	data_range: number | null;
-	/** 自定义部门 */
-	dept: number[];
-}
-
-/**
- * 菜单-列字段数据类型
- */
-export interface RoleMenuFieldType {
-	id: string | number | boolean;
-	/** 模型表字段名 */
-	field_name: string;
-	/** 字段显示名	*/
-	title: string;
-	/** 是否可查询 */
-	is_query: boolean;
-	/** 是否可创建 */
-	is_create: boolean;
-	/** 是否可更新 */
-	is_update: boolean;
-	[key: string]: string | number | boolean;
-}
-/**
- * 菜单-列字段-标题数据类型
- */
-export interface RoleMenuFieldHeaderType {
-	value: string;
-	/** 模型表字段名 */
-	label: string;
-	/** 字段显示名	*/
-	disabled: string;
-	/** 是否可查询 */
-	checked: boolean;
-}

+ 0 - 55
src/views/questionBank/classList/api.ts

@@ -1,55 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-
-export const apiPrefix = '/api/system/job_applications/';
-export function GetList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/question_category/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: apiPrefix + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: '/api/system/question_category/create',
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: '/api/system/question_category/update',
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function DelObj(data:any) {
-	return request({
-		url:`/api/system/question_category/delete`,
-		method: 'post',
-		data: {...data,tenant_id:1},
-	});
-}
-export function GetPermission() {
-    return request({
-        url: apiPrefix,
-        method: 'get',
-    });
-}
-
-export function GetcategoryList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/question_category/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}

+ 0 - 292
src/views/questionBank/classList/crud.tsx

@@ -1,292 +0,0 @@
-import * as api from './api';
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage } from '/@/utils/message';
-import { auth } from '/@/utils/authFunction';
-import tableSelector from '/@/components/tableSelector/index.vue';
-import { shallowRef } from 'vue';
-import { useRouter } from 'vue-router';
-
-export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter();
-	
-	const pageRequest = async (query: UserPageQuery) => {
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj({id:row.id});
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	/**
-	 * 懒加载
-	 * @param row
-	 * @returns {Promise<unknown>}
-	 */
-	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
-		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
-			resolve(res.data);
-		});
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						show: true, // 允许添加标签类别
-					},
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 200,
-				buttons: {
-					view: {
-						show: false, // 移除查看按钮,因为标签类别不需要详情页
-					},
-					edit: {
-						iconRight: 'Edit',
-						type: 'text',
-						show: true, // 允许编辑标签类别
-					},
-					remove: {
-						iconRight: 'Delete',
-						type: 'text',
-						show: true, // 允许删除标签类别
-					},
-				},
-			},
-			pagination: {
-				show: true,
-			},
-			table: {
-				rowKey: 'id',
-			},
-			search: {
-				show: true,
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				}
-			},
-			columns: {
-				_index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				},
-				name: {
-					title: '分类名称',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入分类名称',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '分类名称为必填项' },
-						],
-					},
-				},
-				description: {
-					title: '描述',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入描述',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'textarea',
-					column: {
-						minWidth: 150,
-						showOverflowTooltip: true,
-					},
-					form: {
-						component: {
-							span: 24,
-							placeholder: '请输入标签描述',
-						},
-					},
-				},
-				/* icon: {
-					title: '图标',
-					type: 'icon-selector',
-					column: {
-						minWidth: 80,
-						component: {
-							name: 'fs-icon',
-							props: {
-								icon: (scope: any) => {
-									return typeof scope.row.icon === 'string' ? scope.row.icon : '';
-								}
-							}
-						}
-					},
-					form: {
-						component: {
-							placeholder: '请选择图标'
-						}
-					}
-				}, */
-				/* color: {
-					title: '颜色',
-					type: 'colorpicker',
-					column: {
-						minWidth: 100,
-						component: {
-							name: 'fs-color-block',
-						},
-					},
-					form: {
-						value: '#1890ff', // 默认颜色
-					},
-				}, */
-				sort: {
-					title: '排序',
-					type: 'number',
-					column: {
-						minWidth: 80,
-					},
-					form: {
-						value: 1, // 默认排序值
-						component: {
-							placeholder: '请输入排序值,数字越小越靠前'
-						}
-					},
-				},
-				/* status: {
-					title: '状态',
-					type: 'dict-switch',
-					column: {
-						minWidth: 90,
-						component: {
-							name: 'fs-dict-switch',
-						},
-					},
-					dict: dict({
-						data: [
-							{ value: true, label: '启用' },
-							{ value: false, label: '禁用' },
-						],
-					}),
-					form: {
-						value: true, // 默认启用
-					},
-				},
-				is_default: {
-					title: '默认分类',
-					type: 'dict-switch',
-					column: {
-						minWidth: 100,
-						component: {
-							name: 'fs-dict-switch',
-						},
-					},
-					dict: dict({
-						data: [
-							{ value: true, label: '是' },
-							{ value: false, label: '否' },
-						],
-					}),
-					form: {
-						value: false, // 默认非默认分类
-					},
-				}, */
-				tags_count: {
-					title: '标签数量',
-					type: 'number',
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动计算
-					},
-				},
-				questions_count: {
-					title: '问题数量',
-					type: 'number',
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动计算
-					},
-				},
-				create_datetime: {
-					title: '创建时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动生成
-					},
-				},
-				update_datetime: {
-					title: '更新时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动更新
-					},
-				},
-			},
-		},
-	};
-};

+ 0 - 25
src/views/questionBank/classList/index.vue

@@ -1,25 +0,0 @@
-<template>
-	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="areas">
-import { onMounted } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { GetPermission } from './api';
-import { handleColumnPermission } from '/@/utils/columnPermission';
-
-const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 设置列权限
-	const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	//重置crudBinding
-	resetCrudOptions(newOptions);
-	// 刷新
-	crudExpose.doRefresh();
-});
-</script>

+ 0 - 55
src/views/questionBank/labelList/api.ts

@@ -1,55 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-
-export const apiPrefix = '/api/system/job_applications/';
-export function GetList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/question_tag/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: apiPrefix + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: '/api/system/question_tag/create',
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: '/api/system/question_tag/update',
-		method: 'post',
-		data: {...obj,tenant_id:1},
-	});
-}
-
-export function DelObj(data:any) {
-	return request({
-		url:`/api/system/question_tag/delete`,
-		method: 'post',
-		data: {...data,tenant_id:1},
-	});
-}
-export function GetPermission() {
-    return request({
-        url: apiPrefix,
-        method: 'get',
-    });
-}
-
-export function GetcategoryList(query: UserPageQuery) {
-	return request({
-		url: '/api/system/question_category/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}

+ 0 - 285
src/views/questionBank/labelList/crud.tsx

@@ -1,285 +0,0 @@
-import * as api from './api';
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage } from '/@/utils/message';
-import { auth } from '/@/utils/authFunction';
-import tableSelector from '/@/components/tableSelector/index.vue';
-import { shallowRef } from 'vue';
-import { useRouter } from 'vue-router';
-
-export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter();
-	
-	const pageRequest = async (query: UserPageQuery) => {
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj({id:row.id});
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	/**
-	 * 懒加载
-	 * @param row
-	 * @returns {Promise<unknown>}
-	 */
-	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
-		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
-			resolve(res.data);
-		});
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						show: true, // 允许添加标签类别
-					},
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 200,
-				buttons: {
-					view: {
-						show: false, // 移除查看按钮,因为标签类别不需要详情页
-					},
-					edit: {
-						iconRight: 'Edit',
-						type: 'text',
-						show: true, // 允许编辑标签类别
-					},
-					remove: {
-						iconRight: 'Delete',
-						type: 'text',
-						show: true, // 允许删除标签类别
-					},
-				},
-			},
-			pagination: {
-				show: true,
-			},
-			table: {
-				rowKey: 'id',
-			},
-			search: {
-				show: true,
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				}
-			},
-			columns: {
-				/* _index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				}, */
-				id: {
-					title: 'ID',
-					form: { show: false },
-					column: {
-						show: false,
-					},
-				},
-				name: {
-					title: '标签名称',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入标签名称',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-					},
-					form: {
-						rules: [
-							{ required: true, message: '标签名称为必填项' },
-						],
-					},
-				},
-				category_id: {
-					title: '分类',
-					search: {
-						show: true,
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'dict-select',
-					column: {
-						minWidth: 120,
-						show:false,
-						// component: {
-						// 	name: 'fs-dict-label',
-						// },
-					},
-					dict: dict({
-						getData: async () => {
-							const res = await api.GetcategoryList({page:1,limit:1000,tenant_id:1});
-							console.log(res.data.items);
-							return res.data.items || [];
-						},
-						label: 'name',
-						value: 'id'
-					}),
-					form: {
-						rules: [
-							{ required: true, message: '分类为必填项' },
-						],
-					},
-				},
-				category_name: {
-					title: '分类名称',
-					column: {
-						minWidth: 120,
-						
-					},
-					form: {
-						show: false, // 表单中不显示,由category_id自动关联
-					},
-				},
-				/* color: {
-					title: '标签颜色',
-					type: 'colorpicker',
-					column: {
-						minWidth: 100,
-						component: {
-							name: 'fs-color-block',
-						},
-					},
-					form: {
-						value: '#52c41a', // 默认颜色
-					},
-				}, */
-				sort: {
-					title: '排序',
-					type: 'number',
-					column: {
-						minWidth: 80,
-					},
-					form: {
-						value: 1, // 默认排序值
-					},
-				},
-				/* status: {
-					title: '状态',
-					type: 'dict-switch',
-					column: {
-						minWidth: 90,
-						component: {
-							name: 'fs-dict-switch',
-						},
-					},
-					dict: dict({
-						data: [
-							{ value: true, label: '启用' },
-							{ value: false, label: '禁用' },
-						],
-					}),
-					form: {
-						value: true, // 默认启用
-					},
-				}, */
-				questions_count: {
-					title: '问题数量',
-					type: 'number',
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动计算
-					},
-				},
-				create_datetime: {
-					title: '创建时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动生成
-					},
-				},
-				update_datetime: {
-					title: '更新时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false, // 表单中不显示,由系统自动更新
-					},
-				},
-				description: {
-					title: '描述',
-					search: {
-						show: false,
-						component: {
-							placeholder: '请输入描述',
-						},
-					},
-					type: 'textarea',
-					column: {
-						minWidth: 150,
-						showOverflowTooltip: true,
-					},
-					form: {
-						component: {
-							span: 24,
-							placeholder: '请输入标签描述',
-						},
-					},
-				},
-			},
-		},
-	};
-};

+ 0 - 25
src/views/questionBank/labelList/index.vue

@@ -1,25 +0,0 @@
-<template>
-	<fs-page>
-		<fs-crud ref="crudRef" v-bind="crudBinding"> </fs-crud>
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="areas">
-import { onMounted } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { GetPermission } from './api';
-import { handleColumnPermission } from '/@/utils/columnPermission';
-
-const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 设置列权限
-	const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	//重置crudBinding
-	resetCrudOptions(newOptions);
-	// 刷新
-	crudExpose.doRefresh();
-});
-</script>

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

@@ -1,119 +0,0 @@
-import { request } from '/@/utils/service';
-import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-import { Session } from '/@/utils/storage';
-
-export const apiPrefix = '/system/interview_question/create';
-
-export function GetPermission() {
-	return request({
-		url: apiPrefix + 'field_permission/',
-		method: 'get',
-	});
-}
-
-export function GetList(query: PageQuery) {
-	return request({
-		url: '/api/system/interview_question/list',
-		method: 'get',
-		params: {...query,tenant_id:1},/* :Session.get('tenant_id').tenant_id || ,tenant_id:'1' */
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: "/api/system/interview_question/detail" + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: "/api/system/interview_question/create",
-		method: 'post',
-		data: {...obj,tenant_id: '1'},/* Session.get('tenant_id').tenant_id ||,tenant_id: '1' */
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: "/api/system/interview_question/update",/* + obj.id+'/' */
-		method: 'put',
-		data: {...obj,tenant_id: '1'},
-	});
-}
-
-export function DelObj(id: DelReq) {
-	return request({
-		url: '/api/system/interview_question/delete',/* + id + '/' ,tenant_id: '1' */
-		method: 'delete',
-		data: { id},
-	});
-}
-
-export function GetTagList(params: any) {
-	return request({
-		url: '/api/system/question_tag/list',
-		method: 'get',
-		params: {...params,tenant_id:1},
-	});
-}
-
-export function GetcategoryList(params: any) {
-	return request({
-		url: '/api/system/question_category/list',
-		method: 'get',
-		params: {...params,tenant_id:1},
-	});
-}
-
-// 批量更新标签
-export function BatchUpdateTags(data: any) {
-	return request({
-		url: '/api/system/interview_question/batch_update_tags',
-		method: 'post',
-		data
-	});
-}
-
-// 获取职位列表
-export function GetPositionList(params: any) {
-	return request({
-		url: '/api/system/job/list',
-		method: 'get',
-		params
-	});
-}
-
-// 批量更新分类
-export function BatchUpdateCategory(data: any) {
-	return request({
-		url: '/api/system/interview_question/batch_update_category',
-		method: 'post',
-		data
-	});
-}
-
-// 获取问题类型
-export function GetQuestionTypeList() {
-	return request({
-		url: '/api/wechat/question_type_enums',
-		method: 'get'
-	});
-}
-
-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}
-	});
-}
-

+ 0 - 109
src/views/questionBank/list/components/BatchCategoryDialog.vue

@@ -1,109 +0,0 @@
-<template>
-  <el-dialog
-    v-model="dialogVisible"
-    title="批量设置分类"
-    width="500px"
-    :close-on-click-modal="false"
-    :close-on-press-escape="false"
-  >
-    <el-form :model="form" label-width="100px">
-      <el-form-item label="选择分类">
-        <el-select
-          v-model="form.categoryId"
-          placeholder="请选择分类"
-          style="width: 100%"
-          filterable
-        >
-          <el-option
-            v-for="item in categoryOptions"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleConfirm" :loading="loading">
-          确认
-        </el-button>
-      </span>
-    </template>
-  </el-dialog>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
-import { GetcategoryList } from '../api';
-import { useBatchUpdateCategory } from '../crud';
-import { ElMessage } from 'element-plus';
-
-const props = defineProps({
-  crudExpose: {
-    type: Object,
-    required: true
-  }
-});
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-const selectedRows = ref([]);
-const categoryOptions = ref([]);
-const form = reactive({
-  categoryId: null
-});
-
-const { batchUpdateCategory } = useBatchUpdateCategory(props.crudExpose);
-
-// 获取分类列表
-const fetchCategories = async () => {
-  try {
-    const res = await GetcategoryList({ page: 1, limit: 1000, tenant_id: 1 });
-    if (res.code === 2000 && res.data && res.data.items) {
-      categoryOptions.value = res.data.items;
-      console.log('categoryOptions', categoryOptions.value);
-    }
-  } catch (error) {
-    console.error('获取分类数据失败', error);
-  }
-};
-
-// 打开对话框
-const open = (selection) => {
-  selectedRows.value = selection;
-  form.categoryId = null;
-  dialogVisible.value = true;
-};
-
-// 确认按钮
-const handleConfirm = async () => {
-  if (!form.categoryId) {
-    ElMessage.warning('请选择分类');
-    return;
-  }
-  
-  loading.value = true;
-  try {
-    // 获取所有选中行的ID
-    const questionIds = selectedRows.value.map(row => row.id);
-    await batchUpdateCategory(questionIds, form.categoryId);
-    dialogVisible.value = false;
-    // 手动调用刷新方法
-    props.crudExpose.doRefresh();
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 初始化时获取分类数据
-onMounted(() => {
-  fetchCategories();
-});
-
-// 暴露方法给父组件
-defineExpose({
-  open
-});
-</script> 

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

@@ -1,195 +0,0 @@
-<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> 

+ 0 - 120
src/views/questionBank/list/components/BatchTagsDialog.vue

@@ -1,120 +0,0 @@
-<template>
-  <el-dialog
-    v-model="dialogVisible"
-    title="批量绑定标签"
-    width="500px"
-    :close-on-click-modal="false"
-    @closed="handleClosed"
-  >
-    <el-form :model="form" label-width="100px">
-      <el-form-item label="选中题目">
-        <div class="selected-count">已选择 {{ selectedQuestions.length }} 个题目</div>
-      </el-form-item>
-      <el-form-item label="选择标签" required>
-        <el-select
-          v-model="form.tags"
-          multiple
-          filterable
-          placeholder="请选择要绑定的标签"
-          style="width: 100%"
-        >
-          <el-option
-            v-for="item in tagOptions"
-            :key="item.id"
-            :label="item.name"
-            :value="item.id"
-          />
-        </el-select>
-      </el-form-item>
-    </el-form>
-    <template #footer>
-      <el-button @click="dialogVisible = false">取消</el-button>
-      <el-button type="primary" @click="handleConfirm" :loading="loading">确认绑定</el-button>
-    </template>
-  </el-dialog>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive, onMounted } from 'vue';
-import { GetTagList } from '../api';
-import { useBatchUpdateTags } from '../crud';
-import { successMessage, warningMessage } from '../../../../utils/message';
-
-const props = defineProps({
-  crudExpose: {
-    type: Object,
-    required: true
-  }
-});
-
-const { batchUpdateTags } = useBatchUpdateTags(props.crudExpose);
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-const selectedQuestions = ref<any[]>([]);
-const tagOptions = ref<any[]>([]);
-
-const form = reactive({
-  tags: [] as number[]
-});
-
-// 获取标签列表
-const fetchTags = async () => {
-  try {
-    const res = await GetTagList({ page: 1, limit: 1000, tenant_id: 1 });
-    if (res.code === 2000 && res.data && res.data.items) {
-      tagOptions.value = res.data.items;
-    }
-  } catch (error) {
-    console.error('获取标签数据失败', error);
-  }
-};
-
-// 打开对话框
-const open = (selection: any[]) => {
-  selectedQuestions.value = selection;
-  dialogVisible.value = true;
-  form.tags = [];
-};
-
-// 确认绑定
-const handleConfirm = async () => {
-  if (form.tags.length === 0) {
-    warningMessage('请至少选择一个标签');
-    return;
-  }
-
-  loading.value = true;
-  try {
-    // 提取所有选中题目的ID
-    const questionIds = selectedQuestions.value.map(item => item.id);
-    await batchUpdateTags(questionIds, form.tags);
-    dialogVisible.value = false;
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 对话框关闭后的处理
-const handleClosed = () => {
-  form.tags = [];
-  selectedQuestions.value = [];
-};
-
-// 初始化时获取标签数据
-onMounted(() => {
-  fetchTags();
-});
-
-// 暴露方法给父组件
-defineExpose({
-  open
-});
-</script>
-
-<style scoped>
-.selected-count {
-  font-size: 14px;
-  color: #606266;
-}
-</style> 

+ 0 - 76
src/views/questionBank/list/components/RoleDrawer.vue

@@ -1,76 +0,0 @@
-<template>
-	<el-drawer
-		v-model="RoleDrawer.drawerVisible"
-		title="权限配置"
-		direction="rtl"
-		size="80%"
-		:close-on-click-modal="false"
-		:before-close="RoleDrawer.handleDrawerClose"
-		:destroy-on-close="true"
-	>
-		<template #header>
-			<div>
-				当前授权角色:
-				<el-tag style="margin-right: 20px">{{ RoleDrawer.roleName }}</el-tag>
-				授权人员:
-				<el-button size="small" :icon="UserFilled" @click="handleUsers">{{ RoleDrawer.users.length }}</el-button>
-			</div>
-		</template>
-		<splitpanes class="default-theme" style="height: 100%">
-			<pane min-size="20" size="22">
-				<div class="pane-box">
-					<MenuTreeCom />
-				</div>
-			</pane>
-			<pane min-size="20">
-				<div class="pane-box">
-					<el-tabs v-model="activeName" class="demo-tabs">
-						<el-tab-pane label="接口权限" name="first"><MenuBtnCom /></el-tab-pane>
-						<el-tab-pane label="列字段权限" name="second"><MenuFieldCom /></el-tab-pane>
-					</el-tabs>
-				</div>
-			</pane>
-		</splitpanes>
-	</el-drawer>
-
-	<el-dialog v-model="dialogVisible" title="授权用户" width="700px" :close-on-click-modal="false">
-		<RoleUsersCom />
-	</el-dialog>
-</template>
-
-<script setup lang="ts">
-import { Splitpanes, Pane } from 'splitpanes';
-import 'splitpanes/dist/splitpanes.css';
-import { UserFilled } from '@element-plus/icons-vue';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { defineAsyncComponent, ref } from 'vue';
-import { RoleUsersStores } from '../stores/RoleUsersStores';
-
-const MenuTreeCom = defineAsyncComponent(() => import('./RoleMenuTree.vue'));
-const MenuBtnCom = defineAsyncComponent(() => import('./RoleMenuBtn.vue'));
-const MenuFieldCom = defineAsyncComponent(() => import('./RoleMenuField.vue'));
-const RoleUsersCom = defineAsyncComponent(() => import('./RoleUsers.vue'));
-const RoleDrawer = RoleDrawerStores(); // 抽屉参数
-const RoleUsers = RoleUsersStores(); // 角色-用户
-const activeName = ref('first');
-
-const dialogVisible = ref(false);
-
-const handleUsers = () => {
-	dialogVisible.value = true;
-	RoleUsers.get_all_users(); // 获取所有用户
-	RoleUsers.set_right_users(RoleDrawer.$state.users); // 设置已选中用户
-};
-</script>
-
-<style lang="scss" scoped>
-.pane-box {
-	width: 100vw; /* 视口宽度 */
-	height: 100vh; /* 视口高度 */
-	max-width: 100%; /* 确保不超过父元素的宽度 */
-	max-height: 100%; /* 确保不超过父元素的高度 */
-	overflow: auto; /* 当内容超出容器尺寸时显示滚动条 */
-	padding: 10px;
-	background-color: #fff;
-}
-</style>

+ 0 - 186
src/views/questionBank/list/components/RoleMenuBtn.vue

@@ -1,186 +0,0 @@
-<template>
-	<div class="pccm-item" v-if="RoleMenuBtn.$state.length > 0">
-		<div class="menu-form-alert">配置操作功能接口权限,配置数据权限点击小齿轮</div>
-		<el-checkbox v-for="btn in RoleMenuBtn.$state" :key="btn.id" v-model="btn.isCheck" @change="handleCheckChange(btn)">
-			<div class="btn-item">
-				{{ btn.data_range !== null ? `${btn.name}(${formatDataRange(btn.data_range)})` : btn.name }}
-				<span v-show="btn.isCheck" @click.stop.prevent="handleSettingClick(btn)">
-					<el-icon>
-						<Setting />
-					</el-icon>
-				</span>
-			</div>
-		</el-checkbox>
-	</div>
-	<el-dialog v-model="dialogVisible" title="数据权限配置" width="400px" :close-on-click-modal="false" :before-close="handleDialogClose">
-		<div class="pc-dialog">
-			<el-select v-model="selectBtn.data_range" @change="handlePermissionRangeChange" placeholder="请选择">
-				<el-option v-for="item in dataPermissionRange" :key="item.value" :label="item.label" :value="item.value" />
-			</el-select>
-			<el-tree-select
-				v-show="selectBtn.data_range === 4"
-				node-key="id"
-				v-model="selectBtn.dept"
-				:props="defaultTreeProps"
-				:data="deptData"
-				multiple
-				check-strictly
-				:render-after-expand="false"
-				show-checkbox
-				class="dialog-tree"
-			/>
-		</div>
-		<template #footer>
-			<div>
-				<el-button type="primary" @click="handleDialogConfirm"> 确定</el-button>
-				<el-button @click="handleDialogClose"> 取消</el-button>
-			</div>
-		</template>
-	</el-dialog>
-</template>
-
-<script setup lang="ts">
-import { computed, onMounted, ref } from 'vue';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleMenuBtnStores } from '../stores/RoleMenuBtnStores';
-import { RoleMenuTreeStores } from '../stores/RoleMenuTreeStores';
-import { RoleMenuBtnType } from '../types';
-import { getRoleToDeptAll, setRoleMenuBtn, setRoleMenuBtnDataRange } from './api';
-import XEUtils from 'xe-utils';
-import { ElMessage } from 'element-plus';
-const RoleDrawer = RoleDrawerStores(); // 角色-菜单
-const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
-const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
-const dialogVisible = ref(false);
-// 选中的按钮
-const selectBtn = ref<RoleMenuBtnType>({
-	id: 0,
-	menu_btn_pre_id: 0,
-	/** 是否选中 */
-	isCheck: false,
-	/** 按钮名称 */
-	name: '',
-	/** 数据权限范围 */
-	data_range: 0,
-	dept: [],
-});
-/**
- * 数据权限范围
- */
-const dataPermissionRange = ref([
-	{ label: '仅本人数据权限', value: 0 },
-	{ label: '本部门及以下数据权限', value: 1 },
-	{ label: '本部门数据权限', value: 2 },
-	{ label: '全部数据权限', value: 3 },
-	{ label: '自定数据权限', value: 4 },
-]);
-/**
- * 自定义数据权限的部门树配置
- */
-const defaultTreeProps = {
-	children: 'children',
-	label: 'name',
-	value: 'id',
-};
-
-/**
- * 自定数据权限下拉选择事件
- */
-const handlePermissionRangeChange = async (val: number) => {
-	if (val < 4) {
-		selectBtn.value.dept = [];
-	}
-};
-/**
- * 格式化按钮数据范围
- */
-const formatDataRange = computed(() => {
-	return function (datarange: number) {
-		const datarangeitem = XEUtils.find(dataPermissionRange.value, (item: any) => {
-			if (item.value === datarange) {
-				return item.label;
-			}
-		});
-		return datarangeitem.label;
-	};
-});
-/**
- * 勾选按钮
- */
-const handleCheckChange = async (btn: RoleMenuBtnType) => {
-	const put_data = {
-		isCheck: btn.isCheck,
-		roleId: RoleDrawer.roleId,
-		menuId: RoleMenuTree.id,
-		btnId: btn.id,
-	};
-	const { data, msg } = await setRoleMenuBtn(put_data);
-	RoleMenuBtn.updateState(data);
-	ElMessage({ message: msg, type: 'success' });
-};
-
-/**
- * 按钮-数据范围确定
- */
-const handleDialogConfirm = async () => {
-	const { data, msg } = await setRoleMenuBtnDataRange(selectBtn.value);
-	selectBtn.value = data;
-	dialogVisible.value = false;
-	ElMessage({ message: msg, type: 'success' });
-};
-/**
- * 数据范围关闭
- */
-const handleDialogClose = () => {
-	dialogVisible.value = false;
-};
-
-/**
- * 齿轮点击
- */
-const handleSettingClick = async (btn: RoleMenuBtnType) => {
-	selectBtn.value = btn;
-	dialogVisible.value = true;
-};
-
-/**
- * 部门数据
- *
- */
-const deptData = ref<number[]>([]);
-// 页面打开后获取列表数据
-onMounted(async () => {
-	const res = await getRoleToDeptAll({ role: RoleDrawer.roleId, menu_button: selectBtn.value.id });
-	const depts = XEUtils.toArrayTree(res.data, { parentKey: 'parent', strict: false });
-	deptData.value = depts;
-});
-</script>
-
-<style lang="scss" scoped>
-.pccm-item {
-	margin-bottom: 10px;
-	.menu-form-alert {
-		color: #fff;
-		line-height: 24px;
-		padding: 8px 16px;
-		margin-bottom: 20px;
-		border-radius: 4px;
-		background-color: var(--el-color-primary);
-	}
-}
-// .el-checkbox {
-// 	width: 200px;
-// }
-.btn-item {
-	display: flex;
-	align-items: center;
-	justify-content: center; /* 水平居中 */
-	.el-icon {
-		margin-left: 5px;
-	}
-}
-.dialog-tree {
-	width: 100%;
-	margin-top: 20px;
-}
-</style>

+ 0 - 118
src/views/questionBank/list/components/RoleMenuField.vue

@@ -1,118 +0,0 @@
-<template>
-	<div class="pccm-item" v-if="RoleMenuField.$state.length > 0">
-		<div class="menu-form-alert">
-			<el-button size="small" @click="handleSaveField">保存 </el-button>
-			配置数据列字段权限
-		</div>
-
-		<ul class="columns-list">
-			<li class="columns-head">
-				<div class="width-txt">
-					<span>字段</span>
-				</div>
-				<div v-for="(head, hIndex) in RoleMenuFieldHeader.$state" :key="hIndex" class="width-check">
-					<el-checkbox v-model="head.checked" @change="handleColumnChange($event, head.value, head.disabled)">
-						<span>{{ head.label }}</span>
-					</el-checkbox>
-				</div>
-			</li>
-			<div class="columns-content">
-				<li v-for="(c_item, c_index) in RoleMenuField.$state" :key="c_index" class="columns-item">
-					<div class="width-txt">{{ c_item.title }}</div>
-					<div v-for="(col, cIndex) in RoleMenuFieldHeader.$state" :key="cIndex" class="width-check">
-						<el-checkbox v-model="c_item[col.value]" class="ci-checkout" :disabled="c_item[col.disabled]"></el-checkbox>
-					</div>
-				</li>
-			</div>
-		</ul>
-	</div>
-</template>
-
-<script setup lang="ts">
-import { ElMessage } from 'element-plus';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleMenuFieldStores, RoleMenuFieldHeaderStores } from '../stores/RoleMenuFieldStores';
-import { setRoleMenuField } from './api';
-const RoleMenuField = RoleMenuFieldStores();
-const RoleMenuFieldHeader = RoleMenuFieldHeaderStores();
-const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
-/** 全选 */
-const handleColumnChange = (val: boolean, btnType: string, disabledType: string) => {
-	for (const iterator of RoleMenuField.$state) {
-		iterator[btnType] = iterator[disabledType] ? iterator[btnType] : val;
-	}
-};
-const handleSaveField = async () => {
-	const res = await setRoleMenuField(RoleDrawer.$state.roleId, RoleMenuField.$state);
-	ElMessage({ message: res.msg, type: 'success' });
-};
-</script>
-
-<style lang="scss" scoped>
-.pccm-item {
-	margin-bottom: 10px;
-	.menu-form-alert {
-		color: #fff;
-		line-height: 24px;
-		padding: 8px 16px;
-		margin-bottom: 20px;
-		border-radius: 4px;
-		background-color: var(--el-color-primary);
-	}
-	.menu-form-btn {
-		margin-left: 10px;
-		height: 40px;
-		padding: 8px 16px;
-		margin-bottom: 20px;
-	}
-
-	.btn-item {
-		display: flex;
-		align-items: center;
-
-		span {
-			margin-left: 5px;
-		}
-	}
-
-	.columns-list {
-		.width-txt {
-			width: 200px;
-		}
-
-		.width-check {
-			width: 100px;
-		}
-
-		.width-icon {
-			cursor: pointer;
-		}
-
-		.columns-head {
-			display: flex;
-			align-items: center;
-			padding: 6px 0;
-			border-bottom: 1px solid #ebeef5;
-			box-sizing: border-box;
-
-			span {
-				font-weight: 900;
-			}
-		}
-		.columns-content {
-			max-height: calc(100vh - 240px); /* 视口高度 */
-			overflow-y: auto; /* 当内容超出高度时显示垂直滚动条 */
-			.columns-item {
-				display: flex;
-				align-items: center;
-				padding: 6px 0;
-				box-sizing: border-box;
-
-				.ci-checkout {
-					height: auto !important;
-				}
-			}
-		}
-	}
-}
-</style>

+ 0 - 81
src/views/questionBank/list/components/RoleMenuTree.vue

@@ -1,81 +0,0 @@
-<template>
-	<el-tree
-		ref="treeRef"
-		:data="menuData"
-		:props="defaultTreeProps"
-		:default-checked-keys="menuDefaultCheckedKeys"
-		@check-change="handleMenuChange"
-		@node-click="handleMenuClick"
-		node-key="id"
-		check-strictly
-		highlight-current
-		show-checkbox
-		default-expand-all
-	>
-	</el-tree>
-</template>
-
-<script setup lang="ts">
-import { onMounted, ref } from 'vue';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleMenuTreeStores } from '../stores/RoleMenuTreeStores';
-import { RoleMenuBtnStores } from '../stores/RoleMenuBtnStores';
-import { RoleMenuFieldStores } from '../stores/RoleMenuFieldStores';
-import { RoleMenuFieldHeaderStores } from '../stores/RoleMenuFieldStores';
-import { getRoleMenu, getRoleMenuBtnField, setRoleMenu } from './api';
-import { ElMessage } from 'element-plus';
-import XEUtils from 'xe-utils';
-import { RoleMenuTreeType } from '../types';
-
-const RoleDrawer = RoleDrawerStores(); // 角色-抽屉
-const RoleMenuTree = RoleMenuTreeStores(); // 角色-菜单
-const RoleMenuBtn = RoleMenuBtnStores(); // 角色-菜单-按钮
-const RoleMenuField = RoleMenuFieldStores(); // 角色-菜单-列字段
-const RoleMenuFieldHeader = RoleMenuFieldHeaderStores();// 角色-菜单-列字段
-const menuData = ref<RoleMenuTreeType[]>([]); // 菜单列表数据
-const menuDefaultCheckedKeys = ref<(number | string | undefined)[]>([]); // 默认选中的菜单列表
-// 菜单配置
-const defaultTreeProps = {
-	children: 'children',
-	label: 'name',
-	value: 'id',
-};
-
-/**
- * 菜单复选框选中
- * @param node:当前节点的 Node 对象
- * @param checked:布尔值,表示当前节点是否被选中
- */
-const handleMenuChange = (node: any, checked: boolean) => {
-	setRoleMenu({ roleId: RoleDrawer.roleId, menuId: node.id, isCheck: checked }).then((res: any) => {
-		ElMessage({ message: res.msg, type: 'success' });
-	});
-};
-/**
- * 菜单点击事件
- */
-const handleMenuClick = async (selectNode: RoleMenuTreeType) => {
-	if (!selectNode.is_catalog) {
-		RoleMenuTree.setRoleMenuTree(selectNode); // 更新当前选中的菜单
-		// 获取当前菜单的按钮列表
-		const { data } = await getRoleMenuBtnField({
-			roleId: RoleDrawer.roleId,
-			menuId: selectNode.id,
-		});
-		RoleMenuBtn.setState(data.menu_btn); // 更新按钮列表
-		RoleMenuField.setState(data.menu_field); // 更新列字段列表
-	} else {
-		RoleMenuBtn.setState([]); // 更新按钮列表
-		RoleMenuField.setState([]); // 更新列字段列表
-	}
-	RoleMenuFieldHeader.$reset()
-};
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	menuData.value = await getRoleMenu({ roleId: RoleDrawer.roleId });
-	menuDefaultCheckedKeys.value = XEUtils.toTreeArray(menuData.value)
-		.filter((i) => i.isCheck)
-		.map((i) => i.id);
-});
-</script>

+ 0 - 35
src/views/questionBank/list/components/RoleUsers.vue

@@ -1,35 +0,0 @@
-<template>
-	<el-transfer
-		v-model="RoleUsers.$state.right_users"
-		filterable
-		:titles="['未授权用户', '已授权用户']"
-		:data="RoleUsers.$state.all_users"
-		:props="{
-			key: 'id',
-			label: 'name',
-		}"
-		@change="handleChange"
-	/>
-</template>
-
-<script lang="ts" setup>
-import { ElMessage } from 'element-plus';
-import { RoleDrawerStores } from '../stores/RoleDrawerStores';
-import { RoleUsersStores } from '../stores/RoleUsersStores';
-import { setRoleUsers } from './api';
-const RoleDrawer = RoleDrawerStores(); // 抽屉参数
-const RoleUsers = RoleUsersStores(); // 角色-用户
-
-/**
- *
- * @param value 当前右侧选中的用户
- * @param direction 移动的方向
- * @param movedKeys 移动的用户
- */
-const handleChange = (value: number[] | string[], direction: 'left' | 'right', movedKeys: string[] | number[]) => {
-	setRoleUsers(RoleDrawer.$state.roleId, { direction, movedKeys }).then((res:any) => {
-		RoleDrawer.set_state(res.data)
-		ElMessage({ message: res.msg, type: 'success' });
-	});
-};
-</script>

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

@@ -1,129 +0,0 @@
-import { request } from '/@/utils/service';
-import XEUtils from 'xe-utils';
-/**
- * 获取 角色-菜单
- * @param query
- */
-export function getRoleMenu(query: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/get_role_menu/',
-		method: 'get',
-		params: query,
-	}).then((res: any) => {
-		return XEUtils.toArrayTree(res.data, { key: 'id', parentKey: 'parent', children: 'children', strict: false });
-	});
-}
-/**
- * 设置 角色-菜单
- * @param data
- * @returns
- */
-export function setRoleMenu(data: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/set_role_menu/',
-		method: 'put',
-		data,
-	});
-}
-/**
- * 获取 角色-菜单-按钮-列字段
- * @param query
- */
-export function getRoleMenuBtnField(query: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/get_role_menu_btn_field/',
-		method: 'get',
-		params: query,
-	});
-}
-
-/**
- * 设置 角色-菜单-按钮
- * @param data
- */
-export function setRoleMenuBtn(data: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/set_role_menu_btn/',
-		method: 'put',
-		data,
-	});
-}
-
-/**
- * 设置 角色-菜单-列字段
- * @param data
- */
-export function setRoleMenuField(roleId: string | number | undefined, data: object) {
-	return request({
-		url: `/api/system/role_menu_button_permission/${roleId}/set_role_menu_field/`,
-		method: 'put',
-		data,
-	});
-}
-
-/**
- * 设置 角色-菜单-按钮-数据权限
- * @param query
- * @returns
- */
-export function setRoleMenuBtnDataRange(data: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/set_role_menu_btn_data_range/',
-		method: 'put',
-		data,
-	});
-}
-
-/**
- * 获取当前用户角色下所能授权的部门
- * @param query
- * @returns
- */
-export function getRoleToDeptAll(query: object) {
-	return request({
-		url: '/api/system/role_menu_button_permission/role_to_dept_all/',
-		method: 'get',
-		params: query,
-	});
-}
-
-/**
- * 获取所有用户
- * @param query
- * @returns
- */
-export function getAllUsers() {
-	return request({
-		url: '/api/system/user/',
-		method: 'get',
-		params: { limit: 999 },
-	}).then((res: any) => {
-		return XEUtils.map(res.data, (item: any) => {
-			return {
-				id: item.id,
-				name: item.name,
-			};
-		});
-	});
-}
-
-/**
- * 设置角色-用户
- * @param query
- * @returns
- */
-export function setRoleUsers(roleId: string | number | undefined, data: object) {
-	return request({
-		url: `/api/system/role/${roleId}/set_role_users/`,
-		method: 'put',
-		data,
-	});
-}
-
-export function GetCompetencyList(query:any) {
-	return request({
-		url: '/api/system/competency/list',
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}

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

@@ -1,1467 +0,0 @@
-import { CreateCrudOptionsProps, CreateCrudOptionsRet, AddReq, DelReq, EditReq, dict, compute } from '@fast-crud/fast-crud';
-import * as api from './api';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage, warningMessage } from '../../../utils/message';
-import { auth } from '/@/utils/authFunction';
-import { ref, onMounted, watch } from 'vue';
-import { ElMessage } from 'element-plus';
-
-// 在文件顶部添加全局接口声明
-declare global {
-	interface Window {
-		competencyTagsOptions?: any[];
-	}
-}
-
-/**
- *
- * @param crudExpose:index传递过来的示例
- * @param context:index传递过来的自定义参数
- * @returns
- */
-export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const pageRequest = async (query: any) => {
-		// 如果存在 question 搜索值,将其赋值给 keyword
-		if (query.question) {
-			query.keyword = query.question;
-			delete query.question; // 删除原有的 question 参数
-		}
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		
-		// 处理胜任力标签配置
-		if (form.competency_tags && form.competency_tags.length > 0 && form.competency_tags_config) {
-			form.competency_tags = form.competency_tags_config.map((config: any) => ({
-				id: config.id,
-				weight: config.weight || 100,
-				importance: config.importance || 1,
-				remark: config.remark || ''
-			}));
-		} else {
-			form.competency_tags_config = [];
-		}
-		
-		// 调用更新题目的接口
-		const editResult = await api.UpdateObj(form);
-		
-		// 如果更新成功且题目形式为0(开放题)或5,异步触发视频生成
-		if (editResult.code === 2000 && (form.question_form === 0 || form.question_form === 5)) {
-			// 使用 setTimeout 将视频生成请求放入下一个事件循环
-			setTimeout(async () => {
-				try {
-					// 构造视频生成请求参数
-					const videoParams = {
-						video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
-						text: form.question,
-						question_id: form.id,
-						guidance_scale: 2.0,
-						inference_steps: 20,
-						seed: 1247
-					};
-
-					// 调用视频生成接口
-					const videoResult = await fetch(`${import.meta.env.VITE_API_URL_MINLONG}/queue/process/text/`, {
-						method: 'POST',
-						headers: {
-							'Content-Type': 'application/json'
-						},
-						body: JSON.stringify(videoParams)
-					});
-
-					if (!videoResult.ok) {
-						console.warn('数字人视频生成请求失败:', await videoResult.text());
-						ElMessage({
-							type: 'warning',
-							message: '数字人视频生成请求失败,但不影响题目的更新'
-						});
-					} else {
-						ElMessage({
-							type: 'success',
-							message: '题目已更新,数字人视频正在重新生成中,请稍后查看'
-						});
-					}
-				} catch (error) {
-					console.error('调用数字人视频生成接口失败:', error);
-					ElMessage({
-						type: 'warning',
-						message: '数字人视频生成失败,但题目已成功更新'
-					});
-				}
-			}, 0);
-		}
-
-		return editResult;
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj(row.id);
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		// 处理胜任力标签配置
-		if (form.competency_tags && form.competency_tags.length > 0 && form.competency_tags_config) {
-			form.competency_tags = form.competency_tags_config.map((config: any) => ({
-				id: config.id,
-				weight: config.weight || 100,
-				importance: config.importance || 1,
-				remark: config.remark || ''
-			}));
-		} else {
-			form.competency_tags_config = [];
-		}
-		
-		// 先调用添加题目的接口
-		const addResult = await api.AddObj(form);
-		
-		// 如果添加成功且题目形式为0(开放题)或5,异步触发视频生成
-		if (addResult.code === 2000 && (form.question_form === 0 || form.question_form === 5)) {
-			// 使用 setTimeout 将视频生成请求放入下一个事件循环
-			setTimeout(async () => {
-				try {
-					// 构造视频生成请求参数
-					const videoParams = {
-						video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
-						text: form.question,
-						question_id: addResult.data.id,
-						guidance_scale: 2.0,
-						inference_steps: 20,
-						seed: 1247
-					};
-
-					// 调用视频生成接口http://192.168.66.101:7861
-					const videoResult = await fetch(`${import.meta.env.VITE_API_URL_MINLONG}/queue/process/text/`, {
-						method: 'POST',
-						headers: {
-							'Content-Type': 'application/json'
-						},
-						body: JSON.stringify(videoParams)
-					});
-
-					if (!videoResult.ok) {
-						console.warn('数字人视频生成请求失败:', await videoResult.text());
-						ElMessage({
-							type: 'warning',
-							message: '数字人视频生成请求失败,但不影响题目的保存'
-						});
-					} else {
-						ElMessage({
-							type: 'success',
-							message: '题目已保存,数字人视频正在生成中,请稍后查看'
-						});
-					}
-				} catch (error) {
-					console.error('调用数字人视频生成接口失败:', error);
-					ElMessage({
-						type: 'warning',
-						message: '数字人视频生成失败,但题目已成功保存'
-					});
-				}
-			}, 0);
-		}
-
-		return addResult;
-	};
-
-/* 	// 修改分类和标签数据获取方式
-	const categoryOptions = ref<Array<{value: number, label: string}>>([]);
-	const tagOptions = ref<Array<{value: number, label: string}>>([]);
-	
-	// 获取分类数据
-	const fetchCategories = async () => {
-		try {
-			const res = await api.GetcategoryList({page:1, limit:1000, tenant_id:1});
-			if (res.code === 0 && res.data && res.data.items) {
-				categoryOptions.value = res.data.items;
-			}
-		} catch (error) {
-			console.error('获取分类数据失败', error);
-		}
-	};
-	
-	// 获取标签数据
-	const fetchTags = async (categoryId?: number) => {
-		try {
-			const params = {
-				page: 1,
-				limit: 1000,
-				tenant_id: 1
-			};
-			if (categoryId) {
-				params.category_id = categoryId;
-			}
-			const res = await api.GetTagList(params);
-			if (res.code === 0 && res.data && res.data.items) {
-				tagOptions.value = res.data.items;
-			}
-		} catch (error) {
-			console.error('获取标签数据失败', error);
-		}
-	}; */
-	
-	// 初始化时获取分类数据
-/* 	onMounted(() => {
-		fetchCategories();
-		fetchTags();
-	}); */
-
-	// 添加批量更新标签的方法
-	const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
-		try {
-			const res = await api.BatchUpdateTags({
-				question_ids: questionIds,
-				tags: tagIds,
-				tenant_id: 1
-			});
-			if (res.code === 0) {
-				successMessage('批量更新标签成功');
-				// 刷新列表
-				crudExpose.doRefresh();
-			}
-		} catch (error) {
-			console.error('批量更新标签失败', error);
-		}
-	};
-
-	// 添加批量更新分类的方法
-	const batchUpdateCategory = async (questionIds: number[], categoryId: number) => {
-		try {
-			const res = await api.BatchUpdateCategory({
-				question_ids: questionIds,
-				category_id: categoryId,
-				tenant_id: "1"
-			});
-			if (res.code === 0) {
-				successMessage('批量更新分类成功');
-				// 刷新列表
-				crudExpose.doRefresh();
-			}
-		} catch (error) {
-			console.error('批量更新分类失败', error);
-		}
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			pagination: {
-				show: true,
-			},
-			// 添加搜索表单配置
-			search: {
-				show: true,
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				}
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						size: 'small',
-						show: auth('role:Create'),
-					},
-					// 添加批量绑定标签按钮
-					batchBindTags: {
-						text: '批量绑定标签',
-						type: 'primary',
-						size: 'small',
-						show: true,
-						order: 2,
-						click: () => {
-							// 使用存储的选中行
-							const selection = context.selectedRows || [];
-							console.log('选中的行:', selection);
-							
-							if (!selection || selection.length === 0) {
-								warningMessage('请先选择要操作的题目');
-								return;
-							}
-							// 打开批量绑定标签对话框
-							context.openBatchTagsDialog(selection);
-						},
-					},
-					// 添加批量设置分类按钮
-					batchSetCategory: {
-						text: '批量设置分类',
-						type: 'primary',
-						show: true,
-						size: 'small',
-						order: 3,
-						click: () => {
-							// 使用存储的选中行
-							const selection = context.selectedRows || [];
-							console.log('选中的行:', selection);
-							
-							if (!selection || selection.length === 0) {
-								warningMessage('请先选择要操作的题目');
-								return;
-							}
-							// 打开批量设置分类对话框
-							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: {
-				//固定右侧
-				fixed: 'right',
-				width: 259,
-				buttons: {
-					view: {
-						show: true,
-						size: 'small',
-						icon:"View",
-						type: 'text',
-					},
-					edit: {
-						show: auth('role:Update'),
-						size: 'small',
-						icon:"Edit",
-						type: 'text',
-					},
-					remove: {
-						show: auth('role:Delete'),
-						size: 'small',
-						type: 'text',
-						icon:"Delete",
-					},
-					// 添加生成数字人视频按钮
-					/* generateVideo: {
-						text: '生成视频',
-						type: 'text',
-						size: 'small',
-						show: ({ row }) => {
-							// 只在题目形式为开放题(0)或追加型开放题(5)时显示
-							return row.question_form === 0 || row.question_form === 5;
-						},
-						click: async ({ row }) => {
-							try {
-								// 构造视频生成请求参数
-								const videoParams = {
-									video_url: "http://data.qicai321.com/minlong/32ae7e1f-042f-4ee5-b234-be34d98d70e6.mp4",
-									text: row.question,
-									question_id: row.id,
-									guidance_scale: 2.0,
-									inference_steps: 20,
-									seed: 1247
-								};
-
-								// 调用视频生成接口
-								const videoResult = await fetch('http://192.168.66.101:7861/queue/process/text/', {
-									method: 'POST',
-									headers: {
-										'Content-Type': 'application/json'
-									},
-									body: JSON.stringify(videoParams)
-								});
-
-								if (!videoResult.ok) {
-									console.warn('数字人视频生成请求失败:', await videoResult.text());
-									ElMessage({
-										type: 'error',
-										message: '视频生成请求失败'
-									});
-								} else {
-									ElMessage({
-										type: 'info',
-										message: '数字人视频正在生成中...'
-									});
-								}
-							} catch (error) {
-								console.error('调用数字人视频生成接口失败:', error);
-								ElMessage({
-									type: 'error',
-									message: '视频生成请求失败'
-								});
-							}
-						}
-					} */
-				},
-			},
-			form: {
-				col: { span: 24 },
-				labelWidth: '100px',
-				wrapper: {
-					is: 'el-dialog',
-					width: '600px',
-				},
-			},
-			columns: {
-				_selection: {
-					title: '选择',
-					form: { show: false },
-					column: {
-						type: 'selection',
-						align: 'center',
-						width: 50,
-						fixed: 'left',
-						columnSetDisabled: true,
-					},
-				},
-				id: {
-					title: 'ID',
-					column: { show: true ,width:80,},
-					search: { show: false },
-					form: { show: false },
-				},
-				question: {
-					title: '题目内容',
-					search: { 
-						show: true,
-						size: 'small',
-						col: { span: 3 },
-						component: {
-							name: 'el-input',
-							props: {
-								placeholder: '请输入题目内容搜索',
-								clearable: true
-							}
-						}
-					},
-					column: {
-						showOverflowTooltip: true,
-						width: 300,
-						sortable: 'custom',
-					},
-					form: {
-						rules: [{ required: true, message: '题目内容必填' }],
-						component: {
-							placeholder: '请输入题目内容',
-						},
-					},
-				},
-				competency_tags:{
-					title: '胜任力标签',
-					search: { 
-						show: true,
-						size: 'small',
-						component: {
-							props: {
-								multiple: false,
-								filterable: true,
-							}
-						},
-						col:{ span:3 },
-					},
-					type: 'dict-select',
-					column: {
-						minWidth: 230,
-						component: {
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								if (!row.competency_tags || row.competency_tags.length === 0) return <span>-</span>;
-								
-								const displayTags = row.competency_tags.slice(0, 2);
-								const remainingCount = row.competency_tags.length - 2;
-								
-								return (
-									<div style="display: flex; gap: 4px; align-items: center;">
-										{displayTags.map((tag: any) => (
-											<el-tag
-												key={tag.id}
-												type="warning"
-												effect="plain"
-												size="mini"
-												style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
-												title={tag.name}
-											>
-												{tag.name.length > 4 ? tag.name.slice(0, 4) + '...' : tag.name}
-											</el-tag>
-										))}
-										{remainingCount > 0 && (
-											<el-tooltip
-												placement="top"
-												effect="light"
-												popper-class="tag-tooltip"
-											>
-												{{
-													default: () => (
-														<el-tag
-															type="info"
-															effect="plain"
-															size="mini"
-														>
-															+{remainingCount}
-														</el-tag>
-													),
-													content: () => (
-														<div>
-															<div style="font-weight: bold; margin-bottom: 5px">剩余{remainingCount}个标签:</div>
-															{row.competency_tags.slice(2).map((tag: any) => (
-																<div key={tag.id} style="margin: 3px 0">{tag.name}</div>
-															))}
-														</div>
-													)
-												}}
-											</el-tooltip>
-										)}
-									</div>
-								);
-							}
-						}
-					},
-					dict: dict({
-						getData: async () => {
-							const res = await api.GetCompetencyList({page:1, limit:100, tenant_id:1});
-							window.competencyTagsOptions = res.data.items || [];
-							return res.data.items;
-						},
-						label: 'name',    
-						value: 'id'
-					}),
-					form: {
-						component: {
-							props: {
-								multiple: true,
-								filterable: true,
-								placeholder: '请选择胜任力标签'
-							},
-							value: compute(({ form }) => {
-								if (!form.competency_tags) return [];
-								console.log('form.competency_tags', form.competency_tags);
-								// 如果是数组但元素是对象,提取id
-								if (Array.isArray(form.competency_tags)) {
-									const ids = form.competency_tags.map((tag: any) => {
-										if (typeof tag === 'object' && tag !== null) {
-											// 同时更新 competency_tags_config
-											if (!form.competency_tags_config) {
-												form.competency_tags_config = [];
-												form.competency_tags = [];
-											}
-											
-											// 检查配置是否已存在
-											const existingConfig = form.competency_tags_config.find(
-												(config: any) => config.id === tag.id
-											);
-											
-											if (!existingConfig) {
-												form.competency_tags_config.push({
-													id: tag.id,
-													competency_name: tag.name,
-													weight: tag.weight || 100,
-													importance: tag.importance || 1,
-													remark: ''
-												});
-											
-												form.competency_tags.push(tag.id);
-											}
-											
-											return tag.id;
-										}
-										return tag;
-									});
-									return ids;
-								}
-								
-								return form.competency_tags;
-							}),
-							onValueChange: ({ value, form }: { value: any[], form: any }) => {
-								// 保存选中的ID数组
-								form.competency_tags = value;
-								
-								// 同步更新competency_tags_config
-								if (!form.competency_tags_config) {
-									form.competency_tags_config = [];
-								}
-								
-								// 获取当前已有的配置ID
-								const existingConfigIds = form.competency_tags_config.map((config: any) => config.id);
-								
-								// 从全局变量获取胜任力标签选项数据
-								const competencyOptions = window.competencyTagsOptions || [];
-								
-								// 添加新选择的标签配置
-								value.forEach((tagId: number) => {
-									if (!existingConfigIds.includes(tagId)) {
-										// 查找标签信息
-										const tagInfo = competencyOptions.find((tag: any) => tag.id === tagId);
-										if (tagInfo) {
-											form.competency_tags_config.push({
-												id: tagId,
-												competency_name: tagInfo.name,
-												weight: tagInfo.weight || 100,
-												importance: tagInfo.importance || 1,
-												remark: ''
-											});
-										}
-									}
-								});
-								
-								// 移除已不存在的标签配置
-								form.competency_tags_config = form.competency_tags_config.filter(
-									(config: any) => value.includes(config.id)
-								);
-							}
-						},
-						helper: '选择题目关联的胜任力标签,可多选'
-					}
-				},
-				competency_tags_config: {
-					title: '胜任力配置',
-					search: { show: false },
-					column: { show: false },
-					form: {
-						show: compute(({ form }) => {
-							// 只有当选择了胜任力标签时才显示配置
-							return form && form.competency_tags && form.competency_tags.length > 0;
-						}),
-						component: {
-							name: 'el-card',
-							children: {
-								default: ({ form }: { form: any }) => {
-									// 确保competency_tags_config数组已初始化
-									if (!form.competency_tags_config) {
-										form.competency_tags_config = [];
-									}
-									
-									// 使用ref标记是否已经处理过,避免重复处理
-									const processed = ref(false);
-									
-									// 使用onMounted确保只在组件挂载时执行一次
-									onMounted(() => {
-										if (!processed.value && form.competency_tags && form.competency_tags.length > 0) {
-											updateCompetencyConfig();
-											processed.value = true;
-										}
-									});
-									
-									// 监听competency_tags变化,但避免无限循环
-									watch(() => [...(form.competency_tags || [])], (newTags, oldTags) => {
-										// 只有当标签真正变化时才更新配置
-										if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
-											updateCompetencyConfig();
-										}
-									});
-									
-									// 抽取更新配置的逻辑到单独的函数
-									const updateCompetencyConfig = () => {
-										// 获取当前已有的配置ID
-										const existingConfigIds = form.competency_tags_config.map((config: any) => config.id);
-										
-										// 创建新的配置数组
-										const newConfigs = [...form.competency_tags_config];
-										
-										// 从全局变量获取胜任力标签选项数据
-										const competencyOptions = window.competencyTagsOptions || [];
-										
-										// 添加新选择的标签配置
-										form.competency_tags.forEach((tagId: number) => {
-											if (!existingConfigIds.includes(tagId)) {
-												// 查找标签名称
-												const tagName = competencyOptions.find((tag: any) => tag.id === tagId)?.name || '未知标签';
-												
-												// 添加新配置
-												newConfigs.push({
-													id: tagId,
-													competency_name: tagName,
-													weight: 100, // 默认权重为100%
-													importance: 1, // 默认重要性为1
-													remark: '' // 默认备注为空
-												});
-											}
-										});
-										
-										// 移除已不存在的标签配置
-										const filteredConfigs = newConfigs.filter(
-											(config: any) => form.competency_tags.includes(config.id)
-										);
-										
-										// 一次性更新配置数组
-										form.competency_tags_config = filteredConfigs;
-									};
-									
-									// 渲染表格
-									return (
-										<div>
-											<el-table border style="width: 100%" data={form.competency_tags_config}>
-												<el-table-column label="标签" width="200">
-													{{
-														default: ({ row }: { row: any }) => (
-															<span>{row.competency_name}</span>
-														)
-													}}
-												</el-table-column>
-												<el-table-column label="权重" width="100">
-													{{
-														default: ({ row }: { row: any }) => (
-															<div class="weight-input" style="display: flex; align-items: center;">
-																<el-input-number 
-																	v-model={row.weight} 
-																	min={0} 
-																	max={100}
-																	step={1} 
-																	precision={0}
-																	controls={false}
-																	style="width: calc(100% - 20px)"
-																/>
-																<span style="margin-left: 4px;">%</span>
-															</div>
-														)
-													}}
-												</el-table-column>
-												<el-table-column label="级别" width="100">
-													{{
-														default: ({ row }: { row: any }) => (
-															<el-input-number 
-																v-model={row.importance} 
-																min={1}
-																controls={false}
-																style="width: 100%"
-															/>
-														)
-													}}
-												</el-table-column>
-												{/* <el-table-column label="备注">
-													{{
-														default: ({ row }: { row: any }) => (
-															<el-input 
-																v-model={row.remark} 
-																placeholder="请输入备注"
-															/>
-														)
-													}}
-												</el-table-column> */}
-											</el-table>
-										</div>
-									);
-								}
-							}
-						},
-						helper: '配置每个胜任力标签的权重、重要性和备注'
-					}
-				},
-			
-				/* question_type: {
-					title: '问题类型',
-					search: { show: true },
-					type: 'dict-select',
-					column: {
-						minWidth: 100,
-					},
-					dict: dict({
-						data: [
-							{ value: 1, label: '专业技能' },
-							{ value: 2, label: '通用能力' },
-							{ value: 3, label: '个人特质' }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '问题类型必填' }],
-						component: {
-							placeholder: '请选择问题类型',
-						},
-						helper: '选择问题的类型分类',
-					},
-				}, */
-				question_form: {
-					title: '题目形式',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'dict-select',
-					column: {
-						
-						minWidth: 120,
-					},
-					dict: dict({
-						// 使用API方式获取数据
-						getData: async () => {
-							const res = await api.GetQuestionTypeList();
-							// 返回question_forms数组
-							return res.data.question_forms || [];
-						},
-						label: 'label',
-						value: 'value'
-					}),
-					form: {
-						rules: [{ required: true, message: '题目形式必填' }],
-						component: {
-							placeholder: '请选择题目形式',
-							onChange: ({ form }: { form: any }) => {
-								// 重置相关字段
-								if (form.question_form === 1) {
-									// 单选题
-									form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
-								} else if (form.question_form === 2) {
-									// 多选题
-									form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
-								} else if (form.question_form === 4) {
-									// 得分题 - 初始化五个评分选项
-									form.options = [
-										{ option_text: '无', score: 0, sort: 1 },
-										{ option_text: '轻度', score: 1, sort: 2 },
-										{ option_text: '中度', score: 2, sort: 3 },
-										{ option_text: '偏重', score: 3, sort: 4 },
-										{ option_text: '严重', score: 4, sort: 5 }
-									];
-									// 初始化评分说明
-									if (!form.scoring_reference) {
-										form.scoring_reference = '该评分反映了症状表现的频率与严重程度。';
-									}
-								} else {
-									// 开放问题
-									form.options = [];
-								}
-							}
-						},
-						helper: '选择题目的形式:开放问题、单选题、多选题、色盲题或追加型开放问题',
-					},
-				},
-				position_types: {
-					title: '适用职位',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3}, },
-					type: 'dict-select',
-					column: {
-						minWidth: 120,
-						 maxTagCount: 1
-					},
-					dict: dict({
-						getData: async () => {
-							const res = await api.GetPositionList({page:1,limit:1000,tenant_id:1});
-							console.log(res.data);
-							return res.data || [];
-						},
-						label: 'title',
-						value: 'id'
-					}),
-					form: {
-						component: {
-						/* 	name: 'el-select', */
-							props: {
-								multiple: true,
-								filterable: true,
-								placeholder: '请选择适用职位类型',
-								/* options: [
-									{ value: '0', label: '技术' },
-									{ value: '1', label: '管理' },
-								] */
-							}
-						},
-						helper: '选择题目适用的职位类型,可多选'
-					}
-				},
-				category_id: {
-					title: '分类',
-					search: { show: true,
-						size: 'small',
-						col:{ span:2}, },
-					type: 'dict-select',
-					column: {
-						minWidth: 120,
-						component: {
-							// 自定义渲染组件,显示彩色标签
-							// name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								if (!row.category || row.category.length === 0) return <span>-</span>;
-								
-								return (
-											<el-tag
-												key={row.category.id}
-												type="warning"
-												effect="plain"
-												size="mini"
-												style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
-											>
-												{row.category.name.length > 4 ? row.category.name.slice(0, 4) + '...' : row.category.name}
-											</el-tag>
-								);
-							}
-						}
-					},
-					dict: dict({
-						// 使用API方式获取数据
-						getData: async () => {
-							const res = await api.GetcategoryList({page:1, limit:1000, tenant_id:1});
-							return res.data.items;
-						},
-						label: 'name',
-						value: 'id'
-					}),
-					form: {
-						// rules: [{ required: true, message: '题目分类必填' }],
-						component: {
-							placeholder: '请选择题目分类',
-							/* onChange: ({ value }: { value: number }) => {
-								// 当分类变化时,重新获取对应的标签
-								fetchTags(value);
-							} */
-						},
-						helper: '选择所属的分类'
-					},
-				},
-				tags:{	
-					title: '标签',
-					search: { 
-						show: true,
-						size: 'small',
-						col:{ span:3},
-						component: {
-							props: {
-								multiple: false, // 在搜索表单中使用单选模式
-								filterable: true,
-								placeholder: '请选择标签搜索'
-							}
-						}
-					},
-					type: 'dict-select',
-					column: {
-						minWidth: 220,
-						component: {
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								if (!row.tags || row.tags.length === 0) return <span>-</span>;
-								
-								const displayTags = row.tags.slice(0, 2); // 只取前两个标签
-								const remainingCount = row.tags.length - 2; // 计算剩余标签数量
-								
-								return (
-									<div style="display: flex; gap: 4px; align-items: center;">
-										{displayTags.map((tag: any) => (
-											<el-tag
-												key={tag.id}
-												type="warning"
-												effect="plain"
-												size="mini"
-												style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
-												title={tag.name}
-											>
-												{tag.name.length > 4 ? tag.name.slice(0, 4) + '...' : tag.name}
-											</el-tag>
-										))}
-										{remainingCount > 0 && (
-											<el-tooltip
-												placement="top"
-												effect="light"
-												popper-class="tag-tooltip"
-											>
-												{{
-													default: () => (
-														<el-tag
-															type="info"
-															effect="plain"
-															size="mini"
-														>
-															+{remainingCount}
-														</el-tag>
-													),
-													content: () => (
-														<div>
-															<div style="font-weight: bold; margin-bottom: 5px">剩余{remainingCount}个标签:</div>
-															{row.tags.slice(2).map((tag: any) => (
-																<div key={tag.id} style="margin: 3px 0">{tag.name}</div>
-															))}
-														</div>
-													)
-												}}
-											</el-tooltip>
-										)}
-									</div>
-								);
-							}
-						}
-					},
-					dict:  dict({
-						// 使用API方式获取数据
-						getData: async () => {
-							const res = await api.GetTagList({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 },
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						value: 60,
-						component: {
-							name: 'el-input-number',
-							props: {
-								min: 10,
-								max: 600,
-								step: 10
-							}
-						},
-						helper: '建议回答此题目的时长,单位为秒'
-					}
-				},
-				/* difficulty: {
-					title: '难度等级',
-					search: { show: true },
-					type: 'dict-select',
-					column: {
-						minWidth: 80,
-					},
-					dict: dict({
-						data: [
-							{ value: 1, label: '初级' },
-							{ value: 2, label: '中级' },
-							{ value: 3, label: '高级' }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '难度等级必填' }],
-						helper: '选择题目的难度级别'
-					},
-				}, */
-				is_system: {
-					title: '系统题目',
-					search: { show: false },
-					type: 'dict-select',
-					column: {
-						width: 100,
-						/* component: {
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								return row.is_system ? 
-									<el-tag type="success" size="small">是</el-tag> : 
-									<el-tag type="info" size="small">否</el-tag>;
-							}
-						} */
-					},
-					dict: dict({
-						data: [
-							{ value: true, label: '是' },
-							{ value: false, label: '否' }
-						]
-					}),
-					form: {
-						value: false,
-						component: {
-							placeholder: '请选择是否为系统题目'
-						},
-						helper: '是否为系统预设题目'
-					}
-				},
-				status: {
-					title: '状态',
-					search: { show: true,
-						size: 'small',
-						col:{ span:2},},
-					type: 'dict-select',
-					column: {
-						width: 80,
-					},
-					dict: dict({
-						data: [
-							{ value: 0, label: '停用' },
-							{ value: 1, label: '启用' }
-						]
-					}),
-					form: {
-						value: 1,
-						rules: [{ required: true, message: '状态必填' }],
-						component: {
-							placeholder: '请选择状态',
-						},
-						helper: '题目的启用状态'
-					},
-				},
-				// 添加红线问题字段
-				is_required_correct: {
-					title: '红线问题',
-					search: { show: true,
-						size: 'small',
-						col:{ span:2}, },
-					type: 'dict-select',
-					column: {
-						width: 100,
-						component: {
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								// 只有单选题和多选题才显示红线问题状态
-								if (row.question_form !== 1 && row.question_form !== 2) {
-									return <span>-</span>;
-								}
-								return row.is_required_correct ? 
-									<el-tag type="danger" size="small">是</el-tag> : 
-									<el-tag type="info" size="small">否</el-tag>;
-							}
-						}
-					},
-					dict: dict({
-						data: [
-							{ value: true, label: '是' },
-							{ value: false, label: '否' }
-						]
-					}),
-					form: {
-						value: false,
-						show: compute(({ form }) => {
-							// 只有单选题和多选题才显示红线问题选项
-							return form && (form.question_form === 1 || form.question_form === 2);
-						}),
-						component: {
-							placeholder: '请选择是否为红线问题'
-						},
-						helper: '红线问题是指必须回答正确的题目,否则可能导致面试失败'
-					}
-				},
-				sort: {
-					title: '排序',
-					search: { show: false },
-					column: {
-						width: 80,
-					},
-					form: {
-						component: {
-							name: 'el-input-number',
-							props: {
-								min: 1,
-								max: 999
-							}
-						},
-						helper: '题目的排序值,值越小排序越靠前'
-					}
-				},
-				option_items: {
-					title: '选项列表',
-					search: { show: false },
-					column: { show: false },
-					form: {
-						show: compute(({ form }) => {
-							return form && (form.question_form === 1 || form.question_form === 2 || form.question_form === 4);
-						}),
-						component: {
-							name: 'el-card',
-							children: {
-								default: ({ form }: { form: any }) => {
-									// 确保options数组已初始化
-									if (!form.options) {
-										form.options = [];
-									}
-									
-									// 心理评估题的选项渲染
-									if (form.question_form === 4) {
-										return (
-											<div>
-												<div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
-													<span style="flex: 1;">选项内容</span>
-													<span style="width: 120px; text-align: center;">分值</span>
-												</div>
-												{form.options.map((option: any, index: number) => (
-													<div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
-														<el-input 
-															v-model={option.option_text} 
-															placeholder="请输入选项内容" 
-															style="flex: 1; margin-right: 10px;"
-														/>
-														<el-input-number
-															v-model={option.score}
-															min={0}
-															max={10}
-															style="width: 120px;"
-															placeholder="分值"
-														/>
-													</div>
-												))}
-												<div style="display: flex; justify-content: space-between; margin-top: 10px;">
-													<el-button 
-														type="primary" 
-														onClick={() => {
-															if (!form.options) {
-																form.options = [];
-															}
-															const sort = form.options.length > 0 ? 
-																Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
-															form.options.push({ option_text: '', score: 0, sort });
-														}}
-													>
-														添加选项
-													</el-button>
-													
-													{form.options.length > 2 && (
-														<el-button 
-															type="danger" 
-															onClick={() => {
-																form.options.pop();
-															}}
-														>
-															删除最后一项
-														</el-button>
-													)}
-												</div>
-											</div>
-										);
-									}
-									
-									// 单选题和多选题的选项渲染(原有逻辑)
-									return (
-										<div>
-											<div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
-												<span style="flex: 1;">选项内容</span>
-												<span style="width: 80px; text-align: center;">是否正确</span>
-											</div>
-											{form.options.map((option: any, index: number) => (
-												<div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
-													<el-input 
-														v-model={option.option_text} 
-														placeholder="请输入选项内容" 
-														style="flex: 1; margin-right: 10px;"
-													/>
-													<el-tooltip 
-														content="设置为正确答案" 
-														placement="top" 
-														effect="light"
-													>
-														<div 
-															onClick={() => {
-																if (form.question_form === 1) {
-																	// 单选题:只能有一个正确答案
-																	form.options.forEach((item: any, idx: number) => {
-																		item.is_correct = (idx === index);
-																	});
-																} else {
-																	// 多选题:可以有多个正确答案
-																	option.is_correct = !option.is_correct;
-																}
-															}}
-															style="cursor: pointer; width: 80px; text-align: center;"
-														>
-															{form.question_form === 1 ? (
-																// 单选题使用单选按钮
-																<el-radio 
-																	modelValue={option.is_correct}
-																	label={true}
-																/>
-															) : (
-																// 多选题使用复选框
-																<el-checkbox 
-																	modelValue={option.is_correct}
-																/>
-															)}
-														</div>
-													</el-tooltip>
-												</div>
-											))}
-											<div style="display: flex; justify-content: space-between; margin-top: 10px;">
-												<el-button 
-													type="primary" 
-													onClick={() => {
-														// 确保options数组已初始化
-														if (!form.options) {
-															form.options = [];
-														}
-														// 添加新选项时自动设置排序值
-														const sort = form.options.length > 0 ? 
-															Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
-														form.options.push({ option_text: '', is_correct: false, sort });
-													}}
-												>
-													添加选项
-												</el-button>
-												
-												{form.options.length > 2 && (
-													<el-button 
-														type="danger" 
-														onClick={() => {
-															form.options.pop();
-														}}
-													>
-														删除最后一项
-													</el-button>
-												)}
-											</div>
-										</div>
-									)
-								}
-							}
-						},
-						helper: compute(({ form }) => {
-							if (form.question_form === 1) {
-								return '添加单选题的选项,并标记正确答案(只能有一个正确答案)';
-							} else if (form.question_form === 2) {
-								return '添加多选题的选项,并标记正确答案(可以有多个正确答案)';
-							} else if (form.question_form === 4) {
-								return '添加心理评估题的选项,并设置每个选项的分值';
-							}
-							return '';
-						})
-					}
-				},
-				scoring_reference: {
-					title: '评分标准',
-					search: { show: false },
-					column: { show: false },
-					form: {
-						component: {
-							name: 'el-input',
-							type: 'textarea',
-							rows: 4,
-							placeholder: compute(({ form }) => {
-								if (form && form.question_form === 4) {
-									return '请输入评分标准说明';
-								}
-								return '请输入评分标准说明';
-							})
-						},
-						helper: compute(({ form }) => {
-							if (form && form.question_form === 4) {
-								return '评分标准说明,例如:该评分反映了症状表现的频率与严重程度。';
-							}
-							return '题目的评分标准';
-						})
-					},
-				},
-				digital_human_video_url:{
-					title: '视频是否生成',
-					search: { show: false },
-					column: {
-						width: 120,
-						component: {
-							name: 'fs-component',
-							render: ({ row }: { row: any }) => {
-								return row.digital_human_video_url ? 
-									<el-tag type="success" size="small">是</el-tag> : 
-									<el-tag type="info" size="small">否</el-tag>;
-							}
-						}
-					},
-					form: {
-						show: false // 在表单中不显示此字段,通常由系统自动生成
-					}
-				}
-				
-			},
-			// 确保表格配置正确
-			table: {
-				selection: true,
-				onSelectionChange: (selection: any[]) => {
-					// 存储选中的行到一个全局变量中
-					context.selectedRows = selection;
-				},
-			},
-		},
-	};
-};
-
-// 导出批量更新标签方法,供组件使用
-export const useBatchUpdateTags = (crudExpose: any) => {
-	const batchUpdateTags = async (questionIds: number[], tagIds: number[]) => {
-		try {
-			const res = await api.BatchUpdateTags({
-				question_ids: questionIds,
-				tags: tagIds,
-				tenant_id: 1
-			});
-			if (res.code === 0) {
-				successMessage('批量更新标签成功');
-				// 刷新列表
-				crudExpose.doRefresh();
-			}
-		} catch (error) {
-			console.error('批量更新标签失败', error);
-		}
-	};
-	
-	return { batchUpdateTags };
-};
-
-// 导出批量更新分类方法,供组件使用
-export const useBatchUpdateCategory = (crudExpose: any) => {
-	const batchUpdateCategory = async (questionIds: number[], categoryId: number) => {
-		try {
-			const res = await api.BatchUpdateCategory({
-				question_ids: questionIds,
-				category_id: categoryId,
-				tenant_id: "1"
-			});
-			if (res.code === 0) {
-				successMessage('批量更新分类成功');
-				// 刷新列表
-				crudExpose.doRefresh();
-			}
-		} catch (error) {
-			console.error('批量更新分类失败', error);
-		}
-	};
-	
-	return { batchUpdateCategory };
-};
-
-// 导出批量更新胜任力标签方法,供组件使用
-export const useBatchUpdateCompetencyTags = (crudExpose: any) => {
-	const batchUpdateCompetencyTags = async (questionIds: number[], competencyTags: any[]) => {
-		try {
-			const res = await api.BatchUpdateCompetencyTags({
-				question_ids: questionIds,
-				competency_tags: competencyTags,
-				tenant_id: 1
-			});
-			if (res.code === 0) {
-				successMessage('批量更新胜任力标签成功');
-				// 刷新列表
-				crudExpose.doRefresh();
-			}
-		} catch (error) {
-			console.error('批量更新胜任力标签失败', error);
-		}
-	};
-	
-	return { batchUpdateCompetencyTags };
-};

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

@@ -1,63 +0,0 @@
-<template>
-	<fs-page>
-		<fs-crud ref="crudRef" :dialog-width="800"  v-bind="crudBinding"> </fs-crud>
-		<PermissionDrawerCom />
-		<BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
-		<BatchCategoryDialog ref="batchCategoryDialogRef" :crudExpose="crudExpose" />
-		<BatchCompetencyTagsDialog ref="batchCompetencyTagsDialogRef" :crudExpose="crudExpose" />
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="role">
-import { defineAsyncComponent, onMounted, ref } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { RoleDrawerStores } from './stores/RoleDrawerStores';
-import { RoleMenuBtnStores } from './stores/RoleMenuBtnStores';
-import { RoleMenuFieldStores } from './stores/RoleMenuFieldStores';
-import { RoleUsersStores } from './stores/RoleUsersStores';
-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(); // 角色-菜单
-const RoleMenuField = RoleMenuFieldStores();// 角色-菜单-字段
-const RoleUsers = RoleUsersStores();// 角色-用户
-const { crudBinding, crudRef, crudExpose } = useFs({
-	createCrudOptions,
-	context: { 
-		RoleDrawer, 
-		RoleMenuBtn, 
-		RoleMenuField,
-		$message: {
-			warning: (msg: string) => successMessage(msg)
-		},
-		openBatchTagsDialog: (selection: any) => {
-			batchTagsDialogRef.value.open(selection);
-		},
-		openBatchCategoryDialog: (selection: any) => {
-			batchCategoryDialogRef.value.open(selection);
-		},
-		openBatchCompetencyTagsDialog: (selection: any) => {
-			batchCompetencyTagsDialogRef.value.open(selection);
-		},
-		selectedRows: [] // 存储选中的行
-	},
-});
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 刷新
-	crudExpose.doRefresh();
-	// 获取全部用户
-	RoleUsers.get_all_users();
-});
-</script>

+ 0 - 37
src/views/questionBank/list/stores/RoleDrawerStores.ts

@@ -1,37 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleDrawerType } from '../types';
-/**
- * 权限配置:抽屉
- */
-const initialState: RoleDrawerType = {
-	drawerVisible: false,
-	roleId: undefined,
-	roleName: undefined,
-	users: [],
-};
-
-export const RoleDrawerStores = defineStore('RoleDrawerStores', {
-	state: (): RoleDrawerType => ({
-		...initialState,
-	}),
-	actions: {
-		/**
-		 * 打开权限修改抽屉
-		 */
-		handleDrawerOpen(row: any) {
-			this.drawerVisible = true;
-			this.set_state(row);
-		},
-		set_state(row: any) {
-			this.roleName = row.name;
-			this.roleId = row.id;
-			this.users = row.users;
-		},
-		/**
-		 * 关闭权限修改抽屉
-		 */
-		handleDrawerClose() {
-			Object.assign(this.$state, initialState);
-		},
-	},
-});

+ 0 - 24
src/views/questionBank/list/stores/RoleMenuBtnStores.ts

@@ -1,24 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleMenuBtnType } from '../types';
-/**
- * 权限配置:角色-菜单-按钮
- */
-
-export const RoleMenuBtnStores = defineStore('RoleMenuBtnStores', {
-	state: (): RoleMenuBtnType[] => [],
-	actions: {
-		/**
-		 * 初始化
-		 */
-		setState(data: RoleMenuBtnType[]) {
-			this.$state = data;
-			this.$state.length = data.length;
-		},
-		updateState(data: RoleMenuBtnType) {
-			const index = this.$state.findIndex((item) => item.id === data.id);
-			if (index !== -1) {
-				this.$state[index] = data;
-			}
-		},
-	},
-});

+ 0 - 24
src/views/questionBank/list/stores/RoleMenuFieldStores.ts

@@ -1,24 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleMenuFieldType, RoleMenuFieldHeaderType } from '../types';
-/**
- * 权限配置:角色-菜单-列字段
- */
-
-export const RoleMenuFieldStores = defineStore('RoleMenuFieldStores', {
-	state: (): RoleMenuFieldType[] => [],
-	actions: {
-		/** 重置 */
-		setState(data: RoleMenuFieldType[]) {
-			this.$state = data;
-			this.$state.length = data.length;
-		},
-	},
-});
-
-export const RoleMenuFieldHeaderStores = defineStore('RoleMenuFieldHeaderStores', {
-	state: (): RoleMenuFieldHeaderType[] => [
-		{ value: 'is_create', label: '新增可见', disabled: 'disabled_create', checked: false },
-		{ value: 'is_update', label: '编辑可见', disabled: 'disabled_update', checked: false },
-		{ value: 'is_query', label: '列表可见', disabled: 'disabled_query', checked: false },
-	],
-});

+ 0 - 21
src/views/questionBank/list/stores/RoleMenuTreeStores.ts

@@ -1,21 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleMenuTreeType } from '../types';
-/**
- * 权限抽屉:角色-菜单
- */
-
-export const RoleMenuTreeStores = defineStore('RoleMenuTreeStores', {
-	state: (): RoleMenuTreeType => ({
-		id: 0,
-		parent: 0,
-		name: '',
-		isCheck: false,
-		is_catalog: false,
-	}),
-	actions: {
-		/** 赋值 */
-		setRoleMenuTree(data: RoleMenuTreeType) {
-			this.$state = data;
-		},
-	},
-});

+ 0 - 24
src/views/questionBank/list/stores/RoleUsersStores.ts

@@ -1,24 +0,0 @@
-import { defineStore } from 'pinia';
-import { RoleUsersType } from '../types';
-import { getAllUsers } from '../components/api';
-import XEUtils from 'xe-utils';
-/**
- * 权限抽屉:角色-用户
- */
-
-export const RoleUsersStores = defineStore('RoleUsersStores', {
-	state: (): RoleUsersType => ({
-		all_users: [],
-		right_users: [],
-	}),
-	actions: {
-		get_all_users() {
-			getAllUsers().then((res: any) => {
-				this.$state.all_users = res;
-			});
-		},
-		set_right_users(users: any) {
-			this.$state.right_users = XEUtils.map(users, (item: any) => item.id);
-		},
-	},
-});

+ 0 - 99
src/views/questionBank/list/types.ts

@@ -1,99 +0,0 @@
-/**角色列表数据类型 */
-export interface RoleItemType {
-	id: string | number;
-	modifier_name: string;
-	creator_name: string;
-	create_datetime: string;
-	update_datetime: string;
-	description: string;
-	modifier: string;
-	dept_belong_id: string;
-	name: string;
-	key: string;
-	sort: number;
-	status: boolean;
-	admin: boolean;
-	creator: string;
-}
-
-export interface UsersType {
-	id: string | number;
-	name: string;
-}
-export interface RoleUsersType {
-	all_users: UsersType[];
-	right_users: UsersType[];
-}
-
-/**
- * 权限配置 抽屉组件参数数据类型
- */
-export interface RoleDrawerType {
-	/** 是否显示抽屉*/
-	drawerVisible: boolean;
-	/** 角色id*/
-	roleId: string | number | undefined;
-	/** 角色名称*/
-	roleName: string | undefined;
-	/** 用户*/
-	users: UsersType[];
-}
-
-/**
- * 菜单数据类型
- */
-export interface RoleMenuTreeType {
-	id: number | string | undefined;
-	/** 父级id */
-	parent: number | string | undefined;
-	name: string;
-	/** 是否选中 */
-	isCheck: boolean;
-	/** 是否是目录 */
-	is_catalog: boolean;
-}
-/**
- * 菜单-按钮数据类型
- */
-export interface RoleMenuBtnType {
-	id: string | number;
-	menu_btn_pre_id: string | number;
-	/** 是否选中 */
-	isCheck: boolean;
-	/** 按钮名称 */
-	name: string;
-	/** 数据权限范围 */
-	data_range: number | null;
-	/** 自定义部门 */
-	dept: number[];
-}
-
-/**
- * 菜单-列字段数据类型
- */
-export interface RoleMenuFieldType {
-	id: string | number | boolean;
-	/** 模型表字段名 */
-	field_name: string;
-	/** 字段显示名	*/
-	title: string;
-	/** 是否可查询 */
-	is_query: boolean;
-	/** 是否可创建 */
-	is_create: boolean;
-	/** 是否可更新 */
-	is_update: boolean;
-	[key: string]: string | number | boolean;
-}
-/**
- * 菜单-列字段-标题数据类型
- */
-export interface RoleMenuFieldHeaderType {
-	value: string;
-	/** 模型表字段名 */
-	label: string;
-	/** 字段显示名	*/
-	disabled: string;
-	/** 是否可查询 */
-	checked: boolean;
-}

+ 0 - 118
src/views/questionBank/positionList/api.ts

@@ -1,118 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-import { Session } from '/@/utils/storage';
-import { time } from 'console';
-
-
-export function GetDocumentList(query: UserPageQuery) {
-  return request({
-    url: 'api/system/job/questions',
-    method: 'get',
-    params: {...query,tenant_id:'1'},
-  });
-}
-
-export function AddDocument(obj: AddReq) {
-  return request({
-    url: 'api/system/job/add_question',
-    method: 'post',
-    data: {...obj,tenant_id:"1"},
-  });
-}
-
-/* 文档管理编辑 /api/system/document/{id}/*/
-export function UpdateDocument(obj: any) {
-	return request({
-		url: '/api/system/interview_question/update',
-		method: 'post',
-		data:{...obj,tenant_id:"1"}
-	});
-}
-
-/* 文档管理查看 /api/system/document/{id}/*/
-export function GetDocument(id: any) {
-	return request({
-		url: '/api/system/document/'+id+ '/',
-		method: 'get',
-	});
-}
-
-/* 文档管理删除 /api/system/document/{id}/*/
-export function DelDocument(data:any) {
-	return request({
-		url: 'api/system/job/delete_question',
-		method: 'post',
-		data
-	});
-}
-
-// 获取文档树结构
-export function GetDocumentTree(query: any) {
-  return request({
-    url: '/api/system/job/list',
-    method: 'get',
-    params: {...query,tenant_id:'1'},
-  });
-}
-
-// 添加文档分类
-export function AddDocumentCategory(data: any) {
-  return request({
-    url: '/api/system/document-categories/',
-    method: 'post',
-    data:{...data,tenant_id:'1'}
-  });
-}
-
-// 更新文档分类
-export function UpdateDocumentCategory(data: any) {
-  return request({
-    url: `/api/system/document-categories/${data.id}/`,
-    method: 'put',
-    data:{...data,tenant_id:'1'}
-  });
-}
-
-// 删除文档分类
-export function DeleteDocumentCategory(id: any) {
-  return request({
-    url: `/api/system/document-categories/${id}/`,
-    method: 'delete'
-  });
-}
-
-// 添加获取面试问题的方法
-export function GetInterviewQuestions(params: any, timeout = 60000) {
-    return request({
-        url: '/api/system/interview_question/list',
-        method: 'get',
-        params,
-        timeout
-    });
-}
-
-// 添加更新排序的API函数
-export function UpdateSequence(data:any) {
-  return request({
-    url: '/api/system/job/update_question_sequence_numbers',
-    method: 'post',
-    data
-  });
-}
-
-// 获取问题类型
-export function GetQuestionTypeList() {
-	return request({
-		url: '/api/wechat/question_type_enums',
-		method: 'get'
-	});
-}
-
-// 添加 UpdateInterviewQuestion 函数到 API 文件
-export const UpdateInterviewQuestion = (params: any) => {
-  return request({
-    url: '/api/system/interview_question/update',
-    method: 'post',
-    data: params
-  });
-};

+ 0 - 172
src/views/questionBank/positionList/components/AsideTable/crud.ts

@@ -1,172 +0,0 @@
-import { CreateCrudOptionsProps, CreateCrudOptionsRet, dict, compute } from '@fast-crud/fast-crud';
-import * as api from '../../api';
-import { ElMessage } from 'element-plus';
-
-export const createAsideCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-  const pageRequest = async (query: any) => {
-    const params = {
-      ...query,
-      tenant_id: '1',
-      job_id: query.form?.job_id || ''
-    };
-    
-    return api.GetDocumentList(params);
-  };
-
-  return {
-    crudOptions: {
-      request: {
-        pageRequest
-      },
-      actionbar: {
-        buttons: {
-          add: {
-            show: true,
-            click: () => {
-              // 打开添加问题对话框
-              crudExpose.openAdd();
-            }
-          }
-        }
-      },
-      rowHandle: {
-        width: 300,
-        buttons: {
-          view: {
-            size: 'small',
-            icon: "View",
-            type: 'text',
-            click: ({ row }: any) => {
-              const event = new CustomEvent('viewDocumentDetail', { detail: row });
-              window.dispatchEvent(event);
-            }
-          },
-          uploadVideo: {
-            text: '上传视频',
-            type: 'text',
-            icon: "upload",
-            size: 'small',
-            click: ({ row }: any) => {
-              const event = new CustomEvent('openVideoUploadDialog', { detail: row });
-              window.dispatchEvent(event);
-            }
-          },
-          edit: {
-            type: 'text',
-            icon: "Edit",
-            size: 'small',
-          },
-          remove: {
-            type: 'text',
-            icon: "Delete",
-            size: 'small',
-          },
-          sort: {
-            type: 'text',
-            icon: "sort",
-            size: 'small',
-            text: '修改排序',
-            click: ({ row }: any) => {
-              const event = new CustomEvent('openSortDialog', { detail: row });
-              window.dispatchEvent(event);
-            }
-          }
-        }
-      },
-      columns: {
-        id: {
-          title: 'ID',
-          column: { show: true, width: 80 },
-          search: { show: false },
-          form: { show: false },
-        },
-        question: {
-          title: '题目内容',
-          search: { show: true },
-          column: {
-            sortable: 'custom',
-            showOverflowTooltip: true,
-            width: 300
-          },
-          form: {
-            rules: [{ required: true, message: '题目内容必填' }],
-            component: {
-              placeholder: '请输入题目内容',
-            }
-          },
-        },
-        question_type: {
-          title: '问题类型',
-          search: { show: true },
-          type: 'dict-select',
-          column: {
-            minWidth: 100,
-          },
-          dict: dict({
-            data: [
-              { value: 1, label: '专业技能' },
-              { value: 2, label: '通用能力' },
-              { value: 3, label: '个人特质' }
-            ]
-          }),
-          form: {
-            rules: [{ required: true, message: '问题类型必填' }],
-            component: {
-              placeholder: '请选择问题类型',
-            },
-            helper: '选择问题的类型分类'
-          },
-        },
-        difficulty: {
-          title: '难度等级',
-          search: { show: true },
-          type: 'dict-select',
-          column: {
-            minWidth: 80,
-          },
-          dict: dict({
-            data: [
-              { value: 1, label: '初级' },
-              { value: 2, label: '中级' },
-              { value: 3, label: '高级' }
-            ]
-          }),
-          form: {
-            rules: [{ required: true, message: '难度等级必填' }],
-            helper: '选择题目的难度级别'
-          },
-        },
-        sequence_number: {
-          title: '排序',
-          search: { show: false },
-          column: { show: true },
-          form: { show: true },
-        },
-        digital_human_video_url: {
-          title: '视频链接',
-          search: { show: false },
-          column: { 
-            show: true,
-            width: 120,
-            formatter: ({ row }: any) => {
-              return row.digital_human_video_url ? '已上传' : '未上传';
-            }
-          },
-          form: { show: false },
-        },
-        digital_human_video_subtitle: {
-          title: '字幕',
-          search: { show: false },
-          column: { 
-            show: true,
-            width: 120,
-            formatter: ({ row }: any) => {
-              return row.digital_human_video_subtitle ? '已上传' : '未上传';
-            }
-          },
-          form: { show: false },
-        },
-      }
-    }
-  };
-}; 

+ 0 - 68
src/views/questionBank/positionList/components/AsideTable/index.vue

@@ -1,68 +0,0 @@
-<template>
-  <div class="aside-table-container">
-    <div class="aside-header">
-      <h3>{{ currentPosition ? currentPosition.title + ' - 问题列表' : '请选择职位' }}</h3>
-    </div>
-    <fs-crud ref="asideCrudRef" v-bind="crudBinding"></fs-crud>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref, onMounted, watch } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createAsideCrudOptions } from './crud';
-import { ElMessage } from 'element-plus';
-
-const props = defineProps({
-  positionId: {
-    type: [String, Number],
-    default: ''
-  },
-  currentPosition: {
-    type: Object,
-    default: () => null
-  }
-});
-
-const emit = defineEmits(['refresh']);
-
-const { crudBinding, crudRef: asideCrudRef, crudExpose } = useFs({ createCrudOptions: createAsideCrudOptions });
-
-// 监听职位ID变化,刷新问题列表
-watch(() => props.positionId, (newVal) => {
-  if (newVal) {
-    loadQuestions(newVal);
-  }
-}, { immediate: true });
-
-// 加载问题列表
-const loadQuestions = (positionId) => {
-  if (!positionId) return;
-  
-  crudExpose.doSearch({
-    form: {
-      job_id: positionId
-    }
-  });
-};
-
-// 暴露方法给父组件
-defineExpose({
-  refresh: () => crudExpose.doRefresh(),
-  asideCrudRef
-});
-</script>
-
-<style lang="scss" scoped>
-.aside-table-container {
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-}
-
-.aside-header {
-  padding: 10px 0;
-  border-bottom: 1px solid #ebeef5;
-  margin-bottom: 10px;
-}
-</style> 

+ 0 - 115
src/views/questionBank/positionList/components/DocumentFormCom/index.vue

@@ -1,115 +0,0 @@
-<template>
-  <div class="document-form-container">
-    <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
-      <el-form-item label="分类名称" prop="name">
-        <el-input v-model="formData.name" placeholder="请输入分类名称" />
-      </el-form-item>
-      
-      <el-form-item label="上级分类" prop="parent">
-        <el-tree-select
-          v-model="formData.parent"
-          :data="treeData"
-          :props="{ label: 'name', value: 'id', children: 'children' }"
-          placeholder="请选择上级分类"
-          check-strictly
-          clearable
-        />
-      </el-form-item>
-      
-      <el-form-item label="排序" prop="sort">
-        <el-input-number v-model="formData.sort" :min="0" :max="999" />
-      </el-form-item>
-      
-      <el-form-item label="描述" prop="description">
-        <el-input v-model="formData.description" type="textarea" rows="4" placeholder="请输入分类描述" />
-      </el-form-item>
-      
-      <el-form-item>
-        <el-button type="primary" @click="handleSubmit">保存</el-button>
-        <el-button @click="handleCancel">取消</el-button>
-      </el-form-item>
-    </el-form>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive, defineProps, defineEmits, onMounted } from 'vue';
-import { AddDocumentCategory, UpdateDocumentCategory } from '../../api';
-import { successNotification } from '/@/utils/message';
-import type { FormInstance } from 'element-plus'
-
-const props = defineProps({
-  initFormData: {
-    type: Object,
-    default: () => ({})
-  },
-  cacheData: {
-    type: Array,
-    default: () => []
-  },
-  treeData: {
-    type: Array,
-    default: () => []
-  }
-});
-
-const emit = defineEmits(['drawerClose']);
-
-const formRef = ref<FormInstance | null>(null);
-const formData = reactive({
-  id: '',
-  name: '',
-  parent: '',
-  sort: 0,
-  description: ''
-});
-
-const rules = {
-  name: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
-};
-
-// 初始化表单数据
-onMounted(() => {
-  if (props.initFormData && props.initFormData.id) {
-    Object.assign(formData, props.initFormData);
-  }
-});
-
-// 提交表单
-const handleSubmit = async () => {
-  if (!formRef.value) return;
-  
-  await formRef.value.validate(async (valid: any) => {
-    if (valid) {
-      try {
-        let res;
-        if (formData.id) {
-          // 更新
-          res = await UpdateDocumentCategory(formData);
-        } else {
-          // 新增
-          res = await AddDocumentCategory(formData);
-        }
-        
-        if (res && res.code === 2000) {
-          successNotification(res.msg || '操作成功');
-          emit('drawerClose', 'submit');
-        }
-      } catch (error) {
-        console.error('保存文档分类失败:', error);
-      }
-    }
-  });
-};
-
-// 取消
-const handleCancel = () => {
-  emit('drawerClose');
-};
-</script>
-
-<style lang="scss" scoped>
-.document-form-container {
-  padding: 20px;
-}
-</style> 

+ 0 - 128
src/views/questionBank/positionList/components/DocumentTreeCom/index.vue

@@ -1,128 +0,0 @@
-<template>
-  <div class="document-tree-container">
-    <div class="document-tree-header">
-      <span class="document-tree-title">职位</span>
-     <!--  <el-button type="primary" size="small" @click="handleAddDocument">
-        <el-icon><Plus /></el-icon>添加分类
-      </el-button> -->
-    </div>
-    <el-tree
-      ref="treeRef"
-      :data="treeData"
-      node-key="id"
-      :props="defaultProps"
-      default-expand-all
-      highlight-current
-      @node-click="handleNodeClick"
-    >
-      <template #default="{ node, data }">
-        <div class="custom-tree-node">
-          <div class="node-label">
-            <el-icon><Folder /></el-icon>
-            <span class="ml-2">{{ data.title }}</span>
-          </div>
-          <div class="node-actions" v-if="data.id">
-            <el-button type="primary" link size="small" @click.stop="handleEdit(data)">
-              <el-icon><Edit /></el-icon>
-            </el-button>
-            <el-button type="danger" link size="small" @click.stop="handleDelete(node, data)">
-              <el-icon><Delete /></el-icon>
-            </el-button>
-          </div>
-        </div>
-      </template>
-    </el-tree>
-  </div>
-</template>
-
-<script lang="ts" setup>
-import { ref, defineProps, defineEmits } from 'vue';
-import { Plus, Folder, Edit, Delete } from '@element-plus/icons-vue';
-
-const props = defineProps({
-  treeData: {
-    type: Array,
-    default: () => []
-  }
-});
-
-const emit = defineEmits(['treeClick', 'updateDocument', 'deleteDocument']);
-
-const treeRef = ref(null);
-
-const defaultProps = {
-  children: 'children',
-  label: 'name'
-};
-
-// 处理节点点击
-const handleNodeClick = (data: any) => {
-  emit('treeClick', data);
-};
-
-// 添加文档分类
-const handleAddDocument = () => {
-  emit('updateDocument', 'add');
-};
-
-// 编辑文档分类
-const handleEdit = (data: any) => {
-  emit('updateDocument', 'update', data);
-};
-
-// 删除文档分类
-const handleDelete = (node: any, data: any) => {
-  emit('deleteDocument', data.id, () => {
-    // 回调函数,删除成功后可以执行
-  });
-};
-
-defineExpose({
-  treeRef
-});
-</script>
-
-<style lang="scss" scoped>
-.document-tree-container {
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-}
-
-.document-tree-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16px;
-}
-
-.document-tree-title {
-  font-size: 16px;
-  font-weight: bold;
-}
-
-.custom-tree-node {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding-right: 8px;
-}
-
-.node-label {
-  display: flex;
-  align-items: center;
-}
-
-.node-actions {
-  display: none;
-}
-
-.custom-tree-node:hover .node-actions {
-  display: flex;
-}
-
-.ml-2 {
-  margin-left: 8px;
-}
-</style> 

+ 0 - 689
src/views/questionBank/positionList/crud.tsx

@@ -1,689 +0,0 @@
-import { CreateCrudOptionsProps, CreateCrudOptionsRet, AddReq, DelReq, EditReq, dict, compute } from '@fast-crud/fast-crud';
-import * as api from './api';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage } from '../../../utils/message';
-import { auth } from '/@/utils/authFunction';
-import { useRouter } from 'vue-router';
-import { ElMessage, ElDialog, ElButton, ElUpload } from 'element-plus';
-import { ref } from 'vue';
-
-export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter(); // 添加这行来获取router实例
-	const pageRequest = async (query: any) => {
-		// 确保查询参数正确传递
-		/* api.GetDocumentTree({}).then((ret: any) => { */
-			// 构建请求参数,确保category参数被正确传递
-			console.log(query);
-		const params = {
-			...query,
-			limit:100,
-			tenant_id: '1',
-			// 如果 query.form 中有 category,使用它,否则使用 query.category
-			category: (query.form && query.form.category) || query.category || '',
-			job_id: query.job_id || 1
-		};
-
-		console.log('Request params:', params); // 添加日志以便调试
-
-		return  api.GetDocumentList(params);
-		/* }); */
-		
-	};
-
-	const editRequest = async ({ form, row }: any) => {
-		return await api.UpdateDocument({
-			...form,
-			id: row.id
-		});
-	};
-
-	// 修改绑定parent_question_id的请求函数,使用现有的API
-	const updateParentQuestionRequest = async (id: number, parentQuestionId: number) => {
-		// 使用现有的UpdateDocument API而不是不存在的UpdateInterviewQuestion
-		return await api.UpdateDocument({
-			id: id,
-			parent_question_id: parentQuestionId
-		});
-	};
-
-	const delRequest = async ({ row }: any) => {
-		return await api.DelDocument({id:row.id});
-	};
-
-	const addRequest = async ({ form }: any) => {
-		return await api.AddDocument(form);
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			pagination:{
-				show:false,
-   				pageSize: 100, //默认pageSize
-				pageSizes: [50, 100], // 可选的分页大小选项
-				showSizePicker: true // 显示分页大小选择器
-			},
-			// 添加工具栏配置
-			actionbar: {
-				buttons: {
-					add: {
-						show: true,
-						text: '新增职位问题',
-						icon: 'Plus',
-						size: 'small',
-						click: () => {
-							// 触发自定义事件,让父组件处理弹窗显示
-							const event = new CustomEvent('openPositionQuestionDialog');
-							window.dispatchEvent(event);
-						}
-					}
-				}
-			},
-			search: {
-				show: true,
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				},
-				onSearch: (params: any) => {
-				},
-				columns: {
-					category: {
-						title: "分类",
-						search: { show: true, value: '' },
-						type: 'text',
-						column: { show: false } // 表格中不显示此列
-					}
-				}
-			},
-			rowHandle: {
-				width: 350, // 增加宽度以容纳新按钮
-				buttons: {
-					view: {
-						size: 'small',
-						icon:"View",
-						type: 'text',
-						click: ({ row }: any) => {
-							const event = new CustomEvent('viewDocumentDetail', { detail: row });
-							window.dispatchEvent(event);
-						}
-					},
-					uploadVideo: {
-						text: '上传视频',
-						type: 'text',
-						icon:"upload",
-						size: 'small',
-						click: ({ row }: any) => {
-							// 触发自定义事件,让父组件处理弹窗显示
-							const event = new CustomEvent('openVideoUploadDialog', { detail: row });
-							window.dispatchEvent(event);
-						}
-					},
-					// 添加绑定中文释义按钮
-					bindParentQuestion: {
-						text: '绑定开放问题',
-						type: 'text',
-						icon: "Link",
-						size: 'small',
-						show:false,
-						click: async ({ row }: any) => {
-							// 打开选择开放问题的对话框
-							const event = new CustomEvent('openBindParentQuestionDialog', { detail: row });
-							window.dispatchEvent(event);
-						}
-					},
-					edit: {
-						show:false,
-						type: 'text',
-						icon:"Edit",
-						size: 'small',
-					},
-					remove: {
-						type: 'text',
-						icon:"Delete",
-						size: 'small',
-					},
-					sort: {
-						type: 'text',
-						icon:"sort",
-						size: 'small',
-						text: '修改排序',
-						click: ({ row }: any) => {
-							// 触发自定义事件,让父组件处理弹窗显示
-							const event = new CustomEvent('openSortDialog', { detail: row });
-							window.dispatchEvent(event);
-						}
-					}
-				}
-			},
-			form: {
-				view: {
-					disabled: true,
-					display: 'text'
-				}
-			},
-			columns: {
-				/* _index: {
-					title: '序号',
-					form: { show: false },
-					column: {
-						type: 'index',
-						align: 'center',
-						width: '70px',
-						columnSetDisabled: true, //禁止在列设置中选择
-					},
-				}, */
-				id: {
-					title: 'ID',
-					column: { show: true ,width:80, },
-					search: { show: false },
-					form: { show: false },
-				},
-				position_id: {
-					title: '职位',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					type: 'dict-select',
-					dict:dict({
-                        url({form}){
-                            return  '/api/system/job/list?tenant_id=1'
-                        },
-                        label:'title',
-                        value:'id'
-                        }),
-					column: {
-						value: '1',
-						minWidth: 120,
-						show: false,
-					},
-					form: { show: true },
-				},
-				question_id: {
-					title: '职位问题',
-					search: { show: false },
-					type: 'dict-select',
-					dict:dict({
-						url: '/api/categories', // 获取数据的接口
-						// 或者使用方法
-						getData: async () => {
-						  const res = await api.GetInterviewQuestions({page:1,limit:200,tenant_id:1,});
-						  return res.data.items;
-						},
-						label: 'question',
-						value: 'id'
-					}),
-					column: {
-						value: '1',
-						show: false
-					},
-					form: { 
-						show: true,
-						component: {
-							props: {
-								filterable: true,
-								clearable: true,
-								placeholder: '请选择职位问题',
-							}
-						}
-					},
-				},
-			
-				question: {
-					title: '题目内容',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					column: {
-						
-						sortable: 'custom',
-						showOverflowTooltip: true, // 超出显示提示
-						width: 300 // 固定列宽
-					},
-					form: {
-						rules: [{ required: true, message: '题目内容必填' }],
-						component: {
-							placeholder: '请输入题目内容',
-						},
-						show: false
-					},
-				},
-				
-				// 添加 parent_question_id 字段
-				parent_question_id: {
-					title: '关联开放问题',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'dict-select',
-					dict: dict({
-						//url: '/api/open-questions', // 获取开放问题的接口
-						// 或者使用方法
-						getData: async () => {
-							const res = await api.GetInterviewQuestions({
-								page: 1,
-								limit: 200,
-								tenant_id: 1,
-								question_form: 0, // 开放问题类型
-							});
-							return res.data.items;
-						},
-						label: 'question',
-						value: 'id'
-					}),
-					column: {
-						minWidth: 150,
-						showOverflowTooltip: true,
-						formatter: ({ row }: any) => {
-							// 如果有关联问题,显示关联问题的内容
-							if (row.parent_question && row.parent_question.question) {
-								return row.parent_question.question;
-							} else if (row.parent_question_id) {
-								return `${row.parent_question_id}`;
-							}
-							return '无关联问题';
-						}
-					},
-					form: {
-						show: true,
-						component: {
-							props: {
-								filterable: true,
-								clearable: true,
-								placeholder: '请选择关联的开放问题',
-							}
-						},
-						helper: '关联到对应的开放问题,用于中文释义'
-					},
-				},
-				/* question_type: {
-					title: '问题类型',
-					search: { show: true },
-					type: 'dict-select',
-					column: {
-						minWidth: 100,
-					},
-					dict: dict({
-						data: [
-							{ value: 1, label: '专业技能' },
-							{ value: 2, label: '通用能力' },
-							{ value: 3, label: '个人特质' }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '问题类型必填' }],
-						component: {
-							placeholder: '请选择问题类型',
-						},
-						helper: '选择问题的类型分类',
-						show: false
-					},
-				}, */
-				question_form: {
-					title: '题目形式',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					type: 'dict-select',
-					column: {
-						minWidth: 100,
-					},
-					dict: dict({
-						// 使用API方式获取数据
-						getData: async () => {
-							const res = await api.GetQuestionTypeList();
-							// 返回question_forms数组
-							return res.data.question_forms || [];
-						},
-						label: 'label',
-						value: 'value'
-					}),
-					form: {
-						rules: [{ required: true, message: '题目形式必填' }],
-						component: {
-							placeholder: '请选择题目形式',
-							onChange: ({ form }: { form: any }) => {
-								// 重置相关字段
-								if (form.question_form === 1) {
-									// 单选题
-									form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
-								} else if (form.question_form === 2) {
-									// 多选题
-									form.options = [{ option_text: '', is_correct: false, sort: 1 }, { option_text: '', is_correct: false, sort: 2 }];
-								} else {
-									// 开放问题
-									form.options = [];
-								}
-							}
-						},
-						helper: '选择题目的形式:开放问题、单选题或多选题',
-						show: false
-					},
-				},
-				/* position_types: {
-					title: '适用职位',
-					search: { show: true },
-					type: 'dict-select',
-					column: {
-						minWidth: 120,
-					},
-					dict: dict({
-						data: [
-							{ value: '0', label: '技术' },
-							{ value: '1', label: '管理' },
-						]
-					}),
-					form: {
-						component: {
-							props: {
-								multiple: true,
-								filterable: true,
-								placeholder: '请选择适用职位类型',
-							}
-						},
-						helper: '选择题目适用的职位类型,可多选',
-						show: false
-					}
-				},
-				recommended_duration: {
-					title: '建议时长(秒)',
-					search: { show: false },
-					column: {
-						minWidth: 100,
-					},
-					form: {
-						component: {
-							name: 'el-input-number',
-							props: {
-								min: 10,
-								max: 600,
-								step: 10
-							}
-						},
-						helper: '建议回答此题目的时长,单位为秒',
-						show: false
-					}
-				}, */
-				difficulty: {
-					title: '难度等级',
-					search: { show: true,
-						size: 'small',
-						col:{ span:3},
-					 },
-					type: 'dict-select',
-					column: {
-						minWidth: 80,
-					},
-					dict: dict({
-						data: [
-							{ value: 1, label: '初级' },
-							{ value: 2, label: '中级' },
-							{ value: 3, label: '高级' }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '难度等级必填' }],
-						helper: '选择题目的难度级别',
-						show: false
-					},
-				},
-				/* is_system: {
-					title: '系统题目',
-					search: { show: true },
-					dict: dict({
-						data: [
-							{ value: 0, label: '停用' },
-							{ value: 1, label: '启用' }
-						]
-					}),
-					column: {
-						width: 120,
-					},
-					form: {
-						component: {
-							name: 'el-switch',
-							props: {
-								activeText: '是',
-								inactiveText: '否'
-							}
-						},
-						helper: '是否为系统预设题目',
-						show: false
-					}
-				}, */
-				/* status: {
-					title: '状态',
-					search: { show: true },
-					type: 'dict-select',
-					column: {
-						width: 80,
-					},
-					dict: dict({
-						data: [
-							{ value: 0, label: '停用' },
-							{ value: 1, label: '启用' }
-						]
-					}),
-					form: {
-						rules: [{ required: true, message: '状态必填' }],
-						component: {
-							placeholder: '请选择状态',
-						},
-						helper: '题目的启用状态',
-						show: false
-					},
-				},
-				sort: {
-					title: '排序',
-					search: { show: false },
-					column: {
-						width: 80,
-					},
-					form: {
-						component: {
-							name: 'el-input-number',
-							props: {
-								min: 1,
-								max: 999
-							}
-						},
-						helper: '题目的排序值,值越小排序越靠前',
-						show: false
-					}
-				}, */
-				option_items: {
-					title: '选项列表',
-					search: { show: false },
-					column: { show: false },
-					form: {
-						/* show: compute(({ form }) => {
-							return form && (form.question_form === 1 || form.question_form === 2);
-						}), */
-						component: {
-							name: 'el-card',
-							children: {
-								default: ({ form }: { form: any }) => {
-									// 确保options数组已初始化
-									if (!form.options) {
-										form.options = [];
-									}
-
-									return (
-										<div>
-											<div class="option-header" style="display: flex; margin-bottom: 10px; font-weight: bold;">
-												<span style="flex: 1;">选项内容</span>
-												<span style="width: 80px; text-align: center;">是否正确</span>
-											</div>
-											{form.options.map((option: any, index: number) => (
-												<div class="option-item" key={index} style="display: flex; align-items: center; margin-bottom: 10px;">
-													<el-input
-														v-model={option.option_text}
-														placeholder="请输入选项内容"
-														style="flex: 1; margin-right: 10px;"
-													/>
-													<el-tooltip
-														content="设置为正确答案"
-														placement="top"
-														effect="light"
-													>
-														<div
-															onClick={() => {
-																if (form.question_form === 1) {
-																	// 单选题:只能有一个正确答案
-																	form.options.forEach((item: any, idx: number) => {
-																		item.is_correct = (idx === index);
-																	});
-																} else {
-																	// 多选题:可以有多个正确答案
-																	option.is_correct = !option.is_correct;
-																}
-															}}
-															style="cursor: pointer; width: 80px; text-align: center;"
-														>
-															{form.question_form === 1 ? (
-																// 单选题使用单选按钮
-																<el-radio
-																	modelValue={option.is_correct}
-																	label={true}
-																/>
-															) : (
-																// 多选题使用复选框
-																<el-checkbox
-																	modelValue={option.is_correct}
-																/>
-															)}
-														</div>
-													</el-tooltip>
-												</div>
-											))}
-											<div style="display: flex; justify-content: space-between; margin-top: 10px;">
-												<el-button
-													type="primary"
-													onClick={() => {
-														// 确保options数组已初始化
-														if (!form.options) {
-															form.options = [];
-														}
-														// 添加新选项时自动设置排序值
-														const sort = form.options.length > 0 ?
-															Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
-														form.options.push({ option_text: '', is_correct: false, sort });
-													}}
-												>
-													添加选项
-												</el-button>
-
-												{form.options.length > 2 && (
-													<el-button
-														type="danger"
-														onClick={() => {
-															form.options.pop();
-														}}
-													>
-														删除最后一项
-													</el-button>
-												)}
-											</div>
-										</div>
-									)
-								}
-							}
-						},
-						show: false,
-						helper: compute(({ form }) => {
-							if (form.question_form === 1) {
-								return '添加单选题的选项,并标记正确答案(只能有一个正确答案)';
-							} else if (form.question_form === 2) {
-								return '添加多选题的选项,并标记正确答案(可以有多个正确答案)';
-							}
-							return '';
-						})
-					}
-				},
-				sequence_number: {
-					title: '排序',
-					search: { show: false },
-					column: { show: true },
-					form: { show: true },
-				},
-				answer_explanation: {
-					title: '答案解析',
-					search: { show: false },
-					column: { show: false },
-					form: {
-						component: {
-							name: 'el-input',
-							type: 'textarea',
-							rows: 4,
-							placeholder: '请输入答案解析'
-						},
-						show:false
-					},
-				},
-				// 添加视频相关字段
-				digital_human_video_url: {
-					title: '视频链接',
-					search: { show: false },
-					column: { 
-						show: true,
-						width: 120,
-						formatter: ({ row }: any) => {
-							return row.digital_human_video_url ? '已上传' : '未上传';
-						}
-					},
-					form: { show: false },
-				},
-				digital_human_video_subtitle: {
-					title: '字幕',
-					search: { show: false },
-					column: { 
-						show: true,
-						width: 120,
-						formatter: ({ row }: any) => {
-							return row.digital_human_video_subtitle ? '已上传' : '未上传';
-						}
-					},
-					form: { show: false },
-				},
-			},
-		},
-		// 不需要导出这些状态
-		// videoUploadDialogVisible,
-		// currentRow
-	};
-}; 

+ 0 - 1686
src/views/questionBank/positionList/index.vue

@@ -1,1686 +0,0 @@
-<template>
-  <fs-page>
-    <el-row class="document-el-row">
-      <el-col :span="3">
-        <div class="document-box document-left-box">
-          <DocumentTreeCom
-            ref="documentTreeRef"
-            :treeData="documentTreeData"
-            @treeClick="handleTreeClick"
-            @updateDocument="handleUpdateDocument"
-            @deleteDocument="handleDeleteDocument"
-          />
-        </div>
-      </el-col>
-
-      <el-col :span="21">
-        <div class="document-box document-right-box">
-          <fs-crud ref="crudRef" v-bind="crudBinding">
-           <!-- 行操作按钮插槽 -->
-           <template #cell-rowHandle-middle="scope">
-                <!-- 绑定开放问题按钮 -->
-                <el-button 
-                    v-if="scope.row.question_form === 5" 
-                    type="text" 
-                    @click="bindParentQuestion(scope.row)">
-                    <el-icon><Link /></el-icon>绑定开放问题
-                </el-button>
-                <!-- 编辑按钮 -->
-            </template>
-            <template #form_file="scope">
-              <!-- 只在编辑和添加模式下显示上传功能 -->
-              <template v-if="scope.mode !== 'view'">
-                <el-upload :action="getBaseURL() + 'api/system/minioupload/'" 
-                  
-                  :headers="{
-                    Authorization: 'JWT ' + Session.get('token')
-                  }"
-                  :multiple="false" 
-                  :on-success="(response: any, file: any) => handleUploadSuccess(response, file, scope)" 
-                  :drag="false" 
-                  :show-file-list="false">
-                  <el-button type="primary" icon="plus">上传</el-button>
-                </el-upload>
-                <div v-if="uploadedFile" class="uploaded-file-info mt-2">
-                  <el-card shadow="hover" class="w-full">
-                    <div class="flex items-center justify-between">
-                      <div class="flex items-center">
-                        <el-icon class="mr-2"><Document /></el-icon>
-                        <span>{{ uploadedFile.name }}</span>
-                      </div>
-                      <div class="flex items-center">
-                        <span class="text-gray-500 text-sm mr-4">{{ formatFileSize(uploadedFile.file_size) }}</span>
-                        <el-button type="danger" link @click="handleRemoveFile(scope)">
-                          <el-icon><Delete /></el-icon>
-                        </el-button>
-                      </div>
-                    </div>
-                  </el-card>
-                </div>
-              </template>
-              <!-- 查看模式下只显示文件名 -->
-              <template v-else>
-                <div class="flex items-center justify-between">
-                  <span>{{ scope.form.fileName }}</span>
-                  <el-button v-if="isPreviewable(scope.form.file_type)" type="primary" link @click="previewFile(scope.form)">
-                    <el-icon class="mr-1"><View /></el-icon>预览
-                  </el-button>
-                </div>
-              </template>
-            </template>
-          </fs-crud>
-        </div>
-      </el-col>
-    </el-row>
-
-    <el-drawer v-model="drawerVisible" title="文档配置" direction="rtl" size="500px" :close-on-click-modal="false" :before-close="handleDrawerClose">
-      <DocumentFormCom
-        v-if="drawerVisible"
-        :initFormData="drawerFormData"
-        :cacheData="documentTreeCacheData"
-        :treeData="documentTreeData"
-        @drawerClose="handleDrawerClose"
-      />
-    </el-drawer>
-
-    <!-- 添加自定义查看详情对话框 -->
-    <!-- <el-dialog
-      v-model="detailDialogVisible"
-      title="文档详情"
-      width="50%"
-      destroy-on-close
-    >
-      <div ref="printArea">
-        <el-descriptions :column="2" size="small" border>
-          <el-descriptions-item label-align="right" label="文档名称">{{ detailData.name || '-' }}</el-descriptions-item>
-          <el-descriptions-item label-align="right" label="文档类型">
-            {{ getDocTypeName(detailData.doc_type) || '-' }}
-          </el-descriptions-item>
-          <el-descriptions-item label-align="right" label="文件分类">
-            {{ getCategoryName(detailData.category) || '-' }}
-          </el-descriptions-item>
-          <el-descriptions-item label-align="right" label="所属项目">
-            {{ getProjectName(detailData.project) || '-' }}
-          </el-descriptions-item>
-          <el-descriptions-item label-align="right" label="文档版本">{{ detailData.version || '-' }}</el-descriptions-item>
-          <el-descriptions-item label-align="right" label="文件类型">{{ detailData.file_type || '-' }}</el-descriptions-item>
-          <el-descriptions-item label-align="right" label="文件大小">{{ formatFileSize(detailData.file_size) || '-' }}</el-descriptions-item>
-          <el-descriptions-item label-align="right" label="状态">
-            <el-tag :type="detailData.status ? 'success' : 'info'">
-              {{ detailData.status ? '启用' : '禁用' }}
-            </el-tag>
-          </el-descriptions-item>
-          <el-descriptions-item label-align="right" label="文档描述" :span="2">{{ detailData.doc_desc || '-' }}</el-descriptions-item>
-          <el-descriptions-item label-align="right" label="更新时间">{{ detailData.update_datetime || '-' }}</el-descriptions-item>
-        </el-descriptions>
-      </div>
-      
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button type="primary" @click="handlePrint">
-            <el-icon><Printer /></el-icon>打印
-          </el-button>
-          <el-button @click="detailDialogVisible = false">关闭</el-button>
-        </span>
-      </template>
-    </el-dialog> -->
-
-    <!-- 修改视频上传对话框为表单格式 -->
-    <el-dialog
-      v-model="videoUploadDialogVisible"
-      title="上传数字人视频及字幕"
-      width="600px"
-      destroy-on-close
-    >
-      <el-form 
-        ref="videoFormRef" 
-        :model="videoForm" 
-        :rules="videoFormRules" 
-        label-width="100px"
-        label-position="top"
-      >
-        <el-form-item label="数字人视频" prop="videoUrl">
-         <!--  <div class="upload-section"> -->
-            <el-upload
-              class="video-uploader"
-              :action="getBaseURL() + 'api/system/admin_upload/'"
-              :headers="{
-                Authorization: 'JWT ' + Session.get('token')
-              }"
-              :data="{
-                tenant_id:1
-              }"
-              :multiple="false"
-              :on-success="handleVideoUploadSuccess"
-              :on-error="handleUploadError"
-              :before-upload="beforeVideoUpload"
-              :show-file-list="false"
-              
-            ><!-- accept="video/*" -->
-              <div v-if="videoInfo.url" class="video-preview">
-                <video :src="videoInfo.url" controls class="video-player"></video>
-              </div>
-              <el-button v-else type="primary" icon="Plus">选择视频文件</el-button>
-              <div v-if="videoInfo.name" class="file-info">
-                <span>{{ videoInfo.name }}</span>
-                <span class="file-size">{{ formatFileSize(videoInfo.size) }}</span>
-              </div>
-            </el-upload>
-        <!--   </div> -->
-        </el-form-item>
-
-        <el-form-item label="字幕内容" prop="subtitleContent">
-          <!-- <div class="upload-section"> -->
-            <el-input
-              v-model="videoForm.subtitleContent"
-              type="textarea"
-              :rows="4"
-              placeholder="请输入数字人视频字幕内容"
-            ></el-input>
-           
-           <!--  <el-upload
-              class="subtitle-uploader mt-2"
-              :action="getBaseURL() + 'api/system/minioupload/'"
-              :headers="{
-                Authorization: 'JWT ' + Session.get('token')
-              }"
-              :multiple="false"
-              :on-success="handleSubtitleUploadSuccess"
-              :on-error="handleUploadError"
-              :before-upload="beforeSubtitleUpload"
-              :show-file-list="false"
-              accept=".srt,.vtt,.ass"
-            >
-              <div v-if="subtitleInfo.url" class="file-preview">
-                <el-icon class="file-icon"><Document /></el-icon>
-                <div class="file-info">
-                  <span>{{ subtitleInfo.name }}</span>
-                  <span class="file-size">{{ formatFileSize(subtitleInfo.size) }}</span>
-                </div>
-              </div>
-              <el-button v-else type="primary" icon="Plus">选择字幕文件</el-button>
-            </el-upload> -->
-        <!--   </div> -->
-        </el-form-item>
-      </el-form>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="videoUploadDialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="submitVideoForm" :disabled="!videoInfo.url">保存</el-button>
-        </span>
-      </template>
-    </el-dialog>
-    
-    <!-- 添加排序对话框 -->
-    <el-dialog
-      v-model="sortDialogVisible"
-      title="修改排序"
-      width="400px"
-      destroy-on-close
-    >
-      <el-form 
-        ref="sortFormRef" 
-        :model="sortForm" 
-        :rules="sortFormRules" 
-        label-width="100px"
-      >
-        <el-form-item label="排序值" prop="sequence_number">
-          <el-input-number 
-            v-model="sortForm.sequence_number" 
-            :min="0" 
-            :max="9999"
-            style="width: 100%"
-          />
-        </el-form-item>
-        <div class="form-helper">数值越小,排序越靠前</div>
-      </el-form>
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="sortDialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="submitSortForm">保存</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 添加职位问题对话框 -->
-    <el-dialog
-      v-model="positionQuestionDialogVisible"
-      title="职位问题"
-      width="60%"
-      destroy-on-close
-    >
-      <el-form :model="positionQuestionForm" label-width="80px">
-        <el-form-item label="职位">
-          <el-select v-model="positionQuestionForm.position" placeholder="请选择" style="width: 25%">
-            <el-option
-              v-for="item in positionOptions"
-              :key="item.value"
-              :label="item.label"
-              :value="item.value"
-            />
-          </el-select>
-        </el-form-item>
-        
-        <el-form-item label="职位问题">
-          <div class="question-selection-container">
-            <div class="question-search">
-              <div class="search-row">
-                <el-input
-                  style="width: 200px;"
-                  v-model="questionSearchKeyword"
-                  placeholder="搜索问题"
-                  clearable
-                  @input="filterQuestions"
-                >
-                  <template #prefix>
-                    <el-icon><Search /></el-icon>
-                  </template>
-                </el-input>
-                
-                <el-select 
-                  v-model="selectedQuestionCategory" 
-                  placeholder="问题分类" 
-                  clearable 
-                  @change="filterQuestions"
-                  style="margin-left: 10px; width: 200px;"
-                >
-                  <el-option
-                    v-for="item in questionCategoryOptions"
-                    :key="item.value"
-                    :label="item.label"
-                    :value="item.value"
-                  />
-                </el-select>
-              </div>
-            </div>
-            
-            <div class="question-table-container">
-              <el-table
-                v-loading="questionsLoading"
-                :data="filteredQuestionOptions"
-                style="width: 100%"
-                height="300"
-                @selection-change="handleQuestionSelectionChange"
-              >
-                <el-table-column type="selection" width="55" />
-                <el-table-column prop="label" label="问题" show-overflow-tooltip />
-                <el-table-column prop="category_id.name" label="分类" />
-                <el-table-column prop="question_form_name" label="题目形式" show-overflow-tooltip />
-                <el-table-column prop="chinese_explanation" label="描述" show-overflow-tooltip />
-              </el-table>
-              
-              <div class="pagination-container">
-                <el-pagination
-                  v-model:current-page="questionPagination.currentPage"
-                  v-model:page-size="questionPagination.pageSize"
-                  :page-sizes="[10, 20, 50, 100]"
-                  layout="total, sizes, prev, pager, next, jumper"
-                  :total="questionPagination.total"
-                  @size-change="handleQuestionSizeChange"
-                  @current-change="handleQuestionCurrentChange"
-                />
-              </div>
-            </div>
-          </div>
-        </el-form-item>
-        
-        <el-form-item label="排序">
-          <el-input style="width: 25%" v-model="positionQuestionForm.sequence" placeholder="请输入排序值" />
-        </el-form-item>
-      </el-form>
-      
-   <!--    <div class="question-list-container">
-        <h3>已选问题列表</h3>
-        <el-table :data="selectedQuestions" style="width: 100%">
-          <el-table-column prop="label" label="问题" show-overflow-tooltip />
-          <el-table-column prop="question_form_name" label="问题类型" show-overflow-tooltip />
-          <el-table-column prop="chinese_explanation" label="中文释义" show-overflow-tooltip />
-          <el-table-column label="操作" width="120">
-            <template #default="scope">
-              <el-button type="danger" link @click="removeQuestion(scope.row)">
-                <el-icon><Delete /></el-icon>
-              </el-button>
-            </template>
-          </el-table-column>
-        </el-table>
-      </div> -->
-
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="positionQuestionDialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="submitPositionQuestionForm">保存</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 修改绑定开放问题的对话框 -->
-    <el-dialog
-      v-model="bindParentQuestionDialogVisible"
-      title="绑定开放问题"
-      width="50%"
-      :before-close="handleBindDialogClose"
-    >
-      <div>
-        <!-- <div class="search-container" style="margin-bottom: 15px;">
-          <el-input
-            v-model="openQuestionKeyword"
-            placeholder="搜索开放问题"
-            clearable
-            @input="searchOpenQuestions"
-            style="width: 100%"
-          >
-            <template #prefix>
-              <el-icon><Search /></el-icon>
-            </template>
-          </el-input>
-        </div> -->
-        
-        <p>请选择要关联的开放问题:</p>
-        <el-select
-          v-model="selectedParentQuestionId"
-          filterable
-          clearable
-          placeholder="请选择开放问题"
-          style="width: 100%"
-          :loading="openQuestionsLoading"
-        >
-          <el-option
-            v-for="item in openQuestionsList"
-            :key="item.id"
-            :label="item.question"
-            :value="item.id"
-          />
-        </el-select>
-      </div>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="handleBindDialogClose">取消</el-button>
-          <el-button type="primary" @click="confirmBindParentQuestion">确认</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </fs-page>
-</template>
-
-<script lang="ts" setup name="documentList">
-import { ref, onMounted, onBeforeUnmount, computed } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import XEUtils from 'xe-utils';
-import { ElMessageBox, ElMessage } from 'element-plus';
-import { createCrudOptions } from './crud';
-import { Session } from '/@/utils/storage';
-import { getBaseURL } from '/@/utils/baseUrl';
-import { Document, Delete, View, Printer, Search, Link } from '@element-plus/icons-vue';
-import { successNotification } from '/@/utils/message';
-import DocumentTreeCom from './components/DocumentTreeCom/index.vue';
-import DocumentFormCom from './components/DocumentFormCom/index.vue';
-import { GetDocumentTree, DeleteDocumentCategory, UpdateDocument ,UpdateSequence, GetInterviewQuestions,AddDocument } from './api';
-import { useRouter } from 'vue-router';
-/* import { print } from '/@/utils/print'; */
-
-const { crudBinding, crudRef, crudExpose } = useFs({ createCrudOptions });
-
-const router = useRouter();
-
-// 添加 uploadedFile 类型定义
-interface UploadedFileInfo {
-  name: string;
-  file_path: string;
-  file_type: string;
-  file_size: number;
-  doc_type: string;
-  category: string;
-  status: boolean;
-  doc_desc: string;
-  version: string;
-  file: string;
-}
-
-// 修改 uploadedFile 的类型
-const uploadedFile = ref<UploadedFileInfo | null>(null);
-
-// 树形结构相关数据
-const documentTreeData = ref([]);
-const documentTreeCacheData = ref<any[]>([]);
-const drawerVisible = ref(false);
-const drawerFormData = ref({});
-const documentTreeRef = ref<DocumentTreeRef | null>(null);
-
-// 添加一个响应式变量来存储当前选中的分类ID
-const selectedCategoryId = ref('');
-
-// 添加详情对话框相关数据
-const detailDialogVisible = ref(false);
-const detailData = ref({
-  id: 0,
-  name: '',
-  doc_type: '',
-  category: '',
-  project: '',
-  version: '',
-  file_type: '',
-  file_size: 0,
-  status: true,
-  doc_desc: '',
-  update_datetime: '',
-  file_path: '',
-  file: ''
-});
-
-// 添加打印区域的ref
-const printArea = ref(null);
-
-// 视频和字幕上传相关状态
-const videoInfo = ref({
-  url: '',
-  name: '',
-  size: 0,
-  file_type: ''
-});
-
-const subtitleInfo = ref({
-  url: '',
-  name: '',
-  size: 0,
-  file_type: ''
-});
-
-// 视频上传对话框状态
-const videoUploadDialogVisible = ref(false);
-const currentRow = ref<QuestionRow | null>(null);
-
-// 添加字幕内容输入框
-const subtitleContent = ref('');
-
-// 添加表单引用
-const videoFormRef = ref<{
-  validate: (callback: (valid: boolean) => void) => void;
-} | null>(null);
-
-// 添加表单数据对象
-const videoForm = ref({
-  videoUrl: '',
-  subtitleContent: ''
-});
-
-// 添加表单验证规则
-const videoFormRules = {
-  videoUrl: [
-    { required: true, message: '请上传数字人视频', trigger: 'change' }
-  ]
-};
-
-// 添加排序对话框相关状态
-const sortDialogVisible = ref(false);
-const sortForm = ref({
-  sequence_number: 0
-});
-const sortFormRules = {
-  sequence_number: [
-    { required: true, message: '请输入排序值', trigger: 'blur' },
-    { type: 'number', message: '排序值必须为数字', trigger: 'blur' }
-  ]
-};
-const sortFormRef = ref<any>(null);
-
-// 添加类型定义
-interface DocumentTreeRef {
-  treeRef?: {
-    currentNode?: {
-      parent?: {
-        data: any;
-      };
-    };
-  };
-}
-
-// 修改 currentRow 的类型
-interface QuestionRow {
-  id?: number;
-  question_id?: string | number;
-  sequence_number?: number;
-}
-
-// 职位问题对话框相关状态
-const positionQuestionDialogVisible = ref(false);
-const positionQuestionForm = ref({
-  position: '',
-  questions: [] as string[],
-  sequence: '0'
-});
-
-// 职位选项和问题选项
-const positionOptions = ref<{ value: string, label: string }[]>([]);
-const questionOptions = ref<{ value: string, label: string, chinese_explanation: string, question_form_name: string ,category_id: string}[]>([]);
-
-// 计算属性:已选问题列表
-const selectedQuestions = computed(() => {
-  // 根据已选ID查找完整的问题对象
-  return positionQuestionForm.value.questions.map(questionId => {
-    // 先在当前页面的问题中查找
-    const question = questionOptions.value.find(q => q.value === questionId);
-    if (question) return question;
-    
-    // 如果在当前页面找不到,可能需要额外请求
-    // 这里简化处理,只返回ID
-    return { value: questionId, label: `问题 ${questionId}`, chinese_explanation: '' ,question_form_name: '' ,category_id: ''};
-  });
-});
-
-// 问题加载状态
-const questionsLoading = ref(false);
-
-// 问题分页参数
-const questionPagination = ref({
-  currentPage: 1,
-  pageSize: 10,
-  total: 0
-});
-
-// 添加问题分类选项
-const questionCategoryOptions = ref<{ value: string, label: string }[]>([]);
-const selectedQuestionCategory = ref('');
-
-// 修改 loadQuestionOptions 函数,添加加载分类选项的逻辑
-const loadQuestionOptions = async () => {
-  try {
-    questionsLoading.value = true;
-    
-    // 构建请求参数,添加分类筛选
-    const requestParams: any = {
-      page: questionPagination.value.currentPage,
-      limit: questionPagination.value.pageSize,
-      tenant_id: 1,
-      keyword: questionSearchKeyword.value
-    };
-    
-    // 如果选择了分类,添加到请求参数
-    if (selectedQuestionCategory.value) {
-      requestParams.category_id = selectedQuestionCategory.value;
-    }
-    
-    const response = await GetInterviewQuestions(requestParams);
-    
-    if (response && response.code === 2000 && response.data) {
-      // 转换数据格式
-      questionOptions.value = response.data.items.map((item: any) => ({
-        value: item.id.toString(),
-        label: item.question,
-        chinese_explanation: item.scoring_reference || '暂无释义',
-        question_form_name: item.question_form_name || '暂无问题类型',
-        category_id: item.category || '暂无分类'
-      }));
-      
-      // 更新总数
-      questionPagination.value.total = response.data.total;
-      
-      // 如果是第一次加载,获取所有分类选项
-      if (questionCategoryOptions.value.length === 0) {
-        loadQuestionCategories();
-      }
-    } else {
-      ElMessage.error('获取问题列表失败');
-    }
-  } catch (error) {
-    console.error('加载问题选项失败:', error);
-    ElMessage.error('加载问题选项失败');
-  } finally {
-    questionsLoading.value = false;
-  }
-};
-
-// 添加加载问题分类的方法
-const loadQuestionCategories = async () => {
-  try {
-    // 调用API获取问题分类列表
-    const response = await fetch(getBaseURL() + 'api/system/question_category/list?tenant_id=1', {
-      method: 'GET',
-      headers: {
-        'Authorization': 'JWT ' + Session.get('token'),
-        'Content-Type': 'application/json'
-      }
-    });
-    
-    if (!response.ok) {
-      throw new Error('获取问题分类列表失败');
-    }
-    
-    const data = await response.json();
-    
-    if (data && data.code === 2000 && data.data.items) {
-      // 转换数据格式
-      questionCategoryOptions.value = data.data.items.map((item: any) => ({
-        value: item.id.toString(),
-        label: item.name
-      }));
-    }
-  } catch (error) {
-    console.error('加载问题分类选项失败:', error);
-  }
-};
-
-// 修改 filterQuestions 方法
-const filterQuestions = () => {
-  // 重置到第一页
-  questionPagination.value.currentPage = 1;
-  // 重新加载数据
-  loadQuestionOptions();
-};
-
-// 修改计算属性:过滤后的问题选项
-const filteredQuestionOptions = computed(() => {
-  // 由于我们已经在API请求中处理了关键词和分类筛选,
-  // 这里直接返回问题选项即可
-  return questionOptions.value;
-});
-
-// 处理问题选择变化
-const handleQuestionSelectionChange = (selection: any[]) => {
-  positionQuestionForm.value.questions = selection.map(item => item.value);
-};
-
-// 处理问题分页大小变化
-const handleQuestionSizeChange = (size: number) => {
-  questionPagination.value.pageSize = size;
-  loadQuestionOptions();
-};
-
-// 处理问题当前页变化
-const handleQuestionCurrentChange = (page: number) => {
-  questionPagination.value.currentPage = page;
-  loadQuestionOptions();
-};
-
-// 修改打开职位问题对话框方法
-const openPositionQuestionDialog = () => {
-  // 加载职位选项
-  loadPositionOptions();
-  // 加载问题选项
-  loadQuestionOptions();
-  positionQuestionDialogVisible.value = true;
-};
-
-// 修改 loadPositionOptions 函数,从实际API获取职位数据
-const loadPositionOptions = async () => {
-  try {
-    // 调用实际的API获取职位列表
-    const response = await fetch(getBaseURL() + 'api/system/job/list?tenant_id=1', {
-      method: 'GET',
-      headers: {
-        'Authorization': 'JWT ' + Session.get('token'),
-        'Content-Type': 'application/json'
-      }
-    });
-    
-    if (!response.ok) {
-      throw new Error('获取职位列表失败');
-    }
-    
-    const data = await response.json();
-    
-    if (data && data.code === 2000 && data.data) {
-      // 转换数据格式
-      positionOptions.value = data.data.map((item: any) => ({
-        value: item.id.toString(),
-        label: item.title,
-        // 如果有中文释义字段,可以在这里添加
-        description: item.description || ''
-      }));
-    } else {
-      ElMessage.error('获取职位列表失败');
-    }
-  } catch (error) {
-    console.error('加载职位选项失败:', error);
-    ElMessage.error('加载职位选项失败');
-  }
-};
-
-// 添加一个函数来获取职位名称
-const getPositionName = (positionId: string | number) => {
-  if (!positionId) return '';
-  const position = positionOptions.value.find(p => p.value === positionId.toString());
-  return position ? position.label : positionId.toString();
-};
-
-// 修改 submitPositionQuestionForm 函数,添加中文释义处理
-const submitPositionQuestionForm = async () => {
-  try {
-    if (!positionQuestionForm.value.position) {
-      ElMessage.warning('请选择职位');
-      return;
-    }
-    
-    if (positionQuestionForm.value.questions.length === 0) {
-      ElMessage.warning('请选择至少一个问题');
-      return;
-    }
-    
-    // 获取职位名称,用于显示成功消息
-    const positionName = getPositionName(positionQuestionForm.value.position);
-    
-    // 构建API请求数据 - 修改为符合API期望的格式
-    const requestData = {
-      position_id: parseInt(positionQuestionForm.value.position),
-      question_id: { 
-        question_id: positionQuestionForm.value.questions.map(questionId => parseInt(questionId)) 
-      },
-      duration: parseInt(positionQuestionForm.value.sequence) || 60,
-      tenant_id: 1
-    };
-    
-    console.log('提交的数据:', requestData);
-    
-    // 调用实际的API保存职位问题关联
-    const response = await AddDocument(requestData);
-    
-    if (response && response.code === 2000) {
-      ElMessage.success(`${positionName}职位问题保存成功`);
-      positionQuestionDialogVisible.value = false;
-      
-      // 重置表单
-      positionQuestionForm.value = {
-        position: '',
-        questions: [],
-        sequence: '0'
-      };
-      
-      // 刷新列表
-      crudExpose.doRefresh();
-    } else {
-      ElMessage.error(response?.msg || '保存失败');
-    }
-  } catch (error) {
-    console.error('保存职位问题失败:', error);
-    ElMessage.error('保存失败,请重试');
-  }
-};
-
-// 移除已选问题
-const removeQuestion = (question: { value: string, label: string }) => {
-  positionQuestionForm.value.questions = positionQuestionForm.value.questions.filter(
-    id => id !== question.value
-  );
-};
-
-// 获取文档树数据
-const getTreeData = () => {
-  GetDocumentTree({}).then((ret: any) => {
-    console.log('ret', ret);
-    const responseData = ret.data;
-   /*  const result = XEUtils.toArrayTree(responseData, {
-      parentKey: 'parent',
-      children: 'children',
-      strict: true,
-    }); */
-    documentTreeData.value = responseData;
-  });
-};
-
-// 处理树节点点击
-const handleTreeClick = (record: any) => {
-  console.log('record', record);
-  
-  // 保存当前选中的分类ID
-  selectedCategoryId.value = record?.id || '';
-  
-  // 重置上传文件状态
-  uploadedFile.value = null;
-  
-  // 构建搜索参数
-  const searchParams = {
-    form: {
-      limit:100,
-      job_id: selectedCategoryId.value
-    }
-  };
-  
-  // 执行搜索
-  crudExpose.doSearch(searchParams);
-};
-
-// 处理文档分类的新增或编辑
-const handleUpdateDocument = (type: any, record: any) => {
-  if (type === 'update' && record) {
-    const parentData = documentTreeRef.value?.treeRef?.currentNode?.parent?.data || {};
-    documentTreeCacheData.value = [parentData];
-    drawerFormData.value = record;
-  }
-  drawerVisible.value = true;
-};
-
-// 关闭抽屉
-const handleDrawerClose = (type: string) => {
-  if (type === 'submit') {
-    getTreeData();
-  }
-  drawerVisible.value = false;
-  drawerFormData.value = {};
-};
-
-// 删除文档分类
-const handleDeleteDocument = (id: any, callback: any) => {
-  ElMessageBox.confirm('您确认删除该文档分类吗?', '温馨提示', {
-    confirmButtonText: '确认',
-    cancelButtonText: '取消',
-    type: 'warning',
-  }).then(async () => {
-    const res = await DeleteDocumentCategory(id);
-    callback();
-    if (res?.code === 2000) {
-      successNotification(res.msg);
-      getTreeData();
-    }
-  });
-};
-
-// 处理上传成功
-const handleUploadSuccess = (response: any, uploadFile: any, scope: any) => {
-  if (!response || !response.data) {
-    console.error('Upload response is invalid:', response);
-    return;
-  }
-
-  const fileInfo = {
-    name: uploadFile.name,
-    file_path: response.data.bucket_url,
-    file_type: response.data.file_type,
-    file_size: uploadFile.size,
-    doc_type: scope.form.doc_type || '',
-    category: selectedCategoryId.value, // 使用当前选中的分类ID
-    status: true,
-    doc_desc: '',
-    version: '',
-    /* merchant: Session.get('merchant_info').merchant_id, */
-    file: response.data.bucket_url
-  };
-  
-  // 更新表单数据
-  if (scope.form) {
-    Object.assign(scope.form, fileInfo);
-  } else {
-    console.error('Form reference is not available');
-    return;
-  }
-  
-  // 更新已上传文件显示
-  uploadedFile.value = fileInfo;
-  
-  // 刷新列表时传递category参数
-  crudExpose.doRefresh();
-};
-
-// 处理文件删除
-const handleRemoveFile = (scope: any) => {
-  uploadedFile.value = null;
-  // 清空表单数据
-  if (crudRef.value?.form) {
-    const form = crudRef.value.form;
-    form.name = '';
-    form.file_path = '';
-    form.file_type = '';
-    form.file_size = '';
-    form.doc_type = '';
-    form.category = '';
-    form.doc_desc = '';
-    form.version = '';
-    form.file = '';
-    scope.value = '';
-  }
-};
-
-// 格式化文件大小
-const formatFileSize = (bytes: number) => {
-  if (bytes === 0) return '0 B';
-  const k = 1024;
-  const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
-  const i = Math.floor(Math.log(bytes) / Math.log(k));
-  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
-};
-
-// 判断文件是否可预览
-const isPreviewable = (fileType: string) => {
-  if (!fileType) return false;
-  
-  // 支持的文件类型
-  const supportedTypes = [
-    'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
-    'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'
-  ];
-  
-  return supportedTypes.includes(fileType.toLowerCase());
-};
-
-// 预览文件
-const previewFile = (fileData: any) => {
-  if (!fileData || !fileData.file) {
-    ElMessageBox.alert('文件链接不存在', '预览失败', { type: 'error' });
-    return;
-  }
-  
-  const fileType = fileData.file_type || '';
-  const filePath = fileData.file;
-  const previewUrl = `/#/preview?url=${encodeURIComponent(filePath)}&type=${fileType}`;
-  window.open(previewUrl, '_blank');
-};
-
-// 处理查看详情
-const handleViewDetail = async (event: any) => {
-  const row = event.detail || event;
-  try {
-    // 可以选择直接使用传入的行数据,或者重新请求完整数据
-    // 如果需要重新请求数据,可以添加一个 GetDocument API 方法
-    // const res = await GetDocument(row.id);
-    // detailData.value = res.data;
-    
-    // 这里直接使用行数据
-    detailData.value = row;
-    detailDialogVisible.value = true;
-  } catch (error) {
-    ElMessage.error('获取文档详情失败');
-  }
-};
-
-// 获取文档类型名称
-const getDocTypeName = (docTypeId: number | string) => {
-  if (!docTypeId) return '';
-  const docTypeDict = [
-    { value: 1, label: '合同文档' },
-    { value: 2, label: '技术文档' },
-    { value: 3, label: '操作手册' },
-    { value: 4, label: '维护记录' },
-    { value: 5, label: '其他文档' }
-  ];
-  const docType = docTypeDict.find(item => item.value === docTypeId);
-  return docType ? docType.label : docTypeId;
-};
-
-// 获取分类名称
-const getCategoryName = (categoryId: number | string) => {
-  if (!categoryId) return '';
-  const categoryDict = (window as any).__categoryDict || [];
-  const category = categoryDict.find((item: any) => item.id === categoryId);
-  return category ? category.name : categoryId;
-};
-
-// 获取项目名称
-const getProjectName = (projectId: number | string) => {
-  if (!projectId) return '';
-  const projectDict = (window as any).__projectDict || [];
-  const project = projectDict.find((item: any) => item.id === projectId);
-  return project ? project.name : projectId;
-};
-
-// 预览详情中的文件
-const previewDetailFile = (fileData: any) => {
-  if (!fileData || !fileData.file_path) {
-    ElMessage.error('文件路径无效');
-    return;
-  }
-
-  const filePath = fileData.file_path;
-  const fileType = fileData.file_type;
-  
-  // 支持的文件类型列表
-  const supportedTypes = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
-  
-  if (!supportedTypes.includes(fileType)) {
-    ElMessage.warning(`暂不支持预览该文件类型: ${fileType}`);
-    return;
-  }
-
-  // 使用window.open在新窗口中打开预览页面
-  const previewUrl = `/#/preview?url=${encodeURIComponent(filePath)}&type=${fileType}`;
-  window.open(previewUrl, '_blank');
-};
-
-// 处理打印功能
-const handlePrint = () => {
- /*  print(printArea.value, {
-    title: `${detailData.value.name}文档详情`,
-    style: `
-      .el-descriptions { margin: 10px 0; }
-      .el-descriptions-item__label { font-weight: bold; }
-      .el-tag { padding: 2px 6px; }
-      @media print {
-        .dialog-footer { display: none; }
-      }
-    `
-  }); */
-};
-
-// 视频上传前的验证
-const beforeVideoUpload = (file: any) => {
-  const isVideo = file.type.startsWith('video/');
-  const isLt500M = file.size / 1024 / 1024 < 500;
-
-  if (!isVideo) {
-    ElMessage.error('请上传视频文件!');
-    return false;
-  }
-  if (!isLt500M) {
-    ElMessage.error('视频大小不能超过 500MB!');
-    return false;
-  }
-  return true;
-};
-
-// 字幕上传前的验证
-const beforeSubtitleUpload = (file: any) => {
-  const validExtensions = ['.srt', '.vtt', '.ass'];
-  const extension = file.name.substring(file.name.lastIndexOf('.')).toLowerCase();
-  const isValidType = validExtensions.includes(extension);
-  const isLt2M = file.size / 1024 / 1024 < 2;
-
-  if (!isValidType) {
-    ElMessage.error('请上传有效的字幕文件 (.srt, .vtt, .ass)!');
-    return false;
-  }
-  if (!isLt2M) {
-    ElMessage.error('字幕文件大小不能超过 2MB!');
-    return false;
-  }
-  return true;
-};
-
-// 修改视频上传成功处理函数
-const handleVideoUploadSuccess = (response: any, file: any) => {
-  console.log('response', response);
-  console.log('file', file);
-  
-  // 检查响应格式,确保能正确获取URL
-  if (response && response.url) {
-    // 直接使用响应中的URL
-    videoInfo.value = {
-      url: response.url,
-      name: file.name || response.filename || '',
-      size: file.size || 0,
-      file_type: file.type || response.mime_type || 'video/mp4'
-    };
-    
-    // 更新表单数据
-    videoForm.value.videoUrl = response.url;
-    ElMessage.success('视频上传成功');
-  } else if (response && response.data && response.data.url) {
-    // 兼容另一种可能的响应格式
-    videoInfo.value = {
-      url: response.data.url,
-      name: file.name || response.data.filename || '',
-      size: file.size || 0,
-      file_type: file.type || response.data.mime_type || 'video/mp4'
-    };
-    
-    // 更新表单数据
-    videoForm.value.videoUrl = response.data.url;
-    ElMessage.success('视频上传成功');
-  } else {
-    // 尝试从您提供的响应格式中获取URL
-    try {
-      // 根据您提供的响应示例,URL可能在这个位置
-      const url = response.url || 
-                 (response.response && response.response.url) || 
-                 (response[0] && response[0].url) ||
-                 (response.data && response.data.bucket_url);
-      
-      if (url) {
-        videoInfo.value = {
-          url: url,
-          name: file.name || response.filename || response.original_filename || '',
-          size: file.size || response.size || 0,
-          file_type: file.type || response.mime_type || 'video/mp4'
-        };
-        
-        // 更新表单数据
-        videoForm.value.videoUrl = url;
-        ElMessage.success('视频上传成功');
-      } else {
-        throw new Error('无法从响应中获取URL');
-      }
-    } catch (error) {
-      console.error('处理上传响应时出错:', error);
-      ElMessage.error('视频上传成功,但无法解析响应数据');
-    }
-  }
-};
-
-// 修改字幕上传成功处理函数
-const handleSubtitleUploadSuccess = (response: any, file: any) => {
-  if (response && response.data) {
-    subtitleInfo.value = {
-      url: response.data.bucket_url,
-      name: file.name,
-      size: file.size,
-      file_type: file.name.substring(file.name.lastIndexOf('.') + 1)
-    };
-    
-    // 尝试读取字幕文件内容
-    fetch(response.data.bucket_url)
-      .then(res => res.text())
-      .then(text => {
-        videoForm.value.subtitleContent = text;
-        ElMessage.success('字幕文件已上传并内容已加载');
-      })
-      .catch(err => {
-        console.error('无法读取字幕文件内容:', err);
-        ElMessage.warning('字幕文件已上传,但无法读取内容');
-      });
-  } else {
-    ElMessage.error('字幕上传失败');
-  }
-};
-
-// 上传错误处理
-const handleUploadError = () => {
-  ElMessage.error('上传失败,请重试');
-};
-
-// 处理打开视频上传对话框
-const handleOpenVideoUploadDialog = (event: any) => {
-  const row = event.detail;
-  if (row) {
-    currentRow.value = row;
-    videoUploadDialogVisible.value = true;
-    
-    // 重置上传状态
-    videoInfo.value = { url: '', name: '', size: 0, file_type: '' };
-    subtitleInfo.value = { url: '', name: '', size: 0, file_type: '' };
-    
-    // 重置表单数据
-    videoForm.value = {
-      videoUrl: '',
-      subtitleContent: ''
-    };
-  }
-};
-
-// 提交表单
-const submitVideoForm = () => {
-  if (!videoFormRef.value) return;
-  
-  videoFormRef.value.validate(async (valid: boolean) => {
-    if (valid) {
-      try {
-        if (!currentRow.value || !currentRow.value.id) {
-          ElMessage.error('当前选中的题目信息无效');
-          return;
-        }
-
-        // 确保我们有视频URL
-        if (!videoForm.value.videoUrl && !videoInfo.value.url) {
-          ElMessage.warning('请先上传数字人视频');
-          return;
-        }
-
-        // 修改为符合API要求的参数格式
-        const updateData = {
-          question_id: currentRow.value.question_id, // 必填,问题ID
-          video_url: videoForm.value.videoUrl || videoInfo.value.url, // 数字人视频URL
-          digital_human_video_subtitle: videoForm.value.subtitleContent, // 数字人视频字幕内容
-          tenant_id: 1
-        };
-
-        console.log('提交的数据:', updateData);
-        
-        const response = await UpdateDocument(updateData);
-        
-        if (response && response.code === 2000) {
-          ElMessage.success('数字人视频和字幕信息保存成功');
-          videoUploadDialogVisible.value = false;
-          
-          // 重置上传状态
-          videoInfo.value = { url: '', name: '', size: 0, file_type: '' };
-          subtitleInfo.value = { url: '', name: '', size: 0, file_type: '' };
-          
-          // 重置表单数据
-          videoForm.value = {
-            videoUrl: '',
-            subtitleContent: ''
-          };
-          
-          // 刷新列表
-          crudExpose.doRefresh();
-        } else {
-          ElMessage.error(response?.msg || '保存失败');
-        }
-      } catch (error) {
-        console.error('保存视频和字幕信息时出错:', error);
-        ElMessage.error('保存失败,请重试');
-      }
-    } else {
-      ElMessage.warning('请完成必填项');
-      return false;
-    }
-  });
-};
-
-// 处理打开排序对话框
-const handleOpenSortDialog = (event: any) => {
-  const row = event.detail;
-  if (row) {
-    currentRow.value = row;
-    sortForm.value.sequence_number = row.sequence_number || 0;
-    sortDialogVisible.value = true;
-  }
-};
-
-// 修改提交排序表单函数
-const submitSortForm = () => {
-  if (!sortFormRef.value) return;
-  
-  sortFormRef.value.validate(async (valid: boolean) => {
-    if (valid) {
-      try {
-        if (!currentRow.value || !currentRow.value.question_id) {
-          ElMessage.error('当前选中的题目信息无效');
-          return;
-        }
-
-        // 获取当前职位ID
-        const positionId = selectedCategoryId.value;
-        if (!positionId) {
-          ElMessage.error('未选择职位分类');
-          return;
-        }
-
-        // 构建符合新格式的更新数据
-        const updateData = {
-          position_id: positionId,
-          questions: [
-            {
-              question_id: currentRow.value.question_id,
-              sequence_number: sortForm.value.sequence_number
-            }
-          ],
-          tenant_id: 1
-        };
-
-        console.log('提交的排序数据:', updateData);
-        
-        const response = await UpdateSequence(updateData);
-        
-        if (response && response.code === 2000) {
-          ElMessage.success('排序修改成功');
-          sortDialogVisible.value = false;
-          
-          // 刷新列表
-          crudExpose.doRefresh();
-        } else {
-          ElMessage.error(response?.msg || '保存失败');
-        }
-      } catch (error) {
-        console.error('保存排序信息时出错:', error);
-        ElMessage.error('保存失败,请重试');
-      }
-    } else {
-      ElMessage.warning('请输入有效的排序值');
-      return false;
-    }
-  });
-};
-
-// 页面加载时获取数据
-onMounted(() => {
-  getTreeData();
-  
-  // 先执行一次刷新,确保组件已完全初始化
-  crudExpose.doRefresh();
-  
-  // 监听crud组件初始化完成
-  setTimeout(() => {
-    if (crudRef.value) {
-      // 确保搜索表单已初始化
-      if (!crudRef.value.searchForm) {
-        crudRef.value.searchForm = {};
-      }
-      
-      // 如果有默认选中的分类,设置搜索参数并执行搜索
-      if (selectedCategoryId.value) {
-        crudRef.value.searchForm.category = selectedCategoryId.value;
-        crudExpose.doSearch({
-          form: {
-            category: selectedCategoryId.value
-          }
-        });
-      }
-    }
-  }, 300);
-
-  // 添加全局事件监听器
-  window.addEventListener('viewDocumentDetail', handleViewDetail);
-  window.addEventListener('openVideoUploadDialog', handleOpenVideoUploadDialog);
-  window.addEventListener('openSortDialog', handleOpenSortDialog);
-  window.addEventListener('openPositionQuestionDialog', openPositionQuestionDialog);
-  // 添加绑定开放问题对话框的事件监听器
-  window.addEventListener('openBindParentQuestionDialog', handleOpenBindParentQuestionDialog as EventListener);
-});
-
-// 添加 onBeforeUnmount 钩子移除事件监听器
-onBeforeUnmount(() => {
-  window.removeEventListener('viewDocumentDetail', handleViewDetail);
-  window.removeEventListener('openVideoUploadDialog', handleOpenVideoUploadDialog);
-  window.removeEventListener('openSortDialog', handleOpenSortDialog);
-  window.removeEventListener('openPositionQuestionDialog', openPositionQuestionDialog);
-  // 移除绑定开放问题对话框的事件监听器
-  window.removeEventListener('openBindParentQuestionDialog', handleOpenBindParentQuestionDialog as EventListener);
-});
-
-// 新增 questionSearchKeyword
-const questionSearchKeyword = ref('');
-
-// 绑定开放问题相关变量
-const bindParentQuestionDialogVisible = ref(false);
-const selectedParentQuestionId = ref<number | null>(null);
-const openQuestionsList = ref<any[]>([]);
-const openQuestionsLoading = ref(false);
-const openQuestionKeyword = ref('');
-
-// 获取开放问题列表
-const fetchOpenQuestions = async () => {
-  try {
-    openQuestionsLoading.value = true;
-    
-    // 构建请求参数,添加关键词搜索
-    const params: any = {
-      page: 1,
-      limit: 200,
-      tenant_id: 1,
-      question_form: 0, // 开放问题类型
-    };
-    
-    // 如果有关键词,添加到请求参数
-    if (openQuestionKeyword.value) {
-      params.keyword = openQuestionKeyword.value;
-    }
-    
-    const res = await GetInterviewQuestions(params);
-    openQuestionsList.value = res.data.items || [];
-  } catch (error) {
-    console.error('获取开放问题列表失败:', error);
-    ElMessage.error('获取开放问题列表失败');
-  } finally {
-    openQuestionsLoading.value = false;
-  }
-};
-
-// 搜索开放问题
-const searchOpenQuestions = () => {
-  // 使用防抖函数,避免频繁请求
-  if (searchOpenQuestionsDebounce) {
-    clearTimeout(searchOpenQuestionsDebounce);
-  }
-  
-  searchOpenQuestionsDebounce = setTimeout(() => {
-    fetchOpenQuestions();
-  }, 300);
-};
-
-// 防抖变量
-let searchOpenQuestionsDebounce: number | null = null;
-
-// 监听打开绑定对话框事件
-const handleOpenBindParentQuestionDialog = (event: CustomEvent) => {
-  currentRow.value = event.detail;
-  // 如果已有关联的开放问题,则预选中
-  if (currentRow.value.parent_question_id) {
-    selectedParentQuestionId.value = currentRow.value.parent_question_id;
-  } else {
-    selectedParentQuestionId.value = null;
-  }
-  
-  // 重置搜索关键词
-  openQuestionKeyword.value = '';
-  
-  // 获取开放问题列表
-  fetchOpenQuestions();
-  
-  // 显示对话框
-  bindParentQuestionDialogVisible.value = true;
-};
-
-// 添加处理绑定对话框关闭的函数
-const handleBindDialogClose = () => {
-  bindParentQuestionDialogVisible.value = false;
-  selectedParentQuestionId.value = null;
-  currentRow.value = null;
-};
-
-// 添加确认绑定开放问题的函数
-const confirmBindParentQuestion = async () => {
-  if (!currentRow.value || selectedParentQuestionId.value === null) {
-    ElMessage.warning('请选择要关联的开放问题');
-    return;
-  }
-  
-  try {
-    await UpdateDocument({
-      id: currentRow.value.question_id,
-      parent_question_id: selectedParentQuestionId.value,
-      tenant_id: '1'
-    });
-    
-    ElMessage.success('绑定开放问题成功');
-    bindParentQuestionDialogVisible.value = false;
-    selectedParentQuestionId.value = null;
-    
-    // 刷新表格数据
-    crudExpose.doRefresh();
-  } catch (error) {
-    console.error('绑定开放问题失败:', error);
-    ElMessage.error('绑定开放问题失败');
-  }
-};
-
-// 添加 bindParentQuestion 方法
-const bindParentQuestion = (row: any) => {
-  // 触发自定义事件,让父组件处理弹窗显示
-  const event = new CustomEvent('openBindParentQuestionDialog', { detail: row });
-  window.dispatchEvent(event);
-};
-</script>
-
-<style lang="scss" scoped>
-.document-el-row {
-  height: 100%;
-  overflow: hidden;
-
-  .el-col {
-    height: 100%;
-    padding: 10px 0;
-    box-sizing: border-box;
-  }
-}
-
-.document-box {
-  height: 100%;
-  padding: 10px;
-  background-color: var(--el-fill-color-blank);
-  box-sizing: border-box;
-}
-
-.document-left-box {
-  position: relative;
-  border-radius: 0 8px 8px 0;
-  margin-right: 10px;
-}
-
-.document-right-box {
-  border-radius: 8px 0 0 8px;
-}
-
-.mt-2 {
-  margin-top: 0.5rem;
-}
-.w-full {
-  width: 100%;
-}
-.mr-2 {
-  margin-right: 0.5rem;
-}
-.mr-4 {
-  margin-right: 1rem;
-}
-.text-gray-500 {
-  color: #6b7280;
-}
-.text-sm {
-  font-size: 0.875rem;
-}
-.flex {
-  display: flex;
-}
-.items-center {
-  align-items: center;
-}
-.justify-between {
-  justify-content: space-between;
-}
-
-/* 添加详情对话框样式 */
-.el-descriptions {
-  margin: 10px 0;
-}
-
-.dialog-footer {
-  display: flex;
-  justify-content: flex-end;
-}
-
-.mt-4 {
-  margin-top: 1rem;
-}
-::v-deep .el-upload{
-  flex-direction: column;
-}
-/* 表单样式优化 */
-.el-form-item {
-  margin-bottom: 20px;
-}
-
-.upload-section {
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
-  padding: 15px;
-  background-color: #f8f9fa;
-}
-
-.video-uploader, .subtitle-uploader {
-  width: 100%;
-}
-
-.video-preview {
-  width: 100%;
-  margin-bottom: 10px;
-}
-
-.video-player {
-  width: 100%;
-  max-height: 200px;
-  object-fit: contain;
-  border-radius: 4px;
-}
-
-.file-preview {
-  display: flex;
-  align-items: center;
-  padding: 10px;
-  border: 1px dashed #d9d9d9;
-  border-radius: 4px;
-  background-color: #fff;
-}
-
-.file-icon {
-  font-size: 24px;
-  margin-right: 10px;
-  color: #409eff;
-}
-
-.file-info {
-  display: flex;
-  flex-direction: column;
-  margin-top: 5px;
-}
-
-.file-size {
-  font-size: 12px;
-  color: #909399;
-  margin-top: 3px;
-}
-
-.subtitle-tip {
-  color: #909399;
-  font-size: 12px;
-}
-
-.mt-2 {
-  margin-top: 8px;
-}
-
-.question-list-container {
-  margin-top: 20px;
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
-  padding: 15px;
-  background-color: #f8f9fa;
-}
-
-.question-list-container h3 {
-  margin-top: 0;
-  margin-bottom: 15px;
-  font-size: 16px;
-  color: #303133;
-}
-
-.question-selection-container {
-  width: 100%;
-  border: 1px solid #ebeef5;
-  border-radius: 4px;
-  overflow: hidden;
-}
-
-.question-search {
-  padding: 10px;
-  background-color: #f5f7fa;
-  border-bottom: 1px solid #ebeef5;
-}
-
-.search-row {
-  display: flex;
-  align-items: center;
-}
-
-.question-table-container {
-  display: flex;
-  flex-direction: column;
-  height: 350px;
-}
-
-.pagination-container {
-  margin-top: 10px;
-  padding: 5px 0;
-  display: flex;
-  justify-content: flex-end;
-  background-color: #f5f7fa;
-  border-top: 1px solid #ebeef5;
-}
-
-.question-item {
-  margin-bottom: 8px;
-  padding: 8px;
-  border-radius: 4px;
-  transition: background-color 0.2s;
-  
-  &:hover {
-    background-color: #f5f7fa;
-  }
-}
-
-.no-data {
-  padding: 20px 0;
-  text-align: center;
-}
-
-.search-container {
-  margin-bottom: 15px;
-}
-</style>

+ 22 - 0
src/views/system/dept/components/DeptUserCom/crud.tsx

@@ -42,6 +42,28 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:false},
+                    // 刷新按钮
+                    refresh:{show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			table: {
 				remove: {
 					confirmMessage: '是否删除该用户?',

+ 5 - 5
src/views/system/dept/components/DeptUserCom/index.vue

@@ -1,5 +1,5 @@
 <template>
-	<div v-show="!showCount" class="dept-user-com-box dept-info">
+	<!-- <div v-show="!showCount" class="dept-user-com-box dept-info">
 		<div class="di-left">
 			<h3>{{ deptInfo.dept_name || '' }}</h3>
 			<div class="di-cell">
@@ -29,7 +29,7 @@
 		</div>
 		<div style="height: 180px; width: 380px" ref="deptCountBar"></div>
 		<div style="height: 180px; width: 200px" ref="deptSexPie"></div>
-	</div>
+	</div> -->
 
 	<fs-crud
 		ref="crudRef"
@@ -203,7 +203,7 @@ const getDeptInfo = async () => {
 	const res = await getDeptInfoById(currentDeptId.value, isShowChildFlag.value ? '1' : '0');
 	if (res?.code === 2000) {
 		deptInfo.value = res.data;
-		initDeptCountBarChart();
+		/* initDeptCountBarChart(); */
 		initDeptSexPieChart();
 	}
 };
@@ -261,8 +261,8 @@ const handleResetPwdSubmit = async () => {
 };
 
 onMounted(() => {
-	deptCountChart = init(deptCountBar.value as HTMLElement);
-	deptSexChart = init(deptSexPie.value as HTMLElement);
+	/* deptCountChart = init(deptCountBar.value as HTMLElement);
+	deptSexChart = init(deptSexPie.value as HTMLElement); */
 	getDeptInfo();
   crudExpose.doRefresh();
 });

+ 2 - 2
src/views/system/dept/index.vue

@@ -1,7 +1,7 @@
 <template>
 	<fs-page>
 		<el-row class="dept-el-row">
-			<el-col :span="6">
+			<el-col :span="3">
 				<div class="dept-box dept-left">
 					<DeptTreeCom
 						ref="deptTreeRef"
@@ -13,7 +13,7 @@
 				</div>
 			</el-col>
 
-			<el-col :span="18">
+			<el-col :span="21">
 				<div class="dept-box dept-table">
 					<DeptUserCom ref="deptUserRef" />
 				</div>

+ 22 - 0
src/views/system/dictionary/crud.tsx

@@ -22,6 +22,28 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:false},
+                    // 刷新按钮
+                    refresh:{show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			request: {
 				pageRequest,
 				addRequest,

+ 22 - 0
src/views/system/downloadCenter/crud.tsx

@@ -31,6 +31,28 @@ export const createCrudOptions = function ({ crudExpose }: { crudExpose: CrudExp
     // @ts-ignore
     return {
         crudOptions: {
+            toolbar:{
+                buttons:{
+                    search:{show:false},
+                    // 刷新按钮
+                    refresh:{show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
             request: {
                 pageRequest,
                 addRequest,

+ 23 - 0
src/views/system/fileList/crud.tsx

@@ -29,6 +29,29 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 	};
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:true},
+                    // 刷新按钮
+                    refresh:{
+                        show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			actionbar: {
 				buttons: {
 					add: {

+ 2 - 2
src/views/system/home/index.vue

@@ -591,7 +591,7 @@ export default defineComponent({
 		);
 		// 获取API数据
 		const fetchApiData = async () => {
-			try {
+			/* try {
 				const res = await GetList({});
 				if (res.code === 2000) {
 					state.apiData = res.data;
@@ -599,7 +599,7 @@ export default defineComponent({
 				}
 			} catch (error) {
 				console.error('获取统计数据失败:', error);
-			}
+			} */
 		};
 		// 更新首页数据
 		const updateHomeData = (data: ApiData) => {

+ 22 - 0
src/views/system/log/loginLog/crud.tsx

@@ -18,6 +18,28 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 	};
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:false},
+                    // 刷新按钮
+                    refresh:{show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			request: {
 				pageRequest,
 				addRequest,

+ 22 - 0
src/views/system/log/operationLog/crud.tsx

@@ -17,6 +17,28 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 	};
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:false},
+                    // 刷新按钮
+                    refresh:{show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			request: {
 				pageRequest,
 				addRequest,

+ 28 - 29
src/views/system/login/component/account.vue

@@ -1,39 +1,44 @@
 <template>
-	<el-form ref="formRef" size="large" class="login-content-form" :model="state.ruleForm" :rules="rules"
-		@keyup.enter="loginClick">
-		<el-form-item class="login-animation1" prop="merchant_code">
+	<el-form ref="formRef" size="large" class="login-content-form" :model="state.ruleForm" :rules="rules" @keyup.enter="loginClick">
+		<!-- <el-form-item class="login-animation1" prop="merchant_code">
 				<el-input type="text" :placeholder="'请输入商户号'" v-model="ruleForm.merchant_code" clearable autocomplete="off">
 					<template #prefix>
 						<el-icon class="el-input__icon"><ele-Shop /></el-icon>
 					</template>
 				</el-input>
-		</el-form-item>
+		</el-form-item> -->
 		<el-form-item class="login-animation1" prop="username">
-			<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.username"
-				clearable autocomplete="off">
+			<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.username" clearable autocomplete="off">
 				<template #prefix>
 					<el-icon class="el-input__icon"><ele-User /></el-icon>
 				</template>
 			</el-input>
 		</el-form-item>
 		<el-form-item class="login-animation2" prop="password">
-			<el-input :type="isShowPassword ? 'text' : 'password'"
-				:placeholder="$t('message.account.accountPlaceholder2')" v-model="ruleForm.password">
+			<el-input :type="isShowPassword ? 'text' : 'password'" :placeholder="$t('message.account.accountPlaceholder2')" v-model="ruleForm.password">
 				<template #prefix>
 					<el-icon class="el-input__icon"><ele-Unlock /></el-icon>
 				</template>
 				<template #suffix>
-					<i class="iconfont el-input__icon login-content-password"
+					<i
+						class="iconfont el-input__icon login-content-password"
 						:class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
-						@click="isShowPassword = !isShowPassword">
+						@click="isShowPassword = !isShowPassword"
+					>
 					</i>
 				</template>
 			</el-input>
 		</el-form-item>
 		<el-form-item class="login-animation3" v-if="isShowCaptcha" prop="captcha">
 			<el-col :span="15">
-				<el-input type="text" maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')"
-					v-model="ruleForm.captcha" clearable autocomplete="off">
+				<el-input
+					type="text"
+					maxlength="4"
+					:placeholder="$t('message.account.accountPlaceholder3')"
+					v-model="ruleForm.captcha"
+					clearable
+					autocomplete="off"
+				>
 					<template #prefix>
 						<el-icon class="el-input__icon"><ele-Position /></el-icon>
 					</template>
@@ -55,7 +60,6 @@
 			</el-checkbox>
 		</el-form-item>
 		<el-form-item class="login-animation4">
-			
 			<el-button type="primary" class="login-content-submit" round @click="loginClick" :loading="loading.signIn" :disabled="!state.agreePolicy">
 				<span>{{ $t('message.account.accountBtnText') }}</span>
 			</el-button>
@@ -89,7 +93,7 @@ import { SystemConfigStore } from '/@/stores/systemConfig';
 import { BtnPermissionStore } from '/@/plugin/permission/store.permission';
 import { Md5 } from 'ts-md5';
 import { errorMessage } from '/@/utils/message';
-import { getBaseURL } from "/@/utils/baseUrl";
+import { getBaseURL } from '/@/utils/baseUrl';
 
 export default defineComponent({
 	name: 'loginAccount',
@@ -108,8 +112,8 @@ export default defineComponent({
 				captcha: '',
 				captchaKey: '',
 				captchaImgBase: '',
-				merchant_code: localStorage.getItem('merchant_code') || 'ML01',
-				tenant_id:1
+				//merchant_code: localStorage.getItem('merchant_code') || 'ML01',
+				tenant_id: 1,
 			},
 			loading: {
 				signIn: false,
@@ -117,10 +121,8 @@ export default defineComponent({
 			agreePolicy: true,
 		});
 		const rules = reactive<FormRules>({
-			merchant_code: [{ required: true, message: '请填写商户号', trigger: 'blur' }],
-			username: [
-				{ required: true, message: '请填写账号', trigger: 'blur' },
-			],
+			//merchant_code: [{ required: true, message: '请填写商户号', trigger: 'blur' }],
+			username: [{ required: true, message: '请填写账号', trigger: 'blur' }],
 			password: [
 				{
 					required: true,
@@ -135,7 +137,7 @@ export default defineComponent({
 					trigger: 'blur',
 				},
 			],
-		})
+		});
 		const formRef = ref();
 		// 时间获取
 		const currentTime = computed(() => {
@@ -156,7 +158,7 @@ export default defineComponent({
 			window.open(getBaseURL('/api/system/apply_for_trial/'));
 		};
 		const refreshCaptcha = async () => {
-			state.ruleForm.captcha = ''
+			state.ruleForm.captcha = '';
 			loginApi.getCaptcha().then((ret: any) => {
 				state.ruleForm.captchaImgBase = ret.data.image_base;
 				state.ruleForm.captchaKey = ret.data.key;
@@ -201,9 +203,6 @@ export default defineComponent({
 			});
 		};
 
-
-
-		
 		// 登录成功后的跳转
 		const loginSuccess = () => {
 			//获取所有字典
@@ -240,8 +239,8 @@ export default defineComponent({
 		});
 		// 是否显示申请试用按钮
 		const showApply = () => {
-			return window.location.href.indexOf('public') != -1
-		}
+			return window.location.href.indexOf('public') != -1;
+		};
 
 		return {
 			refreshCaptcha,
@@ -296,10 +295,10 @@ export default defineComponent({
 		font-weight: 800;
 		margin-top: 15px;
 	}
-	:deep(.el-checkbox__label){
+	:deep(.el-checkbox__label) {
 		color: #808080 !important;
 	}
-	:deep(.el-checkbox__input.is-checked+.el-checkbox__label){
+	:deep(.el-checkbox__input.is-checked + .el-checkbox__label) {
 		color: #808080 !important;
 	}
 	.login-link {

+ 23 - 0
src/views/system/menu/components/MenuButtonCom/crud.tsx

@@ -61,6 +61,29 @@ export const createCrudOptions = function ({crudExpose, context}: CreateCrudOpti
     return {
         selectedRows,
         crudOptions: {
+            toolbar:{
+                buttons:{
+                    search:{show:true},
+                    // 刷新按钮
+                    refresh:{
+                        show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
             pagination:{
                 show:false
             },

+ 23 - 0
src/views/system/messageCenter/crud.tsx

@@ -36,6 +36,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
 
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:true},
+                    // 刷新按钮
+                    refresh:{
+                        show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			request: {
 				pageRequest,
 				addRequest,

+ 23 - 0
src/views/system/role/crud.tsx

@@ -27,6 +27,29 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 
 	return {
 		crudOptions: {
+			toolbar:{
+                buttons:{
+                    search:{show:true},
+                    // 刷新按钮
+                    refresh:{
+                        show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
 			request: {
 				pageRequest,
 				addRequest,

+ 23 - 0
src/views/system/user/crud.tsx

@@ -53,6 +53,29 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 
     return {
         crudOptions: {
+            toolbar:{
+                buttons:{
+                    search:{show:true},
+                    // 刷新按钮
+                    refresh:{
+                        show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
             table: {
                 remove: {
                     confirmMessage: '是否删除该用户?',

+ 3 - 3
src/views/system/user/index.vue

@@ -1,7 +1,7 @@
 <template>
   <fs-page>
     <el-row class="mx-2">
-      <el-col xs="24" :sm="8" :md="6" :lg="4" :xl="4" class="p-1">
+      <!-- <el-col xs="24" :sm="8" :md="6" :lg="4" :xl="4" class="p-1">
         <el-card :body-style="{ height: '100%' }">
           <p class="font-mono font-black text-center text-xl pb-5">
             部门列表
@@ -26,8 +26,8 @@
             </template>
           </el-tree>
         </el-card>
-      </el-col>
-      <el-col xs="24" :sm="16" :md="18" :lg="20" :xl="20" class="p-1">
+      </el-col> -->
+      <el-col xs="24" :sm="16" :md="18" :lg="24" :xl="20" class="p-1">
         <el-card :body-style="{ height: '100%' }">
           <fs-crud ref="crudRef" v-bind="crudBinding">
             <template #actionbar-right>

+ 23 - 0
src/views/system/whiteList/crud.tsx

@@ -32,6 +32,29 @@ export const createCrudOptions = function ({crudExpose}: CreateCrudOptionsProps)
 
     return {
         crudOptions: {
+            toolbar:{
+                buttons:{
+                    search:{show:true},
+                    // 刷新按钮
+                    refresh:{
+                        show:true},
+                    // 紧凑模式
+                    compact:{show:false},
+                    // 导出按钮
+                   /*  export:{
+                        text: '导出',
+                        type: 'primary',
+                        size: 'small',
+                        icon: 'upload',
+                        circle: false,
+                        display: true
+                    }, */
+                    // 列设置按钮
+                    columns:{
+                        show:false
+                    },
+                }
+            },
             request: {
                 pageRequest,
                 addRequest,

+ 0 - 84
src/views/talent/list/api.ts

@@ -1,84 +0,0 @@
-import { request } from '/@/utils/service';
-import { UserPageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
-
-export const apiPrefix = '/api/system/talent_pool/list';
-export function GetList(query: UserPageQuery) {
-	return request({
-		url: apiPrefix,
-		method: 'get',
-		params: {...query,tenant_id:1},
-	});
-}
-export function GetObj(id: InfoReq) {
-	return request({
-		url: apiPrefix + id,
-		method: 'get',
-	});
-}
-
-export function AddObj(obj: AddReq) {
-	return request({
-		url: apiPrefix,
-		method: 'post',
-		data: obj,
-	});
-}
-
-export function UpdateObj(obj: EditReq) {
-	return request({
-		url: apiPrefix + obj.id + '/',
-		method: 'put',
-		data: obj,
-	});
-}
-
-export function DelObj(id: DelReq) {
-	return request({
-		url:`/api/system/job_applications/${id}/?tenant_id=1`,
-		method: 'delete',
-		data: { id },
-	});
-}
-export function GetPermission(query: UserPageQuery) {
-    return request({
-        url: apiPrefix,
-        method: 'get',
-		params: {...query,tenant_id:1}
-    });
-}
-
-export function BulkUpdateStatus(data: {
-	application_ids: number[];
-	new_status: number;
-	note?: string;
-	tenant_id: string;
-}) {
-	return request({
-		url: '/api/system/job_applications/bulk_update_status/',
-		method: 'post',
-		data
-	});
-}
-
-export function updateBatchStatus(data: any) {
-	return request({
-		url: '/api/system/job_applications/bulk_update_status/',
-		method: 'post',
-		data
-	});
-}
-
-/* export function updateBatchTags(data) {
-	return request({
-		url: '/job-application/batch-update-tags',
-		method: 'post',
-		data
-	});
-} */
-	export function getApplicationStatusSummary() {
-		return request({
-			url: '/api/system/job/application_status_summary/?tenant_id=1',
-			method: 'get',
-		});
-	}
-	

+ 0 - 153
src/views/talent/list/components/index.vue

@@ -1,153 +0,0 @@
-<template>
-  <el-dialog
-    v-model="dialogVisible"
-    title="批量操作"
-    width="500px"
-    :close-on-click-modal="false"
-    :close-on-press-escape="false"
-  >
-    <el-form :model="form" label-width="100px">
-     <!--  <el-form-item label="操作类型">
-        <el-select v-model="form.operationType" placeholder="请选择操作类型" style="width: 100%">
-          <el-option label="修改状态" value="status"></el-option>
-          <el-option label="添加标签" value="tags"></el-option>
-        </el-select>
-      </el-form-item> -->
-
-      <el-form-item v-if="form.operationType === 'status'" label="状态">
-        <el-select v-model="form.status" placeholder="请选择状态" style="width: 100%">
-          <el-option label="待处理" :value="0"></el-option>
-          <el-option label="已通知面试" :value="1"></el-option>
-          <el-option label="已面试" :value="2"></el-option>
-          <el-option label="已录用" :value="3"></el-option>
-          <el-option label="已拒绝" :value="4"></el-option>
-        </el-select>
-      </el-form-item>
-
-      <el-form-item v-if="form.operationType === 'tags'" label="标签">
-        <el-select
-          v-model="form.tags"
-          multiple
-          filterable
-          allow-create
-          default-first-option
-          placeholder="请选择或创建标签"
-          style="width: 100%"
-        >
-          <el-option
-            v-for="tag in tagOptions"
-            :key="tag.value"
-            :label="tag.label"
-            :value="tag.value"
-          ></el-option>
-        </el-select>
-      </el-form-item>
-    </el-form>
-
-    <template #footer>
-      <span class="dialog-footer">
-        <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="primary" @click="handleSubmit" :loading="loading">确定</el-button>
-      </span>
-    </template>
-  </el-dialog>
-</template>
-
-<script lang="ts" setup>
-import { ref, reactive } from 'vue';
-import { ElMessage } from 'element-plus';
-import { updateBatchStatus } from '../api';
-
-const props = defineProps({
-  crudExpose: {
-    type: Object,
-    required: true
-  }
-});
-
-const dialogVisible = ref(false);
-const loading = ref(false);
-const selectedItems = ref([]);
-
-const form = reactive({
-  operationType: 'status',
-  status: undefined,
-  tags: []
-});
-
-// 标签选项,可以根据实际需求从API获取
-const tagOptions = ref([
-  { value: '优质候选人', label: '优质候选人' },
-  { value: '有经验', label: '有经验' },
-  { value: '应届毕业生', label: '应届毕业生' },
-  { value: '技能熟练', label: '技能熟练' }
-]);
-
-// 打开对话框
-const open = (selection: any) => {
-  if (!selection || selection.length === 0) {
-    ElMessage.warning('请至少选择一条记录');
-    return;
-  }
-  
-  selectedItems.value = selection;
-  dialogVisible.value = true;
-  
-  // 重置表单
-  form.operationType = 'status';
-  form.status = undefined;
-  form.tags = [];
-};
-
-// 提交处理
-const handleSubmit = async () => {
-  
-  if (form.operationType === 'tags' && (!form.tags || form.tags.length === 0)) {
-    ElMessage.warning('请至少选择一个标签');
-    return;
-  }
-  
-  try {
-    loading.value = true;
-    const application_ids = selectedItems.value.map((item: any) => item.id);
-    
-    if (form.operationType === 'status') {
-      await updateBatchStatus({
-        application_ids,
-        new_status: form.status,
-        tenant_id: 1
-      });
-      ElMessage.success('批量更新状态成功');
-    } /* else if (form.operationType === 'tags') {
-      await updateBatchTags({
-        ids,
-        tags: form.tags
-      });
-      ElMessage.success('批量更新标签成功');
-    } */
-    
-    // 关闭对话框
-    dialogVisible.value = false;
-    
-    // 刷新列表
-    props.crudExpose.doRefresh();
-  } catch (error) {
-    console.error('批量操作失败:', error);
-    ElMessage.error('操作失败,请重试');
-  } finally {
-    loading.value = false;
-  }
-};
-
-// 暴露方法给父组件
-defineExpose({
-  open
-});
-</script>
-
-<style scoped>
-.dialog-footer {
-  display: flex;
-  justify-content: flex-end;
-}
-</style>

+ 0 - 555
src/views/talent/list/crud.tsx

@@ -1,555 +0,0 @@
-import * as api from './api';
-import { dict, UserPageQuery, AddReq, DelReq, EditReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
-import { dictionary } from '/@/utils/dictionary';
-import { successMessage,warningMessage } from '/@/utils/message';
-import { auth } from '/@/utils/authFunction';
-import tableSelector from '/@/components/tableSelector/index.vue';
-import { shallowRef, h, createVNode, render } from 'vue';
-import { useRouter } from 'vue-router';
-import { ElDialog, ElDescriptions, ElDescriptionsItem, ElTabs, ElTabPane, ElTable, ElTableColumn, ElButton } from 'element-plus';
-import axios from 'axios';
-
-export const createCrudOptions = function ({ crudExpose, context}: CreateCrudOptionsProps): CreateCrudOptionsRet {
-	const router = useRouter();
-	
-	const pageRequest = async (query: UserPageQuery) => {
-		console.log(query);
-		return await api.GetList(query);
-	};
-	const editRequest = async ({ form, row }: EditReq) => {
-		form.id = row.id;
-		return await api.UpdateObj(form);
-	};
-	const delRequest = async ({ row }: DelReq) => {
-		return await api.DelObj(row.id);
-	};
-	const addRequest = async ({ form }: AddReq) => {
-		return await api.AddObj(form);
-	};
-
-	/**
-	 * 懒加载
-	 * @param row
-	 * @returns {Promise<unknown>}
-	 */
-	const loadContentMethod = (tree: any, treeNode: any, resolve: Function) => {
-		pageRequest({ pcode: tree.code }).then((res: APIResponseData) => {
-			resolve(res.data);
-		});
-	};
-
-	// 修改获取用户信息的方法
-	const getUserProfile = async (userId: number) => {
-		try {
-			const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/system/wechat/user/profile/get?user_id=${userId}&tenant_id=1`);
-			return response.data;
-		} catch (error) {
-			console.error('获取用户信息失败:', error);
-			return null;
-		}
-	};
-
-	// 显示用户信息弹窗
-	const showUserProfileDialog = async (userId: number) => {
-		const profileData = await getUserProfile(userId);
-		if (!profileData || profileData.code !== 2000) {
-			warningMessage('获取用户信息失败');
-			return;
-		}
-
-		// 创建弹窗内容
-		const { user_info, profile, educations, family_members, trainings, work_experiences } = profileData.data;
-
-		// 创建一个容器元素
-		const container = document.createElement('div');
-		
-		// 创建对话框内容
-		const dialogContent = createVNode(
-			ElDialog,
-			{
-				title: '个人信息详情',
-				width: '70%',
-				modelValue: true,
-				'onUpdate:modelValue': (val: boolean) => {
-					if (!val) {
-						// 关闭对话框时销毁组件
-						render(null, container);
-						document.body.removeChild(container);
-					}
-				},
-				beforeClose: () => {
-					render(null, container);
-					document.body.removeChild(container);
-				}
-			},
-			{
-				default: () => createVNode(ElTabs, { type: 'border-card' }, {
-					default: () => [
-						// 基本信息标签页
-						createVNode(ElTabPane, { label: '基本信息' }, {
-							default: () => createVNode(ElDescriptions, { column: 3, border: true }, {
-								default: () => [
-									createVNode(ElDescriptionsItem, { label: '姓名' }, { default: () => user_info?.name || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '电话' }, { default: () => user_info?.phone || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '年龄' }, { default: () => user_info?.age || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '出生日期' }, { default: () => user_info?.birth_date || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '性别' }, { default: () => user_info?.gender_name || '未知' }),
-									createVNode(ElDescriptionsItem, { label: '身份证号' }, { default: () => user_info?.id_card || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '政治面貌' }, { default: () => profile?.political_status || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '民族' }, { default: () => profile?.ethnicity || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '身高' }, { default: () => profile?.height ? `${profile.height}cm` : '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '体重' }, { default: () => profile?.weight ? `${profile.weight}kg` : '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '籍贯' }, { default: () => profile?.native_place || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '户口所在地' }, { default: () => profile?.household_location || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '现居地址' }, { default: () => profile?.current_address || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '婚姻状况' }, { default: () => profile?.marital_status_name || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '是否有子女' }, { default: () => profile?.has_children !== undefined ? (profile.has_children ? '是' : '否') : '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '期望薪资' }, { default: () => profile?.expected_salary || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '紧急联系人' }, { default: () => profile?.emergency_contact || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '紧急联系电话' }, { default: () => profile?.emergency_phone || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '特长' }, { default: () => profile?.specialties || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '人生格言' }, { default: () => profile?.life_motto || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '招聘来源' }, { default: () => profile?.recruitment_source_name || '未填写' }),
-									createVNode(ElDescriptionsItem, { label: '招聘来源详情' }, { default: () => profile?.recruitment_source_detail || '未填写' }),
-								]
-							})
-						}),
-						
-						// 教育经历标签页 - 只在有数据时显示
-						educations && educations.length > 0 ? createVNode(ElTabPane, { label: '教育经历' }, {
-							default: () => createVNode(ElTable, { data: educations, border: true, stripe: true }, {
-								default: () => [
-									createVNode(ElTableColumn, { prop: 'education_type_name', label: '学历类型' }),
-									createVNode(ElTableColumn, { prop: 'degree_name', label: '学位' }),
-									createVNode(ElTableColumn, { prop: 'school_name', label: '学校名称' }),
-									createVNode(ElTableColumn, { prop: 'major', label: '专业' }),
-									createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
-									createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
-								]
-							})
-						}) : null,
-						
-						// 家庭成员标签页 - 只在有数据时显示
-						family_members && family_members.length > 0 ? createVNode(ElTabPane, { label: '家庭成员' }, {
-							default: () => createVNode(ElTable, { data: family_members, border: true, stripe: true }, {
-								default: () => [
-									createVNode(ElTableColumn, { prop: 'relation', label: '关系' }),
-									createVNode(ElTableColumn, { prop: 'name', label: '姓名' }),
-									createVNode(ElTableColumn, { prop: 'workplace', label: '工作单位' }),
-									createVNode(ElTableColumn, { prop: 'position', label: '职位' }),
-									createVNode(ElTableColumn, { prop: 'phone', label: '联系电话' }),
-								]
-							})
-						}) : null,
-						
-						// 工作经历标签页 - 只在有数据时显示
-						work_experiences && work_experiences.length > 0 ? createVNode(ElTabPane, { label: '工作经历' }, {
-							default: () => createVNode(ElTable, { data: work_experiences, border: true, stripe: true }, {
-								default: () => [
-									createVNode(ElTableColumn, { prop: 'company_name', label: '公司名称' }),
-									createVNode(ElTableColumn, { prop: 'department', label: '部门' }),
-									createVNode(ElTableColumn, { prop: 'position', label: '职位' }),
-									createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
-									createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
-									createVNode(ElTableColumn, { prop: 'monthly_salary', label: '月薪' }),
-									createVNode(ElTableColumn, { prop: 'company_size', label: '公司规模' }),
-									createVNode(ElTableColumn, { prop: 'supervisor_name', label: '主管姓名' }),
-									createVNode(ElTableColumn, { prop: 'supervisor_phone', label: '主管电话' }),
-									createVNode(ElTableColumn, { prop: 'resignation_reason', label: '离职原因' }),
-								]
-							})
-						}) : null,
-						
-						// 培训经历标签页 - 只在有数据时显示
-						trainings && trainings.length > 0 ? createVNode(ElTabPane, { label: '培训经历' }, {
-							default: () => createVNode(ElTable, { data: trainings, border: true, stripe: true }, {
-								default: () => [
-									createVNode(ElTableColumn, { prop: 'training_name', label: '培训名称' }),
-									createVNode(ElTableColumn, { prop: 'institution', label: '培训机构' }),
-									createVNode(ElTableColumn, { prop: 'start_date', label: '开始日期' }),
-									createVNode(ElTableColumn, { prop: 'end_date', label: '结束日期' }),
-									createVNode(ElTableColumn, { prop: 'description', label: '描述' }),
-									createVNode(ElTableColumn, { prop: 'certificate', label: '证书' }),
-								]
-							})
-						}) : null,
-					].filter(Boolean) // 过滤掉null值
-				}),
-				footer: () => createVNode(ElButton, {
-					onClick: () => {
-						render(null, container);
-						document.body.removeChild(container);
-					}
-				}, { default: () => '关闭' })
-			}
-		);
-
-		// 将容器添加到body
-		document.body.appendChild(container);
-		
-		// 渲染对话框到容器
-		render(dialogContent, container);
-	};
-
-	return {
-		crudOptions: {
-			toolbar:{
-				buttons:{
-					search:{show:false},
-					// 刷新按钮
-					refresh:{show:false},
-					// 紧凑模式
-					compact:{show:false},
-					// 导出按钮
-					export:{
-						text: '导出',
-						type: 'primary',
-						size: 'small',
-						icon: 'upload',
-						circle: false,
-						display: true
-					},
-					// 列设置按钮
-					columns:{
-						show:false
-					},
-				}
-			},
-			request: {
-				pageRequest,
-				addRequest,
-				editRequest,
-				delRequest,
-			},
-			actionbar: {
-				buttons: {
-					add: {
-						show: false,//auth('area:Create'),
-					},
-					// 添加批量操作按钮
-					// 添加批量绑定标签按钮
-					/* batchBindTags: {
-						text: '批量绑定标签',
-						type: 'primary',
-						show: true,
-						order: 2,
-						click: () => {
-							// 使用正确的方法获取选中行
-							const selection = context.selectedRows || [];
-							console.log('选中的行:', selection);
-							
-							if (!selection || selection.length === 0) {
-								warningMessage('请先选择要操作的申请');
-								return;
-							}
-							// 打开批量绑定标签对话框
-							context.openBatchTagsDialog(selection);
-						},
-					}, */
-				},
-			},
-			rowHandle: {
-				//固定右侧
-				fixed: 'right',
-				width: 280, // 增加宽度以容纳新按钮
-				buttons: {
-					view: {
-						text: '查看报告',
-						iconRight: 'view',
-						show: false,
-						type: 'text',
-						click: ({ row }) => {
-							// 在新窗口中打开报告详情页面
-							const baseUrl = window.location.origin;
-							const url = `${baseUrl}/#/report?id=${row.id}&tenant_id=${1}&application_id=${row.id}`;
-							window.open(url, '_blank');
-						}
-					},
-					profile: { // 添加查看个人信息按钮
-						text: '查看个人信息',
-						iconRight: 'User',
-						type: 'text',
-						show: true,
-						order: 1,
-						click: ({ row }) => {
-							showUserProfileDialog(row.id);
-						}
-					},
-					edit: {
-						text: '编辑',
-						iconRight: 'Edit',
-						type: 'text',
-						show: false,//auth('area:Update'),
-					},
-					remove: {
-						text: '删除',
-						iconRight: 'Delete',
-						type: 'text',
-						show: false,//auth('area:Delete'),
-					},
-				},
-			},
-			pagination: {
-				show: true,
-			},
-			table: {
-				selection: true,
-				onSelectionChange: (selection: any[]) => {
-					// 存储选中的行到一个全局变量中
-					context.selectedRows = selection;
-				},
-			},
-			search: {
-				show: true,
-				layout: 'auto',
-				buttons: {
-					search: {
-						size: 'small', // 设置查询按钮大小为small
-					},
-					reset: {
-						size: 'small', // 设置重置按钮大小为small
-					}
-				},		
-				resetBtn: {
-					show: true,
-					click: () => {
-						// 重置搜索时,也重置树的选中状态
-						const treeRef = document.querySelector('.el-tree');
-						if (treeRef) {
-							// 尝试清除当前高亮
-							const highlightNode = treeRef.querySelector('.is-current');
-							if (highlightNode) {
-								highlightNode.classList.remove('is-current');
-							}
-						}
-					}
-				}
-			},
-			columns: {
-				_selection: {
-					title: '选择',
-					form: { show: false },
-					column: {
-						type: 'selection',
-						align: 'center',
-						width: 50,
-						fixed: 'left',
-						columnSetDisabled: true,
-					},
-				},
-				id: {
-					title: 'ID',
-					search: {
-						show: false,
-						component: {
-							placeholder: '请输入ID',
-						},
-					},
-					type: 'number',
-					column: {
-						width: 80,
-					},
-					form: {
-						show: false,
-					},
-				},
-				name: {
-					title: '姓名',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入姓名',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.name || row.nikename || '未填写';
-						}
-					},
-				},
-				phone: {
-					title: '电话',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入电话',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-						formatter: ({ row }) => {
-							return row.phone || '未填写';
-						}
-					},
-				},
-				gender_name: {
-					title: '性别',
-					search: {
-						show: true,
-						component: {
-							name: 'el-select',
-							options: [
-								{ value: '男', label: '男' },
-								{ value: '女', label: '女' },
-								{ value: '未知', label: '未知' },
-							]
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 80,
-					},
-				},
-				age: {
-					title: '年龄',
-					type: 'number',
-					column: {
-						minWidth: 80,
-						formatter: ({ row }) => {
-							return row.age || '未填写';
-						}
-					},
-				},
-				/* 'profile_summary.political_status': {
-					title: '政治面貌',
-					type: 'input',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.profile_summary?.political_status || '未填写';
-						}
-					},
-				},
-				'profile_summary.ethnicity': {
-					title: '民族',
-					type: 'input',
-					column: {
-						minWidth: 80,
-						formatter: ({ row }) => {
-							return row.profile_summary?.ethnicity || '未填写';
-						}
-					},
-				}, 
-				'profile_summary.marital_status_name': {
-					title: '婚姻状况',
-					type: 'input',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.profile_summary?.marital_status_name || '未填写';
-						}
-					},
-				},*/
-				'profile_summary.expected_salary': {
-					title: '期望薪资',
-					type: 'number',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.profile_summary?.expected_salary ? `${row.profile_summary.expected_salary}元` : '未填写';
-						}
-					},
-				},
-				'application_summary.latest_position': {
-					title: '最近申请职位',
-					search: {
-						show: true,
-						component: {
-							placeholder: '请输入职位',
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 120,
-						formatter: ({ row }) => {
-							return row.application_summary?.latest_position || '未填写';
-						}
-					},
-				},
-				'application_summary.latest_status': {
-					title: '申请状态',
-					search: {
-						show: true,
-						component: {
-							name: 'el-select',
-							options: [
-								{ value: '待面试', label: '待面试' },
-								{ value: '已面试', label: '已面试' },
-								{ value: '已录用', label: '已录用' },
-								{ value: '已拒绝', label: '已拒绝' },
-							]
-						},
-						size: 'small',
-						col:{ span:3},
-					},
-					type: 'input',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.application_summary?.latest_status || '未填写';
-						}
-					},
-				},
-				'application_summary.latest_date': {
-					title: '申请日期',
-					type: 'input',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.application_summary?.latest_date || '未填写';
-						}
-					},
-				},
-				/*'application_summary.highest_score': {
-					title: '最高评分',
-					type: 'number',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.application_summary?.highest_score || '未评分';
-						}
-					},
-				},
-				'application_summary.total_count': {
-					title: '申请次数',
-					type: 'number',
-					column: {
-						minWidth: 100,
-						formatter: ({ row }) => {
-							return row.application_summary?.total_count || 0;
-						}
-					},
-				},
-				 status_name: {
-					title: '账号状态',
-					type: 'input',
-					column: {
-						minWidth: 100,
-					},
-				},
-				create_datetime: {
-					title: '创建时间',
-					type: 'datetime',
-					column: {
-						minWidth: 160,
-					},
-					form: {
-						show: false,
-					},
-				}, */
-			},
-			
-		},
-	};
-};

+ 0 - 379
src/views/talent/list/index.vue

@@ -1,379 +0,0 @@
-<template>
-	<fs-page>
-		<div class="job-application-container">
-			<div class="sidebar">
-				<div class="tree-container">
-					<!-- 全部 -->
-					<div class="tree-item" :class="{ active: activeNode === 'all' }" @click="handleNodeClick({ type: 'all' })">
-						<div class="item-content">
-							<el-icon><Grid /></el-icon>
-							<span>全部</span>
-						</div>
-						<div class="item-count">{{ totalCount }}人</div>
-					</div>
-					
-					<!-- 待安排 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-1' }" @click="handleNodeClick({ type: 'status', value: 0, id: 'status-1' })">
-						<div class="item-content">
-							<el-icon><Clock /></el-icon>
-							<span>待面试</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[1]">{{ statusCounts[1] }}</div>
-					</div>
-					
-					<!-- 推进中 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-2' }" @click="handleNodeClick({ type: 'status', value: 2, id: 'status-2' })">
-						<div class="item-content">
-							<el-icon><ArrowRight /></el-icon>
-							<span>已面试</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[2]">{{ statusCounts[2] }}</div>
-					</div>
-					
-					<!-- 已入职 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-3' }" @click="handleNodeClick({ type: 'status', value: 3, id: 'status-3' })">
-						<div class="item-content">
-							<el-icon><Check /></el-icon>
-							<span>已录用</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[3]">{{ statusCounts[3] }}</div>
-					</div>
-					
-					<!-- 待召回 -->
-					<div class="tree-item" :class="{ active: activeNode === 'status-4' }" @click="handleNodeClick({ type: 'status', value: 4, id: 'status-4' })">
-						<div class="item-content">
-							<el-icon><RefreshRight /></el-icon>
-							<span>已拒绝</span>
-						</div>
-						<div class="item-count" v-if="statusCounts[4]">{{ statusCounts[4] }}</div>
-					</div>
-					
-					<!-- 职位分类 -->
-					<div class="category-title" @click="togglePositionList">
-						<el-icon><Briefcase /></el-icon>
-						<span>职位</span>
-						<el-icon class="expand-icon" :class="{ 'is-expanded': showPositionList }">
-							<ArrowRight />
-						</el-icon>
-					</div>
-					
-					<!-- 职位列表 -->
-					<div v-if="showPositionList" class="position-list">
-						<!-- 全部职位 -->
-						<div class="tree-item" :class="{ active: activeNode === 'position-all' }" @click="handleNodeClick({ type: 'all', id: 'position-all' })">
-							<div class="item-content">
-								<span>全部</span>
-							</div>
-						</div>
-						
-						<!-- 职位列表项 -->
-						<div 
-							v-for="position in positions" 
-							:key="'position-' + position.id" 
-							class="tree-item" 
-							:class="{ active: activeNode === 'position-' + position.id }"
-							@click="handleNodeClick({ type: 'position', value: position.title, id: 'position-' + position.id })"
-						>
-							<div class="item-content">
-								<el-icon><ArrowRight /></el-icon>
-								<span>{{ position.title }}</span>
-							</div>
-							<div class="item-count" v-if="position.count">{{ position.count }}</div>
-						</div>
-					</div>
-				</div>
-			</div>
-			<div class="content">
-				<fs-crud ref="crudRef" v-bind="crudBinding">
-					<!-- 可以添加自定义插槽,如果需要 -->
-				</fs-crud>
-			</div>
-		</div>
-		<BatchTagsDialog ref="batchTagsDialogRef" :crudExpose="crudExpose" />
-		<!-- <BatchStatusDialog ref="batchStatusDialogRef" :crudExpose="crudExpose" @success="handleBatchStatusSuccess" /> -->
-	</fs-page>
-</template>
-
-<script lang="ts" setup name="areas">
-import { onMounted, ref, reactive } from 'vue';
-import { useFs } from '@fast-crud/fast-crud';
-import { createCrudOptions } from './crud';
-import { GetPermission } from './api';
-import { handleColumnPermission } from '/@/utils/columnPermission';
-import { Grid, Clock, ArrowRight, Check, RefreshRight, Briefcase } from '@element-plus/icons-vue';
-import BatchTagsDialog from './components/index.vue';
-/* import BatchStatusDialog from './components/BatchStatusDialog.vue'; */
-import { getApplicationStatusSummary } from './api';
-
-const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ 
-	createCrudOptions,
-	context: {
-		openBatchTagsDialog: (selection: any) => {
-			batchTagsDialogRef.value.open(selection);
-		},
-		openBatchStatusDialog: (selection: any) => {
-			batchStatusDialogRef.value.open(selection);
-		},
-		selectedRows: [] // 存储选中的行
-	}
-});
-
-const batchTagsDialogRef = ref();
-const batchStatusDialogRef = ref();
-
-// 状态计数
-const totalCount = ref(0);
-const statusCounts = reactive<Record<number, number>>({
-	1: 0, // 待面试
-	2: 0, // 已面试
-	3: 0, // 已录用
-	4: 0  // 已拒绝
-});
-
-// 职位列表
-const showPositionList = ref(true);
-const positions = ref<Array<{id: number|string, title: string, count?: number}>>([]);
-
-// 获取职位列表
-const fetchPositions = async () => {
-	try {
-		const res = await fetch(`${import.meta.env.VITE_API_URL}/api/system/job/list?page=1&limit=100&tenant_id=1`);
-		const data = await res.json();
-		
-		if (data.code === 2000 && data.data ) {
-			positions.value = data.data.map((item: any) => ({
-				id: item.id,
-				title: item.title || item.name || item.job_name,
-				count: 0
-			}));
-		} else {
-			console.error('获取职位列表失败:', data.msg || '未知错误');
-		}
-	} catch (error) {
-		console.error('获取职位列表异常:', error);
-		// 设置默认职位,以防接口失败
-		positions.value = [{ id: 1, title: '流水线操作工', count: 0 }];
-	}
-};
-
-// 获取申请状态统计数据
-const fetchStatusSummary = async () => {
-	try {
-		const res = await getApplicationStatusSummary();
-		const data = res;
-		console.log(data)
-		if (data.code === 2000 && data.data) {
-			totalCount.value = data.data.total || 0;
-			
-			// 更新状态计数
-			if (data.data.status_data && Array.isArray(data.data.status_data)) {
-				data.data.status_data.forEach((item: any) => {
-					// 注意:API返回的status可能与前端定义的不完全一致,需要映射
-					const statusMap: Record<number, number> = {
-						0: 1, // API中的0对应前端的1(待面试)
-						2: 2, // 已面试
-						3: 3, // 已录用
-						4: 4  // 已拒绝
-					};
-					
-					const frontendStatus = statusMap[item.status];
-					if (frontendStatus && frontendStatus in statusCounts) {
-						statusCounts[frontendStatus] = item.count;
-					}
-				});
-			} else {
-				// 如果没有status_data,则使用单独的字段
-				statusCounts[1] = data.data.pending || 0;
-				statusCounts[2] = data.data.interviewed || 0;
-				statusCounts[3] = data.data.hired || 0;
-				statusCounts[4] = data.data.rejected || 0;
-			}
-		} else {
-			console.error('获取申请状态统计失败:', data.message || '未知错误');
-		}
-	} catch (error) {
-		console.error('获取申请状态统计异常:', error);
-	}
-};
-
-// 切换职位列表显示
-const togglePositionList = () => {
-	showPositionList.value = !showPositionList.value;
-};
-
-// 当前激活的节点
-const activeNode = ref('all');
-
-// 处理树节点点击
-const handleNodeClick = (data: any) => {
-	activeNode.value = data.id || 'all';
-	
-	if (data.type === 'all') {
-		try {
-			// 尝试重置表单,如果方法存在的话
-			const searchRef = crudExpose.getSearchRef();
-			if (searchRef && typeof searchRef.resetFields === 'function') {
-				searchRef.resetFields();
-			}
-		} catch (error) {
-			console.warn('重置表单失败:', error);
-		}
-		
-		// 无论如何都执行搜索,确保数据刷新
-		crudExpose.doSearch({
-			form: {}
-		});
-	} else if (data.type === 'status') {
-		// 按状态筛选
-		crudExpose.doSearch({
-			form: {
-				status: data.value
-			}
-		});
-	} else if (data.type === 'position') {
-		// 按职位筛选
-		crudExpose.doSearch({
-			form: {
-				position_title: data.value
-			}
-		});
-	}
-};
-
-// 更新计数
-const updateCounts = (data: any) => {
-	if (!data || !data.records) return;
-	
-	// 统计各职位数量
-	// 重置职位计数
-	positions.value.forEach(position => {
-		position.count = 0;
-	});
-	
-	// 统计职位数量
-	data.records.forEach((record: any) => {
-		if (record.position_title) {
-			const position = positions.value.find(p => p.title === record.position_title);
-			if (position) {
-				position.count = (position.count || 0) + 1;
-			}
-		}
-	});
-};
-
-// 处理批量状态修改成功
-const handleBatchStatusSuccess = () => {
-	// 刷新数据和状态统计
-	crudExpose.doRefresh();
-	fetchStatusSummary();
-};
-
-// 页面打开后获取列表数据
-onMounted(async () => {
-	// 获取职位列表
-	await fetchPositions();
-	
-	// 获取申请状态统计
-	await fetchStatusSummary();
-	
-	// 设置列权限
-	const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-	
-	// 添加数据加载后的回调,用于更新职位计数
-	if (newOptions && newOptions.crudOptions && newOptions.crudOptions.request) {
-		const originalPageRequest = newOptions.crudOptions.request.pageRequest;
-		newOptions.crudOptions.request.pageRequest = async (query: any) => {
-			const res = await originalPageRequest(query);
-			updateCounts(res.data);
-			return res;
-		};
-	}
-	
-	// 重置crudBinding
-	resetCrudOptions(newOptions);
-	
-	// 刷新
-	crudExpose.doRefresh();
-});
-</script>
-
-<style scoped>
-.job-application-container {
-	display: flex;
-	height: 100%;
-}
-
-.sidebar {
-	margin-top: 10px;
-	width: 200px;
-	margin-right: 5px;
-	flex-shrink: 0;
-	background-color: #fff;
-	border-right: 1px solid #ebeef5;
-	border-radius: 10px;
-}
-
-.tree-container {
-	padding: 8px 0;
-}
-
-.tree-item {
-	display: flex;
-	justify-content: space-between;
-	align-items: center;
-	padding: 10px 16px;
-	cursor: pointer;
-	transition: background-color 0.3s;
-}
-
-.tree-item:hover {
-	background-color: #f5f7fa;
-}
-
-.tree-item.active {
-	background-color: #f0f7ff;
-	color: #409eff;
-	border-right: 2px solid #409eff;
-}
-
-.item-content {
-	display: flex;
-	align-items: center;
-	gap: 8px;
-}
-
-.item-count {
-	font-size: 12px;
-	color: #909399;
-}
-
-.category-title {
-	display: flex;
-	align-items: center;
-	padding: 12px 16px;
-	margin-top: 8px;
-	font-weight: 500;
-	border-top: 1px solid #ebeef5;
-	border-bottom: 1px solid #ebeef5;
-	gap: 8px;
-	cursor: pointer;
-}
-
-.expand-icon {
-	margin-left: auto;
-	transition: transform 0.3s;
-}
-
-.expand-icon.is-expanded {
-	transform: rotate(90deg);
-}
-
-.position-list {
-	padding-left: 8px;
-}
-
-.content {
-	flex: 1;
-	overflow: hidden;
-}
-</style>