yangg пре 2 месеци
родитељ
комит
f34291bee3

+ 2 - 2
.env.development

@@ -1,8 +1,8 @@
 # 本地环境
 ENV = 'development'
-
+#https://minlong.raycos.com.cn
 # 本地环境接口地址 121.36.251.245
-VITE_API_URL = 'https://minlong.raycos.com.cn'
+VITE_API_URL = 'http://192.168.66.187:8083'
 
 # 是否启用按钮权限
 VITE_PM_ENABLED = true

+ 178 - 0
package-lock.json

@@ -19,6 +19,7 @@
 				"@vitejs/plugin-vue-jsx": "^4.0.1",
 				"@wangeditor/editor": "^5.1.23",
 				"@wangeditor/editor-for-vue": "^5.1.12",
+				"ant-design-vue": "^4.2.6",
 				"autoprefixer": "^10.4.20",
 				"axios": "^1.7.4",
 				"bootstrap": "^5.3.5",
@@ -105,6 +106,31 @@
 				"node": ">=6.0.0"
 			}
 		},
+		"node_modules/@ant-design/colors": {
+			"version": "6.0.0",
+			"resolved": "https://registry.npmmirror.com/@ant-design/colors/-/colors-6.0.0.tgz",
+			"integrity": "sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==",
+			"dependencies": {
+				"@ctrl/tinycolor": "^3.4.0"
+			}
+		},
+		"node_modules/@ant-design/icons-svg": {
+			"version": "4.4.2",
+			"resolved": "https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz",
+			"integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA=="
+		},
+		"node_modules/@ant-design/icons-vue": {
+			"version": "7.0.1",
+			"resolved": "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-7.0.1.tgz",
+			"integrity": "sha512-eCqY2unfZK6Fe02AwFlDHLfoyEFreP6rBwAZMIJ1LugmfMiVgwWDYlp1YsRugaPtICYOabV1iWxXdP12u9U43Q==",
+			"dependencies": {
+				"@ant-design/colors": "^6.0.0",
+				"@ant-design/icons-svg": "^4.2.1"
+			},
+			"peerDependencies": {
+				"vue": ">=3.0.3"
+			}
+		},
 		"node_modules/@aws-crypto/crc32": {
 			"version": "5.2.0",
 			"resolved": "https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz",
@@ -1395,6 +1421,16 @@
 				"vue": "^3.2.0"
 			}
 		},
+		"node_modules/@emotion/hash": {
+			"version": "0.9.2",
+			"resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz",
+			"integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="
+		},
+		"node_modules/@emotion/unitless": {
+			"version": "0.8.1",
+			"resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz",
+			"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+		},
 		"node_modules/@esbuild/aix-ppc64": {
 			"version": "0.21.5",
 			"resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@@ -3033,6 +3069,25 @@
 				"win32"
 			]
 		},
+		"node_modules/@simonwep/pickr": {
+			"version": "1.8.2",
+			"resolved": "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.8.2.tgz",
+			"integrity": "sha512-/l5w8BIkrpP6n1xsetx9MWPWlU6OblN5YgZZphxan0Tq4BByTCETL6lyIeY8lagalS2Nbt4F2W034KHLIiunKA==",
+			"dependencies": {
+				"core-js": "^3.15.1",
+				"nanopop": "^2.1.0"
+			}
+		},
+		"node_modules/@simonwep/pickr/node_modules/core-js": {
+			"version": "3.41.0",
+			"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.41.0.tgz",
+			"integrity": "sha512-SJ4/EHwS36QMJd6h/Rg+GyR4A5xE0FSI3eZ+iBVpfqf1x0eTSg1smWLHrA+2jQThZSh97fmSgFSU8B61nxosxA==",
+			"hasInstallScript": true,
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/core-js"
+			}
+		},
 		"node_modules/@smithy/abort-controller": {
 			"version": "4.0.1",
 			"resolved": "https://registry.npmmirror.com/@smithy/abort-controller/-/abort-controller-4.0.1.tgz",
@@ -4527,6 +4582,45 @@
 				"url": "https://github.com/chalk/ansi-styles?sponsor=1"
 			}
 		},
+		"node_modules/ant-design-vue": {
+			"version": "4.2.6",
+			"resolved": "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-4.2.6.tgz",
+			"integrity": "sha512-t7eX13Yj3i9+i5g9lqFyYneoIb3OzTvQjq9Tts1i+eiOd3Eva/6GagxBSXM1fOCjqemIu0FYVE1ByZ/38epR3Q==",
+			"dependencies": {
+				"@ant-design/colors": "^6.0.0",
+				"@ant-design/icons-vue": "^7.0.0",
+				"@babel/runtime": "^7.10.5",
+				"@ctrl/tinycolor": "^3.5.0",
+				"@emotion/hash": "^0.9.0",
+				"@emotion/unitless": "^0.8.0",
+				"@simonwep/pickr": "~1.8.0",
+				"array-tree-filter": "^2.1.0",
+				"async-validator": "^4.0.0",
+				"csstype": "^3.1.1",
+				"dayjs": "^1.10.5",
+				"dom-align": "^1.12.1",
+				"dom-scroll-into-view": "^2.0.0",
+				"lodash": "^4.17.21",
+				"lodash-es": "^4.17.15",
+				"resize-observer-polyfill": "^1.5.1",
+				"scroll-into-view-if-needed": "^2.2.25",
+				"shallow-equal": "^1.0.0",
+				"stylis": "^4.1.3",
+				"throttle-debounce": "^5.0.0",
+				"vue-types": "^3.0.0",
+				"warning": "^4.0.0"
+			},
+			"engines": {
+				"node": ">=12.22.0"
+			},
+			"funding": {
+				"type": "opencollective",
+				"url": "https://opencollective.com/ant-design-vue"
+			},
+			"peerDependencies": {
+				"vue": ">=3.2.0"
+			}
+		},
 		"node_modules/any-promise": {
 			"version": "1.3.0",
 			"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
@@ -4555,6 +4649,11 @@
 			"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
 			"dev": true
 		},
+		"node_modules/array-tree-filter": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz",
+			"integrity": "sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw=="
+		},
 		"node_modules/array-union": {
 			"version": "2.1.0",
 			"resolved": "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz",
@@ -5502,6 +5601,16 @@
 			"resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
 			"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
 		},
+		"node_modules/dom-align": {
+			"version": "1.12.4",
+			"resolved": "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz",
+			"integrity": "sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw=="
+		},
+		"node_modules/dom-scroll-into-view": {
+			"version": "2.0.1",
+			"resolved": "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz",
+			"integrity": "sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w=="
+		},
 		"node_modules/dom-zindex": {
 			"version": "1.0.6",
 			"resolved": "https://registry.npmmirror.com/dom-zindex/-/dom-zindex-1.0.6.tgz",
@@ -7343,6 +7452,17 @@
 			"resolved": "https://registry.npmmirror.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
 			"integrity": "sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw=="
 		},
+		"node_modules/loose-envify": {
+			"version": "1.4.0",
+			"resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
+			"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+			"dependencies": {
+				"js-tokens": "^3.0.0 || ^4.0.0"
+			},
+			"bin": {
+				"loose-envify": "cli.js"
+			}
+		},
 		"node_modules/lru-cache": {
 			"version": "10.4.3",
 			"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
@@ -7683,6 +7803,11 @@
 				"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
 			}
 		},
+		"node_modules/nanopop": {
+			"version": "2.4.2",
+			"resolved": "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz",
+			"integrity": "sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw=="
+		},
 		"node_modules/natural-compare": {
 			"version": "1.4.0",
 			"resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -8548,6 +8673,11 @@
 			"resolved": "https://registry.npmmirror.com/reinterval/-/reinterval-1.1.0.tgz",
 			"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
 		},
+		"node_modules/resize-observer-polyfill": {
+			"version": "1.5.1",
+			"resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+			"integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+		},
 		"node_modules/resolve": {
 			"version": "1.22.10",
 			"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
@@ -8777,6 +8907,11 @@
 				"node": ">= 0.4"
 			}
 		},
+		"node_modules/shallow-equal": {
+			"version": "1.2.1",
+			"resolved": "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz",
+			"integrity": "sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA=="
+		},
 		"node_modules/shebang-command": {
 			"version": "2.0.0",
 			"resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -9153,6 +9288,11 @@
 			"resolved": "https://registry.npmmirror.com/strnum/-/strnum-1.0.5.tgz",
 			"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="
 		},
+		"node_modules/stylis": {
+			"version": "4.3.6",
+			"resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz",
+			"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="
+		},
 		"node_modules/sucrase": {
 			"version": "3.35.0",
 			"resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz",
@@ -9350,6 +9490,14 @@
 				"node": ">=0.8"
 			}
 		},
+		"node_modules/throttle-debounce": {
+			"version": "5.0.2",
+			"resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz",
+			"integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==",
+			"engines": {
+				"node": ">=12.22"
+			}
+		},
 		"node_modules/through": {
 			"version": "2.3.8",
 			"resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz",
@@ -9982,6 +10130,28 @@
 				"vue": "^3.2.0"
 			}
 		},
+		"node_modules/vue-types": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmmirror.com/vue-types/-/vue-types-3.0.2.tgz",
+			"integrity": "sha512-IwUC0Aq2zwaXqy74h4WCvFCUtoV0iSWr0snWnE9TnU18S66GAQyqQbRf2qfJtUuiFsBf6qp0MEwdonlwznlcrw==",
+			"dependencies": {
+				"is-plain-object": "3.0.1"
+			},
+			"engines": {
+				"node": ">=10.15.0"
+			},
+			"peerDependencies": {
+				"vue": "^3.0.0"
+			}
+		},
+		"node_modules/vue-types/node_modules/is-plain-object": {
+			"version": "3.0.1",
+			"resolved": "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-3.0.1.tgz",
+			"integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==",
+			"engines": {
+				"node": ">=0.10.0"
+			}
+		},
 		"node_modules/vuedraggable-es": {
 			"version": "4.1.1",
 			"resolved": "https://registry.npmmirror.com/vuedraggable-es/-/vuedraggable-es-4.1.1.tgz",
@@ -10014,6 +10184,14 @@
 				"vxe-pc-ui": "^4.3.83"
 			}
 		},
+		"node_modules/warning": {
+			"version": "4.0.3",
+			"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
+			"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+			"dependencies": {
+				"loose-envify": "^1.0.0"
+			}
+		},
 		"node_modules/webpack-virtual-modules": {
 			"version": "0.6.2",
 			"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",

+ 1 - 0
package.json

@@ -20,6 +20,7 @@
 		"@vitejs/plugin-vue-jsx": "^4.0.1",
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",
+		"ant-design-vue": "^4.2.6",
 		"autoprefixer": "^10.4.20",
 		"axios": "^1.7.4",
 		"bootstrap": "^5.3.5",

+ 9 - 9
src/App.vue

@@ -109,15 +109,15 @@ const wsReceive = (message: any) => {
 	const { unread } = data;
 	const messageCenter = messageCenterStore();
 	messageCenter.setUnread(unread);
-	if (data.contentType === 'SYSTEM') {
-		ElNotification({
-			title: '系统消息',
-			message: data.content,
-			type: 'success',
-			position: 'bottom-right',
-			duration: 5000,
-		});
-	}
+	// if (data.contentType === 'SYSTEM') {
+	// 	ElNotification({
+	// 		title: '系统消息',
+	// 		message: data.content,
+	// 		type: 'success',
+	// 		position: 'bottom-right',
+	// 		duration: 5000,
+	// 	});
+	// }
 };
 onBeforeUnmount(() => {
 	// 关闭连接

+ 8 - 0
src/router/route.ts

@@ -95,5 +95,13 @@ export const staticRoutes: Array<RouteRecordRaw> = [
 		meta: {
 			title: 'message.router.personal'
 		},
+	},
+	{
+		path: '/report',
+		name: 'report',
+		component: () => import('/@/views/JobApplication/report/index.vue'),
+		meta: {
+			title: '申请报告'
+		},
 	}
 ];

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

@@ -0,0 +1,47 @@
+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: 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: apiPrefix + id + '/',
+		method: 'delete',
+		data: { id },
+	});
+}
+export function GetPermission() {
+    return request({
+        url: apiPrefix,
+        method: 'get',
+    });
+}

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

@@ -0,0 +1,257 @@
+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(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: {
+			request: {
+				pageRequest,
+				addRequest,
+				editRequest,
+				delRequest,
+			},
+			actionbar: {
+				buttons: {
+					add: {
+						show: false,//auth('area:Create'),
+					},
+				},
+			},
+			rowHandle: {
+				//固定右侧
+				fixed: 'right',
+				width: 200,
+				buttons: {
+					view: {
+						iconRight: 'view',
+						show: true,
+						type: 'text',
+						click: ({ row }) => {
+							// 在新窗口中打开报告详情页面,确保使用正确的哈希路由格式
+							const baseUrl = window.location.origin;
+							const url = `${baseUrl}/#/report?id=${row.id}`;
+							window.open(url, '_blank');
+						}
+					},
+					edit: {
+						iconRight: 'Edit',
+						type: 'text',
+						show: false,//auth('area:Update'),
+					},
+					remove: {
+						iconRight: 'Delete',
+						type: 'text',
+						show: false,//auth('area:Delete'),
+					},
+				},
+			},
+			pagination: {
+				show: true,
+			},
+			table: {
+				rowKey: 'id',
+			},
+			search: {
+				show: true,
+				layout: 'auto',
+			},
+			columns: {
+				_index: {
+					title: '序号',
+					form: { show: false },
+					column: {
+						type: 'index',
+						align: 'center',
+						width: '70px',
+						columnSetDisabled: true, //禁止在列设置中选择
+					},
+				},
+				report_id: {
+					title: '报告编号',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入报告编号',
+						},
+					},
+					type: 'input',
+					column: {
+						minWidth: 150,
+					},
+					form: {
+						rules: [
+							{ required: true, message: '报告编号必填项' },
+						],
+					},
+				},
+				applicant_name: {
+					title: '申请人姓名',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入申请人姓名',
+						},
+					},
+					type: 'input',
+					column: {
+						minWidth: 120,
+					},
+					form: {
+						rules: [
+							{ required: true, message: '申请人姓名必填项' },
+						],
+					},
+				},
+				applicant_phone: {
+					title: '申请人电话',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入申请人电话',
+						},
+					},
+					type: 'input',
+					column: {
+						minWidth: 130,
+					},
+					form: {
+						rules: [
+							{ required: true, message: '申请人电话必填项' },
+						],
+					},
+				},
+				position_title: {
+					title: '申请职位',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入申请职位',
+						},
+					},
+					type: 'input',
+					column: {
+						minWidth: 120,
+					},
+					form: {
+						rules: [
+							{ required: true, message: '申请职位必填项' },
+						],
+					},
+				},
+				status: {
+					title: '申请状态',
+					search: {
+						show: true,
+						component: {
+							name: 'el-select',
+							placeholder: '请选择申请状态',
+						},
+					},
+					type: 'dict-select',
+					column: {
+						minWidth: 100,
+					},
+					dict: dict({
+						data: [
+							{ 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,
+					},
+				},
+			},
+		},
+	};
+};

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

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

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

@@ -0,0 +1,701 @@
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { message } from 'ant-design-vue'
+import type { FormInstance } from 'ant-design-vue'
+
+interface CandidateInfo {
+  name: string
+  idNumber: string
+  phoneNumber: string
+  score: number
+  availabilityPeriod: string
+  onboardingTime: 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
+  }[]
+  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[]
+    }
+  }
+}
+
+const candidateInfo = ref<CandidateInfo>({
+  name: '孙壁涵',
+  idNumber: '152201199809150015',
+  phoneNumber: '19943770171',
+  score: 71,
+  availabilityPeriod: '全天任意时间段',
+  onboardingTime: '1周内',
+  dimensions: {
+    teamwork: '欠佳',
+    learningAbility: '优秀',
+    attention: '欠佳',
+    workAdaptability: '优秀',
+    serviceAwareness: '中等'
+  },
+  dimensionDetails: {
+    teamwork: '主动交流但缺乏倾听与尊重。',
+    learningAbility: '积极向上,具备优秀的学习意愿。',
+    attention: '对细节处理不够严谨,需提高标准意识。',
+    workAdaptability: '快速适应,表现出强烈的责任心。',
+    serviceAwareness: '关注客户需求,但缺乏提升顾客体验的具体措施。'
+  },
+  duvAnalysis: [
+    {
+      title: '工作经验',
+      content: '有相关工作经验,能够胜任岗位要求',
+      score: '优秀',
+      type: 'positive'
+    },
+    {
+      title: '学历背景',
+      content: '学历符合岗位要求',
+      score: '良好',
+      type: 'positive'
+    }
+  ],
+  interviewRecord: [
+    {
+      question: '请问您对餐饮服务工作有什么理解?',
+      answer: '餐饮服务工作需要具备良好的服务意识和沟通能力,要以客户满意为目标,及时响应客户需求,保持微笑和耐心。同时要注意个人卫生和仪容仪表,遵守餐饮服务规范。',
+      analysis: '答案表现出对岗位的基本理解,但缺乏深入思考',
+      score: '中等',
+      videoUrl: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
+      thumbnail: '/images/video-placeholder.jpg'
+    },
+    {
+      question: '您如何处理顾客投诉?',
+      answer: '首先我会认真倾听顾客的意见,表示理解和歉意。然后了解具体情况,及时向主管报告,并根据公司规定提供合适的解决方案。最后会总结经验,避免类似问题再次发生。',
+      analysis: '回答显示了基本的服务意识,但处理方法较为简单',
+      score: '良好',
+      videoUrl: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
+      thumbnail: '/images/video-placeholder.jpg'
+    }
+  ],
+  videoRecords: [
+    {
+      category: '服务态度测评',
+      videos: [
+        {
+          url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
+          thumbnail: '/images/thumbnail1.jpg',
+          description: '微笑服务示范'
+        },
+        {
+          url: 'https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4',
+          thumbnail: '/images/thumbnail2.jpg',
+          description: '礼貌用语展示'
+        }
+      ]
+    }
+  ],
+  additionalInfo: {
+    idVerification: '通过',
+    phoneVerification: '通过',
+    leftHandGestures: {
+      passed: true,
+      images: [
+        '/images/left-hand-1.jpg',
+        '/images/left-hand-2.jpg',
+        '/images/left-hand-3.jpg'
+      ]
+    },
+    rightHandGestures: {
+      passed: true,
+      images: [
+        '/images/right-hand-1.jpg',
+        '/images/right-hand-2.jpg',
+        '/images/right-hand-3.jpg'
+      ]
+    },
+    faceVerification: {
+      passed: true,
+      images: [
+        '/images/face-1.jpg',
+        '/images/face-2.jpg',
+        '/images/face-3.jpg',
+        '/images/face-4.jpg',
+        '/images/face-5.jpg',
+        '/images/face-6.jpg'
+      ]
+    }
+  }
+})
+
+const apiData = ref<any>(null)
+const loading = ref<boolean>(true)
+const error = ref<string>('')
+
+const fetchApplicationDetail = async () => {
+  loading.value = true
+  try {
+    const urlParams = new URLSearchParams(window.location.search)
+    const id = urlParams.get('id') || '1'
+    const tenant_id = urlParams.get('tenant_id') || '1'
+    const application_id = urlParams.get('application_id') || '1'
+    
+    const response = await fetch(`${import.meta.env.VITE_API_URL}/api/job/application_detail?id=${id}&tenant_id=${tenant_id}&application_id=${application_id}`)
+    const result = await response.json()
+    
+    if (result.code === 2000) {
+      apiData.value = 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
+  
+  if (application.comprehensive_score !== null) {
+    candidateInfo.value.score = application.comprehensive_score
+  }
+  
+  if (application.ai_capability_scores) {
+    const dimensionMapping: Record<string, keyof typeof candidateInfo.value.dimensions> = {
+      '专业性': 'workAdaptability',
+      '沟通能力': 'teamwork',
+      '技术匹配度': 'learningAbility',
+      '解决问题能力': 'attention'
+    }
+    
+    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
+      }
+    })
+  }
+  
+  if (interview_progress && interview_progress.length > 0) {
+    candidateInfo.value.interviewRecord = interview_progress
+      .filter(q => q.video_answer)
+      .map(q => ({
+        question: q.question_text,
+        answer: q.video_answer?.transcript || '未提供回答',
+        analysis: '面试官正在评估中',
+        score: '评估中',
+        videoUrl: q.video_answer?.video_url || '',
+        thumbnail: '/images/video-placeholder.jpg'
+      }))
+  }
+  
+  if (posture_photos && posture_photos.length > 0) {
+    const leftHandPhotos = posture_photos.filter(p => 
+      p.description.includes('left_') || p.description.includes('左手')
+    ).map(p => p.photo_url)
+    
+    if (leftHandPhotos.length > 0) {
+      candidateInfo.value.additionalInfo.leftHandGestures.images = leftHandPhotos
+    }
+    
+    const rightHandPhotos = posture_photos.filter(p => 
+      p.description.includes('right_') || p.description.includes('右手')
+    ).map(p => p.photo_url)
+    
+    if (rightHandPhotos.length > 0) {
+      candidateInfo.value.additionalInfo.rightHandGestures.images = rightHandPhotos
+    }
+    
+    const facePhotos = posture_photos.filter(p => 
+      !p.description.includes('left_') && 
+      !p.description.includes('right_') &&
+      !p.description.includes('左手') && 
+      !p.description.includes('右手')
+    ).map(p => p.photo_url)
+    
+    if (facePhotos.length > 0) {
+      candidateInfo.value.additionalInfo.faceVerification.images = facePhotos
+    }
+  }
+  
+  if (application.visual_analysis_results && application.visual_analysis_results.detections) {
+    candidateInfo.value.duvAnalysis = application.visual_analysis_results.detections.map((detection: any) => ({
+      title: detection.feature,
+      content: `在${detection.location}发现${detection.feature}`,
+      score: detection.confidence >= 0.8 ? '确认' : '疑似',
+      type: 'neutral'
+    }))
+  }
+}
+
+onMounted(() => {
+  fetchApplicationDetail()
+})
+
+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 scrollToTop = () => {
+  window.scrollTo({
+    top: 0,
+    behavior: 'smooth'
+  })
+}
+
+const handleShare = () => {
+  message.success('分享链接已复制')
+}
+
+const handleDownload = () => {
+  message.success('报告下载中...')
+}
+
+const videoStyle = {
+  width: '100%',
+  maxWidth: '240px',
+  aspectRatio: '3/4',
+  margin: '0 auto',
+  background: '#000',
+  borderRadius: '12px',
+  overflow: 'hidden'
+}
+</script>
+
+<template>
+  <div class="max-w-4xl mx-auto p-6 relative overflow-y-auto h-full">
+    <!-- 加载状态 -->
+    <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?.applicant?.id || candidateInfo.idNumber }}</div>
+          <div class="text-sm">{{ 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="e => e.target.src = '/images/avatar-placeholder.jpg'"
+              >
+            </div>
+            <div>
+              <h1 class="text-2xl font-bold">{{ candidateInfo.name }}</h1>
+              <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>
+
+        <!-- AI维度分析 -->
+        <div class="mb-8">
+          <h2 class="text-xl font-bold mb-6">1. AI维度分析</h2>
+          <div class="space-y-6">
+            <div v-for="(value, key) in candidateInfo.dimensions" :key="key" class="border-b pb-4">
+              <div class="flex items-center mb-2">
+                <span class="text-gray-600 w-32">{{ {
+                  teamwork: '团队合作能力',
+                  learningAbility: '学习能力',
+                  attention: '细致严谨',
+                  workAdaptability: '工作适应性',
+                  serviceAwareness: '服务意识'
+                }[key] }}</span>
+                <span class="ml-4" :class="{
+                  'text-red-500': value === '欠佳',
+                  'text-green-500': value === '优秀',
+                  'text-yellow-500': value === '中等'
+                }">{{ value }}</span>
+              </div>
+              <p class="text-gray-600 text-sm">{{ candidateInfo.dimensionDetails[key] }}</p>
+            </div>
+          </div>
+        </div>
+
+        <!-- DUV分析评估 -->
+        <div class="mb-8">
+          <h2 class="text-xl font-bold mb-6">2. DUV分析评估</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 class="mb-8">
+          <h2 class="text-xl font-bold mb-6">3. 面试记录</h2>
+          <div class="space-y-6">
+            <div v-for="(record, index) in candidateInfo.interviewRecord" :key="index" class="border-b pb-4">
+              <div class="mb-2">
+                <h3 class="font-semibold">问题:{{ record.question }}</h3>
+                <p class="text-gray-600 mt-2">答案:{{ record.answer }}</p>
+                <p class="text-gray-500 mt-2 text-sm">分析:{{ record.analysis }}</p>
+                
+                <!-- 视频回放 -->
+                <div v-if="record.videoUrl" class="mt-4">
+                  <div class="video-container">
+                    <video
+                      class="w-full h-full object-cover"
+                      controls
+                      :src="record.videoUrl"
+                      preload="none"
+                      :poster="record.thumbnail"
+                    >
+                      <source :src="record.videoUrl" type="video/mp4">
+                      您的浏览器不支持视频播放。
+                    </video>
+                  </div>
+                </div>
+                
+                <div class="mt-2 flex justify-end">
+                  <span class="text-blue-500">得分:{{ record.score }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 视频记录 -->
+        <div class="mb-8">
+          <h2 class="text-xl font-bold mb-6">4. 视频记录</h2>
+          <div class="space-y-8">
+            <div v-for="(category, index) in candidateInfo.videoRecords" :key="index">
+              <h3 class="text-lg font-semibold mb-4">{{ category.category }}</h3>
+              <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+                <div v-for="(video, vIndex) in category.videos" :key="vIndex" class="bg-gray-50 rounded-lg p-4">
+                  <div class="video-container">
+                    <video
+                      class="w-full h-full object-cover"
+                      controls
+                      :src="video.url"
+                      preload="none"
+                      :poster="video.thumbnail"
+                    >
+                      <source :src="video.url" type="video/mp4">
+                      您的浏览器不支持视频播放。
+                    </video>
+                  </div>
+                  <p class="text-sm text-gray-600 mt-2">{{ video.description }}</p>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 其他信息 -->
+        <div class="mb-8">
+          <h2 class="text-xl font-bold mb-6">5. 其他信息</h2>
+          <div class="space-y-6">
+            <!-- 验证状态 -->
+            <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
+              <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>
+
+            <!-- 手势验证 -->
+            <div class="mb-6">
+              <h3 class="font-semibold mb-4">手势验证</h3>
+              <div class="grid grid-cols-2 gap-8">
+                <!-- 左手手势 -->
+                <div>
+                  <h4 class="text-sm font-medium mb-2">左手手势验证</h4>
+                  <div class="grid grid-cols-3 gap-2">
+                    <div v-for="(image, index) in candidateInfo.additionalInfo.leftHandGestures.images" :key="index"
+                      class="aspect-square rounded-lg overflow-hidden bg-gray-100">
+                      <img :src="image" alt="左手手势" class="w-full h-full object-cover">
+                    </div>
+                  </div>
+                </div>
+                <!-- 右手手势 -->
+                <div>
+                  <h4 class="text-sm font-medium mb-2">右手手势验证</h4>
+                  <div class="grid grid-cols-3 gap-2">
+                    <div v-for="(image, index) in candidateInfo.additionalInfo.rightHandGestures.images" :key="index"
+                      class="aspect-square rounded-lg overflow-hidden bg-gray-100">
+                      <img :src="image" alt="右手手势" class="w-full h-full object-cover">
+                    </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">
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+
+        <!-- 评估表单 -->
+        <a-form
+          ref="formRef"
+          :model="candidateInfo"
+          layout="vertical"
+        >
+          <!-- 评分 -->
+          <div class="mb-8">
+            <h2 class="text-xl font-bold mb-4">6. 面试评分</h2>
+            <a-form-item
+              label="请为候选人打分"
+              name="score"
+              :rules="[{ required: true, message: '请选择评分' }]"
+            >
+              <a-rate
+                v-model:value="evaluationScore"
+                :count="5"
+                allow-half
+              />
+            </a-form-item>
+          </div>
+
+          <!-- 评价 -->
+          <div class="mb-8">
+            <h2 class="text-xl font-bold mb-4">评价意见</h2>
+            <a-form-item
+              label="请输入评价意见"
+              name="comments"
+              :rules="[{ required: true, message: '请输入评价意见' }]"
+            >
+              <a-textarea
+                v-model:value="evaluationComments"
+                :rows="4"
+                placeholder="请输入您的评价意见..."
+              />
+            </a-form-item>
+          </div>
+
+          <!-- 提交按钮 -->
+          <div class="flex justify-end">
+            <a-button type="primary" @click="handleSubmit">
+              提交评估
+            </a-button>
+          </div>
+        </a-form>
+      </div>
+    </a-spin>
+
+    <!-- 悬浮按钮 -->
+    <div class="fixed right-8 bottom-24 flex flex-col space-y-4">
+      <a-button
+        type="primary"
+        shape="circle"
+        class="flex items-center justify-center"
+        @click="handleShare"
+      >
+        <template #icon>
+          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
+            <path d="M15 8a3 3 0 10-2.977-2.63l-4.94 2.47a3 3 0 100 4.319l4.94 2.47a3 3 0 10.895-1.789l-4.94-2.47a3.027 3.027 0 000-.74l4.94-2.47C13.456 7.68 14.19 8 15 8z" />
+          </svg>
+        </template>
+      </a-button>
+      <a-button
+        type="primary"
+        shape="circle"
+        class="flex items-center justify-center"
+        @click="handleDownload"
+      >
+        <template #icon>
+          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
+            <path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
+          </svg>
+        </template>
+      </a-button>
+      <a-button
+        type="primary"
+        shape="circle"
+        class="flex items-center justify-center"
+        @click="scrollToTop"
+      >
+        <template #icon>
+          <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
+            <path fill-rule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" clip-rule="evenodd" />
+          </svg>
+        </template>
+      </a-button>
+    </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;
+}
+
+.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%;
+  min-height: 100%;
+  overflow-y: auto !important;
+}
+
+/* 确保主容器不会限制滚动 */
+.max-w-4xl {
+  position: relative;
+  overflow: visible;
+}
+
+/* 添加以下样式确保内容可以正常滚动 */
+body {
+  margin: 0;
+  padding: 0;
+}
+
+#app {
+  height: auto;
+  min-height: 100vh;
+  overflow: visible;
+}
+
+@media (max-width: 768px) {
+  .max-w-4xl {
+    padding: 1rem;
+  }
+  
+  .grid-cols-3 {
+    grid-template-columns: 1fr;
+  }
+  
+  .fixed {
+    right: 1rem;
+  }
+}
+</style>

+ 6 - 6
src/views/questionBank/list/api.ts

@@ -13,21 +13,21 @@ export function GetPermission() {
 
 export function GetList(query: PageQuery) {
 	return request({
-		url: '/system/interview_question/list',
+		url: '/api/system/interview_question/list',
 		method: 'get',
-		params: {...query},/* :Session.get('tenant_id').tenant_id || ,tenant_id:'1' */
+		params: {...query,tenant_id:1},/* :Session.get('tenant_id').tenant_id || ,tenant_id:'1' */
 	});
 }
 export function GetObj(id: InfoReq) {
 	return request({
-		url: "/system/interview_question/detail" + id,
+		url: "/api/system/interview_question/detail" + id,
 		method: 'get',
 	});
 }
 
 export function AddObj(obj: AddReq) {
 	return request({
-		url: "/system/interview_question/create",
+		url: "/api/system/interview_question/create",
 		method: 'post',
 		data: {...obj,tenant_id: '1'},/* Session.get('tenant_id').tenant_id ||,tenant_id: '1' */
 	});
@@ -35,7 +35,7 @@ export function AddObj(obj: AddReq) {
 
 export function UpdateObj(obj: EditReq) {
 	return request({
-		url: "/system/interview_question/update",/* + obj.id+'/' */
+		url: "/api/system/interview_question/update",/* + obj.id+'/' */
 		method: 'put',
 		data: {...obj,tenant_id: '1'},
 	});
@@ -43,7 +43,7 @@ export function UpdateObj(obj: EditReq) {
 
 export function DelObj(id: DelReq) {
 	return request({
-		url: '/system/interview_question/delete',/* + id + '/' ,tenant_id: '1' */
+		url: '/api/system/interview_question/delete',/* + id + '/' ,tenant_id: '1' */
 		method: 'delete',
 		data: { id},
 	});

+ 2 - 2
src/views/questionBank/list/crud.tsx

@@ -409,8 +409,8 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 						})
 					}
 				},
-				answer_explanation: {
-					title: '答案解析',
+				scoring_reference : {
+					title: '备注',
 					search: { show: false },
 					column: { show: false },
 					form: {

+ 12 - 9
src/views/questionBank/positionList/crud.tsx

@@ -4,24 +4,27 @@ import { dictionary } from '/@/utils/dictionary';
 import { successMessage } from '../../../utils/message';
 import { auth } from '/@/utils/authFunction';
 import { useRouter } from 'vue-router';
+import { ElMessage } from 'element-plus';
 
 export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProps): CreateCrudOptionsRet {
 	const router = useRouter(); // 添加这行来获取router实例
 	const pageRequest = async (query: any) => {
 		// 确保查询参数正确传递
-		console.log('Query params:', query); // 添加日志以便调试
-
-		// 构建请求参数,确保category参数被正确传递
+		api.GetDocumentTree({}).then((ret: any) => {
+			// 构建请求参数,确保category参数被正确传递
 		const params = {
 			...query,
 			tenant_id: '1',
 			// 如果 query.form 中有 category,使用它,否则使用 query.category
-			category: (query.form && query.form.category) || query.category || ''
+			category: (query.form && query.form.category) || query.category || '',
+			job_id: ret.data[0].id
 		};
 
 		console.log('Request params:', params); // 添加日志以便调试
 
-		return await api.GetDocumentList(params);
+		return  api.GetDocumentList(params);
+		});
+		
 	};
 
 	const editRequest = async ({ form, row }: any) => {
@@ -207,7 +210,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 						rules: [{ required: true, message: '题目形式必填' }],
 						component: {
 							placeholder: '请选择题目形式',
-							onChange: ({ form }) => {
+							onChange: ({ form }: { form: any }) => {
 								// 重置相关字段
 								if (form.question_form === 1) {
 									// 单选题
@@ -382,7 +385,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 												<span style="flex: 1;">选项内容</span>
 												<span style="width: 80px; text-align: center;">是否正确</span>
 											</div>
-											{form.options.map((option, index) => (
+											{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}
@@ -398,7 +401,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 															onClick={() => {
 																if (form.question_form === 1) {
 																	// 单选题:只能有一个正确答案
-																	form.options.forEach((item, idx) => {
+																	form.options.forEach((item: any, idx: number) => {
 																		item.is_correct = (idx === index);
 																	});
 																} else {
@@ -434,7 +437,7 @@ export const createCrudOptions = function ({ crudExpose }: CreateCrudOptionsProp
 														}
 														// 添加新选项时自动设置排序值
 														const sort = form.options.length > 0 ?
-															Math.max(...form.options.map(o => o.sort || 0)) + 1 : 1;
+															Math.max(...form.options.map((o: any) => o.sort || 0)) + 1 : 1;
 														form.options.push({ option_text: '', is_correct: false, sort });
 													}}
 												>

+ 2 - 2
src/views/questionBank/positionList/index.vue

@@ -73,7 +73,7 @@
     </el-drawer>
 
     <!-- 添加自定义查看详情对话框 -->
-    <el-dialog
+    <!-- <el-dialog
       v-model="detailDialogVisible"
       title="文档详情"
       width="50%"
@@ -112,7 +112,7 @@
           <el-button @click="detailDialogVisible = false">关闭</el-button>
         </span>
       </template>
-    </el-dialog>
+    </el-dialog> -->
   </fs-page>
 </template>