Commit c6c33024847bac4c98008ce13575f4fe2941df20
1 parent
8b6413ac
feat: 增加问题合集
Showing
6 changed files
with
1373 additions
and
1 deletions
src/api/project/quest.ts
0 → 100644
1 | +import { defHttp } from '/@/utils/http/axios'; | |
2 | + | |
3 | +enum Api { | |
4 | + QUEST_LIST = '/order/erp/quest/list_by_page', | |
5 | + QUEST_CREATE = '/order/erp/quest/add', | |
6 | + QUEST_DELETE = '/order/erp/quest/delete_by_id', | |
7 | + QUEST_UPDATE = '/order/erp/quest/edit', | |
8 | +} | |
9 | + | |
10 | +export const questUpdate=async(data:any)=>{ | |
11 | + const res=await defHttp.post<any>({ | |
12 | + url: Api.QUEST_UPDATE, | |
13 | + data},{message:'操作成功'}); | |
14 | + return res; | |
15 | +} | |
16 | + | |
17 | +export const questDelete = async (ids: number[]) => { | |
18 | + console.log('ids',ids); | |
19 | + const res = await defHttp.post<any>({ url: Api.QUEST_DELETE, data: {ids} }, { message: '删除成功' }); | |
20 | + return res; | |
21 | +}; | |
22 | + | |
23 | +export const questCreate = async (data: any) => { | |
24 | + const res = await defHttp.post<any>({ url: Api.QUEST_CREATE, data }, { message: '创建成功' }); | |
25 | + return res; | |
26 | +}; | |
27 | + | |
28 | +export const getQuestList = async (params: any) => { | |
29 | + console.log('params',params); | |
30 | + const res=await defHttp.post<any>({ | |
31 | + url: Api.QUEST_LIST, | |
32 | + params, | |
33 | + }); | |
34 | + const result={ | |
35 | + items: res.records, | |
36 | + total: res.total, | |
37 | + }; | |
38 | + return result; | |
39 | +}; | ... | ... |
src/router/routes/modules/project/quest.ts
0 → 100644
1 | +import type { AppRouteModule } from '/@/router/types'; | |
2 | + | |
3 | +import { LAYOUT } from '/@/router/constant'; | |
4 | +import { t } from '/@/hooks/web/useI18n'; | |
5 | + | |
6 | +const quest: AppRouteModule = { | |
7 | + path: '/questList', | |
8 | + name: 'QuestList', | |
9 | + component: LAYOUT, | |
10 | + redirect: '/questList', | |
11 | + meta: { | |
12 | + hideChildrenInMenu: true, // 代表隐藏子菜单选项,适合于底部没有子菜单选项列表。 | |
13 | + orderNo: 5, | |
14 | + icon: 'ion:help-outline', | |
15 | + title: '问题合集', | |
16 | + }, | |
17 | + children: [ | |
18 | + { | |
19 | + path: '', | |
20 | + name: 'QuestList', | |
21 | + component: () => import('/@/views/project/quest/index.vue'), | |
22 | + meta: { | |
23 | + // affix: true, | |
24 | + title: '问题合集', | |
25 | + }, | |
26 | + }, | |
27 | + // { | |
28 | + // path: 'workbench', | |
29 | + // name: 'Workbench', | |
30 | + // component: () => import('/@/views/dashboard/workbench/index.vue'), | |
31 | + // meta: { | |
32 | + // title: t('routes.dashboard.workbench'), | |
33 | + // }, | |
34 | + // }, | |
35 | + ], | |
36 | +}; | |
37 | + | |
38 | +export default quest; | ... | ... |
src/views/project/approve/FieldPanel.vue
... | ... | @@ -24,7 +24,7 @@ |
24 | 24 | </BasicTable> |
25 | 25 | <BasicDrawer |
26 | 26 | width="500" |
27 | - :showFooter="!isApproved && (role === ROLE.ADMIN || role === ROLE.BUSINESS)" | |
27 | + :showFooter="!isApproved && (role === ROLE.ADMIN || (role === ROLE.BUSINESS && !shouldHideApproveButton))" | |
28 | 28 | @register="registerDrawer" |
29 | 29 | title="申请信息" |
30 | 30 | okText="通过" |
... | ... | @@ -278,6 +278,10 @@ |
278 | 278 | const role = computed(() => { |
279 | 279 | return userStore.getUserInfo?.roleSmallVO?.code; |
280 | 280 | }); |
281 | + const shouldHideApproveButton = computed(() => { | |
282 | + if (role.value !== ROLE.BUSINESS) return false; | |
283 | + return fieldInfos.value.baseFields.some(field => field?.includes('数量') || field?.includes('生产科')); | |
284 | + }); | |
281 | 285 | |
282 | 286 | // 定义MsgModalClose的事件,方便子组件调用 |
283 | 287 | const handleMsgModalClose = async (data) => { |
... | ... | @@ -313,6 +317,7 @@ |
313 | 317 | msgVisible, |
314 | 318 | handleMsgModalClose, |
315 | 319 | handlePreview, |
320 | + shouldHideApproveButton, | |
316 | 321 | }; |
317 | 322 | }, |
318 | 323 | }); | ... | ... |
src/views/project/quest/QuestDrawer.vue
0 → 100644
1 | +<template> | |
2 | + <BasicDrawer | |
3 | + v-bind="$attrs" | |
4 | + @register="registerDrawer" | |
5 | + showFooter | |
6 | + :title="getTitle" | |
7 | + width="70%" | |
8 | + @ok="handleSubmit" | |
9 | + :okButtonProps="{ disabled: isView }" | |
10 | + > | |
11 | + <BasicForm @register="registerForm" /> | |
12 | + | |
13 | + <!-- 自定义图片上传区域 --> | |
14 | + <div class="custom-image-upload" v-if="!isView"> | |
15 | + <div class="upload-title">图片上传</div> | |
16 | + <div class="upload-content"> | |
17 | + <div | |
18 | + class="upload-preview" | |
19 | + v-for="(img, index) in uploadedImages" | |
20 | + :key="index" | |
21 | + > | |
22 | + <img | |
23 | + :src="img.url" | |
24 | + @error="handleImageError($event, index)" | |
25 | + :alt="img.name || '图片'" | |
26 | + /> | |
27 | + <div class="upload-preview-action"> | |
28 | + <div class="action-buttons"> | |
29 | + <EyeOutlined class="action-icon" @click="previewImage(img)" title="预览" /> | |
30 | + <a-popconfirm title="确定删除吗?" ok-text="是" cancel-text="否" @confirm="removeImage(index)"> | |
31 | + <DeleteOutlined class="action-icon" title="删除" /> | |
32 | + </a-popconfirm> | |
33 | + </div> | |
34 | + </div> | |
35 | + </div> | |
36 | + <div class="upload-button" @click="handleCustomImageUpload" v-if="uploadedImages.length < 5"> | |
37 | + <div class="upload-button-text"> | |
38 | + <PlusOutlined /> | |
39 | + <div>上传图片</div> | |
40 | + </div> | |
41 | + </div> | |
42 | + </div> | |
43 | + <input | |
44 | + type="file" | |
45 | + ref="imageFileInput" | |
46 | + accept=".jpg,.jpeg,.png,.gif" | |
47 | + style="display: none;" | |
48 | + @change="uploadSelectedImage" | |
49 | + /> | |
50 | + </div> | |
51 | + | |
52 | + <!-- 查看模式下的图片显示 --> | |
53 | + <div class="custom-image-view" v-if="isView && uploadedImages.length > 0"> | |
54 | + <div class="upload-title">图片附件</div> | |
55 | + <div class="upload-content"> | |
56 | + <div | |
57 | + class="upload-preview" | |
58 | + v-for="(img, index) in uploadedImages" | |
59 | + :key="index" | |
60 | + @click="previewImage(img)" | |
61 | + > | |
62 | + <img | |
63 | + :src="img.url" | |
64 | + @error="handleImageError($event, index)" | |
65 | + | |
66 | + /> | |
67 | + <div class="view-preview-hint"> | |
68 | + <EyeOutlined /> | |
69 | + <span>点击查看</span> | |
70 | + </div> | |
71 | + </div> | |
72 | + </div> | |
73 | + </div> | |
74 | + | |
75 | + <!-- 自定义附件上传区域 --> | |
76 | + <div class="custom-file-upload" v-if="!isView"> | |
77 | + <div class="upload-title">附件上传</div> | |
78 | + <div class="file-list"> | |
79 | + <!-- 附件列表 --> | |
80 | + <div class="file-item" v-for="(file, index) in uploadedFiles" :key="index"> | |
81 | + <div class="file-info"> | |
82 | + <div class="file-name">{{ file.name }}</div> | |
83 | + </div> | |
84 | + <div class="file-actions"> | |
85 | + <a @click="previewFile(file)" class="action-btn">预览</a> | |
86 | + <a @click="downloadFile(file)" class="action-btn">下载</a> | |
87 | + <a-popconfirm title="确定删除吗?" ok-text="是" cancel-text="否" @confirm="removeFile(index)"> | |
88 | + <a class="action-btn delete">删除</a> | |
89 | + </a-popconfirm> | |
90 | + </div> | |
91 | + </div> | |
92 | + | |
93 | + <!-- 上传按钮 --> | |
94 | + <div class="file-upload-button" @click="handleCustomFileUpload"> | |
95 | + <div class="upload-button-text"> | |
96 | + <PlusOutlined /> | |
97 | + <div>上传附件</div> | |
98 | + </div> | |
99 | + </div> | |
100 | + </div> | |
101 | + <input | |
102 | + type="file" | |
103 | + ref="fileInput" | |
104 | + multiple | |
105 | + style="display: none;" | |
106 | + @change="uploadSelectedFile" | |
107 | + /> | |
108 | + </div> | |
109 | + | |
110 | + <!-- 查看模式下的附件显示 --> | |
111 | + <div class="custom-file-view" v-if="isView && uploadedFiles.length > 0"> | |
112 | + <div class="upload-title">文件附件</div> | |
113 | + <div class="file-list"> | |
114 | + <!-- 附件列表 --> | |
115 | + <div class="file-item" v-for="(file, index) in uploadedFiles" :key="index"> | |
116 | + <div class="file-info"> | |
117 | + <div class="file-name">{{ file.name }}</div> | |
118 | + </div> | |
119 | + <div class="file-actions"> | |
120 | + <a @click="previewFile(file)" class="action-btn">预览</a> | |
121 | + <a @click="downloadFile(file)" class="action-btn">下载</a> | |
122 | + </div> | |
123 | + </div> | |
124 | + </div> | |
125 | + </div> | |
126 | + </BasicDrawer> | |
127 | +</template> | |
128 | + | |
129 | +<script lang="ts" setup> | |
130 | + import { ref, computed, unref, onMounted } from 'vue'; | |
131 | + import { BasicForm, useForm } from '/@/components/Form'; | |
132 | + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; | |
133 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
134 | + import { Tinymce } from '/@/components/Tinymce'; | |
135 | + import { h } from 'vue'; | |
136 | + import { questCreate, questUpdate } from '/@/api/project/quest'; | |
137 | + import { PlusOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'; | |
138 | + | |
139 | + const emit = defineEmits(['success', 'register']); | |
140 | + const isUpdate = ref(true); | |
141 | + const isView = ref(false); // 是否为查看模式 | |
142 | + // 修改类型,使其包含id和其他必要属性 | |
143 | + interface RecordType { | |
144 | + id?: number; | |
145 | + title?: string; | |
146 | + contentText?: string; | |
147 | + contentImages?: string; | |
148 | + files?: string; | |
149 | + [key: string]: any; | |
150 | + } | |
151 | + const record = ref<RecordType>({}); | |
152 | + const { createMessage } = useMessage(); | |
153 | + const uploadUrlFile=ref('http://47.104.8.35:80/api/localStorage/upload_file_oss?name='); | |
154 | + const imageUploadUrl = ref('/api/localStorage/upload_oss'); // 使用相对路径避免跨域问题 | |
155 | + const apiBaseDomain = 'http://47.104.8.35:80'; // API基础域名 | |
156 | + | |
157 | + // 自定义图片上传相关 | |
158 | + const imageFileInput = ref<HTMLInputElement | null>(null); | |
159 | + const uploadedImages = ref<Array<{ | |
160 | + url: string; | |
161 | + name?: string; | |
162 | + }>>([]); | |
163 | + const isUploading = ref(false); | |
164 | + | |
165 | + // 自定义附件上传相关 | |
166 | + const fileInput = ref<HTMLInputElement | null>(null); | |
167 | + const uploadedFiles = ref<Array<{ | |
168 | + name: string; | |
169 | + fileUrl: string; | |
170 | + size?: number; | |
171 | + type?: string; | |
172 | + uploadTime?: number; | |
173 | + }>>([]); | |
174 | + const isFileUploading = ref(false); | |
175 | + | |
176 | + // 确保URL是完整路径 | |
177 | + function ensureFullUrl(url) { | |
178 | + if (!url) return ''; | |
179 | + | |
180 | + // 如果已经是完整URL,直接返回 | |
181 | + if (url.startsWith('http://') || url.startsWith('https://')) { | |
182 | + return url; | |
183 | + } | |
184 | + | |
185 | + // 处理不同的URL格式 | |
186 | + if (url.startsWith('//')) { | |
187 | + return 'http:' + url; | |
188 | + } else if (url.startsWith('/')) { | |
189 | + return apiBaseDomain + url; | |
190 | + } else { | |
191 | + return apiBaseDomain + '/' + url; | |
192 | + } | |
193 | + } | |
194 | + // 组件加载完成时执行 | |
195 | + onMounted(() => { | |
196 | + console.log('QuestDrawer组件已加载,自定义文件上传和预览功能已配置'); | |
197 | + }); | |
198 | + | |
199 | + // 点击上传按钮 | |
200 | + function handleCustomImageUpload() { | |
201 | + if (isUploading.value) return; | |
202 | + imageFileInput.value?.click(); | |
203 | + } | |
204 | + | |
205 | + // 点击上传按钮 - 文件 | |
206 | + function handleCustomFileUpload() { | |
207 | + if (isFileUploading.value) return; | |
208 | + fileInput.value?.click(); | |
209 | + } | |
210 | + | |
211 | + // 上传选择的文件 | |
212 | + async function uploadSelectedFile(e: Event) { | |
213 | + const target = e.target as HTMLInputElement; | |
214 | + if (!target.files || target.files.length === 0) return; | |
215 | + | |
216 | + isFileUploading.value = true; | |
217 | + | |
218 | + // 处理所有选中的文件 | |
219 | + const files = Array.from(target.files); | |
220 | + const uploadPromises = files.map(async (file) => { | |
221 | + createMessage.loading(`正在上传: ${file.name}`); | |
222 | + | |
223 | + try { | |
224 | + const formData = new FormData(); | |
225 | + formData.append('file', file); | |
226 | + | |
227 | + const response = await fetch(uploadUrlFile.value + file.name, { | |
228 | + method: 'POST', | |
229 | + body: formData, | |
230 | + }); | |
231 | + | |
232 | + if (!response.ok) { | |
233 | + throw new Error(`上传失败: ${response.statusText}`); | |
234 | + } | |
235 | + | |
236 | + const data = await response.json(); | |
237 | + console.log('附件上传返回数据:', data); | |
238 | + | |
239 | + // 查找fileUrl字段 | |
240 | + let fileUrl = null; | |
241 | + if (data.data && data.data.fileUrl) { | |
242 | + fileUrl = data.data.fileUrl; | |
243 | + console.log("在data.data中找到fileUrl:", fileUrl); | |
244 | + } else { | |
245 | + console.error("未找到fileUrl字段! 数据:", data); | |
246 | + } | |
247 | + | |
248 | + if (!fileUrl) { | |
249 | + throw new Error('无法获取文件URL'); | |
250 | + } | |
251 | + | |
252 | + // 确保URL是完整路径 | |
253 | + const fullUrl = ensureFullUrl(fileUrl); | |
254 | + console.log('完整文件URL:', fullUrl); | |
255 | + | |
256 | + // 从URL中提取文件名 | |
257 | + const fileName = extractFileNameFromUrl(fullUrl); | |
258 | + | |
259 | + // 添加到上传文件列表,只存储fileUrl | |
260 | + uploadedFiles.value.push({ | |
261 | + name: fileName, | |
262 | + fileUrl: fullUrl, | |
263 | + }); | |
264 | + | |
265 | + createMessage.success(`${fileName} 上传成功`); | |
266 | + return true; | |
267 | + } catch (error) { | |
268 | + console.error(`上传文件 ${file.name} 失败:`, error); | |
269 | + createMessage.error(`${file.name} 上传失败: ${error.message || '未知错误'}`); | |
270 | + return false; | |
271 | + } | |
272 | + }); | |
273 | + | |
274 | + // 等待所有文件上传完成 | |
275 | + await Promise.all(uploadPromises); | |
276 | + isFileUploading.value = false; | |
277 | + | |
278 | + // 清空input,以便于再次选择同一文件 | |
279 | + if (fileInput.value) { | |
280 | + fileInput.value.value = ''; | |
281 | + } | |
282 | + } | |
283 | + | |
284 | + // 从URL中提取文件名 | |
285 | + function extractFileNameFromUrl(url) { | |
286 | + if (!url) return '未知文件'; | |
287 | + | |
288 | + try { | |
289 | + // 尝试从URL中提取文件名 | |
290 | + // 移除查询参数 | |
291 | + const urlWithoutParams = url.split('?')[0]; | |
292 | + console.log('移除查询参数后的URL:', urlWithoutParams); | |
293 | + | |
294 | + // 获取最后一个路径部分 | |
295 | + let fileName = urlWithoutParams.split('/').pop(); | |
296 | + console.log('提取的文件名:', fileName); | |
297 | + | |
298 | + // 如果有编码的部分,尝试解码 | |
299 | + if (fileName && (fileName.includes('%') || fileName.includes('+'))) { | |
300 | + try { | |
301 | + fileName = decodeURIComponent(fileName); | |
302 | + console.log('解码后的文件名:', fileName); | |
303 | + } catch (e) { | |
304 | + console.error('解码文件名失败:', e); | |
305 | + } | |
306 | + } | |
307 | + | |
308 | + return fileName || '未知文件'; | |
309 | + } catch (error) { | |
310 | + console.error('提取文件名失败:', error); | |
311 | + return '未知文件'; | |
312 | + } | |
313 | + } | |
314 | + | |
315 | + // 预览文件 | |
316 | + function previewFile(file) { | |
317 | + console.log('预览文件:', file); | |
318 | + if (!file.fileUrl) { | |
319 | + createMessage.error('无法预览:找不到文件URL'); | |
320 | + return; | |
321 | + } | |
322 | + | |
323 | + // 打开新窗口显示文件 | |
324 | + const win = window.open(file.fileUrl, '_blank'); | |
325 | + if (!win) { | |
326 | + createMessage.error('浏览器阻止了弹出窗口,请允许弹出窗口或使用下载功能'); | |
327 | + } | |
328 | + } | |
329 | + | |
330 | + // 下载文件 | |
331 | + function downloadFile(file) { | |
332 | + console.log('下载文件:', file); | |
333 | + if (!file.fileUrl) { | |
334 | + createMessage.error('无法下载:找不到文件URL'); | |
335 | + return; | |
336 | + } | |
337 | + | |
338 | + // 创建一个隐藏的a标签,触发下载 | |
339 | + const a = document.createElement('a'); | |
340 | + a.href = file.fileUrl; | |
341 | + a.download = file.name; | |
342 | + a.target = '_blank'; | |
343 | + document.body.appendChild(a); | |
344 | + a.click(); | |
345 | + document.body.removeChild(a); | |
346 | + } | |
347 | + | |
348 | + // 删除已上传的文件 | |
349 | + function removeFile(index: number) { | |
350 | + uploadedFiles.value.splice(index, 1); | |
351 | + } | |
352 | + | |
353 | + // 上传选择的图片 | |
354 | + async function uploadSelectedImage(e: Event) { | |
355 | + const target = e.target as HTMLInputElement; | |
356 | + if (!target.files || target.files.length === 0) return; | |
357 | + | |
358 | + const file = target.files[0]; | |
359 | + if (!file) return; | |
360 | + | |
361 | + isUploading.value = true; | |
362 | + createMessage.loading('正在上传图片...'); | |
363 | + | |
364 | + try { | |
365 | + const formData = new FormData(); | |
366 | + formData.append('file', file); | |
367 | + formData.append('name', file.name); // 显式添加文件名 | |
368 | + console.log('formData',formData); | |
369 | + console.log('file',file); | |
370 | + console.log('name',file.name); | |
371 | + const response = await fetch(imageUploadUrl.value, { | |
372 | + method: 'POST', | |
373 | + body: formData, | |
374 | + }); | |
375 | + | |
376 | + if (!response.ok) { | |
377 | + throw new Error(`上传失败: ${response.statusText}`); | |
378 | + } | |
379 | + | |
380 | + const data = await response.json(); | |
381 | + console.log('图片上传返回数据', data); | |
382 | + | |
383 | + // 根据接口返回格式获取URL | |
384 | + let imageUrl = ''; | |
385 | + | |
386 | + // 检查嵌套的data对象 | |
387 | + if (data.data) { | |
388 | + if (data.data.picUrl) { | |
389 | + imageUrl = data.data.picUrl; | |
390 | + }else if (data.data.smallPicUrl) { | |
391 | + imageUrl = data.data.smallPicUrl; | |
392 | + } | |
393 | + } | |
394 | + | |
395 | + | |
396 | + // 确保URL是完整路径 | |
397 | + if (imageUrl && !imageUrl.startsWith('http')) { | |
398 | + // 如果URL包含双斜杠但不是http开头,可能是协议相对URL | |
399 | + if (imageUrl.startsWith('//')) { | |
400 | + imageUrl = 'http:' + imageUrl; | |
401 | + } | |
402 | + // 是相对路径,需要添加域名 | |
403 | + else if (imageUrl.startsWith('/')) { | |
404 | + // 使用服务器域名而不是前端域名 | |
405 | + imageUrl = 'http://47.104.8.35:80' + imageUrl; | |
406 | + } else { | |
407 | + // 其他情况,添加完整路径前缀 | |
408 | + imageUrl = 'http://47.104.8.35:80/' + imageUrl; | |
409 | + } | |
410 | + } | |
411 | + | |
412 | + console.log('处理后的图片URL:', imageUrl); | |
413 | + | |
414 | + // 只添加URL,不添加name | |
415 | + uploadedImages.value.push({ | |
416 | + url: imageUrl | |
417 | + }); | |
418 | + | |
419 | + createMessage.success('图片上传成功'); | |
420 | + } catch (error) { | |
421 | + console.error('图片上传失败:', error); | |
422 | + createMessage.error(`上传失败: ${error.message || '未知错误'}`); | |
423 | + } finally { | |
424 | + isUploading.value = false; | |
425 | + // 清空input,以便于再次选择同一文件 | |
426 | + if (imageFileInput.value) { | |
427 | + imageFileInput.value.value = ''; | |
428 | + } | |
429 | + } | |
430 | + } | |
431 | + | |
432 | + // 删除已上传的图片 | |
433 | + function removeImage(index: number) { | |
434 | + uploadedImages.value.splice(index, 1); | |
435 | + } | |
436 | + | |
437 | + // 处理图片加载错误 | |
438 | + function handleImageError(event: Event, index: number) { | |
439 | + const target = event.target as HTMLImageElement; | |
440 | + console.error('图片加载失败:', uploadedImages.value[index].url); | |
441 | + | |
442 | + // 尝试使用不同格式的URL | |
443 | + const currentUrl = uploadedImages.value[index].url; | |
444 | + let newUrl = ''; | |
445 | + | |
446 | + // 尝试各种可能的URL格式 | |
447 | + if (currentUrl.includes('picUrl')) { | |
448 | + // 如果是大图失败,尝试使用小图 | |
449 | + newUrl = currentUrl.replace('picUrl', 'smallPicUrl'); | |
450 | + } | |
451 | + | |
452 | + console.log('尝试新的URL:', newUrl); | |
453 | + | |
454 | + // 使用新URL | |
455 | + uploadedImages.value[index].url = newUrl; | |
456 | + target.src = newUrl; | |
457 | + | |
458 | + // 添加再次失败的处理 | |
459 | + target.onerror = () => { | |
460 | + console.error('备选URL也加载失败:', newUrl); | |
461 | + createMessage.error('图片无法加载,请重新上传'); | |
462 | + // 移除无法加载的图片 | |
463 | + removeImage(index); | |
464 | + }; | |
465 | + } | |
466 | + | |
467 | + // 表单配置 | |
468 | + const getFormSchema = computed(() => { | |
469 | + return [ | |
470 | + { | |
471 | + field: 'id', | |
472 | + label: 'ID', | |
473 | + component: 'Input' as any, | |
474 | + show: false, // 隐藏此字段 | |
475 | + }, | |
476 | + { | |
477 | + field: 'title', | |
478 | + label: '标题', | |
479 | + component: 'Input' as any, | |
480 | + required: true, | |
481 | + componentProps: { | |
482 | + disabled: unref(isView), // 查看模式下禁用 | |
483 | + }, | |
484 | + }, | |
485 | + { | |
486 | + field: 'contentText', | |
487 | + label: '内容', | |
488 | + component: 'Input' as any, | |
489 | + required: true, | |
490 | + render: ({ model, field }) => { | |
491 | + return h(Tinymce, { | |
492 | + value: model[field], | |
493 | + height: 400, | |
494 | + onChange: (value: string) => { | |
495 | + model[field] = value; | |
496 | + }, | |
497 | + disabled: unref(isView), // 查看模式下禁用 | |
498 | + readonly: unref(isView), // 查看模式下只读 | |
499 | + }); | |
500 | + }, | |
501 | + }, | |
502 | + ] as any; | |
503 | + }); | |
504 | + | |
505 | + // 注册表单 | |
506 | + const [registerForm, { resetFields, setFieldsValue, validate }] = useForm({ | |
507 | + labelWidth: 100, | |
508 | + schemas: getFormSchema, //这就是数据。 | |
509 | + showActionButtonGroup: false, | |
510 | + baseColProps: { span: 24 }, | |
511 | + }); | |
512 | + | |
513 | + // 注册抽屉 | |
514 | + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => { | |
515 | + resetFields(); | |
516 | + setDrawerProps({ confirmLoading: false }); | |
517 | + uploadedImages.value = []; // 清空已上传图片 | |
518 | + uploadedFiles.value = []; // 清空已上传文件 | |
519 | + | |
520 | + isUpdate.value = !!data?.isUpdate; | |
521 | + isView.value = !!data?.isView; // 设置查看模式 | |
522 | + | |
523 | + if (unref(isUpdate)) { | |
524 | + record.value = { ...data.record }; | |
525 | + console.log('加载记录数据:', data.record); | |
526 | + console.log('记录ID:', data.record?.id); | |
527 | + | |
528 | + // 确保ID字段被正确保存,即使它不在表单字段中 | |
529 | + if (data.record && data.record.id) { | |
530 | + record.value.id = data.record.id; | |
531 | + console.log('保存记录ID到record:', record.value.id); | |
532 | + } | |
533 | + | |
534 | + await setFieldsValue({ | |
535 | + ...data.record, | |
536 | + }); | |
537 | + | |
538 | + // 处理图片数据 - 现在contentImages是一个URL数组 | |
539 | + try { | |
540 | + if (data.record.contentImages) { | |
541 | + console.log('原始图片数据:', data.record.contentImages); | |
542 | + | |
543 | + // 处理contentImages数组 | |
544 | + if (Array.isArray(data.record.contentImages)) { | |
545 | + // 直接使用数组 | |
546 | + uploadedImages.value = data.record.contentImages.map(url => ({ | |
547 | + url: ensureFullUrl(url) | |
548 | + })); | |
549 | + console.log('数组格式的图片URL:', uploadedImages.value); | |
550 | + } | |
551 | + // 向下兼容旧格式,如果是字符串,尝试解析 | |
552 | + else if (typeof data.record.contentImages === 'string') { | |
553 | + try { | |
554 | + // 尝试解析为JSON | |
555 | + const images = JSON.parse(data.record.contentImages); | |
556 | + if (Array.isArray(images)) { | |
557 | + // 如果解析成功并且是数组,处理每个图片对象 | |
558 | + uploadedImages.value = images.map(img => ({ | |
559 | + url: ensureFullUrl(typeof img === 'string' ? img : (img.url || '')) | |
560 | + })); | |
561 | + } else { | |
562 | + // 如果不是数组,可能是以逗号分隔的字符串 | |
563 | + const urls = data.record.contentImages.split(',').filter(url => url.trim()); | |
564 | + uploadedImages.value = urls.map(url => ({ | |
565 | + url: ensureFullUrl(url.trim()) | |
566 | + })); | |
567 | + } | |
568 | + } catch (e) { | |
569 | + // 解析失败,尝试作为逗号分隔的URL列表处理 | |
570 | + const urls = data.record.contentImages.split(',').filter(url => url.trim()); | |
571 | + uploadedImages.value = urls.map(url => ({ | |
572 | + url: ensureFullUrl(url.trim()) | |
573 | + })); | |
574 | + } | |
575 | + } | |
576 | + | |
577 | + console.log('处理后的图片数据:', uploadedImages.value); | |
578 | + } | |
579 | + | |
580 | + // 处理附件数据 - 现在files是一个URL数组 | |
581 | + if (data.record.files) { | |
582 | + console.log('原始文件数据:', data.record.files); | |
583 | + | |
584 | + // 处理files数组 | |
585 | + if (Array.isArray(data.record.files)) { | |
586 | + // 直接使用数组 | |
587 | + uploadedFiles.value = data.record.files.map(url => { | |
588 | + const fileUrl = ensureFullUrl(url); | |
589 | + return { | |
590 | + name: extractFileNameFromUrl(fileUrl), | |
591 | + fileUrl: fileUrl | |
592 | + }; | |
593 | + }); | |
594 | + console.log('数组格式的文件URL:', uploadedFiles.value); | |
595 | + } | |
596 | + // 向下兼容旧格式,如果是字符串,尝试解析 | |
597 | + else if (typeof data.record.files === 'string') { | |
598 | + // 处理可能的额外引号 | |
599 | + let filesStr = data.record.files; | |
600 | + if (filesStr.startsWith('"') && filesStr.endsWith('"')) { | |
601 | + filesStr = filesStr.substring(1, filesStr.length - 1); | |
602 | + } | |
603 | + | |
604 | + try { | |
605 | + // 尝试解析为JSON | |
606 | + const files = JSON.parse(filesStr); | |
607 | + if (Array.isArray(files)) { | |
608 | + uploadedFiles.value = files.map(file => { | |
609 | + if (typeof file === 'string') { | |
610 | + // 如果文件项是字符串,当作URL处理 | |
611 | + const fileUrl = ensureFullUrl(file); | |
612 | + return { | |
613 | + name: extractFileNameFromUrl(fileUrl), | |
614 | + fileUrl: fileUrl | |
615 | + }; | |
616 | + } else { | |
617 | + // 如果文件项是对象,提取fileUrl | |
618 | + const fileUrl = ensureFullUrl(file.fileUrl || ''); | |
619 | + return { | |
620 | + name: file.name || extractFileNameFromUrl(fileUrl), | |
621 | + fileUrl: fileUrl | |
622 | + }; | |
623 | + } | |
624 | + }); | |
625 | + } else { | |
626 | + // 如果不是数组,可能是以逗号分隔的字符串 | |
627 | + const urls = filesStr.split(',').filter(url => url.trim()); | |
628 | + uploadedFiles.value = urls.map(url => { | |
629 | + const fileUrl = ensureFullUrl(url.trim()); | |
630 | + return { | |
631 | + name: extractFileNameFromUrl(fileUrl), | |
632 | + fileUrl: fileUrl | |
633 | + }; | |
634 | + }); | |
635 | + } | |
636 | + } catch (e) { | |
637 | + // 解析失败,尝试作为逗号分隔的URL列表处理 | |
638 | + const urls = filesStr.split(',').filter(url => url.trim()); | |
639 | + uploadedFiles.value = urls.map(url => { | |
640 | + const fileUrl = ensureFullUrl(url.trim()); | |
641 | + return { | |
642 | + name: extractFileNameFromUrl(fileUrl), | |
643 | + fileUrl: fileUrl | |
644 | + }; | |
645 | + }); | |
646 | + } | |
647 | + } | |
648 | + | |
649 | + console.log('处理后的文件数据:', uploadedFiles.value); | |
650 | + } | |
651 | + } catch (error) { | |
652 | + console.error('处理附件和图片数据时出错:', error); | |
653 | + } | |
654 | + } | |
655 | + }); | |
656 | + | |
657 | + // 计算抽屉标题 | |
658 | + const getTitle = computed(() => { | |
659 | + if (unref(isView)) { | |
660 | + return '查看问题'; | |
661 | + } | |
662 | + return !unref(isUpdate) ? '新建问题' : '编辑问题'; | |
663 | + }); | |
664 | + | |
665 | + // 提交表单 | |
666 | + async function handleSubmit() { | |
667 | + // 查看模式下不允许提交 | |
668 | + if (unref(isView)) return; | |
669 | + | |
670 | + try { | |
671 | + const values = await validate(); | |
672 | + console.log('values的值',values); | |
673 | + setDrawerProps({ confirmLoading: true }); | |
674 | + | |
675 | + // 添加上传的图片到表单数据中 - 直接提交URL数组 | |
676 | + if (uploadedImages.value.length > 0) { | |
677 | + // 直接将URL数组作为contentImages | |
678 | + values.contentImages = uploadedImages.value.map(img => img.url); | |
679 | + console.log('图片数据(数组格式):', values.contentImages); | |
680 | + } | |
681 | + | |
682 | + // 添加上传的附件到表单数据中 - 直接提交URL数组 | |
683 | + if (uploadedFiles.value.length > 0) { | |
684 | + // 直接将fileUrl数组作为files | |
685 | + values.files = uploadedFiles.value.map(file => file.fileUrl); | |
686 | + console.log('附件数据(数组格式):', values.files); | |
687 | + } | |
688 | + | |
689 | + // 如果是更新模式,确保添加ID | |
690 | + const isUpdateValue = unref(isUpdate); | |
691 | + if (isUpdateValue) { | |
692 | + // 从record中获取ID | |
693 | + if (record.value && record.value.id) { | |
694 | + values.id = record.value.id; | |
695 | + console.log('从record中添加记录ID:', values.id); | |
696 | + } else { | |
697 | + console.warn('警告:更新模式下未找到记录ID!'); | |
698 | + } | |
699 | + } | |
700 | + | |
701 | + // 确保表单提交前检查ID | |
702 | + if (isUpdateValue && !values.id) { | |
703 | + console.error('错误:更新模式下缺少ID字段!'); | |
704 | + createMessage.error('更新失败:缺少记录ID'); | |
705 | + setDrawerProps({ confirmLoading: false }); | |
706 | + return; | |
707 | + } | |
708 | + | |
709 | + // 提交数据 | |
710 | + console.log('表单数据', values); | |
711 | + | |
712 | + // 调用后端API保存数据 | |
713 | + const requestApi = isUpdateValue | |
714 | + ? questUpdate // 更新接口 | |
715 | + : questCreate; // 创建接口 | |
716 | + console.log('requestApi调用',requestApi); | |
717 | + const res=await requestApi(values); | |
718 | + console.log('res返回数据',res); | |
719 | + // 关闭抽屉 | |
720 | + closeDrawer(); | |
721 | + // 通知父组件刷新数据 | |
722 | + emit('success'); | |
723 | + setDrawerProps({ confirmLoading: false }); | |
724 | + } catch (error) { | |
725 | + console.error('API请求失败:', error); | |
726 | + createMessage.error(`保存失败: ${error.message || '未知错误'}`); | |
727 | + } | |
728 | + } | |
729 | + | |
730 | + // 预览图片 | |
731 | + function previewImage(image) { | |
732 | + console.log('预览图片:', image); | |
733 | + if (!image.url) { | |
734 | + createMessage.error('无法预览:找不到图片URL'); | |
735 | + return; | |
736 | + } | |
737 | + | |
738 | + // 创建一个新窗口安全地显示图片 | |
739 | + const win = window.open('', '_blank'); | |
740 | + if (!win) { | |
741 | + createMessage.error('浏览器阻止了弹出窗口,请允许弹出窗口'); | |
742 | + return; | |
743 | + } | |
744 | + | |
745 | + // 写入HTML内容 | |
746 | + win.document.write(`<!DOCTYPE html> | |
747 | + <html> | |
748 | + <head> | |
749 | + <title>图片预览</title> | |
750 | + <style> | |
751 | + body { | |
752 | + margin: 0; | |
753 | + padding: 0; | |
754 | + display: flex; | |
755 | + justify-content: center; | |
756 | + align-items: center; | |
757 | + min-height: 100vh; | |
758 | + background-color: #f0f0f0; | |
759 | + } | |
760 | + img { | |
761 | + max-width: 90%; | |
762 | + max-height: 90vh; | |
763 | + box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); | |
764 | + } | |
765 | + .img-container { | |
766 | + background-color: white; | |
767 | + padding: 20px; | |
768 | + border-radius: 5px; | |
769 | + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | |
770 | + } | |
771 | + </style> | |
772 | + </head> | |
773 | + <body> | |
774 | + <div class="img-container"> | |
775 | + <img src="${image.url}" alt="${image.name || '图片'}" /> | |
776 | + </div> | |
777 | + </body> | |
778 | + </html>`); | |
779 | + win.document.close(); | |
780 | + } | |
781 | +</script> | |
782 | + | |
783 | +<style lang="less" scoped> | |
784 | +.custom-image-upload { | |
785 | + margin-top: 20px; | |
786 | + padding: 20px; | |
787 | + border-top: 1px solid #f0f0f0; | |
788 | + | |
789 | + .upload-title { | |
790 | + font-size: 16px; | |
791 | + font-weight: bold; | |
792 | + margin-bottom: 16px; | |
793 | + } | |
794 | + | |
795 | + .upload-content { | |
796 | + display: flex; | |
797 | + flex-wrap: wrap; | |
798 | + gap: 12px; | |
799 | + } | |
800 | + | |
801 | + .upload-preview { | |
802 | + width: 104px; | |
803 | + height: 104px; | |
804 | + border: 1px solid #d9d9d9; | |
805 | + border-radius: 4px; | |
806 | + position: relative; | |
807 | + overflow: hidden; | |
808 | + | |
809 | + img { | |
810 | + width: 100%; | |
811 | + height: 100%; | |
812 | + object-fit: cover; | |
813 | + } | |
814 | + | |
815 | + .upload-preview-action { | |
816 | + position: absolute; | |
817 | + top: 0; | |
818 | + right: 0; | |
819 | + bottom: 0; | |
820 | + left: 0; | |
821 | + background: rgba(0, 0, 0, 0.5); | |
822 | + display: flex; | |
823 | + align-items: center; | |
824 | + justify-content: center; | |
825 | + color: white; | |
826 | + opacity: 0; | |
827 | + transition: opacity 0.3s; | |
828 | + | |
829 | + &:hover { | |
830 | + opacity: 1; | |
831 | + } | |
832 | + | |
833 | + .action-buttons { | |
834 | + display: flex; | |
835 | + gap: 15px; | |
836 | + | |
837 | + .action-icon { | |
838 | + font-size: 18px; | |
839 | + cursor: pointer; | |
840 | + | |
841 | + &:hover { | |
842 | + color: #1890ff; | |
843 | + transform: scale(1.2); | |
844 | + transition: all 0.2s; | |
845 | + } | |
846 | + } | |
847 | + } | |
848 | + } | |
849 | + } | |
850 | +.ant-message-notice .ant-message-loading { | |
851 | + color: red !important; | |
852 | +} | |
853 | + .upload-button { | |
854 | + width: 104px; | |
855 | + height: 104px; | |
856 | + border: 1px dashed #d9d9d9; | |
857 | + border-radius: 4px; | |
858 | + display: flex; | |
859 | + justify-content: center; | |
860 | + align-items: center; | |
861 | + cursor: pointer; | |
862 | + transition: border-color 0.3s; | |
863 | + | |
864 | + &:hover { | |
865 | + border-color: #1890ff; | |
866 | + } | |
867 | + | |
868 | + .upload-button-text { | |
869 | + text-align: center; | |
870 | + color: #666; | |
871 | + | |
872 | + .anticon { | |
873 | + font-size: 24px; | |
874 | + margin-bottom: 8px; | |
875 | + } | |
876 | + } | |
877 | + } | |
878 | +} | |
879 | + | |
880 | +/* 自定义文件上传样式 */ | |
881 | +.custom-file-upload { | |
882 | + margin-top: 20px; | |
883 | + padding: 20px; | |
884 | + border-top: 1px solid #f0f0f0; | |
885 | + | |
886 | + .upload-title { | |
887 | + font-size: 16px; | |
888 | + font-weight: bold; | |
889 | + margin-bottom: 16px; | |
890 | + } | |
891 | + | |
892 | + .file-list { | |
893 | + width: 100%; | |
894 | + } | |
895 | + | |
896 | + .file-item { | |
897 | + display: flex; | |
898 | + justify-content: space-between; | |
899 | + align-items: center; | |
900 | + padding: 12px 16px; | |
901 | + margin-bottom: 8px; | |
902 | + border: 1px solid #e8e8e8; | |
903 | + border-radius: 4px; | |
904 | + background-color: #fafafa; | |
905 | + | |
906 | + &:hover { | |
907 | + background-color: #f0f0f0; | |
908 | + } | |
909 | + | |
910 | + .file-info { | |
911 | + flex: 1; | |
912 | + | |
913 | + .file-name { | |
914 | + font-weight: 500; | |
915 | + margin-bottom: 4px; | |
916 | + word-break: break-all; | |
917 | + } | |
918 | + | |
919 | + .file-size { | |
920 | + color: #8c8c8c; | |
921 | + font-size: 12px; | |
922 | + } | |
923 | + } | |
924 | + | |
925 | + .file-actions { | |
926 | + display: flex; | |
927 | + gap: 12px; | |
928 | + | |
929 | + .action-btn { | |
930 | + color: #1890ff; | |
931 | + cursor: pointer; | |
932 | + | |
933 | + &:hover { | |
934 | + color: #40a9ff; | |
935 | + text-decoration: underline; | |
936 | + } | |
937 | + | |
938 | + &.delete { | |
939 | + color: #ff4d4f; | |
940 | + | |
941 | + &:hover { | |
942 | + color: #ff7875; | |
943 | + } | |
944 | + } | |
945 | + } | |
946 | + } | |
947 | + } | |
948 | + .file-upload-button { | |
949 | + width: 100%; | |
950 | + height: 80px; | |
951 | + border: 1px dashed #d9d9d9; | |
952 | + border-radius: 4px; | |
953 | + display: flex; | |
954 | + justify-content: center; | |
955 | + align-items: center; | |
956 | + cursor: pointer; | |
957 | + transition: border-color 0.3s; | |
958 | + margin-top: 16px; | |
959 | + | |
960 | + &:hover { | |
961 | + border-color: #1890ff; | |
962 | + background-color: #f6f6f6; | |
963 | + } | |
964 | + | |
965 | + .upload-button-text { | |
966 | + text-align: center; | |
967 | + color: #666; | |
968 | + | |
969 | + .anticon { | |
970 | + font-size: 24px; | |
971 | + margin-bottom: 8px; | |
972 | + } | |
973 | + } | |
974 | + } | |
975 | +} | |
976 | + | |
977 | +/* 查看模式下的图片显示样式 */ | |
978 | +.custom-image-view { | |
979 | + margin-top: 20px; | |
980 | + padding: 20px; | |
981 | + border-top: 1px solid #f0f0f0; | |
982 | + | |
983 | + .upload-title { | |
984 | + font-size: 16px; | |
985 | + font-weight: bold; | |
986 | + margin-bottom: 16px; | |
987 | + } | |
988 | + | |
989 | + .upload-content { | |
990 | + display: flex; | |
991 | + flex-wrap: wrap; | |
992 | + gap: 12px; | |
993 | + } | |
994 | + | |
995 | + .upload-preview { | |
996 | + width: 104px; | |
997 | + height: 104px; | |
998 | + border: 1px solid #d9d9d9; | |
999 | + border-radius: 4px; | |
1000 | + position: relative; | |
1001 | + overflow: hidden; | |
1002 | + | |
1003 | + img { | |
1004 | + width: 100%; | |
1005 | + height: 100%; | |
1006 | + object-fit: cover; | |
1007 | + } | |
1008 | + | |
1009 | + .view-preview-hint { | |
1010 | + position: absolute; | |
1011 | + top: 0; | |
1012 | + right: 0; | |
1013 | + bottom: 0; | |
1014 | + left: 0; | |
1015 | + background: rgba(0, 0, 0, 0.5); | |
1016 | + display: flex; | |
1017 | + align-items: center; | |
1018 | + justify-content: center; | |
1019 | + color: white; | |
1020 | + opacity: 0; | |
1021 | + transition: opacity 0.3s; | |
1022 | + | |
1023 | + &:hover { | |
1024 | + opacity: 1; | |
1025 | + } | |
1026 | + | |
1027 | + .anticon { | |
1028 | + font-size: 18px; | |
1029 | + margin-right: 8px; | |
1030 | + } | |
1031 | + } | |
1032 | + } | |
1033 | +} | |
1034 | + | |
1035 | +/* 查看模式下的附件显示样式 */ | |
1036 | +.custom-file-view { | |
1037 | + margin-top: 20px; | |
1038 | + padding: 20px; | |
1039 | + border-top: 1px solid #f0f0f0; | |
1040 | + | |
1041 | + .upload-title { | |
1042 | + font-size: 16px; | |
1043 | + font-weight: bold; | |
1044 | + margin-bottom: 16px; | |
1045 | + } | |
1046 | + | |
1047 | + .file-list { | |
1048 | + width: 100%; | |
1049 | + } | |
1050 | + | |
1051 | + .file-item { | |
1052 | + display: flex; | |
1053 | + justify-content: space-between; | |
1054 | + align-items: center; | |
1055 | + padding: 12px 16px; | |
1056 | + margin-bottom: 8px; | |
1057 | + border: 1px solid #e8e8e8; | |
1058 | + border-radius: 4px; | |
1059 | + background-color: #fafafa; | |
1060 | + | |
1061 | + &:hover { | |
1062 | + background-color: #f0f0f0; | |
1063 | + } | |
1064 | + | |
1065 | + .file-info { | |
1066 | + flex: 1; | |
1067 | + | |
1068 | + .file-name { | |
1069 | + font-weight: 500; | |
1070 | + margin-bottom: 4px; | |
1071 | + word-break: break-all; | |
1072 | + } | |
1073 | + | |
1074 | + .file-size { | |
1075 | + color: #8c8c8c; | |
1076 | + font-size: 12px; | |
1077 | + } | |
1078 | + } | |
1079 | + | |
1080 | + .file-actions { | |
1081 | + display: flex; | |
1082 | + gap: 12px; | |
1083 | + | |
1084 | + .action-btn { | |
1085 | + color: #1890ff; | |
1086 | + cursor: pointer; | |
1087 | + | |
1088 | + &:hover { | |
1089 | + color: #40a9ff; | |
1090 | + text-decoration: underline; | |
1091 | + } | |
1092 | + } | |
1093 | + } | |
1094 | + } | |
1095 | +} | |
1096 | +</style> | |
0 | 1097 | \ No newline at end of file | ... | ... |
src/views/project/quest/index.vue
0 → 100644
1 | +<template> | |
2 | + <div class="quest-container"> | |
3 | + <!-- 搜索区域 --> | |
4 | + <!-- <div class="bg-white p-4 mb-4"> | |
5 | + <BasicForm @register="registerForm" /> | |
6 | + </div> --> | |
7 | + | |
8 | + <!-- 表格区域 --> | |
9 | + <div class="bg-white"> | |
10 | + <BasicTable @register="registerTable"> | |
11 | + <template #toolbar> | |
12 | + <a-button type="primary" @click="handleCreate"> | |
13 | + 新建 | |
14 | + </a-button> | |
15 | + </template> | |
16 | + | |
17 | + <template #bodyCell="{ column, record }"> | |
18 | + <template v-if="column.key === 'action'"> | |
19 | + <TableAction | |
20 | + :actions="[ | |
21 | + { | |
22 | + icon: 'ant-design:eye-outlined', | |
23 | + tooltip: '查看', | |
24 | + onClick: handleView.bind(null, record), | |
25 | + }, | |
26 | + { | |
27 | + icon: 'clarity:note-edit-line', | |
28 | + tooltip: '编辑', | |
29 | + onClick: handleEdit.bind(null, record), | |
30 | + }, | |
31 | + { | |
32 | + icon: 'ant-design:delete-outlined', | |
33 | + color: 'error', | |
34 | + tooltip: '删除', | |
35 | + popConfirm: { | |
36 | + title: '是否确认删除?', | |
37 | + confirm: handleDelete.bind(null, record), | |
38 | + }, | |
39 | + }, | |
40 | + ]" | |
41 | + /> | |
42 | + </template> | |
43 | + </template> | |
44 | + </BasicTable> | |
45 | + </div> | |
46 | + | |
47 | + <!-- 编辑弹窗 --> | |
48 | + <QuestDrawer @register="registerDrawer" @success="handleSuccess" /> | |
49 | + </div> | |
50 | +</template> | |
51 | + | |
52 | +<script lang="ts" setup name="QuestList"> | |
53 | + import { onMounted, ref } from 'vue'; | |
54 | + import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
55 | + import { useDrawer } from '/@/components/Drawer'; | |
56 | + import QuestDrawer from './QuestDrawer.vue'; | |
57 | + import { getQuestList, questDelete } from '/@/api/project/quest'; | |
58 | + import { columns, searchFormSchema } from './quest.data'; | |
59 | + | |
60 | + // 注册表格 | |
61 | + const [registerTable, { reload }] = useTable({ | |
62 | + title: '问题列表', | |
63 | + api: getQuestList, | |
64 | + columns, | |
65 | + formConfig: { | |
66 | + labelWidth: 120, | |
67 | + schemas: searchFormSchema, | |
68 | + autoSubmitOnEnter: true, | |
69 | + }, | |
70 | + bordered: true, | |
71 | + showIndexColumn: true, | |
72 | + useSearchForm: true,//启用表单搜索 | |
73 | + }); | |
74 | + // 注册抽屉 | |
75 | + const [registerDrawer, { openDrawer }] = useDrawer(); | |
76 | + | |
77 | + // 表单提交 | |
78 | + // async function handleSubmit(values) { | |
79 | + // console.log('表单提交', values); | |
80 | + // // 假设 contentText 是需要转换为数组的字段 | |
81 | + // if (typeof values.contentText === 'string') { | |
82 | + // values.contentText = values.contentText.split(',').map(item => item.trim()); | |
83 | + // } | |
84 | + // await reload(values); | |
85 | + // return Promise.resolve(); | |
86 | + // } | |
87 | + | |
88 | + // 新建 | |
89 | + function handleCreate() { | |
90 | + openDrawer(true, { | |
91 | + isUpdate: false, | |
92 | + }); | |
93 | + } | |
94 | + | |
95 | + // 查看(只读模式) | |
96 | + function handleView(record) { | |
97 | + openDrawer(true, { | |
98 | + record, | |
99 | + isUpdate: true, | |
100 | + isView: true, // 标记为查看模式 | |
101 | + }); | |
102 | + } | |
103 | + | |
104 | + // 编辑 | |
105 | + function handleEdit(record) { | |
106 | + console.log('编辑记录:', record); | |
107 | + console.log('记录ID:', record.id); | |
108 | + | |
109 | + openDrawer(true, { | |
110 | + record, | |
111 | + isUpdate: true, | |
112 | + isView: false, // 显式标记为编辑模式 | |
113 | + }); | |
114 | + } | |
115 | + | |
116 | + // 删除 | |
117 | + async function handleDelete(record) { | |
118 | + await questDelete([record.id]); | |
119 | + await reload(); | |
120 | + } | |
121 | + | |
122 | + // 刷新表格 | |
123 | + function handleSuccess() { | |
124 | + reload(); | |
125 | + } | |
126 | + | |
127 | + // 页面加载完成后请求数据 | |
128 | + onMounted(async () => { | |
129 | + | |
130 | + }); | |
131 | +</script> | |
132 | + | |
133 | +<style lang="less" scoped> | |
134 | + .quest-container { | |
135 | + padding: 16px; | |
136 | + | |
137 | + :deep(.ant-table-wrapper) { | |
138 | + padding: 16px; | |
139 | + } | |
140 | + } | |
141 | +</style> | ... | ... |
src/views/project/quest/quest.data.tsx
0 → 100644
1 | +import { BasicColumn, FormSchema } from '/@/components/Table'; | |
2 | + // 表格列定义 | |
3 | + export const columns: BasicColumn[] = [ | |
4 | + { | |
5 | + title: '标题', | |
6 | + dataIndex: 'title', | |
7 | + key: 'title', | |
8 | + width: 600, | |
9 | + customHeaderCell: () => ({ style: { color: 'red' ,fontSize:'16px'} }), // 让表头变红 | |
10 | + }, | |
11 | + { | |
12 | + title: '内容', | |
13 | + dataIndex: 'contentText', | |
14 | + key: 'contentText', | |
15 | + format: (text) => { | |
16 | + if (!text) return ''; | |
17 | + const div = document.createElement('div'); | |
18 | + div.innerHTML = text; | |
19 | + const plainText = div.textContent || div.innerText || ''; | |
20 | + return plainText.length > 30 ? plainText.substring(0, 30) + '...' : plainText; | |
21 | + }, | |
22 | + customHeaderCell: () => ({ style: { color: 'red',fontSize:'16px' } }), // 让表头变红 | |
23 | + }, | |
24 | + { | |
25 | + title: '操作', | |
26 | + dataIndex: 'action', | |
27 | + width: 160, | |
28 | + key: 'action', | |
29 | + }, | |
30 | + ]; | |
31 | + | |
32 | + | |
33 | + // 表单配置 | |
34 | + export const searchFormSchema: FormSchema[] = [ | |
35 | + { | |
36 | + field: 'title', | |
37 | + label: '标题', | |
38 | + component: 'Input', | |
39 | + colProps: { span: 8 }, | |
40 | + }, | |
41 | + { | |
42 | + field: 'contentText', | |
43 | + label: '内容关键字', | |
44 | + component: 'Input', | |
45 | + colProps: { span: 8 }, | |
46 | + }, | |
47 | + { | |
48 | + field: 'createBy', | |
49 | + label: '创建人', | |
50 | + component: 'Input', | |
51 | + colProps: { span: 8 }, | |
52 | + }, | |
53 | + ]; | |
0 | 54 | \ No newline at end of file | ... | ... |