Browse Source

添加分类标签

yangg 4 tháng trước cách đây
mục cha
commit
9d47ffcf78

+ 1 - 1
.env.development

@@ -4,7 +4,7 @@ ENV = 'development'
 #正式: https://backend.qicai321.com
 #线下:http://192.168.66.187:8083
 # 本地环境接口地址 121.36.251.245
-VITE_API_URL = 'https://backend.qicai321.com'
+VITE_API_URL = 'http://192.168.66.187:8083'
 VITE_API_WX_URL='https://api.weixin.qq.com/'
 
 # 是否启用按钮权限

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

@@ -0,0 +1,55 @@
+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},
+	});
+}

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

@@ -0,0 +1,259 @@
+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: {
+			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,
+				layout: 'auto',
+			},
+			columns: {
+				_index: {
+					title: '序号',
+					form: { show: false },
+					column: {
+						type: 'index',
+						align: 'center',
+						width: '70px',
+						columnSetDisabled: true, //禁止在列设置中选择
+					},
+				},
+				name: {
+					title: '分类名称',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入分类名称',
+						},
+					},
+					type: 'input',
+					column: {
+						minWidth: 120,
+					},
+					form: {
+						rules: [
+							{ required: true, message: '分类名称为必填项' },
+						],
+					},
+				},
+				description: {
+					title: '描述',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入描述',
+						},
+					},
+					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, // 表单中不显示,由系统自动更新
+					},
+				},
+			},
+		},
+	};
+};

+ 25 - 0
src/views/questionBank/classList/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>

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

@@ -0,0 +1,55 @@
+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},
+	});
+}

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

@@ -0,0 +1,241 @@
+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: {
+			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,
+				layout: 'auto',
+			},
+			columns: {
+				_index: {
+					title: '序号',
+					form: { show: false },
+					column: {
+						type: 'index',
+						align: 'center',
+						width: '70px',
+						columnSetDisabled: true, //禁止在列设置中选择
+					},
+				},
+				name: {
+					title: '标签名称',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入标签名称',
+						},
+					},
+					type: 'input',
+					column: {
+						minWidth: 120,
+					},
+					form: {
+						rules: [
+							{ required: true, message: '标签名称为必填项' },
+						],
+					},
+				},
+				description: {
+					title: '描述',
+					search: {
+						show: true,
+						component: {
+							placeholder: '请输入描述',
+						},
+					},
+					type: 'textarea',
+					column: {
+						minWidth: 150,
+						showOverflowTooltip: true,
+					},
+					form: {
+						component: {
+							span: 24,
+							placeholder: '请输入标签描述',
+						},
+					},
+				},
+				category_id: {
+					title: '分类',
+					search: {
+						show: true,
+					},
+					type: 'dict-select',
+					column: {
+						minWidth: 120,
+					},
+					dict:dict({
+						url: '/api/categories', // 获取数据的接口
+						// 或者使用方法
+						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: '分类为必填项' },
+						],
+					},
+				},
+			/* 	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, // 表单中不显示,由系统自动更新
+					},
+				},
+			},
+		},
+	};
+};

+ 25 - 0
src/views/questionBank/labelList/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>

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

@@ -48,3 +48,19 @@ export function DelObj(id: DelReq) {
 		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},
+	});
+}

+ 109 - 6
src/views/questionBank/list/crud.tsx

@@ -3,6 +3,7 @@ import * as api from './api';
 import { dictionary } from '/@/utils/dictionary';
 import { successMessage } from '../../../utils/message';
 import { auth } from '/@/utils/authFunction';
+import { ref, onMounted } from 'vue';
 
 /**
  *
@@ -25,6 +26,48 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 		return await api.AddObj(form);
 	};
 
+/* 	// 修改分类和标签数据获取方式
+	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();
+	}); */
+
 	return {
 		crudOptions: {
 			request: {
@@ -150,7 +193,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 						rules: [{ required: true, message: '题目形式必填' }],
 						component: {
 							placeholder: '请选择题目形式',
-							onChange: ({ form }) => {
+							onChange: ({ form }: { form: any }) => {
 								// 重置相关字段
 								if (form.question_form === 1) {
 									// 单选题
@@ -313,7 +356,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 						component: {
 							name: 'el-card',
 							children: {
-								default: ({ form }) => {
+								default: ({ form }: { form: any }) => {
 									// 确保options数组已初始化
 									if (!form.options) {
 										form.options = [];
@@ -325,7 +368,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 												<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} 
@@ -341,7 +384,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 															onClick={() => {
 																if (form.question_form === 1) {
 																	// 单选题:只能有一个正确答案
-																	form.options.forEach((item, idx) => {
+																	form.options.forEach((item: any, idx: number) => {
 																		item.is_correct = (idx === index);
 																	});
 																} else {
@@ -377,7 +420,7 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 														}
 														// 添加新选项时自动设置排序值
 														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 });
 													}}
 												>
@@ -423,7 +466,67 @@ export const createCrudOptions = function ({ crudExpose, context }: CreateCrudOp
 						},
 					},
 				},
-
+				category_id: {
+					title: '题目分类',
+					search: { show: true },
+					type: 'dict-select',
+					column: {
+						minWidth: 120,
+					},
+					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: '选择题目所属的分类'
+					},
+				},
+				tag_ids: {
+					title: '题目标签',
+					search: { show: true },
+					type: 'dict-select',
+					column: {
+						minWidth: 150,
+						component: {
+							name: 'fs-dict-label',
+							props: {
+								multiple: true
+							}
+						}
+					},
+					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: '选择题目关联的标签,可多选'
+					}
+				},
 			},
 		},
 	};