Przeglądaj źródła

Merge branch 'master' of http://192.168.1.222:3000/hnsh-smart-steward/smart-steward-admin

闪电 2 tygodni temu
rodzic
commit
4e1eb2fbbe

+ 11 - 0
apps/web-ele/src/router/routes/local.ts

@@ -103,6 +103,17 @@ const localRoutes: RouteRecordStringComponent[] = [
103 103
     path: '/knowledge/detail/:classId',
104 104
   },
105 105
   {
106
+    component: '/knowledge/edit/index',
107
+    meta: {
108
+      activePath: '/knowledge/edit',
109
+      icon: 'carbon:data-base', 
110
+      title: '知识库编辑',
111
+      hideInMenu: true,
112
+    },
113
+    name: 'KnowledgeDetailEdit',
114
+    path: '/knowledge/detail/edit/:classId',
115
+  },
116
+  {
106 117
     component: '/schedule/detail/index',
107 118
     meta: {
108 119
       activePath: '/schedule/detail',

+ 0 - 75
apps/web-ele/src/views/knowledge/detail/config-data.tsx

@@ -1,75 +0,0 @@
1
-import type { FormSchemaGetter } from '#/adapter/form';
2
-
3
-// 编辑表单配置
4
-export const drawerFormSchema: FormSchemaGetter = () => [
5
-  {
6
-    component: 'Input',
7
-    fieldName: 'title',
8
-    label: '标题',
9
-    componentProps: {
10
-      placeholder: '请输入章节标题',
11
-    },
12
-    rules: 'required',
13
-  },
14
-  {
15
-    component: 'Input',
16
-    fieldName: 'content',
17
-    label: '内容',
18
-    componentProps: {
19
-      placeholder: '请输入章节内容',
20
-      rows: 10,
21
-    },
22
-    // 使用自定义插槽渲染Tinymce编辑器
23
-    slot: 'content',
24
-    rules: 'required',
25
-  },
26
-  {
27
-    component: 'Input',
28
-    fieldName: 'attachments',
29
-    label: '附件',
30
-    componentProps: {
31
-      placeholder: '请上传附件',
32
-      readonly: true,
33
-    },
34
-  },
35
-];
36
-
37
-// 提醒表单配置
38
-export const remindFormSchema: FormSchemaGetter = () => [
39
-  {
40
-    component: 'Input',
41
-    fieldName: 'remindContent',
42
-    label: '提醒内容',
43
-    componentProps: {
44
-      placeholder: '请输入提醒内容',
45
-      rows: 4,
46
-      maxlength: 200,
47
-      type: 'textarea',
48
-      showWordLimit: true,
49
-    },
50
-    // 使用自定义插槽渲染文本域
51
-    slot: 'remindContent',
52
-    rules: 'required',
53
-  },
54
-  {
55
-    // 组件需要在 #/adapter.ts内注册,并加上类型
56
-    component: 'ApiSelect',
57
-    // 对应组件的参数
58
-    componentProps: {
59
-      placeholder: '请选择或搜索提醒人',
60
-      // 菜单接口转options格式
61
-      afterFetch: (data: { name: string; path: string }[]) => {
62
-        return data.map((item: any) => ({
63
-          label: item.name,
64
-          value: item.path,
65
-        }));
66
-      },
67
-      // 菜单接口
68
-      api: () => {},
69
-    },
70
-    // 字段名
71
-    fieldName: 'remindPerson',
72
-    // 界面显示的label
73
-    label: '提醒人',
74
-  },
75
-];

Plik diff jest za duży
+ 1627 - 428
apps/web-ele/src/views/knowledge/detail/index.vue


+ 0 - 39
apps/web-ele/src/views/knowledge/detail/remind-drawer.vue

@@ -1,39 +0,0 @@
1
-<script lang="ts" setup>
2
-import { useVbenDrawer } from '@vben/common-ui';
3
-
4
-import { useVbenForm } from '#/adapter/form';
5
-
6
-import { remindFormSchema } from './config-data';
7
-
8
-defineOptions({
9
-  name: 'RemindForm',
10
-});
11
-
12
-const [Form, formApi] = useVbenForm({
13
-  schema: remindFormSchema(),
14
-  showDefaultActions: false,
15
-});
16
-const [Drawer, drawerApi] = useVbenDrawer({
17
-  onCancel() {
18
-    drawerApi.close();
19
-  },
20
-  onConfirm: async () => {
21
-    await formApi.submitForm();
22
-    drawerApi.close();
23
-  },
24
-  onOpenChange(isOpen: boolean) {
25
-    if (isOpen) {
26
-      const { values } = drawerApi.getData<Record<string, any>>();
27
-      if (values) {
28
-        formApi.setValues(values);
29
-      }
30
-    }
31
-  },
32
-  title: '提醒',
33
-});
34
-</script>
35
-<template>
36
-  <Drawer>
37
-    <Form />
38
-  </Drawer>
39
-</template>

+ 188 - 184
apps/web-ele/src/views/knowledge/detail/edit-drawer.vue

@@ -1,184 +1,188 @@
1
-<script lang="ts" setup>
2
-import { ref } from 'vue';
3
-
4
-import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
5
-
6
-import { Plus } from '@element-plus/icons-vue';
7
-import { ElIcon, ElUpload } from 'element-plus';
8
-
9
-import TinymceEditor from '#/components/tinymce/src/editor.vue';
10
-
11
-import { drawerFormSchema } from './config-data';
12
-
13
-const emit = defineEmits<{
14
-  reload: [];
15
-}>();
16
-
17
-// 附件列表
18
-const attachments = ref<any[]>([]);
19
-const uploadUrlRef = ref<string>('');
20
-
21
-// 表单配置
22
-const [Form, formApi] = useVbenForm({
23
-  showDefaultActions: false,
24
-  schema: drawerFormSchema(),
25
-});
26
-
27
-// 处理附件上传
28
-function handleAttachmentUpload(options: any) {
29
-  // 模拟上传成功处理
30
-  const fileData = {
31
-    uid: options.file.uid,
32
-    name: options.file.name,
33
-    url: URL.createObjectURL(options.file),
34
-    status: 'success',
35
-    raw: options.file,
36
-  };
37
-
38
-  // 多个文件上传时,url以逗号隔开
39
-  uploadUrlRef.value = uploadUrlRef.value
40
-    ? `${uploadUrlRef.value},${fileData.url}`
41
-    : fileData.url;
42
-
43
-  options.onSuccess(fileData);
44
-}
45
-
46
-// 移除附件
47
-function handleAttachmentRemove(file: any, fileList: any[]) {
48
-  attachments.value = fileList;
49
-  // 更新上传url,移除已删除的文件
50
-  uploadUrlRef.value = fileList
51
-    .filter((item: any) => item.status === 'success')
52
-    .map((item: any) => item.url)
53
-    .join(',');
54
-}
55
-
56
-// 预览附件
57
-function handleAttachmentPreview(file: any) {
58
-  console.log('预览附件', file);
59
-  // TODO: 实现附件预览功能
60
-}
61
-
62
-// 确认编辑
63
-async function handleConfirm() {
64
-  try {
65
-    const { valid } = await formApi.validate();
66
-    if (!valid) {
67
-      return;
68
-    }
69
-    const data = await formApi.getValues();
70
-
71
-    // 处理附件url,转换为逗号分隔的字符串
72
-    data.attachments = uploadUrlRef.value;
73
-
74
-    // 模拟提交数据
75
-    console.log('提交数据:', data);
76
-    // TODO: 实现真实的API调用
77
-
78
-    // 提交成功后触发reload事件
79
-    emit('reload');
80
-    drawerApi.close();
81
-  } catch (error) {
82
-    console.error('保存失败:', error);
83
-  }
84
-}
85
-
86
-const isUpdateRef = ref<boolean>(false);
87
-
88
-const [Drawer, drawerApi] = useVbenDrawer({
89
-  async onOpenChange(isOpen) {
90
-    if (!isOpen) {
91
-      return;
92
-    }
93
-    try {
94
-      drawerApi.drawerLoading(true);
95
-      const { chapter, isUpdate } = drawerApi.getData();
96
-      isUpdateRef.value = isUpdate;
97
-      if (isUpdate && chapter) {
98
-        // 处理附件url,转换为文件列表格式
99
-        const attachmentsList = chapter.attachments
100
-          ? chapter.attachments
101
-              .split(',')
102
-              .map((url: string, index: number) => ({
103
-                uid: Date.now() + index,
104
-                name: url.split('/').pop() || `文件${index + 1}`,
105
-                url,
106
-                status: 'success',
107
-              }))
108
-          : [];
109
-
110
-        uploadUrlRef.value = chapter.attachments || '';
111
-        attachments.value = attachmentsList;
112
-
113
-        // 设置表单数据
114
-        await formApi.setValues({
115
-          ...chapter,
116
-          content: chapter.content || '',
117
-          attachments: attachmentsList,
118
-        });
119
-      } else {
120
-        // 新增时重置表单
121
-        await formApi.resetForm();
122
-        attachments.value = [];
123
-        uploadUrlRef.value = '';
124
-      }
125
-    } catch (error) {
126
-      console.error('加载数据失败:', error);
127
-    } finally {
128
-      drawerApi.drawerLoading(false);
129
-    }
130
-  },
131
-  async onConfirm() {
132
-    await handleConfirm();
133
-  },
134
-  onClosed() {
135
-    formApi.resetForm();
136
-    attachments.value = [];
137
-    uploadUrlRef.value = '';
138
-  },
139
-});
140
-</script>
141
-
142
-<template>
143
-  <Drawer :title="isUpdateRef ? '编辑章节' : '新增章节'">
144
-    <Form>
145
-      <!-- 自定义Tinymce编辑器插槽 -->
146
-      <template #content="scope">
147
-        <TinymceEditor v-model="scope.modelValue" :height="400" />
148
-      </template>
149
-
150
-      <!-- 自定义附件上传插槽 -->
151
-      <template #attachments="scope">
152
-        <ElUpload
153
-          v-model:file-list="attachments"
154
-          action="#"
155
-          accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
156
-          list-type="text"
157
-          show-file-list
158
-          :auto-upload="true"
159
-          :drag="false"
160
-          :limit="5"
161
-          :multiple="true"
162
-          :http-request="handleAttachmentUpload"
163
-          :on-remove="handleAttachmentRemove"
164
-          :on-preview="handleAttachmentPreview"
165
-          class="mb-4"
166
-        >
167
-          <div class="flex items-center justify-center">
168
-            <ElIcon class="el-upload__icon">
169
-              <Plus size="18" />
170
-            </ElIcon>
171
-            <div class="el-upload__text">
172
-              <div>上传附件</div>
173
-              <div class="text-xs text-gray-500">最多上传5个附件</div>
174
-            </div>
175
-          </div>
176
-        </ElUpload>
177
-      </template>
178
-    </Form>
179
-  </Drawer>
180
-</template>
181
-
182
-<style scoped lang="scss">
183
-/* 自定义样式 */
184
-</style>
1
+<script lang="ts" setup>
2
+import { ref } from 'vue';
3
+
4
+import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
5
+
6
+import { Plus } from '@element-plus/icons-vue';
7
+import { ElIcon, ElUpload } from 'element-plus';
8
+
9
+import TinymceEditor from '#/components/tinymce/src/editor.vue';
10
+
11
+import { drawerFormSchema } from './config-data';
12
+
13
+const emit = defineEmits<{
14
+  reload: [];
15
+}>();
16
+
17
+// 附件列表
18
+const attachments = ref<any[]>([]);
19
+const uploadUrlRef = ref<string>('');
20
+
21
+// 表单配置
22
+const [Form, formApi] = useVbenForm({
23
+  showDefaultActions: false,
24
+  schema: drawerFormSchema(),
25
+});
26
+
27
+// 处理附件上传
28
+function handleAttachmentUpload(options: any): Promise<unknown> {
29
+  return new Promise((resolve) => {
30
+    // 模拟上传成功处理
31
+    const fileData = {
32
+      uid: options.file.uid,
33
+      name: options.file.name,
34
+      url: URL.createObjectURL(options.file),
35
+      status: 'success',
36
+      raw: options.file,
37
+    };
38
+
39
+    // 多个文件上传时,url以逗号隔开
40
+    uploadUrlRef.value = uploadUrlRef.value
41
+      ? `${uploadUrlRef.value},${fileData.url}`
42
+      : fileData.url;
43
+
44
+    options.onSuccess(fileData);
45
+    resolve(fileData);
46
+  });
47
+}
48
+
49
+// 移除附件
50
+function handleAttachmentRemove(file: any, fileList: any[]) {
51
+  attachments.value = fileList;
52
+  // 更新上传url,移除已删除的文件
53
+  uploadUrlRef.value = fileList
54
+    .filter((item: any) => item.status === 'success')
55
+    .map((item: any) => item.url)
56
+    .join(',');
57
+}
58
+
59
+// 预览附件
60
+function handleAttachmentPreview(file: any) {
61
+  console.log('预览附件', file);
62
+  // TODO: 实现附件预览功能
63
+}
64
+
65
+// 确认编辑
66
+async function handleConfirm() {
67
+  try {
68
+    const { valid } = await formApi.validate();
69
+    if (!valid) {
70
+      return;
71
+    }
72
+    const data = await formApi.getValues();
73
+
74
+    // 处理附件url,转换为逗号分隔的字符串
75
+    data.attachments = uploadUrlRef.value;
76
+
77
+    // 模拟提交数据
78
+    console.log('提交数据:', data);
79
+    // TODO: 实现真实的API调用
80
+
81
+    // 提交成功后触发reload事件
82
+    emit('reload');
83
+    drawerApi.close();
84
+  } catch (error) {
85
+    console.error('保存失败:', error);
86
+  }
87
+}
88
+
89
+const isUpdateRef = ref<boolean>(false);
90
+
91
+const [Drawer, drawerApi] = useVbenDrawer({
92
+  async onOpenChange(isOpen) {
93
+    if (!isOpen) {
94
+      return;
95
+    }
96
+    try {
97
+      drawerApi.drawerLoading(true);
98
+      const { chapter, isUpdate } = drawerApi.getData();
99
+      isUpdateRef.value = isUpdate;
100
+      if (isUpdate && chapter) {
101
+        // 处理附件url,转换为文件列表格式
102
+        const attachmentsList = chapter.attachments
103
+          ? chapter.attachments
104
+              .split(',')
105
+              .map((url: string, index: number) => ({
106
+                uid: Date.now() + index,
107
+                name: url.split('/').pop() || `文件${index + 1}`,
108
+                url,
109
+                status: 'success',
110
+              }))
111
+          : [];
112
+
113
+        uploadUrlRef.value = chapter.attachments || '';
114
+        attachments.value = attachmentsList;
115
+
116
+        // 设置表单数据
117
+        await formApi.setValues({
118
+          ...chapter,
119
+          content: chapter.content || '',
120
+          attachments: attachmentsList,
121
+        });
122
+      } else {
123
+        // 新增时重置表单
124
+        await formApi.resetForm();
125
+        attachments.value = [];
126
+        uploadUrlRef.value = '';
127
+      }
128
+    } catch (error) {
129
+      console.error('加载数据失败:', error);
130
+    } finally {
131
+      drawerApi.drawerLoading(false);
132
+    }
133
+  },
134
+  async onConfirm() {
135
+    await handleConfirm();
136
+  },
137
+  onClosed() {
138
+    formApi.resetForm();
139
+    attachments.value = [];
140
+    uploadUrlRef.value = '';
141
+  },
142
+});
143
+</script>
144
+
145
+<template>
146
+  <Drawer :title="isUpdateRef ? '编辑章节' : '新增章节'">
147
+    <Form>
148
+      <!-- 自定义Tinymce编辑器插槽 -->
149
+      <template #content="scope">
150
+        <TinymceEditor v-model="scope.modelValue" :height="400" />
151
+      </template>
152
+
153
+      <!-- 自定义附件上传插槽 -->
154
+      <template #attachments="scope">
155
+        <ElUpload
156
+          v-model:file-list="attachments"
157
+          action="#"
158
+          accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
159
+          list-type="text"
160
+          show-file-list
161
+          :auto-upload="true"
162
+          :drag="false"
163
+          :limit="5"
164
+          :multiple="true"
165
+          :http-request="handleAttachmentUpload"
166
+          :on-remove="handleAttachmentRemove"
167
+          :on-preview="handleAttachmentPreview"
168
+          class="mb-4"
169
+        >
170
+          <div class="flex items-center justify-center">
171
+            <ElIcon class="el-upload__icon">
172
+              <Plus size="18" />
173
+            </ElIcon>
174
+            <div class="el-upload__text">
175
+              <div>上传附件</div>
176
+              <div class="text-xs text-gray-500">最多上传5个附件</div>
177
+            </div>
178
+          </div>
179
+        </ElUpload>
180
+      </template>
181
+    </Form>
182
+  </Drawer>
183
+</template>
184
+
185
+<style scoped lang="scss">
186
+/* 自定义样式 */
187
+</style>
188
+

+ 970 - 0
apps/web-ele/src/views/knowledge/edit/index.vue

@@ -0,0 +1,970 @@
1
+<script lang="ts" setup>
2
+import { ref, onMounted } from 'vue';
3
+import { useRoute } from 'vue-router';
4
+import { Page } from '@vben/common-ui';
5
+import {
6
+  HomeFilled,
7
+  Plus,
8
+  Edit,
9
+  Delete,
10
+  DArrowLeft,
11
+  Expand,
12
+  Notification,
13
+} from '@element-plus/icons-vue';
14
+import {
15
+  ElButton,
16
+  ElIcon,
17
+  ElTree,
18
+  ElMessage,
19
+  ElInput,
20
+  ElUpload,
21
+} from 'element-plus';
22
+import TinymceEditor from '#/components/tinymce/src/editor.vue';
23
+
24
+// 路由实例
25
+const route = useRoute();
26
+const classId = route.params.classId;
27
+
28
+// 菜单折叠状态
29
+const isCollapse = ref(false);
30
+
31
+// 章节数据
32
+const chapterData = ref([
33
+  {
34
+    id: 1,
35
+    label: '第一章 基础知识',
36
+    children: [
37
+      { id: 2, label: '1.1 概述', saved: true },
38
+      { id: 3, label: '1.2 基本概念', saved: false },
39
+      { id: 4, label: '1.3 核心原理', saved: true },
40
+    ],
41
+  },
42
+  {
43
+    id: 5,
44
+    label: '第二章 进阶知识',
45
+    children: [
46
+      { id: 6, label: '2.1 高级特性', saved: true },
47
+      { id: 7, label: '2.2 最佳实践', saved: true },
48
+    ],
49
+  },
50
+  {
51
+    id: 8,
52
+    label: '第三章 案例分析',
53
+    children: [
54
+      { id: 9, label: '3.1 案例一', saved: false },
55
+      { id: 10, label: '3.2 案例二', saved: true },
56
+    ],
57
+  },
58
+]);
59
+
60
+// 选中的章节
61
+const selectedChapter = ref({
62
+  id: 'home',
63
+  label: '主页',
64
+  content: '这是知识库的主页内容,您可以在这里查看知识库的概述信息。',
65
+  updatedTime: '2025-12-22 14:30:00',
66
+});
67
+// 保存草稿
68
+const saveDraft = () => {
69
+  ElMessage.success('草稿保存成功');
70
+};
71
+
72
+// 发布
73
+const publish = () => {
74
+  ElMessage.success('发布成功');
75
+};
76
+
77
+// 附件列表
78
+const attachments = ref<any[]>([]);
79
+const uploadUrlRef = ref<string>('');
80
+
81
+// 处理附件上传
82
+function handleAttachmentUpload(options: any): Promise<unknown> {
83
+  return new Promise((resolve) => {
84
+    // 模拟上传成功处理
85
+    const fileData = {
86
+      uid: options.file.uid,
87
+      name: options.file.name,
88
+      url: URL.createObjectURL(options.file),
89
+      status: 'success',
90
+      raw: options.file,
91
+    };
92
+
93
+    // 多个文件上传时,url以逗号隔开
94
+    uploadUrlRef.value = uploadUrlRef.value
95
+      ? `${uploadUrlRef.value},${fileData.url}`
96
+      : fileData.url;
97
+
98
+    options.onSuccess(fileData);
99
+    resolve(fileData);
100
+  });
101
+}
102
+
103
+// 移除附件
104
+function handleAttachmentRemove(file: any, fileList: any[]) {
105
+  attachments.value = fileList;
106
+  // 更新上传url,移除已删除的文件
107
+  uploadUrlRef.value = fileList
108
+    .filter((item: any) => item.status === 'success')
109
+    .map((item: any) => item.url)
110
+    .join(',');
111
+}
112
+
113
+// 预览附件
114
+function handleAttachmentPreview(file: any) {
115
+  console.log('预览附件', file);
116
+  // TODO: 实现附件预览功能
117
+}
118
+
119
+
120
+
121
+// 点击章节
122
+const handleChapterClick = (node: any) => {
123
+  selectedChapter.value = {
124
+    id: node.id,
125
+    label: node.label,
126
+    content: `这是 ${node.label} 的内容,内容丰富详实,包含了该章节的核心知识点和实践案例。`,
127
+    updatedTime: '2025-12-22 14:30:00',
128
+  };
129
+};
130
+
131
+// 点击主页
132
+const handleHomeClick = () => {
133
+  selectedChapter.value = {
134
+    id: 'home',
135
+    label: '主页',
136
+    content: '这是知识库的主页内容,您可以在这里查看知识库的概述信息和使用指南。',
137
+    updatedTime: '2025-12-22 14:30:00',
138
+  };
139
+};
140
+
141
+// 添加章节
142
+const addChapter = (parentId = null) => {
143
+  ElMessage.info('添加章节功能待实现');
144
+};
145
+
146
+// 编辑章节
147
+const editChapter = (node) => {
148
+  ElMessage.info('编辑章节功能待实现');
149
+};
150
+
151
+// 删除章节
152
+const deleteChapter = (node) => {
153
+  ElMessage.info('删除章节功能待实现');
154
+};
155
+
156
+
157
+
158
+// 页面加载时获取数据
159
+onMounted(() => {
160
+  // 模拟获取知识库详情数据
161
+  console.log('加载知识库详情,ID:', classId);
162
+});
163
+</script>
164
+
165
+<template>
166
+  <Page>
167
+    <div class="knowledge-detail">
168
+      <!-- 顶部导航栏 -->
169
+      <div class="top-nav">
170
+        <div class="nav-left">
171
+          <ElIcon class="nav-icon"><HomeFilled /></ElIcon>
172
+          <span class="nav-separator">|</span>
173
+          <span class="nav-item gray">知识搜索</span>
174
+          <span class="nav-separator">></span>
175
+          <span class="nav-item active">我的文件</span>
176
+        </div>
177
+        <div class="nav-right">
178
+          <ElButton type="default" @click="saveDraft">保存草稿</ElButton>
179
+          <ElButton type="primary" @click="publish">发布</ElButton>
180
+        </div>
181
+      </div>
182
+
183
+      <!-- 主体内容区 -->
184
+      <div class="main-content">
185
+        <!-- 左侧目录 -->
186
+        <div class="left-sidebar" :class="{ 'collapsed': isCollapse }">
187
+          <!-- 折叠按钮 -->
188
+          <div class="collapse-btn" @click="isCollapse = true">
189
+            <ElIcon><DArrowLeft /></ElIcon>
190
+          </div>
191
+          
192
+          <div class="sidebar-content">
193
+            <!-- 知识库信息 -->
194
+            <div class="knowledge-info">
195
+              <div class="knowledge-icon">
196
+                <ElIcon class="icon-large"><HomeFilled /></ElIcon>
197
+              </div>
198
+              <div class="knowledge-title">
199
+                知识库示例
200
+              </div>
201
+            </div>
202
+
203
+            <!-- 主页按钮 -->
204
+            <div class="home-btn" @click="handleHomeClick">
205
+              <ElIcon><HomeFilled /></ElIcon>
206
+              <span :class="{ 'collapsed': isCollapse }">主页</span>
207
+            </div>
208
+
209
+            <!-- 章节树 -->
210
+            <div class="chapter-tree">
211
+              <ElTree
212
+                :data="chapterData"
213
+                node-key="id"
214
+                default-expand-all
215
+                :expand-on-click-node="false"
216
+                @node-click="handleChapterClick"
217
+              >
218
+                <template #default="{ node, data }">
219
+                  <div class="tree-node-content">
220
+                    <span>
221
+                      {{ node.label }}
222
+                      <ElIcon v-if="!data.saved" class="unsaved-icon">
223
+                        <Notification />
224
+                      </ElIcon>
225
+                    </span>
226
+                    <div class="tree-node-actions">
227
+                      <ElButton
228
+                        type="text"
229
+                        size="small"
230
+                        circle
231
+                        @click.stop="addChapter(data.id)"
232
+                        title="添加子章节"
233
+                      >
234
+                        <ElIcon><Plus /></ElIcon>
235
+                      </ElButton>
236
+                      <ElButton
237
+                        type="text"
238
+                        size="small"
239
+                        circle
240
+                        @click.stop="editChapter(data)"
241
+                        title="编辑章节"
242
+                      >
243
+                        <ElIcon><Edit /></ElIcon>
244
+                      </ElButton>
245
+                      <ElButton
246
+                        type="text"
247
+                        size="small"
248
+                        circle
249
+                        @click.stop="deleteChapter(data)"
250
+                        title="删除章节"
251
+                      >
252
+                        <ElIcon><Delete /></ElIcon>
253
+                      </ElButton>
254
+                    </div>
255
+                  </div>
256
+                </template>
257
+              </ElTree>
258
+            </div>
259
+          </div>
260
+        </div>
261
+
262
+        <!-- 中间内容 -->
263
+        <div class="center-content">
264
+          <ElIcon v-if="isCollapse" :size="20" class="collapse-control-btn" @click="isCollapse = false"><Expand /></ElIcon>
265
+          <!-- 内容表单 -->
266
+          <div class="content-form">
267
+            <!-- 第一行:标题 -->
268
+            <div class="form-row">
269
+              <div class="form-item">
270
+                <ElInput
271
+                  v-model="selectedChapter.label"
272
+                  placeholder="请输入标题"
273
+                  class="title-input"
274
+                />
275
+              </div>
276
+            </div>
277
+
278
+            <!-- 第二行:富文本编辑器 -->
279
+            <div class="form-row">
280
+              <div class="form-item">
281
+                <TinymceEditor v-model="selectedChapter.content" :height="400" />
282
+              </div>
283
+            </div>
284
+
285
+            <!-- 第三行:附件上传 -->
286
+            <div class="form-row">
287
+              <div class="form-item">
288
+                <div class="upload-section">
289
+                  <div class="upload-header">
290
+                    <div class="upload-title">上传附件:</div>
291
+                    <ElUpload
292
+                      v-model:file-list="attachments"
293
+                      action="#"
294
+                      accept=".jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx"
295
+                      list-type="text"
296
+                      show-file-list
297
+                      :auto-upload="true"
298
+                      :drag="false"
299
+                      :limit="5"
300
+                      :multiple="true"
301
+                      :http-request="handleAttachmentUpload"
302
+                      :on-remove="handleAttachmentRemove"
303
+                      :on-preview="handleAttachmentPreview"
304
+                    >
305
+                      <div class="upload-btn">
306
+                        <ElIcon><Plus /></ElIcon>
307
+                        <span>添加附件</span>
308
+                      </div>
309
+                    </ElUpload>
310
+                    <div class="upload-note">不超过5M</div>
311
+                  </div>
312
+                </div>
313
+              </div>
314
+            </div>
315
+          </div>
316
+        </div>
317
+      </div>
318
+
319
+
320
+    </div>
321
+  </Page>
322
+</template>
323
+
324
+<style lang="scss" scoped>
325
+.knowledge-detail {
326
+  display: flex;
327
+  flex-direction: column;
328
+  height: calc(100vh - 122px);
329
+}
330
+
331
+/* 顶部导航 */
332
+.top-nav {
333
+  display: flex;
334
+  align-items: center;
335
+  justify-content: space-between;
336
+  padding: 16px 20px;
337
+  margin-bottom: 10px;
338
+  background-color: #fff;
339
+  border-bottom: 1px solid #e8e8e8;
340
+}
341
+
342
+.nav-left {
343
+  display: flex;
344
+  gap: 8px;
345
+  align-items: center;
346
+}
347
+
348
+.nav-icon {
349
+  font-size: 18px;
350
+  color: #1890ff;
351
+}
352
+
353
+.nav-separator {
354
+  color: #e8e8e8;
355
+}
356
+
357
+.nav-item {
358
+  font-size: 14px;
359
+  cursor: pointer;
360
+  transition: color 0.3s;
361
+  
362
+  &.gray {
363
+    color: #999;
364
+  }
365
+  
366
+  &.active {
367
+    font-weight: 500;
368
+    color: #000;
369
+  }
370
+  
371
+  &:hover {
372
+    color: #1890ff;
373
+  }
374
+}
375
+
376
+.nav-right {
377
+  display: flex;
378
+  gap: 12px;
379
+}
380
+
381
+/* 主体内容区 */
382
+.main-content {
383
+  display: flex;
384
+  flex: 1;
385
+  gap: 10px;
386
+}
387
+
388
+/* 左侧边栏 */
389
+.left-sidebar {
390
+  position: relative;
391
+  width: 250px;
392
+  overflow: hidden;
393
+  background-color: #fff;
394
+  border-radius: 8px;
395
+  box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
396
+  transition: all 0.3s ease;
397
+}
398
+
399
+.left-sidebar.collapsed {
400
+  width: 0;
401
+  padding: 0;
402
+  margin: 0;
403
+}
404
+
405
+.collapse-btn {
406
+  width: 24px;
407
+  height: 24px;
408
+  padding: 15px;
409
+  line-height: 24px;
410
+  text-align: center;
411
+  cursor: pointer;
412
+}
413
+
414
+.left-sidebar.collapsed .collapse-btn {
415
+  right: -15px;
416
+}
417
+
418
+.sidebar-content {
419
+  padding: 15px;
420
+  transition: opacity 0.3s ease;
421
+}
422
+
423
+.left-sidebar.collapsed .sidebar-content {
424
+  pointer-events: none;
425
+  opacity: 0;
426
+}
427
+
428
+.knowledge-info {
429
+  display: flex;
430
+  flex-direction: column;
431
+  align-items: flex-start;
432
+  margin-bottom: 12px;
433
+}
434
+
435
+.knowledge-icon {
436
+  display: flex;
437
+  align-items: center;
438
+  justify-content: center;
439
+  width: 48px;
440
+  height: 48px;
441
+  margin-bottom: 10px;
442
+  background-color: #1890ff;
443
+  border-radius: 8px;
444
+}
445
+
446
+.icon-large {
447
+  font-size: 32px;
448
+  color: #fff;
449
+}
450
+
451
+.knowledge-title {
452
+  font-size: 16px;
453
+  font-weight: 500;
454
+  text-align: center;
455
+  transition: opacity 0.3s;
456
+  
457
+  &.collapsed {
458
+    height: 0;
459
+    margin: 0;
460
+    overflow: hidden;
461
+    opacity: 0;
462
+  }
463
+}
464
+
465
+.home-btn {
466
+  display: flex;
467
+  gap: 8px;
468
+  align-items: center;
469
+  padding: 10px;
470
+  margin-bottom: 20px;
471
+  cursor: pointer;
472
+  background-color: #E8F3FF;
473
+  border-radius: 6px;
474
+  transition: background-color 0.3s;
475
+  
476
+  span {
477
+    transition: opacity 0.3s;
478
+  }
479
+  
480
+  &.collapsed {
481
+    span {
482
+      width: 0;
483
+      overflow: hidden;
484
+      opacity: 0;
485
+    }
486
+  }
487
+  
488
+  &:hover {
489
+    background-color: #e0f2fe;
490
+  }
491
+}
492
+
493
+.chapter-search {
494
+  margin-bottom: 20px;
495
+  transition: opacity 0.3s;
496
+  
497
+  &.collapsed {
498
+    height: 0;
499
+    margin: 0;
500
+    overflow: hidden;
501
+    opacity: 0;
502
+  }
503
+}
504
+
505
+.chapter-tree {
506
+  max-height: calc(100vh - 300px);
507
+  overflow-y: auto;
508
+}
509
+
510
+.tree-node-content {
511
+  display: flex;
512
+  align-items: center;
513
+  justify-content: space-between;
514
+  width: 100%;
515
+}
516
+
517
+.tree-node-actions {
518
+  display: none;
519
+  gap: 4px;
520
+}
521
+
522
+.tree-node-content:hover .tree-node-actions {
523
+  display: flex;
524
+}
525
+
526
+.tree-node-actions button {
527
+  margin-left: 0 !important;
528
+}
529
+
530
+.unsaved-icon {
531
+  margin-left: 4px;
532
+  color: #ff9800;
533
+}
534
+
535
+/* 中间内容区 */
536
+.center-content {
537
+  position: relative;
538
+  flex: 1;
539
+  padding: 20px;
540
+  overflow-y: auto;
541
+  background-color: #fff;
542
+  border-radius: 8px;
543
+  box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
544
+}
545
+
546
+/* 表单行样式 */
547
+.form-row {
548
+  margin-bottom: 15px;
549
+}
550
+
551
+/* 上传区域样式 */
552
+.upload-section {
553
+  width: 100%;
554
+}
555
+
556
+.upload-header {
557
+  display: flex;
558
+  gap: 12px;
559
+  align-items: center;
560
+  margin-bottom: 15px;
561
+}
562
+
563
+.upload-title {
564
+  font-weight: 500;
565
+  white-space: nowrap;
566
+}
567
+
568
+.upload-btn {
569
+  display: inline-flex;
570
+  gap: 4px;
571
+  align-items: center;
572
+  padding: 6px 12px;
573
+  margin-top: 10px;
574
+  font-size: 14px;
575
+  color: #606266;
576
+  cursor: pointer;
577
+  background-color: #fff;
578
+  border: 1px solid #dcdfe6;
579
+  border-radius: 4px;
580
+  transition: all 0.3s;
581
+
582
+  &:hover {
583
+    color: #409eff;
584
+    border-color: #409eff;
585
+  }
586
+}
587
+
588
+.upload-note {
589
+  font-size: 12px;
590
+  color: #909399;
591
+  white-space: nowrap;
592
+}
593
+
594
+/* 附件列表样式 */
595
+.el-upload-list {
596
+  display: grid;
597
+  grid-template-columns: repeat(3, 1fr);
598
+  gap: 10px;
599
+  margin-top: 15px;
600
+}
601
+
602
+.el-upload-list__item {
603
+  display: flex;
604
+  align-items: center;
605
+  justify-content: space-between;
606
+  padding: 8px 12px;
607
+  font-size: 14px;
608
+  color: #606266;
609
+  background-color: #fff;
610
+  border: 1px solid #ebeef5;
611
+  border-radius: 4px;
612
+}
613
+
614
+.el-upload-list__item-name {
615
+  flex: 1;
616
+  overflow: hidden;
617
+  text-overflow: ellipsis;
618
+  white-space: nowrap;
619
+  cursor: pointer;
620
+}
621
+
622
+.el-upload-list__item-delete {
623
+  color: #f56c6c;
624
+  cursor: pointer;
625
+  transition: color 0.3s;
626
+
627
+  &:hover {
628
+    color: #f78989;
629
+  }
630
+}
631
+
632
+.collapse-control-btn {
633
+  position: absolute;
634
+  top: 15px;
635
+  left: 10px;
636
+  z-index: 10;
637
+  padding: 4px;
638
+  margin-right: 10px;
639
+  font-size: 20px;
640
+  color: #666;
641
+  cursor: pointer;
642
+  border-radius: 4px;
643
+  transition: color 0.3s;
644
+}
645
+
646
+.collapse-control-btn:hover {
647
+  color: #1890ff;
648
+  background-color: #f0f9ff;
649
+}
650
+
651
+.content-header {
652
+  margin-bottom: 20px;
653
+  margin-left: 20px;
654
+}
655
+
656
+.content-title {
657
+  margin-bottom: 8px;
658
+  font-size: 24px;
659
+  font-weight: 500;
660
+}
661
+
662
+.content-meta {
663
+  display: flex;
664
+  gap: 4px;
665
+  align-items: center;
666
+  font-size: 12px;
667
+  color: #999;
668
+}
669
+
670
+.content-body {
671
+  line-height: 1.8;
672
+}
673
+
674
+.content-text {
675
+  margin-bottom: 30px;
676
+  font-size: 14px;
677
+}
678
+
679
+.content-attachments {
680
+  padding-top: 20px;
681
+  border-top: 1px solid #e8e8e8;
682
+}
683
+
684
+/* 右侧边栏 */
685
+.right-sidebar {
686
+  display: flex;
687
+  flex-direction: column;
688
+  gap: 20px;
689
+  width: 320px;
690
+}
691
+
692
+.comment-section {
693
+  display: flex;
694
+  flex-direction: column;
695
+  gap: 20px;
696
+  padding: 20px;
697
+  background-color: #fff;
698
+  border-radius: 8px;
699
+  box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
700
+}
701
+
702
+.section-title {
703
+  display: flex;
704
+  gap: 8px;
705
+  align-items: center;
706
+  padding-left: 12px;
707
+  margin-bottom: 20px;
708
+  font-size: 16px;
709
+  font-weight: 500;
710
+  color: #000;
711
+  border-left: 3px solid #1890ff;
712
+}
713
+
714
+.write-comment {
715
+  display: flex;
716
+  flex-direction: column;
717
+  gap: 15px;
718
+  max-height: 300px;
719
+  padding-right: 5px;
720
+  margin-bottom: 20px;
721
+  overflow-y: auto;
722
+}
723
+
724
+.write-comment::-webkit-scrollbar {
725
+  width: 6px;
726
+}
727
+
728
+.write-comment::-webkit-scrollbar-track {
729
+  background: #f1f1f1;
730
+  border-radius: 3px;
731
+}
732
+
733
+.write-comment::-webkit-scrollbar-thumb {
734
+  background: #c1c1c1;
735
+  border-radius: 3px;
736
+}
737
+
738
+.write-comment::-webkit-scrollbar-thumb:hover {
739
+  background: #a8a8a8;
740
+}
741
+
742
+.comment-row {
743
+  display: flex;
744
+  gap: 10px;
745
+  align-items: flex-start;
746
+}
747
+
748
+.row-label {
749
+  flex-shrink: 0;
750
+  width: 60px;
751
+  padding-top: 6px;
752
+  font-size: 14px;
753
+  font-weight: 500;
754
+  color: #000;
755
+  text-align: right;
756
+}
757
+
758
+.row-content {
759
+  display: flex;
760
+  flex: 1;
761
+  flex-direction: column;
762
+  gap: 10px;
763
+}
764
+
765
+.photo-area {
766
+  display: flex;
767
+  gap: 15px;
768
+  align-items: center;
769
+}
770
+
771
+.photo-preview,
772
+.photo-placeholder {
773
+  position: relative;
774
+  display: flex;
775
+  align-items: center;
776
+  justify-content: center;
777
+  width: 80px;
778
+  height: 80px;
779
+  background-color: #fafafa;
780
+  border: 1px solid #e8e8e8;
781
+  border-radius: 4px;
782
+}
783
+
784
+.photo-preview img {
785
+  width: 100%;
786
+  height: 100%;
787
+  object-fit: cover;
788
+  border-radius: 4px;
789
+}
790
+
791
+.remove-photo {
792
+  position: absolute;
793
+  top: -8px;
794
+  right: -8px;
795
+  display: flex;
796
+  align-items: center;
797
+  justify-content: center;
798
+  width: 20px;
799
+  height: 20px;
800
+  padding: 0;
801
+  font-size: 14px;
802
+  line-height: 1;
803
+  color: #fff;
804
+  background-color: rgb(0 0 0 / 50%);
805
+  border-radius: 50%;
806
+}
807
+
808
+.comment-submit {
809
+  display: flex;
810
+  justify-content: flex-start;
811
+  margin-top: 10px;
812
+}
813
+
814
+.comment-submit .el-button {
815
+  padding: 8px 16px;
816
+  margin-left: 20px;
817
+  font-size: 14px;
818
+}
819
+
820
+.upload-btn {
821
+  display: flex;
822
+  align-items: center;
823
+}
824
+
825
+
826
+
827
+.comment-list {
828
+  display: flex;
829
+  flex-direction: column;
830
+  gap: 25px;
831
+
832
+  /* max-height: 400px; */
833
+
834
+  /* overflow-y: auto; */
835
+}
836
+
837
+.comment-item {
838
+  display: flex;
839
+  gap: 12px;
840
+  padding-bottom: 15px;
841
+}
842
+
843
+.comment-avatar {
844
+  width: 36px;
845
+  height: 36px;
846
+  margin-top: 2px;
847
+  color: #fff;
848
+  background-color: #1890ff;
849
+}
850
+
851
+.comment-content {
852
+  display: flex;
853
+  flex: 1;
854
+  flex-direction: column;
855
+  gap: 8px;
856
+}
857
+
858
+/* 第一行:头像、姓名、岗位 */
859
+.comment-header {
860
+  display: flex;
861
+  gap: 8px;
862
+  align-items: center;
863
+  font-size: 14px;
864
+}
865
+
866
+.comment-username {
867
+  font-weight: 500;
868
+  color: #000;
869
+}
870
+
871
+.comment-position {
872
+  font-size: 12px;
873
+  color: #666;
874
+}
875
+
876
+/* 第二行:评论内容 */
877
+.comment-text {
878
+  font-size: 14px;
879
+  line-height: 1.5;
880
+  color: #666;
881
+}
882
+
883
+.comment-images {
884
+  display: flex;
885
+  gap: 8px;
886
+  margin-bottom: 5px;
887
+}
888
+
889
+.comment-image {
890
+  width: 80px;
891
+  height: 80px;
892
+  object-fit: cover;
893
+  border-radius: 4px;
894
+}
895
+
896
+/* 第三行:时间和回复按钮 */
897
+.comment-meta {
898
+  display: flex;
899
+  align-items: center;
900
+  justify-content: space-between;
901
+  font-size: 12px;
902
+  color: #999;
903
+}
904
+
905
+.reply-btn {
906
+  padding: 0 !important;
907
+  margin: 0 !important;
908
+  font-size: 12px !important;
909
+  color: #1890ff !important;
910
+}
911
+
912
+/* 第四行:历史回复消息 */
913
+.reply-list {
914
+  display: flex;
915
+  flex-direction: column;
916
+  gap: 8px;
917
+}
918
+
919
+.reply-item {
920
+  padding: 10px;
921
+  font-size: 14px;
922
+  color: #666;
923
+  background-color: #f5f5f5;
924
+  border-radius: 4px;
925
+}
926
+
927
+.reply-text {
928
+  display: block;
929
+  line-height: 1.4;
930
+}
931
+
932
+.reply-text .reply-username {
933
+  margin-right: 5px;
934
+  font-weight: 500;
935
+  color: #000;
936
+}
937
+
938
+/* 回复输入框 */
939
+.reply-input-wrapper {
940
+  padding: 15px;
941
+  margin-top: 12px;
942
+  background-color: #fafafa;
943
+  border: 1px solid #e8e8e8;
944
+  border-radius: 4px;
945
+}
946
+
947
+.reply-input {
948
+  margin-bottom: 10px;
949
+  border-radius: 4px;
950
+}
951
+
952
+.reply-actions {
953
+  display: flex;
954
+  gap: 10px;
955
+  justify-content: flex-start;
956
+}
957
+
958
+.submit-btn {
959
+  color: #fff;
960
+  background-color: #1890ff;
961
+  border-color: #1890ff;
962
+}
963
+
964
+.cancel-btn {
965
+  color: #606266;
966
+  background-color: #fff;
967
+  border-color: #dcdfe6;
968
+}
969
+</style>
970
+

+ 0 - 195
apps/web-ele/src/views/knowledge/type/config-data.tsx

@@ -1,195 +0,0 @@
1
-import type { FormSchemaGetter } from '#/adapter/form';
2
-import type { VxeGridProps } from '#/adapter/vxe-table';
3
-
4
-// 知识库类别数据模型
5
-export interface KnowledgeType {
6
-  id: string;
7
-  name: string;
8
-  icon: string;
9
-  description: string;
10
-  admin: string;
11
-  applicableEnd: string;
12
-  applicablePost: string;
13
-  createTime: string;
14
-  createUser: string;
15
-}
16
-
17
-// 查询表单配置
18
-export const queryFormSchema: FormSchemaGetter = () => [
19
-  {
20
-    component: 'Input',
21
-    fieldName: 'name',
22
-    label: '名称',
23
-    componentProps: {
24
-      placeholder: '请输入知识库类别名称',
25
-      allowClear: true,
26
-    },
27
-  },
28
-  {
29
-    component: 'Input',
30
-    fieldName: 'description',
31
-    label: '描述',
32
-    componentProps: {
33
-      placeholder: '请输入知识库类别描述',
34
-      allowClear: true,
35
-    },
36
-  },
37
-];
38
-
39
-// 抽屉表单配置
40
-export const drawerFormSchema: FormSchemaGetter = () => [
41
-  {
42
-    component: 'Input',
43
-    dependencies: {
44
-      show: () => false,
45
-      triggerFields: [''],
46
-    },
47
-    fieldName: 'id',
48
-  },
49
-
50
-  {
51
-    component: 'Input',
52
-    fieldName: 'name',
53
-    label: '知识库名称',
54
-    componentProps: {
55
-      placeholder: '请输入知识库名称',
56
-      maxlength: 100,
57
-    },
58
-    rules: 'required',
59
-  },
60
-  {
61
-    component: 'Input',
62
-    fieldName: 'description',
63
-    label: '知识库描述',
64
-    componentProps: {
65
-      placeholder: '请输入知识库描述',
66
-      maxlength: 200,
67
-      type: 'textarea',
68
-      showWordLimit: true,
69
-    },
70
-    rules: 'required',
71
-  },
72
-  {
73
-    component: 'Input',
74
-    fieldName: 'icon',
75
-    label: '知识图标',
76
-    // 使用插槽自定义上传组件
77
-    rules: 'required',
78
-  },
79
-  {
80
-    component: 'Input',
81
-    fieldName: 'admin',
82
-    label: '知识库管理员',
83
-    componentProps: {
84
-      placeholder: '请输入知识库管理员',
85
-      maxlength: 50,
86
-    },
87
-    rules: 'required',
88
-  },
89
-  {
90
-    component: 'Select',
91
-    componentProps: {
92
-      placeholder: '请选择适用端',
93
-      options: [
94
-        { label: '钉钉', value: 'dingtalk' },
95
-        { label: 'PC', value: 'pc' },
96
-      ],
97
-    },
98
-    fieldName: 'applicableEnd',
99
-    label: '适用端',
100
-    rules: 'required',
101
-  },
102
-  {
103
-    component: 'Select',
104
-    componentProps: {
105
-      placeholder: '请选择适用岗位',
106
-      options: [
107
-        { label: '管理员', value: 'admin' },
108
-        { label: '普通用户', value: 'user' },
109
-        { label: '技术人员', value: 'tech' },
110
-      ],
111
-    },
112
-    fieldName: 'applicablePost',
113
-    label: '适用岗位',
114
-    rules: 'required',
115
-  },
116
-];
117
-
118
-// 表格列配置
119
-export const tableColumns: VxeGridProps['columns'] = [
120
-  {
121
-    type: 'checkbox',
122
-    width: 60,
123
-  },
124
-  {
125
-    field: 'icon',
126
-    slots: { default: 'icon' },
127
-    title: '类别图标',
128
-    minWidth: 100,
129
-  },
130
-  {
131
-    field: 'id',
132
-    title: '类别ID',
133
-    width: 80,
134
-  },
135
-  {
136
-    field: 'name',
137
-    title: '类别名称',
138
-    minWidth: 120,
139
-  },
140
-
141
-  {
142
-    field: 'description',
143
-    title: '类别描述',
144
-    minWidth: 150,
145
-  },
146
-  {
147
-    field: 'admin',
148
-    title: '管理员',
149
-    minWidth: 100,
150
-  },
151
-  {
152
-    field: 'applicableEnd',
153
-    title: '适用端',
154
-    minWidth: 100,
155
-    formatter: (params: { cellValue: string }) => {
156
-      const options = [
157
-        { label: '钉钉', value: 'dingtalk' },
158
-        { label: 'PC', value: 'pc' },
159
-      ];
160
-      const option = options.find((item) => item.value === params.cellValue);
161
-      return option ? option.label : params.cellValue;
162
-    },
163
-  },
164
-  {
165
-    field: 'applicablePost',
166
-    title: '适用岗位',
167
-    minWidth: 100,
168
-    formatter: (params: { cellValue: string }) => {
169
-      const options = [
170
-        { label: '管理员', value: 'admin' },
171
-        { label: '普通用户', value: 'user' },
172
-        { label: '技术人员', value: 'tech' },
173
-      ];
174
-      const option = options.find((item) => item.value === params.cellValue);
175
-      return option ? option.label : params.cellValue;
176
-    },
177
-  },
178
-  {
179
-    field: 'createUser',
180
-    title: '创建人',
181
-    minWidth: 100,
182
-  },
183
-  {
184
-    field: 'createTime',
185
-    title: '创建时间',
186
-    minWidth: 150,
187
-  },
188
-  {
189
-    field: 'action',
190
-    fixed: 'right',
191
-    slots: { default: 'action' },
192
-    title: '操作',
193
-    width: 180,
194
-  },
195
-];

+ 374 - 0
apps/web-ele/src/views/knowledge/type/create.vue

@@ -0,0 +1,374 @@
1
+<script lang="ts" setup>
2
+import { ref, watch } from 'vue';
3
+import { ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElUpload, ElMessage, ElButton, ElIcon } from 'element-plus';
4
+import { Upload, Plus } from '@element-plus/icons-vue';
5
+
6
+// 定义表单数据类型
7
+interface FormData {
8
+  name: string;
9
+  description: string;
10
+  icon: string;
11
+  managers: any[];
12
+  visibleApps: any[];
13
+  visiblePositions: any[];
14
+  association: string;
15
+}
16
+
17
+// 定义组件的props
18
+const props = defineProps<{
19
+  mode: string;
20
+  initialData?: FormData;
21
+}>();
22
+
23
+// 定义事件
24
+const emit = defineEmits(['close', 'save']);
25
+
26
+// 表单引用
27
+const formRef = ref();
28
+
29
+// 表单数据
30
+const form = ref<FormData>({
31
+  name: '',
32
+  description: '',
33
+  icon: '',
34
+  managers: [],
35
+  visibleApps: [],
36
+  visiblePositions: [],
37
+  association: '',
38
+});
39
+
40
+// 表单规则
41
+const rules = {
42
+  name: [
43
+    { required: true, message: '请输入知识库名称', trigger: 'blur' },
44
+    { max: 50, message: '知识库名称不能超过50个字符', trigger: 'blur' },
45
+  ],
46
+  description: [
47
+    { required: true, message: '请输入知识库描述', trigger: 'blur' },
48
+    { max: 200, message: '知识库描述不能超过200个字符', trigger: 'blur' },
49
+  ],
50
+  icon: [
51
+    { required: true, message: '请选择知识库图标', trigger: 'change' },
52
+  ],
53
+};
54
+
55
+// 默认图标选项
56
+const defaultIcons = [
57
+  { name: '蓝色书籍', value: 'blue-book' },
58
+  { name: '橙色书籍', value: 'orange-book' },
59
+  { name: '黄色书籍', value: 'yellow-book' },
60
+  { name: '绿色书籍', value: 'green-book' },
61
+  { name: '紫色书籍', value: 'purple-book' },
62
+  { name: '蓝色列表', value: 'blue-list' },
63
+  { name: '橙色列表', value: 'orange-list' },
64
+  { name: '黄色列表', value: 'yellow-list' },
65
+  { name: '绿色列表', value: 'green-list' },
66
+  { name: '紫色列表', value: 'purple-list' },
67
+];
68
+
69
+// 监听初始数据变化
70
+watch(
71
+  () => props.initialData,
72
+  (newData) => {
73
+    if (newData) {
74
+      form.value = { ...newData };
75
+    }
76
+  },
77
+  { deep: true, immediate: true }
78
+);
79
+
80
+// 上传图片处理
81
+const uploadFile = (file: any) => {
82
+  // 这里可以添加图片上传逻辑
83
+  ElMessage.success('图片上传成功');
84
+  form.value.icon = URL.createObjectURL(file.raw);
85
+  return false; // 阻止默认上传行为
86
+};
87
+
88
+// 选择默认图标
89
+const selectDefaultIcon = (iconValue: string) => {
90
+  form.value.icon = iconValue;
91
+};
92
+
93
+// 表单提交
94
+const submitForm = () => {
95
+  // 这里可以添加表单提交逻辑
96
+  emit('save', form.value);
97
+  ElMessage.success(props.mode === 'create' ? '创建成功' : '编辑成功');
98
+};
99
+
100
+// 关闭表单
101
+const closeForm = () => {
102
+  emit('close');
103
+};
104
+</script>
105
+
106
+<template>
107
+  <ElForm
108
+    :model="form"
109
+    :rules="rules"
110
+    ref="formRef"
111
+    label-position="top"
112
+    class="knowledge-form"
113
+  >
114
+    <!-- 知识库名称 -->
115
+    <ElFormItem label="知识库名称" prop="name">
116
+      <ElInput
117
+        v-model="form.name"
118
+        placeholder="请输入"
119
+        maxlength="50"
120
+        show-word-limit
121
+      />
122
+    </ElFormItem>
123
+
124
+    <!-- 知识库描述 -->
125
+    <ElFormItem label="知识库描述" prop="description">
126
+      <ElInput
127
+        v-model="form.description"
128
+        type="textarea"
129
+        placeholder="请输入"
130
+        maxlength="200"
131
+        show-word-limit
132
+        :rows="4"
133
+      />
134
+    </ElFormItem>
135
+
136
+    <!-- 知识库图标 -->
137
+    <ElFormItem label="知识库图标" prop="icon">
138
+      <ElUpload
139
+        :show-file-list="false"
140
+        :auto-upload="false"
141
+        :on-change="uploadFile"
142
+      >
143
+        <div class="icon-upload">
144
+          <ElIcon><Upload /></ElIcon>
145
+          <div class="upload-text">上传图标</div>
146
+        </div>
147
+      </ElUpload>
148
+      <div v-if="form.icon" class="icon-preview">
149
+        <img :src="form.icon" alt="预览" />
150
+      </div>
151
+      <div class="icon-default">
152
+        <div class="section-title">预览可选图标</div>
153
+        <div class="icon-list">
154
+          <div
155
+            v-for="icon in defaultIcons"
156
+            :key="icon.value"
157
+            class="icon-item"
158
+            :class="{ active: form.icon === icon.value }"
159
+            @click="selectDefaultIcon(icon.value)"
160
+          >
161
+            <!-- 这里可以根据icon.value显示不同的图标 -->
162
+            <div class="icon-placeholder" :class="icon.value"></div>
163
+          </div>
164
+        </div>
165
+      </div>
166
+    </ElFormItem>
167
+
168
+    <!-- 知识库管理员 -->
169
+    <ElFormItem label="知识库管理员(管理员具有知识库编辑、删除的权限)" prop="managers">
170
+      <ElSelect
171
+        v-model="form.managers"
172
+        placeholder="可多选"
173
+        multiple
174
+        filterable
175
+      >
176
+        <ElOption label="管理员1" value="admin1" />
177
+        <ElOption label="管理员2" value="admin2" />
178
+        <ElOption label="管理员3" value="admin3" />
179
+      </ElSelect>
180
+    </ElFormItem>
181
+
182
+    <!-- 可见范围 -->
183
+    <ElFormItem label="可见范围(可查看该知识库权限)">
184
+      <div class="visible-range" style="width: 100%;">
185
+        <ElFormItem label="端" label-position="right" prop="visibleApps">
186
+          <ElSelect
187
+            v-model="form.visibleApps"
188
+            placeholder="可多选"
189
+            multiple
190
+            filterable
191
+            style="width: 100%"
192
+          >
193
+            <ElOption label="PC端" value="pc" />
194
+            <ElOption label="移动端" value="mobile" />
195
+            <ElOption label="小程序" value="miniprogram" />
196
+          </ElSelect>
197
+        </ElFormItem>
198
+        <ElFormItem label="岗位" label-position="right" prop="visiblePositions">
199
+          <ElSelect
200
+            v-model="form.visiblePositions"
201
+            placeholder="可多选"
202
+            multiple
203
+            filterable
204
+            style="width: 100%"
205
+          >
206
+            <ElOption label="经理" value="manager" />
207
+            <ElOption label="员工" value="employee" />
208
+            <ElOption label="实习生" value="intern" />
209
+          </ElSelect>
210
+        </ElFormItem>
211
+      </div>
212
+    </ElFormItem>
213
+
214
+    <!-- 知识库关联 -->
215
+    <ElFormItem label="知识库关联(关联后,两个知识库内容同步编辑)" prop="association">
216
+      <ElSelect
217
+        v-model="form.association"
218
+        placeholder="单选"
219
+        filterable
220
+      >
221
+        <ElOption label="无关联" value="" />
222
+        <ElOption label="知识库A" value="knowledgeA" />
223
+        <ElOption label="知识库B" value="knowledgeB" />
224
+      </ElSelect>
225
+    </ElFormItem>
226
+
227
+    <!-- 操作按钮 -->
228
+    <div class="form-actions">
229
+      <ElButton type="primary" @click="submitForm">确定</ElButton>
230
+      <ElButton @click="closeForm">取消</ElButton>
231
+    </div>
232
+  </ElForm>
233
+</template>
234
+
235
+<style lang="scss" scoped>
236
+.knowledge-form {
237
+  padding: 20px;
238
+}
239
+
240
+.icon-upload {
241
+  display: flex;
242
+  flex-direction: column;
243
+  align-items: center;
244
+  justify-content: center;
245
+  width: 120px;
246
+  height: 120px;
247
+  cursor: pointer;
248
+  border: 1px dashed #d9d9d9;
249
+  border-radius: 6px;
250
+  transition: all 0.3s;
251
+  
252
+  &:hover {
253
+    border-color: #409eff;
254
+  }
255
+  
256
+  .upload-text {
257
+    margin-top: 8px;
258
+    font-size: 14px;
259
+    color: #666;
260
+  }
261
+}
262
+
263
+.icon-preview {
264
+  margin: 16px 0;
265
+  
266
+  img {
267
+    width: 120px;
268
+    height: 120px;
269
+    object-fit: cover;
270
+    border-radius: 6px;
271
+  }
272
+}
273
+
274
+.icon-default {
275
+  margin-top: 24px;
276
+  
277
+  .section-title {
278
+    margin-bottom: 16px;
279
+    font-size: 14px;
280
+    font-weight: 500;
281
+    color: #333;
282
+  }
283
+  
284
+  .icon-list {
285
+    display: flex;
286
+    flex-wrap: wrap;
287
+    gap: 16px;
288
+  }
289
+  
290
+  .icon-item {
291
+    display: flex;
292
+    align-items: center;
293
+    justify-content: center;
294
+    width: 60px;
295
+    height: 60px;
296
+    cursor: pointer;
297
+    border: 2px solid transparent;
298
+    border-radius: 6px;
299
+    transition: all 0.3s;
300
+    
301
+    &:hover {
302
+      border-color: #409eff;
303
+    }
304
+    
305
+    &.active {
306
+      background-color: #e6f7ff;
307
+      border-color: #409eff;
308
+    }
309
+    
310
+    .icon-placeholder {
311
+      width: 40px;
312
+      height: 40px;
313
+      border-radius: 4px;
314
+    }
315
+    
316
+    .blue-book {
317
+      background-color: #3370ff;
318
+    }
319
+    
320
+    .orange-book {
321
+      background-color: #ff7d00;
322
+    }
323
+    
324
+    .yellow-book {
325
+      background-color: #ffc107;
326
+    }
327
+    
328
+    .green-book {
329
+      background-color: #00b42a;
330
+    }
331
+    
332
+    .purple-book {
333
+      background-color: #9254de;
334
+    }
335
+    
336
+    .blue-list {
337
+      background-color: #3370ff;
338
+    }
339
+    
340
+    .orange-list {
341
+      background-color: #ff7d00;
342
+    }
343
+    
344
+    .yellow-list {
345
+      background-color: #ffc107;
346
+    }
347
+    
348
+    .green-list {
349
+      background-color: #00b42a;
350
+    }
351
+    
352
+    .purple-list {
353
+      background-color: #9254de;
354
+    }
355
+  }
356
+}
357
+
358
+.visible-range {
359
+  display: flex;
360
+  flex-direction: column;
361
+  gap: 16px;
362
+  padding: 16px;
363
+  background-color: #f5f7fa;
364
+  border-radius: 6px;
365
+}
366
+
367
+.form-actions {
368
+  display: flex;
369
+  gap: 12px;
370
+  justify-content: flex-end;
371
+  margin-top: 32px;
372
+}
373
+</style>
374
+

+ 256 - 181
apps/web-ele/src/views/knowledge/type/index.vue

@@ -1,203 +1,278 @@
1
-<script setup lang="ts">
2
-import type { VbenFormProps } from '@vben/common-ui';
3
-
4
-import type { KnowledgeType } from './config-data';
5
-
6
-import type { VxeGridProps } from '#/adapter/vxe-table';
7
-
1
+<script lang="ts" setup>
2
+import { Page } from '@vben/common-ui';
3
+import { ElButton, ElCard, ElIcon, ElPagination } from 'element-plus';
4
+import { Edit, Delete } from '@element-plus/icons-vue';
5
+import KnowledgeCreate from './create.vue';
6
+import { ref } from 'vue';
8 7
 import { useRouter } from 'vue-router';
9 8
 
10
-import { Page, useVbenDrawer } from '@vben/common-ui';
9
+// 导入 FormData 类型
10
+interface FormData {
11
+  name: string;
12
+  description: string;
13
+  icon: string;
14
+  managers: any[];
15
+  visibleApps: any[];
16
+  visiblePositions: any[];
17
+  association: string;
18
+}
11 19
 
12
-import { ElImage, ElMessageBox } from 'element-plus';
20
+// 路由实例
21
+const router = useRouter();
13 22
 
14
-import { useVbenVxeGrid } from '#/adapter/vxe-table';
23
+// 示例数据
24
+const categories = [
25
+  { id: 1, name: '悬浮态', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
26
+  { id: 2, name: '常规态', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
27
+  { id: 3, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
28
+  { id: 4, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Document', color: '#9254DE' },
29
+  { id: 5, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#FF7D00' },
30
+  { id: 6, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#00B42A' },
31
+  { id: 7, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#FF7D00' },
32
+  { id: 8, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#9254DE' },
33
+  { id: 9, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
34
+  { id: 10, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
35
+  { id: 11, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
36
+  { id: 12, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
37
+  { id: 13, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
38
+  { id: 14, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
39
+  { id: 15, name: '我的文件', description: '位使用人员提供了知识管理场景示例', creator: '张三', createTime: '2025-01-01', icon: 'Menu', color: '#3370FF' },
40
+];
15 41
 
16
-import { queryFormSchema, tableColumns } from './config-data';
17
-import TypeDrawer from './type-drawer.vue';
42
+// 抽屉控制
43
+const drawerVisible = ref(false);
44
+const drawerMode = ref('create');
45
+const selectedCategory = ref<FormData>({
46
+  name: '',
47
+  description: '',
48
+  icon: '',
49
+  managers: [],
50
+  visibleApps: [],
51
+  visiblePositions: [],
52
+  association: '',
53
+});
18 54
 
19
-const router = useRouter();
55
+// 跳转到详情页
56
+const navigateToDetail = (id: number) => {
57
+  router.push(`/knowledge/detail/${id}`);
58
+};
20 59
 
21
-// 模拟API调用,实际项目中需要替换为真实API
22
-const getKnowledgeTypeList = async (params: any) => {
23
-  // 模拟数据
24
-  const mockData = {
25
-    rows: [
26
-      {
27
-        id: '1',
28
-        name: '产品知识',
29
-        icon: '/icon/1.png',
30
-        description: '产品相关知识',
31
-        admin: 'admin',
32
-        applicableEnd: 'pc',
33
-        applicablePost: 'admin',
34
-        createTime: '2024-01-01 10:00:00',
35
-        createUser: 'admin',
36
-      },
37
-      {
38
-        id: '2',
39
-        name: '技术文档',
40
-        icon: '/icon/3.png',
41
-        description: '技术相关文档',
42
-        admin: 'admin',
43
-        applicableEnd: 'dingtalk',
44
-        applicablePost: 'tech',
45
-        createTime: '2024-01-02 11:00:00',
46
-        createUser: 'admin',
47
-      },
48
-      {
49
-        id: '3',
50
-        name: '常见问题',
51
-        icon: '/icon/4.png',
52
-        description: '常见问题解答',
53
-        admin: 'admin',
54
-        applicableEnd: 'pc',
55
-        applicablePost: 'user',
56
-        createTime: '2024-01-03 14:00:00',
57
-        createUser: 'admin',
58
-      },
59
-      {
60
-        id: '4',
61
-        name: '用户指南',
62
-        icon: 'carbon:book-open',
63
-        description: '用户使用指南',
64
-        admin: 'admin',
65
-        applicableEnd: 'dingtalk',
66
-        applicablePost: 'user',
67
-        createTime: '2024-01-04 16:00:00',
68
-        createUser: 'admin',
69
-      },
70
-    ],
71
-    total: 4,
60
+// 打开创建抽屉
61
+const openCreateDrawer = () => {
62
+  drawerMode.value = 'create';
63
+  selectedCategory.value = {
64
+    name: '',
65
+    description: '',
66
+    icon: '',
67
+    managers: [],
68
+    visibleApps: [],
69
+    visiblePositions: [],
70
+    association: '',
72 71
   };
73
-  return mockData;
72
+  drawerVisible.value = true;
74 73
 };
75 74
 
76
-// 定义侧拉
77
-const [TypeDrawerCom, typeDrawerApi] = useVbenDrawer({
78
-  connectedComponent: TypeDrawer,
79
-});
80
-
81
-const deleteKnowledgeType = async (ids: string[]) => {
82
-  // 模拟删除操作
83
-  // console.log('删除知识库类别', ids);
84
-  return { success: true };
75
+// 打开编辑抽屉
76
+const openEditDrawer = (category: any) => {
77
+  drawerMode.value = 'edit';
78
+  selectedCategory.value = {
79
+    name: category.name || '',
80
+    description: category.description || '',
81
+    icon: category.icon || '',
82
+    managers: [],
83
+    visibleApps: [],
84
+    visiblePositions: [],
85
+    association: '',
86
+  };
87
+  drawerVisible.value = true;
85 88
 };
86 89
 
87
-// 查询表单配置
88
-const formOptions: VbenFormProps = {
89
-  commonConfig: {
90
-    labelWidth: 80,
91
-    componentProps: {
92
-      allowClear: true,
93
-    },
94
-  },
95
-  schema: queryFormSchema(),
96
-  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
90
+// 关闭抽屉
91
+const closeDrawer = () => {
92
+  drawerVisible.value = false;
97 93
 };
98 94
 
99
-// 列表配置
100
-const gridOptions: VxeGridProps = {
101
-  checkboxConfig: {
102
-    // 高亮
103
-    highlight: true,
104
-    // 翻页时保留选中状态
105
-    reserve: true,
106
-    // 点击行选中
107
-    trigger: 'default',
108
-  },
109
-  columns: tableColumns,
110
-  size: 'medium',
111
-  height: 'auto',
112
-  proxyConfig: {
113
-    ajax: {
114
-      query: async ({ page }, formValues = {}) => {
115
-        const resp = await getKnowledgeTypeList({
116
-          ...formValues,
117
-          pageNum: page.currentPage,
118
-          pageSize: page.pageSize,
119
-        });
120
-        return { items: resp.rows, total: resp.total };
121
-      },
122
-    },
123
-  },
124
-  rowConfig: {
125
-    keyField: 'id',
126
-  },
127
-  toolbarConfig: {
128
-    custom: true,
129
-    refresh: true,
130
-    zoom: true,
131
-  },
132
-  id: 'knowledge-type-index',
95
+// 保存知识类别
96
+const saveCategory = (data: any) => {
97
+  if (drawerMode.value === 'create') {
98
+    // 这里可以添加创建逻辑
99
+    console.log('创建知识库:', data);
100
+  } else {
101
+    // 这里可以添加编辑逻辑
102
+    console.log('编辑知识库:', data);
103
+  }
133 104
 };
105
+</script>
106
+<template>
107
+<Page>
108
+<div class="wrap">
109
+    <!-- 页面头部 -->
110
+    <div class="header">
111
+      <ElButton type="primary" @click="openCreateDrawer">+ 添加类别</ElButton>
112
+      <!-- <ElButton type="default">管理</ElButton> -->
113
+    </div>
114
+    <!-- 卡片列表 -->
115
+    <div class="card-list">
116
+      <div v-for="category in categories" :key="category.id" class="card-item">
117
+        <ElCard :bordered="false" class="category-card" @click="navigateToDetail(category.id)">
118
+          <div class="card-content">
119
+            <!-- 左侧图标 -->
120
+            <div class="card-icon" :style="{ backgroundColor: category.color + '20', color: category.color }">
121
+              <ElIcon :icon="category.icon" size="24" />
122
+            </div>
123
+            <!-- 中间内容 -->
124
+            <div class="card-main">
125
+              <h3 class="card-title">{{ category.name }}</h3>
126
+              <p class="card-description">{{ category.description }}</p>
127
+            </div>
128
+            <!-- 右侧信息 -->
129
+            <div class="card-info">
130
+              <div class="creator">{{ category.creator }}</div>
131
+              <div class="create-time">{{ category.createTime }}</div>
132
+              <!-- 操作按钮 -->
133
+              <div class="card-actions">
134
+                <ElButton type="text" :icon="Edit" circle @click.stop="openEditDrawer(category)" />
135
+              <ElButton type="text" :icon="Delete" circle />
136
+              </div>
137
+            </div>
138
+          </div>
139
+        </ElCard>
140
+      </div>
141
+    </div>
142
+    <!-- 分页 -->
143
+    <div class="pagination">
144
+      <ElPagination 
145
+        layout="prev, pager, next" 
146
+        :total="categories.length" 
147
+        :page-size="15"
148
+      />
149
+    </div>
150
+    <!-- 创建/编辑抽屉 -->
151
+    <ElDrawer
152
+      v-model="drawerVisible"
153
+      :title="drawerMode === 'create' ? '创建知识库' : '编辑知识库'"
154
+      size="30%"
155
+      direction="rtl"
156
+      @close="closeDrawer"
157
+    >
158
+      <KnowledgeCreate
159
+        :mode="drawerMode"
160
+        :initial-data="selectedCategory"
161
+        @close="closeDrawer"
162
+        @save="saveCategory"
163
+      />
164
+    </ElDrawer>
165
+</div>
166
+</Page>
167
+</template>
168
+<style lang="scss" scoped>
169
+.wrap {
170
+padding: 16px;
171
+background-color: #fff;
172
+border-radius: 6px;
173
+}
134 174
 
135
-// 创建列表实例
136
-const [BasicTable, BasicTableApi] = useVbenVxeGrid({
137
-  formOptions,
138
-  gridOptions,
139
-});
175
+.header {
176
+  display: flex;
177
+  gap: 12px;
178
+  margin-bottom: 16px;
179
+}
140 180
 
141
-// 新增知识库类别
142
-const handleAdd = () => {
143
-  // 调用抽屉组件的open方法
144
-  typeDrawerApi.setData({ isUpdate: false }).open();
145
-};
181
+.card-list {
182
+  display: grid;
183
+  grid-template-columns: repeat(3, 1fr);
184
+  gap: 16px;
185
+  margin-bottom: 24px;
186
+}
146 187
 
147
-// 编辑知识库类别
148
-const handleEdit = (row: KnowledgeType) => {
149
-  // 调用抽屉组件的open方法
150
-  typeDrawerApi.setData({ id: row.id, isUpdate: true }).open();
151
-};
188
+.category-card {
189
+  height: 100%;
190
+  cursor: pointer;
191
+  transition: all 0.3s ease;
192
+}
152 193
 
153
-// 查看知识库类别详情
154
-const handleView = (row: KnowledgeType) => {
155
-  // 实际项目中可以跳转到详情页面
194
+.category-card:hover {
195
+  background-color: #E8F3FF;
196
+}
156 197
 
157
-  router.push(`/knowledge/detail/${row.id}`);
158
-};
198
+.category-card:hover .card-title {
199
+  color: #2D64D0;
200
+}
159 201
 
160
-// 删除知识库类别
161
-const handleDelete = async (row: KnowledgeType) => {
162
-  try {
163
-    await ElMessageBox.confirm(`确认删除知识库类别"${row.name}"吗?`, '提示', {
164
-      confirmButtonText: '确定',
165
-      cancelButtonText: '取消',
166
-      type: 'warning',
167
-    });
168
-    await deleteKnowledgeType([row.id]);
169
-    await BasicTableApi.reload();
170
-  } catch {
171
-    // 取消删除操作
172
-  }
173
-};
174
-</script>
202
+.category-card:hover .creator,
203
+.category-card:hover .create-time {
204
+  display: none;
205
+}
206
+
207
+.category-card:hover .card-actions {
208
+  display: flex;
209
+}
210
+
211
+.card-content {
212
+  display: flex;
213
+  gap: 12px;
214
+  align-items: center;
215
+  padding: 10px 0;
216
+}
217
+
218
+.card-icon {
219
+  display: flex;
220
+  align-items: center;
221
+  justify-content: center;
222
+  width: 48px;
223
+  height: 48px;
224
+  border-radius: 6px;
225
+}
226
+
227
+.card-main {
228
+  flex: 1;
229
+  min-width: 0;
230
+}
231
+
232
+.card-title {
233
+  margin: 0 0 4px;
234
+  overflow: hidden;
235
+  font-size: 16px;
236
+  font-weight: 500;
237
+  text-overflow: ellipsis;
238
+  white-space: nowrap;
239
+  transition: color 0.3s ease;
240
+}
241
+
242
+.card-description {
243
+  margin: 0;
244
+  overflow: hidden;
245
+  font-size: 12px;
246
+  color: #666;
247
+  text-overflow: ellipsis;
248
+  white-space: nowrap;
249
+}
250
+
251
+.card-info {
252
+  min-width: 80px;
253
+  text-align: right;
254
+}
255
+
256
+.creator {
257
+  margin-bottom: 4px;
258
+  font-size: 12px;
259
+  color: #666;
260
+}
261
+
262
+.create-time {
263
+  font-size: 12px;
264
+  color: #999;
265
+}
266
+
267
+.card-actions {
268
+  display: none;
269
+  gap: 8px;
270
+  justify-content: flex-end;
271
+}
272
+
273
+.pagination {
274
+  display: flex;
275
+  justify-content: center;
276
+}
277
+</style>
175 278
 
176
-<template>
177
-  <Page :auto-content-height="true">
178
-    <BasicTable table-title="知识库类别列表">
179
-      <!-- 自定义图标列 -->
180
-      <template #icon="{ row }">
181
-        <ElImage style="width: 30px; height: 30px" :src="row.icon" />
182
-      </template>
183
-
184
-      <!-- 自定义操作列 -->
185
-      <template #action="{ row }">
186
-        <el-button size="small" type="info" plain @click="handleView(row)">
187
-          查看
188
-        </el-button>
189
-        <el-button size="small" type="primary" plain @click="handleEdit(row)">
190
-          编辑
191
-        </el-button>
192
-      </template>
193
-
194
-      <!-- 自定义工具栏 -->
195
-      <template #toolbar-tools>
196
-        <el-button type="primary" @click="handleAdd">新增</el-button>
197
-      </template>
198
-    </BasicTable>
199
-
200
-    <!-- 抽屉组件 -->
201
-    <TypeDrawerCom />
202
-  </Page>
203
-</template>

+ 0 - 263
apps/web-ele/src/views/knowledge/type/type-drawer.vue

@@ -1,263 +0,0 @@
1
-<script setup lang="ts">
2
-import { ref } from 'vue';
3
-import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
4
-import { UploadFilled, Plus } from '@element-plus/icons-vue';
5
-import { ElUpload } from 'element-plus';
6
-import { VbenIcon } from '@vben-core/shadcn-ui';
7
-import { drawerFormSchema } from './config-data';
8
-
9
-const emit = defineEmits<{
10
-  reload: [];
11
-}>();
12
-
13
-// 模拟API调用,实际项目中需要替换为真实API
14
-const addKnowledgeType = async (data: any) => {
15
-  console.log('添加知识库类型', data);
16
-  return { success: true };
17
-};
18
-
19
-const updateKnowledgeType = async (data: any) => {
20
-  console.log('更新知识库类型', data);
21
-  return { success: true };
22
-};
23
-
24
-const getKnowledgeTypeDetail = async (id: string) => {
25
-  console.log('获取知识库类型详情', id);
26
-  // 模拟数据
27
-  return {
28
-    id,
29
-    name: '测试类型',
30
-    description: '测试描述',
31
-    icon: 'carbon:test',
32
-    admin: 'admin',
33
-    applicableEnd: 'pc',
34
-    applicablePost: 'admin',
35
-  };
36
-};
37
-
38
-// 默认图标列表
39
-const defaultIcons = [
40
-  { label: '产品知识', value: 'carbon:product' },
41
-  { label: '技术文档', value: 'carbon:code' },
42
-  { label: '常见问题', value: 'carbon:question' },
43
-];
44
-
45
-// 图标文件列表
46
-const iconFileList = ref([]);
47
-
48
-// 可选图标列表,使用public/icon下的本地图片
49
-const optionalIcons = [
50
-  { label: '文档', value: '/icon/1.png' },
51
-  { label: '代码', value: '/icon/2.png' },
52
-  { label: '问题', value: '/icon/3.png' },
53
-  { label: '书籍', value: '/icon/4.png' }
54
-];
55
-
56
-// 上传请求处理
57
-const handleUpload = (options) => {
58
-  console.log('上传图标', options);
59
-  // 实际项目中需要调用上传API
60
-  // 这里模拟上传成功
61
-  const mockResponse = {
62
-    data: {
63
-      url: URL.createObjectURL(options.file),
64
-      filename: options.file.name,
65
-    },
66
-  };
67
-  // 更新文件列表
68
-  scope.icon = [
69
-    {
70
-      name: options.file.name,
71
-      url: mockResponse.data.url,
72
-      status: 'success',
73
-      uid: options.file.uid,
74
-    },
75
-  ];
76
-  // 更新表单值
77
-  formApi.setValues({ icon: mockResponse.data.url });
78
-  options.onSuccess(mockResponse);
79
-};
80
-
81
-// 移除文件处理
82
-const handleRemove = (file, fileList) => {
83
-  console.log('移除图标', file, fileList);
84
-  scope.icon = fileList;
85
-  if (fileList.length === 0) {
86
-    formApi.setValues({ icon: '' });
87
-  }
88
-};
89
-
90
-// 预览文件处理
91
-const handlePreview = (file) => {
92
-  console.log('预览图标', file);
93
-  // 实际项目中可以实现预览功能
94
-};
95
-
96
-// 选择可选图标
97
-const selectOptionalIcon = (iconValue, currentScope) => {
98
-  console.log('选择可选图标', iconValue, currentScope);
99
-  // 生成一个模拟的文件对象
100
-  const mockFile = {
101
-    name: `${iconValue.replace('carbon:', '')}.svg`,
102
-    url: iconValue,
103
-    status: 'success',
104
-    uid: Date.now(),
105
-    isOptional: true,
106
-  };
107
-  // 更新文件列表
108
-  currentScope.icon = [mockFile];
109
-  // 更新表单值
110
-  formApi.setValues({ icon: iconValue });
111
-};
112
-
113
-const [Form, formApi] = useVbenForm({
114
-  showDefaultActions: false,
115
-  schema: drawerFormSchema(),
116
-});
117
-
118
-const isUpdateRef = ref<boolean>(false);
119
-
120
-const [Drawer, drawerApi] = useVbenDrawer({
121
-  async onOpenChange(isOpen) {
122
-    if (!isOpen) {
123
-      return;
124
-    }
125
-    try {
126
-      drawerApi.drawerLoading(true);
127
-      const { id, isUpdate } = drawerApi.getData();
128
-      isUpdateRef.value = isUpdate;
129
-      if (isUpdate && id) {
130
-        const res = await getKnowledgeTypeDetail(id);
131
-        console.log('获取知识库类型详情数据:', res);
132
-        // 确保从正确的响应结构中获取数据
133
-        const detailData = res?.data || res;
134
-        await formApi.setValues(detailData);
135
-      } else {
136
-        // 新增时重置表单
137
-        await formApi.resetForm();
138
-      }
139
-    } catch (error) {
140
-      console.error('获取知识库类型详情失败:', error);
141
-    } finally {
142
-      drawerApi.drawerLoading(false);
143
-    }
144
-  },
145
-
146
-  async onConfirm() {
147
-    try {
148
-      this.confirmLoading = true;
149
-      const { valid } = await formApi.validate();
150
-      if (!valid) {
151
-        return;
152
-      }
153
-      const data = await formApi.getValues();
154
-
155
-      console.log('提交数据:', data);
156
-      await (isUpdateRef.value
157
-        ? updateKnowledgeType(data)
158
-        : addKnowledgeType(data));
159
-      emit('reload');
160
-      drawerApi.close();
161
-    } catch (error) {
162
-      console.error('保存知识库类型失败:', error);
163
-    } finally {
164
-      this.confirmLoading = false;
165
-    }
166
-  },
167
-});
168
-
169
-// 暴露drawerApi给父组件
170
-defineExpose({
171
-  getDrawerApi: () => drawerApi,
172
-});
173
-</script>
174
-
175
-<template>
176
-  <Drawer :title="isUpdateRef ? '编辑知识库分类' : '新增知识库分类'">
177
-    <Form>
178
-      <!-- 自定义Upload组件插槽 -->
179
-      <template #icon="scope">
180
-        <div class="flex items-start gap-6">
181
-          <!-- 上传组件 -->
182
-          <ElUpload
183
-            v-model:file-list="scope.icon"
184
-            action="#"
185
-            accept=".jpg,.jpeg,.png"
186
-            list-type="picture-card"
187
-            show-file-list
188
-            :auto-upload="true"
189
-            :drag="false"
190
-            :http-request="handleUpload"
191
-            :on-remove="handleRemove"
192
-            :on-preview="handlePreview"
193
-            class="w-[60px] h-[60px]"
194
-            :style="{ width: '60px', height: '60px' }"
195
-          >
196
-            <div class="w-full h-full flex items-center justify-center">
197
-              <ElIcon class="el-upload__icon">
198
-                <Plus size="18" />
199
-              </ElIcon>
200
-            </div>
201
-          </ElUpload>
202
-          
203
-          <!-- 可选图标列表 -->
204
-          <div class="flex flex-col gap-2">
205
-            <div class="text-xs text-gray-500">可选图标:</div>
206
-            <div class="flex gap-3 flex-wrap">
207
-              <div 
208
-                v-for="icon in optionalIcons" 
209
-                :key="icon.value"
210
-                class="optional-icon-item"
211
-                @click="selectOptionalIcon(icon.value, scope)"
212
-              >
213
-                <img :src="icon.value" :alt="icon.label" class="w-10 h-10" />
214
-              </div>
215
-            </div>
216
-          </div>
217
-        </div>
218
-      </template>
219
-    </Form>
220
-  </Drawer>
221
-</template>
222
-
223
-<style scoped lang="scss">
224
-.optional-icon-item {
225
-  width: 40px;
226
-  height: 40px;
227
-  display: flex;
228
-  align-items: center;
229
-  justify-content: center;
230
-  border: 2px solid transparent;
231
-  border-radius: 6px;
232
-  cursor: pointer;
233
-  transition: all 0.3s ease;
234
-  background-color: #f5f7fa;
235
-  
236
-  &:hover {
237
-    border-color: #409eff;
238
-    background-color: #ecf5ff;
239
-    transform: scale(1.1);
240
-  }
241
-  
242
-  &:active {
243
-    transform: scale(0.9);
244
-  }
245
-}
246
-
247
-::v-deep .el-upload--picture-card {
248
-    --el-upload-picture-card-size: 148px;
249
-    align-items: center;
250
-    background-color: #fafafa;
251
-    background-color: var(--el-fill-color-lighter);
252
-    border: 1px dashed #cdd0d6;
253
-    border: 1px dashed var(--el-border-color-darker);
254
-    border-radius: 6px;
255
-    box-sizing: border-box;
256
-    cursor: pointer;
257
-    display: inline-flex;
258
-    height: 60px;
259
-    justify-content: center;
260
-    vertical-align: top;
261
-    width: 60px;
262
-}
263
-</style>

+ 244 - 0
apps/web-ele/src/views/schedule/ceshi/index.vue

@@ -0,0 +1,244 @@
1
+<script setup lang="ts">
2
+import { ref } from 'vue';
3
+import { ElCard, ElTag, ElSpace, ElBadge } from 'element-plus';
4
+
5
+// 任务类型定义
6
+interface Task {
7
+  id: number;
8
+  title: string;
9
+  status: 'overdue' | 'today' | 'upcoming';
10
+  authorization: 'can_authorize' | 'authorized' | 'been_authorized' | 'none';
11
+  timeLeft?: string;
12
+  overdueDays?: number;
13
+}
14
+
15
+// 模拟数据
16
+const overdueTasks = ref<Task[]>([
17
+  {
18
+    id: 1,
19
+    title: '量缸试水 1',
20
+    status: 'overdue',
21
+    authorization: 'can_authorize',
22
+    overdueDays: 23
23
+  },
24
+  {
25
+    id: 2,
26
+    title: '量缸试水 1',
27
+    status: 'overdue',
28
+    authorization: 'been_authorized',
29
+    overdueDays: 23
30
+  },
31
+  {
32
+    id: 3,
33
+    title: '量缸试水 1',
34
+    status: 'overdue',
35
+    authorization: 'authorized',
36
+    overdueDays: 23
37
+  },
38
+  {
39
+    id: 4,
40
+    title: '量缸试水 1',
41
+    status: 'overdue',
42
+    authorization: 'none',
43
+    overdueDays: 23
44
+  }
45
+]);
46
+
47
+const todayTasks = ref<Task[]>([
48
+  {
49
+    id: 5,
50
+    title: '量缸试水 2',
51
+    status: 'today',
52
+    authorization: 'can_authorize',
53
+    timeLeft: '还剩 34 分钟截止'
54
+  },
55
+  {
56
+    id: 6,
57
+    title: '量缸试水 2',
58
+    status: 'today',
59
+    authorization: 'been_authorized',
60
+    timeLeft: '还剩 34 分钟截止'
61
+  },
62
+  {
63
+    id: 7,
64
+    title: '量缸试水 2',
65
+    status: 'today',
66
+    authorization: 'authorized',
67
+    timeLeft: '还剩 34 分钟截止'
68
+  },
69
+  {
70
+    id: 8,
71
+    title: '量缸试水 2',
72
+    status: 'today',
73
+    authorization: 'none',
74
+    timeLeft: '还剩 34 分钟截止'
75
+  }
76
+]);
77
+
78
+// 获取授权标签的颜色
79
+const getAuthorizationTagColor = (auth: string) => {
80
+  switch (auth) {
81
+    case 'can_authorize':
82
+      return 'primary';
83
+    case 'been_authorized':
84
+      return 'danger';
85
+    case 'authorized':
86
+      return 'warning';
87
+    default:
88
+      return '';
89
+  }
90
+};
91
+
92
+// 获取授权标签的文本
93
+const getAuthorizationTagText = (auth: string) => {
94
+  switch (auth) {
95
+    case 'can_authorize':
96
+      return '可授权';
97
+    case 'been_authorized':
98
+      return '被授权';
99
+    case 'authorized':
100
+      return '已授权';
101
+    default:
102
+      return '';
103
+  }
104
+};
105
+</script>
106
+
107
+<template>
108
+  <div class="schedule-day-view-messages">
109
+    <ElCard class="messages-container" shadow="hover" :body-style="{ padding: '16px' }">
110
+      <!-- 逾期待处理部分 -->
111
+      <div class="section">
112
+        <h3 class="section-title">逾期待处理</h3>
113
+        <div class="tasks-list">
114
+          <div 
115
+            v-for="task in overdueTasks" 
116
+            :key="task.id"
117
+            class="task-item overdue"
118
+          >
119
+            <div class="task-content">
120
+              <ElSpace size="small">
121
+                <ElTag 
122
+                  v-if="task.authorization !== 'none'"
123
+                  :type="getAuthorizationTagColor(task.authorization)"
124
+                  size="small"
125
+                >
126
+                  {{ getAuthorizationTagText(task.authorization) }}
127
+                </ElTag>
128
+                <span class="task-title">{{ task.title }}</span>
129
+              </ElSpace>
130
+            </div>
131
+            <div class="task-time">
132
+              <span class="overdue-text">已逾期 {{ task.overdueDays }} 天</span>
133
+            </div>
134
+          </div>
135
+        </div>
136
+      </div>
137
+
138
+      <!-- 当日部分 -->
139
+      <div class="section">
140
+        <h3 class="section-title">当日</h3>
141
+        <div class="tasks-list">
142
+          <div 
143
+            v-for="task in todayTasks" 
144
+            :key="task.id"
145
+            class="task-item today"
146
+          >
147
+            <div class="task-content">
148
+              <ElSpace size="small">
149
+                <ElTag 
150
+                  v-if="task.authorization !== 'none'"
151
+                  :type="getAuthorizationTagColor(task.authorization)"
152
+                  size="small"
153
+                >
154
+                  {{ getAuthorizationTagText(task.authorization) }}
155
+                </ElTag>
156
+                <span class="task-title">{{ task.title }}</span>
157
+              </ElSpace>
158
+            </div>
159
+            <div class="task-time">
160
+              <span class="time-left-text">{{ task.timeLeft }}</span>
161
+            </div>
162
+          </div>
163
+        </div>
164
+      </div>
165
+    </ElCard>
166
+  </div>
167
+</template>
168
+
169
+<style scoped lang="scss">
170
+.schedule-day-view-messages {
171
+  width: 100%;
172
+  height: 100%;
173
+  padding: 16px;
174
+}
175
+
176
+.messages-container {
177
+  height: 100%;
178
+  overflow: auto;
179
+}
180
+
181
+.section {
182
+  margin-bottom: 24px;
183
+
184
+  &-title {
185
+    padding-bottom: 8px;
186
+    margin-bottom: 12px;
187
+    font-size: 16px;
188
+    font-weight: 600;
189
+    color: #409EFF;
190
+    border-bottom: 1px solid #E4E7ED;
191
+  }
192
+}
193
+
194
+.tasks-list {
195
+  display: flex;
196
+  flex-direction: column;
197
+  gap: 8px;
198
+}
199
+
200
+.task-item {
201
+  display: flex;
202
+  align-items: center;
203
+  justify-content: space-between;
204
+  padding: 12px;
205
+  border-radius: 4px;
206
+  transition: all 0.3s;
207
+
208
+  &:hover {
209
+    box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
210
+  }
211
+
212
+  &.overdue {
213
+    background-color: #FDF6EC;
214
+    border: 1px solid #FAE5D3;
215
+  }
216
+
217
+  &.today {
218
+    background-color: #F0F9FF;
219
+    border: 1px solid #E0F2FE;
220
+  }
221
+}
222
+
223
+.task-content {
224
+  flex: 1;
225
+}
226
+
227
+.task-title {
228
+  font-size: 14px;
229
+  color: #303133;
230
+}
231
+
232
+.task-time {
233
+  font-size: 12px;
234
+  font-weight: 500;
235
+}
236
+
237
+.overdue-text {
238
+  color: #F56C6C;
239
+}
240
+
241
+.time-left-text {
242
+  color: #E6A23C;
243
+}
244
+</style>

+ 6 - 6
apps/web-ele/src/views/schedule/view/index.vue

@@ -371,17 +371,17 @@ console.log(currentDate.value);
371 371
 // }
372 372
 
373 373
 :deep(.custom-radio-group .el-radio-button) {
374
-  border: none !important;
374
+  padding: 4px;
375 375
   // background: transparent !important;
376 376
   background-color: #fafafa !important;
377
-  padding: 4px;
377
+  border: none !important;
378 378
 }
379 379
 
380 380
 :deep(.custom-radio-group .el-radio-button__inner) {
381
-  border: none !important;
381
+  padding: 0 8px;
382 382
   background: transparent !important;
383
+  border: none !important;
383 384
   box-shadow: none !important;
384
-  padding: 0 8px;
385 385
 }
386 386
 
387 387
 :deep(.custom-radio-group .el-radio-button__inner:hover) {
@@ -390,9 +390,9 @@ console.log(currentDate.value);
390 390
 }
391 391
 
392 392
 :deep(.custom-radio-group .el-radio-button.is-active .el-radio-button__inner) {
393
-  background: transparent !important;
394 393
   color: #1890ff !important;
395
-  box-shadow: none !important;
394
+  background: transparent !important;
396 395
   border: none !important;
396
+  box-shadow: none !important;
397 397
 }
398 398
 </style>

+ 75 - 18
apps/web-ele/src/views/system/post/config-data.ts

@@ -5,6 +5,20 @@ import { DictEnum } from '@vben/constants';
5 5
 
6 6
 import { getDictOptions } from '#/utils/dict';
7 7
 
8
+// 字典标识常量
9
+const DICT_KEYS = {
10
+  // 任务类型字典标识
11
+  TASK_TYPE: 'task_type',
12
+  // 岗位类型字典标识
13
+  POST_TYPE: 'post_type',
14
+};
15
+
16
+// 获取任务类型字典选项
17
+const getTaskTypeOptions = () => getDictOptions(DICT_KEYS.TASK_TYPE);
18
+
19
+// 获取岗位类型字典选项
20
+const getPostTypeOptions = () => getDictOptions(DICT_KEYS.POST_TYPE);
21
+
8 22
 export const tableColumns: VxeGridProps['columns'] = [
9 23
   {
10 24
     type: 'checkbox',
@@ -15,10 +29,6 @@ export const tableColumns: VxeGridProps['columns'] = [
15 29
     title: '岗位编号',
16 30
   },
17 31
   {
18
-    field: 'postCode',
19
-    title: '岗位编码',
20
-  },
21
-  {
22 32
     field: 'postName',
23 33
     title: '岗位名称',
24 34
   },
@@ -27,6 +37,29 @@ export const tableColumns: VxeGridProps['columns'] = [
27 37
     title: '岗位排序',
28 38
   },
29 39
   {
40
+    field: 'taskType',
41
+    title: '任务类型',
42
+    formatter: (params: { cellValue: string | string[]; column: any; row: any }) => {
43
+      const options = getTaskTypeOptions();
44
+      // 处理接口返回的逗号分隔字符串和前端多选的数组
45
+      const cellValue = params.cellValue;
46
+      const values = typeof cellValue === 'string' ? cellValue.split(',') : Array.isArray(cellValue) ? cellValue : [cellValue];
47
+      return values.map(value => {
48
+        const option = options.find((item) => item.value === value);
49
+        return option ? option.label : value;
50
+      }).filter(label => label).join(', ');
51
+    },
52
+  },
53
+  {
54
+    field: 'type',
55
+    title: '岗位类型',
56
+    formatter: (params: { cellValue: string; column: any; row: any }) => {
57
+      const options = getPostTypeOptions();
58
+      const option = options.find((item) => item.value === params.cellValue);
59
+      return option ? option.label : params.cellValue;
60
+    },
61
+  },
62
+  {
30 63
     field: 'status',
31 64
     title: '状态',
32 65
     slots: { default: 'status' },
@@ -46,16 +79,30 @@ export const tableColumns: VxeGridProps['columns'] = [
46 79
 export const querySchema: FormSchemaGetter = () => [
47 80
   {
48 81
     component: 'Input',
49
-    label: '岗位编码',
50
-    fieldName: 'postCode',
51
-  },
52
-  {
53
-    component: 'Input',
54 82
     label: '岗位名称',
55 83
     fieldName: 'postName',
56 84
   },
57 85
   {
58 86
     component: 'Select',
87
+    label: '任务类型',
88
+    componentProps: {
89
+      placeholder: '请选择任务类型',
90
+      options: getTaskTypeOptions(),
91
+      multiple: true,
92
+    },
93
+    fieldName: 'taskType',
94
+  },
95
+  {
96
+    component: 'Select',
97
+    label: '岗位类型',
98
+    componentProps: {
99
+      placeholder: '请选择岗位类型',
100
+      options: getPostTypeOptions(),
101
+    },
102
+    fieldName: 'type',
103
+  },
104
+  {
105
+    component: 'Select',
59 106
     label: '状态',
60 107
     componentProps: {
61 108
       options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE),
@@ -82,15 +129,6 @@ export const drawerFormSchema: FormSchemaGetter = () => [
82 129
     rules: 'required',
83 130
   },
84 131
   {
85
-    component: 'Input',
86
-    label: '岗位编码',
87
-    componentProps: {
88
-      maxlength: 64,
89
-    },
90
-    fieldName: 'postCode',
91
-    rules: 'required',
92
-  },
93
-  {
94 132
     component: 'InputNumber',
95 133
     label: '岗位排序',
96 134
     componentProps: {
@@ -101,6 +139,25 @@ export const drawerFormSchema: FormSchemaGetter = () => [
101 139
     rules: 'required',
102 140
   },
103 141
   {
142
+    component: 'Select',
143
+    label: '任务类型',
144
+    componentProps: {
145
+      placeholder: '请选择任务类型',
146
+      options: getTaskTypeOptions(),
147
+      multiple: true,
148
+    },
149
+    fieldName: 'taskType',
150
+  },
151
+  {
152
+    component: 'Select',
153
+    label: '岗位类型',
154
+    componentProps: {
155
+      placeholder: '请选择岗位类型',
156
+      options: getPostTypeOptions(),
157
+    },
158
+    fieldName: 'type',
159
+  },
160
+  {
104 161
     component: 'RadioGroup',
105 162
     label: '状态',
106 163
     componentProps: {

+ 6 - 1
apps/web-ele/src/views/system/post/index.vue

@@ -50,8 +50,13 @@ const gridOptions: VxeGridProps = {
50 50
   proxyConfig: {
51 51
     ajax: {
52 52
       query: async ({ page }, formValues = {}) => {
53
+        // 处理任务类型查询条件,转换为逗号分隔字符串
54
+        const queryParams = { ...formValues };
55
+        if (queryParams.taskType && Array.isArray(queryParams.taskType)) {
56
+          queryParams.taskType = queryParams.taskType.join(',');
57
+        }
53 58
         const resp = await getPostList({
54
-          ...formValues,
59
+          ...queryParams,
55 60
           pageNum: page.currentPage,
56 61
           pageSize: page.pageSize,
57 62
         });

+ 8 - 0
apps/web-ele/src/views/system/post/post-drawer.vue

@@ -27,6 +27,10 @@ const [Drawer, drawerApi] = useVbenDrawer({
27 27
       isUpdateRef.value = isUpdate;
28 28
       if (isUpdate) {
29 29
         const postData = await detailPost(postId);
30
+        // 将任务类型字符串转换为数组
31
+        if (postData.taskType) {
32
+          postData.taskType = postData.taskType.split(',');
33
+        }
30 34
         await formApi.setValues(postData);
31 35
       }
32 36
     } catch (error) {
@@ -46,6 +50,10 @@ const [Drawer, drawerApi] = useVbenDrawer({
46 50
         return;
47 51
       }
48 52
       const data = await formApi.getValues<Post>();
53
+      // 将任务类型数组转换为逗号分隔的字符串
54
+      if (Array.isArray(data.taskType)) {
55
+        data.taskType = data.taskType.join(',');
56
+      }
49 57
       isUpdateRef.value ? await updatePost(data) : await addPost(data);
50 58
       emit('reload');
51 59
       drawerApi.close();

+ 0 - 0
ceshi.txt