feat(跑店填报): 自主填报列表支持驻店/跑店切换与跑店详情
跑店填报数据在新表 yxe_visit_report,字段与 yxe_self_report 不同, 列表用顶部单选切换类型、各自分页与导出,不做 UNION 混排。 - 新增 src/api/yxe/yxeVisitReport(index.ts + model) - 自主填报列表页顶部加「驻店填报 / 跑店填报」单选: 切换时同时换数据源、换列定义、清空选中,并重置到第一页 - 跑店列:ID / 提交人 / 巡店门店数 / 活动小结 / 奖金 / 状态 / 驳回原因 / 创建时间 / 操作;驻店列保持不变 - 新增 yxeVisitReportDetail:展示巡店列表(每条含连锁、门店、定位、 打卡照片),展开详情时按 id 拉完整 store_list - 状态切换与批量删除、导出按当前类型路由到对应接口 - 搜索栏在跑店类型下隐藏「活动时间」:yxe_visit_report 无 activity_time 列 验证:vue-tsc 本次改动文件 0 错误;vite build 成功。
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { ApiResult, PageResult } from '@/api';
|
||||||
|
import type { YxeVisitReport, YxeVisitReportParam } from './model';
|
||||||
|
import { MODULES_API_URL } from '@/config/setting';
|
||||||
|
import type { FileRecord } from '@/api/system/file/model';
|
||||||
|
|
||||||
|
export async function pageYxeVisitReport(params: YxeVisitReportParam) {
|
||||||
|
const res = await request.get<ApiResult<PageResult<YxeVisitReport>>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report/page',
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listYxeVisitReport(params?: YxeVisitReportParam) {
|
||||||
|
const res = await request.get<ApiResult<YxeVisitReport[]>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report',
|
||||||
|
{
|
||||||
|
params
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.data.code === 0 && res.data.data) {
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addYxeVisitReport(data: YxeVisitReport) {
|
||||||
|
const res = await request.post<ApiResult<unknown>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateYxeVisitReport(data: YxeVisitReport) {
|
||||||
|
const res = await request.put<ApiResult<unknown>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeYxeVisitReport(id?: number) {
|
||||||
|
const res = await request.delete<ApiResult<unknown>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report/' + id
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeBatchYxeVisitReport(data: (number | undefined)[]) {
|
||||||
|
const res = await request.delete<ApiResult<unknown>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report/batch',
|
||||||
|
{
|
||||||
|
data
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
return res.data.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getYxeVisitReport(id: number) {
|
||||||
|
const res = await request.get<ApiResult<YxeVisitReport>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report/' + id
|
||||||
|
);
|
||||||
|
if (res.data.code === 0 && res.data.data) {
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportYxeVisitReport(params?: YxeVisitReportParam) {
|
||||||
|
const res = await request.post<ApiResult<FileRecord>>(
|
||||||
|
MODULES_API_URL + '/yxe/yxe-visit-report/export',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
if (res.data.code === 0 && res.data.data) {
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.data.message));
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import type { PageParam } from '@/api';
|
||||||
|
import type { User } from '@/api/system/user/model';
|
||||||
|
|
||||||
|
/** 巡店单条明细(store_list JSON 反序列化后的结构) */
|
||||||
|
export interface YxeVisitReportStore {
|
||||||
|
merchantId?: number | string;
|
||||||
|
merchantName?: string;
|
||||||
|
chainId?: string;
|
||||||
|
chainName?: string;
|
||||||
|
visitTime?: string;
|
||||||
|
province?: string;
|
||||||
|
city?: string;
|
||||||
|
merchantLat?: string | number;
|
||||||
|
merchantLng?: string | number;
|
||||||
|
merchantAddress?: string;
|
||||||
|
lat?: string | number;
|
||||||
|
lng?: string | number;
|
||||||
|
location?: string;
|
||||||
|
photos?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YxeVisitReport {
|
||||||
|
id?: number;
|
||||||
|
userId?: number;
|
||||||
|
storeCount?: number;
|
||||||
|
storeList?: string;
|
||||||
|
image?: string;
|
||||||
|
summary?: string;
|
||||||
|
reward?: number | string;
|
||||||
|
lat?: string;
|
||||||
|
lng?: string;
|
||||||
|
location?: string;
|
||||||
|
sortNumber?: number;
|
||||||
|
comments?: string;
|
||||||
|
status?: number;
|
||||||
|
rejectReason?: string;
|
||||||
|
deleted?: number;
|
||||||
|
tenantId?: number;
|
||||||
|
createTime?: string;
|
||||||
|
updateTime?: string;
|
||||||
|
user?: User;
|
||||||
|
realName?: string;
|
||||||
|
userGroup?: string;
|
||||||
|
nickname?: string;
|
||||||
|
phone?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface YxeVisitReportParam extends PageParam {
|
||||||
|
id?: number;
|
||||||
|
idList?: string;
|
||||||
|
keywords?: string;
|
||||||
|
realName?: string;
|
||||||
|
userGroup?: string;
|
||||||
|
chainName?: string;
|
||||||
|
merchantName?: string;
|
||||||
|
status?: number | number[];
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
@pressEnter="search"
|
@pressEnter="search"
|
||||||
/>
|
/>
|
||||||
<a-input
|
<a-input
|
||||||
|
v-if="reportType !== 'visit'"
|
||||||
v-model:value.trim="where.activityTime"
|
v-model:value.trim="where.activityTime"
|
||||||
allow-clear
|
allow-clear
|
||||||
placeholder="请输入活动时间"
|
placeholder="请输入活动时间"
|
||||||
@@ -72,6 +73,8 @@
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
selection?: [];
|
selection?: [];
|
||||||
|
// 'visit' 时共享字段会误导后端:跑店填报没有 activity_time / chain_id
|
||||||
|
reportType?: 'self' | 'visit';
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<ele-modal
|
||||||
|
title="跑店填报详情"
|
||||||
|
:width="980"
|
||||||
|
:footer="null"
|
||||||
|
:visible="visible"
|
||||||
|
@update:visible="updateVisible"
|
||||||
|
>
|
||||||
|
<a-spin :spinning="detailLoading">
|
||||||
|
<a-space direction="vertical" class="ele-fluid">
|
||||||
|
<a-descriptions bordered :column="{ xs: 1, sm: 2 }">
|
||||||
|
<a-descriptions-item label="ID">
|
||||||
|
{{ formatText(detailData?.id) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="提交人">
|
||||||
|
{{ submitterText }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="巡店门店数">
|
||||||
|
{{ storeRows.length }} 家
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="奖励金额">
|
||||||
|
{{ formatText(detailData?.reward) }}元
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="状态">
|
||||||
|
<a-tag v-if="detailData?.status === 0">待审核</a-tag>
|
||||||
|
<a-tag v-else-if="detailData?.status === 1" color="green">通过</a-tag>
|
||||||
|
<a-tag v-else-if="detailData?.status === 2" color="red">拒绝</a-tag>
|
||||||
|
<span v-else class="ele-text-secondary">-</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="创建时间">
|
||||||
|
{{ formatDateTime(detailData?.createTime) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item v-if="detailData?.status === 2 || detailData?.rejectReason" label="驳回原因" :span="2">
|
||||||
|
<div class="detail-text">{{ formatText(detailData?.rejectReason) }}</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="活动小结" :span="2">
|
||||||
|
<div class="detail-text">{{ formatSummary(detailData?.summary) }}</div>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
|
||||||
|
<a-empty v-if="!storeRows.length" description="暂无巡店记录" />
|
||||||
|
<a-card v-for="(store, index) in storeRows" :key="`store-${index}`" size="small">
|
||||||
|
<template #title>
|
||||||
|
<a-space>
|
||||||
|
<span>巡店{{ index + 1 }}</span>
|
||||||
|
<span class="ele-text-secondary">{{ store.visitTime || '-' }}</span>
|
||||||
|
</a-space>
|
||||||
|
</template>
|
||||||
|
<a-descriptions bordered size="small" :column="{ xs: 1, sm: 2 }">
|
||||||
|
<a-descriptions-item label="连锁">
|
||||||
|
{{ formatText(store.chainName) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="门店">
|
||||||
|
{{ formatText(store.merchantName) }}
|
||||||
|
</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="定位" :span="2">
|
||||||
|
{{ store.location || '-' }}
|
||||||
|
<span v-if="store.lat && store.lng" class="ele-text-secondary">
|
||||||
|
({{ store.lat }}, {{ store.lng }})
|
||||||
|
</span>
|
||||||
|
</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
<a-space v-if="store.photos.length" wrap class="store-photos">
|
||||||
|
<a-image
|
||||||
|
v-for="(photo, photoIndex) in store.photos"
|
||||||
|
:key="`store-${index}-photo-${photoIndex}`"
|
||||||
|
:src="photo"
|
||||||
|
:width="110"
|
||||||
|
/>
|
||||||
|
</a-space>
|
||||||
|
<div v-else class="ele-text-secondary">暂无巡店照片</div>
|
||||||
|
</a-card>
|
||||||
|
</a-space>
|
||||||
|
</a-spin>
|
||||||
|
</ele-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref, watch } from 'vue';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import { toDateString } from 'ele-admin-pro';
|
||||||
|
import { getYxeVisitReport } from '@/api/yxe/yxeVisitReport';
|
||||||
|
import type {
|
||||||
|
YxeVisitReport,
|
||||||
|
YxeVisitReportStore
|
||||||
|
} from '@/api/yxe/yxeVisitReport/model';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
visible: boolean;
|
||||||
|
data?: YxeVisitReport | null;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:visible', value: boolean): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const detailLoading = ref(false);
|
||||||
|
const detailData = ref<YxeVisitReport | null>(null);
|
||||||
|
let detailRequestSeed = 0;
|
||||||
|
|
||||||
|
const formatText = (value: unknown) => {
|
||||||
|
if (value === null || value === undefined) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
const text = String(value).trim();
|
||||||
|
return text ? text : '-';
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateTime = (value?: string) => {
|
||||||
|
return value ? toDateString(value, 'yyyy-MM-dd HH:mm:ss') : '-';
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseJson = <T = any>(value?: string | null): T[] => {
|
||||||
|
const raw = String(value ?? '').trim();
|
||||||
|
if (!raw) return [];
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
return Array.isArray(parsed) ? parsed : [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseImageList = (value?: unknown) => {
|
||||||
|
const list = Array.isArray(value) ? value : [];
|
||||||
|
return list
|
||||||
|
.map((item) =>
|
||||||
|
typeof item === 'string'
|
||||||
|
? item
|
||||||
|
: String((item as any)?.url ?? (item as any)?.path ?? '').trim()
|
||||||
|
)
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const storeRows = computed<YxeVisitReportStore[]>(() =>
|
||||||
|
parseJson<YxeVisitReportStore>(detailData.value?.storeList).map((store) => ({
|
||||||
|
...store,
|
||||||
|
photos: parseImageList(store?.photos)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitterText = computed(() => {
|
||||||
|
const user = detailData.value?.user;
|
||||||
|
const name =
|
||||||
|
user?.realName ||
|
||||||
|
user?.nickname ||
|
||||||
|
detailData.value?.realName ||
|
||||||
|
detailData.value?.nickname;
|
||||||
|
const phone = user?.phone || detailData.value?.phone;
|
||||||
|
if (name && phone) return `${name} (${phone})`;
|
||||||
|
return formatText(name || phone || detailData.value?.userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatSummary = (value?: string | null) => {
|
||||||
|
const raw = String(value ?? '').trim();
|
||||||
|
if (!raw) return '-';
|
||||||
|
try {
|
||||||
|
return JSON.parse(raw) || '-';
|
||||||
|
} catch (error) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateVisible = (value: boolean) => {
|
||||||
|
emit('update:visible', value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetDetailState = () => {
|
||||||
|
detailRequestSeed += 1;
|
||||||
|
detailLoading.value = false;
|
||||||
|
detailData.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDetail = async (row?: YxeVisitReport | null) => {
|
||||||
|
const currentRow = row ?? null;
|
||||||
|
detailData.value = currentRow;
|
||||||
|
if (!currentRow?.id) {
|
||||||
|
detailLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const requestSeed = ++detailRequestSeed;
|
||||||
|
detailLoading.value = true;
|
||||||
|
try {
|
||||||
|
const data = await getYxeVisitReport(currentRow.id);
|
||||||
|
if (requestSeed !== detailRequestSeed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
detailData.value = data;
|
||||||
|
} catch (e: any) {
|
||||||
|
if (requestSeed !== detailRequestSeed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message.error(e.message);
|
||||||
|
detailData.value = currentRow;
|
||||||
|
} finally {
|
||||||
|
if (requestSeed === detailRequestSeed) {
|
||||||
|
detailLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [props.visible, props.data?.id] as const,
|
||||||
|
([visible]) => {
|
||||||
|
if (!visible) {
|
||||||
|
resetDetailState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loadDetail(props.data);
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.detail-text {
|
||||||
|
white-space: pre-line;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-photos {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -14,9 +14,20 @@
|
|||||||
class="sys-org-table"
|
class="sys-org-table"
|
||||||
>
|
>
|
||||||
<template #toolbar>
|
<template #toolbar>
|
||||||
|
<a-space :size="10" style="flex-wrap: wrap; margin-right: 10px">
|
||||||
|
<a-radio-group
|
||||||
|
v-model:value="reportType"
|
||||||
|
button-style="solid"
|
||||||
|
@change="changeReportType"
|
||||||
|
>
|
||||||
|
<a-radio-button value="self">驻店填报</a-radio-button>
|
||||||
|
<a-radio-button value="visit">跑店填报</a-radio-button>
|
||||||
|
</a-radio-group>
|
||||||
|
</a-space>
|
||||||
<search
|
<search
|
||||||
@search="reload"
|
@search="reload"
|
||||||
:selection="selection"
|
:selection="selection"
|
||||||
|
:report-type="reportType"
|
||||||
@remove="removeBatch"
|
@remove="removeBatch"
|
||||||
@export="exportData"
|
@export="exportData"
|
||||||
/>
|
/>
|
||||||
@@ -79,10 +90,17 @@
|
|||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<YxeSelfReportDetail
|
<YxeSelfReportDetail
|
||||||
|
v-if="reportType === 'self'"
|
||||||
v-model:visible="showDetail"
|
v-model:visible="showDetail"
|
||||||
:data="detailCurrent"
|
:data="detailCurrent"
|
||||||
@update:visible="updateDetailVisible"
|
@update:visible="updateDetailVisible"
|
||||||
/>
|
/>
|
||||||
|
<YxeVisitReportDetail
|
||||||
|
v-else
|
||||||
|
v-model:visible="showDetail"
|
||||||
|
:data="visitDetailCurrent"
|
||||||
|
@update:visible="updateDetailVisible"
|
||||||
|
/>
|
||||||
<a-modal v-model:visible="imagePreviewVisible" title="图片查看" :footer="null" width="800px">
|
<a-modal v-model:visible="imagePreviewVisible" title="图片查看" :footer="null" width="800px">
|
||||||
<a-image-preview-group>
|
<a-image-preview-group>
|
||||||
<a-space wrap>
|
<a-space wrap>
|
||||||
@@ -135,6 +153,7 @@
|
|||||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||||
import Search from './components/search.vue';
|
import Search from './components/search.vue';
|
||||||
import YxeSelfReportDetail from './components/yxeSelfReportDetail.vue';
|
import YxeSelfReportDetail from './components/yxeSelfReportDetail.vue';
|
||||||
|
import YxeVisitReportDetail from './components/yxeVisitReportDetail.vue';
|
||||||
import {
|
import {
|
||||||
exportYxeSelfReport,
|
exportYxeSelfReport,
|
||||||
pageYxeSelfReport,
|
pageYxeSelfReport,
|
||||||
@@ -142,16 +161,31 @@
|
|||||||
removeYxeSelfReport,
|
removeYxeSelfReport,
|
||||||
updateYxeSelfReport
|
updateYxeSelfReport
|
||||||
} from '@/api/yxe/yxeSelfReport';
|
} from '@/api/yxe/yxeSelfReport';
|
||||||
|
import {
|
||||||
|
exportYxeVisitReport,
|
||||||
|
pageYxeVisitReport,
|
||||||
|
removeBatchYxeVisitReport,
|
||||||
|
removeYxeVisitReport,
|
||||||
|
updateYxeVisitReport
|
||||||
|
} from '@/api/yxe/yxeVisitReport';
|
||||||
import type {
|
import type {
|
||||||
YxeSelfReport,
|
YxeSelfReport,
|
||||||
YxeSelfReportParam
|
YxeSelfReportParam
|
||||||
} from '@/api/yxe/yxeSelfReport/model';
|
} from '@/api/yxe/yxeSelfReport/model';
|
||||||
|
import type { YxeVisitReport } from '@/api/yxe/yxeVisitReport/model';
|
||||||
import { openExportFile } from '@/utils/export-file';
|
import { openExportFile } from '@/utils/export-file';
|
||||||
|
|
||||||
|
// 驻店填报(yxe_self_report)与跑店填报(yxe_visit_report)是两张表,
|
||||||
|
// 列表用顶部单选切换,各自分页/导出,不做 UNION 混排。
|
||||||
|
const reportType = ref<'self' | 'visit'>('self');
|
||||||
|
// 两个接口返回的字段不同,统一按「至少要有什么」约束,避免铺一层两表并集的大类型
|
||||||
|
type ReportRow = YxeSelfReport | YxeVisitReport;
|
||||||
|
|
||||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||||
const selection = ref<YxeSelfReport[]>([]);
|
const selection = ref<ReportRow[]>([]);
|
||||||
const showDetail = ref(false);
|
const showDetail = ref(false);
|
||||||
const detailCurrent = ref<YxeSelfReport | null>(null);
|
const detailCurrent = ref<YxeSelfReport | null>(null);
|
||||||
|
const visitDetailCurrent = ref<YxeVisitReport | null>(null);
|
||||||
const imagePreviewVisible = ref(false);
|
const imagePreviewVisible = ref(false);
|
||||||
const previewImages = ref<string[]>([]);
|
const previewImages = ref<string[]>([]);
|
||||||
|
|
||||||
@@ -161,19 +195,19 @@
|
|||||||
};
|
};
|
||||||
const rewardModalVisible = ref(false);
|
const rewardModalVisible = ref(false);
|
||||||
const rewardModalLoading = ref(false);
|
const rewardModalLoading = ref(false);
|
||||||
const pendingRewardRow = ref<YxeSelfReport | null>(null);
|
const pendingRewardRow = ref<ReportRow | null>(null);
|
||||||
const rewardForm = reactive<{ reward: number | null }>({
|
const rewardForm = reactive<{ reward: number | null }>({
|
||||||
reward: 0
|
reward: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSubmitterName = (record: YxeSelfReport) =>
|
const getSubmitterName = (record: ReportRow) =>
|
||||||
record.user?.realName ||
|
record.user?.realName ||
|
||||||
record.realName ||
|
record.realName ||
|
||||||
record.user?.nickname ||
|
record.user?.nickname ||
|
||||||
record.nickname ||
|
record.nickname ||
|
||||||
'-';
|
'-';
|
||||||
|
|
||||||
const getSubmitterPhone = (record: YxeSelfReport) =>
|
const getSubmitterPhone = (record: ReportRow) =>
|
||||||
record.user?.phone || record.phone || '-';
|
record.user?.phone || record.phone || '-';
|
||||||
|
|
||||||
const parseJsonArray = (value?: string | null) => {
|
const parseJsonArray = (value?: string | null) => {
|
||||||
@@ -199,6 +233,20 @@
|
|||||||
)
|
)
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
|
|
||||||
|
// 小结按 JSON 字符串存,列表里要解回纯文本,否则会带引号
|
||||||
|
const formatSummaryText = (value?: string | null) => {
|
||||||
|
const raw = String(value ?? '').trim();
|
||||||
|
if (!raw) return '-';
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (parsed === null || parsed === undefined) return '-';
|
||||||
|
const text = String(parsed).trim();
|
||||||
|
return text || '-';
|
||||||
|
} catch (error) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const formatRewardText = (value?: number | string | null) => {
|
const formatRewardText = (value?: number | string | null) => {
|
||||||
if (value === null || value === undefined || value === '') {
|
if (value === null || value === undefined || value === '') {
|
||||||
return '-';
|
return '-';
|
||||||
@@ -220,7 +268,8 @@
|
|||||||
if (filters && filters.status !== void 0) {
|
if (filters && filters.status !== void 0) {
|
||||||
where.status = filters.status;
|
where.status = filters.status;
|
||||||
}
|
}
|
||||||
return pageYxeSelfReport({
|
const request = reportType.value === 'visit' ? pageYxeVisitReport : pageYxeSelfReport;
|
||||||
|
return request({
|
||||||
...where,
|
...where,
|
||||||
...orders,
|
...orders,
|
||||||
page,
|
page,
|
||||||
@@ -228,7 +277,8 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns = ref<ColumnItem[]>([
|
// 两表字段不同,列按当前类型切换
|
||||||
|
const selfColumns: ColumnItem[] = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
dataIndex: 'id',
|
dataIndex: 'id',
|
||||||
@@ -334,7 +384,87 @@
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
hideInSetting: true
|
hideInSetting: true
|
||||||
}
|
}
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
const visitColumns: ColumnItem[] = [
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
dataIndex: 'id',
|
||||||
|
key: 'id',
|
||||||
|
align: 'center',
|
||||||
|
width: 90
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '提交人',
|
||||||
|
key: 'submitter',
|
||||||
|
align: 'center',
|
||||||
|
minWidth: 170
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '巡店门店数',
|
||||||
|
dataIndex: 'storeCount',
|
||||||
|
key: 'storeCount',
|
||||||
|
align: 'center',
|
||||||
|
width: 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '活动小结',
|
||||||
|
dataIndex: 'summary',
|
||||||
|
key: 'summary',
|
||||||
|
align: 'center',
|
||||||
|
minWidth: 200,
|
||||||
|
customRender: ({ text }) => formatSummaryText(text)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '奖金金额',
|
||||||
|
dataIndex: 'reward',
|
||||||
|
key: 'reward',
|
||||||
|
align: 'center',
|
||||||
|
width: 120,
|
||||||
|
customRender: ({ text }) => formatRewardText(text)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
align: 'center',
|
||||||
|
width: 130
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '驳回原因',
|
||||||
|
dataIndex: 'rejectReason',
|
||||||
|
key: 'rejectReason',
|
||||||
|
align: 'center',
|
||||||
|
minWidth: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
key: 'createTime',
|
||||||
|
align: 'center',
|
||||||
|
sorter: true,
|
||||||
|
minWidth: 170,
|
||||||
|
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 140,
|
||||||
|
fixed: 'right',
|
||||||
|
align: 'center',
|
||||||
|
hideInSetting: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = ref<ColumnItem[]>(selfColumns);
|
||||||
|
|
||||||
|
const changeReportType = () => {
|
||||||
|
selection.value = [];
|
||||||
|
detailCurrent.value = null;
|
||||||
|
visitDetailCurrent.value = null;
|
||||||
|
columns.value = reportType.value === 'visit' ? visitColumns : selfColumns;
|
||||||
|
tableRef?.value?.reload({ page: 1 });
|
||||||
|
};
|
||||||
|
|
||||||
const reload = (where?: YxeSelfReportParam) => {
|
const reload = (where?: YxeSelfReportParam) => {
|
||||||
selection.value = [];
|
selection.value = [];
|
||||||
@@ -345,16 +475,23 @@
|
|||||||
showDetail.value = value;
|
showDetail.value = value;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
detailCurrent.value = null;
|
detailCurrent.value = null;
|
||||||
|
visitDetailCurrent.value = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDetail = (row: YxeSelfReport) => {
|
const openDetail = (row: ReportRow) => {
|
||||||
detailCurrent.value = row;
|
if (reportType.value === 'visit') {
|
||||||
|
visitDetailCurrent.value = row as YxeVisitReport;
|
||||||
|
} else {
|
||||||
|
detailCurrent.value = row as YxeSelfReport;
|
||||||
|
}
|
||||||
showDetail.value = true;
|
showDetail.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const remove = (row: YxeSelfReport) => {
|
const remove = (row: ReportRow) => {
|
||||||
removeYxeSelfReport(row.id)
|
const request =
|
||||||
|
reportType.value === 'visit' ? removeYxeVisitReport : removeYxeSelfReport;
|
||||||
|
request(row.id)
|
||||||
.then((msg) => {
|
.then((msg) => {
|
||||||
message.success(msg);
|
message.success(msg);
|
||||||
reload();
|
reload();
|
||||||
@@ -371,13 +508,18 @@
|
|||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '提示',
|
title: '提示',
|
||||||
icon: createVNode(ExclamationCircleOutlined),
|
icon: createVNode(ExclamationCircleOutlined),
|
||||||
content: '确定要删除选中的自主填报记录吗?',
|
content:
|
||||||
|
reportType.value === 'visit'
|
||||||
|
? '确定要删除选中的跑店填报记录吗?'
|
||||||
|
: '确定要删除选中的驻店填报记录吗?',
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
const msg = await removeBatchYxeSelfReport(
|
const request =
|
||||||
selection.value.map((d) => d.id)
|
reportType.value === 'visit'
|
||||||
);
|
? removeBatchYxeVisitReport
|
||||||
|
: removeBatchYxeSelfReport;
|
||||||
|
const msg = await request(selection.value.map((d) => d.id));
|
||||||
message.success(msg);
|
message.success(msg);
|
||||||
reload();
|
reload();
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@@ -387,9 +529,11 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStatus = async (row: YxeSelfReport, reward?: number) => {
|
const updateStatus = async (row: ReportRow, reward?: number) => {
|
||||||
try {
|
try {
|
||||||
const msg = await updateYxeSelfReport({
|
const request =
|
||||||
|
reportType.value === 'visit' ? updateYxeVisitReport : updateYxeSelfReport;
|
||||||
|
const msg = await request({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
status: row.status,
|
status: row.status,
|
||||||
...(reward !== void 0 ? { reward } : {})
|
...(reward !== void 0 ? { reward } : {})
|
||||||
@@ -403,7 +547,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openRewardModal = (row: YxeSelfReport) => {
|
const openRewardModal = (row: ReportRow) => {
|
||||||
pendingRewardRow.value = row;
|
pendingRewardRow.value = row;
|
||||||
const reward =
|
const reward =
|
||||||
row.reward === null || row.reward === undefined || row.reward === ''
|
row.reward === null || row.reward === undefined || row.reward === ''
|
||||||
@@ -446,7 +590,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeStatus = (row: YxeSelfReport) => {
|
const changeStatus = (row: ReportRow) => {
|
||||||
if (row.status !== 1) {
|
if (row.status !== 1) {
|
||||||
updateStatus(row).catch(() => {});
|
updateStatus(row).catch(() => {});
|
||||||
return;
|
return;
|
||||||
@@ -461,7 +605,9 @@
|
|||||||
.map((item) => item.id)
|
.map((item) => item.id)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(',');
|
.join(',');
|
||||||
exportYxeSelfReport({
|
const request =
|
||||||
|
reportType.value === 'visit' ? exportYxeVisitReport : exportYxeSelfReport;
|
||||||
|
request({
|
||||||
...(where || {}),
|
...(where || {}),
|
||||||
...(idList ? { idList } : {}),
|
...(idList ? { idList } : {}),
|
||||||
...(orders || {}),
|
...(orders || {}),
|
||||||
@@ -479,7 +625,7 @@
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const customRow = (record: YxeSelfReport) => ({
|
const customRow = (record: ReportRow) => ({
|
||||||
onDblclick: () => openDetail(record)
|
onDblclick: () => openDetail(record)
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user