Commit fb677d76ef6fcf727571c966fdaf5826804005dd

Authored by chenhang4442024
1 parent c6c33024

feat: 问题列表

fix: 禁止业务员对生产科,数量进行字段审核。
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) =&gt; { @@ -37,3 +49,53 @@ export const getQuestList = async (params: any) =&gt; {
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) => {