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,28 +5,40 @@ enum Api { | ||
5 | QUEST_CREATE = '/order/erp/quest/add', | 5 | QUEST_CREATE = '/order/erp/quest/add', |
6 | QUEST_DELETE = '/order/erp/quest/delete_by_id', | 6 | QUEST_DELETE = '/order/erp/quest/delete_by_id', |
7 | QUEST_UPDATE = '/order/erp/quest/edit', | 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 | export const questUpdate=async(data:any)=>{ | 14 | export const questUpdate=async(data:any)=>{ |
11 | const res=await defHttp.post<any>({ | 15 | const res=await defHttp.post<any>({ |
12 | url: Api.QUEST_UPDATE, | 16 | url: Api.QUEST_UPDATE, |
13 | - data},{message:'操作成功'}); | 17 | + data, |
18 | + successMsg: '操作成功' | ||
19 | + }); | ||
14 | return res; | 20 | return res; |
15 | } | 21 | } |
16 | 22 | ||
17 | export const questDelete = async (ids: number[]) => { | 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 | return res; | 29 | return res; |
21 | }; | 30 | }; |
22 | 31 | ||
23 | export const questCreate = async (data: any) => { | 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 | return res; | 38 | return res; |
26 | }; | 39 | }; |
27 | 40 | ||
28 | export const getQuestList = async (params: any) => { | 41 | export const getQuestList = async (params: any) => { |
29 | - console.log('params',params); | ||
30 | const res=await defHttp.post<any>({ | 42 | const res=await defHttp.post<any>({ |
31 | url: Api.QUEST_LIST, | 43 | url: Api.QUEST_LIST, |
32 | params, | 44 | params, |
@@ -37,3 +49,53 @@ export const getQuestList = async (params: any) => { | @@ -37,3 +49,53 @@ export const getQuestList = async (params: any) => { | ||
37 | }; | 49 | }; |
38 | return result; | 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 +16,16 @@ | ||
16 | <BasicForm @register="registerForm" /> | 16 | <BasicForm @register="registerForm" /> |
17 | </div> --> | 17 | </div> --> |
18 | <div style="font-size: 15px">实际收款金额1$</div> | 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 | <div style="margin: 16px 0"></div> | 20 | <div style="margin: 16px 0"></div> |
21 | <div style="font-size: 15px">实际收款金额2$</div> | 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 | <div style="margin: 16px 0"></div> | 23 | <div style="margin: 16px 0"></div> |
24 | <div style="font-size: 15px">实际收款金额3$</div> | 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 | <div style="margin: 16px 0"></div> | 26 | <div style="margin: 16px 0"></div> |
27 | <div style="font-size: 15px">其他费用金额$</div> | 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 | <div style="margin: 16px 0"></div> | 29 | <div style="margin: 16px 0"></div> |
30 | 30 | ||
31 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> | 31 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> |
src/views/project/finance/financeList/FinanceEditCheck.vue
@@ -16,13 +16,13 @@ | @@ -16,13 +16,13 @@ | ||
16 | <BasicForm @register="registerForm" /> | 16 | <BasicForm @register="registerForm" /> |
17 | </div> --> | 17 | </div> --> |
18 | <div style="font-size: 15px">实际付款金额1¥</div> | 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 | <div style="margin: 16px 0"></div> | 20 | <div style="margin: 16px 0"></div> |
21 | <div style="font-size: 15px">实际付款金额2¥</div> | 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 | <div style="margin: 16px 0"></div> | 23 | <div style="margin: 16px 0"></div> |
24 | <div style="font-size: 15px">实际付款金额3¥</div> | 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 | <div style="margin: 16px 0"></div> | 26 | <div style="margin: 16px 0"></div> |
27 | 27 | ||
28 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> | 28 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> |
src/views/project/finance/financeList/TrackEdit.vue
@@ -15,7 +15,16 @@ | @@ -15,7 +15,16 @@ | ||
15 | > | 15 | > |
16 | <div> | 16 | <div> |
17 | <div style="font-size: 15px">客户扣款金额$</div> | 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 | <div style="margin: 16px 0"></div> | 28 | <div style="margin: 16px 0"></div> |
20 | <div>上传扣款单</div | 29 | <div>上传扣款单</div |
21 | ><a-space direction="vertical" style="width: 100%" size="large"> | 30 | ><a-space direction="vertical" style="width: 100%" size="large"> |
@@ -26,11 +35,45 @@ | @@ -26,11 +35,45 @@ | ||
26 | :max-count="1" | 35 | :max-count="1" |
27 | :action="updateDeductUrl" | 36 | :action="updateDeductUrl" |
28 | @change="handleChange" | 37 | @change="handleChange" |
29 | - :disabled="status === 10" | 38 | + :disabled="status === 10 || status === 40" |
30 | > | 39 | > |
31 | <a-button> 上传扣款单 </a-button> | 40 | <a-button> 上传扣款单 </a-button> |
32 | </a-upload> | 41 | </a-upload> |
33 | </a-space> | 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 | </div> | 77 | </div> |
35 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> | 78 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> |
36 | <template #appendFooter> | 79 | <template #appendFooter> |
@@ -41,12 +84,12 @@ | @@ -41,12 +84,12 @@ | ||
41 | </template> | 84 | </template> |
42 | <script lang="ts" setup> | 85 | <script lang="ts" setup> |
43 | import { BasicDrawer, useDrawerInner } from '@/components/Drawer'; | 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 | import type { UploadProps } from 'ant-design-vue'; | 88 | import type { UploadProps } from 'ant-design-vue'; |
48 | import { updateDeduct } from '@/api/project/invoice'; | 89 | import { updateDeduct } from '@/api/project/invoice'; |
49 | import { useMessage } from '/@/hooks/web/useMessage'; | 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 | const emit = defineEmits(['success']); | 94 | const emit = defineEmits(['success']); |
52 | const fileList = ref<UploadProps['fileList']>([]); | 95 | const fileList = ref<UploadProps['fileList']>([]); |
@@ -63,16 +106,264 @@ | @@ -63,16 +106,264 @@ | ||
63 | const { createMessage } = useMessage(); | 106 | const { createMessage } = useMessage(); |
64 | const { error } = createMessage; | 107 | const { error } = createMessage; |
65 | const status = ref(); | 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 | status.value = data.data.invoiceStatus; | 131 | status.value = data.data.invoiceStatus; |
69 | id.value = data.data.invoiceId; | 132 | id.value = data.data.invoiceId; |
70 | invoiceNo.value = data.data.invoiceNo; | 133 | invoiceNo.value = data.data.invoiceNo; |
71 | input1.value = data.data.invoiceDeductAmount; | 134 | input1.value = data.data.invoiceDeductAmount; |
72 | deductUrl.value = data.data.invoiceDeductUrl; | 135 | deductUrl.value = data.data.invoiceDeductUrl; |
73 | deductUrlOld.value = data.data.invoiceDeductUrl; | 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 | function handleChange(info) { | 367 | function handleChange(info) { |
77 | if (info.file.status == 'done') { | 368 | if (info.file.status == 'done') { |
78 | updateDeductUrl.value = info.file.response.data.fileUrl; | 369 | updateDeductUrl.value = info.file.response.data.fileUrl; |
@@ -86,33 +377,56 @@ | @@ -86,33 +377,56 @@ | ||
86 | function beforeUpload(info) { | 377 | function beforeUpload(info) { |
87 | updateDeductUrl.value = uploadUrl.value + info.name; | 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 | async function handleSubmit() { | 404 | async function handleSubmit() { |
104 | if (!input1.value) { | 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 | await updateDeduct({ | 417 | await updateDeduct({ |
108 | id: id.value, | 418 | id: id.value, |
109 | invoiceNo: invoiceNo.value, | 419 | invoiceNo: invoiceNo.value, |
110 | deductAmount: input1.value, | 420 | deductAmount: input1.value, |
111 | deductUrl: deductUrl.value, | 421 | deductUrl: deductUrl.value, |
422 | + questId: selectedTitle.value, // 传递选中的扣款原因ID | ||
112 | }); | 423 | }); |
113 | fileList.value = []; | 424 | fileList.value = []; |
114 | emit('success'); | 425 | emit('success'); |
115 | closeDrawer(); | 426 | closeDrawer(); |
427 | + } catch (err) { | ||
428 | + error('保存失败'); | ||
429 | + console.error('保存失败:', err); | ||
116 | } | 430 | } |
117 | } | 431 | } |
118 | </script> | 432 | </script> |
src/views/project/finance/financeList/TrackEditCheck.vue
@@ -15,13 +15,22 @@ | @@ -15,13 +15,22 @@ | ||
15 | > | 15 | > |
16 | <div> | 16 | <div> |
17 | <div style="font-size: 15px">生产科扣款金额¥</div> | 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 | <div style="margin: 16px 0"></div> | 28 | <div style="margin: 16px 0"></div> |
20 | <div style="font-size: 15px">扣款责任部门</div> | 29 | <div style="font-size: 15px">扣款责任部门</div> |
21 | <a-input | 30 | <a-input |
22 | v-model:value="deductDept" | 31 | v-model:value="deductDept" |
23 | placeholder="请输入" | 32 | placeholder="请输入" |
24 | - :disabled="status === 10" | 33 | + :disabled="status === 10 || status === 40" |
25 | auto-size | 34 | auto-size |
26 | /> | 35 | /> |
27 | <!-- <a-select | 36 | <!-- <a-select |
@@ -44,13 +53,35 @@ | @@ -44,13 +53,35 @@ | ||
44 | :beforeUpload="beforeUpload" | 53 | :beforeUpload="beforeUpload" |
45 | list-type="picture" | 54 | list-type="picture" |
46 | :max-count="1" | 55 | :max-count="1" |
47 | - :disabled="status === 10" | 56 | + :disabled="status === 10 || status === 40" |
48 | :action="updateDeductUrl" | 57 | :action="updateDeductUrl" |
49 | @change="handleChange" | 58 | @change="handleChange" |
50 | > | 59 | > |
51 | <a-button> 上传扣款单 </a-button> | 60 | <a-button> 上传扣款单 </a-button> |
52 | </a-upload> | 61 | </a-upload> |
53 | </a-space> | 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 | </div> | 85 | </div> |
55 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> | 86 | <!-- <template #titleToolbar> <a-button type="primary"> 申请编辑权限 </a-button></template> --> |
56 | <template #appendFooter> | 87 | <template #appendFooter> |
@@ -61,12 +92,12 @@ | @@ -61,12 +92,12 @@ | ||
61 | </template> | 92 | </template> |
62 | <script lang="ts" setup> | 93 | <script lang="ts" setup> |
63 | import { BasicDrawer, useDrawerInner } from '@/components/Drawer'; | 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 | import type { UploadProps } from 'ant-design-vue'; | 96 | import type { UploadProps } from 'ant-design-vue'; |
68 | import { updateDeductInfo } from '@/api/project/invoice'; | 97 | import { updateDeductInfo } from '@/api/project/invoice'; |
69 | import { useMessage } from '/@/hooks/web/useMessage'; | 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 | const emit = defineEmits(['success']); | 102 | const emit = defineEmits(['success']); |
72 | const fileList = ref<UploadProps['fileList']>([]); | 103 | const fileList = ref<UploadProps['fileList']>([]); |
@@ -83,8 +114,27 @@ | @@ -83,8 +114,27 @@ | ||
83 | const { createMessage } = useMessage(); | 114 | const { createMessage } = useMessage(); |
84 | const { error } = createMessage; | 115 | const { error } = createMessage; |
85 | const status = ref(); | 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 | status.value = data.data.checkPayStatus; | 138 | status.value = data.data.checkPayStatus; |
89 | id.value = data.data.checkId; | 139 | id.value = data.data.checkId; |
90 | checkNo.value = data.data.checkNo; | 140 | checkNo.value = data.data.checkNo; |
@@ -92,7 +142,218 @@ | @@ -92,7 +142,218 @@ | ||
92 | deductDept.value = data.data.checkDeductDept; | 142 | deductDept.value = data.data.checkDeductDept; |
93 | deductUrl.value = data.data.checkDeductUrl; | 143 | deductUrl.value = data.data.checkDeductUrl; |
94 | deductUrlOld.value = data.data.checkDeductUrl; | 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 | function handleChange(info) { | 357 | function handleChange(info) { |
97 | if (info.file.status == 'done') { | 358 | if (info.file.status == 'done') { |
98 | updateDeductUrl.value = info.file.response.data.fileUrl; | 359 | updateDeductUrl.value = info.file.response.data.fileUrl; |
@@ -106,29 +367,78 @@ | @@ -106,29 +367,78 @@ | ||
106 | function beforeUpload(info) { | 367 | function beforeUpload(info) { |
107 | updateDeductUrl.value = uploadUrl.value + info.name; | 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 | async function handleSubmit() { | 415 | async function handleSubmit() { |
118 | if (!input1.value || !deductDept.value) { | 416 | if (!input1.value || !deductDept.value) { |
119 | error('选项不能为空'); | 417 | error('选项不能为空'); |
120 | - } else { | 418 | + return; |
419 | + } | ||
420 | + | ||
421 | + // 添加金额不一致的检查 | ||
422 | + if (amountMismatch.value) { | ||
423 | + error(`生产科扣款金额与选择的扣款原因金额不一致!扣款原因金额为: ${bindedDeductAmount.value}`); | ||
424 | + return; | ||
425 | + } | ||
426 | + | ||
427 | + try { | ||
121 | await updateDeductInfo({ | 428 | await updateDeductInfo({ |
122 | id: id.value, | 429 | id: id.value, |
123 | checkNo: checkNo.value, | 430 | checkNo: checkNo.value, |
124 | deductAmount: input1.value, | 431 | deductAmount: input1.value, |
125 | deductDept: deductDept.value, | 432 | deductDept: deductDept.value, |
126 | deductUrl: deductUrl.value, | 433 | deductUrl: deductUrl.value, |
434 | + questId: selectedTitle.value, // 传递选中的扣款原因ID | ||
127 | }); | 435 | }); |
128 | - // productionDepartment: selectedProductionDepartment.value, | ||
129 | fileList.value = []; | 436 | fileList.value = []; |
130 | emit('success'); | 437 | emit('success'); |
131 | closeDrawer(); | 438 | closeDrawer(); |
439 | + } catch (err) { | ||
440 | + error('保存失败'); | ||
441 | + console.error('保存失败:', err); | ||
132 | } | 442 | } |
133 | } | 443 | } |
134 | </script> | 444 | </script> |
src/views/project/finance/financeList/finance.data.tsx
1 | import { FormSchema } from '/@/components/Form'; | 1 | import { FormSchema } from '/@/components/Form'; |
2 | import { BasicColumn } from '/@/components/Table'; | 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 | import { size } from 'lodash-es'; | 4 | import { size } from 'lodash-es'; |
6 | import { view } from '@/utils/pdfShow'; | 5 | import { view } from '@/utils/pdfShow'; |
7 | import { ref } from 'vue'; | 6 | import { ref } from 'vue'; |
8 | import { queryNoOptions } from '/@/api/project/order'; | 7 | import { queryNoOptions } from '/@/api/project/order'; |
9 | import { useOrderStoreWithOut } from '/@/store/modules/order'; | 8 | import { useOrderStoreWithOut } from '/@/store/modules/order'; |
10 | import { useOrderInfo } from '/@/hooks/component/order'; | 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 | const innerNoOptions = ref([]); | 14 | const innerNoOptions = ref([]); |
13 | const projectNoOptions = ref([]); | 15 | const projectNoOptions = ref([]); |
14 | const orderStore = useOrderStoreWithOut(); | 16 | const orderStore = useOrderStoreWithOut(); |
@@ -17,6 +19,27 @@ const { | @@ -17,6 +19,27 @@ const { | ||
17 | productionDepartment, | 19 | productionDepartment, |
18 | } = useOrderInfo(orderStore); | 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 | export const searchFormSchema: FormSchema[] = [ | 43 | export const searchFormSchema: FormSchema[] = [ |
21 | { | 44 | { |
22 | field: 'invoiceNo', | 45 | field: 'invoiceNo', |
@@ -325,6 +348,22 @@ export const columns: BasicColumn[] = [ | @@ -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 | title: '实际应收金额$', | 367 | title: '实际应收金额$', |
329 | dataIndex: 'invoiceActualReceivableAmount', | 368 | dataIndex: 'invoiceActualReceivableAmount', |
330 | width: 120, | 369 | width: 120, |
@@ -465,6 +504,22 @@ export const columns: BasicColumn[] = [ | @@ -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 | title: '生产科实际应付金额¥', | 523 | title: '生产科实际应付金额¥', |
469 | dataIndex: 'checkActualPayedAmount', | 524 | dataIndex: 'checkActualPayedAmount', |
470 | width: 180, | 525 | width: 180, |
@@ -555,6 +610,7 @@ export const columns: BasicColumn[] = [ | @@ -555,6 +610,7 @@ export const columns: BasicColumn[] = [ | ||
555 | width: 280, | 610 | width: 280, |
556 | }, | 611 | }, |
557 | ]; | 612 | ]; |
613 | + | ||
558 | function formatDate(input: string): string { | 614 | function formatDate(input: string): string { |
559 | // 创建一个 Date 对象 | 615 | // 创建一个 Date 对象 |
560 | const date = new Date(input); | 616 | const date = new Date(input); |
@@ -566,4 +622,57 @@ function formatDate(input: string): string { | @@ -566,4 +622,57 @@ function formatDate(input: string): string { | ||
566 | 622 | ||
567 | // 返回格式化后的日期字符串 | 623 | // 返回格式化后的日期字符串 |
568 | return `${year}-${month}-${day}`; | 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 | \ No newline at end of file | 679 | \ No newline at end of file |
src/views/project/finance/financeList/index.vue
@@ -93,6 +93,7 @@ | @@ -93,6 +93,7 @@ | ||
93 | <CommitCheck @register="registerCommitCheck" @success="handleSuccess" /> | 93 | <CommitCheck @register="registerCommitCheck" @success="handleSuccess" /> |
94 | <EditRefundTimeCheck @register="registerEditRefundTimeCheck" @success="handleSuccess" /> | 94 | <EditRefundTimeCheck @register="registerEditRefundTimeCheck" @success="handleSuccess" /> |
95 | <NoteCheck @register="registerNoteCheck" @success="handleSuccess" /> | 95 | <NoteCheck @register="registerNoteCheck" @success="handleSuccess" /> |
96 | + <QuestDrawer @register="registerQuestDrawer" @success="handleSuccess" /> | ||
96 | </template> | 97 | </template> |
97 | <template #bodyCell="{ column, record }"> | 98 | <template #bodyCell="{ column, record }"> |
98 | <template v-if="column.key === 'picUrl'"> | 99 | <template v-if="column.key === 'picUrl'"> |
@@ -288,10 +289,15 @@ | @@ -288,10 +289,15 @@ | ||
288 | </div> | 289 | </div> |
289 | </template> | 290 | </template> |
290 | <script lang="ts" setup> | 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 | import { BasicTable, useTable, BasicColumn, TableAction } from '/@/components/Table'; | 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 | import FinanceEdit from './FinanceEdit.vue'; | 301 | import FinanceEdit from './FinanceEdit.vue'; |
296 | import TrackEdit from './TrackEdit.vue'; | 302 | import TrackEdit from './TrackEdit.vue'; |
297 | import InvoiceAnalysis from './InvoiceAnalysis.vue'; | 303 | import InvoiceAnalysis from './InvoiceAnalysis.vue'; |
@@ -333,11 +339,13 @@ | @@ -333,11 +339,13 @@ | ||
333 | import { useUserStoreWithOut } from '/@/store/modules/user'; | 339 | import { useUserStoreWithOut } from '/@/store/modules/user'; |
334 | import { useMessage } from '/@/hooks/web/useMessage'; | 340 | import { useMessage } from '/@/hooks/web/useMessage'; |
335 | import { useOrderStoreWithOut } from '/@/store/modules/order'; | 341 | import { useOrderStoreWithOut } from '/@/store/modules/order'; |
342 | + import QuestDrawer from '/@/views/project/quest/QuestDrawer.vue'; | ||
336 | 343 | ||
337 | const [registerInvoiceAnalysis, { openModal: openInvoiceAnalysis }] = useModal(); | 344 | const [registerInvoiceAnalysis, { openModal: openInvoiceAnalysis }] = useModal(); |
338 | const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer(); | 345 | const [registerFinanceEdit, { openDrawer: openFinanceEdit }] = useDrawer(); |
339 | const [registerTrackEdit, { openDrawer: openTrackEdit }] = useDrawer(); | 346 | const [registerTrackEdit, { openDrawer: openTrackEdit }] = useDrawer(); |
340 | const [registerInvoiceDetail, { openDrawer: openInvoiceDetail }] = useDrawer(); | 347 | const [registerInvoiceDetail, { openDrawer: openInvoiceDetail }] = useDrawer(); |
348 | + const [registerQuestDrawer, { openDrawer: openQuestDrawer }] = useDrawer(); | ||
341 | const [registerCommit, { openModal: openCommit }] = useModal(); | 349 | const [registerCommit, { openModal: openCommit }] = useModal(); |
342 | const [registerEditRefundTime, { openModal: openEditRefundTime }] = useModal(); | 350 | const [registerEditRefundTime, { openModal: openEditRefundTime }] = useModal(); |
343 | const [registerReUploadBgUrl, { openModal: openReUploadBgUrl }] = useModal(); | 351 | const [registerReUploadBgUrl, { openModal: openReUploadBgUrl }] = useModal(); |
@@ -394,8 +402,59 @@ | @@ -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 | onMounted(async () => { | 448 | onMounted(async () => { |
398 | await orderStore.getDict(); | 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,7 +575,6 @@ | ||
516 | }); | 575 | }); |
517 | } | 576 | } |
518 | function handleDeleteDeduct(record) { | 577 | function handleDeleteDeduct(record) { |
519 | - console.log(record, '5656record'); | ||
520 | deleteDeduct({ id: record.invoiceId }); | 578 | deleteDeduct({ id: record.invoiceId }); |
521 | } | 579 | } |
522 | function handleCheckDeleteDeduct(record) { | 580 | function handleCheckDeleteDeduct(record) { |
src/views/project/quest/QuestDrawer.vue
@@ -6,12 +6,12 @@ | @@ -6,12 +6,12 @@ | ||
6 | :title="getTitle" | 6 | :title="getTitle" |
7 | width="70%" | 7 | width="70%" |
8 | @ok="handleSubmit" | 8 | @ok="handleSubmit" |
9 | - :okButtonProps="{ disabled: isView }" | 9 | + :okButtonProps="{ disabled: unref(isView) }" |
10 | > | 10 | > |
11 | <BasicForm @register="registerForm" /> | 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 | <div class="upload-title">图片上传</div> | 15 | <div class="upload-title">图片上传</div> |
16 | <div class="upload-content"> | 16 | <div class="upload-content"> |
17 | <div | 17 | <div |
@@ -50,7 +50,7 @@ | @@ -50,7 +50,7 @@ | ||
50 | </div> | 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 | <div class="upload-title">图片附件</div> | 54 | <div class="upload-title">图片附件</div> |
55 | <div class="upload-content"> | 55 | <div class="upload-content"> |
56 | <div | 56 | <div |
@@ -73,7 +73,7 @@ | @@ -73,7 +73,7 @@ | ||
73 | </div> | 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 | <div class="upload-title">附件上传</div> | 77 | <div class="upload-title">附件上传</div> |
78 | <div class="file-list"> | 78 | <div class="file-list"> |
79 | <!-- 附件列表 --> | 79 | <!-- 附件列表 --> |
@@ -108,7 +108,7 @@ | @@ -108,7 +108,7 @@ | ||
108 | </div> | 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 | <div class="upload-title">文件附件</div> | 112 | <div class="upload-title">文件附件</div> |
113 | <div class="file-list"> | 113 | <div class="file-list"> |
114 | <!-- 附件列表 --> | 114 | <!-- 附件列表 --> |
@@ -126,7 +126,7 @@ | @@ -126,7 +126,7 @@ | ||
126 | </BasicDrawer> | 126 | </BasicDrawer> |
127 | </template> | 127 | </template> |
128 | 128 | ||
129 | -<script lang="ts" setup> | 129 | +<script lang="ts" setup name="QuestDrawer"> |
130 | import { ref, computed, unref, onMounted } from 'vue'; | 130 | import { ref, computed, unref, onMounted } from 'vue'; |
131 | import { BasicForm, useForm } from '/@/components/Form'; | 131 | import { BasicForm, useForm } from '/@/components/Form'; |
132 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; | 132 | import { BasicDrawer, useDrawerInner } from '/@/components/Drawer'; |
@@ -135,6 +135,7 @@ | @@ -135,6 +135,7 @@ | ||
135 | import { h } from 'vue'; | 135 | import { h } from 'vue'; |
136 | import { questCreate, questUpdate } from '/@/api/project/quest'; | 136 | import { questCreate, questUpdate } from '/@/api/project/quest'; |
137 | import { PlusOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'; | 137 | import { PlusOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons-vue'; |
138 | + import { defHttp } from '/@/utils/http/axios'; | ||
138 | 139 | ||
139 | const emit = defineEmits(['success', 'register']); | 140 | const emit = defineEmits(['success', 'register']); |
140 | const isUpdate = ref(true); | 141 | const isUpdate = ref(true); |
@@ -173,6 +174,61 @@ | @@ -173,6 +174,61 @@ | ||
173 | }>>([]); | 174 | }>>([]); |
174 | const isFileUploading = ref(false); | 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 | // 确保URL是完整路径 | 232 | // 确保URL是完整路径 |
177 | function ensureFullUrl(url) { | 233 | function ensureFullUrl(url) { |
178 | if (!url) return ''; | 234 | if (!url) return ''; |
@@ -193,7 +249,7 @@ | @@ -193,7 +249,7 @@ | ||
193 | } | 249 | } |
194 | // 组件加载完成时执行 | 250 | // 组件加载完成时执行 |
195 | onMounted(() => { | 251 | onMounted(() => { |
196 | - console.log('QuestDrawer组件已加载,自定义文件上传和预览功能已配置'); | 252 | + fetchQuestTypes(); |
197 | }); | 253 | }); |
198 | 254 | ||
199 | // 点击上传按钮 | 255 | // 点击上传按钮 |
@@ -234,24 +290,17 @@ | @@ -234,24 +290,17 @@ | ||
234 | } | 290 | } |
235 | 291 | ||
236 | const data = await response.json(); | 292 | const data = await response.json(); |
237 | - console.log('附件上传返回数据:', data); | ||
238 | 293 | ||
239 | // 查找fileUrl字段 | 294 | // 查找fileUrl字段 |
240 | let fileUrl = null; | 295 | let fileUrl = null; |
241 | if (data.data && data.data.fileUrl) { | 296 | if (data.data && data.data.fileUrl) { |
242 | fileUrl = data.data.fileUrl; | 297 | fileUrl = data.data.fileUrl; |
243 | - console.log("在data.data中找到fileUrl:", fileUrl); | ||
244 | } else { | 298 | } else { |
245 | - console.error("未找到fileUrl字段! 数据:", data); | ||
246 | - } | ||
247 | - | ||
248 | - if (!fileUrl) { | ||
249 | throw new Error('无法获取文件URL'); | 299 | throw new Error('无法获取文件URL'); |
250 | } | 300 | } |
251 | 301 | ||
252 | // 确保URL是完整路径 | 302 | // 确保URL是完整路径 |
253 | const fullUrl = ensureFullUrl(fileUrl); | 303 | const fullUrl = ensureFullUrl(fileUrl); |
254 | - console.log('完整文件URL:', fullUrl); | ||
255 | 304 | ||
256 | // 从URL中提取文件名 | 305 | // 从URL中提取文件名 |
257 | const fileName = extractFileNameFromUrl(fullUrl); | 306 | const fileName = extractFileNameFromUrl(fullUrl); |
@@ -265,7 +314,6 @@ | @@ -265,7 +314,6 @@ | ||
265 | createMessage.success(`${fileName} 上传成功`); | 314 | createMessage.success(`${fileName} 上传成功`); |
266 | return true; | 315 | return true; |
267 | } catch (error) { | 316 | } catch (error) { |
268 | - console.error(`上传文件 ${file.name} 失败:`, error); | ||
269 | createMessage.error(`${file.name} 上传失败: ${error.message || '未知错误'}`); | 317 | createMessage.error(`${file.name} 上传失败: ${error.message || '未知错误'}`); |
270 | return false; | 318 | return false; |
271 | } | 319 | } |
@@ -289,32 +337,27 @@ | @@ -289,32 +337,27 @@ | ||
289 | // 尝试从URL中提取文件名 | 337 | // 尝试从URL中提取文件名 |
290 | // 移除查询参数 | 338 | // 移除查询参数 |
291 | const urlWithoutParams = url.split('?')[0]; | 339 | const urlWithoutParams = url.split('?')[0]; |
292 | - console.log('移除查询参数后的URL:', urlWithoutParams); | ||
293 | 340 | ||
294 | // 获取最后一个路径部分 | 341 | // 获取最后一个路径部分 |
295 | let fileName = urlWithoutParams.split('/').pop(); | 342 | let fileName = urlWithoutParams.split('/').pop(); |
296 | - console.log('提取的文件名:', fileName); | ||
297 | 343 | ||
298 | // 如果有编码的部分,尝试解码 | 344 | // 如果有编码的部分,尝试解码 |
299 | if (fileName && (fileName.includes('%') || fileName.includes('+'))) { | 345 | if (fileName && (fileName.includes('%') || fileName.includes('+'))) { |
300 | try { | 346 | try { |
301 | fileName = decodeURIComponent(fileName); | 347 | fileName = decodeURIComponent(fileName); |
302 | - console.log('解码后的文件名:', fileName); | ||
303 | } catch (e) { | 348 | } catch (e) { |
304 | - console.error('解码文件名失败:', e); | 349 | + // 解码失败 |
305 | } | 350 | } |
306 | } | 351 | } |
307 | 352 | ||
308 | return fileName || '未知文件'; | 353 | return fileName || '未知文件'; |
309 | } catch (error) { | 354 | } catch (error) { |
310 | - console.error('提取文件名失败:', error); | ||
311 | return '未知文件'; | 355 | return '未知文件'; |
312 | } | 356 | } |
313 | } | 357 | } |
314 | 358 | ||
315 | // 预览文件 | 359 | // 预览文件 |
316 | function previewFile(file) { | 360 | function previewFile(file) { |
317 | - console.log('预览文件:', file); | ||
318 | if (!file.fileUrl) { | 361 | if (!file.fileUrl) { |
319 | createMessage.error('无法预览:找不到文件URL'); | 362 | createMessage.error('无法预览:找不到文件URL'); |
320 | return; | 363 | return; |
@@ -329,7 +372,6 @@ | @@ -329,7 +372,6 @@ | ||
329 | 372 | ||
330 | // 下载文件 | 373 | // 下载文件 |
331 | function downloadFile(file) { | 374 | function downloadFile(file) { |
332 | - console.log('下载文件:', file); | ||
333 | if (!file.fileUrl) { | 375 | if (!file.fileUrl) { |
334 | createMessage.error('无法下载:找不到文件URL'); | 376 | createMessage.error('无法下载:找不到文件URL'); |
335 | return; | 377 | return; |
@@ -364,10 +406,8 @@ | @@ -364,10 +406,8 @@ | ||
364 | try { | 406 | try { |
365 | const formData = new FormData(); | 407 | const formData = new FormData(); |
366 | formData.append('file', file); | 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 | const response = await fetch(imageUploadUrl.value, { | 411 | const response = await fetch(imageUploadUrl.value, { |
372 | method: 'POST', | 412 | method: 'POST', |
373 | body: formData, | 413 | body: formData, |
@@ -378,7 +418,6 @@ | @@ -378,7 +418,6 @@ | ||
378 | } | 418 | } |
379 | 419 | ||
380 | const data = await response.json(); | 420 | const data = await response.json(); |
381 | - console.log('图片上传返回数据', data); | ||
382 | 421 | ||
383 | // 根据接口返回格式获取URL | 422 | // 根据接口返回格式获取URL |
384 | let imageUrl = ''; | 423 | let imageUrl = ''; |
@@ -391,7 +430,6 @@ | @@ -391,7 +430,6 @@ | ||
391 | imageUrl = data.data.smallPicUrl; | 430 | imageUrl = data.data.smallPicUrl; |
392 | } | 431 | } |
393 | } | 432 | } |
394 | - | ||
395 | 433 | ||
396 | // 确保URL是完整路径 | 434 | // 确保URL是完整路径 |
397 | if (imageUrl && !imageUrl.startsWith('http')) { | 435 | if (imageUrl && !imageUrl.startsWith('http')) { |
@@ -409,8 +447,6 @@ | @@ -409,8 +447,6 @@ | ||
409 | } | 447 | } |
410 | } | 448 | } |
411 | 449 | ||
412 | - console.log('处理后的图片URL:', imageUrl); | ||
413 | - | ||
414 | // 只添加URL,不添加name | 450 | // 只添加URL,不添加name |
415 | uploadedImages.value.push({ | 451 | uploadedImages.value.push({ |
416 | url: imageUrl | 452 | url: imageUrl |
@@ -418,10 +454,10 @@ | @@ -418,10 +454,10 @@ | ||
418 | 454 | ||
419 | createMessage.success('图片上传成功'); | 455 | createMessage.success('图片上传成功'); |
420 | } catch (error) { | 456 | } catch (error) { |
421 | - console.error('图片上传失败:', error); | ||
422 | - createMessage.error(`上传失败: ${error.message || '未知错误'}`); | 457 | + createMessage.error(`图片上传失败: ${error.message || '未知错误'}`); |
423 | } finally { | 458 | } finally { |
424 | isUploading.value = false; | 459 | isUploading.value = false; |
460 | + | ||
425 | // 清空input,以便于再次选择同一文件 | 461 | // 清空input,以便于再次选择同一文件 |
426 | if (imageFileInput.value) { | 462 | if (imageFileInput.value) { |
427 | imageFileInput.value.value = ''; | 463 | imageFileInput.value.value = ''; |
@@ -437,7 +473,6 @@ | @@ -437,7 +473,6 @@ | ||
437 | // 处理图片加载错误 | 473 | // 处理图片加载错误 |
438 | function handleImageError(event: Event, index: number) { | 474 | function handleImageError(event: Event, index: number) { |
439 | const target = event.target as HTMLImageElement; | 475 | const target = event.target as HTMLImageElement; |
440 | - console.error('图片加载失败:', uploadedImages.value[index].url); | ||
441 | 476 | ||
442 | // 尝试使用不同格式的URL | 477 | // 尝试使用不同格式的URL |
443 | const currentUrl = uploadedImages.value[index].url; | 478 | const currentUrl = uploadedImages.value[index].url; |
@@ -449,19 +484,22 @@ | @@ -449,19 +484,22 @@ | ||
449 | newUrl = currentUrl.replace('picUrl', 'smallPicUrl'); | 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 | createMessage.error('图片无法加载,请重新上传'); | 499 | createMessage.error('图片无法加载,请重新上传'); |
462 | // 移除无法加载的图片 | 500 | // 移除无法加载的图片 |
463 | removeImage(index); | 501 | removeImage(index); |
464 | - }; | 502 | + } |
465 | } | 503 | } |
466 | 504 | ||
467 | // 表单配置 | 505 | // 表单配置 |
@@ -483,6 +521,49 @@ | @@ -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 | field: 'contentText', | 567 | field: 'contentText', |
487 | label: '内容', | 568 | label: '内容', |
488 | component: 'Input' as any, | 569 | component: 'Input' as any, |
@@ -516,37 +597,54 @@ | @@ -516,37 +597,54 @@ | ||
516 | setDrawerProps({ confirmLoading: false }); | 597 | setDrawerProps({ confirmLoading: false }); |
517 | uploadedImages.value = []; // 清空已上传图片 | 598 | uploadedImages.value = []; // 清空已上传图片 |
518 | uploadedFiles.value = []; // 清空已上传文件 | 599 | uploadedFiles.value = []; // 清空已上传文件 |
600 | + | ||
601 | + // 每次打开表单时都刷新问题类型列表数据 | ||
602 | + await fetchQuestTypes(); | ||
519 | 603 | ||
604 | + // 明确设置状态,避免混淆 | ||
520 | isUpdate.value = !!data?.isUpdate; | 605 | isUpdate.value = !!data?.isUpdate; |
521 | isView.value = !!data?.isView; // 设置查看模式 | 606 | isView.value = !!data?.isView; // 设置查看模式 |
607 | + | ||
608 | + // 确保确认按钮状态正确 | ||
609 | + setDrawerProps({ | ||
610 | + okButtonProps: { | ||
611 | + disabled: unref(isView) | ||
612 | + } | ||
613 | + }); | ||
522 | 614 | ||
523 | if (unref(isUpdate)) { | 615 | if (unref(isUpdate)) { |
524 | record.value = { ...data.record }; | 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 | // 确保ID字段被正确保存,即使它不在表单字段中 | 623 | // 确保ID字段被正确保存,即使它不在表单字段中 |
529 | if (data.record && data.record.id) { | 624 | if (data.record && data.record.id) { |
530 | record.value.id = data.record.id; | 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 | try { | 640 | try { |
540 | if (data.record.contentImages) { | 641 | if (data.record.contentImages) { |
541 | - console.log('原始图片数据:', data.record.contentImages); | ||
542 | - | ||
543 | // 处理contentImages数组 | 642 | // 处理contentImages数组 |
544 | if (Array.isArray(data.record.contentImages)) { | 643 | if (Array.isArray(data.record.contentImages)) { |
545 | // 直接使用数组 | 644 | // 直接使用数组 |
546 | uploadedImages.value = data.record.contentImages.map(url => ({ | 645 | uploadedImages.value = data.record.contentImages.map(url => ({ |
547 | url: ensureFullUrl(url) | 646 | url: ensureFullUrl(url) |
548 | })); | 647 | })); |
549 | - console.log('数组格式的图片URL:', uploadedImages.value); | ||
550 | } | 648 | } |
551 | // 向下兼容旧格式,如果是字符串,尝试解析 | 649 | // 向下兼容旧格式,如果是字符串,尝试解析 |
552 | else if (typeof data.record.contentImages === 'string') { | 650 | else if (typeof data.record.contentImages === 'string') { |
@@ -573,14 +671,10 @@ | @@ -573,14 +671,10 @@ | ||
573 | })); | 671 | })); |
574 | } | 672 | } |
575 | } | 673 | } |
576 | - | ||
577 | - console.log('处理后的图片数据:', uploadedImages.value); | ||
578 | } | 674 | } |
579 | 675 | ||
580 | // 处理附件数据 - 现在files是一个URL数组 | 676 | // 处理附件数据 - 现在files是一个URL数组 |
581 | if (data.record.files) { | 677 | if (data.record.files) { |
582 | - console.log('原始文件数据:', data.record.files); | ||
583 | - | ||
584 | // 处理files数组 | 678 | // 处理files数组 |
585 | if (Array.isArray(data.record.files)) { | 679 | if (Array.isArray(data.record.files)) { |
586 | // 直接使用数组 | 680 | // 直接使用数组 |
@@ -591,7 +685,6 @@ | @@ -591,7 +685,6 @@ | ||
591 | fileUrl: fileUrl | 685 | fileUrl: fileUrl |
592 | }; | 686 | }; |
593 | }); | 687 | }); |
594 | - console.log('数组格式的文件URL:', uploadedFiles.value); | ||
595 | } | 688 | } |
596 | // 向下兼容旧格式,如果是字符串,尝试解析 | 689 | // 向下兼容旧格式,如果是字符串,尝试解析 |
597 | else if (typeof data.record.files === 'string') { | 690 | else if (typeof data.record.files === 'string') { |
@@ -645,11 +738,9 @@ | @@ -645,11 +738,9 @@ | ||
645 | }); | 738 | }); |
646 | } | 739 | } |
647 | } | 740 | } |
648 | - | ||
649 | - console.log('处理后的文件数据:', uploadedFiles.value); | ||
650 | } | 741 | } |
651 | } catch (error) { | 742 | } catch (error) { |
652 | - console.error('处理附件和图片数据时出错:', error); | 743 | + // 处理附件和图片数据时出错 |
653 | } | 744 | } |
654 | } | 745 | } |
655 | }); | 746 | }); |
@@ -664,26 +755,32 @@ | @@ -664,26 +755,32 @@ | ||
664 | 755 | ||
665 | // 提交表单 | 756 | // 提交表单 |
666 | async function handleSubmit() { | 757 | async function handleSubmit() { |
667 | - // 查看模式下不允许提交 | ||
668 | - if (unref(isView)) return; | 758 | + // 查看模式下直接返回,不执行任何操作 |
759 | + if (unref(isView)) { | ||
760 | + closeDrawer(); | ||
761 | + return; | ||
762 | + } | ||
669 | 763 | ||
670 | try { | 764 | try { |
671 | const values = await validate(); | 765 | const values = await validate(); |
672 | - console.log('values的值',values); | ||
673 | setDrawerProps({ confirmLoading: true }); | 766 | setDrawerProps({ confirmLoading: true }); |
674 | 767 | ||
768 | + // 添加货币类型参数 | ||
769 | + values.isRmb = currencyType.value; | ||
770 | + | ||
771 | + // 移除辅助字段,不需要提交到后端 | ||
772 | + delete values.currencySelector; | ||
773 | + | ||
675 | // 添加上传的图片到表单数据中 - 直接提交URL数组 | 774 | // 添加上传的图片到表单数据中 - 直接提交URL数组 |
676 | if (uploadedImages.value.length > 0) { | 775 | if (uploadedImages.value.length > 0) { |
677 | // 直接将URL数组作为contentImages | 776 | // 直接将URL数组作为contentImages |
678 | values.contentImages = uploadedImages.value.map(img => img.url); | 777 | values.contentImages = uploadedImages.value.map(img => img.url); |
679 | - console.log('图片数据(数组格式):', values.contentImages); | ||
680 | } | 778 | } |
681 | 779 | ||
682 | // 添加上传的附件到表单数据中 - 直接提交URL数组 | 780 | // 添加上传的附件到表单数据中 - 直接提交URL数组 |
683 | if (uploadedFiles.value.length > 0) { | 781 | if (uploadedFiles.value.length > 0) { |
684 | // 直接将fileUrl数组作为files | 782 | // 直接将fileUrl数组作为files |
685 | values.files = uploadedFiles.value.map(file => file.fileUrl); | 783 | values.files = uploadedFiles.value.map(file => file.fileUrl); |
686 | - console.log('附件数据(数组格式):', values.files); | ||
687 | } | 784 | } |
688 | 785 | ||
689 | // 如果是更新模式,确保添加ID | 786 | // 如果是更新模式,确保添加ID |
@@ -692,44 +789,32 @@ | @@ -692,44 +789,32 @@ | ||
692 | // 从record中获取ID | 789 | // 从record中获取ID |
693 | if (record.value && record.value.id) { | 790 | if (record.value && record.value.id) { |
694 | values.id = record.value.id; | 791 | values.id = record.value.id; |
695 | - console.log('从record中添加记录ID:', values.id); | ||
696 | } else { | 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 | // 调用后端API保存数据 | 799 | // 调用后端API保存数据 |
713 | const requestApi = isUpdateValue | 800 | const requestApi = isUpdateValue |
714 | ? questUpdate // 更新接口 | 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 | closeDrawer(); | 807 | closeDrawer(); |
721 | // 通知父组件刷新数据 | 808 | // 通知父组件刷新数据 |
722 | emit('success'); | 809 | emit('success'); |
723 | setDrawerProps({ confirmLoading: false }); | 810 | setDrawerProps({ confirmLoading: false }); |
724 | } catch (error) { | 811 | } catch (error) { |
725 | - console.error('API请求失败:', error); | ||
726 | createMessage.error(`保存失败: ${error.message || '未知错误'}`); | 812 | createMessage.error(`保存失败: ${error.message || '未知错误'}`); |
727 | } | 813 | } |
728 | } | 814 | } |
729 | 815 | ||
730 | // 预览图片 | 816 | // 预览图片 |
731 | function previewImage(image) { | 817 | function previewImage(image) { |
732 | - console.log('预览图片:', image); | ||
733 | if (!image.url) { | 818 | if (!image.url) { |
734 | createMessage.error('无法预览:找不到图片URL'); | 819 | createMessage.error('无法预览:找不到图片URL'); |
735 | return; | 820 | return; |
@@ -780,6 +865,12 @@ | @@ -780,6 +865,12 @@ | ||
780 | } | 865 | } |
781 | </script> | 866 | </script> |
782 | 867 | ||
868 | +<script lang="ts"> | ||
869 | +export default { | ||
870 | + name: 'QuestDrawer', | ||
871 | +}; | ||
872 | +</script> | ||
873 | + | ||
783 | <style lang="less" scoped> | 874 | <style lang="less" scoped> |
784 | .custom-image-upload { | 875 | .custom-image-upload { |
785 | margin-top: 20px; | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 187 | \ No newline at end of file |
src/views/project/quest/index.vue
1 | <template> | 1 | <template> |
2 | <div class="quest-container"> | 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 | <div class="bg-white"> | 12 | <div class="bg-white"> |
10 | - <BasicTable @register="registerTable"> | 13 | + <!-- 问题列表 --> |
14 | + <BasicTable v-if="activeTab === 'problems'" @register="registerProblemTable"> | ||
11 | <template #toolbar> | 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 | </a-button> | 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 | </template> | 68 | </template> |
16 | 69 | ||
17 | <template #bodyCell="{ column, record }"> | 70 | <template #bodyCell="{ column, record }"> |
@@ -19,6 +72,22 @@ | @@ -19,6 +72,22 @@ | ||
19 | <TableAction | 72 | <TableAction |
20 | :actions="[ | 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 | icon: 'ant-design:eye-outlined', | 91 | icon: 'ant-design:eye-outlined', |
23 | tooltip: '查看', | 92 | tooltip: '查看', |
24 | onClick: handleView.bind(null, record), | 93 | onClick: handleView.bind(null, record), |
@@ -36,6 +105,7 @@ | @@ -36,6 +105,7 @@ | ||
36 | title: '是否确认删除?', | 105 | title: '是否确认删除?', |
37 | confirm: handleDelete.bind(null, record), | 106 | confirm: handleDelete.bind(null, record), |
38 | }, | 107 | }, |
108 | + ifShow: role === ROLE.ADMIN | ||
39 | }, | 109 | }, |
40 | ]" | 110 | ]" |
41 | /> | 111 | /> |
@@ -46,19 +116,107 @@ | @@ -46,19 +116,107 @@ | ||
46 | 116 | ||
47 | <!-- 编辑弹窗 --> | 117 | <!-- 编辑弹窗 --> |
48 | <QuestDrawer @register="registerDrawer" @success="handleSuccess" /> | 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 | </div> | 160 | </div> |
50 | </template> | 161 | </template> |
51 | 162 | ||
52 | <script lang="ts" setup name="QuestList"> | 163 | <script lang="ts" setup name="QuestList"> |
53 | - import { onMounted, ref } from 'vue'; | 164 | + import { onMounted, ref, computed } from 'vue'; |
54 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; | 165 | import { BasicTable, useTable, TableAction } from '/@/components/Table'; |
55 | import { useDrawer } from '/@/components/Drawer'; | 166 | import { useDrawer } from '/@/components/Drawer'; |
56 | import QuestDrawer from './QuestDrawer.vue'; | 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 | title: '问题列表', | 220 | title: '问题列表', |
63 | api: getQuestList, | 221 | api: getQuestList, |
64 | columns, | 222 | columns, |
@@ -67,28 +225,101 @@ | @@ -67,28 +225,101 @@ | ||
67 | schemas: searchFormSchema, | 225 | schemas: searchFormSchema, |
68 | autoSubmitOnEnter: true, | 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 | bordered: true, | 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 | const [registerDrawer, { openDrawer }] = useDrawer(); | 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 | function handleCreate() { | 319 | function handleCreate() { |
90 | openDrawer(true, { | 320 | openDrawer(true, { |
91 | isUpdate: false, | 321 | isUpdate: false, |
322 | + isView: false, // 明确指定不是查看模式 | ||
92 | }); | 323 | }); |
93 | } | 324 | } |
94 | 325 | ||
@@ -103,9 +334,6 @@ | @@ -103,9 +334,6 @@ | ||
103 | 334 | ||
104 | // 编辑 | 335 | // 编辑 |
105 | function handleEdit(record) { | 336 | function handleEdit(record) { |
106 | - console.log('编辑记录:', record); | ||
107 | - console.log('记录ID:', record.id); | ||
108 | - | ||
109 | openDrawer(true, { | 337 | openDrawer(true, { |
110 | record, | 338 | record, |
111 | isUpdate: true, | 339 | isUpdate: true, |
@@ -116,17 +344,129 @@ | @@ -116,17 +344,129 @@ | ||
116 | // 删除 | 344 | // 删除 |
117 | async function handleDelete(record) { | 345 | async function handleDelete(record) { |
118 | await questDelete([record.id]); | 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 | function handleSuccess() { | 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 | onMounted(async () => { | 468 | onMounted(async () => { |
129 | - | 469 | + // 默认加载问题列表数据 |
130 | }); | 470 | }); |
131 | </script> | 471 | </script> |
132 | 472 | ||
@@ -134,8 +474,61 @@ | @@ -134,8 +474,61 @@ | ||
134 | .quest-container { | 474 | .quest-container { |
135 | padding: 16px; | 475 | padding: 16px; |
136 | 476 | ||
477 | + .tab-container { | ||
478 | + background-color: white; | ||
479 | + padding: 0 16px; | ||
480 | + margin-bottom: 16px; | ||
481 | + } | ||
482 | + | ||
137 | :deep(.ant-table-wrapper) { | 483 | :deep(.ant-table-wrapper) { |
138 | padding: 16px; | 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 | </style> | 534 | </style> |
src/views/project/quest/quest.data.tsx
1 | import { BasicColumn, FormSchema } from '/@/components/Table'; | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 152 | \ No newline at end of file |
vite.config.ts
@@ -30,12 +30,8 @@ export default defineApplicationConfig({ | @@ -30,12 +30,8 @@ export default defineApplicationConfig({ | ||
30 | }, | 30 | }, |
31 | }, | 31 | }, |
32 | '/basic-api/order': { | 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 | changeOrigin: true, | 35 | changeOrigin: true, |
40 | ws: true, | 36 | ws: true, |
41 | rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), | 37 | rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), |
@@ -52,11 +48,8 @@ export default defineApplicationConfig({ | @@ -52,11 +48,8 @@ export default defineApplicationConfig({ | ||
52 | // rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), | 48 | // rewrite: (path) => path.replace(new RegExp(`^/basic-api`), ''), |
53 | // }, | 49 | // }, |
54 | '/api/localStorage/upload': { | 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 | changeOrigin: true, | 53 | changeOrigin: true, |
61 | ws: true, | 54 | ws: true, |
62 | // rewrite: (path) => { | 55 | // rewrite: (path) => { |