Merge remote-tracking branch 'origin/dev' into dev
# Conflicts: # .env.development
This commit is contained in:
@@ -20,7 +20,7 @@
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="LOGO" name="avatar">
|
||||
<a-form-item label="Logo" name="avatar">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
@@ -29,6 +29,9 @@
|
||||
@del="onDeleteItem"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="账号类型" name="type">
|
||||
{{ form.type }}
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序名称" name="websiteName">
|
||||
<a-input
|
||||
allow-clear
|
||||
@@ -64,26 +67,14 @@
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="账号类型" name="type">
|
||||
{{ form.websiteType }}
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序码" name="avatar">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
:data="websiteQrcode"
|
||||
@done="chooseQrcode"
|
||||
@del="onDeleteQrcode"
|
||||
<a-form-item label="SEO关键词" name="keywords">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入SEO关键词"
|
||||
v-model:value="form.keywords"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="SEO关键词" name="keywords">-->
|
||||
<!-- <a-textarea-->
|
||||
<!-- :rows="4"-->
|
||||
<!-- :maxlength="200"-->
|
||||
<!-- placeholder="请输入SEO关键词"-->
|
||||
<!-- v-model:value="form.keywords"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="全局样式" name="style">-->
|
||||
<!-- <a-textarea-->
|
||||
<!-- :rows="4"-->
|
||||
@@ -123,6 +114,7 @@
|
||||
v-model:value="form.statusText"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-divider style="margin-bottom: 24px" />-->
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
@@ -135,9 +127,10 @@ import {addCmsWebsite, updateCmsWebsite} from '@/api/cms/cmsWebsite';
|
||||
import {CmsWebsite} from '@/api/cms/cmsWebsite/model';
|
||||
import {useThemeStore} from '@/store/modules/theme';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {FormInstance} from 'ant-design-vue/es/form';
|
||||
import {FormInstance, type Rule} from 'ant-design-vue/es/form';
|
||||
import {ItemType} from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import {FileRecord} from '@/api/system/file/model';
|
||||
import {checkExistence} from '@/api/cms/cmsDomain';
|
||||
import {updateCmsDomain} from '@/api/cms/cmsDomain';
|
||||
import {updateTenant} from "@/api/system/tenant";
|
||||
|
||||
@@ -203,14 +196,14 @@ const updateVisible = (value: boolean) => {
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序描述',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// comments: [
|
||||
// {
|
||||
// required: true,
|
||||
// type: 'string',
|
||||
// message: '请填写小程序描述',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
keywords: [
|
||||
{
|
||||
required: true,
|
||||
@@ -243,6 +236,31 @@ const rules = reactive({
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// websiteCode: [
|
||||
// {
|
||||
// required: true,
|
||||
// type: 'string',
|
||||
// message: '该域名已被使用',
|
||||
// validator: (_rule: Rule, value: string) => {
|
||||
// return new Promise<void>((resolve, reject) => {
|
||||
// if (!value) {
|
||||
// return reject('请输入二级域名');
|
||||
// }
|
||||
// checkExistence('domain', `${value}.wsdns.cn`)
|
||||
// .then(() => {
|
||||
// if (value === oldDomain.value) {
|
||||
// return resolve();
|
||||
// }
|
||||
// reject('已存在');
|
||||
// })
|
||||
// .catch(() => {
|
||||
// resolve();
|
||||
// });
|
||||
// });
|
||||
// },
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
adminUrl: [
|
||||
{
|
||||
required: true,
|
||||
@@ -286,26 +304,28 @@ const chooseImage = (data: FileRecord) => {
|
||||
form.websiteLogo = data.downloadUrl;
|
||||
};
|
||||
|
||||
const chooseQrcode = (data: FileRecord) => {
|
||||
websiteQrcode.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.websiteDarkLogo = data.downloadUrl;
|
||||
}
|
||||
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.websiteLogo = '';
|
||||
};
|
||||
|
||||
const onDeleteQrcode = (index: number) => {
|
||||
websiteQrcode.value.splice(index, 1);
|
||||
form.websiteDarkLogo = '';
|
||||
const chooseFile = (data: FileRecord) => {
|
||||
form.websiteCode = data.url;
|
||||
files.value.push({
|
||||
uid: data.id,
|
||||
url: data.url,
|
||||
status: 'done'
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteFile = (index: number) => {
|
||||
files.value.splice(index, 1);
|
||||
};
|
||||
|
||||
// const onWebsiteType = (text: string) => {
|
||||
// form.websiteType = text;
|
||||
// };
|
||||
|
||||
const {resetFields} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
@@ -374,13 +394,6 @@ watch(
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
if (props.data.websiteDarkLogo) {
|
||||
websiteQrcode.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.websiteDarkLogo,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
if (props.data.files) {
|
||||
files.value = JSON.parse(props.data.files);
|
||||
}
|
||||
295
src/views/shop/dashboard/index.vue
Normal file
295
src/views/shop/dashboard/index.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<a-page-header :show-back="false">
|
||||
<a-row :gutter="16">
|
||||
<!-- 应用基本信息卡片 -->
|
||||
<a-col :span="24" style="margin-bottom: 16px">
|
||||
<a-card title="概况" :bordered="false">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-image
|
||||
:width="80"
|
||||
:height="80"
|
||||
:preview="false"
|
||||
style="border-radius: 8px"
|
||||
:src="siteStore.logo"
|
||||
fallback="/logo.png"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="14">
|
||||
<div class="system-info">
|
||||
<h2 class="ele-text-heading cursor-pointer" @click="$router.push('/website/index')">{{ siteStore.appName }}</h2>
|
||||
<p class="ele-text-secondary">{{ siteStore.description }}</p>
|
||||
<a-space>
|
||||
<a-tag color="green">{{ siteStore.statusText }}</a-tag>
|
||||
<a-tag color="blue" v-if="siteStore.version">{{ siteStore.version }}</a-tag>
|
||||
<a-popover title="小程序码" v-if="siteStore.mpQrCode">
|
||||
<template #content>
|
||||
<p><img :src="siteStore.mpQrCode" alt="小程序码" width="300" height="300"></p>
|
||||
</template>
|
||||
<a-tag>
|
||||
<QrcodeOutlined/>
|
||||
</a-tag>
|
||||
</a-popover>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col :span="3">
|
||||
<div class="flex justify-center items-center h-full w-full">
|
||||
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<!-- 统计数据卡片 -->
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="用户总数"
|
||||
:value="userCount"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="订单总数"
|
||||
:value="orderCount"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<AccountBookOutlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="总营业额"
|
||||
:value="totalSales"
|
||||
:value-style="{ color: '#cf1322' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<MoneyCollectOutlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="6">
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="系统运行天数"
|
||||
:value="runDays"
|
||||
suffix="天"
|
||||
:value-style="{ color: '#722ed1' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<ClockCircleOutlined/>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<!-- 系统基本信息 -->
|
||||
<a-col :span="12">
|
||||
<a-card title="基本信息" :bordered="false">
|
||||
<a-descriptions :column="1" size="small">
|
||||
<a-descriptions-item label="系统名称">
|
||||
{{ systemInfo.name }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="版本号">
|
||||
{{ systemInfo.version }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="部署环境">
|
||||
{{ systemInfo.environment }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="数据库">
|
||||
{{ systemInfo.database }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="服务器">
|
||||
{{ systemInfo.server }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="创建时间">
|
||||
{{ siteInfo?.createTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="到期时间">
|
||||
{{ siteInfo?.expirationTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="技术支持">
|
||||
<span class="cursor-pointer" @click="openNew(`https://websoft.top`)">网宿软件</span>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
||||
<!-- 快捷操作 -->
|
||||
<a-col :span="12">
|
||||
<a-card title="快捷操作" :bordered="false" style="min-height: 353px">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-button type="primary" block @click="$router.push('/website/field')" :loading="loading">
|
||||
<UngroupOutlined/>
|
||||
参数配置
|
||||
</a-button>
|
||||
<a-button block @click="$router.push('/shopOrder')">
|
||||
<CalendarOutlined/>
|
||||
订单管理
|
||||
</a-button>
|
||||
<a-button block @click="$router.push('/system/user')">
|
||||
<UserOutlined/>
|
||||
用户管理
|
||||
</a-button>
|
||||
<a-button block @click="$router.push('/website/index')">
|
||||
<ShopOutlined/>
|
||||
站点管理
|
||||
</a-button>
|
||||
<!-- <a-button block @click="refreshStatistics" :loading="loading">-->
|
||||
<!-- <ReloadOutlined/>-->
|
||||
<!-- 刷新统计-->
|
||||
<!-- </a-button>-->
|
||||
<a-button block @click="$router.push('/system/login-record')">
|
||||
<FileTextOutlined/>
|
||||
登录日志
|
||||
</a-button>
|
||||
<a-button block @click="clearSiteInfoCache">
|
||||
<ClearOutlined/>
|
||||
清除缓存
|
||||
</a-button>
|
||||
<!-- <a-button block @click="$router.push('/system/setting')">-->
|
||||
<!-- <SettingOutlined/>-->
|
||||
<!-- 系统设置-->
|
||||
<!-- </a-button>-->
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted, onUnmounted, computed} from 'vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
CalendarOutlined,
|
||||
QrcodeOutlined,
|
||||
ShopOutlined,
|
||||
ClockCircleOutlined,
|
||||
AccountBookOutlined,
|
||||
FileTextOutlined,
|
||||
ClearOutlined,
|
||||
UngroupOutlined,
|
||||
MoneyCollectOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import {message} from 'ant-design-vue/es';
|
||||
import {openNew} from "@/utils/common";
|
||||
import {useSiteStore} from '@/store/modules/site';
|
||||
import {useStatisticsStore} from '@/store/modules/statistics';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {removeSiteInfoCache} from "@/api/cms/cmsWebsite";
|
||||
|
||||
// 使用状态管理
|
||||
const siteStore = useSiteStore();
|
||||
const statisticsStore = useStatisticsStore();
|
||||
|
||||
// 从 store 中获取响应式数据
|
||||
const {siteInfo, loading: siteLoading} = storeToRefs(siteStore);
|
||||
const {loading: statisticsLoading} = storeToRefs(statisticsStore);
|
||||
|
||||
// 系统信息
|
||||
const systemInfo = ref({
|
||||
name: '小程序开发',
|
||||
description: '基于Spring、SpringBoot、SpringMVC等技术栈构建的前后端分离开发平台',
|
||||
version: '2.0.0',
|
||||
status: '运行中',
|
||||
logo: '/logo.png',
|
||||
environment: '生产环境',
|
||||
database: 'MySQL 8.0',
|
||||
server: 'Linux CentOS 7.9',
|
||||
expirationTime: '2024-01-01 09:00:00'
|
||||
});
|
||||
|
||||
// 计算属性
|
||||
const runDays = computed(() => siteStore.runDays);
|
||||
const userCount = computed(() => statisticsStore.userCount);
|
||||
const orderCount = computed(() => statisticsStore.orderCount);
|
||||
const totalSales = computed(() => statisticsStore.totalSales);
|
||||
|
||||
// 加载状态
|
||||
const loading = computed(() => siteLoading.value || statisticsLoading.value);
|
||||
|
||||
// 清除缓存
|
||||
const clearSiteInfoCache = () => {
|
||||
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
|
||||
(msg) => {
|
||||
if (msg) {
|
||||
message.success(msg);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// 刷新统计数据
|
||||
const refreshStatistics = async () => {
|
||||
try {
|
||||
await statisticsStore.forceRefresh();
|
||||
message.success('统计数据刷新成功');
|
||||
} catch (error) {
|
||||
console.error('刷新统计数据失败:', error);
|
||||
message.error('刷新统计数据失败');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载网站信息和统计数据
|
||||
try {
|
||||
await Promise.all([
|
||||
siteStore.fetchSiteInfo(),
|
||||
statisticsStore.fetchStatistics()
|
||||
]);
|
||||
|
||||
// 开始自动刷新统计数据(每5分钟)
|
||||
statisticsStore.startAutoRefresh();
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 组件卸载时停止自动刷新
|
||||
statisticsStore.stopAutoRefresh();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.system-info h2 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
text-align: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.stat-card :deep(.ant-statistic-title) {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stat-card :deep(.ant-statistic-content) {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -1,430 +0,0 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-spin :spinning="loading" class="page">
|
||||
<a-card>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="LOGO" name="avatar">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
:data="images"
|
||||
@done="chooseImage"
|
||||
@del="onDeleteItem"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序名称" name="websiteName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入小程序名称"
|
||||
v-model:value="form.websiteName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="网站域名" name="domain" v-if="form.type == 10">
|
||||
<a-input
|
||||
v-model:value="form.domain"
|
||||
placeholder="huawei.com"
|
||||
>
|
||||
<template #addonBefore>
|
||||
<a-select v-model:value="form.prefix" style="width: 90px">
|
||||
<a-select-option value="http://">http://</a-select-option>
|
||||
<a-select-option value="https://">https://</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item label="AppId" name="websiteCode" v-if="form.type == 20">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入AppId"
|
||||
v-model:value="form.websiteCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序描述" name="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入小程序描述"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="账号类型" name="type">
|
||||
{{ form.websiteType }}
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序码" name="avatar">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="1"
|
||||
:data="websiteQrcode"
|
||||
@done="chooseQrcode"
|
||||
@del="onDeleteQrcode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="SEO关键词" name="keywords">-->
|
||||
<!-- <a-textarea-->
|
||||
<!-- :rows="4"-->
|
||||
<!-- :maxlength="200"-->
|
||||
<!-- placeholder="请输入SEO关键词"-->
|
||||
<!-- v-model:value="form.keywords"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="全局样式" name="style">-->
|
||||
<!-- <a-textarea-->
|
||||
<!-- :rows="4"-->
|
||||
<!-- :maxlength="200"-->
|
||||
<!-- placeholder="全局样式"-->
|
||||
<!-- v-model:value="form.style"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="小程序类型" name="websiteType">-->
|
||||
<!-- <a-select-->
|
||||
<!-- :options="websiteType"-->
|
||||
<!-- :value="form.websiteType"-->
|
||||
<!-- placeholder="请选择主体类型"-->
|
||||
<!-- @change="onCmsWebsiteType"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="当前版本" name="version">-->
|
||||
<!-- <a-tag color="red" v-if="form.version === 10">标准版</a-tag>-->
|
||||
<!-- <a-tag color="green" v-if="form.version === 20">专业版</a-tag>-->
|
||||
<!-- <a-tag color="cyan" v-if="form.version === 30">永久授权</a-tag>-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="状态" name="running">
|
||||
<a-radio-group
|
||||
v-model:value="form.running"
|
||||
:disabled="form.running == 4 || form.running == 5"
|
||||
>
|
||||
<a-radio :value="1">运行中</a-radio>
|
||||
<a-radio :value="2">维护中</a-radio>
|
||||
<a-radio :value="3">已关闭</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item v-if="form.running == 2" label="维护说明" name="statusText">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="状态说明"
|
||||
v-model:value="form.statusText"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import {assignObject, uuid} from 'ele-admin-pro';
|
||||
import {addCmsWebsite, updateCmsWebsite} from '@/api/cms/cmsWebsite';
|
||||
import {CmsWebsite} from '@/api/cms/cmsWebsite/model';
|
||||
import {useThemeStore} from '@/store/modules/theme';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {FormInstance} from 'ant-design-vue/es/form';
|
||||
import {ItemType} from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import {FileRecord} from '@/api/system/file/model';
|
||||
import {updateCmsDomain} from '@/api/cms/cmsDomain';
|
||||
import {updateTenant} from "@/api/system/tenant";
|
||||
import {getPageTitle, push} from "@/utils/common";
|
||||
import router from "@/router";
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import useFormData from "@/utils/use-form-data";
|
||||
import type {User} from "@/api/system/user/model";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const {styleResponsive} = storeToRefs(themeStore);
|
||||
const siteStore = useSiteStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const siteInfo = ref<CmsWebsite>({})
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
const websiteQrcode = ref<ItemType[]>([]);
|
||||
const files = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const {form, assignFields} = useFormData<CmsWebsite>({
|
||||
websiteId: undefined,
|
||||
websiteLogo: undefined,
|
||||
websiteName: undefined,
|
||||
websiteCode: undefined,
|
||||
type: 20,
|
||||
files: undefined,
|
||||
keywords: '',
|
||||
prefix: '',
|
||||
domain: '',
|
||||
adminUrl: '',
|
||||
style: '',
|
||||
icpNo: undefined,
|
||||
email: undefined,
|
||||
version: undefined,
|
||||
websiteType: '',
|
||||
running: 1,
|
||||
expirationTime: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: undefined,
|
||||
status: undefined,
|
||||
statusText: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序描述',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
keywords: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写SEO关键词',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
running: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请选择小程序状态',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
domain: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序域名',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
websiteCode: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序码',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
adminUrl: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序后台管理地址',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
icpNo: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写ICP备案号',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
appSecret: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序秘钥',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
websiteName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写小程序信息名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.websiteLogo = data.downloadUrl;
|
||||
};
|
||||
|
||||
const chooseQrcode = (data: FileRecord) => {
|
||||
websiteQrcode.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.websiteDarkLogo = data.downloadUrl;
|
||||
}
|
||||
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.websiteLogo = '';
|
||||
};
|
||||
|
||||
const onDeleteQrcode = (index: number) => {
|
||||
websiteQrcode.value.splice(index, 1);
|
||||
form.websiteDarkLogo = '';
|
||||
};
|
||||
|
||||
const {resetFields} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateCmsWebsite : addCmsWebsite;
|
||||
if (!isUpdate.value) {
|
||||
updateVisible(false);
|
||||
message.loading('创建过程中请勿刷新页面!', 0)
|
||||
}
|
||||
const formData = {
|
||||
...form,
|
||||
type: 20,
|
||||
adminUrl: `mp.websoft.top`,
|
||||
files: JSON.stringify(files.value),
|
||||
};
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
updateVisible(false);
|
||||
updateCmsDomain({
|
||||
websiteId: form.websiteId,
|
||||
domain: `${localStorage.getItem('TenantId')}.shoplnk.cn`
|
||||
});
|
||||
updateTenant({
|
||||
tenantName: `${form.websiteName}`
|
||||
}).then(() => {
|
||||
})
|
||||
localStorage.setItem('Domain', `${form.websiteCode}.shoplnk.cn`);
|
||||
localStorage.setItem('WebsiteId', `${form.websiteId}`);
|
||||
localStorage.setItem('WebsiteName', `${form.websiteName}`);
|
||||
message.destroy();
|
||||
message.success(msg);
|
||||
// window.location.reload();
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.destroy();
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
try {
|
||||
const data = await siteStore.fetchSiteInfo();
|
||||
if (data) {
|
||||
console.log(data);
|
||||
assignFields({
|
||||
...data
|
||||
});
|
||||
if (data.websiteLogo) {
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: data.websiteLogo,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取网站信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value.query,
|
||||
(query) => {
|
||||
if (query) {
|
||||
reload();
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
// watch(
|
||||
// () => props.visible,
|
||||
// (visible) => {
|
||||
// if (visible) {
|
||||
// images.value = [];
|
||||
// files.value = [];
|
||||
// websiteQrcode.value = [];
|
||||
// if (props.data?.websiteId) {
|
||||
// assignObject(form, props.data);
|
||||
// if (props.data.websiteLogo) {
|
||||
// images.value.push({
|
||||
// uid: uuid(),
|
||||
// url: props.data.websiteLogo,
|
||||
// status: 'done'
|
||||
// });
|
||||
// }
|
||||
// if (props.data.websiteDarkLogo) {
|
||||
// websiteQrcode.value.push({
|
||||
// uid: uuid(),
|
||||
// url: props.data.websiteDarkLogo,
|
||||
// status: 'done'
|
||||
// });
|
||||
// }
|
||||
// if (props.data.files) {
|
||||
// files.value = JSON.parse(props.data.files);
|
||||
// }
|
||||
// if (props.data.websiteCode) {
|
||||
// oldDomain.value = props.data.websiteCode;
|
||||
// }
|
||||
// isUpdate.value = true;
|
||||
// } else {
|
||||
// isUpdate.value = false;
|
||||
// }
|
||||
// } else {
|
||||
// resetFields();
|
||||
// }
|
||||
// },
|
||||
// {immediate: true}
|
||||
// );
|
||||
</script>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'CmsWebsite'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<a-card title="项目成员" style="margin-bottom: 20px">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button>编辑</a-button>
|
||||
<a-button type="primary">添加</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-for="(item,_) in list" class="bg-gray-50 rounded-lg w-80 p-4 flex justify-between items-center">
|
||||
<a-space>
|
||||
<a-avatar size="large" :src="item.avatar"/>
|
||||
<div class="text-gray-400 flex flex-col">
|
||||
<span>{{ item.nickname }}</span>
|
||||
<span>{{ item.createTime }}</span>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, onMounted} from 'vue';
|
||||
import {
|
||||
listUsers
|
||||
} from '@/api/system/user';
|
||||
import {User} from "@/api/system/user/model";
|
||||
|
||||
const list = ref<User[]>([]);
|
||||
|
||||
const reload = async () => {
|
||||
const data = await listUsers({
|
||||
isAdmin: 1
|
||||
});
|
||||
if (data.length > 0) {
|
||||
list.value = data;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
reload();
|
||||
})
|
||||
</script>
|
||||
293
src/views/shop/shopAdmin/components/invitation-modal.vue
Normal file
293
src/views/shop/shopAdmin/components/invitation-modal.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="500"
|
||||
:visible="visible"
|
||||
:footer="null"
|
||||
title="邀请注册"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<div style="text-align: center">
|
||||
<div style="margin-bottom: 20px">
|
||||
<a-typography-title :level="4">邀请新成员注册</a-typography-title>
|
||||
<a-typography-text type="secondary">
|
||||
分享以下链接或二维码,邀请新管理人员注册(非普通用户)
|
||||
</a-typography-text>
|
||||
</div>
|
||||
|
||||
<!-- 邀请链接 -->
|
||||
<div style="margin-bottom: 20px">
|
||||
<a-input
|
||||
:value="invitationLink"
|
||||
readonly
|
||||
style="margin-bottom: 8px"
|
||||
>
|
||||
<template #addonAfter>
|
||||
<a-button type="link" size="small" @click="copyLink">
|
||||
复制链接
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
|
||||
<!-- 二维码选择 -->
|
||||
<div style="margin-bottom: 16px">
|
||||
<a-radio-group v-model:value="qrCodeType" @change="onQRCodeTypeChange">
|
||||
<a-radio-button value="web">网页二维码</a-radio-button>
|
||||
<a-radio-button value="miniprogram">小程序码</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<!-- 二维码显示 -->
|
||||
<div style="margin-bottom: 20px">
|
||||
<div style="display: inline-block; padding: 10px; background: white; border-radius: 4px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)">
|
||||
<!-- 网页二维码 -->
|
||||
<ele-qr-code-svg
|
||||
v-if="qrCodeType === 'web'"
|
||||
:value="invitationLink"
|
||||
:size="200"
|
||||
/>
|
||||
<!-- 小程序码 -->
|
||||
<div v-else-if="qrCodeType === 'miniprogram'" style="width: 200px; height: 200px; display: flex; align-items: center; justify-content: center;">
|
||||
<img
|
||||
v-if="miniProgramCodeUrl"
|
||||
:src="miniProgramCodeUrl"
|
||||
style="width: 180px; height: 180px; object-fit: contain;"
|
||||
alt="小程序码"
|
||||
@error="onMiniProgramCodeError"
|
||||
@load="onMiniProgramCodeLoad"
|
||||
/>
|
||||
<a-spin v-else-if="loadingMiniCode" tip="正在生成小程序码..." />
|
||||
<div v-else style="color: #999; text-align: center;">
|
||||
<div>小程序码加载失败</div>
|
||||
<a-button size="small" @click="loadMiniProgramCode">重新加载</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 使用说明 -->
|
||||
<div style="text-align: left; background: #f5f5f5; padding: 12px; border-radius: 4px; margin-bottom: 20px">
|
||||
<div style="font-weight: 500; margin-bottom: 8px">使用说明:</div>
|
||||
<div style="font-size: 12px; color: #666; line-height: 1.5">
|
||||
<template v-if="qrCodeType === 'web'">
|
||||
1. 复制邀请链接发送给用户,或让用户扫描网页二维码<br>
|
||||
2. 用户点击链接或扫码进入注册页面<br>
|
||||
3. 用户完成注册后,系统自动建立推荐关系<br>
|
||||
4. 您可以在"推荐关系管理"中查看邀请结果
|
||||
</template>
|
||||
<template v-else>
|
||||
1. 让用户扫描小程序码进入小程序<br>
|
||||
2. 小程序会自动识别邀请信息<br>
|
||||
3. 用户在小程序内完成注册后,系统自动建立推荐关系<br>
|
||||
4. 您可以在"推荐关系管理"中查看邀请结果
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 调试信息 -->
|
||||
<div v-if="showDebugInfo" style="margin-bottom: 16px; padding: 8px; background: #f0f0f0; border-radius: 4px; font-size: 12px;">
|
||||
<div><strong>调试信息:</strong></div>
|
||||
<div>邀请人ID: {{ inviterId }}</div>
|
||||
<div>邀请链接: {{ invitationLink }}</div>
|
||||
<div v-if="qrCodeType === 'miniprogram'">小程序码URL: {{ miniProgramCodeUrl }}</div>
|
||||
<div>BaseUrl: {{ baseUrl }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div>
|
||||
<a-space>
|
||||
<a-button @click="downloadQRCode">下载二维码</a-button>
|
||||
<a-button type="primary" @click="copyLink">复制链接</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { generateInviteCode } from '@/api/miniprogram';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
inviterId?: number; // 邀请人ID(当前登录用户ID)
|
||||
}>();
|
||||
|
||||
// 二维码类型
|
||||
const qrCodeType = ref<'web' | 'miniprogram'>('web');
|
||||
// 小程序码URL
|
||||
const miniProgramCodeUrl = ref<string>('');
|
||||
// 小程序码加载状态
|
||||
const loadingMiniCode = ref(false);
|
||||
// 显示调试信息
|
||||
const showDebugInfo = ref(false);
|
||||
// 基础URL(用于调试)
|
||||
const baseUrl = ref('');
|
||||
|
||||
// 获取邀请人ID
|
||||
const inviterId = computed(() => {
|
||||
return props.inviterId || Number(localStorage.getItem('UserId'));
|
||||
});
|
||||
|
||||
// 生成邀请链接
|
||||
const invitationLink = computed(() => {
|
||||
const baseUrl = window.location.origin;
|
||||
return `${baseUrl}/dealer/register?inviter=${inviterId.value}`;
|
||||
});
|
||||
|
||||
// 复制链接
|
||||
const copyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(invitationLink.value);
|
||||
message.success('邀请链接已复制到剪贴板');
|
||||
} catch (e) {
|
||||
// 降级方案
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = invitationLink.value;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textArea);
|
||||
message.success('邀请链接已复制到剪贴板');
|
||||
}
|
||||
};
|
||||
|
||||
// 加载小程序码
|
||||
const loadMiniProgramCode = async () => {
|
||||
const currentInviterId = inviterId.value;
|
||||
if (!currentInviterId) {
|
||||
console.error('邀请人ID不存在');
|
||||
message.error('邀请人ID不存在');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('开始加载小程序码,邀请人ID:', currentInviterId);
|
||||
loadingMiniCode.value = true;
|
||||
|
||||
try {
|
||||
const codeUrl = await generateInviteCode(currentInviterId);
|
||||
console.log('小程序码生成成功:', codeUrl);
|
||||
miniProgramCodeUrl.value = codeUrl;
|
||||
message.success('小程序码加载成功');
|
||||
} catch (e: any) {
|
||||
console.error('加载小程序码失败:', e);
|
||||
message.error(`小程序码加载失败: ${e.message}`);
|
||||
} finally {
|
||||
loadingMiniCode.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 小程序码加载错误
|
||||
const onMiniProgramCodeError = () => {
|
||||
console.error('小程序码图片加载失败');
|
||||
message.error('小程序码显示失败');
|
||||
};
|
||||
|
||||
// 小程序码加载成功
|
||||
const onMiniProgramCodeLoad = () => {
|
||||
console.log('小程序码图片加载成功');
|
||||
};
|
||||
|
||||
// 二维码类型切换
|
||||
const onQRCodeTypeChange = () => {
|
||||
if (qrCodeType.value === 'miniprogram' && !miniProgramCodeUrl.value) {
|
||||
loadMiniProgramCode();
|
||||
}
|
||||
};
|
||||
|
||||
// 下载二维码
|
||||
const downloadQRCode = () => {
|
||||
try {
|
||||
if (qrCodeType.value === 'web') {
|
||||
// 下载网页二维码 - 查找SVG元素
|
||||
const svgElement = document.querySelector('.ant-modal-body svg') as SVGElement;
|
||||
if (svgElement) {
|
||||
// 将SVG转换为Canvas再下载
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
|
||||
// 获取SVG的XML字符串
|
||||
const svgData = new XMLSerializer().serializeToString(svgElement);
|
||||
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const url = URL.createObjectURL(svgBlob);
|
||||
|
||||
img.onload = () => {
|
||||
canvas.width = img.width || 200;
|
||||
canvas.height = img.height || 200;
|
||||
ctx?.drawImage(img, 0, 0);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.download = `邀请注册二维码.png`;
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
message.success('二维码已下载');
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
URL.revokeObjectURL(url);
|
||||
message.error('二维码下载失败');
|
||||
};
|
||||
|
||||
img.src = url;
|
||||
} else {
|
||||
message.error('未找到二维码,请稍后重试');
|
||||
}
|
||||
} else {
|
||||
// 下载小程序码
|
||||
if (miniProgramCodeUrl.value) {
|
||||
const link = document.createElement('a');
|
||||
link.download = `邀请小程序码.png`;
|
||||
link.href = miniProgramCodeUrl.value;
|
||||
link.target = '_blank';
|
||||
link.click();
|
||||
message.success('小程序码已下载');
|
||||
} else {
|
||||
message.error('小程序码未加载');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('下载失败:', e);
|
||||
message.error('下载失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 更新visible
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(() => props.visible, (visible) => {
|
||||
if (visible) {
|
||||
// 重置状态
|
||||
qrCodeType.value = 'web';
|
||||
miniProgramCodeUrl.value = '';
|
||||
loadingMiniCode.value = false;
|
||||
showDebugInfo.value = false;
|
||||
|
||||
// 获取调试信息
|
||||
import('@/config/setting').then(({ SERVER_API_URL }) => {
|
||||
baseUrl.value = SERVER_API_URL;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-typography-title) {
|
||||
margin-bottom: 8px !important;
|
||||
}
|
||||
|
||||
:deep(.ant-input-group-addon) {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,71 +0,0 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
allow-clear
|
||||
mode="multiple"
|
||||
:value="roleIds"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="item in data"
|
||||
:key="item.roleId"
|
||||
:value="item.roleId"
|
||||
>
|
||||
{{ item.roleName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { listRoles } from '@/api/system/role';
|
||||
import type { Role } from '@/api/system/role/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: Role[]): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
value?: Role[];
|
||||
//
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择角色'
|
||||
}
|
||||
);
|
||||
|
||||
// 选中的角色id
|
||||
const roleIds = computed(() => props.value?.map((d) => d.roleId as number));
|
||||
|
||||
// 角色数据
|
||||
const data = ref<Role[]>([]);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: number[]) => {
|
||||
emit(
|
||||
'update:value',
|
||||
value.map((v) => ({ roleId: v }))
|
||||
);
|
||||
};
|
||||
|
||||
/* 获取角色数据 */
|
||||
listRoles()
|
||||
.then((list) => {
|
||||
data.value = list;
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<a-table :dataSource="dataSource" :columns="columns" :pagination="false">
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="flex">
|
||||
<span class="w-32">{{ record.name }}</span>
|
||||
<span class="w-32">{{ record.value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="record.key === '2'">
|
||||
<a-button>重置</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
return {
|
||||
columns: [
|
||||
{
|
||||
title: '开发者ID',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 240,
|
||||
},
|
||||
],
|
||||
dataSource: [
|
||||
{
|
||||
key: '1',
|
||||
name: '租户ID',
|
||||
value: '10550'
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: 'AppSecret',
|
||||
value: 'sdfsdfsdfsdfs'
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,45 +0,0 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择性别'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions('sex');
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
@@ -4,7 +4,7 @@
|
||||
:width="500"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑员工' : '添加员工'"
|
||||
:title="isUpdate ? '编辑项目成员' : '添加项目成员'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -22,7 +22,6 @@
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
:disabled="isUpdate"
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
@@ -36,9 +35,6 @@
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色" name="roles">
|
||||
<role-select v-model:value="form.roles" />
|
||||
</a-form-item>
|
||||
<a-form-item v-if="!isUpdate" label="登录密码" name="password">
|
||||
<a-input-password
|
||||
:maxlength="20"
|
||||
@@ -46,22 +42,22 @@
|
||||
placeholder="请输入登录密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别" name="sex">
|
||||
<DictSelect
|
||||
dict-code="sex"
|
||||
:placeholder="`请选择性别`"
|
||||
v-model:value="form.sexName"
|
||||
@done="chooseSex"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入邮箱"
|
||||
v-model:value="form.email"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="性别" name="sex">-->
|
||||
<!-- <DictSelect-->
|
||||
<!-- dict-code="sex"-->
|
||||
<!-- :placeholder="`请选择性别`"-->
|
||||
<!-- v-model:value="form.sexName"-->
|
||||
<!-- @done="chooseSex"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- <a-form-item label="邮箱" name="email">-->
|
||||
<!-- <a-input-->
|
||||
<!-- allow-clear-->
|
||||
<!-- :maxlength="100"-->
|
||||
<!-- placeholder="请输入邮箱"-->
|
||||
<!-- v-model:value="form.email"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="所属机构" name="type">
|
||||
<org-select
|
||||
:data="organizationList"
|
||||
@@ -81,13 +77,10 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import RoleSelect from './role-select.vue';
|
||||
import { addUser, updateUser, checkExistence } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import OrgSelect from './org-select.vue';
|
||||
// import { getDictionaryOptions } from '@/utils/common';
|
||||
import { Organization } from '@/api/system/organization/model';
|
||||
import { Grade } from '@/api/user/grade/model';
|
||||
import {TEMPLATE_ID} from "@/config/setting";
|
||||
|
||||
// 是否开启响应式布局
|
||||
@@ -183,14 +176,6 @@
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// sex: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请选择性别',
|
||||
// type: 'string',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
roles: [
|
||||
{
|
||||
required: true,
|
||||
@@ -231,20 +216,11 @@
|
||||
]
|
||||
});
|
||||
|
||||
const chooseGradeId = (data: Grade) => {
|
||||
form.gradeName = data.name;
|
||||
form.gradeId = data.gradeId;
|
||||
};
|
||||
|
||||
const chooseSex = (data: any) => {
|
||||
form.sex = data.key;
|
||||
form.sexName = data.label;
|
||||
};
|
||||
|
||||
const updateIsAdmin = (value: boolean) => {
|
||||
form.isAdmin = value;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="680"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="'基本信息'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
:footer="null"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 6 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 19 }, sm: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="账号">
|
||||
<span class="ele-text">{{ user.username }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称">
|
||||
<span class="ele-text">{{ user.nickname }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="性别">
|
||||
<span class="ele-text">{{ user.sexName }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号">
|
||||
<span class="ele-text">{{ user.phone }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色">
|
||||
<a-tag v-for="item in user.roles" :key="item.roleId" color="blue">
|
||||
{{ item.roleName }}
|
||||
</a-tag>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-badge
|
||||
v-if="typeof user.status === 'number'"
|
||||
:status="(['processing', 'error'][user.status] as any)"
|
||||
:text="['正常', '冻结'][user.status]"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="地址">
|
||||
<span class="ele-text">{{ user.address }}</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { md: 12, sm: 24, xs: 24 } : { span: 12 }"
|
||||
>
|
||||
<a-form-item label="可用余额">
|
||||
<span class="ele-text-success">¥{{ formatNumber(user.balance) }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="可用积分">
|
||||
<span class="ele-text">{{ user.points }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="实际消费">
|
||||
<span class="ele-text">{{ user.payMoney }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="机构/部门">
|
||||
<span class="ele-text">{{ user.organizationName }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="头像">
|
||||
<a-image :src="user.avatar" :width="36" />
|
||||
</a-form-item>
|
||||
<a-form-item label="生日">
|
||||
<span class="ele-text">{{ user.birthday }}</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="创建时间">
|
||||
<span class="ele-text">{{ user.createTime }}</span>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { formatNumber } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 用户信息
|
||||
const user = reactive<User>({
|
||||
username: '',
|
||||
nickname: '',
|
||||
sexName: '',
|
||||
phone: '',
|
||||
avatar: '',
|
||||
balance: undefined,
|
||||
points: 0,
|
||||
payMoney: 0,
|
||||
birthday: '',
|
||||
address: '',
|
||||
roles: [],
|
||||
createTime: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
// 请求状态
|
||||
const loading = ref(true);
|
||||
|
||||
const { resetFields } = useForm(user);
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
assignObject(user, props.data);
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -1,111 +0,0 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-form
|
||||
:label-col="
|
||||
styleResponsive ? { xl: 7, lg: 5, md: 7, sm: 4 } : { flex: '90px' }
|
||||
"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { xl: 17, lg: 19, md: 17, sm: 20 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户账号">
|
||||
<a-input
|
||||
v-model:value.trim="form.username"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="昵称">
|
||||
<a-input
|
||||
v-model:value.trim="form.nickname"
|
||||
placeholder="请输入"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item label="性别">
|
||||
<a-select v-model:value="form.sex" placeholder="请选择" allow-clear>
|
||||
<a-select-option value="1">男</a-select-option>
|
||||
<a-select-option value="2">女</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 24, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-form-item class="ele-text-right" :wrapper-col="{ span: 24 }">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { UserParam } from '@/api/system/user/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 默认搜索条件
|
||||
where?: UserParam;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: UserParam): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData<UserParam>({
|
||||
username: '',
|
||||
nickname: '',
|
||||
sex: undefined,
|
||||
...props.where
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', form);
|
||||
};
|
||||
|
||||
/* 重置 */
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
</script>
|
||||
@@ -16,11 +16,11 @@
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openInvitation">
|
||||
<template #icon>
|
||||
<plus-outlined/>
|
||||
</template>
|
||||
<span>添加</span>
|
||||
<span>邀请注册</span>
|
||||
</a-button>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
@@ -43,12 +43,12 @@
|
||||
</template>
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<span>{{ record.nickname }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'mobile'">
|
||||
<span v-if="hasRole('superAdmin')">{{ record.phone }}</span>
|
||||
<span v-else>{{ record.mobile }}</span>
|
||||
<template v-if="column.key === 'realName'">
|
||||
<div class="flex flex-col items-center">
|
||||
<span>{{ record.realName }}</span>
|
||||
<span class="text-gray-400" v-if="hasRole('superAdmin')">{{ record.phone }}</span>
|
||||
<span class="text-gray-400" v-else>{{ record.mobile }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'roles'">
|
||||
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
|
||||
@@ -105,6 +105,8 @@
|
||||
<user-import v-model:visible="showImport" @done="reload"/>
|
||||
<!-- 用户详情 -->
|
||||
<user-info v-model:visible="showInfo" :data="current" @done="reload"/>
|
||||
<!-- 邀请注册弹窗 -->
|
||||
<invitation-modal v-model:visible="showInvitation" :inviter-id="currentUserId"/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
@@ -126,13 +128,11 @@ import type {
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import {messageLoading, formatNumber} from 'ele-admin-pro/es';
|
||||
import UserEdit from './components/user-edit.vue';
|
||||
import UserImport from './components/user-import.vue';
|
||||
import UserInfo from './components/user-info.vue';
|
||||
import InvitationModal from './components/invitation-modal.vue';
|
||||
import {toDateString} from 'ele-admin-pro';
|
||||
import {
|
||||
pageUsers,
|
||||
removeUser,
|
||||
removeUsers,
|
||||
updateUserPassword,
|
||||
updateUser
|
||||
} from '@/api/system/user';
|
||||
@@ -142,10 +142,9 @@ import {listRoles} from '@/api/system/role';
|
||||
import {listOrganizations} from '@/api/system/organization';
|
||||
import {Organization} from '@/api/system/organization/model';
|
||||
import {hasRole} from '@/utils/permission';
|
||||
import {getPageTitle, push} from "@/utils/common";
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import router from "@/router";
|
||||
import SuperAdmin from './components/super-admin.vue';
|
||||
import Admin from './components/admin.vue';
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
@@ -165,8 +164,11 @@ const showEdit = ref(false);
|
||||
const showInfo = ref(false);
|
||||
// 是否显示用户导入弹窗
|
||||
const showImport = ref(false);
|
||||
const userType = ref<number>();
|
||||
// 是否显示邀请注册弹窗
|
||||
const showInvitation = ref(false);
|
||||
const searchText = ref('');
|
||||
// 当前用户ID
|
||||
const currentUserId = ref<number>();
|
||||
|
||||
// 加载角色
|
||||
const roles = ref<any[]>([]);
|
||||
@@ -217,19 +219,7 @@ const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '真实姓名',
|
||||
dataIndex: 'realName',
|
||||
align: 'center',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'mobile',
|
||||
align: 'center',
|
||||
key: 'mobile',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'sexName',
|
||||
key: 'realName',
|
||||
align: 'center',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
@@ -252,7 +242,7 @@ const columns = ref<ColumnItem[]>([
|
||||
align: 'center',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd')
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -296,22 +286,6 @@ const openEdit = (row?: User) => {
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开用户详情弹窗 */
|
||||
const openInfo = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showInfo.value = true;
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
const handleTabs = (e) => {
|
||||
userType.value = Number(e.target.value);
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: User) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
@@ -327,33 +301,6 @@ const remove = (row: User) => {
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的用户吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeUsers(selection.value.map((d) => d.userId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 重置用户密码 */
|
||||
const resetPsw = (row: User) => {
|
||||
Modal.confirm({
|
||||
@@ -410,6 +357,18 @@ const query = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
/* 打开邀请注册弹窗 */
|
||||
const openInvitation = () => {
|
||||
// 获取当前用户ID
|
||||
const userId = localStorage.getItem('UserId');
|
||||
if (userId) {
|
||||
currentUserId.value = Number(userId);
|
||||
showInvitation.value = true;
|
||||
} else {
|
||||
message.error('获取用户信息失败');
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value.query,
|
||||
() => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,42 +1,159 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="search-container">
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="searchForm"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item label="客户名称">
|
||||
<a-input
|
||||
v-model:value="searchForm.dealerName"
|
||||
placeholder="请输入客户名称"
|
||||
allow-clear
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="联系电话">
|
||||
<a-input
|
||||
v-model:value="searchForm.mobile"
|
||||
placeholder="客户联系电话"
|
||||
allow-clear
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="审核状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.applyStatus"
|
||||
placeholder="全部状态"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option :value="10">跟进中</a-select-option>
|
||||
<a-select-option :value="20">已签约</a-select-option>
|
||||
<a-select-option :value="30">已取消</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="添加时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" html-type="submit" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="resetSearch">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
import {
|
||||
SearchOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { ShopDealerApplyParam } from '@/api/shop/shopDealerApply/model';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
// 选中的数据
|
||||
selection?: any[];
|
||||
}>(),
|
||||
{}
|
||||
{
|
||||
selection: () => []
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'search', where?: ShopDealerApplyParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'batchApprove'): void;
|
||||
(e: 'export'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
// 搜索表单
|
||||
const searchForm = reactive<any>({
|
||||
realName: '',
|
||||
mobile: '',
|
||||
applyType: undefined,
|
||||
applyStatus: undefined,
|
||||
dateRange: undefined
|
||||
});
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const searchParams: ShopDealerApplyParam = {};
|
||||
|
||||
if (searchForm.realName) {
|
||||
searchParams.realName = searchForm.realName;
|
||||
}
|
||||
if (searchForm.mobile) {
|
||||
searchParams.mobile = searchForm.mobile;
|
||||
}
|
||||
if (searchForm.applyType) {
|
||||
searchParams.applyType = searchForm.applyType;
|
||||
}
|
||||
if (searchForm.applyStatus) {
|
||||
searchParams.applyStatus = searchForm.applyStatus;
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
searchParams.startTime = dayjs(searchForm.dateRange[0]).format('YYYY-MM-DD');
|
||||
searchParams.endTime = dayjs(searchForm.dateRange[1]).format('YYYY-MM-DD');
|
||||
}
|
||||
if(searchForm.dealerName){
|
||||
searchParams.dealerName = searchForm.dealerName;
|
||||
}
|
||||
|
||||
emit('search', searchParams);
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchForm.realName = '';
|
||||
searchForm.mobile = '';
|
||||
searchForm.applyType = undefined;
|
||||
searchForm.applyStatus = undefined;
|
||||
searchForm.dateRange = undefined;
|
||||
emit('search', {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search-container {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:width="900"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商申请记录表' : '添加分销商申请记录表'"
|
||||
:title="isUpdate ? '编辑客户' : '新增客户'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -14,81 +14,197 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-item label="用户ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户ID"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="姓名" name="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="mobile">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="form.mobile"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="推荐人用户ID" name="refereeId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入推荐人用户ID"
|
||||
v-model:value="form.refereeId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="申请方式(10需后台审核 20无需审核)" name="applyType">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入申请方式(10需后台审核 20无需审核)"
|
||||
v-model:value="form.applyType"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="申请时间" name="applyTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入申请时间"
|
||||
v-model:value="form.applyTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核状态 (10待审核 20审核通过 30驳回)" name="applyStatus">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入审核状态 (10待审核 20审核通过 30驳回)"
|
||||
v-model:value="form.applyStatus"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核时间" name="auditTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入审核时间"
|
||||
v-model:value="form.auditTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="驳回原因" name="rejectReason">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入驳回原因"
|
||||
v-model:value="form.rejectReason"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 客户信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">客户信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户名称" name="dealerName">
|
||||
<a-input
|
||||
placeholder="请输入客户名称"
|
||||
v-model:value="form.dealerName"
|
||||
:disabled="form.applyStatus == 20"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="联系人" name="realName">
|
||||
<a-input
|
||||
placeholder="请输入联系人"
|
||||
v-model:value="form.realName"
|
||||
:disabled="form.applyStatus == 20"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="手机号码" name="mobile">
|
||||
<a-input
|
||||
placeholder="请输入手机号码"
|
||||
:disabled="form.applyStatus == 20"
|
||||
v-model:value="form.mobile"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="收益基数" name="rate">
|
||||
<a-input-number
|
||||
placeholder="0.007"
|
||||
:min="0"
|
||||
:max="1"
|
||||
step="0.01"
|
||||
:disabled="!hasRole('superAdmin')"
|
||||
v-model:value="form.rate"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 报备人信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">报备人信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="报备人ID" name="userId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入报备人ID"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.userId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="报备人" name="nickName">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入报备人名称"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.nickName"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="推荐人ID" name="refereeId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入推荐人用户ID"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.refereeId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="推荐人名称" name="refereeName">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入推荐人名称"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.refereeName"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 审核信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">审核状态</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="审核状态" name="applyStatus">
|
||||
<a-select v-model:value="form.applyStatus" placeholder="请选择审核状态" @change="handleStatusChange">
|
||||
<a-select-option :value="10">
|
||||
<a-tag color="orange">跟进中</a-tag>
|
||||
<span style="margin-left: 8px;">正在跟进中</span>
|
||||
</a-select-option>
|
||||
<a-select-option :value="20">
|
||||
<a-tag color="success">已签约</a-tag>
|
||||
<span style="margin-left: 8px;">客户已签约</span>
|
||||
</a-select-option>
|
||||
<a-select-option :value="30">
|
||||
<a-tag color="error">已取消</a-tag>
|
||||
<span style="margin-left: 8px;">客户已取消</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="form.applyStatus === 30">
|
||||
<a-form-item label="取消原因" name="rejectReason">
|
||||
<a-textarea
|
||||
v-model:value="form.rejectReason"
|
||||
placeholder="请输入取消原因"
|
||||
style="width: 100%"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 跟进情况 -->
|
||||
<a-divider orientation="left" v-if="form.applyStatus == 10">
|
||||
<span style="color: #1890ff; font-weight: 600;">跟进情况</span>
|
||||
</a-divider>
|
||||
|
||||
<!-- 历史跟进记录 -->
|
||||
<div v-if="form.applyStatus == 10 && historyRecords.length > 0">
|
||||
<a-divider orientation="left" style="font-size: 14px; color: #666;">
|
||||
历史跟进记录
|
||||
</a-divider>
|
||||
<div v-for="(record, index) in historyRecords" :key="record.id" style="margin-bottom: 16px; padding: 12px; background-color: #f5f5f5; border-radius: 4px;">
|
||||
<div class="flex justify-between" style="font-weight: 500; margin-bottom: 8px;">
|
||||
<div>跟进 #{{ historyRecords.length - index }} <span class="text-gray-400 px-4">{{ record.createTime }}</span></div>
|
||||
<a-tag color="#f50" class="cursor-pointer" @click="remove(record)">删除</a-tag>
|
||||
</div>
|
||||
<div style="white-space: pre-wrap;">
|
||||
{{ record.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增跟进记录 -->
|
||||
<div v-if="form.applyStatus == 10">
|
||||
<a-divider orientation="left" style="font-size: 14px; color: #666;">
|
||||
新增跟进记录
|
||||
</a-divider>
|
||||
<a-form-item :wrapper-col="{ span: 24 }">
|
||||
<div class="flex flex-col gap-2">
|
||||
<a-textarea
|
||||
v-model:value="newFollowUpContent"
|
||||
placeholder="请输入本次跟进内容"
|
||||
:rows="4"
|
||||
:maxlength="500"
|
||||
style="width: 80%"
|
||||
show-count
|
||||
/>
|
||||
<div class="btn">
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="saveFollowUpRecord"
|
||||
:loading="followUpLoading"
|
||||
:disabled="!newFollowUpContent.trim()"
|
||||
>
|
||||
保存跟进记录
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
@@ -96,21 +212,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import dayjs from 'dayjs';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addShopDealerApply, updateShopDealerApply } from '@/api/shop/shopDealerApply';
|
||||
import {listShopDealerRecord, addShopDealerRecord, removeShopDealerRecord} from '@/api/shop/shopDealerRecord';
|
||||
import { ShopDealerApply } from '@/api/shop/shopDealerApply/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import { ShopDealerRecord } from '@/api/shop/shopDealerRecord/model';
|
||||
import { FormInstance, RuleObject } from 'ant-design-vue/es/form';
|
||||
import { messageLoading } from 'ele-admin-pro';
|
||||
import {hasRole} from "@/utils/permission";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
@@ -126,32 +240,38 @@
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 跟进记录保存状态
|
||||
const followUpLoading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
// 历史跟进记录
|
||||
const historyRecords = ref<ShopDealerRecord[]>([]);
|
||||
|
||||
// 新的跟进内容
|
||||
const newFollowUpContent = ref('');
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<ShopDealerApply>({
|
||||
applyId: undefined,
|
||||
userId: undefined,
|
||||
realName: undefined,
|
||||
mobile: undefined,
|
||||
nickName: undefined,
|
||||
realName: '',
|
||||
mobile: '',
|
||||
dealerName: '',
|
||||
rate: 0.007,
|
||||
refereeId: undefined,
|
||||
applyType: undefined,
|
||||
refereeName: undefined,
|
||||
applyType: 10,
|
||||
applyTime: undefined,
|
||||
applyStatus: undefined,
|
||||
applyStatus: 10,
|
||||
auditTime: undefined,
|
||||
rejectReason: undefined,
|
||||
rejectReason: '',
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopDealerApplyId: undefined,
|
||||
shopDealerApplyName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
@@ -161,77 +281,281 @@
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopDealerApplyName: [
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写分销商申请记录表名称',
|
||||
message: '请输入用户ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
} as RuleObject
|
||||
],
|
||||
realName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入客户名称',
|
||||
trigger: 'blur'
|
||||
} as RuleObject,
|
||||
{
|
||||
min: 2,
|
||||
max: 20,
|
||||
message: '姓名长度应在2-20个字符之间',
|
||||
trigger: 'blur'
|
||||
} as RuleObject
|
||||
],
|
||||
rate: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入收益基数',
|
||||
trigger: 'blur'
|
||||
} as RuleObject
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入手机号码',
|
||||
trigger: 'blur'
|
||||
} as RuleObject,
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur'
|
||||
} as RuleObject
|
||||
],
|
||||
applyType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择申请方式',
|
||||
trigger: 'change'
|
||||
} as RuleObject
|
||||
],
|
||||
applyStatus: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择审核状态',
|
||||
trigger: 'change'
|
||||
} as RuleObject
|
||||
],
|
||||
rejectReason: [
|
||||
{
|
||||
required: true,
|
||||
message: '驳回时必须填写驳回原因',
|
||||
trigger: 'blur'
|
||||
} as RuleObject
|
||||
],
|
||||
auditTime: [
|
||||
{
|
||||
required: true,
|
||||
message: '审核时请选择审核时间',
|
||||
trigger: 'change'
|
||||
} as RuleObject
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
/* 获取历史跟进记录 */
|
||||
const fetchHistoryRecords = async (dealerId: number) => {
|
||||
try {
|
||||
// 先通过list接口获取所有记录,然后过滤
|
||||
const allRecords = await listShopDealerRecord({});
|
||||
const records = allRecords.filter(record => record.dealerId === dealerId);
|
||||
|
||||
// 按创建时间倒序排列(最新的在前面)
|
||||
historyRecords.value = records.sort((a, b) => {
|
||||
if (a.createTime && b.createTime) {
|
||||
return new Date(b.createTime).getTime() - new Date(a.createTime).getTime();
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取历史跟进记录失败:', error);
|
||||
message.error('获取历史跟进记录失败');
|
||||
historyRecords.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
/* 保存新的跟进记录 */
|
||||
const saveFollowUpRecord = async () => {
|
||||
// 检查是否有客户ID
|
||||
if (!form.applyId) {
|
||||
message.warning('请先保存客户信息');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有跟进内容
|
||||
if (!newFollowUpContent.value.trim()) {
|
||||
message.warning('请输入跟进内容');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
followUpLoading.value = true;
|
||||
const recordData: any = {
|
||||
content: newFollowUpContent.value.trim(),
|
||||
dealerId: form.applyId,
|
||||
userId: form.userId,
|
||||
status: 1, // 默认设置为已完成
|
||||
sortNumber: 100
|
||||
};
|
||||
|
||||
// 新增逻辑
|
||||
await addShopDealerRecord(recordData);
|
||||
message.success('跟进记录保存成功');
|
||||
// 保存最后跟进内容到主表
|
||||
await updateShopDealerApply({
|
||||
...form,
|
||||
comments: newFollowUpContent.value.trim()
|
||||
})
|
||||
|
||||
// 清空输入框
|
||||
newFollowUpContent.value = '';
|
||||
|
||||
// 重新加载历史记录
|
||||
await fetchHistoryRecords(form.applyId);
|
||||
} catch (error) {
|
||||
console.error('保存跟进记录失败:', error);
|
||||
message.error('保存跟进记录失败');
|
||||
} finally {
|
||||
followUpLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 处理审核状态变化 */
|
||||
const handleStatusChange = (value: number) => {
|
||||
// 当状态改为审核通过或驳回时,自动设置审核时间为当前时间
|
||||
if ((value === 20 || value === 30) && !form.auditTime) {
|
||||
form.auditTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
// 当状态改为待审核时,清空审核时间和驳回原因
|
||||
if (value === 10) {
|
||||
form.auditTime = undefined;
|
||||
form.rejectReason = '';
|
||||
}
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerRecord) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeShopDealerRecord(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
// 重新加载历史记录
|
||||
if(props.data?.applyId){
|
||||
fetchHistoryRecords(props.data?.applyId);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态验证规则
|
||||
const validateFields: string[] = ['userId', 'realName', 'mobile', 'applyStatus'];
|
||||
|
||||
// 如果是驳回状态,需要验证驳回原因
|
||||
if (form.applyStatus === 30) {
|
||||
validateFields.push('rejectReason');
|
||||
}
|
||||
|
||||
// 如果是审核通过或驳回状态,需要验证审核时间
|
||||
if (form.applyStatus === 20 || form.applyStatus === 30) {
|
||||
validateFields.push('auditTime');
|
||||
}
|
||||
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
.validate(validateFields)
|
||||
.then(async () => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
|
||||
// 处理时间字段转换 - 转换为ISO字符串格式
|
||||
if (formData.applyTime) {
|
||||
if (dayjs.isDayjs(formData.applyTime)) {
|
||||
formData.applyTime = formData.applyTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
} else if (typeof formData.applyTime === 'number') {
|
||||
formData.applyTime = dayjs(formData.applyTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
}
|
||||
|
||||
if (formData.auditTime) {
|
||||
if (dayjs.isDayjs(formData.auditTime)) {
|
||||
formData.auditTime = formData.auditTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
} else if (typeof formData.auditTime === 'number') {
|
||||
formData.auditTime = dayjs(formData.auditTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
}
|
||||
|
||||
// 当审核状态为通过或驳回时,确保有审核时间
|
||||
if ((formData.applyStatus === 20 || formData.applyStatus === 30) && !formData.auditTime) {
|
||||
formData.auditTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
// 当状态为待审核时,清空审核时间
|
||||
if (formData.applyStatus === 10) {
|
||||
formData.auditTime = undefined;
|
||||
}
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerApply : addShopDealerApply;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
try {
|
||||
const msg = await saveOrUpdate(formData);
|
||||
message.success(msg);
|
||||
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
async (visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
|
||||
// 如果是修改且状态为跟进中,获取历史跟进记录
|
||||
if (props.data.applyId && props.data.applyStatus === 10) {
|
||||
await fetchHistoryRecords(props.data.applyId);
|
||||
}
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
applyId: undefined,
|
||||
userId: undefined,
|
||||
realName: '',
|
||||
mobile: '',
|
||||
refereeId: undefined,
|
||||
applyType: 10,
|
||||
applyTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
applyStatus: 10,
|
||||
auditTime: undefined,
|
||||
rejectReason: '',
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
isUpdate.value = false;
|
||||
|
||||
// 重置历史记录和新内容
|
||||
historyRecords.value = [];
|
||||
newFollowUpContent.value = '';
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
@@ -240,3 +564,32 @@
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-radio) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.ant-radio-inner {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,274 +1,440 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerApplyId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="applyId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@batchApprove="batchApprove"
|
||||
@export="exportData"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'applyStatus'">
|
||||
<a-tag v-if="record.applyStatus === 10" color="orange">跟进中</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 20" color="green">已签约</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 30" color="red">已取消</a-tag>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
<template v-if="column.key === 'customer'">
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ record.dealerName }}</span>
|
||||
<span class="text-gray-400">联系人:{{ record.realName }}</span>
|
||||
<span class="text-gray-400">联系电话:{{ record.mobile }}</span>
|
||||
<span class="text-gray-400">户号:{{ record.dealerCode }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'applicantInfo'">
|
||||
<div class="text-gray-600">{{ record.nickName }}</div>
|
||||
<div class="text-gray-400">{{ record.phone }}</div>
|
||||
<div class="text-gray-400">{{ record.rate }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<div class="text-gray-400">{{ record.comments }}</div>
|
||||
<div class="text-gray-400" v-if="record.comments">{{ record.updateTime }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<div class="flex flex-col">
|
||||
<span>{{ record.createTime }}</span>
|
||||
<span>保护期:7天</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a @click="openEdit(record)" class="ele-text-primary">
|
||||
<EditOutlined/>
|
||||
编辑
|
||||
</a>
|
||||
<template v-if="record.applyStatus !== 20">
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="approveApply(record)" class="ele-text-success">
|
||||
<CheckOutlined/>
|
||||
已签约
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="rejectApply(record)" class="ele-text-warning">
|
||||
<CloseOutlined/>
|
||||
驳回
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm
|
||||
v-if="record.applyStatus != 20"
|
||||
title="确定要删除此申请记录吗?"
|
||||
@confirm="remove(record)"
|
||||
placement="topRight"
|
||||
>
|
||||
<a class="ele-text-danger">
|
||||
<DeleteOutlined/>
|
||||
删除
|
||||
</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerApplyEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerApplyEdit v-model:visible="showEdit" :data="current" @done="reload"/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerApplyEdit from './components/shopDealerApplyEdit.vue';
|
||||
import { pageShopDealerApply, removeShopDealerApply, removeBatchShopDealerApply } from '@/api/shop/shopDealerApply';
|
||||
import type { ShopDealerApply, ShopDealerApplyParam } from '@/api/shop/shopDealerApply/model';
|
||||
import {createVNode, ref} from 'vue';
|
||||
import {message, Modal} from 'ant-design-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {EleProTable} from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerApplyEdit from './components/shopDealerApplyEdit.vue';
|
||||
import {
|
||||
pageShopDealerApply,
|
||||
removeShopDealerApply,
|
||||
removeBatchShopDealerApply,
|
||||
batchApproveShopDealerApply,
|
||||
updateShopDealerApply
|
||||
} from '@/api/shop/shopDealerApply';
|
||||
import type {ShopDealerApply, ShopDealerApplyParam} from '@/api/shop/shopDealerApply/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerApply[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerApply | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerApply[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerApply | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
where.type = 4;
|
||||
return pageShopDealerApply({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '客户名称',
|
||||
dataIndex: 'customer',
|
||||
key: 'customer'
|
||||
},
|
||||
{
|
||||
title: '最后跟进情况',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
align: 'left'
|
||||
},
|
||||
// {
|
||||
// title: '收益基数',
|
||||
// dataIndex: 'rate',
|
||||
// key: 'rate',
|
||||
// align: 'left'
|
||||
// },
|
||||
{
|
||||
title: '报备人信息',
|
||||
dataIndex: 'applicantInfo',
|
||||
key: 'applicantInfo',
|
||||
align: 'left',
|
||||
fixed: 'left',
|
||||
customRender: ({record}) => {
|
||||
return `${record.nickName || '-'} (${record.phone || '-'})`;
|
||||
}
|
||||
return pageShopDealerApply({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'applyStatus',
|
||||
key: 'applyStatus',
|
||||
align: 'center',
|
||||
width: 120
|
||||
},
|
||||
// {
|
||||
// title: '申请时间',
|
||||
// dataIndex: 'applyTime',
|
||||
// key: 'applyTime',
|
||||
// align: 'center',
|
||||
// width: 120,
|
||||
// customRender: ({ text }) => text ? toDateString(new Date(text), 'yyyy-MM-dd HH:mm') : '-'
|
||||
// },
|
||||
// {
|
||||
// title: '审核时间',
|
||||
// dataIndex: 'auditTime',
|
||||
// key: 'auditTime',
|
||||
// align: 'center',
|
||||
// customRender: ({ text }) => text ? toDateString(new Date(text), 'yyyy-MM-dd HH:mm') : '-'
|
||||
// },
|
||||
{
|
||||
title: '添加时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true
|
||||
}
|
||||
// {
|
||||
// title: '操作',
|
||||
// key: 'action',
|
||||
// fixed: 'right',
|
||||
// align: 'center',
|
||||
// width: 380,
|
||||
// hideInSetting: true
|
||||
// }
|
||||
]);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '主键ID',
|
||||
dataIndex: 'applyId',
|
||||
key: 'applyId',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realName',
|
||||
key: 'realName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '推荐人用户ID',
|
||||
dataIndex: 'refereeId',
|
||||
key: 'refereeId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '申请方式(10需后台审核 20无需审核)',
|
||||
dataIndex: 'applyType',
|
||||
key: 'applyType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'applyTime',
|
||||
key: 'applyTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '审核状态 (10待审核 20审核通过 30驳回)',
|
||||
dataIndex: 'applyStatus',
|
||||
key: 'applyStatus',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '审核时间',
|
||||
dataIndex: 'auditTime',
|
||||
key: 'auditTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '驳回原因',
|
||||
dataIndex: 'rejectReason',
|
||||
key: 'rejectReason',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerApplyParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({where: where});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerApplyParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerApply) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerApply) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerApply(row.shopDealerApplyId)
|
||||
.then((msg) => {
|
||||
/* 审核通过 */
|
||||
const approveApply = (row: ShopDealerApply) => {
|
||||
Modal.confirm({
|
||||
title: '审核通过确认',
|
||||
content: `确定要通过 ${row.realName} 的经销商申请吗?`,
|
||||
icon: createVNode(CheckOutlined),
|
||||
okText: '确认通过',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const hide = message.loading('正在处理审核...', 0);
|
||||
try {
|
||||
await updateShopDealerApply({
|
||||
...row,
|
||||
applyId: row.applyId,
|
||||
applyStatus: 20
|
||||
});
|
||||
hide();
|
||||
message.success(msg);
|
||||
message.success('审核通过成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
} catch (error: any) {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
message.error(error.message || '审核失败,请重试');
|
||||
}
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerApply(selection.value.map((d) => d.shopDealerApplyId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* 审核驳回 */
|
||||
const rejectApply = (row: ShopDealerApply) => {
|
||||
let rejectReason = '';
|
||||
Modal.confirm({
|
||||
title: '审核驳回',
|
||||
content: createVNode('div', null, [
|
||||
createVNode('p', null, `申请人: ${row.realName} (${row.mobile})`),
|
||||
createVNode('p', {style: 'margin-top: 12px;'}, '请输入驳回原因:'),
|
||||
createVNode('textarea', {
|
||||
placeholder: '请输入驳回原因...',
|
||||
style: 'width: 100%; height: 80px; margin-top: 8px; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px;',
|
||||
onInput: (e: any) => {
|
||||
rejectReason = e.target.value;
|
||||
}
|
||||
})
|
||||
]),
|
||||
icon: createVNode(CloseOutlined),
|
||||
okText: '确认驳回',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
if (!rejectReason.trim()) {
|
||||
message.error('请输入驳回原因');
|
||||
return Promise.reject();
|
||||
}
|
||||
const hide = message.loading('正在处理审核...', 0);
|
||||
try {
|
||||
await updateShopDealerApply({
|
||||
...row,
|
||||
applyStatus: 30,
|
||||
rejectReason: rejectReason.trim()
|
||||
});
|
||||
hide();
|
||||
message.success('审核驳回成功');
|
||||
reload();
|
||||
} catch (error: any) {
|
||||
hide();
|
||||
message.error(error.message || '审核失败,请重试');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerApply) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerApply) => {
|
||||
if (!row.applyId) {
|
||||
message.error('删除失败:缺少必要参数');
|
||||
return;
|
||||
}
|
||||
|
||||
const hide = message.loading('正在删除申请记录...', 0);
|
||||
removeShopDealerApply(row.applyId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg || '删除成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || '删除失败');
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerApply) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
const validIds = selection.value.filter(d => d.applyId).map(d => d.applyId);
|
||||
if (!validIds.length) {
|
||||
message.error('选中的数据中没有有效的ID');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '批量删除确认',
|
||||
content: `确定要删除选中的 ${validIds.length} 条申请记录吗?此操作不可恢复。`,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
okText: '确认删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
const hide = message.loading(`正在删除 ${validIds.length} 条记录...`, 0);
|
||||
removeBatchShopDealerApply(validIds)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg || `成功删除 ${validIds.length} 条记录`);
|
||||
selection.value = [];
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || '批量删除失败');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量通过 */
|
||||
const batchApprove = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingApplies = selection.value.filter(item => item.applyStatus === 10);
|
||||
if (!pendingApplies.length) {
|
||||
message.error('所选申请中没有待审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '批量通过确认',
|
||||
content: `确定要通过选中的 ${pendingApplies.length} 个申请吗?`,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
okText: '确认通过',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const hide = message.loading('正在批量通过...', 0);
|
||||
try {
|
||||
const ids = pendingApplies.map(item => item.applyId);
|
||||
await batchApproveShopDealerApply(ids);
|
||||
hide();
|
||||
message.success(`成功通过 ${pendingApplies.length} 个申请`);
|
||||
selection.value = [];
|
||||
reload();
|
||||
} catch (error: any) {
|
||||
hide();
|
||||
message.error(error.message || '批量审核失败,请重试');
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const exportData = () => {
|
||||
const hide = message.loading('正在导出申请数据...', 0);
|
||||
// 这里调用导出API
|
||||
setTimeout(() => {
|
||||
hide();
|
||||
message.success('申请数据导出成功');
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerApply) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
query();
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopDealerApply'
|
||||
};
|
||||
export default {
|
||||
name: 'ShopDealerApply'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:width="900"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商资金明细表' : '添加分销商资金明细表'"
|
||||
:title="isUpdate ? '编辑资金流动记录' : '新增资金流动记录'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -14,60 +14,128 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-item label="分销商用户ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销商用户ID"
|
||||
v-model:value="form.userId"
|
||||
<!-- 基本信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">基本信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="分销商用户ID" name="userId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入分销商用户ID"
|
||||
v-model:value="form.userId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="订单ID" name="orderId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入订单ID(可选)"
|
||||
v-model:value="form.orderId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 资金流动信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">资金流动信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="流动类型" name="flowType">
|
||||
<a-select v-model:value="form.flowType" placeholder="请选择资金流动类型">
|
||||
<a-select-option :value="10">
|
||||
<div class="flow-type-option">
|
||||
<a-tag color="success">佣金收入</a-tag>
|
||||
<span>获得分销佣金</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option :value="20">
|
||||
<div class="flow-type-option">
|
||||
<a-tag color="warning">提现支出</a-tag>
|
||||
<span>申请提现</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option :value="30">
|
||||
<div class="flow-type-option">
|
||||
<a-tag color="error">转账支出</a-tag>
|
||||
<span>转账给他人</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option :value="40">
|
||||
<div class="flow-type-option">
|
||||
<a-tag color="processing">转账收入</a-tag>
|
||||
<span>收到转账</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="金额" name="money">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="请输入金额"
|
||||
v-model:value="form.money"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-form-item label="流动描述" name="comments">
|
||||
<a-textarea
|
||||
v-model:value="form.comments"
|
||||
placeholder="请输入资金流动描述"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单ID" name="orderId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单ID"
|
||||
v-model:value="form.orderId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)" name="flowType">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)"
|
||||
v-model:value="form.flowType"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="金额" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入金额"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="describe">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.describe"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="对方用户ID" name="toUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
|
||||
<!-- 关联信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">关联信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-form-item
|
||||
label="对方用户ID"
|
||||
name="toUserId"
|
||||
v-if="form.flowType === 30 || form.flowType === 40"
|
||||
>
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入对方用户ID"
|
||||
v-model:value="form.toUserId"
|
||||
style="width: 300px"
|
||||
/>
|
||||
<span style="margin-left: 12px; color: #999; font-size: 12px;">
|
||||
转账相关操作需要填写对方用户ID
|
||||
</span>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
|
||||
<!-- 金额预览 -->
|
||||
<div class="amount-preview" v-if="form.money && form.flowType">
|
||||
<a-alert
|
||||
:type="getAmountAlertType()"
|
||||
:message="getAmountPreviewText()"
|
||||
show-icon
|
||||
style="margin-top: 16px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
@@ -111,23 +179,18 @@
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
// 表单数据
|
||||
const form = reactive<ShopDealerCapital>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
orderId: undefined,
|
||||
flowType: undefined,
|
||||
money: undefined,
|
||||
describe: undefined,
|
||||
comments: '',
|
||||
toUserId: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopDealerCapitalId: undefined,
|
||||
shopDealerCapitalName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
@@ -137,28 +200,94 @@
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopDealerCapitalName: [
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写分销商资金明细表名称',
|
||||
message: '请输入分销商用户ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
flowType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择资金流动类型',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
money: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入金额',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value && value <= 0) {
|
||||
return Promise.reject('金额必须大于0');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入流动描述',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 200,
|
||||
message: '描述长度应在2-200个字符之间',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
toUserId: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if ((form.flowType === 30 || form.flowType === 40) && !value) {
|
||||
return Promise.reject('转账操作必须填写对方用户ID');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
/* 获取金额预览提示类型 */
|
||||
const getAmountAlertType = () => {
|
||||
if (!form.flowType) return 'info';
|
||||
|
||||
switch (form.flowType) {
|
||||
case 10: // 佣金收入
|
||||
case 40: // 转账收入
|
||||
return 'success';
|
||||
case 20: // 提现支出
|
||||
case 30: // 转账支出
|
||||
return 'warning';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
/* 获取金额预览文本 */
|
||||
const getAmountPreviewText = () => {
|
||||
if (!form.money || !form.flowType) return '';
|
||||
|
||||
const amount = parseFloat(form.money.toString()).toFixed(2);
|
||||
const flowTypeMap = {
|
||||
10: '佣金收入',
|
||||
20: '提现支出',
|
||||
30: '转账支出',
|
||||
40: '转账收入'
|
||||
};
|
||||
|
||||
const flowTypeName = flowTypeMap[form.flowType] || '未知类型';
|
||||
const symbol = form.flowType === 10 || form.flowType === 40 ? '+' : '-';
|
||||
|
||||
return `${flowTypeName}:${symbol}¥${amount}`;
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
@@ -195,18 +324,23 @@
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
orderId: undefined,
|
||||
flowType: undefined,
|
||||
money: undefined,
|
||||
comments: '',
|
||||
toUserId: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
@@ -216,3 +350,49 @@
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.flow-type-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.amount-preview {
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerCapitalId"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
@@ -100,47 +100,82 @@
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '主键ID',
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
width: 80,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '分销商用户ID',
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '订单ID',
|
||||
dataIndex: 'orderId',
|
||||
key: 'orderId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '资金流动类型 (10佣金收入 20提现支出 30转账支出 40转账收入)',
|
||||
title: '流动类型',
|
||||
dataIndex: 'flowType',
|
||||
key: 'flowType',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
customRender: ({ text }) => {
|
||||
const typeMap = {
|
||||
10: { text: '佣金收入', color: 'success' },
|
||||
20: { text: '提现支出', color: 'warning' },
|
||||
30: { text: '转账支出', color: 'error' },
|
||||
40: { text: '转账收入', color: 'processing' }
|
||||
};
|
||||
const type = typeMap[text] || { text: '未知', color: 'default' };
|
||||
return { type: 'tag', props: { color: type.color }, children: type.text };
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
dataIndex: 'money',
|
||||
key: 'money',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
customRender: ({ text, record }) => {
|
||||
const amount = parseFloat(text || '0').toFixed(2);
|
||||
const isIncome = record.flowType === 10 || record.flowType === 40;
|
||||
return {
|
||||
type: 'span',
|
||||
props: {
|
||||
style: {
|
||||
color: isIncome ? '#52c41a' : '#ff4d4f',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
children: `${isIncome ? '+' : '-'}¥${amount}`
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '关联订单',
|
||||
dataIndex: 'orderNo',
|
||||
key: 'orderNo',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => text || '-'
|
||||
},
|
||||
{
|
||||
title: '对方用户',
|
||||
dataIndex: 'toUserId',
|
||||
key: 'toUserId',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
customRender: ({ text }) => text ? `ID: ${text}` : '-'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '对方用户ID',
|
||||
dataIndex: 'toUserId',
|
||||
key: 'toUserId',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 200,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => text || '-'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
@@ -187,7 +222,7 @@
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerCapital) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerCapital(row.shopDealerCapitalId)
|
||||
removeShopDealerCapital(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
@@ -212,7 +247,7 @@
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerCapital(selection.value.map((d) => d.shopDealerCapitalId))
|
||||
removeBatchShopDealerCapital(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
|
||||
83
src/views/shop/shopDealerOrder/components/Import.vue
Normal file
83
src/views/shop/shopDealerOrder/components/Import.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<!-- 经销商订单导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:footer="null"
|
||||
title="导入分销订单"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doUpload"
|
||||
style="padding: 24px 0; margin-bottom: 16px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined/>
|
||||
</p>
|
||||
<p class="ant-upload-hint">将文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
<div class="ant-upload-text text-gray-400">
|
||||
<div>1、必须按<a href="https://oss.wsdns.cn/20251018/408b805ec3cd4084a4dc686e130af578.xlsx" target="_blank">导入模版</a>的格式上传</div>
|
||||
<div>2、导入成功确认结算完成佣金的发放</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue';
|
||||
import {message} from 'ant-design-vue/es';
|
||||
import {CloudUploadOutlined} from '@ant-design/icons-vue';
|
||||
import {importSdyDealerOrder} from "@/api/sdy/sdyDealerOrder";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 是否打开弹窗
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
// 导入请求状态
|
||||
const loading = ref(false);
|
||||
|
||||
/* 上传 */
|
||||
const doUpload = ({file}) => {
|
||||
if (
|
||||
![
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
].includes(file.type)
|
||||
) {
|
||||
message.error('只能选择 excel 文件');
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
message.error('大小不能超过 10MB');
|
||||
return false;
|
||||
}
|
||||
loading.value = true;
|
||||
importSdyDealerOrder(file)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
/* 更新 visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
@@ -1,42 +1,184 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="flex items-center gap-20">
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="where"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button
|
||||
danger
|
||||
class="ele-btn-icon"
|
||||
v-if="selection.length > 0"
|
||||
:disabled="selection?.length === 0"
|
||||
@click="removeBatch"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined/>
|
||||
</template>
|
||||
<span>批量删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
<!-- <a-form-item label="订单状态">-->
|
||||
<!-- <a-select-->
|
||||
<!-- v-model:value="where.isInvalid"-->
|
||||
<!-- placeholder="全部"-->
|
||||
<!-- allow-clear-->
|
||||
<!-- style="width: 120px"-->
|
||||
<!-- >-->
|
||||
<!-- <a-select-option :value="0">有效</a-select-option>-->
|
||||
<!-- <a-select-option :value="1">失效</a-select-option>-->
|
||||
<!-- </a-select>-->
|
||||
<!-- </a-form-item>-->
|
||||
|
||||
<!-- <a-form-item label="结算状态">-->
|
||||
<!-- <a-select-->
|
||||
<!-- v-model:value="where.isSettled"-->
|
||||
<!-- placeholder="全部"-->
|
||||
<!-- allow-clear-->
|
||||
<!-- style="width: 120px"-->
|
||||
<!-- >-->
|
||||
<!-- <a-select-option :value="0">未结算</a-select-option>-->
|
||||
<!-- <a-select-option :value="1">已结算</a-select-option>-->
|
||||
<!-- </a-select>-->
|
||||
<!-- </a-form-item>-->
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入关键词"
|
||||
style="width: 240px"
|
||||
v-model:value="where.keywords"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<!-- <a-button type="primary" html-type="submit" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <SearchOutlined/>-->
|
||||
<!-- </template>-->
|
||||
<!-- 搜索-->
|
||||
<!-- </a-button>-->
|
||||
<a-button @click="resetSearch">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider type="vertical"/>
|
||||
<a-space>
|
||||
<!-- <a-button @click="exportData" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <ExportOutlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- 导出数据-->
|
||||
<!-- </a-button>-->
|
||||
<a-button @click="openImport" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<UploadOutlined/>
|
||||
</template>
|
||||
导入数据
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
danger
|
||||
@click="batchSettle"
|
||||
:disabled="selection?.length === 0"
|
||||
>
|
||||
<template #icon>
|
||||
<DollarOutlined/>
|
||||
</template>
|
||||
批量结算
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<Import v-model:visible="showImport" @done="emit('importDone')"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
import {ref} from 'vue';
|
||||
import {
|
||||
DollarOutlined,
|
||||
UploadOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {ShopDealerOrderParam} from '@/api/shop/shopDealerOrder/model';
|
||||
import Import from './Import.vue';
|
||||
import useSearch from "@/utils/use-search";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 选中的数据
|
||||
selection?: any[];
|
||||
}>(),
|
||||
{
|
||||
selection: () => []
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ShopDealerOrderParam): void;
|
||||
(e: 'batchSettle'): void;
|
||||
(e: 'export'): void;
|
||||
(e: 'importDone'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
// 是否显示导入弹窗
|
||||
const showImport = ref(false);
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
// 搜索表单
|
||||
const {where, resetFields} = useSearch<ShopDealerOrderParam>({
|
||||
orderNo: '',
|
||||
productName: '',
|
||||
isInvalid: undefined,
|
||||
isSettled: undefined
|
||||
});
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const searchParams = {...where};
|
||||
// 清除空值
|
||||
Object.keys(searchParams).forEach(key => {
|
||||
if (searchParams[key] === '' || searchParams[key] === undefined) {
|
||||
delete searchParams[key];
|
||||
}
|
||||
});
|
||||
emit('search', searchParams);
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
// Object.keys(searchForm).forEach(key => {
|
||||
// searchForm[key] = key === 'orderId' ? undefined : '';
|
||||
// });
|
||||
resetFields();
|
||||
emit('search', {});
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
// 批量结算
|
||||
const batchSettle = () => {
|
||||
emit('batchSettle');
|
||||
};
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
emit('export');
|
||||
};
|
||||
|
||||
// 打开导入弹窗
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,266 +1,352 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:width="900"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商订单记录表' : '添加分销商订单记录表'"
|
||||
:title="isUpdate ? '分销订单' : '分销订单'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
:okText="`立即结算`"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-item label="买家用户ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入买家用户ID"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单ID" name="orderId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单ID"
|
||||
v-model:value="form.orderId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单总金额(不含运费)" name="orderPrice">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单总金额(不含运费)"
|
||||
v-model:value="form.orderPrice"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分销商用户id(一级)" name="firstUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销商用户id(一级)"
|
||||
v-model:value="form.firstUserId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分销商用户id(二级)" name="secondUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销商用户id(二级)"
|
||||
v-model:value="form.secondUserId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分销商用户id(三级)" name="thirdUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销商用户id(三级)"
|
||||
v-model:value="form.thirdUserId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分销佣金(一级)" name="firstMoney">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销佣金(一级)"
|
||||
v-model:value="form.firstMoney"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分销佣金(二级)" name="secondMoney">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销佣金(二级)"
|
||||
v-model:value="form.secondMoney"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分销佣金(三级)" name="thirdMoney">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销佣金(三级)"
|
||||
v-model:value="form.thirdMoney"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="订单是否失效(0未失效 1已失效)" name="isInvalid">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入订单是否失效(0未失效 1已失效)"
|
||||
v-model:value="form.isInvalid"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="佣金结算(0未结算 1已结算)" name="isSettled">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入佣金结算(0未结算 1已结算)"
|
||||
v-model:value="form.isSettled"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="结算时间" name="settleTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入结算时间"
|
||||
v-model:value="form.settleTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
<!-- 订单基本信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">基本信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="客户名称" name="title">
|
||||
{{ form.title }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="订单编号" name="orderNo">
|
||||
{{ form.orderNo }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="结算电量" name="orderPrice">
|
||||
{{ parseFloat(form.orderPrice || 0).toFixed(2) }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="换算成度" name="dealerPrice">
|
||||
{{ parseFloat(form.degreePrice || 0).toFixed(2) }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="税率" name="rate">
|
||||
{{ form.rate }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="单价" name="price">
|
||||
{{ form.price }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="结算金额" name="payPrice">
|
||||
{{ parseFloat(form.settledPrice || 0).toFixed(2) }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="实发金额" name="payPrice">
|
||||
{{ parseFloat(form.payPrice || 0).toFixed(2) }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="font-bold text-gray-400 bg-gray-50">开发调试</div>
|
||||
<div class="text-gray-400 bg-gray-50">
|
||||
<div>业务员({{ form.userId }}):{{ form.nickname }}</div>
|
||||
<div>一级分销商({{ form.firstUserId }}):{{ form.firstNickname }},一级佣金30%:{{ form.firstMoney }}</div>
|
||||
<div>二级分销商({{ form.secondUserId }}):{{ form.secondNickname }},二级佣金10%:{{ form.secondMoney }}</div>
|
||||
<div>三级分销商({{ form.thirdUserId }}):{{ form.thirdNickname }},三级佣金60%:{{ form.thirdMoney }}</div>
|
||||
</div>
|
||||
<!-- 分销商信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">收益计算</span>
|
||||
</a-divider>
|
||||
|
||||
<!-- 一级分销商 -->
|
||||
<div class="dealer-section">
|
||||
<h4 class="dealer-title">
|
||||
<a-tag color="orange">一级佣金30%</a-tag>
|
||||
</h4>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户ID" name="firstUserId">
|
||||
{{ form.firstUserId }}
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称" name="firstNickname">
|
||||
{{ form.firstNickname }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="占比" name="rate">
|
||||
{{ '30%' }}
|
||||
</a-form-item>
|
||||
<a-form-item label="获取收益" name="firstMoney">
|
||||
{{ form.firstMoney }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 二级分销商 -->
|
||||
<div class="dealer-section">
|
||||
<h4 class="dealer-title">
|
||||
<a-tag color="orange">二级佣金10%</a-tag>
|
||||
</h4>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户ID" name="secondUserId">
|
||||
{{ form.secondUserId }}
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称" name="nickname">
|
||||
{{ form.secondNickname }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="占比" name="rate">
|
||||
10%
|
||||
</a-form-item>
|
||||
<a-form-item label="获取收益" name="firstMoney">
|
||||
{{ form.secondMoney }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 三级分销商 -->
|
||||
<div class="dealer-section" v-if="form.thirdUserId > 0">
|
||||
<h4 class="dealer-title">
|
||||
<a-tag color="orange">三级佣金60%</a-tag>
|
||||
</h4>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户ID" name="thirdUserId">
|
||||
{{ form.thirdUserId }}
|
||||
</a-form-item>
|
||||
<a-form-item label="昵称" name="thirdNickname">
|
||||
{{ form.thirdNickname }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="占比" name="rate">
|
||||
{{ '60%' }}
|
||||
</a-form-item>
|
||||
<a-form-item label="获取收益" name="thirdMoney">
|
||||
{{ form.thirdMoney }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<a-form-item label="结算时间" name="settleTime" v-if="form.isSettled === 1">
|
||||
{{ form.settleTime }}
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addShopDealerOrder, updateShopDealerOrder } from '@/api/shop/shopDealerOrder';
|
||||
import { ShopDealerOrder } from '@/api/shop/shopDealerOrder/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import {assignObject} from 'ele-admin-pro';
|
||||
import {ShopDealerOrder} from '@/api/shop/shopDealerOrder/model';
|
||||
import {FormInstance} from 'ant-design-vue/es/form';
|
||||
import {updateSdyDealerOrder} from "@/api/sdy/sdyDealerOrder";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopDealerOrder | null;
|
||||
}>();
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopDealerOrder | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ShopDealerOrder>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
orderId: undefined,
|
||||
orderPrice: undefined,
|
||||
firstUserId: undefined,
|
||||
secondUserId: undefined,
|
||||
thirdUserId: undefined,
|
||||
firstMoney: undefined,
|
||||
secondMoney: undefined,
|
||||
thirdMoney: undefined,
|
||||
isInvalid: undefined,
|
||||
isSettled: undefined,
|
||||
settleTime: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopDealerOrderId: undefined,
|
||||
shopDealerOrderName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
// 表单数据
|
||||
const form = reactive<ShopDealerOrder>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
nickname: undefined,
|
||||
orderNo: undefined,
|
||||
title: undefined,
|
||||
orderPrice: undefined,
|
||||
settledPrice: undefined,
|
||||
degreePrice: undefined,
|
||||
price: undefined,
|
||||
month: undefined,
|
||||
payPrice: undefined,
|
||||
firstUserId: undefined,
|
||||
secondUserId: undefined,
|
||||
thirdUserId: undefined,
|
||||
firstMoney: undefined,
|
||||
secondMoney: undefined,
|
||||
thirdMoney: undefined,
|
||||
firstNickname: undefined,
|
||||
secondNickname: undefined,
|
||||
thirdNickname: undefined,
|
||||
rate: undefined,
|
||||
comments: undefined,
|
||||
isInvalid: 0,
|
||||
isSettled: 0,
|
||||
settleTime: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopDealerOrderName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写分销商订单记录表名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择用户ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerOrder : addShopDealerOrder;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
],
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
|
||||
const {resetFields} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
if (form.isSettled == 1) {
|
||||
message.error('请勿重复结算');
|
||||
return;
|
||||
}
|
||||
if (form.userId == 0) {
|
||||
message.error('未签约');
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form,
|
||||
isSettled: 1
|
||||
};
|
||||
updateSdyDealerOrder(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
console.log(localStorage.getItem(''))
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
resetFields();
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
orderNo: undefined,
|
||||
orderPrice: undefined,
|
||||
firstUserId: undefined,
|
||||
secondUserId: undefined,
|
||||
thirdUserId: undefined,
|
||||
firstMoney: undefined,
|
||||
secondMoney: undefined,
|
||||
thirdMoney: undefined,
|
||||
isInvalid: 0,
|
||||
isSettled: 0,
|
||||
settleTime: undefined
|
||||
});
|
||||
isUpdate.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dealer-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid #1890ff;
|
||||
|
||||
.dealer-title {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
|
||||
.ant-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,292 +1,433 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerOrderId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
v-model:selection="selection"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@batchSettle="batchSettle"
|
||||
@export="handleExport"
|
||||
@remove="removeBatch"
|
||||
@importDone="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerOrderEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<template v-if="column.key === 'title'">
|
||||
<div>{{ record.title }}</div>
|
||||
<div class="text-gray-400">用户ID:{{ record.userId }}</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'orderPrice'">
|
||||
{{ record.orderPrice.toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'degreePrice'">
|
||||
{{ record.degreePrice.toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'price'">
|
||||
{{ record.price }}
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'settledPrice'">
|
||||
{{ record.settledPrice.toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'payPrice'">
|
||||
{{ record.payPrice.toFixed(2) }}
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'dealerInfo'">
|
||||
<div class="dealer-info">
|
||||
<div v-if="record.firstUserId" class="dealer-level">
|
||||
<a-tag color="red">一级</a-tag>
|
||||
用户{{ record.firstUserId }} - ¥{{ parseFloat(record.firstMoney || '0').toFixed(2) }}
|
||||
</div>
|
||||
<div v-if="record.secondUserId" class="dealer-level">
|
||||
<a-tag color="orange">二级</a-tag>
|
||||
用户{{ record.secondUserId }} - ¥{{ parseFloat(record.secondMoney || '0').toFixed(2) }}
|
||||
</div>
|
||||
<div v-if="record.thirdUserId" class="dealer-level">
|
||||
<a-tag color="gold">三级</a-tag>
|
||||
用户{{ record.thirdUserId }} - ¥{{ parseFloat(record.thirdMoney || '0').toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'isInvalid'">
|
||||
<a-tag v-if="record.isInvalid === 0" color="success">已签约</a-tag>
|
||||
<a-tag v-if="record.isInvalid === 1" color="error">未签约</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'isSettled'">
|
||||
<a-tag v-if="record.isSettled === 0" color="orange">未结算</a-tag>
|
||||
<a-tag v-if="record.isSettled === 1" color="success">已结算</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<div class="flex flex-col">
|
||||
<a-tooltip title="创建时间">
|
||||
<span class="text-gray-500">{{ record.createTime }}</span>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="结算时间">
|
||||
<span class="text-purple-500">{{ record.settleTime }}</span>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="record.isSettled === 0 && record.isInvalid === 0">
|
||||
<a @click="openEdit(record)" class="ele-text-success">
|
||||
结算
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
</template>
|
||||
<a-popconfirm
|
||||
v-if="record.isSettled === 0"
|
||||
title="确定要删除吗?"
|
||||
@confirm="remove(record)"
|
||||
placement="topRight"
|
||||
>
|
||||
<a class="text-red-500">
|
||||
删除
|
||||
</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerOrderEdit v-model:visible="showEdit" :data="current" @done="reload"/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerOrderEdit from './components/shopDealerOrderEdit.vue';
|
||||
import { pageShopDealerOrder, removeShopDealerOrder, removeBatchShopDealerOrder } from '@/api/shop/shopDealerOrder';
|
||||
import type { ShopDealerOrder, ShopDealerOrderParam } from '@/api/shop/shopDealerOrder/model';
|
||||
import {createVNode, ref} from 'vue';
|
||||
import {message, Modal} from 'ant-design-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {EleProTable} from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerOrderEdit from './components/shopDealerOrderEdit.vue';
|
||||
import {pageShopDealerOrder, removeShopDealerOrder, removeBatchShopDealerOrder} from '@/api/shop/shopDealerOrder';
|
||||
import type {ShopDealerOrder, ShopDealerOrderParam} from '@/api/shop/shopDealerOrder/model';
|
||||
import {exportSdyDealerOrder} from "@/api/sdy/sdyDealerOrder";
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerOrder[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerOrder | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerOrder[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerOrder | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
// 当前搜索条件
|
||||
const currentWhere = ref<ShopDealerOrderParam>({});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
// 保存当前搜索条件用于导出
|
||||
currentWhere.value = {...where};
|
||||
// 未结算订单
|
||||
where.isSettled = 0;
|
||||
where.myOrder = 1;
|
||||
return pageShopDealerOrder({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageShopDealerOrder({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '主键ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '买家用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '订单ID',
|
||||
dataIndex: 'orderId',
|
||||
key: 'orderId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '订单总金额(不含运费)',
|
||||
dataIndex: 'orderPrice',
|
||||
key: 'orderPrice',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分销商用户id(一级)',
|
||||
dataIndex: 'firstUserId',
|
||||
key: 'firstUserId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分销商用户id(二级)',
|
||||
dataIndex: 'secondUserId',
|
||||
key: 'secondUserId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分销商用户id(三级)',
|
||||
dataIndex: 'thirdUserId',
|
||||
key: 'thirdUserId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分销佣金(一级)',
|
||||
dataIndex: 'firstMoney',
|
||||
key: 'firstMoney',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分销佣金(二级)',
|
||||
dataIndex: 'secondMoney',
|
||||
key: 'secondMoney',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '分销佣金(三级)',
|
||||
dataIndex: 'thirdMoney',
|
||||
key: 'thirdMoney',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '订单是否失效(0未失效 1已失效)',
|
||||
dataIndex: 'isInvalid',
|
||||
key: 'isInvalid',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '佣金结算(0未结算 1已结算)',
|
||||
dataIndex: 'isSettled',
|
||||
key: 'isSettled',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '结算时间',
|
||||
dataIndex: 'settleTime',
|
||||
key: 'settleTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '订单编号',
|
||||
dataIndex: 'orderNo',
|
||||
key: 'orderNo'
|
||||
},
|
||||
{
|
||||
title: '客户名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
width: 220,
|
||||
},
|
||||
{
|
||||
title: '结算电量',
|
||||
dataIndex: 'orderPrice',
|
||||
key: 'orderPrice',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '换算成度',
|
||||
dataIndex: 'degreePrice',
|
||||
key: 'degreePrice',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '结算单价',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '结算金额',
|
||||
dataIndex: 'settledPrice',
|
||||
key: 'settledPrice',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '税费',
|
||||
dataIndex: 'rate',
|
||||
key: 'rate',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '实发金额',
|
||||
dataIndex: 'payPrice',
|
||||
key: 'payPrice',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '签约状态',
|
||||
dataIndex: 'isInvalid',
|
||||
key: 'isInvalid',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '月份',
|
||||
dataIndex: 'month',
|
||||
key: 'month',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '结算状态',
|
||||
dataIndex: 'isSettled',
|
||||
key: 'isSettled',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerOrderParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerOrderParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({where: where});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerOrder) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
/* 批量结算 */
|
||||
const batchSettle = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
const validOrders = selection.value.filter(order =>
|
||||
order.isSettled === 0 && order.isInvalid === 0
|
||||
);
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerOrder) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerOrder(row.shopDealerOrderId)
|
||||
.then((msg) => {
|
||||
if (!validOrders.length) {
|
||||
message.error('所选订单中没有可结算的订单');
|
||||
return;
|
||||
}
|
||||
|
||||
const totalCommission = validOrders.reduce((sum, order) => {
|
||||
return sum + parseFloat(order.firstMoney || '0') +
|
||||
parseFloat(order.secondMoney || '0') +
|
||||
parseFloat(order.thirdMoney || '0');
|
||||
}, 0).toFixed(2);
|
||||
|
||||
Modal.confirm({
|
||||
title: '批量结算确认',
|
||||
content: `确定要结算选中的 ${validOrders.length} 个订单吗?总佣金金额:¥${totalCommission}`,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
okText: '确认结算',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
const hide = message.loading('正在批量结算...', 0);
|
||||
// 这里调用批量结算API
|
||||
setTimeout(() => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
message.success(`成功结算 ${validOrders.length} 个订单`);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}, 1500);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerOrder(selection.value.map((d) => d.shopDealerOrderId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const handleExport = () => {
|
||||
// 调用导出API,传入当前搜索条件
|
||||
exportSdyDealerOrder(currentWhere.value);
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerOrder) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerOrder) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerOrder(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerOrder(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerOrder) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerOrder) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
query();
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopDealerOrder'
|
||||
};
|
||||
export default {
|
||||
name: 'ShopDealerOrder'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.order-info {
|
||||
.order-id {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.order-price {
|
||||
color: #ff4d4f;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.dealer-info {
|
||||
.dealer-level {
|
||||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.detail-section) {
|
||||
h4 {
|
||||
color: #1890ff;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 4px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:deep(.ant-tag) {
|
||||
margin: 2px 4px 2px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
751
src/views/shop/shopDealerPoster/index.vue
Normal file
751
src/views/shop/shopDealerPoster/index.vue
Normal file
@@ -0,0 +1,751 @@
|
||||
<template>
|
||||
<a-page-header title="分销海报设置" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '24px' }">
|
||||
<div class="poster-container">
|
||||
<!-- 说明信息 -->
|
||||
<a-alert
|
||||
message="分销商海报设置说明"
|
||||
type="info"
|
||||
show-icon
|
||||
class="poster-alert"
|
||||
>
|
||||
<template #description>
|
||||
<div>
|
||||
<p>1. 可根据需要,二维码、昵称等动态位置</p>
|
||||
<p>2. 保存后将海报生成新图片(这里一般不变更),清除图片缓存</p>
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
|
||||
<div class="poster-content">
|
||||
<!-- 左侧海报预览 -->
|
||||
<div class="poster-preview">
|
||||
<div class="poster-canvas" ref="posterCanvasRef">
|
||||
<img
|
||||
:src="posterConfig.backgroundImage || defaultBackground"
|
||||
alt="海报背景"
|
||||
class="poster-bg"
|
||||
@load="onBackgroundLoad"
|
||||
/>
|
||||
|
||||
<!-- 动态元素层 -->
|
||||
<div class="poster-elements">
|
||||
<!-- 头像 -->
|
||||
<div
|
||||
class="poster-avatar draggable-element"
|
||||
:style="getElementStyle('avatar')"
|
||||
v-if="posterConfig.showAvatar"
|
||||
@mousedown="startDrag($event, 'avatar')"
|
||||
>
|
||||
<img :src="posterConfig.avatarUrl || defaultAvatar" alt="头像" />
|
||||
<div class="element-handle">拖拽</div>
|
||||
</div>
|
||||
|
||||
<!-- 昵称 -->
|
||||
<div
|
||||
class="poster-nickname draggable-element"
|
||||
:style="getElementStyle('nickname')"
|
||||
v-if="posterConfig.showNickname"
|
||||
@mousedown="startDrag($event, 'nickname')"
|
||||
>
|
||||
{{ posterConfig.nickname || '这里是昵称' }}
|
||||
<div class="element-handle">拖拽</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码 -->
|
||||
<div
|
||||
class="poster-qrcode draggable-element"
|
||||
:style="getElementStyle('qrcode')"
|
||||
v-if="posterConfig.showQrcode"
|
||||
@mousedown="startDrag($event, 'qrcode')"
|
||||
>
|
||||
<img :src="posterConfig.qrcodeUrl || defaultQrcode" alt="二维码" />
|
||||
<div class="element-handle">拖拽</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧设置面板 -->
|
||||
<div class="poster-settings">
|
||||
<!-- 预设模板 -->
|
||||
<div class="setting-section">
|
||||
<h4>预设模板:</h4>
|
||||
<div class="template-grid">
|
||||
<div
|
||||
v-for="template in templates"
|
||||
:key="template.id"
|
||||
class="template-item"
|
||||
:class="{ active: currentTemplate === template.id }"
|
||||
@click="applyTemplate(template)"
|
||||
>
|
||||
<img :src="template.preview" :alt="template.name" />
|
||||
<span>{{ template.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景图片设置 -->
|
||||
<div class="setting-section">
|
||||
<h4>背景图片:</h4>
|
||||
<div class="background-preview">
|
||||
<img :src="posterConfig.backgroundImage || defaultBackground" alt="背景预览" />
|
||||
</div>
|
||||
<a-upload
|
||||
:file-list="backgroundFileList"
|
||||
list-type="picture"
|
||||
:max-count="1"
|
||||
@change="handleBackgroundChange"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<a-button>
|
||||
<UploadOutlined /> 选择背景图片
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<div class="setting-desc">
|
||||
图片尺寸:宽750像素 高1200<br>
|
||||
请务必按尺寸上传,否则生成的海报会变形
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 头像设置 -->
|
||||
<div class="setting-section">
|
||||
<h4>头像设置:</h4>
|
||||
<div class="setting-row">
|
||||
<span>头像宽度:</span>
|
||||
<a-input-number
|
||||
v-model:value="posterConfig.avatarWidth"
|
||||
:min="20"
|
||||
:max="200"
|
||||
@change="updatePreview"
|
||||
/>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<span>头像样式:</span>
|
||||
<a-radio-group v-model:value="posterConfig.avatarShape" @change="updatePreview">
|
||||
<a-radio value="circle">圆形</a-radio>
|
||||
<a-radio value="square">方形</a-radio>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 昵称设置 -->
|
||||
<div class="setting-section">
|
||||
<h4>昵称字体大小:</h4>
|
||||
<a-input-number
|
||||
v-model:value="posterConfig.nicknameFontSize"
|
||||
:min="12"
|
||||
:max="48"
|
||||
@change="updatePreview"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 昵称颜色设置 -->
|
||||
<div class="setting-section">
|
||||
<h4>昵称字体颜色:</h4>
|
||||
<div class="color-picker">
|
||||
<input
|
||||
type="color"
|
||||
v-model="posterConfig.nicknameColor"
|
||||
@change="updatePreview"
|
||||
/>
|
||||
<span>{{ posterConfig.nicknameColor }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码设置 -->
|
||||
<div class="setting-section">
|
||||
<h4>二维码宽度:</h4>
|
||||
<a-input-number
|
||||
v-model:value="posterConfig.qrcodeWidth"
|
||||
:min="50"
|
||||
:max="200"
|
||||
@change="updatePreview"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="setting-footer">
|
||||
<a-button type="primary" size="large" @click="savePosterConfigData" :loading="saving">
|
||||
保存
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
getCurrentPosterConfig,
|
||||
savePosterConfig,
|
||||
uploadPosterBackground
|
||||
} from '@/api/shop/shopDealerPoster';
|
||||
import type { PosterConfig } from '@/api/shop/shopDealerPoster/model';
|
||||
|
||||
// 海报配置
|
||||
const posterConfig = reactive({
|
||||
backgroundImage: 'https://pro2.yiovo.com/assets/store/img/dealer/backdrop.png',
|
||||
showAvatar: true,
|
||||
avatarUrl: 'https://pro2.yiovo.com/assets/store/img/dealer/avatar.png',
|
||||
avatarWidth: 50,
|
||||
avatarShape: 'circle',
|
||||
showNickname: true,
|
||||
nickname: '这里是昵称',
|
||||
nicknameFontSize: 12,
|
||||
nicknameColor: '#000000',
|
||||
showQrcode: true,
|
||||
qrcodeUrl: '',
|
||||
qrcodeWidth: 66,
|
||||
// 元素位置配置
|
||||
elements: {
|
||||
avatar: { x: 50, y: 50 },
|
||||
nickname: { x: 120, y: 65 },
|
||||
qrcode: { x: 300, y: 500 }
|
||||
}
|
||||
});
|
||||
|
||||
// 默认资源 - 使用在线图片作为占位符
|
||||
const defaultBackground = 'https://via.placeholder.com/750x1200/ff6b35/ffffff?text=分享赚钱';
|
||||
const defaultAvatar = 'https://via.placeholder.com/100x100/4CAF50/ffffff?text=头像';
|
||||
const defaultQrcode = 'https://via.placeholder.com/100x100/2196F3/ffffff?text=二维码';
|
||||
|
||||
// 状态
|
||||
const saving = ref(false);
|
||||
const backgroundFileList = ref([]);
|
||||
const posterCanvasRef = ref();
|
||||
|
||||
// 拖拽状态
|
||||
const dragging = ref(false);
|
||||
const dragElement = ref('');
|
||||
const dragStart = ref({ x: 0, y: 0 });
|
||||
const elementStart = ref({ x: 0, y: 0 });
|
||||
|
||||
// 当前模板
|
||||
const currentTemplate = ref(1);
|
||||
|
||||
// 预设模板
|
||||
const templates = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '经典模板',
|
||||
preview: 'https://via.placeholder.com/120x180/ff6b35/ffffff?text=经典',
|
||||
config: {
|
||||
backgroundImage: 'https://via.placeholder.com/750x1200/ff6b35/ffffff?text=分享赚钱',
|
||||
elements: {
|
||||
avatar: { x: 50, y: 50 },
|
||||
nickname: { x: 120, y: 65 },
|
||||
qrcode: { x: 300, y: 500 }
|
||||
},
|
||||
avatarWidth: 50,
|
||||
nicknameFontSize: 16,
|
||||
nicknameColor: '#ffffff',
|
||||
qrcodeWidth: 100
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '简约模板',
|
||||
preview: 'https://via.placeholder.com/120x180/2196F3/ffffff?text=简约',
|
||||
config: {
|
||||
backgroundImage: 'https://via.placeholder.com/750x1200/2196F3/ffffff?text=简约风格',
|
||||
elements: {
|
||||
avatar: { x: 325, y: 100 },
|
||||
nickname: { x: 300, y: 200 },
|
||||
qrcode: { x: 325, y: 800 }
|
||||
},
|
||||
avatarWidth: 80,
|
||||
nicknameFontSize: 20,
|
||||
nicknameColor: '#ffffff',
|
||||
qrcodeWidth: 120
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '活力模板',
|
||||
preview: 'https://via.placeholder.com/120x180/4CAF50/ffffff?text=活力',
|
||||
config: {
|
||||
backgroundImage: 'https://via.placeholder.com/750x1200/4CAF50/ffffff?text=活力四射',
|
||||
elements: {
|
||||
avatar: { x: 100, y: 300 },
|
||||
nickname: { x: 200, y: 320 },
|
||||
qrcode: { x: 500, y: 300 }
|
||||
},
|
||||
avatarWidth: 60,
|
||||
nicknameFontSize: 18,
|
||||
nicknameColor: '#ffffff',
|
||||
qrcodeWidth: 80
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
/* 获取元素样式 */
|
||||
const getElementStyle = (elementType: string) => {
|
||||
const element = posterConfig.elements[elementType];
|
||||
const baseStyle = {
|
||||
position: 'absolute',
|
||||
left: `${element.x}px`,
|
||||
top: `${element.y}px`
|
||||
};
|
||||
|
||||
switch (elementType) {
|
||||
case 'avatar':
|
||||
return {
|
||||
...baseStyle,
|
||||
width: `${posterConfig.avatarWidth}px`,
|
||||
height: `${posterConfig.avatarWidth}px`,
|
||||
borderRadius: posterConfig.avatarShape === 'circle' ? '50%' : '4px',
|
||||
overflow: 'hidden'
|
||||
};
|
||||
case 'nickname':
|
||||
return {
|
||||
...baseStyle,
|
||||
fontSize: `${posterConfig.nicknameFontSize}px`,
|
||||
color: posterConfig.nicknameColor,
|
||||
fontWeight: 'bold'
|
||||
};
|
||||
case 'qrcode':
|
||||
return {
|
||||
...baseStyle,
|
||||
width: `${posterConfig.qrcodeWidth}px`,
|
||||
height: `${posterConfig.qrcodeWidth}px`
|
||||
};
|
||||
default:
|
||||
return baseStyle;
|
||||
}
|
||||
};
|
||||
|
||||
/* 背景图片加载完成 */
|
||||
const onBackgroundLoad = () => {
|
||||
updatePreview();
|
||||
};
|
||||
|
||||
/* 更新预览 */
|
||||
const updatePreview = () => {
|
||||
// 这里可以添加实时预览更新逻辑
|
||||
console.log('更新预览');
|
||||
};
|
||||
|
||||
/* 应用模板 */
|
||||
const applyTemplate = (template: any) => {
|
||||
currentTemplate.value = template.id;
|
||||
|
||||
// 应用模板配置
|
||||
posterConfig.backgroundImage = template.config.backgroundImage;
|
||||
posterConfig.elements = { ...template.config.elements };
|
||||
posterConfig.avatarWidth = template.config.avatarWidth;
|
||||
posterConfig.nicknameFontSize = template.config.nicknameFontSize;
|
||||
posterConfig.nicknameColor = template.config.nicknameColor;
|
||||
posterConfig.qrcodeWidth = template.config.qrcodeWidth;
|
||||
|
||||
updatePreview();
|
||||
message.success(`已应用${template.name}`);
|
||||
};
|
||||
|
||||
/* 开始拖拽 */
|
||||
const startDrag = (event: MouseEvent, elementType: string) => {
|
||||
event.preventDefault();
|
||||
dragging.value = true;
|
||||
dragElement.value = elementType;
|
||||
|
||||
dragStart.value = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
|
||||
const element = posterConfig.elements[elementType];
|
||||
elementStart.value = {
|
||||
x: element.x,
|
||||
y: element.y
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', onDrag);
|
||||
document.addEventListener('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
/* 拖拽中 */
|
||||
const onDrag = (event: MouseEvent) => {
|
||||
if (!dragging.value || !dragElement.value) return;
|
||||
|
||||
const deltaX = event.clientX - dragStart.value.x;
|
||||
const deltaY = event.clientY - dragStart.value.y;
|
||||
|
||||
// 计算新位置,考虑缩放比例
|
||||
const scale = 0.4; // 预览区域的缩放比例
|
||||
const newX = Math.max(0, Math.min(750 - 100, elementStart.value.x + deltaX / scale));
|
||||
const newY = Math.max(0, Math.min(1200 - 100, elementStart.value.y + deltaY / scale));
|
||||
|
||||
posterConfig.elements[dragElement.value] = {
|
||||
x: Math.round(newX),
|
||||
y: Math.round(newY)
|
||||
};
|
||||
};
|
||||
|
||||
/* 停止拖拽 */
|
||||
const stopDrag = () => {
|
||||
dragging.value = false;
|
||||
dragElement.value = '';
|
||||
|
||||
document.removeEventListener('mousemove', onDrag);
|
||||
document.removeEventListener('mouseup', stopDrag);
|
||||
};
|
||||
|
||||
/* 背景图片上传前检查 */
|
||||
const beforeUpload = (file: File) => {
|
||||
const isImage = file.type.startsWith('image/');
|
||||
if (!isImage) {
|
||||
message.error('只能上传图片文件!');
|
||||
return false;
|
||||
}
|
||||
|
||||
const isLt5M = file.size / 1024 / 1024 < 5;
|
||||
if (!isLt5M) {
|
||||
message.error('图片大小不能超过 5MB!');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/* 背景图片变更 */
|
||||
const handleBackgroundChange = async (info: any) => {
|
||||
backgroundFileList.value = info.fileList;
|
||||
|
||||
if (info.file.status === 'uploading') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.file.originFileObj) {
|
||||
try {
|
||||
const result = await uploadPosterBackground(info.file.originFileObj);
|
||||
posterConfig.backgroundImage = result.url;
|
||||
message.success('背景图片上传成功');
|
||||
updatePreview();
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error);
|
||||
message.error('背景图片上传失败');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* 保存海报配置 */
|
||||
const savePosterConfigData = async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
const configData: PosterConfig = {
|
||||
backgroundImage: posterConfig.backgroundImage,
|
||||
width: 750,
|
||||
height: 1200,
|
||||
showAvatar: posterConfig.showAvatar,
|
||||
avatarUrl: posterConfig.avatarUrl,
|
||||
avatarWidth: posterConfig.avatarWidth,
|
||||
avatarShape: posterConfig.avatarShape,
|
||||
showNickname: posterConfig.showNickname,
|
||||
nickname: posterConfig.nickname,
|
||||
nicknameFontSize: posterConfig.nicknameFontSize,
|
||||
nicknameColor: posterConfig.nicknameColor,
|
||||
showQrcode: posterConfig.showQrcode,
|
||||
qrcodeUrl: posterConfig.qrcodeUrl,
|
||||
qrcodeWidth: posterConfig.qrcodeWidth,
|
||||
elements: posterConfig.elements
|
||||
};
|
||||
|
||||
await savePosterConfig(configData);
|
||||
message.success('海报配置保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
message.error('保存失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* 加载海报配置 */
|
||||
const loadPosterConfig = async () => {
|
||||
try {
|
||||
const config = await getCurrentPosterConfig();
|
||||
if (config) {
|
||||
Object.assign(posterConfig, config);
|
||||
updatePreview();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
// 使用默认配置,不显示错误信息
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadPosterConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopDealerPoster'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.poster-container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.poster-alert {
|
||||
margin-bottom: 24px;
|
||||
|
||||
:deep(.ant-alert-description) {
|
||||
p {
|
||||
margin: 4px 0;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.poster-preview {
|
||||
flex: 0 0 400px;
|
||||
|
||||
.poster-canvas {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
height: 480px;
|
||||
border: 2px solid #d9d9d9;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #f5f5f5;
|
||||
margin: 0 auto;
|
||||
|
||||
.poster-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.poster-elements {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.draggable-element {
|
||||
pointer-events: auto;
|
||||
cursor: move;
|
||||
border: 2px dashed transparent;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
|
||||
.element-handle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.element-handle {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-avatar {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.poster-nickname {
|
||||
white-space: nowrap;
|
||||
text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
|
||||
padding: 4px 8px;
|
||||
background: rgba(255,255,255,0.8);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.poster-qrcode {
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.poster-settings {
|
||||
flex: 1;
|
||||
|
||||
.setting-section {
|
||||
margin-bottom: 24px;
|
||||
padding: 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 6px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 12px 0;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.background-preview {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
.template-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border: 2px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1890ff;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #1890ff;
|
||||
background: #f0f8ff;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 60px;
|
||||
height: 90px;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
span {
|
||||
width: 100px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
input[type="color"] {
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-family: monospace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.setting-footer {
|
||||
text-align: center;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-upload-list) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
:deep(.ant-radio-group) {
|
||||
.ant-radio-wrapper {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 1200px) {
|
||||
.poster-content {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.poster-preview {
|
||||
flex: none;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.poster-settings {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
205
src/views/shop/shopDealerReferee/components/RefereeTree.vue
Normal file
205
src/views/shop/shopDealerReferee/components/RefereeTree.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:visible="visible"
|
||||
:title="title"
|
||||
:width="1000"
|
||||
:footer="null"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<div class="tree-container">
|
||||
<v-chart
|
||||
ref="chartRef"
|
||||
class="chart"
|
||||
:option="chartOption"
|
||||
:loading="loading"
|
||||
autoresize
|
||||
/>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, computed } from 'vue';
|
||||
import { use } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { TreeChart } from 'echarts/charts';
|
||||
import {
|
||||
TooltipComponent,
|
||||
TitleComponent
|
||||
} from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import type { ShopDealerReferee } from '@/api/shop/shopDealerReferee/model';
|
||||
|
||||
// 注册 echarts 组件
|
||||
use([
|
||||
CanvasRenderer,
|
||||
TreeChart,
|
||||
TooltipComponent,
|
||||
TitleComponent
|
||||
]);
|
||||
|
||||
// 定义组件属性
|
||||
const props = withDefaults(defineProps<{
|
||||
visible: boolean;
|
||||
data?: ShopDealerReferee[];
|
||||
title?: string;
|
||||
}>(), {
|
||||
visible: false,
|
||||
data: () => [],
|
||||
title: '推荐关系树'
|
||||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
(e: 'cancel'): void;
|
||||
}>();
|
||||
|
||||
// 图表引用
|
||||
const chartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 处理取消事件
|
||||
const handleCancel = () => {
|
||||
emit('update:visible', false);
|
||||
emit('cancel');
|
||||
};
|
||||
|
||||
// 转换数据为树形结构
|
||||
const transformToTreeData = (data: ShopDealerReferee[]) => {
|
||||
if (!data || data.length === 0) {
|
||||
return { name: '暂无数据', children: [] };
|
||||
}
|
||||
|
||||
// 构建节点映射
|
||||
const nodeMap = new Map<number, any>();
|
||||
const rootNodes: any[] = [];
|
||||
|
||||
// 创建所有节点
|
||||
data.forEach(item => {
|
||||
// 推荐人节点
|
||||
if (item.dealerId && !nodeMap.has(item.dealerId)) {
|
||||
nodeMap.set(item.dealerId, {
|
||||
id: item.dealerId,
|
||||
name: `推荐人\nID:${item.dealerId}\n${item.dealerName || ''}`,
|
||||
level: 'dealer',
|
||||
children: []
|
||||
});
|
||||
}
|
||||
|
||||
// 被推荐人节点
|
||||
if (item.userId && !nodeMap.has(item.userId)) {
|
||||
nodeMap.set(item.userId, {
|
||||
id: item.userId,
|
||||
name: `被推荐人\nID:${item.userId}\n${item.nickname || ''}`,
|
||||
level: 'user',
|
||||
children: []
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 构建关系树
|
||||
data.forEach(item => {
|
||||
if (!item.dealerId || !item.userId) return;
|
||||
|
||||
const dealerNode = nodeMap.get(item.dealerId);
|
||||
const userNode = nodeMap.get(item.userId);
|
||||
|
||||
if (dealerNode && userNode) {
|
||||
// 添加层级标签
|
||||
const levelText = { 1: '一级', 2: '二级', 3: '三级' }[item.level || 0] || `${item.level || 0}级`;
|
||||
userNode.name += `\n${levelText}推荐`;
|
||||
dealerNode.children.push(userNode);
|
||||
}
|
||||
});
|
||||
|
||||
// 查找根节点(没有被推荐关系的节点)
|
||||
const referencedIds = new Set(data.map(item => item.userId).filter(id => id));
|
||||
data.forEach(item => {
|
||||
if (item.dealerId && !referencedIds.has(item.dealerId)) {
|
||||
const rootNode = nodeMap.get(item.dealerId);
|
||||
if (rootNode) {
|
||||
rootNodes.push(rootNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有明确的根节点,使用第一个节点作为根
|
||||
if (rootNodes.length === 0 && data.length > 0 && data[0].dealerId) {
|
||||
const firstNode = nodeMap.get(data[0].dealerId);
|
||||
if (firstNode) {
|
||||
rootNodes.push(firstNode);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果还是没有根节点,返回默认节点
|
||||
if (rootNodes.length === 0) {
|
||||
return { name: '暂无数据', children: [] };
|
||||
}
|
||||
|
||||
return rootNodes[0];
|
||||
};
|
||||
|
||||
// 图表配置
|
||||
const chartOption = computed(() => {
|
||||
const treeData = transformToTreeData(props.data || []);
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
triggerOn: 'mousemove'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'tree',
|
||||
data: [treeData],
|
||||
top: '1%',
|
||||
left: '7%',
|
||||
bottom: '1%',
|
||||
right: '20%',
|
||||
symbolSize: 12,
|
||||
symbol: 'circle',
|
||||
orient: 'LR', // 从左到右
|
||||
expandAndCollapse: true,
|
||||
label: {
|
||||
position: 'left',
|
||||
verticalAlign: 'middle',
|
||||
align: 'right',
|
||||
fontSize: 12,
|
||||
backgroundColor: '#fff',
|
||||
padding: [2, 4],
|
||||
borderRadius: 4,
|
||||
borderWidth: 1,
|
||||
borderColor: '#ccc'
|
||||
},
|
||||
leaves: {
|
||||
label: {
|
||||
position: 'right',
|
||||
verticalAlign: 'middle',
|
||||
align: 'left'
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'descendant'
|
||||
},
|
||||
animationDuration: 500,
|
||||
animationDurationUpdate: 750
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tree-container {
|
||||
height: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,42 +1,183 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="searchForm"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item label="推荐人ID">
|
||||
<a-input-number
|
||||
v-model:value="searchForm.dealerId"
|
||||
placeholder="请输入推荐人ID"
|
||||
:min="1"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="被推荐人ID">
|
||||
<a-input-number
|
||||
v-model:value="searchForm.userId"
|
||||
placeholder="请输入被推荐人ID"
|
||||
:min="1"
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="建立时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" html-type="submit" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<SearchOutlined/>
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="resetSearch">
|
||||
重置
|
||||
</a-button>
|
||||
<a-button @click="exportData" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<ExportOutlined/>
|
||||
</template>
|
||||
导出
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<!-- <div class="action-buttons">-->
|
||||
<!-- <a-space>-->
|
||||
<!-- <a-button type="primary" @click="add" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <PlusOutlined/>-->
|
||||
<!-- </template>-->
|
||||
<!-- 建立推荐关系-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <a-button @click="viewTree" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <ApartmentOutlined/>-->
|
||||
<!-- </template>-->
|
||||
<!-- 推荐关系树-->
|
||||
<!-- </a-button>-->
|
||||
<!-- <a-button @click="exportData" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <ExportOutlined/>-->
|
||||
<!-- </template>-->
|
||||
<!-- 导出数据-->
|
||||
<!-- </a-button>-->
|
||||
<!-- </a-space>-->
|
||||
<!-- </div>-->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
import {reactive} from 'vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
ApartmentOutlined,
|
||||
ExportOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {ShopDealerRefereeParam} from '@/api/shop/shopDealerReferee/model';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的数据
|
||||
selection?: any[];
|
||||
}>(),
|
||||
{
|
||||
selection: () => []
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ShopDealerRefereeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'viewTree'): void;
|
||||
(e: 'export'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
// 搜索表单
|
||||
const searchForm = reactive<any>({
|
||||
dealerId: undefined,
|
||||
userId: undefined,
|
||||
level: undefined,
|
||||
dateRange: undefined
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const searchParams: ShopDealerRefereeParam = {};
|
||||
|
||||
if (searchForm.dealerId) {
|
||||
searchParams.dealerId = searchForm.dealerId;
|
||||
}
|
||||
if (searchForm.userId) {
|
||||
searchParams.userId = searchForm.userId;
|
||||
}
|
||||
if (searchForm.level) {
|
||||
searchParams.level = searchForm.level;
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
searchParams.startTime = dayjs(searchForm.dateRange[0]).format('YYYY-MM-DD');
|
||||
searchParams.endTime = dayjs(searchForm.dateRange[1]).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
emit('search', searchParams);
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchForm.dealerId = undefined;
|
||||
searchForm.userId = undefined;
|
||||
searchForm.level = undefined;
|
||||
searchForm.dateRange = undefined;
|
||||
emit('search', {});
|
||||
};
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
// 查看推荐树
|
||||
const viewTree = () => {
|
||||
emit('viewTree');
|
||||
};
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
emit('export');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search-container {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商推荐关系表' : '添加分销商推荐关系表'"
|
||||
:title="isUpdate ? '编辑推荐' : '添加推荐关系'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -19,34 +19,20 @@
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="分销商用户ID" name="dealerId">
|
||||
<a-form-item label="推荐人信息" name="dealerId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销商用户ID"
|
||||
v-model:value="form.dealerId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户id(被推荐人)" name="userId">
|
||||
<a-form-item label="被推荐人信息" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户id(被推荐人)"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="推荐关系层级(1,2,3)" name="level">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入推荐关系层级(1,2,3)"
|
||||
v-model:value="form.level"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
@@ -54,14 +40,13 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addShopDealerReferee, updateShopDealerReferee } from '@/api/shop/shopDealerReferee';
|
||||
import { ShopDealerReferee } from '@/api/shop/shopDealerReferee/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
@@ -98,12 +83,7 @@
|
||||
level: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopDealerRefereeId: undefined,
|
||||
shopDealerRefereeName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
@@ -123,20 +103,6 @@
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
@@ -174,13 +140,6 @@
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerRefereeId"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
@@ -14,29 +14,69 @@
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
@viewTree="viewRefereeTree"
|
||||
@export="exportData"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
<template v-if="column.key === 'dealerInfo'">
|
||||
<div class="user-info">
|
||||
<div class="user-id">{{ record.dealerName }}({{ record.dealerId }})</div>
|
||||
<div class="user-id"></div>
|
||||
<div class="user-role">
|
||||
<a-tag color="blue">推荐人</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
|
||||
<template v-if="column.key === 'relationship'">
|
||||
<ArrowRightOutlined />
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'userInfo'">
|
||||
<div class="user-info">
|
||||
<div class="user-id">{{ record.nickname }}({{ record.userId }})</div>
|
||||
<div class="user-role">
|
||||
<a-tag color="green">被推荐人</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'level'">
|
||||
<a-tag
|
||||
:color="getLevelColor(record.level || 0)"
|
||||
class="level-tag"
|
||||
>
|
||||
{{ getLevelText(record.level || 0) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'relationChain'">
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="viewRelationChain(record)"
|
||||
class="chain-btn"
|
||||
>
|
||||
<TeamOutlined /> 查看关系链
|
||||
</a-button>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
<a @click="openEdit(record)" class="ele-text-primary">
|
||||
<EditOutlined /> 编辑
|
||||
</a>
|
||||
<!-- <a-divider type="vertical" />-->
|
||||
<!-- <a-popconfirm-->
|
||||
<!-- title="确定要解除此推荐关系吗?"-->
|
||||
<!-- @confirm="remove(record)"-->
|
||||
<!-- placement="topRight"-->
|
||||
<!-- >-->
|
||||
<!-- <a class="ele-text-danger">-->
|
||||
<!-- <DisconnectOutlined /> 解除-->
|
||||
<!-- </a>-->
|
||||
<!-- </a-popconfirm>-->
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
@@ -45,13 +85,26 @@
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerRefereeEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
|
||||
<!-- 树状图弹窗 -->
|
||||
<RefereeTree
|
||||
v-model:visible="showTree"
|
||||
:data="treeData"
|
||||
:title="'推荐关系树'"
|
||||
@cancel="showTree = false"
|
||||
/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
TeamOutlined,
|
||||
EditOutlined,
|
||||
ArrowRightOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
@@ -61,7 +114,14 @@
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerRefereeEdit from './components/shopDealerRefereeEdit.vue';
|
||||
import { pageShopDealerReferee, removeShopDealerReferee, removeBatchShopDealerReferee } from '@/api/shop/shopDealerReferee';
|
||||
import RefereeTree from './components/RefereeTree.vue';
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import {
|
||||
pageShopDealerReferee,
|
||||
removeShopDealerReferee,
|
||||
removeBatchShopDealerReferee,
|
||||
listShopDealerReferee
|
||||
} from '@/api/shop/shopDealerReferee';
|
||||
import type { ShopDealerReferee, ShopDealerRefereeParam } from '@/api/shop/shopDealerReferee/model';
|
||||
|
||||
// 表格实例
|
||||
@@ -73,8 +133,10 @@
|
||||
const current = ref<ShopDealerReferee | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 是否显示树状图弹窗
|
||||
const showTree = ref(false);
|
||||
// 树状图数据
|
||||
const treeData = ref<ShopDealerReferee[]>([]);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
@@ -100,55 +162,183 @@
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '主键ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
title: '推荐人信息',
|
||||
key: 'dealerInfo',
|
||||
align: 'left',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '分销商用户ID',
|
||||
dataIndex: 'dealerId',
|
||||
key: 'dealerId',
|
||||
title: '',
|
||||
key: 'relationship',
|
||||
align: 'center',
|
||||
width: 50
|
||||
},
|
||||
{
|
||||
title: '用户id(被推荐人)',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
title: '被推荐人信息',
|
||||
key: 'userInfo',
|
||||
align: 'left',
|
||||
width: 150
|
||||
},
|
||||
// {
|
||||
// title: '推荐层级',
|
||||
// key: 'level',
|
||||
// align: 'center',
|
||||
// width: 120,
|
||||
// filters: [
|
||||
// { text: '一级推荐', value: 1 },
|
||||
// { text: '二级推荐', value: 2 },
|
||||
// { text: '三级推荐', value: 3 }
|
||||
// ]
|
||||
// },
|
||||
{
|
||||
title: '推荐关系层级(1,2,3)',
|
||||
dataIndex: 'level',
|
||||
key: 'level',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
title: '建立时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
width: 200,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 获取层级颜色 */
|
||||
const getLevelColor = (level: number) => {
|
||||
const colors = {
|
||||
1: 'red',
|
||||
2: 'orange',
|
||||
3: 'gold'
|
||||
};
|
||||
return colors[level] || 'default';
|
||||
};
|
||||
|
||||
/* 获取层级文本 */
|
||||
const getLevelText = (level: number) => {
|
||||
const texts = {
|
||||
1: '一级推荐',
|
||||
2: '二级推荐',
|
||||
3: '三级推荐'
|
||||
};
|
||||
return texts[level] || `${level}级推荐`;
|
||||
};
|
||||
|
||||
/* 查看关系链 */
|
||||
const viewRelationChain = (record: ShopDealerReferee) => {
|
||||
// 这里可以调用API获取完整的推荐关系链
|
||||
Modal.info({
|
||||
title: '推荐关系链',
|
||||
width: 800,
|
||||
content: createVNode('div', { class: 'relation-chain' }, [
|
||||
createVNode('div', { class: 'chain-item' }, [
|
||||
createVNode('div', { class: 'chain-node dealer' }, [
|
||||
createVNode('div', { class: 'node-title' }, '推荐人'),
|
||||
createVNode('div', { class: 'node-id' }, `用户ID: ${record.dealerId}`),
|
||||
createVNode('div', { class: 'node-level' }, '分销商')
|
||||
]),
|
||||
createVNode('div', { class: 'chain-arrow' }, '→'),
|
||||
createVNode('div', { class: 'chain-node user' }, [
|
||||
createVNode('div', { class: 'node-title' }, '被推荐人'),
|
||||
createVNode('div', { class: 'node-id' }, `用户ID: ${record.userId}`),
|
||||
createVNode('div', { class: 'node-level' }, getLevelText(record.level || 0))
|
||||
])
|
||||
]),
|
||||
createVNode('div', { class: 'chain-info' }, [
|
||||
createVNode('p', null, `推荐关系建立于: ${toDateString(record.createTime, 'yyyy-MM-dd HH:mm:ss')}`),
|
||||
createVNode('p', null, '点击可查看更多上下级关系')
|
||||
])
|
||||
]),
|
||||
okText: '关闭'
|
||||
});
|
||||
};
|
||||
|
||||
/* 查看推荐树 */
|
||||
const viewRefereeTree = () => {
|
||||
// 加载所有数据用于树状图展示
|
||||
loading.value = true;
|
||||
pageShopDealerReferee({ page: 1, limit: 10000 }) // 获取所有数据
|
||||
.then((result) => {
|
||||
treeData.value = result?.list || [];
|
||||
showTree.value = true;
|
||||
loading.value = false;
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error('加载数据失败: ' + e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const exportData = async () => {
|
||||
try {
|
||||
// 定义表头
|
||||
const array: (string | number)[][] = [
|
||||
[
|
||||
'推荐人',
|
||||
'推荐人ID',
|
||||
'推荐人电话',
|
||||
'被推荐人',
|
||||
'被推荐人ID',
|
||||
'被推荐人电话',
|
||||
'创建时间'
|
||||
]
|
||||
];
|
||||
|
||||
// 获取用户列表数据
|
||||
const list = await listShopDealerReferee({});
|
||||
|
||||
if (!list || list.length === 0) {
|
||||
message.warning('没有数据可以导出');
|
||||
return;
|
||||
}
|
||||
|
||||
// 将数据转换为Excel行
|
||||
list.forEach((user: ShopDealerReferee) => {
|
||||
array.push([
|
||||
`${user.dealerName}`,
|
||||
`${user.dealerId}`,
|
||||
`${user.dealerPhone}`,
|
||||
`${user.nickname}`,
|
||||
`${user.userId}`,
|
||||
`${user.phone}`,
|
||||
`${user.createTime}`
|
||||
]);
|
||||
});
|
||||
|
||||
// 生成Excel文件
|
||||
const sheetName = `shop_dealer_referee`;
|
||||
const workbook = {
|
||||
SheetNames: [sheetName],
|
||||
Sheets: {}
|
||||
};
|
||||
const sheet = utils.aoa_to_sheet(array);
|
||||
workbook.Sheets[sheetName] = sheet;
|
||||
|
||||
// 设置列宽
|
||||
sheet['!cols'] = [
|
||||
{ wch: 10 }, // 用户ID
|
||||
{ wch: 15 }, // 账号
|
||||
];
|
||||
|
||||
message.loading('正在生成Excel文件...', 0);
|
||||
|
||||
setTimeout(() => {
|
||||
writeFile(workbook, `${sheetName}.xlsx`);
|
||||
message.destroy();
|
||||
message.success(`成功导出 ${list.length} 条记录`);
|
||||
}, 1000);
|
||||
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '导出失败');
|
||||
}
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerRefereeParam) => {
|
||||
selection.value = [];
|
||||
@@ -161,15 +351,10 @@
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerReferee) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerReferee(row.shopDealerRefereeId)
|
||||
removeShopDealerReferee(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
@@ -194,7 +379,7 @@
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerReferee(selection.value.map((d) => d.shopDealerRefereeId))
|
||||
removeBatchShopDealerReferee(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
@@ -235,4 +420,118 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.user-info {
|
||||
.user-id {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.level-tag {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chain-btn {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:deep(.referee-detail) {
|
||||
.detail-section {
|
||||
h4 {
|
||||
color: #1890ff;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 8px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.level-badge {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.relation-chain) {
|
||||
.chain-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.chain-node {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
min-width: 120px;
|
||||
|
||||
&.dealer {
|
||||
background: #e6f7ff;
|
||||
border: 2px solid #1890ff;
|
||||
}
|
||||
|
||||
&.user {
|
||||
background: #f6ffed;
|
||||
border: 2px solid #52c41a;
|
||||
}
|
||||
|
||||
.node-title {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.node-id {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.node-level {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.chain-arrow {
|
||||
font-size: 24px;
|
||||
color: #1890ff;
|
||||
margin: 0 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chain-info {
|
||||
background: #fafafa;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-top: 16px;
|
||||
|
||||
p {
|
||||
margin: 4px 0;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr > td) {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
:deep(.ant-tag) {
|
||||
margin: 2px 4px 2px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:width="1000"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商设置表' : '添加分销商设置表'"
|
||||
:title="isUpdate ? '编辑分销商设置' : '新增分销商设置'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -14,31 +14,282 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
>
|
||||
<a-form-item label="设置项描述" name="describe">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入设置项描述"
|
||||
v-model:value="form.describe"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="设置内容(json格式)" name="values">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入设置内容(json格式)"
|
||||
v-model:value="form.values"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="更新时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入更新时间"
|
||||
v-model:value="form.updateTime"
|
||||
<!-- 基本信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">基本信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="设置标识" name="key">
|
||||
<a-select
|
||||
v-model:value="form.key"
|
||||
placeholder="请选择设置标识"
|
||||
@change="onSettingKeyChange"
|
||||
>
|
||||
<a-select-option value="commission_rate">
|
||||
<div class="setting-option">
|
||||
<a-tag color="blue">佣金比例</a-tag>
|
||||
<span>分销佣金比例设置</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option value="withdraw_config">
|
||||
<div class="setting-option">
|
||||
<a-tag color="green">提现配置</a-tag>
|
||||
<span>提现相关参数设置</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option value="level_config">
|
||||
<div class="setting-option">
|
||||
<a-tag color="orange">等级配置</a-tag>
|
||||
<span>分销商等级设置</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option value="reward_config">
|
||||
<div class="setting-option">
|
||||
<a-tag color="purple">奖励配置</a-tag>
|
||||
<span>推广奖励设置</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option value="other">
|
||||
<div class="setting-option">
|
||||
<a-tag color="default">其他设置</a-tag>
|
||||
<span>自定义设置项</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="设置描述" name="describe">
|
||||
<a-input
|
||||
placeholder="请输入设置项描述"
|
||||
v-model:value="form.describe"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 设置内容 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">设置内容</span>
|
||||
</a-divider>
|
||||
|
||||
<!-- 预设配置模板 -->
|
||||
<div v-if="form.key && form.key !== 'other'" class="config-template">
|
||||
<a-alert
|
||||
:message="getTemplateTitle()"
|
||||
:description="getTemplateDescription()"
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
|
||||
<!-- 佣金比例配置 -->
|
||||
<div v-if="form.key === 'commission_rate'" class="commission-config">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="一级佣金比例">
|
||||
<a-input-number
|
||||
v-model:value="configData.firstRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>%</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="二级佣金比例">
|
||||
<a-input-number
|
||||
v-model:value="configData.secondRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>%</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="三级佣金比例">
|
||||
<a-input-number
|
||||
v-model:value="configData.thirdRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>%</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 提现配置 -->
|
||||
<div v-if="form.key === 'withdraw_config'" class="withdraw-config">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="最小提现金额">
|
||||
<a-input-number
|
||||
v-model:value="configData.minAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="手续费比例">
|
||||
<a-input-number
|
||||
v-model:value="configData.feeRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>%</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="审核方式">
|
||||
<a-select v-model:value="configData.auditType" style="width: 100%">
|
||||
<a-select-option :value="1">自动审核</a-select-option>
|
||||
<a-select-option :value="2">人工审核</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 等级配置 -->
|
||||
<div v-if="form.key === 'level_config'" class="level-config">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="升级条件">
|
||||
<a-select v-model:value="configData.upgradeType" style="width: 100%">
|
||||
<a-select-option :value="1">按推广人数</a-select-option>
|
||||
<a-select-option :value="2">按累计佣金</a-select-option>
|
||||
<a-select-option :value="3">按订单数量</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="升级阈值">
|
||||
<a-input-number
|
||||
v-model:value="configData.upgradeThreshold"
|
||||
:min="0"
|
||||
placeholder="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 奖励配置 -->
|
||||
<div v-if="form.key === 'reward_config'" class="reward-config">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item label="推广奖励">
|
||||
<a-input-number
|
||||
v-model:value="configData.promotionReward"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="首单奖励">
|
||||
<a-input-number
|
||||
v-model:value="configData.firstOrderReward"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item label="月度奖励">
|
||||
<a-input-number
|
||||
v-model:value="configData.monthlyReward"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JSON 编辑器 -->
|
||||
<a-form-item label="配置内容" name="values">
|
||||
<div class="json-editor-container">
|
||||
<div class="json-editor-header">
|
||||
<span>JSON 配置</span>
|
||||
<a-space>
|
||||
<a-button size="small" @click="formatJson">
|
||||
<template #icon>
|
||||
<FormatPainterOutlined />
|
||||
</template>
|
||||
格式化
|
||||
</a-button>
|
||||
<a-button size="small" @click="validateJson">
|
||||
<template #icon>
|
||||
<CheckCircleOutlined />
|
||||
</template>
|
||||
验证
|
||||
</a-button>
|
||||
<a-button size="small" @click="resetToTemplate" v-if="form.key && form.key !== 'other'">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</template>
|
||||
重置为模板
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-textarea
|
||||
v-model:value="form.values"
|
||||
placeholder="请输入JSON格式的配置内容"
|
||||
:rows="12"
|
||||
class="json-editor"
|
||||
@blur="onJsonBlur"
|
||||
/>
|
||||
<div class="json-status" v-if="jsonStatus">
|
||||
<a-alert
|
||||
:type="jsonStatus.type"
|
||||
:message="jsonStatus.message"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
@@ -47,21 +298,19 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import {
|
||||
FormatPainterOutlined,
|
||||
CheckCircleOutlined,
|
||||
ReloadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addShopDealerSetting, updateShopDealerSetting } from '@/api/shop/shopDealerSetting';
|
||||
import { ShopDealerSetting } from '@/api/shop/shopDealerSetting/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
@@ -81,22 +330,38 @@
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
// 表单数据
|
||||
const form = reactive<ShopDealerSetting>({
|
||||
key: undefined,
|
||||
describe: undefined,
|
||||
values: undefined,
|
||||
describe: '',
|
||||
values: '',
|
||||
tenantId: undefined,
|
||||
updateTime: undefined,
|
||||
shopDealerSettingId: undefined,
|
||||
shopDealerSettingName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
// 配置数据(用于模板配置)
|
||||
const configData = reactive<any>({
|
||||
// 佣金比例配置
|
||||
firstRate: 0,
|
||||
secondRate: 0,
|
||||
thirdRate: 0,
|
||||
// 提现配置
|
||||
minAmount: 0,
|
||||
feeRate: 0,
|
||||
auditType: 1,
|
||||
// 等级配置
|
||||
upgradeType: 1,
|
||||
upgradeThreshold: 0,
|
||||
// 奖励配置
|
||||
promotionReward: 0,
|
||||
firstOrderReward: 0,
|
||||
monthlyReward: 0
|
||||
});
|
||||
|
||||
// JSON状态
|
||||
const jsonStatus = ref<any>(null);
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
@@ -104,28 +369,180 @@
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopDealerSettingName: [
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写分销商设置表名称',
|
||||
message: '请选择设置标识',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
describe: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入设置描述',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 100,
|
||||
message: '描述长度应在2-100个字符之间',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
values: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入配置内容',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value) {
|
||||
try {
|
||||
JSON.parse(value);
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject('配置内容必须是有效的JSON格式');
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
/* 获取模板标题 */
|
||||
const getTemplateTitle = () => {
|
||||
const titleMap = {
|
||||
commission_rate: '佣金比例配置模板',
|
||||
withdraw_config: '提现配置模板',
|
||||
level_config: '等级配置模板',
|
||||
reward_config: '奖励配置模板'
|
||||
};
|
||||
return titleMap[form.key] || '配置模板';
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
/* 获取模板描述 */
|
||||
const getTemplateDescription = () => {
|
||||
const descMap = {
|
||||
commission_rate: '设置一级、二级、三级分销商的佣金比例,支持小数点后两位',
|
||||
withdraw_config: '配置提现的最小金额、手续费比例和审核方式',
|
||||
level_config: '设置分销商等级升级的条件和阈值',
|
||||
reward_config: '配置推广奖励、首单奖励和月度奖励金额'
|
||||
};
|
||||
return descMap[form.key] || '请根据业务需求配置相关参数';
|
||||
};
|
||||
|
||||
/* 设置标识改变时的处理 */
|
||||
const onSettingKeyChange = (value: string) => {
|
||||
// 重置配置数据
|
||||
Object.keys(configData).forEach(key => {
|
||||
configData[key] = typeof configData[key] === 'number' ? 0 : '';
|
||||
});
|
||||
|
||||
// 设置默认描述
|
||||
const descMap = {
|
||||
commission_rate: '分销佣金比例设置',
|
||||
withdraw_config: '提现相关参数配置',
|
||||
level_config: '分销商等级配置',
|
||||
reward_config: '推广奖励配置',
|
||||
other: '自定义设置项'
|
||||
};
|
||||
|
||||
if (!form.describe) {
|
||||
form.describe = descMap[value] || '';
|
||||
}
|
||||
|
||||
// 生成默认JSON
|
||||
resetToTemplate();
|
||||
};
|
||||
|
||||
/* 重置为模板 */
|
||||
const resetToTemplate = () => {
|
||||
if (!form.key || form.key === 'other') {
|
||||
form.values = '{}';
|
||||
return;
|
||||
}
|
||||
|
||||
let template = {};
|
||||
|
||||
switch (form.key) {
|
||||
case 'commission_rate':
|
||||
template = {
|
||||
firstRate: configData.firstRate || 10,
|
||||
secondRate: configData.secondRate || 5,
|
||||
thirdRate: configData.thirdRate || 2,
|
||||
description: '分销佣金比例配置'
|
||||
};
|
||||
break;
|
||||
case 'withdraw_config':
|
||||
template = {
|
||||
minAmount: configData.minAmount || 100,
|
||||
feeRate: configData.feeRate || 1,
|
||||
auditType: configData.auditType || 1,
|
||||
description: '提现配置参数'
|
||||
};
|
||||
break;
|
||||
case 'level_config':
|
||||
template = {
|
||||
upgradeType: configData.upgradeType || 1,
|
||||
upgradeThreshold: configData.upgradeThreshold || 10,
|
||||
description: '分销商等级配置'
|
||||
};
|
||||
break;
|
||||
case 'reward_config':
|
||||
template = {
|
||||
promotionReward: configData.promotionReward || 10,
|
||||
firstOrderReward: configData.firstOrderReward || 5,
|
||||
monthlyReward: configData.monthlyReward || 50,
|
||||
description: '推广奖励配置'
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
form.values = JSON.stringify(template, null, 2);
|
||||
validateJson();
|
||||
};
|
||||
|
||||
/* 格式化JSON */
|
||||
const formatJson = () => {
|
||||
try {
|
||||
const parsed = JSON.parse(form.values);
|
||||
form.values = JSON.stringify(parsed, null, 2);
|
||||
jsonStatus.value = {
|
||||
type: 'success',
|
||||
message: 'JSON格式化成功'
|
||||
};
|
||||
} catch (e) {
|
||||
jsonStatus.value = {
|
||||
type: 'error',
|
||||
message: 'JSON格式错误,无法格式化'
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/* 验证JSON */
|
||||
const validateJson = () => {
|
||||
try {
|
||||
JSON.parse(form.values);
|
||||
jsonStatus.value = {
|
||||
type: 'success',
|
||||
message: 'JSON格式正确'
|
||||
};
|
||||
} catch (e) {
|
||||
jsonStatus.value = {
|
||||
type: 'error',
|
||||
message: `JSON格式错误: ${e.message}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/* JSON失焦时验证 */
|
||||
const onJsonBlur = () => {
|
||||
if (form.values) {
|
||||
validateJson();
|
||||
}
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
@@ -135,13 +552,26 @@
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 先验证JSON格式
|
||||
if (form.values) {
|
||||
try {
|
||||
JSON.parse(form.values);
|
||||
} catch (e) {
|
||||
message.error('配置内容JSON格式错误,请检查后重试');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
...form,
|
||||
updateTime: Date.now()
|
||||
};
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerSetting : addShopDealerSetting;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
@@ -162,18 +592,40 @@
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
jsonStatus.value = null;
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
|
||||
// 解析配置数据到模板
|
||||
if (props.data.values) {
|
||||
try {
|
||||
const parsed = JSON.parse(props.data.values);
|
||||
Object.keys(configData).forEach(key => {
|
||||
if (parsed[key] !== undefined) {
|
||||
configData[key] = parsed[key];
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('解析配置数据失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
key: undefined,
|
||||
describe: '',
|
||||
values: '{}',
|
||||
tenantId: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
// 重置配置数据
|
||||
Object.keys(configData).forEach(key => {
|
||||
configData[key] = typeof configData[key] === 'number' ? 0 : '';
|
||||
});
|
||||
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
@@ -182,4 +634,111 @@
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 监听配置数据变化,自动更新JSON
|
||||
watch(
|
||||
() => configData,
|
||||
() => {
|
||||
if (form.key && form.key !== 'other') {
|
||||
resetToTemplate();
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.setting-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.config-template {
|
||||
background: #fafafa;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.commission-config,
|
||||
.withdraw-config,
|
||||
.level-config,
|
||||
.reward-config {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.json-editor-container {
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
.json-editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: #fafafa;
|
||||
border-bottom: 1px solid #d9d9d9;
|
||||
|
||||
span {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.json-status {
|
||||
padding: 8px 12px;
|
||||
border-top: 1px solid #d9d9d9;
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
|
||||
@@ -1,217 +1,329 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerSettingId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<a-card :bordered="false" :body-style="{ padding: '24px' }">
|
||||
<!-- 设置标签页 -->
|
||||
<a-tabs v-model:activeKey="activeTab" type="card" class="setting-tabs">
|
||||
<a-tab-pane key="basic" tab="基础设置">
|
||||
<a-form
|
||||
:model="basicSettings"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<!-- 是否开启分销功能 -->
|
||||
<a-form-item label="是否开启分销功能">
|
||||
<a-radio-group v-model:value="basicSettings.enableDistribution">
|
||||
<a-radio :value="true">开启</a-radio>
|
||||
<a-radio :value="false">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="setting-desc">开启后用户可以申请成为分销商</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerSettingEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 分销层级 -->
|
||||
<a-form-item label="分销层级">
|
||||
<a-radio-group v-model:value="basicSettings.distributionLevel">
|
||||
<a-radio :value="1">一级</a-radio>
|
||||
<a-radio :value="2">二级</a-radio>
|
||||
<a-radio :value="3">三级</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="setting-desc">设置分销商推荐层级关系</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 分销商内购 -->
|
||||
<a-form-item label="分销商内购">
|
||||
<a-radio-group v-model:value="basicSettings.dealerSelfBuy">
|
||||
<a-radio :value="true">开启</a-radio>
|
||||
<a-radio :value="false">关闭</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="setting-desc">分销商自己购买是否获得佣金,开启一般佣金</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="commission" tab="分销条件">
|
||||
<a-form
|
||||
:model="commissionSettings"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<!-- 申请方式 -->
|
||||
<a-form-item label="申请方式">
|
||||
<a-radio-group v-model:value="commissionSettings.applyType">
|
||||
<a-radio :value="10">需后台审核</a-radio>
|
||||
<a-radio :value="20">无需审核</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="setting-desc">设置用户申请分销商的审核方式</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 佣金结算 -->
|
||||
<a-form-item label="佣金结算">
|
||||
<a-radio-group v-model:value="commissionSettings.settlementType">
|
||||
<a-radio :value="10">订单完成</a-radio>
|
||||
<a-radio :value="20">订单确认收货</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="setting-desc">设置佣金何时结算到分销商账户</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 最低提现金额 -->
|
||||
<a-form-item label="最低提现金额">
|
||||
<a-input-number
|
||||
v-model:value="commissionSettings.minWithdrawAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
style="width: 200px"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
<div class="setting-desc">分销商申请提现的最低金额限制</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="withdraw" tab="提现设置">
|
||||
<a-form
|
||||
:model="withdrawSettings"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<!-- 提现方式 -->
|
||||
<a-form-item label="提现方式">
|
||||
<a-checkbox-group v-model:value="withdrawSettings.withdrawMethods">
|
||||
<a-checkbox :value="10">微信</a-checkbox>
|
||||
<a-checkbox :value="20">支付宝</a-checkbox>
|
||||
<a-checkbox :value="30">银行卡</a-checkbox>
|
||||
</a-checkbox-group>
|
||||
<div class="setting-desc">设置支持的提现方式</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 提现手续费 -->
|
||||
<a-form-item label="提现手续费">
|
||||
<a-input-number
|
||||
v-model:value="withdrawSettings.withdrawFeeRate"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:precision="2"
|
||||
style="width: 200px"
|
||||
>
|
||||
<template #addonAfter>%</template>
|
||||
</a-input-number>
|
||||
<div class="setting-desc">提现时收取的手续费比例</div>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 提现审核 -->
|
||||
<a-form-item label="提现审核">
|
||||
<a-radio-group v-model:value="withdrawSettings.withdrawAudit">
|
||||
<a-radio :value="true">需要审核</a-radio>
|
||||
<a-radio :value="false">无需审核</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="setting-desc">设置提现申请是否需要人工审核</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="agreement" tab="协议">
|
||||
<a-form
|
||||
:model="agreementSettings"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<!-- 分销商协议 -->
|
||||
<a-form-item label="分销商协议">
|
||||
<a-textarea
|
||||
v-model:value="agreementSettings.dealerAgreement"
|
||||
:rows="10"
|
||||
placeholder="请输入分销商协议内容..."
|
||||
/>
|
||||
<div class="setting-desc">用户申请分销商时需要同意的协议内容</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="notification" tab="自定义文字">
|
||||
<a-form
|
||||
:model="notificationSettings"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<!-- 申请成功提示 -->
|
||||
<a-form-item label="申请成功提示">
|
||||
<a-textarea
|
||||
v-model:value="notificationSettings.applySuccessText"
|
||||
:rows="3"
|
||||
placeholder="请输入申请成功后的提示文字..."
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 申请失败提示 -->
|
||||
<a-form-item label="申请失败提示">
|
||||
<a-textarea
|
||||
v-model:value="notificationSettings.applyFailText"
|
||||
:rows="3"
|
||||
placeholder="请输入申请失败后的提示文字..."
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 提现成功提示 -->
|
||||
<a-form-item label="提现成功提示">
|
||||
<a-textarea
|
||||
v-model:value="notificationSettings.withdrawSuccessText"
|
||||
:rows="3"
|
||||
placeholder="请输入提现成功后的提示文字..."
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="page" tab="页面设置">
|
||||
<a-form
|
||||
:model="pageSettings"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<!-- 分销中心标题 -->
|
||||
<a-form-item label="分销中心标题">
|
||||
<a-input
|
||||
v-model:value="pageSettings.centerTitle"
|
||||
placeholder="请输入分销中心页面标题"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 分销中心背景图 -->
|
||||
<a-form-item label="分销中心背景图">
|
||||
<a-upload
|
||||
v-model:file-list="pageSettings.backgroundImages"
|
||||
list-type="picture-card"
|
||||
:max-count="1"
|
||||
@preview="handlePreview"
|
||||
>
|
||||
<div v-if="pageSettings.backgroundImages.length < 1">
|
||||
<PlusOutlined />
|
||||
<div style="margin-top: 8px">上传</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="setting-footer">
|
||||
<a-button type="primary" size="large" @click="saveSettings" :loading="saving">
|
||||
保存设置
|
||||
</a-button>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerSettingEdit from './components/shopDealerSettingEdit.vue';
|
||||
import { pageShopDealerSetting, removeShopDealerSetting, removeBatchShopDealerSetting } from '@/api/shop/shopDealerSetting';
|
||||
import type { ShopDealerSetting, ShopDealerSettingParam } from '@/api/shop/shopDealerSetting/model';
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { getPageTitle } from '@/utils/common';
|
||||
import { updateShopDealerSetting, getShopDealerSetting } from '@/api/shop/shopDealerSetting';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 当前激活的标签页
|
||||
const activeTab = ref('basic');
|
||||
// 保存状态
|
||||
const saving = ref(false);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerSetting[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerSetting | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 基础设置
|
||||
const basicSettings = reactive({
|
||||
enableDistribution: true,
|
||||
distributionLevel: 3,
|
||||
dealerSelfBuy: false
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
// 分销条件设置
|
||||
const commissionSettings = reactive({
|
||||
applyType: 10,
|
||||
settlementType: 10,
|
||||
minWithdrawAmount: 100
|
||||
});
|
||||
|
||||
// 提现设置
|
||||
const withdrawSettings = reactive({
|
||||
withdrawMethods: [10, 20, 30],
|
||||
withdrawFeeRate: 0,
|
||||
withdrawAudit: true
|
||||
});
|
||||
|
||||
// 协议设置
|
||||
const agreementSettings = reactive({
|
||||
dealerAgreement: '分销商协议内容...'
|
||||
});
|
||||
|
||||
// 通知设置
|
||||
const notificationSettings = reactive({
|
||||
applySuccessText: '恭喜您成功成为分销商!',
|
||||
applyFailText: '很抱歉,您的申请未通过审核。',
|
||||
withdrawSuccessText: '提现申请已提交,请耐心等待处理。'
|
||||
});
|
||||
|
||||
// 页面设置
|
||||
const pageSettings = reactive({
|
||||
centerTitle: '分销中心',
|
||||
backgroundImages: []
|
||||
});
|
||||
|
||||
/* 图片预览 */
|
||||
const handlePreview = (file: any) => {
|
||||
console.log('预览图片:', file);
|
||||
};
|
||||
|
||||
/* 加载设置 */
|
||||
const loadSettings = async () => {
|
||||
try {
|
||||
// 这里应该调用API获取设置数据
|
||||
// const settings = await getShopDealerSetting();
|
||||
// 然后将数据分配到各个设置对象中
|
||||
console.log('加载设置数据');
|
||||
} catch (error) {
|
||||
console.error('加载设置失败:', error);
|
||||
message.error('加载设置失败');
|
||||
}
|
||||
return pageShopDealerSetting({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '设置项标示',
|
||||
dataIndex: 'key',
|
||||
key: 'key',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '设置项描述',
|
||||
dataIndex: 'describe',
|
||||
key: 'describe',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '设置内容(json格式)',
|
||||
dataIndex: 'values',
|
||||
key: 'values',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
/* 保存设置 */
|
||||
const saveSettings = async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
// 收集所有设置数据
|
||||
const allSettings = {
|
||||
basic: basicSettings,
|
||||
commission: commissionSettings,
|
||||
withdraw: withdrawSettings,
|
||||
agreement: agreementSettings,
|
||||
notification: notificationSettings,
|
||||
page: pageSettings
|
||||
};
|
||||
|
||||
console.log('保存设置:', allSettings);
|
||||
|
||||
// 这里应该调用API保存设置
|
||||
// await updateShopDealerSetting(allSettings);
|
||||
|
||||
// 模拟保存
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
message.success('设置保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存设置失败:', error);
|
||||
message.error('保存设置失败');
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerSettingParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerSetting) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerSetting) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerSetting(row.shopDealerSettingId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerSetting(selection.value.map((d) => d.shopDealerSettingId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerSetting) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
// 页面加载时获取设置数据
|
||||
onMounted(() => {
|
||||
loadSettings();
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -220,4 +332,72 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.dealer-setting-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.setting-tabs {
|
||||
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab) {
|
||||
border-radius: 6px 6px 0 0;
|
||||
background: #fafafa;
|
||||
border: 1px solid #d9d9d9;
|
||||
margin-right: 8px;
|
||||
|
||||
&.ant-tabs-tab-active {
|
||||
background: #fff;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-tabs-content-holder) {
|
||||
background: #fff;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 0 6px 6px 6px;
|
||||
padding: 24px;
|
||||
min-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.setting-footer {
|
||||
text-align: center;
|
||||
margin-top: 32px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label > label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.ant-radio-group) {
|
||||
.ant-radio-wrapper {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-checkbox-group) {
|
||||
.ant-checkbox-wrapper {
|
||||
margin-right: 16px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-upload-select-picture-card) {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
:deep(.ant-upload-list-picture-card .ant-upload-list-item) {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
</style>
|
||||
|
||||
110
src/views/shop/shopDealerUser/components/Import.vue
Normal file
110
src/views/shop/shopDealerUser/components/Import.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<!-- 分销商用户导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:footer="null"
|
||||
title="分销商用户批量导入"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doUpload"
|
||||
style="padding: 24px 0; margin-bottom: 16px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-hint">将文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
<div class="ele-text-center">
|
||||
<span>只能上传xls、xlsx文件,导入模板和导出模板格式一致</span>
|
||||
</div>
|
||||
<div class="import-tips" style="margin-top: 16px;">
|
||||
<a-alert
|
||||
message="导入说明"
|
||||
type="info"
|
||||
show-icon
|
||||
>
|
||||
<template #description>
|
||||
<div>
|
||||
<p>1. 请按照导出的Excel格式准备数据</p>
|
||||
<p>2. 必填字段:用户ID、姓名、手机号</p>
|
||||
<p>3. 佣金字段请填写数字,不要包含货币符号</p>
|
||||
<p>4. 状态字段:正常 或 已删除</p>
|
||||
<p>5. 推荐人ID必须是已存在的用户ID</p>
|
||||
</div>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||
import {importShopDealerUsers} from "@/api/shop/shopDealerUser";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 是否打开弹窗
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
// 导入请求状态
|
||||
const loading = ref(false);
|
||||
|
||||
/* 上传 */
|
||||
const doUpload = ({ file }) => {
|
||||
if (
|
||||
![
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
].includes(file.type)
|
||||
) {
|
||||
message.error('只能选择 excel 文件');
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
message.error('大小不能超过 10MB');
|
||||
return false;
|
||||
}
|
||||
loading.value = true;
|
||||
importShopDealerUsers(file)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
/* 更新 visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.import-tips {
|
||||
:deep(.ant-alert-description) {
|
||||
p {
|
||||
margin: 4px 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,42 +1,219 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="search-container">
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="searchForm"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item label="申请人姓名">
|
||||
<a-input
|
||||
v-model:value="searchForm.realName"
|
||||
placeholder="请输入申请人姓名"
|
||||
allow-clear
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="手机号码">
|
||||
<a-input
|
||||
v-model:value="searchForm.mobile"
|
||||
placeholder="请输入手机号码"
|
||||
allow-clear
|
||||
style="width: 160px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="申请方式">
|
||||
<a-select
|
||||
v-model:value="searchForm.applyType"
|
||||
placeholder="全部方式"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option :value="10">需要审核</a-select-option>
|
||||
<a-select-option :value="20">免审核</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="审核状态">
|
||||
<a-select
|
||||
v-model:value="searchForm.applyStatus"
|
||||
placeholder="全部状态"
|
||||
allow-clear
|
||||
style="width: 120px"
|
||||
>
|
||||
<a-select-option :value="10">待审核</a-select-option>
|
||||
<a-select-option :value="20">审核通过</a-select-option>
|
||||
<a-select-option :value="30">审核驳回</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item label="申请时间">
|
||||
<a-range-picker
|
||||
v-model:value="searchForm.dateRange"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" html-type="submit" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="resetSearch">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-buttons">
|
||||
<a-space>
|
||||
<!-- <a-button type="primary" @click="add" class="ele-btn-icon">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <PlusOutlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- 新增申请-->
|
||||
<!-- </a-button>-->
|
||||
<a-button
|
||||
type="primary"
|
||||
ghost
|
||||
:disabled="!selection?.length"
|
||||
@click="batchApprove"
|
||||
class="ele-btn-icon"
|
||||
>
|
||||
<template #icon>
|
||||
<CheckOutlined />
|
||||
</template>
|
||||
批量通过
|
||||
</a-button>
|
||||
<a-button
|
||||
:disabled="!selection?.length"
|
||||
@click="exportData"
|
||||
class="ele-btn-icon"
|
||||
>
|
||||
<template #icon>
|
||||
<ExportOutlined />
|
||||
</template>
|
||||
导出数据
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
import { reactive } from 'vue';
|
||||
import {
|
||||
PlusOutlined,
|
||||
SearchOutlined,
|
||||
CheckOutlined,
|
||||
ExportOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type { ShopDealerApplyParam } from '@/api/shop/shopDealerApply/model';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
// 选中的数据
|
||||
selection?: any[];
|
||||
}>(),
|
||||
{}
|
||||
{
|
||||
selection: () => []
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'search', where?: ShopDealerApplyParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'batchApprove'): void;
|
||||
(e: 'export'): void;
|
||||
}>();
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive<any>({
|
||||
realName: '',
|
||||
mobile: '',
|
||||
applyType: undefined,
|
||||
applyStatus: undefined,
|
||||
dateRange: undefined
|
||||
});
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const searchParams: ShopDealerApplyParam = {};
|
||||
|
||||
if (searchForm.realName) {
|
||||
searchParams.realName = searchForm.realName;
|
||||
}
|
||||
if (searchForm.mobile) {
|
||||
searchParams.mobile = searchForm.mobile;
|
||||
}
|
||||
if (searchForm.applyType) {
|
||||
searchParams.applyType = searchForm.applyType;
|
||||
}
|
||||
if (searchForm.applyStatus) {
|
||||
searchParams.applyStatus = searchForm.applyStatus;
|
||||
}
|
||||
if (searchForm.dateRange && searchForm.dateRange.length === 2) {
|
||||
searchParams.startTime = dayjs(searchForm.dateRange[0]).format('YYYY-MM-DD');
|
||||
searchParams.endTime = dayjs(searchForm.dateRange[1]).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
emit('search', searchParams);
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
searchForm.realName = '';
|
||||
searchForm.mobile = '';
|
||||
searchForm.applyType = undefined;
|
||||
searchForm.applyStatus = undefined;
|
||||
searchForm.dateRange = undefined;
|
||||
emit('search', {});
|
||||
};
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
// 批量通过
|
||||
const batchApprove = () => {
|
||||
emit('batchApprove');
|
||||
};
|
||||
|
||||
// 导出数据
|
||||
const exportData = () => {
|
||||
emit('export');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.search-container {
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
margin-bottom: 16px;
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
border-top: 1px solid #f0f0f0;
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
407
src/views/shop/shopDealerUser/components/shopDealerApplyEdit.vue
Normal file
407
src/views/shop/shopDealerUser/components/shopDealerApplyEdit.vue
Normal file
@@ -0,0 +1,407 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="900"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商申请' : '新增分销商申请'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<!-- 申请人信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">申请人信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="用户ID" name="userId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入用户ID"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.userId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="真实姓名" name="realName">
|
||||
<a-input
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
:disabled="isUpdate"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="手机号码" name="mobile">
|
||||
<a-input
|
||||
placeholder="请输入手机号码"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.mobile"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="推荐人ID" name="refereeId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入推荐人用户ID"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.refereeId"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 审核信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">审核信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="审核状态" name="applyStatus">
|
||||
<a-select v-model:value="form.applyStatus" placeholder="请选择审核状态" @change="handleStatusChange">
|
||||
<a-select-option :value="10">
|
||||
<a-tag color="processing">待审核</a-tag>
|
||||
<span style="margin-left: 8px;">等待审核</span>
|
||||
</a-select-option>
|
||||
<a-select-option :value="20">
|
||||
<a-tag color="success">审核通过</a-tag>
|
||||
<span style="margin-left: 8px;">申请通过</span>
|
||||
</a-select-option>
|
||||
<a-select-option :value="30">
|
||||
<a-tag color="error">审核驳回</a-tag>
|
||||
<span style="margin-left: 8px;">申请驳回</span>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- <a-col :span="12">-->
|
||||
<!-- <a-form-item label="审核时间" name="auditTime" v-if="form.applyStatus === 20 || form.applyStatus === 30">-->
|
||||
<!-- <a-date-picker-->
|
||||
<!-- v-model:value="form.auditTime"-->
|
||||
<!-- show-time-->
|
||||
<!-- format="YYYY-MM-DD HH:mm:ss"-->
|
||||
<!-- placeholder="请选择审核时间"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- </a-col>-->
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16" v-if="form.applyStatus === 30">
|
||||
<a-col :span="24">
|
||||
<a-form-item label="驳回原因" name="rejectReason">
|
||||
<a-textarea
|
||||
v-model:value="form.rejectReason"
|
||||
placeholder="请输入驳回原因"
|
||||
style="width: 100%"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addShopDealerApply, updateShopDealerApply } from '@/api/shop/shopDealerApply';
|
||||
import { ShopDealerApply } from '@/api/shop/shopDealerApply/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopDealerApply | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<ShopDealerApply>({
|
||||
applyId: undefined,
|
||||
userId: undefined,
|
||||
realName: '',
|
||||
mobile: '',
|
||||
refereeId: undefined,
|
||||
applyType: 10,
|
||||
applyTime: undefined,
|
||||
applyStatus: 10,
|
||||
auditTime: undefined,
|
||||
rejectReason: '',
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
realName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入真实姓名',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 20,
|
||||
message: '姓名长度应在2-20个字符之间',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入手机号码',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号码',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
applyType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择申请方式',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
applyStatus: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择审核状态',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
rejectReason: [
|
||||
{
|
||||
required: true,
|
||||
message: '驳回时必须填写驳回原因',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
auditTime: [
|
||||
{
|
||||
required: true,
|
||||
message: '审核时请选择审核时间',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 处理审核状态变化 */
|
||||
const handleStatusChange = (value: number) => {
|
||||
// 当状态改为审核通过或驳回时,自动设置审核时间为当前时间
|
||||
if ((value === 20 || value === 30) && !form.auditTime) {
|
||||
form.auditTime = dayjs();
|
||||
}
|
||||
// 当状态改为待审核时,清空审核时间和驳回原因
|
||||
if (value === 10) {
|
||||
form.auditTime = undefined;
|
||||
form.rejectReason = '';
|
||||
}
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 动态验证规则
|
||||
const validateFields: string[] = ['userId', 'realName', 'mobile', 'applyStatus'];
|
||||
|
||||
// 如果是驳回状态,需要验证驳回原因
|
||||
if (form.applyStatus === 30) {
|
||||
validateFields.push('rejectReason');
|
||||
}
|
||||
|
||||
// 如果是审核通过或驳回状态,需要验证审核时间
|
||||
if (form.applyStatus === 20 || form.applyStatus === 30) {
|
||||
validateFields.push('auditTime');
|
||||
}
|
||||
|
||||
formRef.value
|
||||
.validate(validateFields)
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
|
||||
// 处理时间字段转换 - 转换为ISO字符串格式
|
||||
if (formData.applyTime) {
|
||||
if (dayjs.isDayjs(formData.applyTime)) {
|
||||
formData.applyTime = formData.applyTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
} else if (typeof formData.applyTime === 'number') {
|
||||
formData.applyTime = dayjs(formData.applyTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
}
|
||||
|
||||
if (formData.auditTime) {
|
||||
if (dayjs.isDayjs(formData.auditTime)) {
|
||||
formData.auditTime = formData.auditTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
} else if (typeof formData.auditTime === 'number') {
|
||||
formData.auditTime = dayjs(formData.auditTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
}
|
||||
|
||||
// 当审核状态为通过或驳回时,确保有审核时间
|
||||
if ((formData.applyStatus === 20 || formData.applyStatus === 30) && !formData.auditTime) {
|
||||
formData.auditTime = dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
// 当状态为待审核时,清空审核时间
|
||||
if (formData.applyStatus === 10) {
|
||||
formData.auditTime = undefined;
|
||||
}
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerApply : addShopDealerApply;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
// 处理时间字段 - 确保转换为dayjs对象
|
||||
if (props.data.applyTime) {
|
||||
form.applyTime = dayjs(props.data.applyTime);
|
||||
}
|
||||
if (props.data.auditTime) {
|
||||
form.auditTime = dayjs(props.data.auditTime);
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
applyId: undefined,
|
||||
userId: undefined,
|
||||
realName: '',
|
||||
mobile: '',
|
||||
refereeId: undefined,
|
||||
applyType: 10,
|
||||
applyTime: dayjs(),
|
||||
applyStatus: 10,
|
||||
auditTime: undefined,
|
||||
rejectReason: '',
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-radio) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.ant-radio-inner {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,12 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:width="900"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商用户记录表' : '添加分销商用户记录表'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
:title="isUpdate ? '编辑分销商用户' : '添加分销商用户'"
|
||||
:body-style="{ paddingBottom: '28px', maxHeight: '70vh', overflowY: 'auto' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
@@ -14,109 +14,247 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<a-form-item label="自增ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入自增ID"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="姓名" name="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="mobile">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="form.mobile"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付密码" name="payPassword">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入支付密码"
|
||||
v-model:value="form.payPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="当前可提现佣金" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入当前可提现佣金"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="已冻结佣金" name="freezeMoney">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入已冻结佣金"
|
||||
v-model:value="form.freezeMoney"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="累积提现佣金" name="totalMoney">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入累积提现佣金"
|
||||
v-model:value="form.totalMoney"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="推荐人用户ID" name="refereeId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入推荐人用户ID"
|
||||
v-model:value="form.refereeId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="成员数量(一级)" name="firstNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入成员数量(一级)"
|
||||
v-model:value="form.firstNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="成员数量(二级)" name="secondNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入成员数量(二级)"
|
||||
v-model:value="form.secondNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="成员数量(三级)" name="thirdNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入成员数量(三级)"
|
||||
v-model:value="form.thirdNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="专属二维码" name="qrcode">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入专属二维码"
|
||||
v-model:value="form.qrcode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除" name="isDelete">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除"
|
||||
v-model:value="form.isDelete"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
<!-- 基本信息 -->
|
||||
<a-divider orientation="left">基本信息</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="用户ID"
|
||||
name="userId"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入用户ID"
|
||||
v-model:value="form.userId"
|
||||
style="width: 100%"
|
||||
:disabled="isUpdate"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="姓名"
|
||||
name="realName"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="手机号"
|
||||
name="mobile"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="form.mobile"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="支付密码"
|
||||
name="payPassword"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-input-password
|
||||
allow-clear
|
||||
placeholder="请输入支付密码"
|
||||
v-model:value="form.payPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<!-- 佣金信息 -->
|
||||
<a-divider orientation="left">佣金信息</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="可提现佣金"
|
||||
name="money"
|
||||
:label-col="{ span: 12 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
v-model:value="form.money"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="冻结佣金"
|
||||
name="freezeMoney"
|
||||
:label-col="{ span: 12 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
v-model:value="form.freezeMoney"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="累计提现"
|
||||
name="totalMoney"
|
||||
:label-col="{ span: 12 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:precision="2"
|
||||
placeholder="0.00"
|
||||
v-model:value="form.totalMoney"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #addonAfter>元</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 推荐关系 -->
|
||||
<a-divider orientation="left">推荐关系</a-divider>
|
||||
|
||||
<a-form-item label="推荐人" name="refereeId">
|
||||
<a-input-group compact>
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入推荐人用户ID"
|
||||
v-model:value="form.refereeId"
|
||||
style="width: calc(100% - 80px)"
|
||||
/>
|
||||
<a-button type="primary" @click="selectReferee">选择</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
<!-- 团队信息 -->
|
||||
<a-divider orientation="left">团队信息</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="一级成员"
|
||||
name="firstNum"
|
||||
:label-col="{ span: 12 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="0"
|
||||
placeholder="0"
|
||||
v-model:value="form.firstNum"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #addonAfter>人</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="二级成员"
|
||||
name="secondNum"
|
||||
:label-col="{ span: 12 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="0"
|
||||
placeholder="0"
|
||||
v-model:value="form.secondNum"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #addonAfter>人</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
label="三级成员"
|
||||
name="thirdNum"
|
||||
:label-col="{ span: 12 }"
|
||||
:wrapper-col="{ span: 12 }"
|
||||
>
|
||||
<a-input-number
|
||||
:min="0"
|
||||
placeholder="0"
|
||||
v-model:value="form.thirdNum"
|
||||
style="width: 100%"
|
||||
:disabled="true"
|
||||
>
|
||||
<template #addonAfter>人</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 其他设置 -->
|
||||
<a-divider orientation="left">其他设置</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="专属二维码"
|
||||
name="qrcode"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
placeholder="系统自动生成"
|
||||
v-model:value="form.qrcode"
|
||||
style="width: calc(100% - 80px)"
|
||||
:disabled="true"
|
||||
/>
|
||||
<a-button type="primary" @click="generateQrcode">生成</a-button>
|
||||
</a-input-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item
|
||||
label="账户状态"
|
||||
name="isDelete"
|
||||
:label-col="{ span: 8 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
>
|
||||
<a-radio-group v-model:value="form.isDelete">
|
||||
<a-radio :value="0">正常</a-radio>
|
||||
<a-radio :value="1">已删除</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
@@ -193,11 +331,51 @@
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopDealerUserName: [
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写分销商用户记录表名称',
|
||||
message: '请输入用户ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
realName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入真实姓名',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 20,
|
||||
message: '姓名长度应在2-20个字符之间',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入手机号',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号格式',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
money: [
|
||||
{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: '可提现佣金不能小于0',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
freezeMoney: [
|
||||
{
|
||||
type: 'number',
|
||||
min: 0,
|
||||
message: '冻结佣金不能小于0',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
@@ -219,6 +397,25 @@
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 选择推荐人 */
|
||||
const selectReferee = () => {
|
||||
message.info('推荐人选择功能待开发');
|
||||
// 这里可以打开用户选择器
|
||||
};
|
||||
|
||||
/* 生成二维码 */
|
||||
const generateQrcode = () => {
|
||||
if (!form.userId) {
|
||||
message.error('请先填写用户ID');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成二维码逻辑
|
||||
const qrcode = `DEALER_${form.userId}_${Date.now()}`;
|
||||
form.qrcode = qrcode;
|
||||
message.success('二维码生成成功');
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
@@ -228,20 +425,37 @@
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
|
||||
// 数据处理
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
|
||||
// 确保数值类型正确
|
||||
if (formData.userId) formData.userId = Number(formData.userId);
|
||||
if (formData.refereeId) formData.refereeId = Number(formData.refereeId);
|
||||
if (formData.money !== undefined) formData.money = Number(formData.money);
|
||||
if (formData.freezeMoney !== undefined) formData.freezeMoney = Number(formData.freezeMoney);
|
||||
if (formData.totalMoney !== undefined) formData.totalMoney = Number(formData.totalMoney);
|
||||
if (formData.firstNum !== undefined) formData.firstNum = Number(formData.firstNum);
|
||||
if (formData.secondNum !== undefined) formData.secondNum = Number(formData.secondNum);
|
||||
if (formData.thirdNum !== undefined) formData.thirdNum = Number(formData.thirdNum);
|
||||
if (formData.isDelete !== undefined) formData.isDelete = Number(formData.isDelete);
|
||||
|
||||
console.log('提交的数据:', formData);
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerUser : addShopDealerUser;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
message.success(msg || '操作成功');
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
console.error('保存失败:', e);
|
||||
message.error(e.message || '操作失败');
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
@@ -272,3 +486,31 @@
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.ant-form-item-label) {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
:deep(.ant-form-item-label > label) {
|
||||
text-align: left !important;
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
color: #1890ff;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-input-group-addon) {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number-disabled) {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,298 +1,496 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerUserId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="applyId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
v-model:selection="selection"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@batchApprove="batchApprove"
|
||||
@export="exportData"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'applyStatus'">
|
||||
<a-tag v-if="record.applyStatus === 10" color="orange">待审核</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 20" color="green">已通过</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 30" color="red">已驳回</a-tag>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a @click="openEdit(record)" class="ele-text-primary">
|
||||
<EditOutlined/>
|
||||
编辑
|
||||
</a>
|
||||
<template v-if="record.applyStatus !== 20">
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="approveApply(record)" class="ele-text-success">
|
||||
<CheckOutlined/>
|
||||
通过
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a @click="rejectApply(record)" class="ele-text-warning">
|
||||
<CloseOutlined/>
|
||||
驳回
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm
|
||||
v-if="record.applyStatus != 20"
|
||||
title="确定要删除此申请记录吗?"
|
||||
@confirm="remove(record)"
|
||||
placement="topRight"
|
||||
>
|
||||
<a class="ele-text-danger">
|
||||
<DeleteOutlined/>
|
||||
删除
|
||||
</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerUserEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerApplyEdit v-model:visible="showEdit" :data="current" @done="reload"/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerUserEdit from './components/shopDealerUserEdit.vue';
|
||||
import { pageShopDealerUser, removeShopDealerUser, removeBatchShopDealerUser } from '@/api/shop/shopDealerUser';
|
||||
import type { ShopDealerUser, ShopDealerUserParam } from '@/api/shop/shopDealerUser/model';
|
||||
import {createVNode, ref} from 'vue';
|
||||
import {message, Modal} from 'ant-design-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {EleProTable} from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerApplyEdit from './components/shopDealerApplyEdit.vue';
|
||||
import {
|
||||
pageShopDealerApply,
|
||||
removeShopDealerApply,
|
||||
removeBatchShopDealerApply,
|
||||
batchApproveShopDealerApply,
|
||||
updateShopDealerApply
|
||||
} from '@/api/shop/shopDealerApply';
|
||||
import type {ShopDealerApply, ShopDealerApplyParam} from '@/api/shop/shopDealerApply/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerUser[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerUser | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerApply[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerApply | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
where.type = 4;
|
||||
where.applyStatus = 20;
|
||||
return pageShopDealerApply({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'applyId',
|
||||
key: 'applyId',
|
||||
align: 'center',
|
||||
width: 80,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '申请人信息',
|
||||
key: 'applicantInfo',
|
||||
align: 'left',
|
||||
fixed: 'left',
|
||||
customRender: ({record}) => {
|
||||
return `${record.realName || '-'} (${record.mobile || '-'})`;
|
||||
}
|
||||
return pageShopDealerUser({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '主键ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '自增ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realName',
|
||||
key: 'realName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '支付密码',
|
||||
dataIndex: 'payPassword',
|
||||
key: 'payPassword',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '当前可提现佣金',
|
||||
dataIndex: 'money',
|
||||
key: 'money',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '已冻结佣金',
|
||||
dataIndex: 'freezeMoney',
|
||||
key: 'freezeMoney',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '累积提现佣金',
|
||||
dataIndex: 'totalMoney',
|
||||
key: 'totalMoney',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '推荐人用户ID',
|
||||
dataIndex: 'refereeId',
|
||||
key: 'refereeId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '成员数量(一级)',
|
||||
dataIndex: 'firstNum',
|
||||
key: 'firstNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '成员数量(二级)',
|
||||
dataIndex: 'secondNum',
|
||||
key: 'secondNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '成员数量(三级)',
|
||||
dataIndex: 'thirdNum',
|
||||
key: 'thirdNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '专属二维码',
|
||||
dataIndex: 'qrcode',
|
||||
key: 'qrcode',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否删除',
|
||||
dataIndex: 'isDelete',
|
||||
key: 'isDelete',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
},
|
||||
{
|
||||
title: '申请方式',
|
||||
dataIndex: 'applyType',
|
||||
key: 'applyType',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
customRender: ({text}) => {
|
||||
const typeMap = {
|
||||
10: {text: '需审核', color: 'orange'},
|
||||
20: {text: '免审核', color: 'green'}
|
||||
};
|
||||
const type = typeMap[text] || {text: '未知', color: 'default'};
|
||||
return {type: 'tag', props: {color: type.color}, children: type.text};
|
||||
}
|
||||
]);
|
||||
},
|
||||
{
|
||||
title: '审核状态',
|
||||
dataIndex: 'applyStatus',
|
||||
key: 'applyStatus',
|
||||
align: 'center',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '推荐人',
|
||||
dataIndex: 'refereeId',
|
||||
key: 'refereeId',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
customRender: ({text}) => text ? `ID: ${text}` : '无'
|
||||
},
|
||||
// {
|
||||
// title: '申请时间',
|
||||
// dataIndex: 'applyTime',
|
||||
// key: 'applyTime',
|
||||
// align: 'center',
|
||||
// width: 120,
|
||||
// customRender: ({ text }) => text ? toDateString(new Date(text), 'yyyy-MM-dd HH:mm') : '-'
|
||||
// },
|
||||
// {
|
||||
// title: '审核时间',
|
||||
// dataIndex: 'auditTime',
|
||||
// key: 'auditTime',
|
||||
// align: 'center',
|
||||
// customRender: ({ text }) => text ? toDateString(new Date(text), 'yyyy-MM-dd HH:mm') : '-'
|
||||
// },
|
||||
{
|
||||
title: '驳回原因',
|
||||
dataIndex: 'rejectReason',
|
||||
key: 'rejectReason',
|
||||
align: 'left',
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => text || '-'
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
width: 380,
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerUserParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerApplyParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({where: where});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerUser) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerUser) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerUser(row.shopDealerUserId)
|
||||
.then((msg) => {
|
||||
/* 审核通过 */
|
||||
const approveApply = (row: ShopDealerApply) => {
|
||||
Modal.confirm({
|
||||
title: '审核通过确认',
|
||||
content: `确定要通过 ${row.realName} 的经销商申请吗?`,
|
||||
icon: createVNode(CheckOutlined),
|
||||
okText: '确认通过',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const hide = message.loading('正在处理审核...', 0);
|
||||
try {
|
||||
await updateShopDealerApply({
|
||||
...row,
|
||||
applyId: row.applyId,
|
||||
applyStatus: 20
|
||||
});
|
||||
hide();
|
||||
message.success(msg);
|
||||
message.success('审核通过成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
} catch (error: any) {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
message.error(error.message || '审核失败,请重试');
|
||||
}
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerUser(selection.value.map((d) => d.shopDealerUserId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* 审核驳回 */
|
||||
const rejectApply = (row: ShopDealerApply) => {
|
||||
let rejectReason = '';
|
||||
Modal.confirm({
|
||||
title: '审核驳回',
|
||||
content: createVNode('div', null, [
|
||||
createVNode('p', null, `申请人: ${row.realName} (${row.mobile})`),
|
||||
createVNode('p', {style: 'margin-top: 12px;'}, '请输入驳回原因:'),
|
||||
createVNode('textarea', {
|
||||
placeholder: '请输入驳回原因...',
|
||||
style: 'width: 100%; height: 80px; margin-top: 8px; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px;',
|
||||
onInput: (e: any) => {
|
||||
rejectReason = e.target.value;
|
||||
}
|
||||
})
|
||||
]),
|
||||
icon: createVNode(CloseOutlined),
|
||||
okText: '确认驳回',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
if (!rejectReason.trim()) {
|
||||
message.error('请输入驳回原因');
|
||||
return Promise.reject();
|
||||
}
|
||||
const hide = message.loading('正在处理审核...', 0);
|
||||
try {
|
||||
await updateShopDealerApply({
|
||||
...row,
|
||||
applyStatus: 30,
|
||||
rejectReason: rejectReason.trim()
|
||||
});
|
||||
hide();
|
||||
message.success('审核驳回成功');
|
||||
reload();
|
||||
} catch (error: any) {
|
||||
hide();
|
||||
message.error(error.message || '审核失败,请重试');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerApply) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerApply) => {
|
||||
if (!row.applyId) {
|
||||
message.error('删除失败:缺少必要参数');
|
||||
return;
|
||||
}
|
||||
|
||||
const hide = message.loading('正在删除申请记录...', 0);
|
||||
removeShopDealerApply(row.applyId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg || '删除成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || '删除失败');
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerUser) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
const validIds = selection.value.filter(d => d.applyId).map(d => d.applyId);
|
||||
if (!validIds.length) {
|
||||
message.error('选中的数据中没有有效的ID');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '批量删除确认',
|
||||
content: `确定要删除选中的 ${validIds.length} 条申请记录吗?此操作不可恢复。`,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
okText: '确认删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
const hide = message.loading(`正在删除 ${validIds.length} 条记录...`, 0);
|
||||
removeBatchShopDealerApply(validIds)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg || `成功删除 ${validIds.length} 条记录`);
|
||||
selection.value = [];
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || '批量删除失败');
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量通过 */
|
||||
const batchApprove = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
|
||||
const pendingApplies = selection.value.filter(item => item.applyStatus === 10);
|
||||
if (!pendingApplies.length) {
|
||||
message.error('所选申请中没有待审核的记录');
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '批量通过确认',
|
||||
content: `确定要通过选中的 ${pendingApplies.length} 个申请吗?`,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
okText: '确认通过',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
const hide = message.loading('正在批量通过...', 0);
|
||||
try {
|
||||
const ids = pendingApplies.map(item => item.applyId);
|
||||
await batchApproveShopDealerApply(ids);
|
||||
hide();
|
||||
message.success(`成功通过 ${pendingApplies.length} 个申请`);
|
||||
selection.value = [];
|
||||
reload();
|
||||
} catch (error: any) {
|
||||
hide();
|
||||
message.error(error.message || '批量审核失败,请重试');
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const exportData = () => {
|
||||
const hide = message.loading('正在导出申请数据...', 0);
|
||||
// 这里调用导出API
|
||||
setTimeout(() => {
|
||||
hide();
|
||||
message.success('申请数据导出成功');
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerApply) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
query();
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopDealerUser'
|
||||
};
|
||||
export default {
|
||||
name: 'ShopDealerApply'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.sys-org-table {
|
||||
:deep(.ant-table-thead > tr > th) {
|
||||
background-color: #fafafa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.ant-table-tbody > tr:hover > td) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
p {
|
||||
margin: 4px 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-primary {
|
||||
color: #1890ff;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-info {
|
||||
color: #13c2c2;
|
||||
|
||||
&:hover {
|
||||
color: #36cfc9;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-success {
|
||||
color: #52c41a;
|
||||
|
||||
&:hover {
|
||||
color: #73d13d;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-warning {
|
||||
color: #faad14;
|
||||
|
||||
&:hover {
|
||||
color: #ffc53d;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-danger {
|
||||
color: #ff4d4f;
|
||||
|
||||
&:hover {
|
||||
color: #ff7875;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
83
src/views/shop/shopDealerWithdraw/components/Import.vue
Normal file
83
src/views/shop/shopDealerWithdraw/components/Import.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<!-- 经销商订单导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:footer="null"
|
||||
title="导入分销订单"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doUpload"
|
||||
style="padding: 24px 0; margin-bottom: 16px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined/>
|
||||
</p>
|
||||
<p class="ant-upload-hint">将文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
<div class="ant-upload-text text-gray-400">
|
||||
<div>1、必须按<a href="https://oss.wsdns.cn/20251018/408b805ec3cd4084a4dc686e130af578.xlsx" target="_blank">导入模版</a>的格式上传</div>
|
||||
<div>2、导入成功确认结算完成佣金的发放</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue';
|
||||
import {message} from 'ant-design-vue/es';
|
||||
import {CloudUploadOutlined} from '@ant-design/icons-vue';
|
||||
import {importSdyDealerOrder} from "@/api/sdy/sdyDealerOrder";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 是否打开弹窗
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
// 导入请求状态
|
||||
const loading = ref(false);
|
||||
|
||||
/* 上传 */
|
||||
const doUpload = ({file}) => {
|
||||
if (
|
||||
![
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
].includes(file.type)
|
||||
) {
|
||||
message.error('只能选择 excel 文件');
|
||||
return false;
|
||||
}
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
message.error('大小不能超过 10MB');
|
||||
return false;
|
||||
}
|
||||
loading.value = true;
|
||||
importSdyDealerOrder(file)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
/* 更新 visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
@@ -1,42 +1,106 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="flex items-center gap-20">
|
||||
<!-- 搜索表单 -->
|
||||
<a-form
|
||||
:model="where"
|
||||
layout="inline"
|
||||
class="search-form"
|
||||
@finish="handleSearch"
|
||||
>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button
|
||||
danger
|
||||
class="ele-btn-icon"
|
||||
v-if="selection.length > 0"
|
||||
:disabled="selection?.length === 0"
|
||||
@click="removeBatch"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined/>
|
||||
</template>
|
||||
<span>批量删除</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入用户ID"
|
||||
style="width: 240px"
|
||||
v-model:value="where.keywords"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
<a-button @click="resetSearch">
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<Import v-model:visible="showImport" @done="emit('importDone')"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
import {ref} from 'vue';
|
||||
import {
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import Import from './Import.vue';
|
||||
import useSearch from "@/utils/use-search";
|
||||
import {ShopDealerWithdrawParam} from "@/api/shop/shopDealerWithdraw/model";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 选中的数据
|
||||
selection?: any[];
|
||||
}>(),
|
||||
{
|
||||
selection: () => []
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ShopDealerWithdrawParam): void;
|
||||
(e: 'batchSettle'): void;
|
||||
(e: 'export'): void;
|
||||
(e: 'importDone'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
// 是否显示导入弹窗
|
||||
const showImport = ref(false);
|
||||
|
||||
// 搜索表单
|
||||
const {where, resetFields} = useSearch<ShopDealerWithdrawParam>({
|
||||
keywords: '',
|
||||
});
|
||||
|
||||
// 搜索
|
||||
const handleSearch = () => {
|
||||
const searchParams = {...where};
|
||||
// 清除空值
|
||||
Object.keys(searchParams).forEach(key => {
|
||||
if (searchParams[key] === '' || searchParams[key] === undefined) {
|
||||
delete searchParams[key];
|
||||
}
|
||||
});
|
||||
emit('search', searchParams);
|
||||
};
|
||||
|
||||
// 重置搜索
|
||||
const resetSearch = () => {
|
||||
resetFields();
|
||||
emit('search', {});
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:width="1000"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑分销商提现明细表' : '添加分销商提现明细表'"
|
||||
:title="isUpdate ? '编辑提现申请' : '新增提现申请'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -14,253 +14,669 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-item label="分销商用户ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分销商用户ID"
|
||||
v-model:value="form.userId"
|
||||
<!-- 基本信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">基本信息</span>
|
||||
</a-divider>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="备注" name="comments">
|
||||
<div class="text-red-500">{{ form.comments }}</div>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 收款信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">收款信息</span>
|
||||
</a-divider>
|
||||
|
||||
<!-- 微信收款信息 -->
|
||||
<div v-if="form.payType === 10" class="payment-info wechat-info">
|
||||
<a-alert
|
||||
message="微信收款信息"
|
||||
description="请确保微信账号信息准确,以免影响到账"
|
||||
type="success"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="提现金额" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入提现金额"
|
||||
v-model:value="form.money"
|
||||
<a-form-item label="微信号" name="wechatAccount">
|
||||
<a-input
|
||||
placeholder="请输入微信号"
|
||||
v-model:value="form.wechatAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="微信昵称" name="wechatName">
|
||||
<a-input
|
||||
placeholder="请输入微信昵称"
|
||||
v-model:value="form.wechatName"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 支付宝收款信息 -->
|
||||
<div v-if="form.payType === 20" class="payment-info alipay-info">
|
||||
<a-alert
|
||||
message="支付宝收款信息"
|
||||
description="请确保支付宝账号信息准确,姓名需与实名认证一致"
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="打款方式 (10微信 20支付宝 30银行卡)" name="payType">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入打款方式 (10微信 20支付宝 30银行卡)"
|
||||
v-model:value="form.payType"
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="支付宝姓名" name="alipayName">
|
||||
<a-input
|
||||
placeholder="请输入支付宝实名姓名"
|
||||
disabled
|
||||
v-model:value="form.alipayName"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="支付宝账号" name="alipayAccount">
|
||||
<a-input
|
||||
placeholder="请输入支付宝账号"
|
||||
disabled
|
||||
v-model:value="form.alipayAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 银行卡收款信息 -->
|
||||
<div v-if="form.payType === 30" class="payment-info bank-info">
|
||||
<a-alert
|
||||
message="银行卡收款信息"
|
||||
description="请确保银行卡信息准确,开户名需与身份证姓名一致"
|
||||
type="warning"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付宝姓名" name="alipayName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入支付宝姓名"
|
||||
v-model:value="form.alipayName"
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="开户行名称" name="bankName">
|
||||
{{ form.bankName }}
|
||||
</a-form-item>
|
||||
<a-form-item label="银行开户名" name="bankAccount">
|
||||
{{ form.bankAccount }}
|
||||
</a-form-item>
|
||||
<a-form-item label="银行卡号" name="bankCard">
|
||||
{{ form.bankCard }}
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
|
||||
<!-- 审核信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">审核信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="申请状态" name="applyStatus">
|
||||
<a-select v-model:value="form.applyStatus" :disabled="form.applyStatus == 40 || form.applyStatus == 30"
|
||||
placeholder="请选择申请状态">
|
||||
<a-select-option :value="10">
|
||||
<div class="status-option">
|
||||
<a-tag color="orange">待审核</a-tag>
|
||||
<span>等待审核</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option :value="20">
|
||||
<div class="status-option">
|
||||
<a-tag color="success">审核通过</a-tag>
|
||||
<span>审核通过</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option :value="30">
|
||||
<div class="status-option">
|
||||
<a-tag color="error">审核驳回</a-tag>
|
||||
<span>审核驳回</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option :value="40">
|
||||
<div class="status-option">
|
||||
<a-tag>已打款</a-tag>
|
||||
<span>已完成打款</span>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="驳回原因" name="rejectReason" v-if="form.applyStatus === 30">
|
||||
<a-textarea
|
||||
v-model:value="form.rejectReason"
|
||||
placeholder="请输入驳回原因"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="上传支付凭证" name="image" v-if="form.applyStatus === 40">
|
||||
<SelectFile
|
||||
:placeholder="`请选择图片`"
|
||||
:limit="2"
|
||||
:data="files"
|
||||
@done="chooseFile"
|
||||
@del="onDeleteFile"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 提现预览 -->
|
||||
<div class="withdraw-preview" v-if="form.money && form.payType">
|
||||
<a-alert
|
||||
:type="getPreviewAlertType()"
|
||||
:message="getPreviewText()"
|
||||
show-icon
|
||||
style="margin-top: 16px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="支付宝账号" name="alipayAccount">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入支付宝账号"
|
||||
v-model:value="form.alipayAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="开户行名称" name="bankName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入开户行名称"
|
||||
v-model:value="form.bankName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="银行开户名" name="bankAccount">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入银行开户名"
|
||||
v-model:value="form.bankAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="银行卡号" name="bankCard">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入银行卡号"
|
||||
v-model:value="form.bankCard"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="申请状态 (10待审核 20审核通过 30驳回 40已打款)" name="applyStatus">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入申请状态 (10待审核 20审核通过 30驳回 40已打款)"
|
||||
v-model:value="form.applyStatus"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="审核时间" name="auditTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入审核时间"
|
||||
v-model:value="form.auditTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="驳回原因" name="rejectReason">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入驳回原因"
|
||||
v-model:value="form.rejectReason"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="来源客户端(APP、H5、小程序等)" name="platform">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入来源客户端(APP、H5、小程序等)"
|
||||
v-model:value="form.platform"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addShopDealerWithdraw, updateShopDealerWithdraw } from '@/api/shop/shopDealerWithdraw';
|
||||
import { ShopDealerWithdraw } from '@/api/shop/shopDealerWithdraw/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import {assignObject, uuid} from 'ele-admin-pro';
|
||||
import {addShopDealerWithdraw, updateShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw';
|
||||
import {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model';
|
||||
import {FormInstance} from 'ant-design-vue/es/form';
|
||||
import {ItemType} from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import dayjs from 'dayjs';
|
||||
import {FileRecord} from "@/api/system/file/model";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopDealerWithdraw | null;
|
||||
}>();
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopDealerWithdraw | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
// 提交状态
|
||||
const loading = ref<boolean>(false);
|
||||
const isSuccess = ref<boolean>(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
const files = ref<ItemType[]>([]);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ShopDealerWithdraw>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
money: undefined,
|
||||
payType: undefined,
|
||||
alipayName: undefined,
|
||||
alipayAccount: undefined,
|
||||
bankName: undefined,
|
||||
bankAccount: undefined,
|
||||
bankCard: undefined,
|
||||
applyStatus: undefined,
|
||||
auditTime: undefined,
|
||||
rejectReason: undefined,
|
||||
platform: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopDealerWithdrawId: undefined,
|
||||
shopDealerWithdrawName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
// 表单数据
|
||||
const form = reactive<ShopDealerWithdraw>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
realName: undefined,
|
||||
nickname: undefined,
|
||||
phone: undefined,
|
||||
avatar: undefined,
|
||||
money: undefined,
|
||||
payType: undefined,
|
||||
// 微信相关
|
||||
wechatAccount: '',
|
||||
wechatName: '',
|
||||
// 支付宝相关
|
||||
alipayName: '',
|
||||
alipayAccount: '',
|
||||
// 银行卡相关
|
||||
bankName: '',
|
||||
bankAccount: '',
|
||||
bankCard: '',
|
||||
// 审核相关
|
||||
applyStatus: 10,
|
||||
auditTime: undefined,
|
||||
rejectReason: '',
|
||||
platform: '',
|
||||
comments: '',
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopDealerWithdrawName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写分销商提现明细表名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入分销商用户ID',
|
||||
trigger: 'blur'
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerWithdraw : addShopDealerWithdraw;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
],
|
||||
money: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入提现金额',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value && value <= 0) {
|
||||
return Promise.reject('提现金额必须大于0');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
payType: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择打款方式',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
platform: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择来源平台',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
// 微信验证
|
||||
wechatAccount: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 10 && !value) {
|
||||
return Promise.reject('请输入微信号');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
wechatName: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 10 && !value) {
|
||||
return Promise.reject('请输入微信昵称');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// 支付宝验证
|
||||
alipayName: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 20 && !value) {
|
||||
return Promise.reject('请输入支付宝姓名');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
alipayAccount: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 20 && !value) {
|
||||
return Promise.reject('请输入支付宝账号');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// 银行卡验证
|
||||
bankName: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 30 && !value) {
|
||||
return Promise.reject('请输入开户行名称');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
bankAccount: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 30 && !value) {
|
||||
return Promise.reject('请输入银行开户名');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
bankCard: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.payType === 30 && !value) {
|
||||
return Promise.reject('请输入银行卡号');
|
||||
}
|
||||
if (form.payType === 30 && value && !/^\d{16,19}$/.test(value)) {
|
||||
return Promise.reject('银行卡号格式不正确');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
applyStatus: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择申请状态',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
rejectReason: [
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (form.applyStatus === 30 && !value) {
|
||||
return Promise.reject('驳回时必须填写驳回原因');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
image: [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传打款凭证',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 打款方式改变时的处理 */
|
||||
const onPayTypeChange = (e: any) => {
|
||||
const payType = e.target.value;
|
||||
|
||||
// 清空其他支付方式的信息
|
||||
if (payType !== 10) {
|
||||
form.alipayAccount = '';
|
||||
form.alipayName = '';
|
||||
}
|
||||
if (payType !== 20) {
|
||||
form.alipayName = '';
|
||||
form.alipayAccount = '';
|
||||
}
|
||||
if (payType !== 30) {
|
||||
form.bankName = '';
|
||||
form.bankAccount = '';
|
||||
form.bankCard = '';
|
||||
}
|
||||
};
|
||||
|
||||
const chooseFile = (data: FileRecord) => {
|
||||
files.value.push({
|
||||
uid: data.id,
|
||||
url: data.url,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = JSON.stringify(files.value.map((d) => d.url));
|
||||
};
|
||||
|
||||
const onDeleteFile = (index: number) => {
|
||||
files.value.splice(index, 1);
|
||||
};
|
||||
|
||||
/* 获取预览提示类型 */
|
||||
const getPreviewAlertType = () => {
|
||||
if (!form.applyStatus) return 'info';
|
||||
|
||||
switch (form.applyStatus) {
|
||||
case 10:
|
||||
return 'processing';
|
||||
case 20:
|
||||
return 'success';
|
||||
case 30:
|
||||
return 'error';
|
||||
case 40:
|
||||
return 'success';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
/* 获取预览文本 */
|
||||
const getPreviewText = () => {
|
||||
if (!form.money || !form.payType) return '';
|
||||
|
||||
const amount = parseFloat(form.money.toString()).toFixed(2);
|
||||
const payTypeMap = {
|
||||
10: '微信',
|
||||
20: '支付宝',
|
||||
30: '银行卡'
|
||||
};
|
||||
const statusMap = {
|
||||
10: '待审核',
|
||||
20: '审核通过',
|
||||
30: '审核驳回',
|
||||
40: '已打款'
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
const payTypeName = payTypeMap[form.payType] || '未知方式';
|
||||
const statusName = statusMap[form.applyStatus] || '未知状态';
|
||||
|
||||
return `提现金额:¥${amount},打款方式:${payTypeName},当前状态:${statusName}`;
|
||||
};
|
||||
|
||||
const {resetFields} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
if (isSuccess.value) {
|
||||
console.log('isSuccess')
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
return;
|
||||
}
|
||||
if (form.realName == '' || form.realName == null) {
|
||||
message.error('该用户未完成实名认证!');
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
|
||||
// 处理时间字段转换
|
||||
if (formData.auditTime && dayjs.isDayjs(formData.auditTime)) {
|
||||
formData.auditTime = formData.auditTime.valueOf();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 根据支付方式清理不相关字段
|
||||
if (formData.payType !== 10) {
|
||||
delete formData.wechatAccount;
|
||||
delete formData.wechatName;
|
||||
}
|
||||
if (formData.payType !== 20) {
|
||||
delete formData.alipayName;
|
||||
delete formData.alipayAccount;
|
||||
}
|
||||
if (formData.payType !== 30) {
|
||||
delete formData.bankName;
|
||||
delete formData.bankAccount;
|
||||
delete formData.bankCard;
|
||||
}
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopDealerWithdraw : addShopDealerWithdraw;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
files.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
// 处理时间字段
|
||||
if (props.data.auditTime) {
|
||||
form.auditTime = dayjs(props.data.auditTime);
|
||||
}
|
||||
if (props.data.image) {
|
||||
const arr = JSON.parse(props.data.image);
|
||||
arr.map((url: string) => {
|
||||
files.value.push({
|
||||
uid: uuid(),
|
||||
url: url,
|
||||
status: 'done'
|
||||
});
|
||||
});
|
||||
isSuccess.value = true;
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
money: undefined,
|
||||
payType: undefined,
|
||||
wechatAccount: '',
|
||||
wechatName: '',
|
||||
alipayName: '',
|
||||
alipayAccount: '',
|
||||
bankName: '',
|
||||
bankAccount: '',
|
||||
bankCard: '',
|
||||
applyStatus: 10,
|
||||
auditTime: undefined,
|
||||
rejectReason: '',
|
||||
platform: '',
|
||||
image: '',
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.platform-option,
|
||||
.status-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ant-tag {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-info {
|
||||
background: #fafafa;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.wechat-info {
|
||||
border-left: 3px solid #52c41a;
|
||||
}
|
||||
|
||||
&.alipay-info {
|
||||
border-left: 3px solid #1890ff;
|
||||
}
|
||||
|
||||
&.bank-info {
|
||||
border-left: 3px solid #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
.withdraw-preview {
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-radio) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.ant-radio-inner {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,292 +1,439 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerWithdrawId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopDealerWithdrawId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'applyStatus'">
|
||||
<a-tag v-if="record.applyStatus === 10" color="orange">待审核</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 20" color="success">审核通过</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 30" color="error">已驳回</a-tag>
|
||||
<a-tag v-if="record.applyStatus === 40">已打款</a-tag>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
<template v-if="column.key === 'userInfo'">
|
||||
<a-space>
|
||||
<a-avatar :src="record.avatar" />
|
||||
<div class="flex flex-col">
|
||||
<span>{{ record.realName || '未实名认证' }}</span>
|
||||
<span class="text-gray-400">{{ record.phone }}</span>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'paymentInfo'">
|
||||
<template v-if="record.payType === 10">
|
||||
<a-space direction="vertical">
|
||||
<a-tag color="blue">微信</a-tag>
|
||||
<span>{{ record.wechatName }}</span>
|
||||
<span>{{ record.wechatName }}</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
<template v-if="record.payType === 20">
|
||||
<a-space direction="vertical">
|
||||
<a-tag color="blue">支付宝</a-tag>
|
||||
<span>{{ record.alipayName }}</span>
|
||||
<span>{{ record.alipayAccount }}</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
<template v-if="record.payType === 30">
|
||||
<a-space direction="vertical">
|
||||
<a-tag color="blue">银行卡</a-tag>
|
||||
<span>{{ record.bankName }}</span>
|
||||
<span>{{ record.bankAccount }}</span>
|
||||
<span>{{ record.bankCard }}</span>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<template v-if="record.applyStatus === 30">
|
||||
<div class="text-red-500">驳回原因:{{ record.rejectReason }}</div>
|
||||
</template>
|
||||
<template v-if="record.applyStatus === 40 && record.image">
|
||||
<a-image v-for="(item,index) in JSON.parse(record.image)" :key="index" :src="item" :width="50"
|
||||
:height="50"/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<a-space direction="vertical">
|
||||
<a-tooltip title="创建时间">{{ record.createTime }}</a-tooltip>
|
||||
<a-tooltip title="审核/打款时间" class="text-green-500">{{ record.auditTime }}</a-tooltip>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="record.applyStatus !== 40">
|
||||
<a @click="openEdit(record)" class="ele-text-primary">
|
||||
<EditOutlined/>
|
||||
编辑
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm
|
||||
title="确定要删除此提现记录吗?"
|
||||
@confirm="remove(record)"
|
||||
placement="topRight"
|
||||
>
|
||||
<a class="ele-text-danger">
|
||||
<DeleteOutlined/>
|
||||
删除
|
||||
</a>
|
||||
</a-popconfirm>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerWithdrawEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopDealerWithdrawEdit v-model:visible="showEdit" :data="current" @done="reload"/>
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerWithdrawEdit from './components/shopDealerWithdrawEdit.vue';
|
||||
import { pageShopDealerWithdraw, removeShopDealerWithdraw, removeBatchShopDealerWithdraw } from '@/api/shop/shopDealerWithdraw';
|
||||
import type { ShopDealerWithdraw, ShopDealerWithdrawParam } from '@/api/shop/shopDealerWithdraw/model';
|
||||
import {createVNode, ref} from 'vue';
|
||||
import {message, Modal} from 'ant-design-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
DollarOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {EleProTable} from 'ele-admin-pro';
|
||||
import {toDateString} from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopDealerWithdrawEdit from './components/shopDealerWithdrawEdit.vue';
|
||||
import {
|
||||
pageShopDealerWithdraw,
|
||||
removeShopDealerWithdraw,
|
||||
removeBatchShopDealerWithdraw,
|
||||
updateShopDealerWithdraw
|
||||
} from '@/api/shop/shopDealerWithdraw';
|
||||
import type {ShopDealerWithdraw, ShopDealerWithdrawParam} from '@/api/shop/shopDealerWithdraw/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerWithdraw[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerWithdraw | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopDealerWithdraw[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopDealerWithdraw | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageShopDealerWithdraw({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
dataIndex: 'money',
|
||||
key: 'money',
|
||||
align: 'center',
|
||||
width: 150,
|
||||
customRender: ({text}) => {
|
||||
const amount = parseFloat(text || '0').toFixed(2);
|
||||
return {
|
||||
type: 'span',
|
||||
children: `¥${amount}`
|
||||
};
|
||||
}
|
||||
return pageShopDealerWithdraw({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
},
|
||||
{
|
||||
title: '用户信息',
|
||||
dataIndex: 'userInfo',
|
||||
key: 'userInfo'
|
||||
},
|
||||
{
|
||||
title: '收款信息',
|
||||
dataIndex: 'paymentInfo',
|
||||
key: 'paymentInfo'
|
||||
},
|
||||
// {
|
||||
// title: '审核时间',
|
||||
// dataIndex: 'auditTime',
|
||||
// key: 'auditTime',
|
||||
// align: 'center',
|
||||
// width: 120,
|
||||
// customRender: ({ text }) => text ? toDateString(new Date(text), 'yyyy-MM-dd HH:mm') : '-'
|
||||
// },
|
||||
// {
|
||||
// title: '驳回原因',
|
||||
// dataIndex: 'rejectReason',
|
||||
// key: 'rejectReason',
|
||||
// align: 'left',
|
||||
// ellipsis: true,
|
||||
// customRender: ({ text }) => text || '-'
|
||||
// },
|
||||
// {
|
||||
// title: '来源平台',
|
||||
// dataIndex: 'platform',
|
||||
// key: 'platform',
|
||||
// align: 'center',
|
||||
// width: 100,
|
||||
// customRender: ({ text }) => text || '-'
|
||||
// },
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
},
|
||||
{
|
||||
title: '申请状态',
|
||||
dataIndex: 'applyStatus',
|
||||
key: 'applyStatus',
|
||||
align: 'center',
|
||||
width: 150
|
||||
},
|
||||
// {
|
||||
// title: '驳回原因',
|
||||
// dataIndex: 'rejectReason',
|
||||
// key: 'rejectReason',
|
||||
// align: 'center',
|
||||
// },
|
||||
// {
|
||||
// title: '来源客户端',
|
||||
// dataIndex: 'platform',
|
||||
// key: 'platform',
|
||||
// align: 'center',
|
||||
// },
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
width: 180,
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
},
|
||||
// {
|
||||
// title: '操作',
|
||||
// key: 'action',
|
||||
// width: 180,
|
||||
// fixed: 'right',
|
||||
// align: 'center',
|
||||
// hideInSetting: true
|
||||
// }
|
||||
]);
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '主键ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '分销商用户ID',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
dataIndex: 'money',
|
||||
key: 'money',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '打款方式 (10微信 20支付宝 30银行卡)',
|
||||
dataIndex: 'payType',
|
||||
key: 'payType',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '支付宝姓名',
|
||||
dataIndex: 'alipayName',
|
||||
key: 'alipayName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '支付宝账号',
|
||||
dataIndex: 'alipayAccount',
|
||||
key: 'alipayAccount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '开户行名称',
|
||||
dataIndex: 'bankName',
|
||||
key: 'bankName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '银行开户名',
|
||||
dataIndex: 'bankAccount',
|
||||
key: 'bankAccount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '银行卡号',
|
||||
dataIndex: 'bankCard',
|
||||
key: 'bankCard',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '申请状态 (10待审核 20审核通过 30驳回 40已打款)',
|
||||
dataIndex: 'applyStatus',
|
||||
key: 'applyStatus',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '审核时间',
|
||||
dataIndex: 'auditTime',
|
||||
key: 'auditTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '驳回原因',
|
||||
dataIndex: 'rejectReason',
|
||||
key: 'rejectReason',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '来源客户端(APP、H5、小程序等)',
|
||||
dataIndex: 'platform',
|
||||
key: 'platform',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerWithdrawParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({where: where});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopDealerWithdrawParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerWithdraw) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerWithdraw) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerWithdraw(row.shopDealerWithdrawId)
|
||||
.then((msg) => {
|
||||
/* 审核通过 */
|
||||
const approveWithdraw = (row: ShopDealerWithdraw) => {
|
||||
Modal.confirm({
|
||||
title: '审核通过确认',
|
||||
content: `已核对信息进行核对,正确无误!`,
|
||||
icon: createVNode(CheckOutlined),
|
||||
okText: '确认通过',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
const hide = message.loading('正在处理审核...', 0);
|
||||
// 这里需要调用审核通过的API
|
||||
setTimeout(() => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
updateShopDealerWithdraw({
|
||||
id: row.id,
|
||||
applyStatus: 20
|
||||
});
|
||||
message.success('审核通过成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}, 1000);
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerWithdraw(selection.value.map((d) => d.shopDealerWithdrawId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* 审核驳回 */
|
||||
const rejectWithdraw = (row: ShopDealerWithdraw) => {
|
||||
let rejectReason = '';
|
||||
Modal.confirm({
|
||||
title: '审核驳回',
|
||||
content: createVNode('div', null, [
|
||||
createVNode('p', null, `用户ID: ${row.userId}`),
|
||||
createVNode('p', null, `提现金额: ¥${parseFloat(row.money || '0').toFixed(2)}`),
|
||||
createVNode('p', {style: 'margin-top: 12px;'}, '请输入驳回原因:'),
|
||||
createVNode('textarea', {
|
||||
placeholder: '请输入驳回原因...',
|
||||
style: 'width: 100%; height: 80px; margin-top: 8px; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px;',
|
||||
onInput: (e: any) => {
|
||||
rejectReason = e.target.value;
|
||||
}
|
||||
})
|
||||
]),
|
||||
icon: createVNode(CloseOutlined),
|
||||
okText: '确认驳回',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
if (!rejectReason.trim()) {
|
||||
message.error('请输入驳回原因');
|
||||
return Promise.reject();
|
||||
}
|
||||
const hide = message.loading('正在处理审核...', 0);
|
||||
setTimeout(() => {
|
||||
hide();
|
||||
message.success('审核驳回成功');
|
||||
reload();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 确认打款 */
|
||||
const confirmPayment = (row: ShopDealerWithdraw) => {
|
||||
Modal.confirm({
|
||||
title: '确认打款',
|
||||
content: `确定已向用户${row.bankAccount}完成打款?此操作不可撤销`,
|
||||
icon: createVNode(DollarOutlined),
|
||||
okText: '确认打款',
|
||||
okType: 'primary',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
const hide = message.loading('正在确认打款...', 0);
|
||||
setTimeout(() => {
|
||||
updateShopDealerWithdraw({
|
||||
id: row.id,
|
||||
applyStatus: 40
|
||||
})
|
||||
hide();
|
||||
message.success('打款确认成功');
|
||||
reload();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopDealerWithdraw) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopDealerWithdraw) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopDealerWithdraw(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopDealerWithdraw(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerWithdraw) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopDealerWithdraw) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
query();
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopDealerWithdraw'
|
||||
};
|
||||
export default {
|
||||
name: 'ShopDealerWithdraw'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
42
src/views/shop/shopExpress/components/search.vue
Normal file
42
src/views/shop/shopExpress/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
220
src/views/shop/shopExpress/components/shopExpressEdit.vue
Normal file
220
src/views/shop/shopExpress/components/shopExpressEdit.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑物流公司' : '添加物流公司'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="物流公司名称" name="expressName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入物流公司名称"
|
||||
v-model:value="form.expressName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="物流公司编码 (微信)" name="wxCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入物流公司编码 (微信)"
|
||||
v-model:value="form.wxCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="物流公司编码 (快递100)" name="kuaidi100Code">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入物流公司编码 (快递100)"
|
||||
v-model:value="form.kuaidi100Code"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="物流公司编码 (快递鸟)" name="kdniaoCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入物流公司编码 (快递鸟)"
|
||||
v-model:value="form.kdniaoCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addShopExpress, updateShopExpress } from '@/api/shop/shopExpress';
|
||||
import { ShopExpress } from '@/api/shop/shopExpress/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopExpress | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ShopExpress>({
|
||||
expressId: undefined,
|
||||
expressName: undefined,
|
||||
wxCode: undefined,
|
||||
kuaidi100Code: undefined,
|
||||
kdniaoCode: undefined,
|
||||
sortNumber: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopExpressId: undefined,
|
||||
shopExpressName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopExpressName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写物流公司名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateShopExpress : addShopExpress;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
244
src/views/shop/shopExpress/index.vue
Normal file
244
src/views/shop/shopExpress/index.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopExpressId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopExpressEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopExpressEdit from './components/shopExpressEdit.vue';
|
||||
import { pageShopExpress, removeShopExpress, removeBatchShopExpress } from '@/api/shop/shopExpress';
|
||||
import type { ShopExpress, ShopExpressParam } from '@/api/shop/shopExpress/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopExpress[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopExpress | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageShopExpress({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '物流公司ID',
|
||||
dataIndex: 'expressId',
|
||||
key: 'expressId',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '物流公司名称',
|
||||
dataIndex: 'expressName',
|
||||
key: 'expressName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '物流公司编码 (微信)',
|
||||
dataIndex: 'wxCode',
|
||||
key: 'wxCode',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '物流公司编码 (快递100)',
|
||||
dataIndex: 'kuaidi100Code',
|
||||
key: 'kuaidi100Code',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '物流公司编码 (快递鸟)',
|
||||
dataIndex: 'kdniaoCode',
|
||||
key: 'kdniaoCode',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '排序号',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopExpressParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopExpress) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopExpress) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopExpress(row.shopExpressId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopExpress(selection.value.map((d) => d.shopExpressId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopExpress) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopExpress'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
42
src/views/shop/shopExpressTemplate/components/search.vue
Normal file
42
src/views/shop/shopExpressTemplate/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
@@ -5,7 +5,7 @@
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑' : '添加'"
|
||||
:title="isUpdate ? '编辑运费模板' : '添加运费模板'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -19,102 +19,75 @@
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户唯一小程序id" name="openId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户唯一小程序id"
|
||||
v-model:value="form.openId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="小程序用户秘钥" name="sessionKey">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入小程序用户秘钥"
|
||||
v-model:value="form.sessionKey"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户名" name="username">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户名"
|
||||
v-model:value="form.username"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="头像地址" name="avatarUrl">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入头像地址"
|
||||
v-model:value="form.avatarUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="1男,2女" name="gender">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入1男,2女"
|
||||
v-model:value="form.gender"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="国家" name="country">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入国家"
|
||||
v-model:value="form.country"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="省份" name="province">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入省份"
|
||||
v-model:value="form.province"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="城市" name="city">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入城市"
|
||||
v-model:value="form.city"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号码" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入手机号码"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="积分" name="integral">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入积分"
|
||||
v-model:value="form.integral"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="余额" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入余额"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="idcard">
|
||||
<a-form-item label="" name="type">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.idcard"
|
||||
v-model:value="form.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="truename">
|
||||
<a-form-item label="" name="title">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.truename"
|
||||
v-model:value="form.title"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否管理员:1是;2否" name="isAdmin">
|
||||
<a-form-item label="收件价格" name="firstAmount">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否管理员:1是;2否"
|
||||
v-model:value="form.isAdmin"
|
||||
placeholder="请输入收件价格"
|
||||
v-model:value="form.firstAmount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="续件价格" name="extraAmount">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入续件价格"
|
||||
v-model:value="form.extraAmount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0已发布, 1待审核 2已驳回 3违规内容" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="0">显示</a-radio>
|
||||
<a-radio :value="1">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="首件数量/重量" name="firstNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入首件数量/重量"
|
||||
v-model:value="form.firstNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="续件数量/重量" name="extraNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入续件数量/重量"
|
||||
v-model:value="form.extraNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -125,8 +98,8 @@
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addUser, updateUser } from '@/api/system/user';
|
||||
import { User } from '@/api/system/user/model';
|
||||
import { addShopExpressTemplate, updateShopExpressTemplate } from '@/api/shop/shopExpressTemplate';
|
||||
import { ShopExpressTemplate } from '@/api/shop/shopExpressTemplate/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
@@ -144,7 +117,7 @@
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
data?: ShopExpressTemplate | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -161,26 +134,18 @@
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<User>({
|
||||
const form = reactive<ShopExpressTemplate>({
|
||||
id: undefined,
|
||||
openId: undefined,
|
||||
sessionKey: undefined,
|
||||
username: undefined,
|
||||
avatarUrl: undefined,
|
||||
gender: undefined,
|
||||
country: undefined,
|
||||
province: undefined,
|
||||
city: undefined,
|
||||
phone: undefined,
|
||||
integral: undefined,
|
||||
money: undefined,
|
||||
createTime: undefined,
|
||||
idcard: undefined,
|
||||
truename: undefined,
|
||||
isAdmin: undefined,
|
||||
type: undefined,
|
||||
title: undefined,
|
||||
firstAmount: undefined,
|
||||
extraAmount: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
userId: undefined,
|
||||
userName: '',
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
firstNum: undefined,
|
||||
extraNum: undefined,
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
@@ -193,11 +158,11 @@
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
userName: [
|
||||
shopExpressTemplateName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写名称',
|
||||
message: '请填写运费模板名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
@@ -231,7 +196,7 @@
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
|
||||
const saveOrUpdate = isUpdate.value ? updateShopExpressTemplate : addShopExpressTemplate;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
274
src/views/shop/shopExpressTemplate/index.vue
Normal file
274
src/views/shop/shopExpressTemplate/index.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopExpressTemplateId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopExpressTemplateEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopExpressTemplateEdit from './components/shopExpressTemplateEdit.vue';
|
||||
import { pageShopExpressTemplate, removeShopExpressTemplate, removeBatchShopExpressTemplate } from '@/api/shop/shopExpressTemplate';
|
||||
import type { ShopExpressTemplate, ShopExpressTemplateParam } from '@/api/shop/shopExpressTemplate/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopExpressTemplate[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopExpressTemplate | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageShopExpressTemplate({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '收件价格',
|
||||
dataIndex: 'firstAmount',
|
||||
key: 'firstAmount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '续件价格',
|
||||
dataIndex: 'extraAmount',
|
||||
key: 'extraAmount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0已发布, 1待审核 2已驳回 3违规内容',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否删除, 0否, 1是',
|
||||
dataIndex: 'deleted',
|
||||
key: 'deleted',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '首件数量/重量',
|
||||
dataIndex: 'firstNum',
|
||||
key: 'firstNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '续件数量/重量',
|
||||
dataIndex: 'extraNum',
|
||||
key: 'extraNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopExpressTemplateParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopExpressTemplate) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopExpressTemplate) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopExpressTemplate(row.shopExpressTemplateId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopExpressTemplate(selection.value.map((d) => d.shopExpressTemplateId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopExpressTemplate) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopExpressTemplate'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,232 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑运费模板' : '添加运费模板'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="" name="templateId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.templateId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="0按件" name="type">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入0按件"
|
||||
v-model:value="form.type"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="provinceId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.provinceId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="cityId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.cityId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="首件数量/重量" name="firstNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入首件数量/重量"
|
||||
v-model:value="form.firstNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="收件价格" name="firstAmount">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入收件价格"
|
||||
v-model:value="form.firstAmount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="续件价格" name="extraAmount">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入续件价格"
|
||||
v-model:value="form.extraAmount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="续件数量/重量" name="extraNum">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入续件数量/重量"
|
||||
v-model:value="form.extraNum"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0已发布, 1待审核 2已驳回 3违规内容" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="0">显示</a-radio>
|
||||
<a-radio :value="1">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addShopExpressTemplateDetail, updateShopExpressTemplateDetail } from '@/api/shop/shopExpressTemplateDetail';
|
||||
import { ShopExpressTemplateDetail } from '@/api/shop/shopExpressTemplateDetail/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopExpressTemplateDetail | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ShopExpressTemplateDetail>({
|
||||
id: undefined,
|
||||
templateId: undefined,
|
||||
type: undefined,
|
||||
provinceId: undefined,
|
||||
cityId: undefined,
|
||||
firstNum: undefined,
|
||||
firstAmount: undefined,
|
||||
extraAmount: undefined,
|
||||
extraNum: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
status: 0,
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopExpressTemplateDetailName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写运费模板名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateShopExpressTemplateDetail : addShopExpressTemplateDetail;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
286
src/views/shop/shopExpressTemplateDetail/index.vue
Normal file
286
src/views/shop/shopExpressTemplateDetail/index.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="shopExpressTemplateDetailId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopExpressTemplateDetailEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import {getPageTitle} from '@/utils/common';
|
||||
import ShopExpressTemplateDetailEdit from './components/shopExpressTemplateDetailEdit.vue';
|
||||
import { pageShopExpressTemplateDetail, removeShopExpressTemplateDetail, removeBatchShopExpressTemplateDetail } from '@/api/shop/shopExpressTemplateDetail';
|
||||
import type { ShopExpressTemplateDetail, ShopExpressTemplateDetailParam } from '@/api/shop/shopExpressTemplateDetail/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopExpressTemplateDetail[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopExpressTemplateDetail | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageShopExpressTemplateDetail({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'templateId',
|
||||
key: 'templateId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '0按件',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'provinceId',
|
||||
key: 'provinceId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'cityId',
|
||||
key: 'cityId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '首件数量/重量',
|
||||
dataIndex: 'firstNum',
|
||||
key: 'firstNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '收件价格',
|
||||
dataIndex: 'firstAmount',
|
||||
key: 'firstAmount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '续件价格',
|
||||
dataIndex: 'extraAmount',
|
||||
key: 'extraAmount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '续件数量/重量',
|
||||
dataIndex: 'extraNum',
|
||||
key: 'extraNum',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0已发布, 1待审核 2已驳回 3违规内容',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否删除, 0否, 1是',
|
||||
dataIndex: 'deleted',
|
||||
key: 'deleted',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopExpressTemplateDetailParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopExpressTemplateDetail) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopExpressTemplateDetail) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopExpressTemplateDetail(row.shopExpressTemplateDetailId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopExpressTemplateDetail(selection.value.map((d) => d.shopExpressTemplateDetailId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopExpressTemplateDetail) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopExpressTemplateDetail'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -19,122 +19,352 @@
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="名称" name="name">
|
||||
<a-input allow-clear placeholder="请输入" v-model:value="form.name" />
|
||||
<a-form-item label="礼品卡" name="name">
|
||||
<a-input allow-clear placeholder="请输入礼品卡名称" v-model:value="form.name"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="商品" name="goodsId">
|
||||
<a-select v-model:value="form.goodsId">
|
||||
<a-select-option
|
||||
v-for="item in goodsList"
|
||||
:key="item.goodsId"
|
||||
:value="item.goodsId"
|
||||
>
|
||||
{{ item.name }}
|
||||
<a-form-item label="关联商品" name="goodsId">
|
||||
<a-select
|
||||
v-model:value="form.goodsId"
|
||||
placeholder="请选择关联商品"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
:loading="goodsLoading"
|
||||
@search="searchGoods"
|
||||
@change="onGoodsChange"
|
||||
@dropdown-visible-change="onDropdownVisibleChange"
|
||||
>
|
||||
<a-select-option v-for="goods in goodsList" :key="goods.goodsId" :value="goods.goodsId">
|
||||
<div class="goods-option">
|
||||
<span>{{ goods.name }}</span>
|
||||
<a-tag color="blue" style="margin-left: 8px;">¥{{ goods.price || 0 }}</a-tag>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option v-if="goodsList.length === 0" disabled>
|
||||
<div style="text-align: center; color: #999;">
|
||||
{{ goodsLoading ? '加载中...' : '暂无商品数据' }}
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="数量" name="num">
|
||||
<a-input-number v-model:value="form.num" :min="0" />
|
||||
<a-form-item label="生成数量" name="num">
|
||||
<a-input-number v-model:value="form.num" :min="0"/>
|
||||
</a-form-item>
|
||||
<a-form-item label="使用地址" name="useLocation">
|
||||
<a-input
|
||||
placeholder="请输入使用的门店地址"
|
||||
v-model:value="form.useLocation"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注信息" name="comments">
|
||||
<a-textarea
|
||||
v-model:value="form.comments"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<!-- 礼品卡预览 -->
|
||||
<div class="gift-card-preview" v-if="form.name">
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">礼品卡预览</span>
|
||||
</a-divider>
|
||||
<div class="gift-card">
|
||||
<div class="gift-card-header">
|
||||
<div class="gift-card-title">{{ form.name }}</div>
|
||||
<div class="gift-card-status">
|
||||
<a-tag>
|
||||
<span v-if="form.takeTime">领取时间:{{ formatTime(form.takeTime) }}</span>
|
||||
<span v-else>未领取</span>
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gift-card-body">
|
||||
<div class="gift-card-code">
|
||||
<span class="code-label text-gray-50">卡密:</span>
|
||||
<span class="code-value">{{ form.code || '自动生成' }}</span>
|
||||
</div>
|
||||
<div class="gift-card-goods" v-if="selectedGoods">
|
||||
<span class="goods-label text-gray-50">关联商品:</span>
|
||||
<span class="goods-name">{{ selectedGoods.name }}</span>
|
||||
<a-tag color="blue" style="margin-left: 8px;">¥{{ selectedGoods.price }}</a-tag>
|
||||
</div>
|
||||
<div class="gift-card-goods py-2" v-if="selectedGoods">
|
||||
<span class="goods-label">使用地址:</span>
|
||||
<span class="goods-name">{{ form.useLocation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gift-card-footer">
|
||||
<div class="gift-card-info text-gray-50">
|
||||
备注: {{ form.comments }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { makeShopGift } from '@/api/shop/shopGift';
|
||||
import { ShopGift } from '@/api/shop/shopGift/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { listShopGoods } from '@/api/shop/shopGoods';
|
||||
import { ShopGoods } from '@/api/shop/shopGoods/model';
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import {makeShopGift} from '@/api/shop/shopGift';
|
||||
import {ShopGift} from '@/api/shop/shopGift/model';
|
||||
import {useThemeStore} from '@/store/modules/theme';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {FormInstance} from 'ant-design-vue/es/form';
|
||||
import {listShopGoods} from '@/api/shop/shopGoods';
|
||||
import {ShopGoods} from '@/api/shop/shopGoods/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const {styleResponsive} = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
}>();
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
// 商品列表
|
||||
const goodsList = ref<ShopGoods[]>([]);
|
||||
// 商品加载状态
|
||||
const goodsLoading = ref(false);
|
||||
// 选中的商品
|
||||
const selectedGoods = ref<ShopGoods | null>(null);
|
||||
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
|
||||
goodsId: [{ required: true, message: '请选择商品', trigger: 'change' }],
|
||||
num: [{ required: true, message: '请输入数量', trigger: 'blur' }]
|
||||
});
|
||||
const rules = reactive({
|
||||
name: [{required: true, message: '请输入名称', trigger: 'blur'}],
|
||||
goodsId: [{required: true, message: '请选择商品', trigger: 'change'}],
|
||||
num: [{required: true, message: '请输入数量', trigger: 'blur'}]
|
||||
});
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ShopGift>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
goodsId: undefined,
|
||||
takeTime: undefined,
|
||||
operatorUserId: undefined,
|
||||
isShow: undefined,
|
||||
status: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
userId: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
num: undefined
|
||||
});
|
||||
// 用户信息
|
||||
const form = reactive<ShopGift>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
goodsId: undefined,
|
||||
takeTime: undefined,
|
||||
operatorUserId: undefined,
|
||||
isShow: undefined,
|
||||
status: undefined,
|
||||
useLocation: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
userId: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
num: 1000
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const goodsList = ref<ShopGoods[]>([]);
|
||||
const getGoodsList = async () => {
|
||||
goodsList.value = await listShopGoods();
|
||||
};
|
||||
getGoodsList();
|
||||
const getGoodsList = async () => {
|
||||
goodsList.value = await listShopGoods();
|
||||
};
|
||||
getGoodsList();
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
/* 搜索商品 */
|
||||
const searchGoods = async (value: string) => {
|
||||
if (value && value.trim()) {
|
||||
goodsLoading.value = true;
|
||||
try {
|
||||
const res = await listShopGoods({keywords: value.trim()});
|
||||
goodsList.value = res || [];
|
||||
console.log('搜索到的商品:', goodsList.value);
|
||||
} catch (e) {
|
||||
console.error('搜索商品失败:', e);
|
||||
goodsList.value = [];
|
||||
} finally {
|
||||
goodsLoading.value = false;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
makeShopGift(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
watch(() => props.visible, { immediate: true });
|
||||
/* 下拉框显示状态改变 */
|
||||
const onDropdownVisibleChange = (open: boolean) => {
|
||||
if (open && goodsList.value.length === 0) {
|
||||
// 当下拉框打开且没有数据时,加载默认商品列表
|
||||
getGoodsList();
|
||||
}
|
||||
};
|
||||
|
||||
/* 商品选择改变 */
|
||||
const onGoodsChange = (goodsId: number) => {
|
||||
selectedGoods.value = goodsList.value.find(goods => goods.goodsId === goodsId) || null;
|
||||
console.log('选中的商品:', selectedGoods.value);
|
||||
};
|
||||
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
makeShopGift(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
watch(() => props.visible, {immediate: true});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.goods-option,
|
||||
.status-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.ant-tag {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-preview {
|
||||
margin-top: 24px;
|
||||
|
||||
.gift-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
right: -50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.gift-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.gift-card-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-body {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.gift-card-code {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.code-label {
|
||||
font-weight: 600;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-goods {
|
||||
.goods-label {
|
||||
font-weight: 600;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
margin-left: 8px;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-footer {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.gift-card-info {
|
||||
font-size: 12px;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,75 +1,627 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<!-- <a-button type="primary" class="ele-btn-icon" @click="add">-->
|
||||
<!-- <template #icon>-->
|
||||
<!-- <PlusOutlined />-->
|
||||
<!-- </template>-->
|
||||
<!-- <span>添加</span>-->
|
||||
<!-- </a-button>-->
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openMultiAdd">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
<PlusOutlined/>
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
<a-button class="ele-btn-icon" @click="openMultiAdd">
|
||||
<span>批量生成</span>
|
||||
</a-button>
|
||||
<a-button class="ele-btn-icon" @click="exportData">
|
||||
<span>导出</span>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="名称|秘钥|用户ID"
|
||||
style="width: 240px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-button
|
||||
type="text"
|
||||
:icon="h(QrcodeOutlined)"
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
>导出二维码
|
||||
</a-button
|
||||
>
|
||||
<a-button
|
||||
type="text"
|
||||
@click="handlePrint">打印
|
||||
</a-button>
|
||||
<MakeCard v-model:visible="showMultiAdd" @done="done"></MakeCard>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { watch, ref } from 'vue';
|
||||
import {ShopGift, ShopGiftParam} from "@/api/shop/shopGift/model";
|
||||
import MakeCard from "@/views/shop/shopGift/components/makeCard.vue";
|
||||
import {exportShopGift} from "@/api/shop/shopGift";
|
||||
import {message} from "ant-design-vue";
|
||||
import {PlusOutlined, QrcodeOutlined} from '@ant-design/icons-vue';
|
||||
import {watch, ref, h} from 'vue';
|
||||
import {ShopGift, ShopGiftParam} from "@/api/shop/shopGift/model";
|
||||
import MakeCard from "@/views/shop/shopGift/components/makeCard.vue";
|
||||
import {listShopGift} from "@/api/shop/shopGift";
|
||||
import {message} from "ant-design-vue";
|
||||
import {
|
||||
Document,
|
||||
Packer,
|
||||
Paragraph,
|
||||
ImageRun,
|
||||
AlignmentType,
|
||||
Table,
|
||||
TableRow,
|
||||
TableCell,
|
||||
WidthType
|
||||
} from 'docx';
|
||||
import {saveAs} from 'file-saver';
|
||||
import QRCode from 'qrcode';
|
||||
import useSearch from "@/utils/use-search";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: ShopGift[];
|
||||
}>(),
|
||||
{}
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: ShopGift[];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ShopGiftParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'done'): void;
|
||||
}>();
|
||||
|
||||
// 表单数据
|
||||
const {where} = useSearch<ShopGiftParam>({
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 新增
|
||||
// const add = () => {
|
||||
// emit('add');
|
||||
// };
|
||||
|
||||
const reload = () => {
|
||||
emit('search', {...where});
|
||||
};
|
||||
|
||||
const done = () => {
|
||||
emit('done');
|
||||
};
|
||||
|
||||
const showMultiAdd = ref(false)
|
||||
const exportLoading = ref(false);
|
||||
|
||||
const openMultiAdd = () => {
|
||||
showMultiAdd.value = true
|
||||
};
|
||||
|
||||
// 批量导出二维码到Word文档
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
exportLoading.value = true;
|
||||
message.loading('正在生成二维码文档,请稍候...', 0);
|
||||
|
||||
// 获取所有礼品卡数据
|
||||
let giftList: ShopGift[] = [];
|
||||
if (props.selection && props.selection.length > 0) {
|
||||
// 如果有选中的数据,只导出选中的
|
||||
giftList = props.selection;
|
||||
} else {
|
||||
// 如果没有选中,导出所有数据
|
||||
giftList = await listShopGift();
|
||||
}
|
||||
|
||||
if (!giftList || giftList.length === 0) {
|
||||
message.error('没有礼品卡数据可导出');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成二维码图片
|
||||
const qrCodeImages: { dataUrl: string; giftInfo: ShopGift }[] = [];
|
||||
|
||||
for (const gift of giftList) {
|
||||
try {
|
||||
// 生成二维码(使用礼品卡code作为内容)
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(String(gift.code), {
|
||||
width: 200,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
});
|
||||
|
||||
qrCodeImages.push({dataUrl: qrCodeDataUrl, giftInfo: gift});
|
||||
} catch (error) {
|
||||
console.error(`生成礼品卡 ${gift.code} 的二维码失败:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (qrCodeImages.length === 0) {
|
||||
message.error('二维码生成失败');
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试创建Word文档,如果失败则使用HTML方式
|
||||
try {
|
||||
await createWordDocument(qrCodeImages);
|
||||
message.destroy();
|
||||
message.success(`成功导出 ${qrCodeImages.length} 个礼品卡二维码`);
|
||||
} catch (docError) {
|
||||
console.warn('Word文档生成失败,使用HTML方式:', docError);
|
||||
createHtmlDocument(qrCodeImages);
|
||||
message.destroy();
|
||||
message.success(
|
||||
`成功生成 ${qrCodeImages.length} 个礼品卡二维码(HTML格式,可直接打印)`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error);
|
||||
message.destroy();
|
||||
message.error('导出失败,请重试');
|
||||
} finally {
|
||||
exportLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 创建Word文档
|
||||
const createWordDocument = async (
|
||||
qrCodeImages: { dataUrl: string; giftInfo: ShopGift }[]
|
||||
) => {
|
||||
const children: (Paragraph | Table)[] = [];
|
||||
|
||||
// 添加标题
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: '礼品卡二维码清单',
|
||||
alignment: AlignmentType.CENTER,
|
||||
spacing: {after: 400}
|
||||
})
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: ShopGiftParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'done'): void;
|
||||
}>();
|
||||
// 每行放置3个二维码,保持适当间距
|
||||
const itemsPerRow = 3;
|
||||
const rows = Math.ceil(qrCodeImages.length / itemsPerRow);
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
for (let row = 0; row < rows; row++) {
|
||||
const startIndex = row * itemsPerRow;
|
||||
const endIndex = Math.min(startIndex + itemsPerRow, qrCodeImages.length);
|
||||
const rowItems = qrCodeImages.slice(startIndex, endIndex);
|
||||
|
||||
const done = () => {
|
||||
emit('done');
|
||||
};
|
||||
// 创建表格行来放置二维码
|
||||
const qrCodeCells: TableCell[] = [];
|
||||
const infoCells: TableCell[] = [];
|
||||
|
||||
const showMultiAdd = ref(false)
|
||||
const openMultiAdd = () => {
|
||||
showMultiAdd.value = true
|
||||
};
|
||||
for (let i = 0; i < itemsPerRow; i++) {
|
||||
if (i < rowItems.length) {
|
||||
const item = rowItems[i];
|
||||
|
||||
const exportData = async () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
const ids = []
|
||||
if (props.selection && props.selection.length) {
|
||||
props.selection.forEach(d => {
|
||||
ids.push(d.id)
|
||||
})
|
||||
// 将DataURL转换为Buffer
|
||||
const base64Data = item.dataUrl.split(',')[1];
|
||||
const binaryData = atob(base64Data);
|
||||
const bytes = new Uint8Array(binaryData.length);
|
||||
for (let j = 0; j < binaryData.length; j++) {
|
||||
bytes[j] = binaryData.charCodeAt(j);
|
||||
}
|
||||
|
||||
qrCodeCells.push(
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
children: [
|
||||
// @ts-ignore
|
||||
new ImageRun({
|
||||
data: bytes,
|
||||
transformation: {
|
||||
width: 150,
|
||||
height: 150
|
||||
}
|
||||
})
|
||||
],
|
||||
alignment: AlignmentType.CENTER
|
||||
})
|
||||
],
|
||||
width: {size: 33, type: WidthType.PERCENTAGE}
|
||||
})
|
||||
);
|
||||
|
||||
infoCells.push(
|
||||
new TableCell({
|
||||
children: [
|
||||
new Paragraph({
|
||||
text: `${item.giftInfo.code || '未设置'}`,
|
||||
alignment: AlignmentType.CENTER
|
||||
}),
|
||||
new Paragraph({
|
||||
text: `${item.giftInfo.name || ''}`,
|
||||
alignment: AlignmentType.CENTER
|
||||
})
|
||||
],
|
||||
width: {size: 33, type: WidthType.PERCENTAGE}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// 空单元格
|
||||
qrCodeCells.push(
|
||||
new TableCell({
|
||||
children: [new Paragraph({text: ''})],
|
||||
width: {size: 33, type: WidthType.PERCENTAGE}
|
||||
})
|
||||
);
|
||||
infoCells.push(
|
||||
new TableCell({
|
||||
children: [new Paragraph({text: ''})],
|
||||
width: {size: 33, type: WidthType.PERCENTAGE}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
const res = await exportShopGift(ids);
|
||||
window.open(res.url);
|
||||
hide();
|
||||
|
||||
// 添加表格
|
||||
children.push(
|
||||
new Table({
|
||||
rows: [
|
||||
new TableRow({
|
||||
children: qrCodeCells
|
||||
}),
|
||||
new TableRow({
|
||||
children: infoCells
|
||||
})
|
||||
],
|
||||
width: {size: 100, type: WidthType.PERCENTAGE}
|
||||
})
|
||||
);
|
||||
|
||||
// 添加行间距
|
||||
children.push(
|
||||
new Paragraph({
|
||||
text: '',
|
||||
spacing: {after: 400}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
// 创建文档
|
||||
const doc = new Document({
|
||||
sections: [
|
||||
{
|
||||
properties: {
|
||||
page: {
|
||||
size: {
|
||||
orientation: 'portrait',
|
||||
width: 11906, // A4宽度 (210mm)
|
||||
height: 16838 // A4高度 (297mm)
|
||||
},
|
||||
margin: {
|
||||
top: 1134, // 2cm
|
||||
right: 1134, // 2cm
|
||||
bottom: 1134, // 2cm
|
||||
left: 1134 // 2cm
|
||||
}
|
||||
}
|
||||
},
|
||||
children
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
// 生成并下载文档
|
||||
try {
|
||||
const buffer = await Packer.toBlob(doc);
|
||||
const fileName = `礼品卡二维码清单_${new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10)}.docx`;
|
||||
saveAs(buffer, fileName);
|
||||
} catch (error) {
|
||||
console.error('文档生成失败:', error);
|
||||
// 如果Packer.toBlob失败,尝试使用toBuffer
|
||||
const buffer = await Packer.toBuffer(doc);
|
||||
const blob = new Blob([buffer], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
});
|
||||
const fileName = `礼品卡二维码清单_${new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10)}.docx`;
|
||||
saveAs(blob, fileName);
|
||||
}
|
||||
};
|
||||
|
||||
// 创建HTML文档(备用方案)
|
||||
const createHtmlDocument = (
|
||||
qrCodeImages: { dataUrl: string; giftInfo: ShopGift }[]
|
||||
) => {
|
||||
const itemsPerRow = 3;
|
||||
let htmlContent = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>礼品卡二维码清单</title>
|
||||
<style>
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 2cm;
|
||||
}
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.title {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.qr-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${itemsPerRow}, 1fr);
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.qr-item {
|
||||
text-align: center;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.qr-code {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
.qr-info {
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@media print {
|
||||
.no-print { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="title">礼品卡二维码清单</div>
|
||||
<div class="qr-grid">
|
||||
`;
|
||||
|
||||
qrCodeImages.forEach((item) => {
|
||||
htmlContent += `
|
||||
<div class="qr-item">
|
||||
<img src="${item.dataUrl}" alt="QR Code" class="qr-code">
|
||||
<div class="qr-info">
|
||||
<div>礼品卡编号: ${item.giftInfo.code || '未设置'}</div>
|
||||
<div>礼品卡名称: ${item.giftInfo.name || ''}</div>
|
||||
<div>ID: ${item.giftInfo.id}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
htmlContent += `
|
||||
</div>
|
||||
<div class="no-print" style="text-align: center; margin-top: 30px;">
|
||||
<button onclick="window.print()" style="padding: 10px 20px; font-size: 16px;">打印文档</button>
|
||||
<button onclick="window.close()" style="padding: 10px 20px; font-size: 16px; margin-left: 10px;">关闭</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
// 在新窗口中打开HTML文档
|
||||
const newWindow = window.open('', '_blank');
|
||||
if (newWindow) {
|
||||
newWindow.document.write(htmlContent);
|
||||
newWindow.document.close();
|
||||
} else {
|
||||
// 如果弹窗被阻止,创建下载链接
|
||||
const blob = new Blob([htmlContent], {type: 'text/html;charset=utf-8'});
|
||||
const fileName = `礼品卡二维码清单_${new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10)}.html`;
|
||||
saveAs(blob, fileName);
|
||||
}
|
||||
};
|
||||
|
||||
// 使用原生 window.print() 的打印功能
|
||||
const handlePrint = async () => {
|
||||
try {
|
||||
message.loading('正在准备打印数据...', 0);
|
||||
|
||||
// 获取打印数据
|
||||
let printData: ShopGift[] = [];
|
||||
|
||||
if (props.selection && props.selection.length > 0) {
|
||||
printData = props.selection;
|
||||
} else {
|
||||
printData = await listShopGift();
|
||||
}
|
||||
|
||||
if (!printData || printData.length === 0) {
|
||||
message.destroy();
|
||||
message.warning('没有数据可以打印');
|
||||
return;
|
||||
}
|
||||
|
||||
message.destroy();
|
||||
|
||||
// 创建打印窗口
|
||||
const printWindow = window.open('', '_blank');
|
||||
if (!printWindow) {
|
||||
message.error('无法打开打印窗口,请检查浏览器弹窗设置');
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成完整的HTML文档
|
||||
const printHtml = createPrintHtml(printData);
|
||||
|
||||
// 写入HTML内容
|
||||
printWindow.document.write(printHtml);
|
||||
printWindow.document.close();
|
||||
|
||||
// 等待内容加载完成后打印
|
||||
printWindow.onload = () => {
|
||||
printWindow.print();
|
||||
// 打印完成后关闭窗口
|
||||
printWindow.onafterprint = () => {
|
||||
printWindow.close();
|
||||
};
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
message.destroy();
|
||||
console.error('打印失败:', error);
|
||||
message.error('打印失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 创建完整的打印HTML文档
|
||||
const createPrintHtml = (data: ShopGift[]) => {
|
||||
const getStatusText = (record: ShopGift) => {
|
||||
if (record.userId == 0) return '未领取';
|
||||
if (record.userId > 0 && record.status === 0) return '已领取';
|
||||
if (record.status === 1) return '已使用';
|
||||
if (record.status === 2) return '已失效';
|
||||
return '未知';
|
||||
};
|
||||
|
||||
// 安全地处理数据,避免 undefined 或 null 值
|
||||
const safeValue = (value: any) => {
|
||||
if (value === null || value === undefined) return '';
|
||||
return String(value).replace(/</g, '<').replace(/>/g, '>');
|
||||
};
|
||||
|
||||
let tableRows = '';
|
||||
data.forEach(record => {
|
||||
tableRows += `
|
||||
<tr>
|
||||
<td>${safeValue(record.id)}</td>
|
||||
<td>${safeValue(record.name)}</td>
|
||||
<td>${safeValue(record.code)}</td>
|
||||
<td>${safeValue(record.goodsName)}</td>
|
||||
<td>${safeValue(getStatusText(record))}</td>
|
||||
<td>${safeValue(record.createTime)}</td>
|
||||
</tr>`;
|
||||
});
|
||||
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>礼品卡清单</title>
|
||||
<style>
|
||||
@page {
|
||||
margin: 15mm;
|
||||
size: A4;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, "Microsoft YaHei", sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #333;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-bottom: 15px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #333;
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">礼品卡清单</div>
|
||||
|
||||
<div class="info">
|
||||
<div>打印时间:${new Date().toLocaleString()}</div>
|
||||
<div>数据条数:${data.length} 条</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>名称</th>
|
||||
<th>秘钥</th>
|
||||
<th>商品</th>
|
||||
<th>状态</th>
|
||||
<th>创建时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${tableRows}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="footer">
|
||||
<div>共 ${data.length} 条记录</div>
|
||||
</div>
|
||||
|
||||
<div class="no-print" style="text-align: center; margin-top: 20px;">
|
||||
<button onclick="window.print()" style="padding: 10px 20px; font-size: 14px;">重新打印</button>
|
||||
<button onclick="window.close()" style="padding: 10px 20px; font-size: 14px; margin-left: 10px;">关闭</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="800"
|
||||
width="65%"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:maxable="maxable"
|
||||
:title="isUpdate ? '编辑礼品卡' : '添加礼品卡'"
|
||||
:title="isUpdate ? '礼品卡详情' : '礼品卡详情'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
@@ -14,247 +14,694 @@
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<a-form-item label="" name="name">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="秘钥" name="code">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入秘钥"
|
||||
v-model:value="form.code"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="商品ID" name="goodsId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入商品ID"
|
||||
v-model:value="form.goodsId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="领取时间" name="takeTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入领取时间"
|
||||
v-model:value="form.takeTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="操作人" name="operatorUserId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入操作人"
|
||||
v-model:value="form.operatorUserId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否展示" name="isShow">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否展示"
|
||||
v-model:value="form.isShow"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0上架 1待上架 2待审核 3审核不通过" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="0">显示</a-radio>
|
||||
<a-radio :value="1">隐藏</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" name="sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="9999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用户ID" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用户ID"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否删除, 0否, 1是" name="deleted">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否删除, 0否, 1是"
|
||||
v-model:value="form.deleted"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="修改时间" name="updateTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入修改时间"
|
||||
v-model:value="form.updateTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 基本信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">基本信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="礼品卡名称" name="name">
|
||||
<a-input
|
||||
placeholder="请输入礼品卡名称"
|
||||
:disabled="isUpdate"
|
||||
v-model:value="form.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="礼品卡密钥" name="code">
|
||||
<a-input
|
||||
placeholder="请输入礼品卡密钥"
|
||||
v-model:value="form.code"
|
||||
:disabled="isUpdate"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-button v-if="!isUpdate" type="link" size="small" @click="generateCode">
|
||||
生成
|
||||
</a-button>
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="关联商品" name="goodsId">
|
||||
<a-select
|
||||
v-model:value="form.goodsId"
|
||||
placeholder="请选择关联商品"
|
||||
show-search
|
||||
:filter-option="false"
|
||||
:loading="goodsLoading"
|
||||
@search="searchGoods"
|
||||
:disabled="isUpdate"
|
||||
@change="onGoodsChange"
|
||||
@dropdown-visible-change="onDropdownVisibleChange"
|
||||
>
|
||||
<a-select-option v-for="goods in goodsList" :key="goods.goodsId" :value="goods.goodsId">
|
||||
<div class="goods-option">
|
||||
<span>{{ goods.name }}</span>
|
||||
<a-tag color="blue" style="margin-left: 8px;">¥{{ goods.price || 0 }}</a-tag>
|
||||
</div>
|
||||
</a-select-option>
|
||||
<a-select-option v-if="goodsList.length === 0" disabled>
|
||||
<div style="text-align: center; color: #999;">
|
||||
{{ goodsLoading ? '加载中...' : '暂无商品数据' }}
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12" v-if="!isUpdate">
|
||||
<a-form-item label="生成数量" name="num">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
:max="1000"
|
||||
placeholder="请输入生成数量"
|
||||
v-model:value="form.num"
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #addonAfter>张</template>
|
||||
</a-input-number>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="使用地址" name="useLocation">
|
||||
<a-input
|
||||
placeholder="请输入使用的门店地址"
|
||||
v-model:value="form.useLocation"
|
||||
:disabled="isUpdate"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="备注信息" name="comments">
|
||||
<a-textarea
|
||||
v-model:value="form.comments"
|
||||
:disabled="isUpdate"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 状态设置 -->
|
||||
<!-- <a-divider orientation="left">-->
|
||||
<!-- <span style="color: #1890ff; font-weight: 600;">状态设置</span>-->
|
||||
<!-- </a-divider>-->
|
||||
|
||||
<!-- <a-row :gutter="16">-->
|
||||
<!-- <a-col :span="8">-->
|
||||
<!-- <a-form-item label="上架状态" name="status">-->
|
||||
<!-- <a-select v-model:value="form.status" placeholder="请选择上架状态">-->
|
||||
<!-- <a-select-option :value="0">-->
|
||||
<!-- <div class="status-option">-->
|
||||
<!-- <a-tag color="success">已上架</a-tag>-->
|
||||
<!-- <span>正常销售</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </a-select-option>-->
|
||||
<!-- <a-select-option :value="1">-->
|
||||
<!-- <div class="status-option">-->
|
||||
<!-- <a-tag color="warning">待上架</a-tag>-->
|
||||
<!-- <span>准备上架</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </a-select-option>-->
|
||||
<!-- <a-select-option :value="2">-->
|
||||
<!-- <div class="status-option">-->
|
||||
<!-- <a-tag color="processing">待审核</a-tag>-->
|
||||
<!-- <span>等待审核</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </a-select-option>-->
|
||||
<!-- <a-select-option :value="3">-->
|
||||
<!-- <div class="status-option">-->
|
||||
<!-- <a-tag color="error">审核不通过</a-tag>-->
|
||||
<!-- <span>审核失败</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- </a-select-option>-->
|
||||
<!-- </a-select>-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- </a-col>-->
|
||||
<!-- <a-col :span="8">-->
|
||||
<!-- <a-form-item label="展示状态" name="isShow">-->
|
||||
<!-- <a-switch-->
|
||||
<!-- v-model:checked="form.isShow"-->
|
||||
<!-- checked-children="展示"-->
|
||||
<!-- un-checked-children="隐藏"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- </a-col>-->
|
||||
<!-- <a-col :span="8">-->
|
||||
<!-- <a-form-item label="排序" name="sortNumber">-->
|
||||
<!-- <a-input-number-->
|
||||
<!-- :min="0"-->
|
||||
<!-- placeholder="数字越小越靠前"-->
|
||||
<!-- v-model:value="form.sortNumber"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<!-- </a-col>-->
|
||||
<!-- </a-row>-->
|
||||
|
||||
<!-- 使用信息 -->
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">使用信息</span>
|
||||
</a-divider>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="领取时间" name="takeTime">
|
||||
<a-date-picker
|
||||
v-model:value="form.takeTime"
|
||||
placeholder="请选择领取时间"
|
||||
:disabled="isUpdate"
|
||||
show-time
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="领取用户ID" name="userId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入领取用户ID"
|
||||
v-model:value="form.userId"
|
||||
:disabled="isUpdate"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="操作人ID" name="operatorUserId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
placeholder="请输入操作人用户ID"
|
||||
v-model:value="form.operatorUserId"
|
||||
:disabled="isUpdate"
|
||||
style="width: 300px"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="操作员备注" name="userId">
|
||||
<a-textarea
|
||||
v-model:value="form.operatorRemarks"
|
||||
:disabled="isUpdate"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 礼品卡预览 -->
|
||||
<div class="gift-card-preview" v-if="form.name">
|
||||
<a-divider orientation="left">
|
||||
<span style="color: #1890ff; font-weight: 600;">礼品卡预览</span>
|
||||
</a-divider>
|
||||
<div class="gift-card">
|
||||
<div class="gift-card-header">
|
||||
<div class="gift-card-title">{{ form.name }}</div>
|
||||
<div class="gift-card-status">
|
||||
<a-tag>
|
||||
<span v-if="form.takeTime">领取时间:{{ formatTime(form.takeTime) }}</span>
|
||||
<span v-else>未领取</span>
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gift-card-body">
|
||||
<div class="gift-card-code">
|
||||
<span class="code-label text-gray-50">卡密:</span>
|
||||
<span class="code-value">{{ form.code || '未设置' }}</span>
|
||||
</div>
|
||||
<div class="gift-card-goods" v-if="selectedGoods">
|
||||
<span class="goods-label text-gray-50">关联商品:</span>
|
||||
<span class="goods-name">{{ selectedGoods.name }}</span>
|
||||
<a-tag color="blue" style="margin-left: 8px;">¥{{ selectedGoods.price }}</a-tag>
|
||||
</div>
|
||||
<div class="gift-card-goods py-2" v-if="selectedGoods">
|
||||
<span class="goods-label">使用地址:</span>
|
||||
<span class="goods-name">{{ form.useLocation }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gift-card-footer">
|
||||
<div class="gift-card-info text-gray-50">
|
||||
备注: {{ form.comments }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject, uuid } from 'ele-admin-pro';
|
||||
import { addShopGift, updateShopGift } from '@/api/shop/shopGift';
|
||||
import { ShopGift } from '@/api/shop/shopGift/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import {ref, reactive, watch} from 'vue';
|
||||
import {Form, message} from 'ant-design-vue';
|
||||
import {assignObject} from 'ele-admin-pro';
|
||||
import {addShopGift, updateShopGift} from '@/api/shop/shopGift';
|
||||
import {ShopGift} from '@/api/shop/shopGift/model';
|
||||
import {FormInstance} from 'ant-design-vue/es/form';
|
||||
import {listShopGoods} from '@/api/shop/shopGoods';
|
||||
import {ShopGoods} from '@/api/shop/shopGoods/model';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopGift | null;
|
||||
}>();
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: ShopGift | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<ShopGift>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
goodsId: undefined,
|
||||
takeTime: undefined,
|
||||
operatorUserId: undefined,
|
||||
isShow: undefined,
|
||||
status: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
userId: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopGiftId: undefined,
|
||||
shopGiftName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
// 表单数据
|
||||
const form = reactive<ShopGift>({
|
||||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
goodsId: undefined,
|
||||
takeTime: undefined,
|
||||
operatorUserId: undefined,
|
||||
operatorUserName: undefined,
|
||||
operatorRemarks: undefined,
|
||||
isShow: true,
|
||||
status: 0,
|
||||
useLocation: '',
|
||||
comments: '',
|
||||
sortNumber: 100,
|
||||
userId: undefined,
|
||||
deleted: 0,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
num: 1000
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
// 商品列表
|
||||
const goodsList = ref<ShopGoods[]>([]);
|
||||
// 商品加载状态
|
||||
const goodsLoading = ref(false);
|
||||
// 选中的商品
|
||||
const selectedGoods = ref<ShopGoods | null>(null);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopGiftName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写礼品卡名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateShopGift : addShopGift;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入礼品卡名称',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
{
|
||||
min: 2,
|
||||
max: 50,
|
||||
message: '礼品卡名称长度应在2-50个字符之间',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入礼品卡密钥',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
min: 6,
|
||||
max: 32,
|
||||
message: '密钥长度应在6-32个字符之间',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
goodsId: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择关联商品',
|
||||
trigger: 'change'
|
||||
}
|
||||
],
|
||||
num: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入生成数量',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
validator: (rule: any, value: any) => {
|
||||
if (value && (value < 1 || value > 1000)) {
|
||||
return Promise.reject('生成数量必须在1-1000之间');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
status: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择上架状态',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 生成密钥 */
|
||||
const generateCode = () => {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
form.code = result;
|
||||
};
|
||||
|
||||
/* 搜索商品 */
|
||||
const searchGoods = async (value: string) => {
|
||||
if (value && value.trim()) {
|
||||
goodsLoading.value = true;
|
||||
try {
|
||||
const res = await listShopGoods({keywords: value.trim()});
|
||||
goodsList.value = res || [];
|
||||
console.log('搜索到的商品:', goodsList.value);
|
||||
} catch (e) {
|
||||
console.error('搜索商品失败:', e);
|
||||
goodsList.value = [];
|
||||
} finally {
|
||||
goodsLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* 下拉框显示状态改变 */
|
||||
const onDropdownVisibleChange = (open: boolean) => {
|
||||
if (open && goodsList.value.length === 0) {
|
||||
// 当下拉框打开且没有数据时,加载默认商品列表
|
||||
getGoodsList();
|
||||
}
|
||||
};
|
||||
|
||||
/* 商品选择改变 */
|
||||
const onGoodsChange = (goodsId: number) => {
|
||||
selectedGoods.value = goodsList.value.find(goods => goods.goodsId === goodsId) || null;
|
||||
console.log('选中的商品:', selectedGoods.value);
|
||||
};
|
||||
|
||||
/* 获取状态颜色 */
|
||||
const getStatusColor = () => {
|
||||
const colorMap = {
|
||||
0: 'success',
|
||||
1: 'warning',
|
||||
2: 'processing',
|
||||
3: 'error'
|
||||
};
|
||||
return colorMap[form.status] || 'default';
|
||||
};
|
||||
|
||||
/* 获取状态文本 */
|
||||
const getStatusText = () => {
|
||||
const textMap = {
|
||||
0: '已上架',
|
||||
1: '待上架',
|
||||
2: '待审核',
|
||||
3: '审核不通过'
|
||||
};
|
||||
return textMap[form.status] || '未知状态';
|
||||
};
|
||||
|
||||
/* 格式化时间 */
|
||||
const formatTime = (time: any) => {
|
||||
if (!time) return '';
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
|
||||
const {resetFields} = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
|
||||
// 处理时间字段转换
|
||||
if (formData.takeTime && dayjs.isDayjs(formData.takeTime)) {
|
||||
formData.takeTime = formData.takeTime.format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
|
||||
// 处理数据类型转换
|
||||
if (formData.isShow !== undefined) {
|
||||
formData.isShow = formData.isShow === '1' || formData.isShow === true;
|
||||
}
|
||||
|
||||
console.log('提交的礼品卡数据:', formData);
|
||||
|
||||
const saveOrUpdate = isUpdate.value ? updateShopGift : addShopGift;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
console.error('保存失败:', e);
|
||||
});
|
||||
})
|
||||
.catch((errors) => {
|
||||
console.error('表单验证失败:', errors);
|
||||
});
|
||||
};
|
||||
|
||||
/* 获取商品列表 */
|
||||
const getGoodsList = async () => {
|
||||
if (goodsLoading.value) return; // 防止重复加载
|
||||
|
||||
goodsLoading.value = true;
|
||||
try {
|
||||
const res = await listShopGoods({pageSize: 50}); // 限制返回数量
|
||||
goodsList.value = res || [];
|
||||
console.log('获取到的商品列表:', goodsList.value);
|
||||
} catch (e) {
|
||||
console.error('获取商品列表失败:', e);
|
||||
goodsList.value = [];
|
||||
} finally {
|
||||
goodsLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (visible) => {
|
||||
if (visible) {
|
||||
await getGoodsList();
|
||||
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
|
||||
// 处理时间字段转换
|
||||
if (props.data.takeTime) {
|
||||
form.takeTime = dayjs(props.data.takeTime);
|
||||
}
|
||||
|
||||
// 设置选中的商品
|
||||
if (props.data.goodsId) {
|
||||
selectedGoods.value = goodsList.value.find(goods => goods.goodsId === props.data.goodsId) || null;
|
||||
}
|
||||
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
// 重置为默认值
|
||||
Object.assign(form, {
|
||||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
goodsId: undefined,
|
||||
takeTime: undefined,
|
||||
operatorUserId: undefined,
|
||||
isShow: true,
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100,
|
||||
userId: undefined,
|
||||
deleted: 0,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
num: 1000
|
||||
});
|
||||
|
||||
selectedGoods.value = null;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.goods-option,
|
||||
.status-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.ant-tag {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-preview {
|
||||
margin-top: 24px;
|
||||
|
||||
.gift-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
right: -50px;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.gift-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.gift-card-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-body {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.gift-card-code {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.code-label {
|
||||
font-weight: 600;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
|
||||
.code-value {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-goods {
|
||||
.goods-label {
|
||||
font-weight: 600;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
margin-left: 8px;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-footer {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.gift-card-info {
|
||||
font-size: 12px;
|
||||
color: #f3f3f3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-divider-horizontal.ant-divider-with-text-left) {
|
||||
margin: 24px 0 16px 0;
|
||||
|
||||
.ant-divider-inner-text {
|
||||
padding: 0 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-form-item) {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
:deep(.ant-select-selection-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.ant-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,12 +22,26 @@
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'goodsId'">
|
||||
<div>{{ record.goodsName }}</div>
|
||||
<div class="text-gray-300" v-if="record.nickName">领取人:{{ record.userId }}</div>
|
||||
<div class="text-gray-300" v-if="record.operatorUserId">核销人:{{ record.operatorUserId }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image :src="record.image" :width="50" />
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
|
||||
<a-space>
|
||||
<a-tag v-if="record.userId == 0">未领取</a-tag>
|
||||
<a-tag v-if="record.userId > 0 && record.status === 0" color="green">已领取</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="green">已使用</a-tag>
|
||||
<a-tag v-if="record.status === 2" color="red">已失效</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'createTime'">
|
||||
<div v-if="record.createTime">创建时间:{{ record.createTime }}</div>
|
||||
<div v-if="record.takeTime" class="text-green-500">领取时间:{{ record.takeTime }}</div>
|
||||
<div v-if="record.verificationTime" class="text-purple-500">核销时间:{{ record.verificationTime }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
@@ -55,7 +69,6 @@
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
@@ -127,19 +140,18 @@
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '领取时间',
|
||||
dataIndex: 'takeTime',
|
||||
key: 'takeTime',
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 170
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd')
|
||||
sorter: true
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@@ -228,6 +240,7 @@
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -77,9 +77,17 @@
|
||||
/>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="团长价" name="originPrice">
|
||||
<a-form-item label="市场价" name="salePrice">
|
||||
<a-input-number
|
||||
:placeholder="`团长价`"
|
||||
:placeholder="`市场价`"
|
||||
style="width: 240px"
|
||||
:min="0.01"
|
||||
v-model:value="form.salePrice"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="会员价" name="originPrice">
|
||||
<a-input-number
|
||||
:placeholder="`会员价`"
|
||||
style="width: 240px"
|
||||
:min="0.01"
|
||||
v-model:value="form.dealerPrice"
|
||||
|
||||
@@ -198,7 +198,7 @@ const columns = ref<ColumnItem[]>([
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
width: 180,
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd')
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
}
|
||||
// {
|
||||
// title: '操作',
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
@@ -117,15 +116,11 @@
|
||||
id: undefined,
|
||||
goodsId: undefined,
|
||||
issueCouponId: undefined,
|
||||
sortNumber: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
userId: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopGoodsCouponId: undefined,
|
||||
shopGoodsCouponName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
@@ -148,20 +143,6 @@
|
||||
]
|
||||
});
|
||||
|
||||
const chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
};
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
@@ -199,13 +180,6 @@
|
||||
images.value = [];
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
if(props.data.image){
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: props.data.image,
|
||||
status: 'done'
|
||||
})
|
||||
}
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
|
||||
@@ -182,12 +182,7 @@
|
||||
status: undefined,
|
||||
comments: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
shopGoodsSkuId: undefined,
|
||||
shopGoodsSkuName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
createTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
|
||||
@@ -875,8 +875,8 @@ const isRefundStatus = (orderStatus?: number) => {
|
||||
|
||||
// 判断是否可以删除订单
|
||||
const canDeleteOrder = (order: ShopOrder) => {
|
||||
// 已完成、已取消、退款成功的订单可以删除
|
||||
return [1, 2, 6].includes(order.orderStatus || 0);
|
||||
// 已完成、已取消、退款成功的订单可以删除 (原来是[1, 2, 6],后面改成只有取消的订单能删除)
|
||||
return [2].includes(order.orderStatus || 0);
|
||||
};
|
||||
|
||||
// 判断是否可以申请退款
|
||||
|
||||
@@ -13,6 +13,14 @@
|
||||
<!-- </template>-->
|
||||
<!-- <span>批量删除</span>-->
|
||||
<!-- </a-button>-->
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.orderNo"
|
||||
placeholder="订单编号"
|
||||
style="width: 240px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-select
|
||||
v-model:value="where.type"
|
||||
style="width: 150px"
|
||||
@@ -64,11 +72,30 @@
|
||||
/>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="请输入关键词"
|
||||
style="width: 280px"
|
||||
:placeholder="getSearchPlaceholder()"
|
||||
style="width: 320px"
|
||||
v-model:value="where.keywords"
|
||||
@search="reload"
|
||||
/>
|
||||
>
|
||||
<template #addonBefore>
|
||||
<a-select
|
||||
v-model:value="type"
|
||||
style="width: 88px;"
|
||||
@change="onType"
|
||||
>
|
||||
<a-select-option value="">不限</a-select-option>
|
||||
<a-select-option value="userId">
|
||||
用户ID
|
||||
</a-select-option>
|
||||
<a-select-option value="phone">
|
||||
手机号
|
||||
</a-select-option>
|
||||
<a-select-option value="nickname">
|
||||
昵称
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
</a-input-search>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
<a-button @click="handleExport">导出</a-button>
|
||||
</a-space>
|
||||
@@ -106,6 +133,8 @@
|
||||
createTimeStart: undefined,
|
||||
createTimeEnd: undefined,
|
||||
userId: undefined,
|
||||
payUserId: undefined,
|
||||
nickname: undefined,
|
||||
phone: undefined,
|
||||
payStatus: undefined,
|
||||
orderStatus: undefined,
|
||||
@@ -113,14 +142,35 @@
|
||||
});
|
||||
|
||||
const reload = () => {
|
||||
emit('search', where);
|
||||
emit('search', {...where, keywords: type.value == '' ? where.keywords : undefined});
|
||||
};
|
||||
|
||||
// 批量删除
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
// const removeBatch = () => {
|
||||
// emit('remove');
|
||||
// };
|
||||
|
||||
const onType = () => {
|
||||
resetFields();
|
||||
};
|
||||
|
||||
// 获取搜索框placeholder
|
||||
const getSearchPlaceholder = () => {
|
||||
switch (type.value){
|
||||
case 'userId':
|
||||
where.userId = Number(where.keywords);
|
||||
return '请输入用户ID';
|
||||
case 'phone':
|
||||
where.phone = where.keywords;
|
||||
return '请输入手机号';
|
||||
case 'nickname':
|
||||
where.nickname = where.keywords;
|
||||
return '请输入用户昵称';
|
||||
default:
|
||||
return '请输入搜索内容';
|
||||
}
|
||||
}
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
const [d1, d2] = dateRange.value ?? [];
|
||||
@@ -143,6 +193,7 @@
|
||||
const loading = ref(false);
|
||||
const orders = ref<ShopOrder[]>([])
|
||||
const xlsFileName = ref<string>();
|
||||
const type = ref('');
|
||||
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<a-tab-pane key="undelivered" tab="待发货"/>
|
||||
<a-tab-pane key="unreceived" tab="待收货"/>
|
||||
<a-tab-pane key="completed" tab="已完成"/>
|
||||
<a-tab-pane key="refunded" tab="已退款"/>
|
||||
<a-tab-pane key="cancelled" tab="已取消"/>
|
||||
<a-tab-pane key="refunded" tab="退货/售后"/>
|
||||
<a-tab-pane key="cancelled" tab="已关闭"/>
|
||||
</a-tabs>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
@@ -83,8 +83,8 @@
|
||||
<template v-if="column.key === 'orderStatus'">
|
||||
<a-tag v-if="record.orderStatus === 0">未完成</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 1" color="green">已完成</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 2" color="red">已取消</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 3" color="red">取消中</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 2">已关闭</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 3" color="red">关闭中</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 4" color="red">退款申请中</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 5" color="red">退款被拒绝</a-tag>
|
||||
<a-tag v-if="record.orderStatus === 6" color="orange">退款成功</a-tag>
|
||||
@@ -113,11 +113,11 @@
|
||||
</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a
|
||||
@click.stop="openEdit(record)"
|
||||
@click.stop="handleCancelOrder(record)"
|
||||
>
|
||||
<a class="ele-text-warning">
|
||||
<CloseOutlined /> 取消
|
||||
</a>
|
||||
<span class="ele-text-warning">
|
||||
<CloseOutlined /> 关闭
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
@@ -174,14 +174,14 @@
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<!-- 删除操作 - 已完成、已取消、退款成功的订单可以删除 -->
|
||||
<!-- 删除操作 - 已完成、已关闭、退款成功的订单可以删除 -->
|
||||
<template v-if="canDeleteOrder(record)">
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm
|
||||
title="确定要删除此订单吗?删除后无法恢复。"
|
||||
@confirm.stop="remove(record)"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">
|
||||
<a class="ele-text-danger" @click.stop>
|
||||
<DeleteOutlined /> 删除
|
||||
</a>
|
||||
</a-popconfirm>
|
||||
@@ -258,6 +258,7 @@ const datasource: DatasourceFunction = ({
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
where.type = 0;
|
||||
return pageShopOrder({
|
||||
...where,
|
||||
...orders,
|
||||
@@ -316,7 +317,7 @@ const columns = ref<ColumnItem[]>([
|
||||
title: '订单状态',
|
||||
dataIndex: 'orderStatus',
|
||||
key: 'orderStatus',
|
||||
align: 'center',
|
||||
align: 'center'
|
||||
},
|
||||
// {
|
||||
// title: '备注',
|
||||
@@ -395,11 +396,11 @@ const onTabs = () => {
|
||||
filterParams.statusFilter = 5;
|
||||
break;
|
||||
case 'cancelled':
|
||||
// 已取消:order_status = 2
|
||||
// 已关闭:order_status = 2
|
||||
filterParams.statusFilter = 8;
|
||||
break;
|
||||
case 'refunded':
|
||||
// 已退款:order_status = 6
|
||||
// 退款/售后:order_status = 6
|
||||
filterParams.statusFilter = 6;
|
||||
break;
|
||||
case 'deleted':
|
||||
@@ -452,7 +453,7 @@ const query = () => {
|
||||
};
|
||||
|
||||
/* 辅助判断函数 */
|
||||
// 判断是否为取消状态
|
||||
// 判断是否为关闭状态
|
||||
const isCancelledStatus = (orderStatus?: number) => {
|
||||
return [2, 3].includes(orderStatus || 0);
|
||||
};
|
||||
@@ -464,8 +465,8 @@ const isRefundStatus = (orderStatus?: number) => {
|
||||
|
||||
// 判断是否可以删除订单
|
||||
const canDeleteOrder = (order: ShopOrder) => {
|
||||
// 已完成、已取消、退款成功的订单可以删除
|
||||
return [1, 2, 6].includes(order.orderStatus || 0);
|
||||
// 已完成、已关闭、退款成功的订单可以删除 (原来是[1, 2, 6],后面改成只有关闭的订单能删除)
|
||||
return [2].includes(order.orderStatus || 0);
|
||||
};
|
||||
|
||||
/* 订单操作方法 */
|
||||
@@ -475,21 +476,21 @@ const handleEditOrder = (record: ShopOrder) => {
|
||||
// TODO: 实现订单修改功能
|
||||
};
|
||||
|
||||
// 取消订单
|
||||
// 关闭订单
|
||||
const handleCancelOrder = (record: ShopOrder) => {
|
||||
Modal.confirm({
|
||||
title: '确认取消订单',
|
||||
content: '确定要取消此订单吗?取消后无法恢复。',
|
||||
title: '确认关闭订单',
|
||||
content: '确定要关闭此订单吗?关闭后无法恢复。',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await updateShopOrder({
|
||||
...record,
|
||||
orderStatus: 2 // 已取消
|
||||
orderStatus: 2 // 已关闭
|
||||
});
|
||||
message.success('订单已取消');
|
||||
message.success('订单已关闭');
|
||||
reload();
|
||||
} catch (error: any) {
|
||||
message.error(error.message || '取消订单失败');
|
||||
message.error(error.message || '关闭订单失败');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,396 +0,0 @@
|
||||
import {Avatar, Cell, Space, Tabs, Button, TabPane, Image} from '@nutui/nutui-react-taro'
|
||||
import {useEffect, useState, CSSProperties} from "react";
|
||||
import {View} from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro';
|
||||
import {InfiniteLoading} from '@nutui/nutui-react-taro'
|
||||
import dayjs from "dayjs";
|
||||
import {pageShopOrder, removeShopOrder, updateShopOrder} from "@/api/shop/shopOrder";
|
||||
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
||||
import {listShopOrderGoods} from "@/api/shop/shopOrderGoods";
|
||||
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||
import {copyText} from "@/utils/common";
|
||||
|
||||
const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
|
||||
marginTop: showSearch ? '65px' : '44px', // 如果显示搜索框,增加更多的上边距
|
||||
height: showSearch ? '75vh' : '82vh', // 相应调整高度
|
||||
width: '100%',
|
||||
padding: '0',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
|
||||
})
|
||||
|
||||
// 统一的订单状态标签配置,与后端 statusFilter 保持一致
|
||||
const tabs = [
|
||||
{
|
||||
index: 0,
|
||||
key: '全部',
|
||||
title: '全部',
|
||||
description: '所有订单',
|
||||
statusFilter: undefined // 不传statusFilter,显示所有订单
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
key: '待付款',
|
||||
title: '待付款',
|
||||
description: '等待付款的订单',
|
||||
statusFilter: 0 // 对应后端:pay_status = false
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
key: '待发货',
|
||||
title: '待发货',
|
||||
description: '已付款待发货的订单',
|
||||
statusFilter: 1 // 对应后端:pay_status = true AND delivery_status = 10
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
key: '待收货',
|
||||
title: '待收货',
|
||||
description: '已发货待收货的订单',
|
||||
statusFilter: 3 // 对应后端:pay_status = true AND delivery_status = 20
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
key: '已完成',
|
||||
title: '已完成',
|
||||
description: '已完成的订单',
|
||||
statusFilter: 5 // 对应后端:order_status = 1
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
key: '已取消',
|
||||
title: '已取消',
|
||||
description: '已取消/退款的订单',
|
||||
statusFilter: 6 // 对应后端:order_status = 6 (已退款)
|
||||
}
|
||||
]
|
||||
|
||||
// 扩展订单接口,包含商品信息
|
||||
interface OrderWithGoods extends ShopOrder {
|
||||
orderGoods?: ShopOrderGoods[];
|
||||
}
|
||||
|
||||
interface OrderListProps {
|
||||
data: ShopOrder[];
|
||||
onReload?: () => void;
|
||||
searchParams?: ShopOrderParam;
|
||||
showSearch?: boolean;
|
||||
}
|
||||
|
||||
function OrderList(props: OrderListProps) {
|
||||
const [list, setList] = useState<OrderWithGoods[]>([])
|
||||
const [page, setPage] = useState(1)
|
||||
const [hasMore, setHasMore] = useState(true)
|
||||
const [tapIndex, setTapIndex] = useState<string | number>(0)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
// 获取订单状态文本
|
||||
const getOrderStatusText = (order: ShopOrder) => {
|
||||
console.log(order,'order')
|
||||
|
||||
// 优先检查订单状态
|
||||
if (order.orderStatus === 2) return '已取消';
|
||||
if (order.orderStatus === 4) return '退款申请中';
|
||||
if (order.orderStatus === 5) return '退款被拒绝';
|
||||
if (order.orderStatus === 6) return '退款成功';
|
||||
if (order.orderStatus === 7) return '客户端申请退款';
|
||||
|
||||
// 检查支付状态 (payStatus为boolean类型,false/0表示未付款,true/1表示已付款)
|
||||
if (!order.payStatus) return '等待买家付款';
|
||||
|
||||
// 已付款后检查发货状态
|
||||
if (order.deliveryStatus === 10) return '待发货';
|
||||
if (order.deliveryStatus === 20) return '待收货';
|
||||
if (order.deliveryStatus === 30) return '已收货';
|
||||
|
||||
// 最后检查订单完成状态
|
||||
if (order.orderStatus === 1) return '已完成';
|
||||
if (order.orderStatus === 0) return '未使用';
|
||||
|
||||
return '未知状态';
|
||||
};
|
||||
|
||||
// 获取订单状态颜色
|
||||
const getOrderStatusColor = (order: ShopOrder) => {
|
||||
// 优先检查订单状态
|
||||
if (order.orderStatus === 2) return 'text-gray-500'; // 已取消
|
||||
if (order.orderStatus === 4) return 'text-orange-500'; // 退款申请中
|
||||
if (order.orderStatus === 5) return 'text-red-500'; // 退款被拒绝
|
||||
if (order.orderStatus === 6) return 'text-green-500'; // 退款成功
|
||||
if (order.orderStatus === 7) return 'text-orange-500'; // 客户端申请退款
|
||||
|
||||
// 检查支付状态
|
||||
if (!order.payStatus) return 'text-orange-500'; // 等待买家付款
|
||||
|
||||
// 已付款后检查发货状态
|
||||
if (order.deliveryStatus === 10) return 'text-blue-500'; // 待发货
|
||||
if (order.deliveryStatus === 20) return 'text-purple-500'; // 待收货
|
||||
if (order.deliveryStatus === 30) return 'text-green-500'; // 已收货
|
||||
|
||||
// 最后检查订单完成状态
|
||||
if (order.orderStatus === 1) return 'text-green-600'; // 已完成
|
||||
if (order.orderStatus === 0) return 'text-gray-500'; // 未使用
|
||||
|
||||
return 'text-gray-600'; // 默认颜色
|
||||
};
|
||||
|
||||
// 使用后端统一的 statusFilter 进行筛选
|
||||
const getOrderStatusParams = (index: string | number) => {
|
||||
let params: ShopOrderParam = {};
|
||||
// 添加用户ID过滤
|
||||
params.userId = Taro.getStorageSync('UserId');
|
||||
|
||||
// 获取当前tab的statusFilter配置
|
||||
const currentTab = tabs.find(tab => tab.index === Number(index));
|
||||
if (currentTab && currentTab.statusFilter !== undefined) {
|
||||
params.statusFilter = currentTab.statusFilter;
|
||||
}
|
||||
|
||||
console.log(`Tab ${index} (${currentTab?.title}) 筛选参数:`, params);
|
||||
return params;
|
||||
};
|
||||
|
||||
const reload = async (resetPage = false) => {
|
||||
setLoading(true);
|
||||
const currentPage = resetPage ? 1 : page;
|
||||
const statusParams = getOrderStatusParams(tapIndex);
|
||||
const searchConditions = {
|
||||
page: currentPage,
|
||||
...statusParams,
|
||||
...props.searchParams
|
||||
};
|
||||
console.log('订单筛选条件:', {
|
||||
tapIndex,
|
||||
statusParams,
|
||||
searchConditions
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await pageShopOrder(searchConditions);
|
||||
let newList: OrderWithGoods[] = [];
|
||||
|
||||
if (res?.list && res?.list.length > 0) {
|
||||
// 为每个订单获取商品信息
|
||||
const ordersWithGoods = await Promise.all(
|
||||
res.list.map(async (order) => {
|
||||
try {
|
||||
const orderGoods = await listShopOrderGoods({ orderId: order.orderId });
|
||||
return {
|
||||
...order,
|
||||
orderGoods: orderGoods || []
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取订单商品失败:', error);
|
||||
return {
|
||||
...order,
|
||||
orderGoods: []
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 合并数据
|
||||
newList = resetPage ? ordersWithGoods : list?.concat(ordersWithGoods);
|
||||
setHasMore(true);
|
||||
} else {
|
||||
newList = [];
|
||||
setHasMore(false);
|
||||
}
|
||||
|
||||
setList(newList || []);
|
||||
setPage(currentPage);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('加载订单失败:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const reloadMore = async () => {
|
||||
setPage(page + 1);
|
||||
reload();
|
||||
};
|
||||
|
||||
// 格式化日期为后端期望的格式
|
||||
const formatDateForBackend = (date: Date) => {
|
||||
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
|
||||
// 确认收货
|
||||
const confirmReceive = async (order: ShopOrder) => {
|
||||
try {
|
||||
await updateShopOrder({
|
||||
...order,
|
||||
deliveryStatus: 30, // 已收货
|
||||
orderStatus: 1 // 已完成
|
||||
});
|
||||
Taro.showToast({
|
||||
title: '确认收货成功',
|
||||
});
|
||||
reload(true); // 重新加载列表
|
||||
props.onReload?.(); // 通知父组件刷新
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '确认收货失败',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 取消订单
|
||||
const cancelOrder = async (order: ShopOrder) => {
|
||||
try {
|
||||
await removeShopOrder(order.orderId);
|
||||
Taro.showToast({
|
||||
title: '订单已删除',
|
||||
});
|
||||
reload(true); // 重新加载列表
|
||||
props.onReload?.(); // 通知父组件刷新
|
||||
} catch (error) {
|
||||
console.error('取消订单失败:', error);
|
||||
Taro.showToast({
|
||||
title: '取消订单失败',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reload(true); // 首次加载或tab切换时重置页码
|
||||
}, [tapIndex]); // 监听tapIndex变化
|
||||
|
||||
useEffect(() => {
|
||||
reload(true); // 搜索参数变化时重置页码
|
||||
}, [props.searchParams]); // 监听搜索参数变化
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
align={'left'}
|
||||
className={'fixed left-0'}
|
||||
style={{
|
||||
top: '44px',
|
||||
zIndex: 998,
|
||||
borderBottom: '1px solid #e5e5e5'
|
||||
}}
|
||||
tabStyle={{
|
||||
backgroundColor: '#ffffff',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
value={tapIndex}
|
||||
onChange={(paneKey) => {
|
||||
console.log('Tab切换到:', paneKey, '对应状态:', tabs[paneKey]?.title);
|
||||
setTapIndex(paneKey)
|
||||
}}
|
||||
>
|
||||
{
|
||||
tabs?.map((item, index) => {
|
||||
return (
|
||||
<TabPane
|
||||
key={index}
|
||||
title={loading && tapIndex === index ? `${item.title}...` : item.title}
|
||||
></TabPane>
|
||||
)
|
||||
})
|
||||
}
|
||||
</Tabs>
|
||||
<div style={getInfiniteUlStyle(props.showSearch)} id="scroll">
|
||||
<InfiniteLoading
|
||||
target="scroll"
|
||||
hasMore={hasMore}
|
||||
onLoadMore={reloadMore}
|
||||
onScroll={() => {
|
||||
|
||||
}}
|
||||
onScrollToUpper={() => {
|
||||
|
||||
}}
|
||||
loadingText={
|
||||
<>
|
||||
加载中
|
||||
</>
|
||||
}
|
||||
loadMoreText={
|
||||
<>
|
||||
没有更多了
|
||||
</>
|
||||
}
|
||||
>
|
||||
{list?.map((item, index) => {
|
||||
return (
|
||||
<Cell key={index} style={{padding: '16px'}} onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
|
||||
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
||||
<View className={'order-no flex justify-between'}>
|
||||
<View className={'text-gray-600 font-bold text-sm'}
|
||||
onClick={(e) => {e.stopPropagation(); copyText(`${item.orderNo}`)}}>{item.orderNo}</View>
|
||||
<View className={`${getOrderStatusColor(item)} font-medium`}>{getOrderStatusText(item)}</View>
|
||||
</View>
|
||||
<div
|
||||
className={'create-time text-gray-400 text-xs'}>{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}</div>
|
||||
|
||||
{/* 商品信息 */}
|
||||
<div className={'goods-info'}>
|
||||
{item.orderGoods && item.orderGoods.length > 0 ? (
|
||||
item.orderGoods.map((goods, goodsIndex) => (
|
||||
<div key={goodsIndex} className={'flex items-center mb-2'}>
|
||||
<Image
|
||||
src={goods.image || '/default-goods.png'}
|
||||
width="50"
|
||||
height="50"
|
||||
lazyLoad={false}
|
||||
className={'rounded'}
|
||||
/>
|
||||
<div className={'ml-2 flex-1'}>
|
||||
<div className={'text-sm font-bold'}>{goods.goodsName}</div>
|
||||
{goods.spec && <div className={'text-gray-500 text-xs'}>规格:{goods.spec}</div>}
|
||||
<div className={'text-gray-500 text-xs'}>数量:{goods.totalNum}</div>
|
||||
</div>
|
||||
<div className={'text-sm'}>¥{goods.price}</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className={'flex items-center'}>
|
||||
<Avatar
|
||||
src='/default-goods.png'
|
||||
size={'50'}
|
||||
shape={'square'}
|
||||
/>
|
||||
<div className={'ml-2'}>
|
||||
<div className={'text-sm'}>{item.title || '订单商品'}</div>
|
||||
<div className={'text-gray-400 text-xs'}>{item.totalNum}件商品</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={'w-full text-right'}>实付金额:¥{item.payPrice}</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<Space className={'btn flex justify-end'}>
|
||||
{/* 待付款状态:显示取消订单和立即支付 */}
|
||||
{(!item.payStatus) && item.orderStatus !== 2 && (
|
||||
<Space>
|
||||
<Button size={'small'} onClick={(e) => {e.stopPropagation(); cancelOrder(item)}}>取消订单</Button>
|
||||
<Button size={'small'} type="primary" onClick={(e) => {e.stopPropagation(); console.log('立即支付')}}>立即支付</Button>
|
||||
</Space>
|
||||
)}
|
||||
{/* 待收货状态:显示确认收货 */}
|
||||
{item.deliveryStatus === 20 && (
|
||||
<Button size={'small'} type="primary" onClick={(e) => {e.stopPropagation(); confirmReceive(item)}}>确认收货</Button>
|
||||
)}
|
||||
{/* 已完成状态:显示申请退款 */}
|
||||
{item.orderStatus === 1 && (
|
||||
<Button size={'small'} onClick={(e) => {e.stopPropagation(); console.log('申请退款')}}>申请退款</Button>
|
||||
)}
|
||||
{/* 退款相关状态的按钮可以在这里添加 */}
|
||||
</Space>
|
||||
</Space>
|
||||
</Cell>
|
||||
)
|
||||
})}
|
||||
</InfiniteLoading>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrderList
|
||||
@@ -263,12 +263,7 @@
|
||||
userId: undefined,
|
||||
tenantId: undefined,
|
||||
updateTime: undefined,
|
||||
createTime: undefined,
|
||||
shopOrderGoodsId: undefined,
|
||||
shopOrderGoodsName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
createTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
|
||||
@@ -132,12 +132,7 @@
|
||||
status: undefined,
|
||||
sortNumber: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
shopSpecId: undefined,
|
||||
shopSpecName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
createTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
|
||||
@@ -99,13 +99,8 @@
|
||||
specId: undefined,
|
||||
specValue: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
shopSpecValueId: undefined,
|
||||
shopSpecValueName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
|
||||
39
src/views/shop/shopUser/components/org-select.vue
Normal file
39
src/views/shop/shopUser/components/org-select.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<!-- 机构选择下拉框 -->
|
||||
<template>
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:placeholder="placeholder"
|
||||
:value="value || undefined"
|
||||
:tree-data="data"
|
||||
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
|
||||
@update:value="updateValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Organization } from '@/api/system/organization/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value?: number): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 选中的数据(v-modal)
|
||||
value?: number;
|
||||
// 提示信息
|
||||
placeholder?: string;
|
||||
// 机构数据
|
||||
data: Organization[];
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择角色'
|
||||
}
|
||||
);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value?: number) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
</script>
|
||||
42
src/views/shop/shopUser/components/search.vue
Normal file
42
src/views/shop/shopUser/components/search.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="add">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import type { GradeParam } from '@/api/user/grade/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: GradeParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
278
src/views/shop/shopUser/components/user-edit.vue
Normal file
278
src/views/shop/shopUser/components/user-edit.vue
Normal file
@@ -0,0 +1,278 @@
|
||||
<!-- 管理员编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="500"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑客户' : '添加客户'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 4, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 17, sm: 20, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="姓名" name="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入真实姓名"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="手机号" name="phone">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="11"
|
||||
:disabled="isUpdate"
|
||||
placeholder="请输入手机号"
|
||||
v-model:value="form.phone"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="邮箱" name="email">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="100"
|
||||
placeholder="请输入邮箱"
|
||||
v-model:value="form.email"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属机构" name="type">
|
||||
<org-select
|
||||
:data="organizationList"
|
||||
placeholder="请选择所属机构"
|
||||
v-model:value="form.organizationId"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { emailReg, phoneReg } from 'ele-admin-pro/es';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { addUser, updateUser, checkExistence } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import OrgSelect from './org-select.vue';
|
||||
import { Organization } from '@/api/system/organization/model';
|
||||
import { Grade } from '@/api/user/grade/model';
|
||||
import {TEMPLATE_ID} from "@/config/setting";
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
// 获取字典数据
|
||||
// const userTypeData = getDictionaryOptions('userType');
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
// 全部机构
|
||||
organizationList: Organization[];
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<User>({
|
||||
type: undefined,
|
||||
userId: undefined,
|
||||
username: '',
|
||||
nickname: '',
|
||||
realName: '',
|
||||
companyName: '',
|
||||
sex: undefined,
|
||||
sexName: undefined,
|
||||
roles: [],
|
||||
email: '',
|
||||
phone: '',
|
||||
mobile: '',
|
||||
password: '',
|
||||
introduction: '',
|
||||
organizationId: undefined,
|
||||
birthday: '',
|
||||
idCard: '',
|
||||
comments: '',
|
||||
gradeName: '',
|
||||
isAdmin: true,
|
||||
gradeId: undefined,
|
||||
templateId: TEMPLATE_ID
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
username: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
validator: (_rule: Rule, value: string) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (!value) {
|
||||
return reject('请输入管理员账号');
|
||||
}
|
||||
checkExistence('username', value, props.data?.userId)
|
||||
.then(() => {
|
||||
reject('账号已经存在');
|
||||
})
|
||||
.catch(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
nickname: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入昵称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
realName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入真实姓名',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
// sex: [
|
||||
// {
|
||||
// required: true,
|
||||
// message: '请选择性别',
|
||||
// type: 'string',
|
||||
// trigger: 'blur'
|
||||
// }
|
||||
// ],
|
||||
roles: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择角色',
|
||||
type: 'array',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
email: [
|
||||
{
|
||||
pattern: emailReg,
|
||||
message: '邮箱格式不正确',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
validator: async (_rule: Rule, value: string) => {
|
||||
if (isUpdate.value || /^[\S]{5,18}$/.test(value)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject('密码必须为5-18位非空白字符');
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
phone: [
|
||||
{
|
||||
required: true,
|
||||
pattern: phoneReg,
|
||||
message: '手机号格式不正确',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseGradeId = (data: Grade) => {
|
||||
form.gradeName = data.name;
|
||||
form.gradeId = data.gradeId;
|
||||
};
|
||||
|
||||
const chooseSex = (data: any) => {
|
||||
form.sex = data.key;
|
||||
form.sexName = data.label;
|
||||
};
|
||||
|
||||
const updateIsAdmin = (value: boolean) => {
|
||||
form.isAdmin = value;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateUser : addUser;
|
||||
form.username = form.phone;
|
||||
form.nickname = form.realName;
|
||||
saveOrUpdate(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignFields({
|
||||
...props.data,
|
||||
password: ''
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
<ele-modal
|
||||
:width="520"
|
||||
:footer="null"
|
||||
title="导入用户"
|
||||
title="用户批量导入"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
@@ -23,10 +23,10 @@
|
||||
<div class="ele-text-center">
|
||||
<span>只能上传xls、xlsx文件,</span>
|
||||
<a
|
||||
href="https://oss.wsdns.cn/20200610/用户导入模板.xlsx"
|
||||
href="https://server.websoft.top/api/system/user/import/template"
|
||||
download="用户导入模板.xlsx"
|
||||
>
|
||||
下载模板
|
||||
下载导入模板
|
||||
</a>
|
||||
</div>
|
||||
</ele-modal>
|
||||
603
src/views/shop/shopUser/index.vue
Normal file
603
src/views/shop/shopUser/index.vue
Normal file
@@ -0,0 +1,603 @@
|
||||
<template>
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<a-card :bordered="false">
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
class="sys-org-table"
|
||||
:scroll="{ x: 1300 }"
|
||||
:where="defaultWhere"
|
||||
:customRow="customRow"
|
||||
cache-key="proSystemUserTable"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="primary" class="ele-btn-icon" @click="openEdit()">
|
||||
<template #icon>
|
||||
<plus-outlined/>
|
||||
</template>
|
||||
<span>添加</span>
|
||||
</a-button>
|
||||
<a-button class="ele-btn-icon" @click="openImport()">
|
||||
<template #icon>
|
||||
<cloud-upload-outlined/>
|
||||
</template>
|
||||
<span>导入</span>
|
||||
</a-button>
|
||||
<a-button class="ele-btn-icon" @click="exportData()" :loading="exportLoading">
|
||||
<template #icon>
|
||||
<download-outlined/>
|
||||
</template>
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入关键词"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'avatar'">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.avatar}`"
|
||||
style="margin-right: 4px"
|
||||
>
|
||||
<template #icon>
|
||||
<UserOutlined/>
|
||||
</template>
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<div>{{ record.nickname }}</div>
|
||||
<div class="text-gray-400">{{ record.realName }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'phone'">
|
||||
<span v-if="hasRole('superAdmin')">{{ record.phone }}</span>
|
||||
<span v-else>{{ record.phone }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'roles'">
|
||||
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
|
||||
{{ item.roleName }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'platform'">
|
||||
<WechatOutlined v-if="record.platform === 'MP-WEIXIN'"/>
|
||||
<Html5Outlined v-if="record.platform === 'H5'"/>
|
||||
<ChromeOutlined v-if="record.platform === 'WEB'"/>
|
||||
</template>
|
||||
<template v-if="column.key === 'balance'">
|
||||
<span class="ele-text-success">
|
||||
¥{{ formatNumber(record.balance) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'expendMoney'">
|
||||
<span class="ele-text-warning">
|
||||
¥{{ formatNumber(record.expendMoney) }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'isAdmin'">
|
||||
<a-switch
|
||||
:checked="record.isAdmin == 1"
|
||||
@change="updateIsAdmin(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<div>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<a-divider type="vertical"/>
|
||||
<a-popconfirm
|
||||
placement="topRight"
|
||||
title="确定要删除此用户吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
<!-- 编辑弹窗 -->
|
||||
<user-edit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:organization-list="data"
|
||||
@done="reload"
|
||||
/>
|
||||
<!-- 导入弹窗 -->
|
||||
<user-import v-model:visible="showImport" @done="reload"/>
|
||||
|
||||
</a-page-header>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {createVNode, ref, reactive, watch} from 'vue';
|
||||
import {message, Modal} from 'ant-design-vue/es';
|
||||
import {
|
||||
PlusOutlined,
|
||||
UserOutlined,
|
||||
Html5Outlined,
|
||||
ChromeOutlined,
|
||||
WechatOutlined,
|
||||
CloudUploadOutlined,
|
||||
DownloadOutlined,
|
||||
ExclamationCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import type {EleProTable} from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import {messageLoading, formatNumber} from 'ele-admin-pro/es';
|
||||
import UserEdit from './components/user-edit.vue';
|
||||
import UserImport from './components/user-import.vue';
|
||||
import {toDateString} from 'ele-admin-pro';
|
||||
import { utils, writeFile } from 'xlsx';
|
||||
import dayjs from 'dayjs';
|
||||
import {
|
||||
pageShopUser,
|
||||
removeShopUser,
|
||||
removeBatchShopUser,
|
||||
updateShopUser,
|
||||
listShopUser
|
||||
} from '@/api/shop/shopUser';
|
||||
import type {ShopUser, ShopUserParam} from '@/api/shop/shopUser/model';
|
||||
import {toTreeData, uuid} from 'ele-admin-pro';
|
||||
import {listRoles} from '@/api/system/role';
|
||||
import {listOrganizations} from '@/api/system/organization';
|
||||
import {Organization} from '@/api/system/organization/model';
|
||||
import {hasRole} from '@/utils/permission';
|
||||
import {getPageTitle} from "@/utils/common";
|
||||
import router from "@/router";
|
||||
import {getTenantId} from "@/utils/domain";
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 树形数据
|
||||
const data = ref<Organization[]>([]);
|
||||
// 树展开的key
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
// 树选中的key
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
// 表格选中数据
|
||||
const selection = ref<ShopUser[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopUser | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示用户详情
|
||||
const showInfo = ref(false);
|
||||
// 是否显示用户导入弹窗
|
||||
const showImport = ref(false);
|
||||
// 导出加载状态
|
||||
const exportLoading = ref(false);
|
||||
const userType = ref<number>();
|
||||
const searchText = ref('');
|
||||
|
||||
// 加载角色
|
||||
const roles = ref<any[]>([]);
|
||||
// 加载机构
|
||||
listOrganizations()
|
||||
.then((list) => {
|
||||
loading.value = false;
|
||||
const eks: number[] = [];
|
||||
list.forEach((d) => {
|
||||
d.key = d.organizationId;
|
||||
d.value = d.organizationId;
|
||||
d.title = d.organizationName;
|
||||
if (typeof d.key === 'number') {
|
||||
eks.push(d.key);
|
||||
}
|
||||
});
|
||||
expandedRowKeys.value = eks;
|
||||
data.value = toTreeData({
|
||||
data: list,
|
||||
idField: 'organizationId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
if (list.length) {
|
||||
if (typeof list[0].key === 'number') {
|
||||
selectedRowKeys.value = [list[0].key];
|
||||
}
|
||||
// current.value = list[0];
|
||||
} else {
|
||||
selectedRowKeys.value = [];
|
||||
// current.value = null;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'userId',
|
||||
width: 90,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '昵称/姓名',
|
||||
dataIndex: 'nickname',
|
||||
key: 'nickname',
|
||||
align: 'center',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
align: 'center',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '积分',
|
||||
dataIndex: 'points',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '余额',
|
||||
dataIndex: 'balance',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
// {
|
||||
// title: '角色',
|
||||
// dataIndex: 'roles',
|
||||
// key: 'roles',
|
||||
// align: 'center'
|
||||
// },
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
customRender: ({text}) => {
|
||||
return text === 1
|
||||
? createVNode(
|
||||
'span',
|
||||
{
|
||||
class: 'text-red-400'
|
||||
},
|
||||
'封号'
|
||||
)
|
||||
: createVNode(
|
||||
'span',
|
||||
{
|
||||
class: 'text-gray-400'
|
||||
},
|
||||
'正常'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
align: 'center',
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({text}) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 默认搜索条件
|
||||
const defaultWhere = reactive({
|
||||
username: '',
|
||||
nickname: ''
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
where = {};
|
||||
where.roleId = filters.roles;
|
||||
where.keywords = searchText.value;
|
||||
return pageShopUser({page, limit, ...where, ...orders});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopUserParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({where});
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopUser) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开用户详情弹窗 */
|
||||
const openInfo = (row?: ShopUser) => {
|
||||
current.value = row ?? null;
|
||||
showInfo.value = true;
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
/* 导出数据 */
|
||||
const exportData = async () => {
|
||||
exportLoading.value = true;
|
||||
|
||||
try {
|
||||
// 定义表头
|
||||
const array: (string | number)[][] = [
|
||||
[
|
||||
'用户ID',
|
||||
'账号',
|
||||
'昵称',
|
||||
'真实姓名',
|
||||
'手机号',
|
||||
'邮箱',
|
||||
'性别',
|
||||
'状态',
|
||||
'注册时间'
|
||||
]
|
||||
];
|
||||
|
||||
// 构建查询参数,使用当前搜索条件
|
||||
const params = {
|
||||
keywords: searchText.value,
|
||||
isAdmin: 0
|
||||
};
|
||||
|
||||
// 获取用户列表数据
|
||||
const list = await listShopUser(params);
|
||||
|
||||
if (!list || list.length === 0) {
|
||||
message.warning('没有数据可以导出');
|
||||
exportLoading.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 将数据转换为Excel行
|
||||
list.forEach((user: ShopUser) => {
|
||||
array.push([
|
||||
`${user.userId || ''}`,
|
||||
`${user.username || ''}`,
|
||||
`${user.nickname || ''}`,
|
||||
`${user.realName || ''}`,
|
||||
`${user.phone || ''}`,
|
||||
`${user.email || ''}`,
|
||||
`${user.sex == 1 ? '男' : '女'}`,
|
||||
`${user.status === 0 ? '正常' : '冻结'}`,
|
||||
`${user.createTime || ''}`
|
||||
]);
|
||||
});
|
||||
|
||||
// 生成Excel文件
|
||||
const sheetName = `shop_user_${getTenantId()}_${dayjs(new Date()).format('YYYYMMDD')}`;
|
||||
const workbook = {
|
||||
SheetNames: [sheetName],
|
||||
Sheets: {}
|
||||
};
|
||||
const sheet = utils.aoa_to_sheet(array);
|
||||
workbook.Sheets[sheetName] = sheet;
|
||||
|
||||
// 设置列宽
|
||||
sheet['!cols'] = [
|
||||
{ wch: 10 }, // 用户ID
|
||||
{ wch: 15 }, // 账号
|
||||
{ wch: 12 }, // 昵称
|
||||
{ wch: 12 }, // 真实姓名
|
||||
{ wch: 15 }, // 手机号
|
||||
{ wch: 20 }, // 邮箱
|
||||
{ wch: 8 }, // 性别
|
||||
{ wch: 15 }, // 所属部门
|
||||
{ wch: 20 }, // 角色
|
||||
{ wch: 8 }, // 状态
|
||||
{ wch: 20 } // 注册时间
|
||||
];
|
||||
|
||||
message.loading('正在生成Excel文件...', 0);
|
||||
|
||||
setTimeout(() => {
|
||||
writeFile(workbook, `${sheetName}.xlsx`);
|
||||
exportLoading.value = false;
|
||||
message.destroy();
|
||||
message.success(`成功导出 ${list.length} 条记录`);
|
||||
}, 1000);
|
||||
|
||||
} catch (error: any) {
|
||||
exportLoading.value = false;
|
||||
message.error(error.message || '导出失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabs = (e) => {
|
||||
userType.value = Number(e.target.value);
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopUser) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeShopUser(row.userId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的用户吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeShopUser(selection.value.map((d) => d.userId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 重置用户密码 */
|
||||
const resetPsw = (row: ShopUser) => {
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要重置此用户的密码吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
const password = uuid(8);
|
||||
updateShopUser({
|
||||
...row,
|
||||
password: password
|
||||
})
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg + ',新密码:' + password);
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 修改用户状态 */
|
||||
const updateIsAdmin = (row: ShopUser) => {
|
||||
updateShopUser(row)
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopUser) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const query = async () => {
|
||||
const info = await listRoles({})
|
||||
if (info) {
|
||||
roles.value = info
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => router.currentRoute.value.query,
|
||||
() => {
|
||||
query();
|
||||
},
|
||||
{immediate: true}
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopUser'
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-org-table {
|
||||
:deep(.ant-table) {
|
||||
.ant-table-thead > tr > th {
|
||||
background: #fafafa;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
border-bottom: 2px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
padding: 12px 8px;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background: #f8f9ff;
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-primary {
|
||||
color: #1890ff;
|
||||
|
||||
&:hover {
|
||||
color: #40a9ff;
|
||||
}
|
||||
}
|
||||
|
||||
.ele-text-danger {
|
||||
color: #ff4d4f;
|
||||
|
||||
&:hover {
|
||||
color: #ff7875;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -225,12 +225,7 @@
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopUserCouponId: undefined,
|
||||
shopUserCouponName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
|
||||
@@ -115,12 +115,7 @@
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopUserRefereeId: undefined,
|
||||
shopUserRefereeName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
|
||||
Reference in New Issue
Block a user