Commit fb677d76ef6fcf727571c966fdaf5826804005dd
1 parent
c6c33024
feat: 问题列表
fix: 禁止业务员对生产科,数量进行字段审核。
Showing
13 changed files
with
2114 additions
and
225 deletions
src/api/project/quest.ts
... | ... | @@ -5,28 +5,40 @@ enum Api { |
5 | 5 | QUEST_CREATE = '/order/erp/quest/add', |
6 | 6 | QUEST_DELETE = '/order/erp/quest/delete_by_id', |
7 | 7 | QUEST_UPDATE = '/order/erp/quest/edit', |
8 | + QUEST_SET_STATUS = '/order/erp/quest/setStatus', | |
9 | + QUEST_RELEASE_LIST = '/order/erp/quest/realseList', | |
10 | + QUEST_GET_ALL = '/order/erp/quest/get_all', | |
11 | + QUEST_QUERY_TITLE = '/order/erp/quest/queryTitle', | |
8 | 12 | } |
9 | 13 | |
10 | 14 | export const questUpdate=async(data:any)=>{ |
11 | 15 | const res=await defHttp.post<any>({ |
12 | 16 | url: Api.QUEST_UPDATE, |
13 | - data},{message:'操作成功'}); | |
17 | + data, | |
18 | + successMsg: '操作成功' | |
19 | + }); | |
14 | 20 | return res; |
15 | 21 | } |
16 | 22 | |
17 | 23 | 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: '删除成功' }); | |
24 | + const res = await defHttp.post<any>({ | |
25 | + url: Api.QUEST_DELETE, | |
26 | + data: {ids}, | |
27 | + successMsg: '删除成功' | |
28 | + }); | |
20 | 29 | return res; |
21 | 30 | }; |
22 | 31 | |
23 | 32 | export const questCreate = async (data: any) => { |
24 | - const res = await defHttp.post<any>({ url: Api.QUEST_CREATE, data }, { message: '创建成功' }); | |
33 | + const res = await defHttp.post<any>({ | |
34 | + url: Api.QUEST_CREATE, | |
35 | + data, | |
36 | + successMsg: '创建成功' | |
37 | + }); | |
25 | 38 | return res; |
26 | 39 | }; |
27 | 40 | |
28 | 41 | export const getQuestList = async (params: any) => { |
29 | - console.log('params',params); | |
30 | 42 | const res=await defHttp.post<any>({ |
31 | 43 | url: Api.QUEST_LIST, |
32 | 44 | params, |
... | ... | @@ -37,3 +49,53 @@ export const getQuestList = async (params: any) => { |
37 | 49 | }; |
38 | 50 | return result; |
39 | 51 | }; |
52 | + | |
53 | +export const setQuestStatus = async (data: { id: number; status: number; refuseRemark?: string }) => { | |
54 | + const res = await defHttp.post<any>({ | |
55 | + url: Api.QUEST_SET_STATUS, | |
56 | + data, | |
57 | + successMsg: data.status === 10 ? '审核通过成功' : '审核驳回成功' | |
58 | + }); | |
59 | + return res; | |
60 | +}; | |
61 | + | |
62 | +export const getQuestReleaseData = async (id: number) => { | |
63 | + const res = await defHttp.post<any>({ | |
64 | + url: Api.QUEST_RELEASE_LIST, | |
65 | + data: { id } | |
66 | + }); | |
67 | + | |
68 | + // 如果返回的是带记录和总数的格式,处理为标准格式 | |
69 | + if (res && res.records && res.total !== undefined) { | |
70 | + return { | |
71 | + items: res.records, | |
72 | + total: res.total | |
73 | + }; | |
74 | + } | |
75 | + | |
76 | + return res; | |
77 | +}; | |
78 | + | |
79 | +/** | |
80 | + * 获取所有扣款原因列表 | |
81 | + * @returns 扣款原因列表数组,包含id和title | |
82 | + */ | |
83 | +export const getAllQuests = async () => { | |
84 | + const res = await defHttp.post<any>({ | |
85 | + url: Api.QUEST_GET_ALL | |
86 | + }); | |
87 | + return res; | |
88 | +}; | |
89 | + | |
90 | +/** | |
91 | + * 根据输入的关键词搜索匹配的扣款原因 | |
92 | + * @param title 搜索关键词 | |
93 | + * @returns 匹配的扣款原因列表数组,包含id和title | |
94 | + */ | |
95 | +export const queryQuestTitle = async (title: string) => { | |
96 | + const res = await defHttp.post<any>({ | |
97 | + url: Api.QUEST_QUERY_TITLE, | |
98 | + data: { title } | |
99 | + }); | |
100 | + return res; | |
101 | +}; | ... | ... |
src/views/project/finance/financeList/FinanceEdit.vue
... | ... | @@ -16,16 +16,16 @@ |
16 | 16 | <BasicForm @register="registerForm" /> |
17 | 17 | </div> --> |
18 | 18 | <div style="font-size: 15px">实际收款金额1$</div> |
19 | - <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10" auto-size /> | |
19 | + <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
20 | 20 | <div style="margin: 16px 0"></div> |
21 | 21 | <div style="font-size: 15px">实际收款金额2$</div> |
22 | - <a-input v-model:value="input2" placeholder="请输入" :disabled="status === 10" auto-size /> | |
22 | + <a-input v-model:value="input2" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
23 | 23 | <div style="margin: 16px 0"></div> |
24 | 24 | <div style="font-size: 15px">实际收款金额3$</div> |
25 | - <a-input v-model:value="input3" placeholder="请输入" :disabled="status === 10" auto-size /> | |
25 | + <a-input v-model:value="input3" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
26 | 26 | <div style="margin: 16px 0"></div> |
27 | 27 | <div style="font-size: 15px">其他费用金额$</div> |
28 | - <a-input v-model:value="input4" placeholder="请输入" :disabled="status === 10" auto-size /> | |
28 | + <a-input v-model:value="input4" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
29 | 29 | <div style="margin: 16px 0"></div> |
30 | 30 | |
31 | 31 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> | ... | ... |
src/views/project/finance/financeList/FinanceEditCheck.vue
... | ... | @@ -16,13 +16,13 @@ |
16 | 16 | <BasicForm @register="registerForm" /> |
17 | 17 | </div> --> |
18 | 18 | <div style="font-size: 15px">实际付款金额1¥</div> |
19 | - <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10" auto-size /> | |
19 | + <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
20 | 20 | <div style="margin: 16px 0"></div> |
21 | 21 | <div style="font-size: 15px">实际付款金额2¥</div> |
22 | - <a-input v-model:value="input2" placeholder="请输入" :disabled="status === 10" auto-size /> | |
22 | + <a-input v-model:value="input2" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
23 | 23 | <div style="margin: 16px 0"></div> |
24 | 24 | <div style="font-size: 15px">实际付款金额3¥</div> |
25 | - <a-input v-model:value="input3" placeholder="请输入" :disabled="status === 10" auto-size /> | |
25 | + <a-input v-model:value="input3" placeholder="请输入" :disabled="status === 10 || status === 40" auto-size /> | |
26 | 26 | <div style="margin: 16px 0"></div> |
27 | 27 | |
28 | 28 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> | ... | ... |
src/views/project/finance/financeList/TrackEdit.vue
... | ... | @@ -15,7 +15,16 @@ |
15 | 15 | > |
16 | 16 | <div> |
17 | 17 | <div style="font-size: 15px">客户扣款金额$</div> |
18 | - <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10" auto-size /> | |
18 | + <a-input | |
19 | + v-model:value="input1" | |
20 | + placeholder="请输入" | |
21 | + :disabled="status === 10 || status === 40" | |
22 | + auto-size | |
23 | + :status="amountMismatch ? 'error' : ''" | |
24 | + /> | |
25 | + <div v-if="amountMismatch" style="color: #ff4d4f; font-size: 19px; margin-top: 4px"> | |
26 | + 金额不匹配!扣款原因金额为: {{ bindedDeductAmount }} | |
27 | + </div> | |
19 | 28 | <div style="margin: 16px 0"></div> |
20 | 29 | <div>上传扣款单</div |
21 | 30 | ><a-space direction="vertical" style="width: 100%" size="large"> |
... | ... | @@ -26,11 +35,45 @@ |
26 | 35 | :max-count="1" |
27 | 36 | :action="updateDeductUrl" |
28 | 37 | @change="handleChange" |
29 | - :disabled="status === 10" | |
38 | + :disabled="status === 10 || status === 40" | |
30 | 39 | > |
31 | 40 | <a-button> 上传扣款单 </a-button> |
32 | 41 | </a-upload> |
33 | 42 | </a-space> |
43 | + | |
44 | + <div style="margin: 16px 0"></div> | |
45 | + <div>扣款原因(可选)</div> | |
46 | + <a-select | |
47 | + v-model:value="selectedTitle" | |
48 | + placeholder="请选择或输入扣款原因" | |
49 | + style="width: 100%" | |
50 | + :filter-option="false" | |
51 | + show-search | |
52 | + :disabled="status === 10" | |
53 | + @search="handleSearch" | |
54 | + @change="handleTitleChange" | |
55 | + :loading="titleLoading" | |
56 | + :options="formattedOptions" | |
57 | + :not-found-content="titleLoading ? '加载中...' : (titleOptions.length === 0 ? '未找到匹配项' : null)" | |
58 | + allow-clear | |
59 | + @clear="handleClear" | |
60 | + :auto-focus="true" | |
61 | + :default-open="false" | |
62 | + :drop-down-style="{ maxHeight: '400px', overflow: 'auto' }" | |
63 | + > | |
64 | + </a-select> | |
65 | + <!-- 调试信息 --> | |
66 | + <div v-if="debug" style="margin-top: 8px; color: #999; font-size: 12px;"> | |
67 | + 选项数量: {{ titleOptions.length }} | |
68 | + <div v-if="titleOptions.length > 0"> | |
69 | + 第一个选项: {{ titleOptions[0].title }} (ID: {{ titleOptions[0].id }}) | |
70 | + </div> | |
71 | + <div> | |
72 | + 绑定金额: {{ bindedDeductAmount }} | |
73 | + 当前输入: {{ input1 }} | |
74 | + 是否不匹配: {{ amountMismatch }} | |
75 | + </div> | |
76 | + </div> | |
34 | 77 | </div> |
35 | 78 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> |
36 | 79 | <template #appendFooter> |
... | ... | @@ -41,12 +84,12 @@ |
41 | 84 | </template> |
42 | 85 | <script lang="ts" setup> |
43 | 86 | import { BasicDrawer, useDrawerInner } from '@/components/Drawer'; |
44 | - import { defineComponent, ref, computed, unref, toRaw, reactive } from 'vue'; | |
45 | - import { getEmailList } from '/@/api/sys/config'; | |
46 | - import { UploadOutlined } from '@ant-design/icons-vue'; | |
87 | + import { ref, onMounted, computed, watch } from 'vue'; | |
47 | 88 | import type { UploadProps } from 'ant-design-vue'; |
48 | 89 | import { updateDeduct } from '@/api/project/invoice'; |
49 | 90 | import { useMessage } from '/@/hooks/web/useMessage'; |
91 | + import { getAllQuests, queryQuestTitle } from '@/api/project/quest'; | |
92 | + import { handleViewRichText } from '../financeList/finance.data'; | |
50 | 93 | |
51 | 94 | const emit = defineEmits(['success']); |
52 | 95 | const fileList = ref<UploadProps['fileList']>([]); |
... | ... | @@ -63,16 +106,264 @@ |
63 | 106 | const { createMessage } = useMessage(); |
64 | 107 | const { error } = createMessage; |
65 | 108 | const status = ref(); |
109 | + | |
110 | + // 扣款原因相关状态 | |
111 | + const titleOptions = ref<any[]>([]); | |
112 | + const selectedTitle = ref<number | undefined>(undefined); | |
113 | + const selectedTitleText = ref(''); | |
114 | + const titleLoading = ref(false); | |
115 | + const debug = ref(false); // 关闭调试模式 | |
116 | + | |
117 | + // 添加绑定的deductAmount变量 | |
118 | + const bindedDeductAmount = ref<number | undefined>(undefined); | |
119 | + // 添加金额不一致的提示状态 | |
120 | + const amountMismatch = ref(false); | |
121 | + | |
122 | + // 转换为antd需要的options格式 | |
123 | + const formattedOptions = computed(() => { | |
124 | + return titleOptions.value.map(item => ({ | |
125 | + value: item.id, | |
126 | + label: item.title, | |
127 | + })); | |
128 | + }); | |
66 | 129 | |
67 | - const [register, { setDrawerProps, closeDrawer }] = useDrawerInner((data) => { | |
130 | + const [register, { closeDrawer }] = useDrawerInner((data) => { | |
68 | 131 | status.value = data.data.invoiceStatus; |
69 | 132 | id.value = data.data.invoiceId; |
70 | 133 | invoiceNo.value = data.data.invoiceNo; |
71 | 134 | input1.value = data.data.invoiceDeductAmount; |
72 | 135 | deductUrl.value = data.data.invoiceDeductUrl; |
73 | 136 | deductUrlOld.value = data.data.invoiceDeductUrl; |
137 | + | |
138 | + // 优先使用questId,如果没有则尝试使用invoiceRichTextId | |
139 | + selectedTitle.value = data.data.questId || data.data.invoiceRichTextId; | |
140 | + | |
141 | + // 加载扣款原因选项 | |
142 | + loadQuestOptions(); | |
74 | 143 | }); |
75 | 144 | |
145 | + // 处理API返回的数据,统一格式 | |
146 | + function normalizeData(data: any[]): { id: number; title: string; deductAmount?: number }[] { | |
147 | + | |
148 | + if (!data || !Array.isArray(data) || data.length === 0) { | |
149 | + return []; | |
150 | + } | |
151 | + | |
152 | + const result = data.map(item => { | |
153 | + // 处理可能的不同字段名称 | |
154 | + const id = item.id !== undefined ? item.id : | |
155 | + (item.questId !== undefined ? item.questId : null); | |
156 | + | |
157 | + const title = item.title !== undefined ? item.title : | |
158 | + (item.questTitle !== undefined ? item.questTitle : | |
159 | + (item.name !== undefined ? item.name : '未知标题')); | |
160 | + | |
161 | + // 添加新的deductAmount字段处理 | |
162 | + const deductAmount = item.deductAmount !== undefined ? Number(item.deductAmount) : undefined; | |
163 | + | |
164 | + return { id, title, deductAmount }; | |
165 | + }).filter(item => item.id !== null); // 过滤掉无效项 | |
166 | + return result; | |
167 | + } | |
168 | + | |
169 | + // 加载所有扣款原因选项 | |
170 | + async function loadQuestOptions() { | |
171 | + titleLoading.value = true; | |
172 | + try { | |
173 | + // 如果已有选中ID但没有选项,先尝试直接获取该ID的详情 | |
174 | + if (selectedTitle.value && (!titleOptions.value || titleOptions.value.length === 0)) { | |
175 | + try { | |
176 | + // 使用finance.data.ts中的handleViewRichText方法获取富文本详情 | |
177 | + const richTextDetail = await handleViewRichText(String(selectedTitle.value)); | |
178 | + if (richTextDetail) { | |
179 | + // 设置选项 | |
180 | + titleOptions.value = [{ | |
181 | + id: richTextDetail.id, | |
182 | + title: richTextDetail.title || '扣款原因详情', | |
183 | + deductAmount: richTextDetail.deductAmount // 确保获取deductAmount | |
184 | + }]; | |
185 | + selectedTitleText.value = richTextDetail.title || '扣款原因详情'; | |
186 | + // 绑定deductAmount如果存在 | |
187 | + if (richTextDetail.deductAmount !== undefined) { | |
188 | + bindedDeductAmount.value = Number(richTextDetail.deductAmount); | |
189 | + checkAmountMatch(); | |
190 | + } | |
191 | + titleLoading.value = false; | |
192 | + return; | |
193 | + } | |
194 | + } catch (err) { | |
195 | + console.error('获取单个扣款原因详情失败:', err); | |
196 | + } | |
197 | + } | |
198 | + | |
199 | + const res = await getAllQuests(); | |
200 | + | |
201 | + // 处理不同格式的返回数据 | |
202 | + let tempData: any[] = []; | |
203 | + | |
204 | + if (Array.isArray(res)) { | |
205 | + tempData = res; | |
206 | + } else if (res && Array.isArray(res.records)) { | |
207 | + tempData = res.records; | |
208 | + } else if (res && Array.isArray(res.items)) { | |
209 | + tempData = res.items; | |
210 | + } else if (res && typeof res === 'object') { | |
211 | + tempData = [res]; | |
212 | + } | |
213 | + | |
214 | + // 标准化数据格式 | |
215 | + titleOptions.value = normalizeData(tempData); | |
216 | + | |
217 | + // 如果有已选择的ID,查找对应的title用于显示 | |
218 | + if (selectedTitle.value && titleOptions.value.length > 0) { | |
219 | + const found = titleOptions.value.find(item => item.id === selectedTitle.value); | |
220 | + if (found) { | |
221 | + selectedTitleText.value = found.title; | |
222 | + // 绑定deductAmount如果存在 | |
223 | + if (found.deductAmount !== undefined) { | |
224 | + bindedDeductAmount.value = Number(found.deductAmount); | |
225 | + checkAmountMatch(); | |
226 | + } | |
227 | + } else { | |
228 | + // 如果在选项中找不到对应ID,可以尝试单独请求这个ID的详情 | |
229 | + try { | |
230 | + const richTextDetail = await handleViewRichText(String(selectedTitle.value)); | |
231 | + if (richTextDetail) { | |
232 | + // 添加到选项中 | |
233 | + titleOptions.value.push({ | |
234 | + id: richTextDetail.id, | |
235 | + title: richTextDetail.title || '扣款原因详情', | |
236 | + deductAmount: richTextDetail.deductAmount // 确保获取deductAmount | |
237 | + }); | |
238 | + selectedTitleText.value = richTextDetail.title || '扣款原因详情'; | |
239 | + // 绑定deductAmount如果存在 | |
240 | + if (richTextDetail.deductAmount !== undefined) { | |
241 | + bindedDeductAmount.value = Number(richTextDetail.deductAmount); | |
242 | + checkAmountMatch(); | |
243 | + } | |
244 | + } | |
245 | + } catch (err) { | |
246 | + console.error('获取缺失的扣款原因详情失败:', err); | |
247 | + } | |
248 | + } | |
249 | + } | |
250 | + | |
251 | + } catch (err) { | |
252 | + error('获取扣款原因列表失败'); | |
253 | + console.error('获取扣款原因列表失败:', err); | |
254 | + titleOptions.value = []; | |
255 | + } finally { | |
256 | + titleLoading.value = false; | |
257 | + } | |
258 | + } | |
259 | + | |
260 | + // 搜索扣款原因 | |
261 | + async function handleSearch(value: string) { | |
262 | + if (!value || value.trim() === '') { | |
263 | + await loadQuestOptions(); | |
264 | + return; | |
265 | + } | |
266 | + | |
267 | + titleLoading.value = true; | |
268 | + try { | |
269 | + const res = await queryQuestTitle(value); | |
270 | + | |
271 | + // 确保res不为null或undefined | |
272 | + if (!res) { | |
273 | + titleOptions.value = []; | |
274 | + return; | |
275 | + } | |
276 | + | |
277 | + // 处理不同格式的返回数据 | |
278 | + let tempData: any[] = []; | |
279 | + | |
280 | + if (Array.isArray(res)) { | |
281 | + tempData = res; | |
282 | + } else if (res && Array.isArray(res.records)) { | |
283 | + tempData = res.records; | |
284 | + } else if (res && Array.isArray(res.items)) { | |
285 | + tempData = res.items; | |
286 | + } else if (res && typeof res === 'object') { | |
287 | + tempData = [res]; | |
288 | + } | |
289 | + | |
290 | + // 标准化数据格式 | |
291 | + titleOptions.value = normalizeData(tempData); | |
292 | + } catch (err) { | |
293 | + error('搜索扣款原因失败'); | |
294 | + console.error('搜索扣款原因失败:', err); | |
295 | + titleOptions.value = []; | |
296 | + } finally { | |
297 | + titleLoading.value = false; | |
298 | + } | |
299 | + } | |
300 | + | |
301 | + // 选择扣款原因时触发 | |
302 | + function handleTitleChange(value: number, option: any) { | |
303 | + // 检查是否为空值(用户点击了清除按钮) | |
304 | + if (value === undefined || value === null) { | |
305 | + handleClear(); | |
306 | + return; | |
307 | + } | |
308 | + | |
309 | + selectedTitle.value = value; | |
310 | + | |
311 | + // 当使用options属性时,option为选中的选项对象 | |
312 | + if (option && typeof option === 'object') { | |
313 | + if (option.label) { | |
314 | + // 新版本Ant Design Vue返回的选项格式 | |
315 | + selectedTitleText.value = option.label; | |
316 | + } else if (option.children) { | |
317 | + // 旧版本Ant Design Vue返回的选项格式 | |
318 | + selectedTitleText.value = option.children; | |
319 | + } | |
320 | + } | |
321 | + | |
322 | + // 直接从选项列表中查找 | |
323 | + const found = titleOptions.value.find(item => item.id === value); | |
324 | + if (found) { | |
325 | + selectedTitleText.value = found.title; | |
326 | + // 绑定deductAmount | |
327 | + bindedDeductAmount.value = found.deductAmount; | |
328 | + // 检查金额是否一致 | |
329 | + checkAmountMatch(); | |
330 | + } | |
331 | + } | |
332 | + | |
333 | + // 清除选择 | |
334 | + function handleClear() { | |
335 | + selectedTitle.value = undefined; | |
336 | + selectedTitleText.value = ''; | |
337 | + // 清除绑定的deductAmount | |
338 | + bindedDeductAmount.value = undefined; | |
339 | + // 重置金额不匹配状态 | |
340 | + amountMismatch.value = false; | |
341 | + | |
342 | + // 重新加载所有选项 | |
343 | + loadQuestOptions(); | |
344 | + } | |
345 | + | |
346 | + // 检查金额是否匹配 | |
347 | + function checkAmountMatch() { | |
348 | + if (bindedDeductAmount.value !== undefined && input1.value !== undefined) { | |
349 | + try { | |
350 | + // 确保转换为数字进行比较,处理可能的字符串转换问题 | |
351 | + const boundAmount = typeof bindedDeductAmount.value === 'string' | |
352 | + ? parseFloat(bindedDeductAmount.value) | |
353 | + : Number(bindedDeductAmount.value); | |
354 | + | |
355 | + const inputAmount = typeof input1.value === 'string' | |
356 | + ? parseFloat(input1.value) | |
357 | + : Number(input1.value); | |
358 | + | |
359 | + // 比较金额是否一致,使用差值小于极小值来处理浮点数误差 | |
360 | + amountMismatch.value = Math.abs(boundAmount - inputAmount) > 0.001; | |
361 | + } catch (err) { | |
362 | + console.error('金额比较出错:', err); | |
363 | + } | |
364 | + } | |
365 | + } | |
366 | + | |
76 | 367 | function handleChange(info) { |
77 | 368 | if (info.file.status == 'done') { |
78 | 369 | updateDeductUrl.value = info.file.response.data.fileUrl; |
... | ... | @@ -86,33 +377,56 @@ |
86 | 377 | function beforeUpload(info) { |
87 | 378 | updateDeductUrl.value = uploadUrl.value + info.name; |
88 | 379 | } |
89 | - function handleShow() { | |
90 | - // if (!visible) { | |
91 | - // input1.value = 0; | |
92 | - // deductUrl.value = ''; | |
93 | - // updateDeductUrl.value = ''; | |
94 | - // fileList.value = []; | |
95 | - // } | |
96 | - // input1.value = ''; | |
97 | - // deductUrl.value = ''; | |
98 | - // updateDeductUrl.value = ''; | |
99 | - // fileList.value = []; | |
380 | + function handleShow(visible) { | |
381 | + if (visible) { | |
382 | + // 抽屉打开时加载扣款原因选项 | |
383 | + // loadQuestOptions(); | |
384 | + } | |
100 | 385 | } |
101 | 386 | |
387 | + // 页面加载时获取扣款原因列表 | |
388 | + onMounted(() => { | |
389 | + // loadQuestOptions(); | |
390 | + }); | |
391 | + | |
392 | + // 监听客户扣款金额变化 | |
393 | + function handleInputChange() { | |
394 | + // 检查金额是否匹配 | |
395 | + checkAmountMatch(); | |
396 | + } | |
397 | + | |
398 | + // 监听input1的变化 | |
399 | + watch(input1, () => { | |
400 | + handleInputChange(); | |
401 | + }, { immediate: true }); // 确保首次加载也会触发 | |
402 | + | |
102 | 403 | //完成编辑 |
103 | 404 | async function handleSubmit() { |
104 | 405 | if (!input1.value) { |
105 | - error('选项不能为空'); | |
106 | - } else { | |
406 | + error('客户扣款金额不能为空'); | |
407 | + return; | |
408 | + } | |
409 | + | |
410 | + // 添加金额不一致的检查 | |
411 | + if (amountMismatch.value) { | |
412 | + error(`客户扣款金额与选择的扣款原因金额不一致!扣款原因金额为: ${bindedDeductAmount.value}`); | |
413 | + return; | |
414 | + } | |
415 | + | |
416 | + try { | |
107 | 417 | await updateDeduct({ |
108 | 418 | id: id.value, |
109 | 419 | invoiceNo: invoiceNo.value, |
110 | 420 | deductAmount: input1.value, |
111 | 421 | deductUrl: deductUrl.value, |
422 | + questId: selectedTitle.value, // 传递选中的扣款原因ID | |
112 | 423 | }); |
113 | 424 | fileList.value = []; |
114 | 425 | emit('success'); |
115 | 426 | closeDrawer(); |
427 | + } catch (err) { | |
428 | + error('保存失败'); | |
429 | + console.error('保存失败:', err); | |
116 | 430 | } |
117 | 431 | } |
118 | 432 | </script> | ... | ... |
src/views/project/finance/financeList/TrackEditCheck.vue
... | ... | @@ -15,13 +15,22 @@ |
15 | 15 | > |
16 | 16 | <div> |
17 | 17 | <div style="font-size: 15px">生产科扣款金额¥</div> |
18 | - <a-input v-model:value="input1" placeholder="请输入" :disabled="status === 10" auto-size /> | |
18 | + <a-input | |
19 | + v-model:value="input1" | |
20 | + placeholder="请输入" | |
21 | + :disabled="status === 10 || status === 40" | |
22 | + auto-size | |
23 | + :status="amountMismatch ? 'error' : ''" | |
24 | + /> | |
25 | + <div v-if="amountMismatch" style="color: #ff4d4f; font-size: 19px; margin-top: 4px"> | |
26 | + 金额不匹配!扣款原因金额为: {{ bindedDeductAmount }} | |
27 | + </div> | |
19 | 28 | <div style="margin: 16px 0"></div> |
20 | 29 | <div style="font-size: 15px">扣款责任部门</div> |
21 | 30 | <a-input |
22 | 31 | v-model:value="deductDept" |
23 | 32 | placeholder="请输入" |
24 | - :disabled="status === 10" | |
33 | + :disabled="status === 10 || status === 40" | |
25 | 34 | auto-size |
26 | 35 | /> |
27 | 36 | <!-- <a-select |
... | ... | @@ -44,13 +53,35 @@ |
44 | 53 | :beforeUpload="beforeUpload" |
45 | 54 | list-type="picture" |
46 | 55 | :max-count="1" |
47 | - :disabled="status === 10" | |
56 | + :disabled="status === 10 || status === 40" | |
48 | 57 | :action="updateDeductUrl" |
49 | 58 | @change="handleChange" |
50 | 59 | > |
51 | 60 | <a-button> 上传扣款单 </a-button> |
52 | 61 | </a-upload> |
53 | 62 | </a-space> |
63 | + | |
64 | + <div style="margin: 16px 0"></div> | |
65 | + <div>扣款原因(可选)</div> | |
66 | + <a-select | |
67 | + v-model:value="selectedTitle" | |
68 | + placeholder="请选择或输入扣款原因" | |
69 | + style="width: 100%" | |
70 | + :filter-option="false" | |
71 | + show-search | |
72 | + :disabled="status === 10" | |
73 | + @search="handleSearch" | |
74 | + @change="handleTitleChange" | |
75 | + :loading="titleLoading" | |
76 | + :options="formattedOptions" | |
77 | + :not-found-content="titleLoading ? '加载中...' : (titleOptions.length === 0 ? '未找到匹配项' : null)" | |
78 | + allow-clear | |
79 | + @clear="handleClear" | |
80 | + :auto-focus="true" | |
81 | + :default-open="false" | |
82 | + :drop-down-style="{ maxHeight: '400px', overflow: 'auto' }" | |
83 | + > | |
84 | + </a-select> | |
54 | 85 | </div> |
55 | 86 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> |
56 | 87 | <template #appendFooter> |
... | ... | @@ -61,12 +92,12 @@ |
61 | 92 | </template> |
62 | 93 | <script lang="ts" setup> |
63 | 94 | import { BasicDrawer, useDrawerInner } from '@/components/Drawer'; |
64 | - import { defineComponent, ref, computed, unref, toRaw, reactive, onMounted } from 'vue'; | |
65 | - import { getEmailList } from '/@/api/sys/config'; | |
66 | - import { UploadOutlined } from '@ant-design/icons-vue'; | |
95 | + import { ref, computed, onMounted, watch } from 'vue'; | |
67 | 96 | import type { UploadProps } from 'ant-design-vue'; |
68 | 97 | import { updateDeductInfo } from '@/api/project/invoice'; |
69 | 98 | import { useMessage } from '/@/hooks/web/useMessage'; |
99 | + import { getAllQuests, queryQuestTitle } from '@/api/project/quest'; | |
100 | + import { handleViewRichText } from '../financeList/finance.data'; | |
70 | 101 | |
71 | 102 | const emit = defineEmits(['success']); |
72 | 103 | const fileList = ref<UploadProps['fileList']>([]); |
... | ... | @@ -83,8 +114,27 @@ |
83 | 114 | const { createMessage } = useMessage(); |
84 | 115 | const { error } = createMessage; |
85 | 116 | const status = ref(); |
117 | + | |
118 | + // 扣款原因相关状态 | |
119 | + const titleOptions = ref<any[]>([]); | |
120 | + const selectedTitle = ref<number | undefined>(undefined); | |
121 | + const selectedTitleText = ref(''); | |
122 | + const titleLoading = ref(false); | |
123 | + | |
124 | + // 添加绑定的deductAmount变量 | |
125 | + const bindedDeductAmount = ref<number | undefined>(undefined); | |
126 | + // 添加金额不一致的提示状态 | |
127 | + const amountMismatch = ref(false); | |
128 | + | |
129 | + // 转换为antd需要的options格式 | |
130 | + const formattedOptions = computed(() => { | |
131 | + return titleOptions.value.map(item => ({ | |
132 | + value: item.id, | |
133 | + label: item.title, | |
134 | + })); | |
135 | + }); | |
86 | 136 | |
87 | - const [register, { setDrawerProps, closeDrawer }] = useDrawerInner((data) => { | |
137 | + const [register, { closeDrawer }] = useDrawerInner((data) => { | |
88 | 138 | status.value = data.data.checkPayStatus; |
89 | 139 | id.value = data.data.checkId; |
90 | 140 | checkNo.value = data.data.checkNo; |
... | ... | @@ -92,7 +142,218 @@ |
92 | 142 | deductDept.value = data.data.checkDeductDept; |
93 | 143 | deductUrl.value = data.data.checkDeductUrl; |
94 | 144 | deductUrlOld.value = data.data.checkDeductUrl; |
145 | + | |
146 | + // 优先使用questId,如果没有则尝试使用checkRichTextId | |
147 | + selectedTitle.value = data.data.questId || data.data.checkRichTextId; | |
148 | + | |
149 | + // 加载扣款原因选项 | |
150 | + loadQuestOptions(); | |
95 | 151 | }); |
152 | + | |
153 | + // 处理API返回的数据,统一格式 | |
154 | + function normalizeData(data: any[]): { id: number; title: string; deductAmount?: number }[] { | |
155 | + | |
156 | + if (!data || !Array.isArray(data) || data.length === 0) { | |
157 | + return []; | |
158 | + } | |
159 | + | |
160 | + const result = data.map(item => { | |
161 | + // 处理可能的不同字段名称 | |
162 | + const id = item.id !== undefined ? item.id : | |
163 | + (item.questId !== undefined ? item.questId : null); | |
164 | + | |
165 | + const title = item.title !== undefined ? item.title : | |
166 | + (item.questTitle !== undefined ? item.questTitle : | |
167 | + (item.name !== undefined ? item.name : '未知标题')); | |
168 | + | |
169 | + // 添加新的deductAmount字段处理 | |
170 | + const deductAmount = item.deductAmount !== undefined ? Number(item.deductAmount) : undefined; | |
171 | + | |
172 | + return { id, title, deductAmount }; | |
173 | + }).filter(item => item.id !== null); // 过滤掉无效项 | |
174 | + | |
175 | + return result; | |
176 | + } | |
177 | + | |
178 | + // 加载所有扣款原因选项 | |
179 | + async function loadQuestOptions() { | |
180 | + titleLoading.value = true; | |
181 | + try { | |
182 | + // 如果已有选中ID但没有选项,先尝试直接获取该ID的详情 | |
183 | + if (selectedTitle.value && (!titleOptions.value || titleOptions.value.length === 0)) { | |
184 | + try { | |
185 | + // 使用finance.data.ts中的handleViewRichText方法获取富文本详情 | |
186 | + const richTextDetail = await handleViewRichText(String(selectedTitle.value)); | |
187 | + if (richTextDetail) { | |
188 | + // 设置选项 | |
189 | + titleOptions.value = [{ | |
190 | + id: richTextDetail.id, | |
191 | + title: richTextDetail.title || '扣款原因详情', | |
192 | + deductAmount: richTextDetail.deductAmount // 确保获取deductAmount | |
193 | + }]; | |
194 | + selectedTitleText.value = richTextDetail.title || '扣款原因详情'; | |
195 | + // 绑定deductAmount如果存在 | |
196 | + if (richTextDetail.deductAmount !== undefined) { | |
197 | + bindedDeductAmount.value = Number(richTextDetail.deductAmount); | |
198 | + checkAmountMatch(); | |
199 | + } | |
200 | + titleLoading.value = false; | |
201 | + return; | |
202 | + } | |
203 | + } catch (err) { | |
204 | + console.error('获取单个扣款原因详情失败:', err); | |
205 | + } | |
206 | + } | |
207 | + | |
208 | + const res = await getAllQuests(); | |
209 | + | |
210 | + // 处理不同格式的返回数据 | |
211 | + let tempData: any[] = []; | |
212 | + | |
213 | + if (Array.isArray(res)) { | |
214 | + tempData = res; | |
215 | + } else if (res && Array.isArray(res.records)) { | |
216 | + tempData = res.records; | |
217 | + } else if (res && Array.isArray(res.items)) { | |
218 | + tempData = res.items; | |
219 | + } else if (res && typeof res === 'object') { | |
220 | + tempData = [res]; | |
221 | + } | |
222 | + | |
223 | + // 标准化数据格式 | |
224 | + titleOptions.value = normalizeData(tempData); | |
225 | + | |
226 | + // 如果有已选择的ID,查找对应的title用于显示 | |
227 | + if (selectedTitle.value && titleOptions.value.length > 0) { | |
228 | + const found = titleOptions.value.find(item => item.id === selectedTitle.value); | |
229 | + if (found) { | |
230 | + selectedTitleText.value = found.title; | |
231 | + // 绑定deductAmount如果存在 | |
232 | + if (found.deductAmount !== undefined) { | |
233 | + bindedDeductAmount.value = Number(found.deductAmount); | |
234 | + checkAmountMatch(); | |
235 | + } | |
236 | + } else { | |
237 | + // 如果在选项中找不到对应ID,可以尝试单独请求这个ID的详情 | |
238 | + try { | |
239 | + const richTextDetail = await handleViewRichText(String(selectedTitle.value)); | |
240 | + if (richTextDetail) { | |
241 | + // 添加到选项中 | |
242 | + titleOptions.value.push({ | |
243 | + id: richTextDetail.id, | |
244 | + title: richTextDetail.title || '扣款原因详情', | |
245 | + deductAmount: richTextDetail.deductAmount // 确保获取deductAmount | |
246 | + }); | |
247 | + selectedTitleText.value = richTextDetail.title || '扣款原因详情'; | |
248 | + // 绑定deductAmount如果存在 | |
249 | + if (richTextDetail.deductAmount !== undefined) { | |
250 | + bindedDeductAmount.value = Number(richTextDetail.deductAmount); | |
251 | + checkAmountMatch(); | |
252 | + } | |
253 | + } | |
254 | + } catch (err) { | |
255 | + console.error('获取缺失的扣款原因详情失败:', err); | |
256 | + } | |
257 | + } | |
258 | + } | |
259 | + | |
260 | + } catch (err) { | |
261 | + error('获取扣款原因列表失败'); | |
262 | + console.error('获取扣款原因列表失败:', err); | |
263 | + titleOptions.value = []; | |
264 | + } finally { | |
265 | + titleLoading.value = false; | |
266 | + } | |
267 | + } | |
268 | + | |
269 | + // 搜索扣款原因 | |
270 | + async function handleSearch(value: string) { | |
271 | + if (!value || value.trim() === '') { | |
272 | + await loadQuestOptions(); | |
273 | + return; | |
274 | + } | |
275 | + | |
276 | + titleLoading.value = true; | |
277 | + try { | |
278 | + const res = await queryQuestTitle(value); | |
279 | + // 确保res不为null或undefined | |
280 | + if (!res) { | |
281 | + titleOptions.value = []; | |
282 | + return; | |
283 | + } | |
284 | + | |
285 | + // 处理不同格式的返回数据 | |
286 | + let tempData: any[] = []; | |
287 | + | |
288 | + if (Array.isArray(res)) { | |
289 | + tempData = res; | |
290 | + } else if (res && Array.isArray(res.records)) { | |
291 | + tempData = res.records; | |
292 | + } else if (res && Array.isArray(res.items)) { | |
293 | + tempData = res.items; | |
294 | + } else if (res && typeof res === 'object') { | |
295 | + tempData = [res]; | |
296 | + } | |
297 | + | |
298 | + // 标准化数据格式 | |
299 | + titleOptions.value = normalizeData(tempData); | |
300 | + | |
301 | + // 调试输出处理后的选项 | |
302 | + } catch (err) { | |
303 | + console.error('搜索扣款原因失败:', err); | |
304 | + error('搜索扣款原因失败'); | |
305 | + titleOptions.value = []; | |
306 | + } finally { | |
307 | + titleLoading.value = false; | |
308 | + } | |
309 | + } | |
310 | + | |
311 | + // 选择扣款原因时触发 | |
312 | + function handleTitleChange(value: number, option: any) { | |
313 | + | |
314 | + // 检查是否为空值(用户点击了清除按钮) | |
315 | + if (value === undefined || value === null) { | |
316 | + handleClear(); | |
317 | + return; | |
318 | + } | |
319 | + | |
320 | + selectedTitle.value = value; | |
321 | + | |
322 | + // 当使用options属性时,option为选中的选项对象 | |
323 | + if (option && typeof option === 'object') { | |
324 | + if (option.label) { | |
325 | + // 新版本Ant Design Vue返回的选项格式 | |
326 | + selectedTitleText.value = option.label; | |
327 | + } else if (option.children) { | |
328 | + // 旧版本Ant Design Vue返回的选项格式 | |
329 | + selectedTitleText.value = option.children; | |
330 | + } | |
331 | + } | |
332 | + | |
333 | + // 直接从选项列表中查找 | |
334 | + const found = titleOptions.value.find(item => item.id === value); | |
335 | + if (found) { | |
336 | + selectedTitleText.value = found.title; | |
337 | + // 绑定deductAmount | |
338 | + bindedDeductAmount.value = found.deductAmount; | |
339 | + // 检查金额是否一致 | |
340 | + checkAmountMatch(); | |
341 | + } | |
342 | + } | |
343 | + | |
344 | + // 清除选择 | |
345 | + function handleClear() { | |
346 | + selectedTitle.value = undefined; | |
347 | + selectedTitleText.value = ''; | |
348 | + // 清除绑定的deductAmount | |
349 | + bindedDeductAmount.value = undefined; | |
350 | + // 重置金额不匹配状态 | |
351 | + amountMismatch.value = false; | |
352 | + | |
353 | + // 重新加载所有选项 | |
354 | + loadQuestOptions(); | |
355 | + } | |
356 | + | |
96 | 357 | function handleChange(info) { |
97 | 358 | if (info.file.status == 'done') { |
98 | 359 | updateDeductUrl.value = info.file.response.data.fileUrl; |
... | ... | @@ -106,29 +367,78 @@ |
106 | 367 | function beforeUpload(info) { |
107 | 368 | updateDeductUrl.value = uploadUrl.value + info.name; |
108 | 369 | } |
109 | - function handleShow() { | |
110 | - // input1.value = ''; | |
111 | - // deductUrl.value = ''; | |
112 | - // deductDept.value = ''; | |
113 | - // updateDeductUrl.value = ''; | |
114 | - // fileList.value = []; | |
370 | + function handleShow(visible) { | |
371 | + if (visible) { | |
372 | + // 抽屉打开时加载扣款原因选项 | |
373 | + // loadQuestOptions(); | |
374 | + } | |
115 | 375 | } |
376 | + | |
377 | + // 页面加载时获取扣款原因列表 | |
378 | + onMounted(() => { | |
379 | + // loadQuestOptions(); | |
380 | + }); | |
381 | + | |
382 | + // 检查金额是否匹配 | |
383 | + function checkAmountMatch() { | |
384 | + if (bindedDeductAmount.value !== undefined && input1.value !== undefined) { | |
385 | + try { | |
386 | + // 确保转换为数字进行比较,处理可能的字符串转换问题 | |
387 | + const boundAmount = typeof bindedDeductAmount.value === 'string' | |
388 | + ? parseFloat(bindedDeductAmount.value) | |
389 | + : Number(bindedDeductAmount.value); | |
390 | + | |
391 | + const inputAmount = typeof input1.value === 'string' | |
392 | + ? parseFloat(input1.value) | |
393 | + : Number(input1.value); | |
394 | + | |
395 | + // 比较金额是否一致,使用差值小于极小值来处理浮点数误差 | |
396 | + amountMismatch.value = Math.abs(boundAmount - inputAmount) > 0.001; | |
397 | + } catch (err) { | |
398 | + console.error('金额比较出错:', err); | |
399 | + } | |
400 | + } | |
401 | + } | |
402 | + | |
403 | + // 监听客户扣款金额变化 | |
404 | + function handleInputChange() { | |
405 | + // 检查金额是否匹配 | |
406 | + checkAmountMatch(); | |
407 | + } | |
408 | + | |
409 | + // 监听input1的变化 | |
410 | + watch(input1, () => { | |
411 | + handleInputChange(); | |
412 | + }, { immediate: true }); // 确保首次加载也会触发 | |
413 | + | |
116 | 414 | //完成编辑 |
117 | 415 | async function handleSubmit() { |
118 | 416 | if (!input1.value || !deductDept.value) { |
119 | 417 | error('选项不能为空'); |
120 | - } else { | |
418 | + return; | |
419 | + } | |
420 | + | |
421 | + // 添加金额不一致的检查 | |
422 | + if (amountMismatch.value) { | |
423 | + error(`生产科扣款金额与选择的扣款原因金额不一致!扣款原因金额为: ${bindedDeductAmount.value}`); | |
424 | + return; | |
425 | + } | |
426 | + | |
427 | + try { | |
121 | 428 | await updateDeductInfo({ |
122 | 429 | id: id.value, |
123 | 430 | checkNo: checkNo.value, |
124 | 431 | deductAmount: input1.value, |
125 | 432 | deductDept: deductDept.value, |
126 | 433 | deductUrl: deductUrl.value, |
434 | + questId: selectedTitle.value, // 传递选中的扣款原因ID | |
127 | 435 | }); |
128 | - // productionDepartment: selectedProductionDepartment.value, | |
129 | 436 | fileList.value = []; |
130 | 437 | emit('success'); |
131 | 438 | closeDrawer(); |
439 | + } catch (err) { | |
440 | + error('保存失败'); | |
441 | + console.error('保存失败:', err); | |
132 | 442 | } |
133 | 443 | } |
134 | 444 | </script> | ... | ... |
src/views/project/finance/financeList/finance.data.tsx
1 | 1 | import { FormSchema } from '/@/components/Form'; |
2 | 2 | import { BasicColumn } from '/@/components/Table'; |
3 | -import { icon } from 'ant-design-vue'; | |
4 | -import { FolderAddOutlined, FilePptOutlined } from '@ant-design/icons-vue'; | |
3 | +import { FolderAddOutlined, FilePptOutlined, EyeOutlined } from '@ant-design/icons-vue'; | |
5 | 4 | import { size } from 'lodash-es'; |
6 | 5 | import { view } from '@/utils/pdfShow'; |
7 | 6 | import { ref } from 'vue'; |
8 | 7 | import { queryNoOptions } from '/@/api/project/order'; |
9 | 8 | import { useOrderStoreWithOut } from '/@/store/modules/order'; |
10 | 9 | import { useOrderInfo } from '/@/hooks/component/order'; |
10 | +import { defHttp } from '/@/utils/http/axios'; | |
11 | +import { useMessage } from '/@/hooks/web/useMessage'; | |
11 | 12 | |
13 | +const { createMessage } = useMessage(); | |
12 | 14 | const innerNoOptions = ref([]); |
13 | 15 | const projectNoOptions = ref([]); |
14 | 16 | const orderStore = useOrderStoreWithOut(); |
... | ... | @@ -17,6 +19,27 @@ const { |
17 | 19 | productionDepartment, |
18 | 20 | } = useOrderInfo(orderStore); |
19 | 21 | |
22 | +// 声明一个全局变量来保存回调函数 | |
23 | +let viewRichTextCallback: ((richTextId: any) => void) | null = null; | |
24 | + | |
25 | +// 设置回调函数的方法 | |
26 | +export function setViewRichTextCallback(callback: ((richTextId: any) => void) | null) { | |
27 | + viewRichTextCallback = callback; | |
28 | +} | |
29 | + | |
30 | +// 直接调用回调函数的方法 | |
31 | +function callViewRichText(richTextId: any) { | |
32 | + if (viewRichTextCallback) { | |
33 | + // 显示加载提示 | |
34 | + createMessage.loading({ content: '正在加载扣款原因详情...', duration: 0, key: 'richTextLoading' }); | |
35 | + | |
36 | + // 调用回调函数 | |
37 | + viewRichTextCallback(richTextId); | |
38 | + } else { | |
39 | + console.error('查看回调函数未设置'); | |
40 | + } | |
41 | +} | |
42 | + | |
20 | 43 | export const searchFormSchema: FormSchema[] = [ |
21 | 44 | { |
22 | 45 | field: 'invoiceNo', |
... | ... | @@ -325,6 +348,22 @@ export const columns: BasicColumn[] = [ |
325 | 348 | }, |
326 | 349 | }, |
327 | 350 | { |
351 | + title: '扣款原因', | |
352 | + dataIndex: 'invoiceRichTextId', | |
353 | + width: 120, | |
354 | + customRender: ({ record }) => { | |
355 | + // 如果存在invoiceRichTextId,显示查看按钮 | |
356 | + if (record.invoiceRichTextId) { | |
357 | + return ( | |
358 | + <a onClick={() => callViewRichText(record.invoiceRichTextId)}> | |
359 | + <EyeOutlined style="margin-right: 5px" /> | |
360 | + 查看 | |
361 | + </a> | |
362 | + ); | |
363 | + } | |
364 | + }, | |
365 | + }, | |
366 | + { | |
328 | 367 | title: '实际应收金额$', |
329 | 368 | dataIndex: 'invoiceActualReceivableAmount', |
330 | 369 | width: 120, |
... | ... | @@ -465,6 +504,22 @@ export const columns: BasicColumn[] = [ |
465 | 504 | }, |
466 | 505 | }, |
467 | 506 | { |
507 | + title: '扣款原因', | |
508 | + dataIndex: 'checkRichTextId', | |
509 | + width: 120, | |
510 | + customRender: ({ record }) => { | |
511 | + // 如果存在checkRichTextId,显示查看按钮 | |
512 | + if (record.checkRichTextId) { | |
513 | + return ( | |
514 | + <a onClick={() => callViewRichText(record.checkRichTextId)}> | |
515 | + <EyeOutlined style="margin-right: 5px" /> | |
516 | + 查看 | |
517 | + </a> | |
518 | + ); | |
519 | + } | |
520 | + }, | |
521 | + }, | |
522 | + { | |
468 | 523 | title: '生产科实际应付金额¥', |
469 | 524 | dataIndex: 'checkActualPayedAmount', |
470 | 525 | width: 180, |
... | ... | @@ -555,6 +610,7 @@ export const columns: BasicColumn[] = [ |
555 | 610 | width: 280, |
556 | 611 | }, |
557 | 612 | ]; |
613 | + | |
558 | 614 | function formatDate(input: string): string { |
559 | 615 | // 创建一个 Date 对象 |
560 | 616 | const date = new Date(input); |
... | ... | @@ -566,4 +622,57 @@ function formatDate(input: string): string { |
566 | 622 | |
567 | 623 | // 返回格式化后的日期字符串 |
568 | 624 | return `${year}-${month}-${day}`; |
625 | +} | |
626 | + | |
627 | +// 处理富文本查看API请求 | |
628 | +export async function handleViewRichText(id: string) { | |
629 | + if (!id) { | |
630 | + return null; | |
631 | + } | |
632 | + | |
633 | + // createMessage.loading({ content: '正在获取数据...', key: 'richTextLoading', duration: 0 }); | |
634 | + | |
635 | + try { | |
636 | + // 使用正确的API路径和POST请求 | |
637 | + const res = await defHttp.post( | |
638 | + { | |
639 | + url: '/order/erp/quest/queryRichText_by_id', | |
640 | + data: { id }, | |
641 | + }, | |
642 | + { errorMessageMode: 'none' } | |
643 | + ); | |
644 | + | |
645 | + if (res) { | |
646 | + // 如果响应中包含data字段,使用该字段内容 | |
647 | + let formattedData = res; | |
648 | + | |
649 | + // 确保必要的字段存在 | |
650 | + formattedData.id = formattedData.id || id; | |
651 | + formattedData.title = formattedData.title || '扣款原因详情'; | |
652 | + | |
653 | + // 处理内容字段,可能是richText或contentText | |
654 | + if (!formattedData.contentText && formattedData.richText) { | |
655 | + formattedData.contentText = formattedData.richText; | |
656 | + } | |
657 | + if (formattedData.questType) { | |
658 | + formattedData.questType = formattedData.questType; | |
659 | + } | |
660 | + return formattedData; | |
661 | + } else { | |
662 | + return null; | |
663 | + } | |
664 | + } catch (error) { | |
665 | + return null; | |
666 | + } | |
667 | +} | |
668 | + | |
669 | +// 简化版富文本查看函数(作为后备方案) | |
670 | +export async function handleViewRichTextSimple(id: string) { | |
671 | + // 返回一个基本的模拟数据 | |
672 | + return { | |
673 | + id: id, | |
674 | + title: '问题原因被删除或者不存在', | |
675 | + contentText: `<p>问题原因被删除或者不存在</p>`, | |
676 | + createTime: new Date().toISOString() | |
677 | + }; | |
569 | 678 | } |
570 | 679 | \ No newline at end of file | ... | ... |
src/views/project/finance/financeList/index.vue
... | ... | @@ -93,6 +93,7 @@ |
93 | 93 | <CommitCheck @register="registerCommitCheck" @success="handleSuccess" /> |
94 | 94 | <EditRefundTimeCheck @register="registerEditRefundTimeCheck" @success="handleSuccess" /> |
95 | 95 | <NoteCheck @register="registerNoteCheck" @success="handleSuccess" /> |
96 | + <QuestDrawer @register="registerQuestDrawer" @success="handleSuccess" /> | |
96 | 97 | </template> |
97 | 98 | <template #bodyCell="{ column, record }"> |
98 | 99 | <template v-if="column.key === 'picUrl'"> |
... | ... | @@ -288,10 +289,15 @@ |
288 | 289 | </div> |
289 | 290 | </template> |
290 | 291 | <script lang="ts" setup> |
291 | - import { computed, defineComponent, onMounted, ref, watchEffect } from 'vue'; | |
292 | + import { computed, defineComponent, onMounted, ref, watchEffect, onUnmounted } from 'vue'; | |
292 | 293 | import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table'; |
293 | - // import { searchFormSchema, columns } from './receive.data'; | |
294 | - import { searchFormSchema, columns } from './finance.data'; | |
294 | + import { | |
295 | + searchFormSchema, | |
296 | + columns, | |
297 | + setViewRichTextCallback, | |
298 | + handleViewRichText, | |
299 | + handleViewRichTextSimple, | |
300 | + } from './finance.data'; | |
295 | 301 | import FinanceEdit from './FinanceEdit.vue'; |
296 | 302 | import TrackEdit from './TrackEdit.vue'; |
297 | 303 | import InvoiceAnalysis from './InvoiceAnalysis.vue'; |
... | ... | @@ -333,11 +339,13 @@ |
333 | 339 | import { useUserStoreWithOut } from '/@/store/modules/user'; |
334 | 340 | import { useMessage } from '/@/hooks/web/useMessage'; |
335 | 341 | import { useOrderStoreWithOut } from '/@/store/modules/order'; |
342 | + import QuestDrawer from '/@/views/project/quest/QuestDrawer.vue'; | |
336 | 343 | |
337 | 344 | const [registerInvoiceAnalysis, { openModal: openInvoiceAnalysis }] = useModal(); |
338 | 345 | const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer(); |
339 | 346 | const [registerTrackEdit, { openDrawer: openTrackEdit }] = useDrawer(); |
340 | 347 | const [registerInvoiceDetail, { openDrawer: openInvoiceDetail }] = useDrawer(); |
348 | + const [registerQuestDrawer, { openDrawer: openQuestDrawer }] = useDrawer(); | |
341 | 349 | const [registerCommit, { openModal: openCommit }] = useModal(); |
342 | 350 | const [registerEditRefundTime, { openModal: openEditRefundTime }] = useModal(); |
343 | 351 | const [registerReUploadBgUrl, { openModal: openReUploadBgUrl }] = useModal(); |
... | ... | @@ -394,8 +402,59 @@ |
394 | 402 | // }, |
395 | 403 | }); |
396 | 404 | |
405 | + // 设置扣款原因查看回调函数 - 放在全局位置以确保一直有效 | |
406 | + setViewRichTextCallback(async (richTextId) => { | |
407 | + try { | |
408 | + // 获取富文本数据 | |
409 | + const data = await handleViewRichText(richTextId); | |
410 | + | |
411 | + // 关闭加载提示 | |
412 | + createMessage.destroy('richTextLoading'); | |
413 | + | |
414 | + if (data) { | |
415 | + // 创建一个适配QuestDrawer的记录对象 | |
416 | + const questRecord = { | |
417 | + id: data.id, | |
418 | + title: data.title || '扣款原因详情', | |
419 | + contentText: data.contentText || '', | |
420 | + contentImages: data.contentImages || [], | |
421 | + files: data.files || [], | |
422 | + questType: data.questType || '' | |
423 | + }; | |
424 | + | |
425 | + // 打开抽屉组件显示数据 | |
426 | + openQuestDrawer(true, { | |
427 | + record: questRecord, | |
428 | + isUpdate: true, | |
429 | + isView: true, | |
430 | + }); | |
431 | + } else { | |
432 | + // API请求失败,使用简化版函数创建模拟数据 | |
433 | + const fallbackData = await handleViewRichTextSimple(richTextId); | |
434 | + | |
435 | + openQuestDrawer(true, { | |
436 | + record: fallbackData, | |
437 | + isUpdate: true, | |
438 | + isView: true, | |
439 | + }); | |
440 | + } | |
441 | + } catch (error) { | |
442 | + // 关闭加载提示 | |
443 | + createMessage.destroy('richTextLoading'); | |
444 | + createMessage.error('查看扣款原因时发生错误,请联系管理员'); | |
445 | + } | |
446 | + }); | |
447 | + | |
397 | 448 | onMounted(async () => { |
398 | 449 | await orderStore.getDict(); |
450 | + | |
451 | + // 回调函数已移到全局位置 | |
452 | + }); | |
453 | + | |
454 | + // 组件卸载时清除回调函数 | |
455 | + onUnmounted(() => { | |
456 | + // 清除回调函数 | |
457 | + setViewRichTextCallback(null); | |
399 | 458 | }); |
400 | 459 | |
401 | 460 | // 单选函数 |
... | ... | @@ -516,7 +575,6 @@ |
516 | 575 | }); |
517 | 576 | } |
518 | 577 | function handleDeleteDeduct(record) { |
519 | - console.log(record, '5656record'); | |
520 | 578 | deleteDeduct({ id: record.invoiceId }); |
521 | 579 | } |
522 | 580 | function handleCheckDeleteDeduct(record) { | ... | ... |
src/views/project/quest/QuestDrawer.vue
... | ... | @@ -6,12 +6,12 @@ |
6 | 6 | :title="getTitle" |
7 | 7 | width="70%" |
8 | 8 | @ok="handleSubmit" |
9 | - :okButtonProps="{ disabled: isView }" | |
9 | + :okButtonProps="{ disabled: unref(isView) }" | |
10 | 10 | > |
11 | 11 | <BasicForm @register="registerForm" /> |
12 | 12 | |
13 | 13 | <!-- 自定义图片上传区域 --> |
14 | - <div class="custom-image-upload" v-if="!isView"> | |
14 | + <div class="custom-image-upload" v-if="!unref(isView)"> | |
15 | 15 | <div class="upload-title">图片上传</div> |
16 | 16 | <div class="upload-content"> |
17 | 17 | <div |
... | ... | @@ -50,7 +50,7 @@ |
50 | 50 | </div> |
51 | 51 | |
52 | 52 | <!-- 查看模式下的图片显示 --> |
53 | - <div class="custom-image-view" v-if="isView && uploadedImages.length > 0"> | |
53 | + <div class="custom-image-view" v-if="unref(isView) && uploadedImages.length > 0"> | |
54 | 54 | <div class="upload-title">图片附件</div> |
55 | 55 | <div class="upload-content"> |
56 | 56 | <div |
... | ... | @@ -73,7 +73,7 @@ |
73 | 73 | </div> |
74 | 74 | |
75 | 75 | <!-- 自定义附件上传区域 --> |
76 | - <div class="custom-file-upload" v-if="!isView"> | |
76 | + <div class="custom-file-upload" v-if="!unref(isView)"> | |
77 | 77 | <div class="upload-title">附件上传</div> |
78 | 78 | <div class="file-list"> |
79 | 79 | <!-- 附件列表 --> |
... | ... | @@ -108,7 +108,7 @@ |
108 | 108 | </div> |
109 | 109 | |
110 | 110 | <!-- 查看模式下的附件显示 --> |
111 | - <div class="custom-file-view" v-if="isView && uploadedFiles.length > 0"> | |
111 | + <div class="custom-file-view" v-if="unref(isView) && uploadedFiles.length > 0"> | |
112 | 112 | <div class="upload-title">文件附件</div> |
113 | 113 | <div class="file-list"> |
114 | 114 | <!-- 附件列表 --> |
... | ... | @@ -126,7 +126,7 @@ |
126 | 126 | </BasicDrawer> |
127 | 127 | </template> |
128 | 128 | |
129 | -<script lang="ts" setup> | |
129 | +<script lang="ts" setup name="QuestDrawer"> | |
130 | 130 | import { ref, computed, unref, onMounted } from 'vue'; |
131 | 131 | import { BasicForm, useForm } from '/@/components/Form'; |
132 | 132 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
... | ... | @@ -135,6 +135,7 @@ |
135 | 135 | import { h } from 'vue'; |
136 | 136 | import { questCreate, questUpdate } from '/@/api/project/quest'; |
137 | 137 | import { PlusOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'; |
138 | + import { defHttp } from '/@/utils/http/axios'; | |
138 | 139 | |
139 | 140 | const emit = defineEmits(['success', 'register']); |
140 | 141 | const isUpdate = ref(true); |
... | ... | @@ -173,6 +174,61 @@ |
173 | 174 | }>>([]); |
174 | 175 | const isFileUploading = ref(false); |
175 | 176 | |
177 | + // 问题类型相关 | |
178 | + const questTypeList = ref<any[]>([]); | |
179 | + const loadingQuestTypes = ref<boolean>(false); | |
180 | + | |
181 | + // 货币类型选择 | |
182 | + const currencyType = ref<string>('0'); // 默认美元 | |
183 | + const currencySymbol = computed(() => currencyType.value === '0' ? '$' : '¥'); | |
184 | + const currencyOptions = [ | |
185 | + { label: '美元 ($)', value: '0' }, | |
186 | + { label: '人民币 (¥)', value: '1' } | |
187 | + ]; | |
188 | + | |
189 | + // 问题类型选项计算属性 | |
190 | + const questTypeOptions = computed(() => { | |
191 | + return questTypeList.value.map(item => ({ | |
192 | + label: item.settingValue, | |
193 | + value: item.settingValue, | |
194 | + })); | |
195 | + }); | |
196 | + | |
197 | + // 获取问题类型列表 | |
198 | + async function fetchQuestTypes() { | |
199 | + loadingQuestTypes.value = true; | |
200 | + try { | |
201 | + const result = await defHttp.post({ | |
202 | + url: '/order/erp/system_setting/query_list', | |
203 | + data: { settingCode: 'questType' }, | |
204 | + }); | |
205 | + | |
206 | + // 简化判断逻辑,直接检查必要条件 | |
207 | + if (result && typeof result === 'object') { | |
208 | + if (result.result === 0 && Array.isArray(result.data)) { | |
209 | + // 标准返回格式:{ data: [], result: 0, message: "" } | |
210 | + questTypeList.value = result.data; | |
211 | + } else if (Array.isArray(result)) { | |
212 | + // 另一种可能:直接返回数组 | |
213 | + questTypeList.value = result; | |
214 | + } else { | |
215 | + // 数据格式不符合预期 | |
216 | + questTypeList.value = []; | |
217 | + createMessage.error('获取问题类型列表失败: 数据格式不符合预期'); | |
218 | + } | |
219 | + } else { | |
220 | + questTypeList.value = []; | |
221 | + createMessage.error('获取问题类型列表失败: 无响应数据'); | |
222 | + } | |
223 | + } catch (error) { | |
224 | + console.error('QuestDrawer-获取问题类型列表出错:', error); | |
225 | + questTypeList.value = []; | |
226 | + createMessage.error('获取问题类型列表失败: ' + (error.message || '未知错误')); | |
227 | + } finally { | |
228 | + loadingQuestTypes.value = false; | |
229 | + } | |
230 | + } | |
231 | + | |
176 | 232 | // 确保URL是完整路径 |
177 | 233 | function ensureFullUrl(url) { |
178 | 234 | if (!url) return ''; |
... | ... | @@ -193,7 +249,7 @@ |
193 | 249 | } |
194 | 250 | // 组件加载完成时执行 |
195 | 251 | onMounted(() => { |
196 | - console.log('QuestDrawer组件已加载,自定义文件上传和预览功能已配置'); | |
252 | + fetchQuestTypes(); | |
197 | 253 | }); |
198 | 254 | |
199 | 255 | // 点击上传按钮 |
... | ... | @@ -234,24 +290,17 @@ |
234 | 290 | } |
235 | 291 | |
236 | 292 | const data = await response.json(); |
237 | - console.log('附件上传返回数据:', data); | |
238 | 293 | |
239 | 294 | // 查找fileUrl字段 |
240 | 295 | let fileUrl = null; |
241 | 296 | if (data.data && data.data.fileUrl) { |
242 | 297 | fileUrl = data.data.fileUrl; |
243 | - console.log("在data.data中找到fileUrl:", fileUrl); | |
244 | 298 | } else { |
245 | - console.error("未找到fileUrl字段! 数据:", data); | |
246 | - } | |
247 | - | |
248 | - if (!fileUrl) { | |
249 | 299 | throw new Error('无法获取文件URL'); |
250 | 300 | } |
251 | 301 | |
252 | 302 | // 确保URL是完整路径 |
253 | 303 | const fullUrl = ensureFullUrl(fileUrl); |
254 | - console.log('完整文件URL:', fullUrl); | |
255 | 304 | |
256 | 305 | // 从URL中提取文件名 |
257 | 306 | const fileName = extractFileNameFromUrl(fullUrl); |
... | ... | @@ -265,7 +314,6 @@ |
265 | 314 | createMessage.success(`${fileName} 上传成功`); |
266 | 315 | return true; |
267 | 316 | } catch (error) { |
268 | - console.error(`上传文件 ${file.name} 失败:`, error); | |
269 | 317 | createMessage.error(`${file.name} 上传失败: ${error.message || '未知错误'}`); |
270 | 318 | return false; |
271 | 319 | } |
... | ... | @@ -289,32 +337,27 @@ |
289 | 337 | // 尝试从URL中提取文件名 |
290 | 338 | // 移除查询参数 |
291 | 339 | const urlWithoutParams = url.split('?')[0]; |
292 | - console.log('移除查询参数后的URL:', urlWithoutParams); | |
293 | 340 | |
294 | 341 | // 获取最后一个路径部分 |
295 | 342 | let fileName = urlWithoutParams.split('/').pop(); |
296 | - console.log('提取的文件名:', fileName); | |
297 | 343 | |
298 | 344 | // 如果有编码的部分,尝试解码 |
299 | 345 | if (fileName && (fileName.includes('%') || fileName.includes('+'))) { |
300 | 346 | try { |
301 | 347 | fileName = decodeURIComponent(fileName); |
302 | - console.log('解码后的文件名:', fileName); | |
303 | 348 | } catch (e) { |
304 | - console.error('解码文件名失败:', e); | |
349 | + // 解码失败 | |
305 | 350 | } |
306 | 351 | } |
307 | 352 | |
308 | 353 | return fileName || '未知文件'; |
309 | 354 | } catch (error) { |
310 | - console.error('提取文件名失败:', error); | |
311 | 355 | return '未知文件'; |
312 | 356 | } |
313 | 357 | } |
314 | 358 | |
315 | 359 | // 预览文件 |
316 | 360 | function previewFile(file) { |
317 | - console.log('预览文件:', file); | |
318 | 361 | if (!file.fileUrl) { |
319 | 362 | createMessage.error('无法预览:找不到文件URL'); |
320 | 363 | return; |
... | ... | @@ -329,7 +372,6 @@ |
329 | 372 | |
330 | 373 | // 下载文件 |
331 | 374 | function downloadFile(file) { |
332 | - console.log('下载文件:', file); | |
333 | 375 | if (!file.fileUrl) { |
334 | 376 | createMessage.error('无法下载:找不到文件URL'); |
335 | 377 | return; |
... | ... | @@ -364,10 +406,8 @@ |
364 | 406 | try { |
365 | 407 | const formData = new FormData(); |
366 | 408 | 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); | |
409 | + formData.append('name', file.name); | |
410 | + | |
371 | 411 | const response = await fetch(imageUploadUrl.value, { |
372 | 412 | method: 'POST', |
373 | 413 | body: formData, |
... | ... | @@ -378,7 +418,6 @@ |
378 | 418 | } |
379 | 419 | |
380 | 420 | const data = await response.json(); |
381 | - console.log('图片上传返回数据', data); | |
382 | 421 | |
383 | 422 | // 根据接口返回格式获取URL |
384 | 423 | let imageUrl = ''; |
... | ... | @@ -391,7 +430,6 @@ |
391 | 430 | imageUrl = data.data.smallPicUrl; |
392 | 431 | } |
393 | 432 | } |
394 | - | |
395 | 433 | |
396 | 434 | // 确保URL是完整路径 |
397 | 435 | if (imageUrl && !imageUrl.startsWith('http')) { |
... | ... | @@ -409,8 +447,6 @@ |
409 | 447 | } |
410 | 448 | } |
411 | 449 | |
412 | - console.log('处理后的图片URL:', imageUrl); | |
413 | - | |
414 | 450 | // 只添加URL,不添加name |
415 | 451 | uploadedImages.value.push({ |
416 | 452 | url: imageUrl |
... | ... | @@ -418,10 +454,10 @@ |
418 | 454 | |
419 | 455 | createMessage.success('图片上传成功'); |
420 | 456 | } catch (error) { |
421 | - console.error('图片上传失败:', error); | |
422 | - createMessage.error(`上传失败: ${error.message || '未知错误'}`); | |
457 | + createMessage.error(`图片上传失败: ${error.message || '未知错误'}`); | |
423 | 458 | } finally { |
424 | 459 | isUploading.value = false; |
460 | + | |
425 | 461 | // 清空input,以便于再次选择同一文件 |
426 | 462 | if (imageFileInput.value) { |
427 | 463 | imageFileInput.value.value = ''; |
... | ... | @@ -437,7 +473,6 @@ |
437 | 473 | // 处理图片加载错误 |
438 | 474 | function handleImageError(event: Event, index: number) { |
439 | 475 | const target = event.target as HTMLImageElement; |
440 | - console.error('图片加载失败:', uploadedImages.value[index].url); | |
441 | 476 | |
442 | 477 | // 尝试使用不同格式的URL |
443 | 478 | const currentUrl = uploadedImages.value[index].url; |
... | ... | @@ -449,19 +484,22 @@ |
449 | 484 | newUrl = currentUrl.replace('picUrl', 'smallPicUrl'); |
450 | 485 | } |
451 | 486 | |
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); | |
487 | + // 如果有新URL,尝试使用它 | |
488 | + if (newUrl) { | |
489 | + uploadedImages.value[index].url = newUrl; | |
490 | + target.src = newUrl; | |
491 | + | |
492 | + // 添加再次失败的处理 | |
493 | + target.onerror = () => { | |
494 | + createMessage.error('图片无法加载,请重新上传'); | |
495 | + // 移除无法加载的图片 | |
496 | + removeImage(index); | |
497 | + }; | |
498 | + } else { | |
461 | 499 | createMessage.error('图片无法加载,请重新上传'); |
462 | 500 | // 移除无法加载的图片 |
463 | 501 | removeImage(index); |
464 | - }; | |
502 | + } | |
465 | 503 | } |
466 | 504 | |
467 | 505 | // 表单配置 |
... | ... | @@ -483,6 +521,49 @@ |
483 | 521 | }, |
484 | 522 | }, |
485 | 523 | { |
524 | + field: 'questType', | |
525 | + label: '问题类型', | |
526 | + component: 'Select' as any, | |
527 | + required: true, | |
528 | + componentProps: () => ({ | |
529 | + options: questTypeOptions.value, | |
530 | + disabled: unref(isView), // 查看模式下禁用 | |
531 | + loading: loadingQuestTypes.value, | |
532 | + }), | |
533 | + }, | |
534 | + { | |
535 | + field: 'currencySelector', | |
536 | + label: '扣款金额', | |
537 | + component: 'Select' as any, | |
538 | + defaultValue: '0', | |
539 | + required: true, | |
540 | + componentProps: { | |
541 | + options: currencyOptions, | |
542 | + disabled: unref(isView), | |
543 | + style: 'width: 100%', | |
544 | + placeholder: '选择货币', | |
545 | + onChange: (val: string) => { | |
546 | + currencyType.value = val; | |
547 | + } | |
548 | + }, | |
549 | + colProps: { span: 6 }, | |
550 | + }, | |
551 | + { | |
552 | + field: 'deductAmount', | |
553 | + label: ' ', | |
554 | + component: 'InputNumber' as any, | |
555 | + required: true, | |
556 | + componentProps: { | |
557 | + disabled: unref(isView), | |
558 | + min: 0, | |
559 | + precision: 2, | |
560 | + style: 'width: 100%', | |
561 | + addonBefore: currencySymbol.value, | |
562 | + placeholder: '请输入扣款金额', | |
563 | + }, | |
564 | + colProps: { span: 18 }, | |
565 | + }, | |
566 | + { | |
486 | 567 | field: 'contentText', |
487 | 568 | label: '内容', |
488 | 569 | component: 'Input' as any, |
... | ... | @@ -516,37 +597,54 @@ |
516 | 597 | setDrawerProps({ confirmLoading: false }); |
517 | 598 | uploadedImages.value = []; // 清空已上传图片 |
518 | 599 | uploadedFiles.value = []; // 清空已上传文件 |
600 | + | |
601 | + // 每次打开表单时都刷新问题类型列表数据 | |
602 | + await fetchQuestTypes(); | |
519 | 603 | |
604 | + // 明确设置状态,避免混淆 | |
520 | 605 | isUpdate.value = !!data?.isUpdate; |
521 | 606 | isView.value = !!data?.isView; // 设置查看模式 |
607 | + | |
608 | + // 确保确认按钮状态正确 | |
609 | + setDrawerProps({ | |
610 | + okButtonProps: { | |
611 | + disabled: unref(isView) | |
612 | + } | |
613 | + }); | |
522 | 614 | |
523 | 615 | if (unref(isUpdate)) { |
524 | 616 | record.value = { ...data.record }; |
525 | - console.log('加载记录数据:', data.record); | |
526 | - console.log('记录ID:', data.record?.id); | |
617 | + | |
618 | + // 设置货币类型 | |
619 | + if (data.record.isRmb !== undefined) { | |
620 | + currencyType.value = data.record.isRmb; | |
621 | + } | |
527 | 622 | |
528 | 623 | // 确保ID字段被正确保存,即使它不在表单字段中 |
529 | 624 | if (data.record && data.record.id) { |
530 | 625 | record.value.id = data.record.id; |
531 | - console.log('保存记录ID到record:', record.value.id); | |
532 | 626 | } |
533 | 627 | |
534 | - await setFieldsValue({ | |
535 | - ...data.record, | |
536 | - }); | |
628 | + // 延迟一帧后设置表单值,确保DOM已更新 | |
629 | + setTimeout(async () => { | |
630 | + await setFieldsValue({ | |
631 | + ...data.record, | |
632 | + // 确保扣款金额正确加载 | |
633 | + deductAmount: data.record.deductAmount !== undefined ? Number(data.record.deductAmount) : undefined, | |
634 | + // 设置货币选择器的值 | |
635 | + currencySelector: data.record.isRmb !== undefined ? data.record.isRmb : '0', | |
636 | + }); | |
637 | + }, 0); | |
537 | 638 | |
538 | - // 处理图片数据 - 现在contentImages是一个URL数组 | |
639 | + // 处理图片数据 | |
539 | 640 | try { |
540 | 641 | if (data.record.contentImages) { |
541 | - console.log('原始图片数据:', data.record.contentImages); | |
542 | - | |
543 | 642 | // 处理contentImages数组 |
544 | 643 | if (Array.isArray(data.record.contentImages)) { |
545 | 644 | // 直接使用数组 |
546 | 645 | uploadedImages.value = data.record.contentImages.map(url => ({ |
547 | 646 | url: ensureFullUrl(url) |
548 | 647 | })); |
549 | - console.log('数组格式的图片URL:', uploadedImages.value); | |
550 | 648 | } |
551 | 649 | // 向下兼容旧格式,如果是字符串,尝试解析 |
552 | 650 | else if (typeof data.record.contentImages === 'string') { |
... | ... | @@ -573,14 +671,10 @@ |
573 | 671 | })); |
574 | 672 | } |
575 | 673 | } |
576 | - | |
577 | - console.log('处理后的图片数据:', uploadedImages.value); | |
578 | 674 | } |
579 | 675 | |
580 | 676 | // 处理附件数据 - 现在files是一个URL数组 |
581 | 677 | if (data.record.files) { |
582 | - console.log('原始文件数据:', data.record.files); | |
583 | - | |
584 | 678 | // 处理files数组 |
585 | 679 | if (Array.isArray(data.record.files)) { |
586 | 680 | // 直接使用数组 |
... | ... | @@ -591,7 +685,6 @@ |
591 | 685 | fileUrl: fileUrl |
592 | 686 | }; |
593 | 687 | }); |
594 | - console.log('数组格式的文件URL:', uploadedFiles.value); | |
595 | 688 | } |
596 | 689 | // 向下兼容旧格式,如果是字符串,尝试解析 |
597 | 690 | else if (typeof data.record.files === 'string') { |
... | ... | @@ -645,11 +738,9 @@ |
645 | 738 | }); |
646 | 739 | } |
647 | 740 | } |
648 | - | |
649 | - console.log('处理后的文件数据:', uploadedFiles.value); | |
650 | 741 | } |
651 | 742 | } catch (error) { |
652 | - console.error('处理附件和图片数据时出错:', error); | |
743 | + // 处理附件和图片数据时出错 | |
653 | 744 | } |
654 | 745 | } |
655 | 746 | }); |
... | ... | @@ -664,26 +755,32 @@ |
664 | 755 | |
665 | 756 | // 提交表单 |
666 | 757 | async function handleSubmit() { |
667 | - // 查看模式下不允许提交 | |
668 | - if (unref(isView)) return; | |
758 | + // 查看模式下直接返回,不执行任何操作 | |
759 | + if (unref(isView)) { | |
760 | + closeDrawer(); | |
761 | + return; | |
762 | + } | |
669 | 763 | |
670 | 764 | try { |
671 | 765 | const values = await validate(); |
672 | - console.log('values的值',values); | |
673 | 766 | setDrawerProps({ confirmLoading: true }); |
674 | 767 | |
768 | + // 添加货币类型参数 | |
769 | + values.isRmb = currencyType.value; | |
770 | + | |
771 | + // 移除辅助字段,不需要提交到后端 | |
772 | + delete values.currencySelector; | |
773 | + | |
675 | 774 | // 添加上传的图片到表单数据中 - 直接提交URL数组 |
676 | 775 | if (uploadedImages.value.length > 0) { |
677 | 776 | // 直接将URL数组作为contentImages |
678 | 777 | values.contentImages = uploadedImages.value.map(img => img.url); |
679 | - console.log('图片数据(数组格式):', values.contentImages); | |
680 | 778 | } |
681 | 779 | |
682 | 780 | // 添加上传的附件到表单数据中 - 直接提交URL数组 |
683 | 781 | if (uploadedFiles.value.length > 0) { |
684 | 782 | // 直接将fileUrl数组作为files |
685 | 783 | values.files = uploadedFiles.value.map(file => file.fileUrl); |
686 | - console.log('附件数据(数组格式):', values.files); | |
687 | 784 | } |
688 | 785 | |
689 | 786 | // 如果是更新模式,确保添加ID |
... | ... | @@ -692,44 +789,32 @@ |
692 | 789 | // 从record中获取ID |
693 | 790 | if (record.value && record.value.id) { |
694 | 791 | values.id = record.value.id; |
695 | - console.log('从record中添加记录ID:', values.id); | |
696 | 792 | } else { |
697 | - console.warn('警告:更新模式下未找到记录ID!'); | |
793 | + createMessage.error('更新失败:缺少记录ID'); | |
794 | + setDrawerProps({ confirmLoading: false }); | |
795 | + return; | |
698 | 796 | } |
699 | 797 | } |
700 | 798 | |
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 | 799 | // 调用后端API保存数据 |
713 | 800 | const requestApi = isUpdateValue |
714 | 801 | ? questUpdate // 更新接口 |
715 | - : questCreate; // 创建接口 | |
716 | - console.log('requestApi调用',requestApi); | |
717 | - const res=await requestApi(values); | |
718 | - console.log('res返回数据',res); | |
719 | - // 关闭抽屉 | |
802 | + : questCreate; // 创建接口 | |
803 | + | |
804 | + await requestApi(values); | |
805 | + | |
806 | + // 关闭抽屉 | |
720 | 807 | closeDrawer(); |
721 | 808 | // 通知父组件刷新数据 |
722 | 809 | emit('success'); |
723 | 810 | setDrawerProps({ confirmLoading: false }); |
724 | 811 | } catch (error) { |
725 | - console.error('API请求失败:', error); | |
726 | 812 | createMessage.error(`保存失败: ${error.message || '未知错误'}`); |
727 | 813 | } |
728 | 814 | } |
729 | 815 | |
730 | 816 | // 预览图片 |
731 | 817 | function previewImage(image) { |
732 | - console.log('预览图片:', image); | |
733 | 818 | if (!image.url) { |
734 | 819 | createMessage.error('无法预览:找不到图片URL'); |
735 | 820 | return; |
... | ... | @@ -780,6 +865,12 @@ |
780 | 865 | } |
781 | 866 | </script> |
782 | 867 | |
868 | +<script lang="ts"> | |
869 | +export default { | |
870 | + name: 'QuestDrawer', | |
871 | +}; | |
872 | +</script> | |
873 | + | |
783 | 874 | <style lang="less" scoped> |
784 | 875 | .custom-image-upload { |
785 | 876 | margin-top: 20px; | ... | ... |
src/views/project/quest/QuestTypeModal.vue
0 → 100644
1 | +<template> | |
2 | + <a-modal | |
3 | + :visible="visible" | |
4 | + title="问题类型管理" | |
5 | + @cancel="handleCancel" | |
6 | + :footer="null" | |
7 | + width="800px" | |
8 | + > | |
9 | + <div class="quest-type-container"> | |
10 | + <!-- 列表展示区域 --> | |
11 | + <div class="list-header"> | |
12 | + <div class="title">问题类型</div> | |
13 | + <a-button type="primary" @click="handleAddNew" size="large"> | |
14 | + 新增 | |
15 | + </a-button> | |
16 | + </div> | |
17 | + | |
18 | + <!-- 问题类型列表 --> | |
19 | + <a-table | |
20 | + :dataSource="typeList" | |
21 | + :columns="columns" | |
22 | + :pagination="false" | |
23 | + :loading="loading" | |
24 | + rowKey="id" | |
25 | + size="large" | |
26 | + > | |
27 | + <template #bodyCell="{ column, record }"> | |
28 | + <template v-if="column.key === 'action'"> | |
29 | + <a-popconfirm | |
30 | + v-if="role == ROLE.ADMIN" | |
31 | + title="确定要删除此问题类型吗?" | |
32 | + @confirm="handleDelete(record)" | |
33 | + okText="确定" | |
34 | + cancelText="取消" | |
35 | + > | |
36 | + <a-button type="link" danger size="large" >删除</a-button> | |
37 | + </a-popconfirm> | |
38 | + </template> | |
39 | + </template> | |
40 | + </a-table> | |
41 | + </div> | |
42 | + | |
43 | + <!-- 新增问题类型弹框 --> | |
44 | + <a-modal | |
45 | + v-model:visible="addModalVisible" | |
46 | + title="新增问题类型" | |
47 | + @ok="handleAddSubmit" | |
48 | + @cancel="addModalVisible = false" | |
49 | + :confirmLoading="addLoading" | |
50 | + width="500px" | |
51 | + > | |
52 | + <a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }"> | |
53 | + <a-form-item label="问题类型名称" :rules="[{ required: true, message: '请输入问题类型名称' }]"> | |
54 | + <a-input v-model:value="newTypeName" placeholder="请输入问题类型名称" size="large" /> | |
55 | + </a-form-item> | |
56 | + </a-form> | |
57 | + </a-modal> | |
58 | + </a-modal> | |
59 | +</template> | |
60 | + | |
61 | +<script lang="ts" setup name="QuestTypeModal"> | |
62 | + import { ref, onMounted, watch, defineProps, defineEmits, computed } from 'vue'; | |
63 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
64 | + import { defHttp } from '/@/utils/http/axios'; | |
65 | + import { ROLE } from '../order/type.d'; | |
66 | + import {useUserStoreWithOut} from '@/store/modules/user' | |
67 | + const userStore = useUserStoreWithOut(); | |
68 | + const user=userStore.getUserInfo; | |
69 | + | |
70 | + const props = defineProps({ | |
71 | + visible: { | |
72 | + type: Boolean, | |
73 | + default: false, | |
74 | + }, | |
75 | + }); | |
76 | + | |
77 | + const emit = defineEmits(['update:visible', 'success']); | |
78 | + | |
79 | + const { createMessage } = useMessage(); | |
80 | + | |
81 | + // 问题类型列表相关 | |
82 | + const typeList = ref<any[]>([]); | |
83 | + const loading = ref<boolean>(false); | |
84 | + const columns = [ | |
85 | + { | |
86 | + title: '问题类型', | |
87 | + dataIndex: 'settingValue', | |
88 | + key: 'settingValue', | |
89 | + width: '70%', | |
90 | + }, | |
91 | + { | |
92 | + title: '操作', | |
93 | + key: 'action', | |
94 | + width: '30%', | |
95 | + align: 'center', | |
96 | + }, | |
97 | + ]; | |
98 | + | |
99 | + // 新增问题类型相关 | |
100 | + const addModalVisible = ref<boolean>(false); | |
101 | + const addLoading = ref<boolean>(false); | |
102 | + const newTypeName = ref<string>(''); | |
103 | + const role = computed(() =>{ | |
104 | + return user?.roleSmallVO?.code; | |
105 | + }) | |
106 | + // 监听visible变化,当打开时获取数据 | |
107 | + watch( | |
108 | + () => props.visible, | |
109 | + (val) => { | |
110 | + if (val) { | |
111 | + fetchTypeList(); | |
112 | + } | |
113 | + } | |
114 | + ); | |
115 | + | |
116 | + // 获取问题类型列表 | |
117 | + async function fetchTypeList() { | |
118 | + loading.value = true; | |
119 | + try { | |
120 | + const result = await defHttp.post({ | |
121 | + url: '/order/erp/system_setting/query_list', | |
122 | + data: { settingCode: 'questType' }, | |
123 | + }); | |
124 | + | |
125 | + // 简化判断逻辑,直接检查必要条件 | |
126 | + if (result && typeof result === 'object') { | |
127 | + if (result.result === 0 && Array.isArray(result.data)) { | |
128 | + // 标准返回格式:{ data: [], result: 0, message: "" } | |
129 | + typeList.value = result.data; | |
130 | + } else if (Array.isArray(result)) { | |
131 | + // 另一种可能:直接返回数组 | |
132 | + typeList.value = result; | |
133 | + } else { | |
134 | + // 数据格式不符合预期 | |
135 | + typeList.value = []; | |
136 | + console.error('数据格式不符合预期:', result); | |
137 | + createMessage.error('获取问题类型列表失败: 数据格式不符合预期'); | |
138 | + } | |
139 | + } else { | |
140 | + typeList.value = []; | |
141 | + createMessage.error('获取问题类型列表失败: 无响应数据'); | |
142 | + } | |
143 | + } catch (error) { | |
144 | + console.error('获取问题类型列表出错:', error); | |
145 | + typeList.value = []; | |
146 | + createMessage.error('获取问题类型列表失败: ' + (error.message || '未知错误')); | |
147 | + } finally { | |
148 | + loading.value = false; | |
149 | + } | |
150 | + } | |
151 | + | |
152 | + // 关闭弹框 | |
153 | + function handleCancel() { | |
154 | + emit('update:visible', false); | |
155 | + } | |
156 | + | |
157 | + // 打开新增弹框 | |
158 | + function handleAddNew() { | |
159 | + newTypeName.value = ''; | |
160 | + addModalVisible.value = true; | |
161 | + } | |
162 | + | |
163 | + // 提交新增 | |
164 | + async function handleAddSubmit() { | |
165 | + if (!newTypeName.value.trim()) { | |
166 | + createMessage.warning('请输入问题类型名称'); | |
167 | + return; | |
168 | + } | |
169 | + | |
170 | + addLoading.value = true; | |
171 | + try { | |
172 | + const result = await defHttp.post({ | |
173 | + url: '/order/erp/system_setting/add', | |
174 | + data: { | |
175 | + settingCode: 'questType', | |
176 | + settingName: '问题类型', | |
177 | + settingValue: newTypeName.value.trim(), | |
178 | + settingType: 0, | |
179 | + relationCode: 'questType', | |
180 | + relationName: '问题类型', | |
181 | + relationValue: newTypeName.value.trim(), | |
182 | + }, | |
183 | + }); | |
184 | + | |
185 | + | |
186 | + | |
187 | + // 放宽判断条件 | |
188 | + if (result) { | |
189 | + // 不再强制检查result.result === 0 | |
190 | + createMessage.success('新增问题类型成功'); | |
191 | + addModalVisible.value = false; | |
192 | + await fetchTypeList(); // 刷新列表 | |
193 | + emit('success'); // 通知父组件操作成功 | |
194 | + } else { | |
195 | + console.error('添加问题类型失败:', result); | |
196 | + createMessage.error('新增问题类型失败'); | |
197 | + } | |
198 | + } catch (error) { | |
199 | + console.error('添加问题类型出错:', error); | |
200 | + createMessage.error('新增问题类型失败: ' + (error.message || '未知错误')); | |
201 | + } finally { | |
202 | + addLoading.value = false; | |
203 | + } | |
204 | + } | |
205 | + | |
206 | + // 删除问题类型 | |
207 | + async function handleDelete(record) { | |
208 | + loading.value = true; | |
209 | + try { | |
210 | + const result = await defHttp.post({ | |
211 | + url: '/order/erp/system_setting/delete_by_id', | |
212 | + data: { ids: [record.id] }, | |
213 | + }); | |
214 | + | |
215 | + | |
216 | + | |
217 | + // 放宽判断条件 | |
218 | + if (result) { | |
219 | + // 不再强制检查result.result === 0 | |
220 | + createMessage.success('删除问题类型成功'); | |
221 | + await fetchTypeList(); // 刷新列表 | |
222 | + emit('success'); // 通知父组件操作成功 | |
223 | + } else { | |
224 | + console.error('删除问题类型失败:', result); | |
225 | + createMessage.error('删除问题类型失败'); | |
226 | + } | |
227 | + } catch (error) { | |
228 | + console.error('删除问题类型出错:', error); | |
229 | + createMessage.error('删除问题类型失败: ' + (error.message || '未知错误')); | |
230 | + } finally { | |
231 | + loading.value = false; | |
232 | + } | |
233 | + } | |
234 | + | |
235 | + // 组件挂载时获取数据 | |
236 | + onMounted(() => { | |
237 | + if (props.visible) { | |
238 | + fetchTypeList(); | |
239 | + } | |
240 | + }); | |
241 | +</script> | |
242 | + | |
243 | +<script lang="ts"> | |
244 | +export default { | |
245 | + name: 'QuestTypeModal', | |
246 | +}; | |
247 | +</script> | |
248 | + | |
249 | +<style lang="less" scoped> | |
250 | + .quest-type-container { | |
251 | + .list-header { | |
252 | + display: flex; | |
253 | + justify-content: space-between; | |
254 | + align-items: center; | |
255 | + margin-bottom: 24px; | |
256 | + | |
257 | + .title { | |
258 | + font-size: 18px; | |
259 | + font-weight: 500; | |
260 | + } | |
261 | + } | |
262 | + | |
263 | + :deep(.ant-table) { | |
264 | + font-size: 16px; | |
265 | + | |
266 | + .ant-table-thead > tr > th { | |
267 | + font-size: 16px; | |
268 | + padding: 16px 16px; | |
269 | + } | |
270 | + | |
271 | + .ant-table-tbody > tr > td { | |
272 | + padding: 16px 16px; | |
273 | + } | |
274 | + } | |
275 | + } | |
276 | +</style> | |
0 | 277 | \ No newline at end of file | ... | ... |
src/views/project/quest/ReleaseModal.vue
0 → 100644
1 | +<template> | |
2 | + <a-modal | |
3 | + :visible="visible" | |
4 | + title="已通过版本记录" | |
5 | + width="80%" | |
6 | + :footer="null" | |
7 | + @cancel="handleCancel" | |
8 | + @update:visible="(val) => emit('update:visible', val)" | |
9 | + > | |
10 | + <div v-if="loading" class="loading-container"> | |
11 | + <a-spin /> | |
12 | + </div> | |
13 | + <div v-else> | |
14 | + <div v-if="tableData.length === 0" class="empty-data"> | |
15 | + <a-empty description="没有找到已通过版本的记录" /> | |
16 | + </div> | |
17 | + <BasicTable | |
18 | + v-else | |
19 | + :dataSource="tableData" | |
20 | + :columns="columns" | |
21 | + :pagination="false" | |
22 | + bordered | |
23 | + showIndexColumn | |
24 | + :canResize="false" | |
25 | + :loading="loading" | |
26 | + > | |
27 | + <template #bodyCell="{ column, record }"> | |
28 | + <template v-if="column.key === 'action'"> | |
29 | + <TableAction | |
30 | + :actions="[ | |
31 | + { | |
32 | + icon: 'ant-design:eye-outlined', | |
33 | + tooltip: '查看', | |
34 | + onClick: handleView.bind(null, record), | |
35 | + } | |
36 | + ]" | |
37 | + /> | |
38 | + </template> | |
39 | + </template> | |
40 | + </BasicTable> | |
41 | + </div> | |
42 | + </a-modal> | |
43 | + | |
44 | + <!-- 查看详情抽屉 --> | |
45 | + <QuestDrawer @register="registerDrawer" /> | |
46 | +</template> | |
47 | + | |
48 | +<script lang="ts" setup name="ReleaseModal"> | |
49 | + import { ref, watch } from 'vue'; | |
50 | + import { getQuestReleaseData } from '/@/api/project/quest'; | |
51 | + import { BasicTable, TableAction } from '/@/components/Table'; | |
52 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
53 | + import { useDrawer } from '/@/components/Drawer'; | |
54 | + import QuestDrawer from './QuestDrawer.vue'; | |
55 | + | |
56 | + const props = defineProps({ | |
57 | + visible: { | |
58 | + type: Boolean, | |
59 | + default: false, | |
60 | + }, | |
61 | + recordId: { | |
62 | + type: Number, | |
63 | + default: undefined, | |
64 | + }, | |
65 | + }); | |
66 | + | |
67 | + const emit = defineEmits(['update:visible', 'cancel']); | |
68 | + const { createMessage } = useMessage(); | |
69 | + const loading = ref(false); | |
70 | + const tableData = ref<any[]>([]); | |
71 | + | |
72 | + // 注册抽屉 | |
73 | + const [registerDrawer, { openDrawer }] = useDrawer(); | |
74 | + | |
75 | + // 表格列定义 | |
76 | + const columns = [ | |
77 | + { | |
78 | + title: '标题', | |
79 | + dataIndex: 'title', | |
80 | + key: 'title', | |
81 | + width: 400, | |
82 | + customHeaderCell: () => ({ style: { color: 'red', fontSize: '16px' } }), | |
83 | + }, | |
84 | + { | |
85 | + title: '内容', | |
86 | + dataIndex: 'contentText', | |
87 | + key: 'contentText', | |
88 | + width: 500, | |
89 | + customRender: ({ text }) => { | |
90 | + if (!text) return ''; | |
91 | + const div = document.createElement('div'); | |
92 | + div.innerHTML = text; | |
93 | + const plainText = div.textContent || div.innerText || ''; | |
94 | + return plainText.length > 30 ? plainText.substring(0, 30) + '...' : plainText; | |
95 | + }, | |
96 | + customHeaderCell: () => ({ style: { color: 'red', fontSize: '16px' } }), | |
97 | + }, | |
98 | + { | |
99 | + title: '操作', | |
100 | + dataIndex: 'action', | |
101 | + width: 100, | |
102 | + key: 'action', | |
103 | + fixed: 'right', | |
104 | + }, | |
105 | + ]; | |
106 | + | |
107 | + // 监听visible变化,加载数据 | |
108 | + watch(() => props.visible, async (newVisible) => { | |
109 | + if (newVisible && props.recordId) { | |
110 | + await loadReleaseData(props.recordId); | |
111 | + } | |
112 | + }); | |
113 | + | |
114 | + // 加载已通过版本数据 | |
115 | + async function loadReleaseData(id: number) { | |
116 | + loading.value = true; | |
117 | + tableData.value = []; | |
118 | + | |
119 | + try { | |
120 | + const response = await getQuestReleaseData(id); | |
121 | + | |
122 | + // 简化数据处理逻辑 | |
123 | + if (Array.isArray(response)) { | |
124 | + // 如果返回的是数组,直接使用 | |
125 | + tableData.value = response; | |
126 | + } else if (response && Array.isArray(response.items)) { | |
127 | + // 如果返回的是包含items属性的对象 | |
128 | + tableData.value = response.items; | |
129 | + } else if (response && Array.isArray(response.records)) { | |
130 | + // 如果返回的是包含records属性的对象 | |
131 | + tableData.value = response.records; | |
132 | + } else if (response && typeof response === 'object' && !Array.isArray(response)) { | |
133 | + // 如果返回的是单个对象 | |
134 | + tableData.value = [response]; | |
135 | + } | |
136 | + | |
137 | + // 显示空数据提示 | |
138 | + if (tableData.value.length === 0) { | |
139 | + createMessage.info('没有找到已通过版本的记录'); | |
140 | + } | |
141 | + } catch (error) { | |
142 | + createMessage.error('获取已通过版本数据失败: ' + error.message); | |
143 | + } finally { | |
144 | + loading.value = false; | |
145 | + } | |
146 | + } | |
147 | + | |
148 | + // 取消 | |
149 | + function handleCancel() { | |
150 | + emit('update:visible', false); | |
151 | + emit('cancel'); | |
152 | + } | |
153 | + | |
154 | + // 查看详情 | |
155 | + function handleView(record) { | |
156 | + openDrawer(true, { | |
157 | + record, | |
158 | + isUpdate: true, | |
159 | + isView: true, // 标记为查看模式 | |
160 | + }); | |
161 | + } | |
162 | +</script> | |
163 | + | |
164 | +<style lang="less" scoped> | |
165 | + .loading-container { | |
166 | + display: flex; | |
167 | + justify-content: center; | |
168 | + align-items: center; | |
169 | + height: 200px; | |
170 | + } | |
171 | + | |
172 | + .empty-data { | |
173 | + display: flex; | |
174 | + justify-content: center; | |
175 | + align-items: center; | |
176 | + height: 300px; | |
177 | + background-color: #fafafa; | |
178 | + border-radius: 4px; | |
179 | + } | |
180 | +</style> | |
181 | + | |
182 | +<script lang="ts"> | |
183 | +export default { | |
184 | + name: 'ReleaseModal', | |
185 | +}; | |
186 | +</script> | |
0 | 187 | \ No newline at end of file | ... | ... |
src/views/project/quest/index.vue
1 | 1 | <template> |
2 | 2 | <div class="quest-container"> |
3 | - <!-- 搜索区域 --> | |
4 | - <!-- <div class="bg-white p-4 mb-4"> | |
5 | - <BasicForm @register="registerForm" /> | |
6 | - </div> --> | |
3 | + <!-- 选项卡区域 --> | |
4 | + <div class="tab-container"> | |
5 | + <a-tabs v-model:activeKey="activeTab" @change="handleTabChange"> | |
6 | + <a-tab-pane key="problems" tab="问题列表"></a-tab-pane> | |
7 | + <a-tab-pane key="review" tab="审核列表"></a-tab-pane> | |
8 | + </a-tabs> | |
9 | + </div> | |
7 | 10 | |
8 | 11 | <!-- 表格区域 --> |
9 | 12 | <div class="bg-white"> |
10 | - <BasicTable @register="registerTable"> | |
13 | + <!-- 问题列表 --> | |
14 | + <BasicTable v-if="activeTab === 'problems'" @register="registerProblemTable"> | |
11 | 15 | <template #toolbar> |
12 | - <a-button type="primary" @click="handleCreate"> | |
13 | - 新建 | |
16 | + <a-button type="primary" @click="handleCalculateAmount" style="margin-right: 8px;" :disabled="checkedRows.length === 0"> | |
17 | + 计算扣款金额 | |
18 | + </a-button> | |
19 | + <a-button type="primary" @click="handleCreate" style="margin-right: 8px;"> | |
20 | + 新建问题 | |
14 | 21 | </a-button> |
22 | + <a-button @click="handleQuestTypeManager"> | |
23 | + 问题类型管理 | |
24 | + </a-button> | |
25 | + <!-- 已勾选数据提示 --> | |
26 | + <div v-if="checkedRows.length > 0" class="selected-count"> | |
27 | + 已勾选 {{ checkedRows.length }} 条数据 | |
28 | + </div> | |
29 | + </template> | |
30 | + | |
31 | + <template #bodyCell="{ column, record }"> | |
32 | + <template v-if="column.key === 'action'"> | |
33 | + <TableAction | |
34 | + :actions="[ | |
35 | + { | |
36 | + icon: 'ant-design:eye-outlined', | |
37 | + tooltip: '查看', | |
38 | + onClick: handleView.bind(null, record), | |
39 | + }, | |
40 | + { | |
41 | + icon: 'clarity:note-edit-line', | |
42 | + tooltip: '编辑', | |
43 | + onClick: handleEdit.bind(null, record), | |
44 | + }, | |
45 | + { | |
46 | + icon: 'ant-design:delete-outlined', | |
47 | + color: 'error', | |
48 | + tooltip: '删除', | |
49 | + popConfirm: { | |
50 | + title: '是否确认删除?', | |
51 | + confirm: handleDelete.bind(null, record), | |
52 | + }, | |
53 | + ifShow: role === ROLE.ADMIN | |
54 | + }, | |
55 | + ]" | |
56 | + /> | |
57 | + </template> | |
58 | + </template> | |
59 | + </BasicTable> | |
60 | + | |
61 | + <!-- 审核列表 --> | |
62 | + <BasicTable v-if="activeTab === 'review'" @register="registerReviewTable"> | |
63 | + <template #toolbar> | |
64 | + <!-- 已勾选数据提示 --> | |
65 | + <div v-if="checkedRows.length > 0" class="selected-count"> | |
66 | + 已勾选 {{ checkedRows.length }} 条数据 | |
67 | + </div> | |
15 | 68 | </template> |
16 | 69 | |
17 | 70 | <template #bodyCell="{ column, record }"> |
... | ... | @@ -19,6 +72,22 @@ |
19 | 72 | <TableAction |
20 | 73 | :actions="[ |
21 | 74 | { |
75 | + label: '通过', | |
76 | + color: 'success', | |
77 | + onClick: handleApprove.bind(null, record), | |
78 | + ifShow: record.status === 0 && role === ROLE.ADMIN | |
79 | + }, | |
80 | + { | |
81 | + label: '不通过', | |
82 | + color: 'error', | |
83 | + onClick: handleReject.bind(null, record), | |
84 | + ifShow: record.status === 0 && role === ROLE.ADMIN | |
85 | + }, | |
86 | + { | |
87 | + label: '查看已通过版本', | |
88 | + onClick: handleViewRelease.bind(null, record), | |
89 | + }, | |
90 | + { | |
22 | 91 | icon: 'ant-design:eye-outlined', |
23 | 92 | tooltip: '查看', |
24 | 93 | onClick: handleView.bind(null, record), |
... | ... | @@ -36,6 +105,7 @@ |
36 | 105 | title: '是否确认删除?', |
37 | 106 | confirm: handleDelete.bind(null, record), |
38 | 107 | }, |
108 | + ifShow: role === ROLE.ADMIN | |
39 | 109 | }, |
40 | 110 | ]" |
41 | 111 | /> |
... | ... | @@ -46,19 +116,107 @@ |
46 | 116 | |
47 | 117 | <!-- 编辑弹窗 --> |
48 | 118 | <QuestDrawer @register="registerDrawer" @success="handleSuccess" /> |
119 | + | |
120 | + <!-- 拒绝原因弹窗 --> | |
121 | + <a-modal v-model:visible="rejectModalVisible" title="拒绝原因" @ok="confirmReject"> | |
122 | + <a-textarea v-model:value="rejectReason" placeholder="请输入拒绝原因" :rows="4" /> | |
123 | + </a-modal> | |
124 | + | |
125 | + <!-- 已通过版本模态框 --> | |
126 | + <ReleaseModal | |
127 | + :visible="releaseModalVisible" | |
128 | + :recordId="currentReleaseRecordId" | |
129 | + @update:visible="(val) => releaseModalVisible = val" | |
130 | + /> | |
131 | + | |
132 | + <!-- 问题类型管理模态框 --> | |
133 | + <QuestTypeModal | |
134 | + :visible="questTypeModalVisible" | |
135 | + @update:visible="(val) => questTypeModalVisible = val" | |
136 | + @success="handleQuestTypeSuccess" | |
137 | + /> | |
138 | + | |
139 | + <!-- 扣款金额统计弹窗 --> | |
140 | + <a-modal | |
141 | + v-model:visible="amountModalVisible" | |
142 | + title="扣款金额统计" | |
143 | + :footer="null" | |
144 | + width="400px" | |
145 | + > | |
146 | + <div class="amount-statistics"> | |
147 | + <div class="statistic-item"> | |
148 | + <div class="label">数据总条数:</div> | |
149 | + <div class="value">{{ deductCountInfo.distinctIdCount }}</div> | |
150 | + </div> | |
151 | + <div class="statistic-item"> | |
152 | + <div class="label">合计金额:</div> | |
153 | + <div class="value amount">{{ getCurrencySymbol(deductCountInfo.isRmb) }}{{ formatAmount(deductCountInfo.sumDeductAmount) }}</div> | |
154 | + </div> | |
155 | + </div> | |
156 | + <div class="modal-footer"> | |
157 | + <a-button type="primary" @click="amountModalVisible = false">确定</a-button> | |
158 | + </div> | |
159 | + </a-modal> | |
49 | 160 | </div> |
50 | 161 | </template> |
51 | 162 | |
52 | 163 | <script lang="ts" setup name="QuestList"> |
53 | - import { onMounted, ref } from 'vue'; | |
164 | + import { onMounted, ref, computed } from 'vue'; | |
54 | 165 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; |
55 | 166 | import { useDrawer } from '/@/components/Drawer'; |
56 | 167 | import QuestDrawer from './QuestDrawer.vue'; |
57 | - import { getQuestList, questDelete } from '/@/api/project/quest'; | |
58 | - import { columns, searchFormSchema } from './quest.data'; | |
168 | + import ReleaseModal from './ReleaseModal.vue'; | |
169 | + import QuestTypeModal from './QuestTypeModal.vue'; | |
170 | + import { getQuestList, questDelete, setQuestStatus } from '/@/api/project/quest'; | |
171 | + import { columns, reviewColumns, searchFormSchema } from './quest.data'; | |
172 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
173 | + import { defHttp } from '/@/utils/http/axios'; | |
174 | + import { useUserStoreWithOut } from '/@/store/modules/user'; | |
175 | + import {ROLE} from '../order/type.d'; | |
176 | + import { filterFinancialData } from './quest.data'; | |
177 | + | |
178 | + const { createMessage } = useMessage(); | |
179 | + | |
180 | + // 选项卡状态 | |
181 | + const activeTab = ref<string>('problems'); | |
182 | + | |
183 | + // 拒绝原因弹窗状态 | |
184 | + const rejectModalVisible = ref<boolean>(false); | |
185 | + const rejectReason = ref<string>(''); | |
186 | + const currentRejectRecord = ref<any>(null); | |
187 | + | |
188 | + // 已通过版本模态框状态 | |
189 | + const releaseModalVisible = ref<boolean>(false); | |
190 | + const currentReleaseRecordId = ref<number | undefined>(undefined); | |
191 | + | |
192 | + // 问题类型管理模态框状态 | |
193 | + const questTypeModalVisible = ref<boolean>(false); | |
194 | + | |
195 | + // 扣款金额统计模态框状态 | |
196 | + const amountModalVisible = ref<boolean>(false); | |
197 | + const deductCountInfo = ref({ | |
198 | + distinctIdCount: 0, | |
199 | + sumDeductAmount: 0, | |
200 | + isRmb: '0' // 默认美元 | |
201 | + }); | |
202 | + | |
203 | + // 表格选中行数据 | |
204 | + const checkedKeys = ref<(string | number)[]>([]); | |
205 | + const checkedRows = ref<any[]>([]); | |
206 | + const userStore = useUserStoreWithOut(); | |
207 | + const user = userStore.getUserInfo; | |
208 | + const role = computed(() => { | |
209 | + return user?.roleSmallVO?.code; | |
210 | + }); | |
59 | 211 | |
60 | - // 注册表格 | |
61 | - const [registerTable, { reload }] = useTable({ | |
212 | + // 表格选择回调函数 | |
213 | + function handleSelectionChange(selectedRowKeys: (string | number)[], selectedRows: any[]) { | |
214 | + checkedKeys.value = selectedRowKeys; | |
215 | + checkedRows.value = selectedRows; | |
216 | + } | |
217 | + | |
218 | + // 注册问题列表表格 | |
219 | + const [registerProblemTable, { reload: reloadProblemTable }] = useTable({ | |
62 | 220 | title: '问题列表', |
63 | 221 | api: getQuestList, |
64 | 222 | columns, |
... | ... | @@ -67,28 +225,101 @@ |
67 | 225 | schemas: searchFormSchema, |
68 | 226 | autoSubmitOnEnter: true, |
69 | 227 | }, |
228 | + beforeFetch: (params) => { | |
229 | + // 处理日期格式 | |
230 | + if (params.startTime) { | |
231 | + params.createStartTime = params.startTime.format('YYYY-MM-DD'); | |
232 | + delete params.startTime; | |
233 | + } | |
234 | + if (params.endTime) { | |
235 | + params.createEndTime = params.endTime.format('YYYY-MM-DD'); | |
236 | + delete params.endTime; | |
237 | + } | |
238 | + return { ...params, status: 10 }; | |
239 | + }, | |
240 | + // 添加afterFetch钩子,过滤财务专用数据 | |
241 | + afterFetch: (res) => { | |
242 | + const data = res; | |
243 | + if (Array.isArray(data)) { | |
244 | + return filterFinancialData(data); | |
245 | + } | |
246 | + if (data && Array.isArray(data.items)) { | |
247 | + data.items = filterFinancialData(data.items); | |
248 | + return data; | |
249 | + } | |
250 | + return res; | |
251 | + }, | |
252 | + rowKey: 'id', | |
70 | 253 | bordered: true, |
71 | - showIndexColumn: true, | |
72 | - useSearchForm: true,//启用表单搜索 | |
254 | + showIndexColumn: false, | |
255 | + useSearchForm: true, | |
256 | + clickToRowSelect: false, // 禁止点击行时选中 | |
257 | + rowSelection: { | |
258 | + type: 'checkbox', | |
259 | + onChange: handleSelectionChange, | |
260 | + preserveSelectedRowKeys: true, // 保留选中的行,即使它们不在当前页面 | |
261 | + }, | |
73 | 262 | }); |
263 | + | |
264 | + // 注册审核列表表格 | |
265 | + const [registerReviewTable, { reload: reloadReviewTable }] = useTable({ | |
266 | + title: '审核列表', | |
267 | + api: getQuestList, | |
268 | + columns: reviewColumns, | |
269 | + formConfig: { | |
270 | + labelWidth: 120, | |
271 | + schemas: searchFormSchema, | |
272 | + autoSubmitOnEnter: true, | |
273 | + }, | |
274 | + beforeFetch: (params) => { | |
275 | + // 处理日期格式 | |
276 | + if (params.startTime) { | |
277 | + params.createStartTime = params.startTime.format('YYYY-MM-DD'); | |
278 | + delete params.startTime; | |
279 | + } | |
280 | + if (params.endTime) { | |
281 | + params.createEndTime = params.endTime.format('YYYY-MM-DD'); | |
282 | + delete params.endTime; | |
283 | + } | |
284 | + return { ...params, status: 0 }; | |
285 | + }, | |
286 | + // 添加afterFetch钩子,过滤财务专用数据 | |
287 | + afterFetch: (res) => { | |
288 | + const data = res; | |
289 | + if (Array.isArray(data)) { | |
290 | + return filterFinancialData(data); | |
291 | + } | |
292 | + if (data && Array.isArray(data.items)) { | |
293 | + data.items = filterFinancialData(data.items); | |
294 | + return data; | |
295 | + } | |
296 | + return res; | |
297 | + }, | |
298 | + rowKey: 'id', | |
299 | + bordered: true, | |
300 | + showIndexColumn: false, | |
301 | + useSearchForm: true, | |
302 | + clickToRowSelect: false, // 禁止点击行时选中 | |
303 | + rowSelection: { | |
304 | + type: 'checkbox', | |
305 | + onChange: handleSelectionChange, | |
306 | + preserveSelectedRowKeys: true, // 保留选中的行,即使它们不在当前页面 | |
307 | + }, | |
308 | + }); | |
309 | + | |
74 | 310 | // 注册抽屉 |
75 | 311 | const [registerDrawer, { openDrawer }] = useDrawer(); |
76 | 312 | |
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 | - // } | |
313 | + // 处理选项卡切换 | |
314 | + function handleTabChange(key: string) { | |
315 | + activeTab.value = key; | |
316 | + } | |
87 | 317 | |
88 | 318 | // 新建 |
89 | 319 | function handleCreate() { |
90 | 320 | openDrawer(true, { |
91 | 321 | isUpdate: false, |
322 | + isView: false, // 明确指定不是查看模式 | |
92 | 323 | }); |
93 | 324 | } |
94 | 325 | |
... | ... | @@ -103,9 +334,6 @@ |
103 | 334 | |
104 | 335 | // 编辑 |
105 | 336 | function handleEdit(record) { |
106 | - console.log('编辑记录:', record); | |
107 | - console.log('记录ID:', record.id); | |
108 | - | |
109 | 337 | openDrawer(true, { |
110 | 338 | record, |
111 | 339 | isUpdate: true, |
... | ... | @@ -116,17 +344,129 @@ |
116 | 344 | // 删除 |
117 | 345 | async function handleDelete(record) { |
118 | 346 | await questDelete([record.id]); |
119 | - await reload(); | |
347 | + if (activeTab.value === 'problems') { | |
348 | + await reloadProblemTable(); | |
349 | + } else { | |
350 | + await reloadReviewTable(); | |
351 | + } | |
352 | + } | |
353 | + | |
354 | + // 审核通过 | |
355 | + async function handleApprove(record) { | |
356 | + try { | |
357 | + await setQuestStatus({ id: record.id, status: 10 }); | |
358 | + await reloadReviewTable(); | |
359 | + } catch (error) { | |
360 | + createMessage.error('审核失败:' + error.message); | |
361 | + } | |
362 | + } | |
363 | + | |
364 | + // 审核拒绝(点击触发弹窗) | |
365 | + function handleReject(record) { | |
366 | + currentRejectRecord.value = record; | |
367 | + rejectReason.value = ''; | |
368 | + rejectModalVisible.value = true; | |
120 | 369 | } |
121 | 370 | |
122 | - // 刷新表格 | |
371 | + // 确认拒绝并提交 | |
372 | + async function confirmReject() { | |
373 | + if (!rejectReason.value.trim()) { | |
374 | + createMessage.warning('请输入拒绝原因'); | |
375 | + return; | |
376 | + } | |
377 | + | |
378 | + try { | |
379 | + await setQuestStatus({ | |
380 | + id: currentRejectRecord.value.id, | |
381 | + status: 20, | |
382 | + refuseRemark: rejectReason.value | |
383 | + }); | |
384 | + rejectModalVisible.value = false; | |
385 | + await reloadReviewTable(); | |
386 | + } catch (error) { | |
387 | + createMessage.error('审核失败:' + error.message); | |
388 | + } | |
389 | + } | |
390 | + | |
391 | + // 表格刷新 | |
123 | 392 | function handleSuccess() { |
124 | - reload(); | |
393 | + if (activeTab.value === 'problems') { | |
394 | + reloadProblemTable(); | |
395 | + } else { | |
396 | + reloadReviewTable(); | |
397 | + } | |
398 | + } | |
399 | + | |
400 | + // 查看已通过版本 | |
401 | + function handleViewRelease(record) { | |
402 | + currentReleaseRecordId.value = record.id; | |
403 | + releaseModalVisible.value = true; | |
404 | + } | |
405 | + | |
406 | + // 打开问题类型管理 | |
407 | + function handleQuestTypeManager() { | |
408 | + questTypeModalVisible.value = true; | |
409 | + } | |
410 | + | |
411 | + // 问题类型管理成功后刷新相关数据 | |
412 | + async function handleQuestTypeSuccess() { | |
413 | + // 如果有打开的抽屉,通知其刷新问题类型列表 | |
414 | + // 由于没有直接引用QuestDrawer内部方法的方式,我们采用关闭再打开的方式刷新 | |
415 | + const questDrawerInstance = document.querySelector('.quest-drawer'); | |
416 | + if (questDrawerInstance) { | |
417 | + // 存在打开的抽屉,需要刷新其中的问题类型数据 | |
418 | + createMessage.success('已更新问题类型列表,新建问题时将显示最新数据'); | |
419 | + } | |
420 | + } | |
421 | + | |
422 | + // 格式化金额,保留两位小数 | |
423 | + function formatAmount(amount) { | |
424 | + if (amount === undefined || amount === null) return '0.00'; | |
425 | + return Number(amount).toFixed(2); | |
426 | + } | |
427 | + | |
428 | + // 获取货币符号 | |
429 | + function getCurrencySymbol(isRmb) { | |
430 | + return isRmb === '1' ? '¥' : '$'; | |
431 | + } | |
432 | + | |
433 | + // 计算扣款金额 | |
434 | + async function handleCalculateAmount() { | |
435 | + if (checkedKeys.value.length === 0) { | |
436 | + createMessage.warning('请先选择要计算的数据'); | |
437 | + return; | |
438 | + } | |
439 | + | |
440 | + try { | |
441 | + // 发送请求获取扣款金额统计 | |
442 | + const res = await defHttp.post({ | |
443 | + url: '/order/erp/quest/getSumDeductAmount_by_ids', | |
444 | + data: { ids: checkedKeys.value } | |
445 | + }); | |
446 | + | |
447 | + // 检查返回结果 | |
448 | + if (res && typeof res === 'object') { | |
449 | + // 设置统计数据 | |
450 | + deductCountInfo.value = { | |
451 | + distinctIdCount: res.distinctIdCount || 0, | |
452 | + sumDeductAmount: res.sumDeductAmount || 0, | |
453 | + // 获取并处理isRmb值,确保类型一致 | |
454 | + isRmb: (res.isRmb !== undefined && res.isRmb !== null) ? String(res.isRmb) : '0' | |
455 | + }; | |
456 | + | |
457 | + // 显示弹窗 | |
458 | + amountModalVisible.value = true; | |
459 | + } else { | |
460 | + createMessage.error('获取扣款金额统计失败:返回数据格式不正确'); | |
461 | + } | |
462 | + } catch (error) { | |
463 | + createMessage.error('获取扣款金额统计失败:' + (error.message || '未知错误')); | |
464 | + } | |
125 | 465 | } |
126 | 466 | |
127 | 467 | // 页面加载完成后请求数据 |
128 | 468 | onMounted(async () => { |
129 | - | |
469 | + // 默认加载问题列表数据 | |
130 | 470 | }); |
131 | 471 | </script> |
132 | 472 | |
... | ... | @@ -134,8 +474,61 @@ |
134 | 474 | .quest-container { |
135 | 475 | padding: 16px; |
136 | 476 | |
477 | + .tab-container { | |
478 | + background-color: white; | |
479 | + padding: 0 16px; | |
480 | + margin-bottom: 16px; | |
481 | + } | |
482 | + | |
137 | 483 | :deep(.ant-table-wrapper) { |
138 | 484 | padding: 16px; |
139 | 485 | } |
486 | + | |
487 | + .selected-count { | |
488 | + display: inline-block; | |
489 | + margin-left: 16px; | |
490 | + padding: 4px 12px; | |
491 | + background-color: #e6f7ff; | |
492 | + border: 1px solid #91d5ff; | |
493 | + border-radius: 4px; | |
494 | + color: #1890ff; | |
495 | + font-weight: 500; | |
496 | + } | |
497 | + } | |
498 | + | |
499 | + // 扣款金额统计弹窗样式 | |
500 | + .amount-statistics { | |
501 | + padding: 16px; | |
502 | + | |
503 | + .statistic-item { | |
504 | + display: flex; | |
505 | + align-items: center; | |
506 | + margin-bottom: 16px; | |
507 | + | |
508 | + .label { | |
509 | + width: 120px; | |
510 | + text-align: right; | |
511 | + padding-right: 12px; | |
512 | + color: #606266; | |
513 | + font-size: 14px; | |
514 | + } | |
515 | + | |
516 | + .value { | |
517 | + flex: 1; | |
518 | + font-size: 16px; | |
519 | + color: #303133; | |
520 | + font-weight: 500; | |
521 | + | |
522 | + &.amount { | |
523 | + color: #f56c6c; | |
524 | + font-size: 18px; | |
525 | + } | |
526 | + } | |
527 | + } | |
528 | + } | |
529 | + | |
530 | + .modal-footer { | |
531 | + text-align: center; | |
532 | + margin-top: 24px; | |
140 | 533 | } |
141 | 534 | </style> | ... | ... |
src/views/project/quest/quest.data.tsx
1 | 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'} }), // 让表头变红 | |
2 | + | |
3 | +import { useUserStoreWithOut } from '/@/store/modules/user'; | |
4 | +import { ROLE } from '../order/type.d'; | |
5 | +import { computed } from 'vue'; | |
6 | +const userStore = useUserStoreWithOut(); | |
7 | +const user=userStore.getUserInfo; | |
8 | + | |
9 | +// 基础列定义(包含所有可能的列) | |
10 | +const baseColumns: BasicColumn[] = [ | |
11 | + { | |
12 | + title: '标题', | |
13 | + dataIndex: 'title', | |
14 | + key: 'title', | |
15 | + width: 350, // 减小宽度 | |
16 | + customHeaderCell: () => ({ style: { color: 'red' ,fontSize:'16px'} }), // 让表头变红 | |
17 | + }, | |
18 | + { | |
19 | + title: '问题类型', | |
20 | + dataIndex: 'questType', | |
21 | + key: 'questType', | |
22 | + width: 150, // 设置适当宽度 | |
23 | + customHeaderCell: () => ({ style: { color: 'red', fontSize:'16px' } }), // 让表头变红 | |
24 | + customRender: ({ text, record }) => { | |
25 | + // 正常显示财务专用信息 | |
26 | + if (record.deductAmount > 0 && record.deductAmount !== undefined) { | |
27 | + // 根据isRmb字段决定显示$还是¥ | |
28 | + const currencySymbol = record.isRmb === '1' ? '¥' : '$'; | |
29 | + | |
30 | + // 格式化金额为$xxx.xx或¥xxx.xx格式 | |
31 | + const formattedAmount = typeof record.deductAmount === 'number' | |
32 | + ? `${currencySymbol}${record.deductAmount.toFixed(2)}` | |
33 | + : `${currencySymbol}${Number(record.deductAmount).toFixed(2)}`; | |
34 | + | |
35 | + return `${text}(${formattedAmount})`; | |
36 | + } | |
37 | + return text; | |
10 | 38 | }, |
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' } }), // 让表头变红 | |
39 | + }, | |
40 | + { | |
41 | + title: '内容', | |
42 | + dataIndex: 'contentText', | |
43 | + key: 'contentText', | |
44 | + width: 350, // 减小宽度 | |
45 | + format: (text) => { | |
46 | + if (!text) return ''; | |
47 | + const div = document.createElement('div'); | |
48 | + div.innerHTML = text; | |
49 | + const plainText = div.textContent || div.innerText || ''; | |
50 | + return plainText.length > 30 ? plainText.substring(0, 30) + '...' : plainText; | |
23 | 51 | }, |
24 | - { | |
25 | - title: '操作', | |
26 | - dataIndex: 'action', | |
27 | - width: 160, | |
28 | - key: 'action', | |
52 | + customHeaderCell: () => ({ style: { color: 'red',fontSize:'16px' } }), // 让表头变红 | |
53 | + }, | |
54 | + { | |
55 | + title: '状态', | |
56 | + dataIndex: 'status', | |
57 | + key: 'status', | |
58 | + width: 100, | |
59 | + customRender: ({ text }) => { | |
60 | + if (text === 10) return '已解决'; | |
61 | + if (text === 20) return '已驳回'; | |
62 | + return '待审核'; | |
29 | 63 | }, |
30 | - ]; | |
64 | + }, | |
65 | + { | |
66 | + title: '操作', | |
67 | + dataIndex: 'action', | |
68 | + width: 160, | |
69 | + key: 'action', | |
70 | + fixed: 'right', // 固定在右侧 | |
71 | + }, | |
72 | +]; | |
73 | +const role=computed(() =>{ | |
74 | + return user?.roleSmallVO?.code; | |
75 | +}); | |
31 | 76 | |
77 | +// 问题列表表格列定义(不包含状态列) | |
78 | +export const columns: BasicColumn[] = [ | |
79 | + baseColumns[0], // 标题 | |
80 | + baseColumns[1], // 问题类型 | |
81 | + baseColumns[2], // 内容 | |
82 | + baseColumns[4], // 操作 | |
83 | +]; | |
32 | 84 | |
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 }, | |
85 | +// 审核列表表格列定义(包含状态列) | |
86 | +export const reviewColumns: BasicColumn[] = [ | |
87 | + baseColumns[0], // 标题 | |
88 | + baseColumns[1], // 问题类型 | |
89 | + baseColumns[2], // 内容 | |
90 | + baseColumns[3], // 状态 | |
91 | + { | |
92 | + title: '操作', | |
93 | + dataIndex: 'action', | |
94 | + width: 320, // 增加宽度以容纳更多按钮 | |
95 | + key: 'action', | |
96 | + fixed: 'right', // 固定在右侧 | |
97 | + }, | |
98 | +]; | |
99 | + | |
100 | +// 表单配置 | |
101 | +export const searchFormSchema: FormSchema[] = [ | |
102 | + { | |
103 | + field: 'title', | |
104 | + label: '标题', | |
105 | + component: 'Input', | |
106 | + colProps: { span: 6 }, | |
107 | + }, | |
108 | + { | |
109 | + field: 'contentText', | |
110 | + label: '内容关键字', | |
111 | + component: 'Input', | |
112 | + colProps: { span: 6 }, | |
113 | + }, | |
114 | + { | |
115 | + field: 'questType', | |
116 | + label: '问题类型', | |
117 | + component: 'Input', | |
118 | + colProps: { span: 6 }, | |
119 | + }, | |
120 | + { | |
121 | + field: `createStartTime`, | |
122 | + label: `生成开始时间`, | |
123 | + component: 'DatePicker', | |
124 | + colProps: { | |
125 | + span: 6, | |
46 | 126 | }, |
47 | - { | |
48 | - field: 'createBy', | |
49 | - label: '创建人', | |
50 | - component: 'Input', | |
51 | - colProps: { span: 8 }, | |
127 | + labelWidth: 150, | |
128 | + }, | |
129 | + { | |
130 | + field: `createEndTime`, | |
131 | + label: `生成结束时间`, | |
132 | + component: 'DatePicker', | |
133 | + colProps: { | |
134 | + span: 6, | |
52 | 135 | }, |
53 | - ]; | |
54 | 136 | \ No newline at end of file |
137 | + labelWidth: 150, | |
138 | + }, | |
139 | +]; | |
140 | + | |
141 | +// 导出用于过滤财务专用数据的方法 | |
142 | +export const filterFinancialData = (dataList: any[]) => { | |
143 | + const currentRole = user?.roleSmallVO?.code; | |
144 | + console.log('角色'+currentRole); | |
145 | + // 如果用户是管理员或财务,返回所有数据 | |
146 | + if (currentRole === ROLE.ADMIN || currentRole === ROLE.FINANCE) { | |
147 | + return dataList; | |
148 | + } | |
149 | + // 否则过滤掉财务专用的数据 | |
150 | + return dataList.filter(item => item.questType !== '财务专用'); | |
151 | +}; | |
55 | 152 | \ No newline at end of file | ... | ... |
vite.config.ts
... | ... | @@ -30,12 +30,8 @@ export default defineApplicationConfig({ |
30 | 30 | }, |
31 | 31 | }, |
32 | 32 | '/basic-api/order': { |
33 | - target: 'http://47.104.8.35:8000', | |
34 | - // target: 'http://localhost:18000', | |
35 | - // target: 'http://39.108.227.113:8000', | |
36 | - // target: 'http://localhost:8000', | |
37 | - // target: 'http://39.108.227.113:3000/mock/35', | |
38 | - // http://39.108.227.113:8000/order/erp/captcha/get_img_captcha_code | |
33 | + // target: 'http://localhost:18001', | |
34 | + target: 'http://47.104.8.35:18001', | |
39 | 35 | changeOrigin: true, |
40 | 36 | ws: true, |
41 | 37 | rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), |
... | ... | @@ -52,11 +48,8 @@ export default defineApplicationConfig({ |
52 | 48 | // rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), |
53 | 49 | // }, |
54 | 50 | '/api/localStorage/upload': { |
55 | - target: 'http://47.104.8.35:8000', | |
56 | - // target: 'http://localhost:18000', | |
57 | - // target: 'http://39.108.227.113:8000', | |
58 | - // target: '192.168.31.250:18000', | |
59 | - // target: 'http://localhost:8000', | |
51 | + // target: 'http://localhost:18001', | |
52 | + target: 'http://47.104.8.35:18001', | |
60 | 53 | changeOrigin: true, |
61 | 54 | ws: true, |
62 | 55 | // rewrite: (path) => { | ... | ... |