docs: 新增优惠券和礼品卡相关文档
- 新增 Vue 模板标签错误修复总结文档 - 新增 优惠券列表页面优化说明文档 - 新增 优惠券和礼品卡弹窗优化说明文档 - 新增 商品关联功能修复说明文档
This commit is contained in:
@@ -17,7 +17,7 @@ export interface ShopGift {
|
||||
// 操作人
|
||||
operatorUserId?: number;
|
||||
// 是否展示
|
||||
isShow?: string;
|
||||
isShow?: boolean;
|
||||
// 状态, 0上架 1待上架 2待审核 3审核不通过
|
||||
status?: number;
|
||||
// 备注
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,65 +1,260 @@
|
||||
<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="shopCouponId"
|
||||
: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" />
|
||||
<div class="shop-coupon-container">
|
||||
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="openEdit()">
|
||||
<template #icon>
|
||||
<PlusOutlined />
|
||||
</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-button>
|
||||
<a-button @click="reload()">
|
||||
<template #icon>
|
||||
<ReloadOutlined />
|
||||
</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-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-page-header>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopCouponEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</a-page-header>
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-container">
|
||||
<a-form layout="inline" :model="searchForm" class="search-form">
|
||||
<a-form-item label="优惠券名称">
|
||||
<a-input
|
||||
v-model:value="searchForm.name"
|
||||
placeholder="请输入优惠券名称"
|
||||
allow-clear
|
||||
style="width: 200px"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="优惠券类型">
|
||||
<a-select
|
||||
v-model:value="searchForm.type"
|
||||
placeholder="请选择类型"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
>
|
||||
<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-select
|
||||
v-model:value="searchForm.expireType"
|
||||
placeholder="请选择到期类型"
|
||||
allow-clear
|
||||
style="width: 150px"
|
||||
>
|
||||
<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.isExpire"
|
||||
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-button type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SearchOutlined />
|
||||
</template>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button @click="handleReset">
|
||||
<template #icon>
|
||||
<ClearOutlined />
|
||||
</template>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</div>
|
||||
|
||||
<!-- 批量操作区域 -->
|
||||
<div v-if="selection.length > 0" class="batch-actions">
|
||||
<a-alert
|
||||
:message="`已选择 ${selection.length} 项`"
|
||||
type="info"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #action>
|
||||
<a-space>
|
||||
<a-button size="small" @click="clearSelection">取消选择</a-button>
|
||||
<a-popconfirm
|
||||
title="确定要删除选中的优惠券吗?"
|
||||
@confirm="removeBatch"
|
||||
>
|
||||
<a-button size="small" danger>批量删除</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-alert>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
:row-selection="rowSelection"
|
||||
:scroll="{ x: 1800 }"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="coupon-table"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="coupon-name">
|
||||
<a-typography-text strong>{{ record.name }}</a-typography-text>
|
||||
<div class="coupon-description">{{ record.description || '暂无描述' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="getCouponTypeColor(record.type)">
|
||||
{{ getCouponTypeText(record.type) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'value'">
|
||||
<div class="coupon-value">
|
||||
<template v-if="record.type === 10">
|
||||
<span class="value-amount">¥{{ record.reducePrice?.toFixed(2) || '0.00' }}</span>
|
||||
<div class="value-condition">满¥{{ record.minPrice?.toFixed(2) || '0.00' }}可用</div>
|
||||
</template>
|
||||
<template v-else-if="record.type === 20">
|
||||
<span class="value-discount">{{ record.discount }}折</span>
|
||||
<div class="value-condition">满¥{{ record.minPrice?.toFixed(2) || '0.00' }}可用</div>
|
||||
</template>
|
||||
<template v-else-if="record.type === 30">
|
||||
<span class="value-free">免费券</span>
|
||||
<div class="value-condition">满¥{{ record.minPrice?.toFixed(2) || '0.00' }}可用</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'expireInfo'">
|
||||
<div class="expire-info">
|
||||
<a-tag :color="record.expireType === 10 ? 'blue' : 'green'">
|
||||
{{ record.expireType === 10 ? '领取后生效' : '固定时间' }}
|
||||
</a-tag>
|
||||
<div class="expire-detail">
|
||||
<template v-if="record.expireType === 10">
|
||||
{{ record.expireDay }}天有效
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ formatDate(record.startTime) }} 至 {{ formatDate(record.endTime) }}
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'applyRange'">
|
||||
<a-tag :color="getApplyRangeColor(record.applyRange)">
|
||||
{{ getApplyRangeText(record.applyRange) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'usage'">
|
||||
<div class="usage-info">
|
||||
<a-progress
|
||||
:percent="getUsagePercent(record)"
|
||||
:stroke-color="getUsageColor(record)"
|
||||
size="small"
|
||||
/>
|
||||
<div class="usage-text">
|
||||
已发放: {{ record.issuedCount || 0 }}
|
||||
<template v-if="record.totalCount !== -1">
|
||||
/ {{ record.totalCount }}
|
||||
</template>
|
||||
<template v-else>
|
||||
(无限制)
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'isExpire'">
|
||||
<a-tag :color="record.isExpire === 0 ? 'success' : 'error'">
|
||||
{{ record.isExpire === 0 ? '未过期' : '已过期' }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-tooltip title="编辑">
|
||||
<a-button type="link" size="small" @click="openEdit(record)">
|
||||
<template #icon>
|
||||
<EditOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-tooltip title="复制">
|
||||
<a-button type="link" size="small" @click="copyRecord(record)">
|
||||
<template #icon>
|
||||
<CopyOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-popconfirm
|
||||
title="确定要删除此优惠券吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a-tooltip title="删除">
|
||||
<a-button type="link" size="small" danger>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ShopCouponEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { createVNode, ref, reactive, computed } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import {
|
||||
ExclamationCircleOutlined,
|
||||
PlusOutlined,
|
||||
ReloadOutlined,
|
||||
SearchOutlined,
|
||||
ClearOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
CopyOutlined
|
||||
} 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 { getPageTitle } from '@/utils/common';
|
||||
import ShopCouponEdit from './components/shopCouponEdit.vue';
|
||||
import { pageShopCoupon, removeShopCoupon, removeBatchShopCoupon } from '@/api/shop/shopCoupon';
|
||||
import type { ShopCoupon, ShopCouponParam } from '@/api/shop/shopCoupon/model';
|
||||
@@ -73,10 +268,16 @@
|
||||
const current = ref<ShopCoupon | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
const loading = ref(false);
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive<ShopCouponParam>({
|
||||
name: '',
|
||||
type: undefined,
|
||||
expireType: undefined,
|
||||
isExpire: undefined
|
||||
});
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
@@ -86,206 +287,254 @@
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageShopCoupon({
|
||||
const params = {
|
||||
...where,
|
||||
...searchForm,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
if (filters) {
|
||||
Object.assign(params, filters);
|
||||
}
|
||||
|
||||
return pageShopCoupon(params);
|
||||
};
|
||||
|
||||
// 行选择配置
|
||||
const rowSelection = computed(() => ({
|
||||
selectedRowKeys: selection.value.map(item => item.id),
|
||||
onChange: (selectedRowKeys: (string | number)[], selectedRows: ShopCoupon[]) => {
|
||||
selection.value = selectedRows;
|
||||
},
|
||||
onSelect: (record: ShopCoupon, selected: boolean) => {
|
||||
if (selected) {
|
||||
selection.value.push(record);
|
||||
} else {
|
||||
const index = selection.value.findIndex(item => item.id === record.id);
|
||||
if (index > -1) {
|
||||
selection.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
onSelectAll: (selected: boolean, selectedRows: ShopCoupon[], changeRows: ShopCoupon[]) => {
|
||||
if (selected) {
|
||||
changeRows.forEach(row => {
|
||||
if (!selection.value.find(item => item.id === row.id)) {
|
||||
selection.value.push(row);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
changeRows.forEach(row => {
|
||||
const index = selection.value.findIndex(item => item.id === row.id);
|
||||
if (index > -1) {
|
||||
selection.value.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'id',
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
width: 80,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '优惠券名称',
|
||||
title: '优惠券信息',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
align: 'left',
|
||||
width: 250,
|
||||
fixed: 'left',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '优惠券描述',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '优惠券类型',
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 10:
|
||||
return '满减券';
|
||||
case 20:
|
||||
return '折扣券';
|
||||
case 30:
|
||||
return '免费劵';
|
||||
}
|
||||
}
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '满减券',
|
||||
dataIndex: 'reducePrice',
|
||||
key: 'reducePrice',
|
||||
title: '优惠价值',
|
||||
dataIndex: 'value',
|
||||
key: 'value',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => text.toFixed(2)
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '折扣券',
|
||||
dataIndex: 'discount',
|
||||
key: 'discount',
|
||||
title: '有效期信息',
|
||||
dataIndex: 'expireInfo',
|
||||
key: 'expireInfo',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '最低消费金额',
|
||||
dataIndex: 'minPrice',
|
||||
key: 'minPrice',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '到期类型',
|
||||
dataIndex: 'expireType',
|
||||
key: 'expireType',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 10:
|
||||
return '领取后生效';
|
||||
case 20:
|
||||
return '固定时间';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '有效天数',
|
||||
dataIndex: 'expireDay',
|
||||
key: 'expireDay',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '有效期开始时间',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
customRender: ({ text }) => text ? toDateString(text, 'yyyy-MM-dd') : '-'
|
||||
},
|
||||
{
|
||||
title: '有效期结束时间',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
align: 'center',
|
||||
width: 120,
|
||||
customRender: ({ text }) => text ? toDateString(text, 'yyyy-MM-dd') : '-'
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: '适用范围',
|
||||
dataIndex: 'applyRange',
|
||||
key: 'applyRange',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 10:
|
||||
return '全部商品';
|
||||
case 20:
|
||||
return '指定商品';
|
||||
case 30:
|
||||
return '指定分类';
|
||||
}
|
||||
}
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '是否过期',
|
||||
title: '使用情况',
|
||||
dataIndex: 'usage',
|
||||
key: 'usage',
|
||||
align: 'center',
|
||||
width: 150
|
||||
},
|
||||
{
|
||||
title: '每人限领',
|
||||
dataIndex: 'limitPerUser',
|
||||
key: 'limitPerUser',
|
||||
align: 'center',
|
||||
width: 100,
|
||||
customRender: ({ text }) => text === -1 ? '无限制' : text
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'isExpire',
|
||||
key: 'isExpire',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 0:
|
||||
return '未过期';
|
||||
case 1:
|
||||
return '已过期';
|
||||
}
|
||||
}
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
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',
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => text ? toDateString(text, 'yyyy-MM-dd HH:mm') : '-'
|
||||
},
|
||||
{
|
||||
title: '发放总数量(-1表示无限制)',
|
||||
dataIndex: 'totalCount',
|
||||
key: 'totalCount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '已发放数量',
|
||||
dataIndex: 'issuedCount',
|
||||
key: 'issuedCount',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '每人限领数量(-1表示无限制)',
|
||||
dataIndex: 'limitPerUser',
|
||||
key: 'limitPerUser',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
width: 150,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 工具方法
|
||||
const getCouponTypeText = (type: number) => {
|
||||
const typeMap = {
|
||||
10: '满减券',
|
||||
20: '折扣券',
|
||||
30: '免费券'
|
||||
};
|
||||
return typeMap[type as keyof typeof typeMap] || '未知';
|
||||
};
|
||||
|
||||
const getCouponTypeColor = (type: number) => {
|
||||
const colorMap = {
|
||||
10: 'red',
|
||||
20: 'orange',
|
||||
30: 'green'
|
||||
};
|
||||
return colorMap[type as keyof typeof colorMap] || 'default';
|
||||
};
|
||||
|
||||
const getApplyRangeText = (range: number) => {
|
||||
const rangeMap = {
|
||||
10: '全部商品',
|
||||
20: '指定商品',
|
||||
30: '指定分类'
|
||||
};
|
||||
return rangeMap[range as keyof typeof rangeMap] || '未知';
|
||||
};
|
||||
|
||||
const getApplyRangeColor = (range: number) => {
|
||||
const colorMap = {
|
||||
10: 'blue',
|
||||
20: 'purple',
|
||||
30: 'cyan'
|
||||
};
|
||||
return colorMap[range as keyof typeof colorMap] || 'default';
|
||||
};
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return dateStr ? toDateString(dateStr, 'yyyy-MM-dd') : '-';
|
||||
};
|
||||
|
||||
const getUsagePercent = (record: ShopCoupon) => {
|
||||
if (record.totalCount === -1) return 0;
|
||||
return Math.round(((record.issuedCount || 0) / record.totalCount) * 100);
|
||||
};
|
||||
|
||||
const getUsageColor = (record: ShopCoupon) => {
|
||||
const percent = getUsagePercent(record);
|
||||
if (percent >= 90) return '#ff4d4f';
|
||||
if (percent >= 70) return '#faad14';
|
||||
return '#52c41a';
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopCouponParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 处理搜索 */
|
||||
const handleSearch = () => {
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 重置搜索 */
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
name: '',
|
||||
type: undefined,
|
||||
expireType: undefined,
|
||||
isExpire: undefined
|
||||
});
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 清除选择 */
|
||||
const clearSelection = () => {
|
||||
selection.value = [];
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopCoupon) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
/* 复制记录 */
|
||||
const copyRecord = (record: ShopCoupon) => {
|
||||
const copyData = {
|
||||
...record,
|
||||
id: undefined,
|
||||
name: `${record.name}_副本`,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
issuedCount: 0
|
||||
};
|
||||
current.value = copyData;
|
||||
showEdit.value = true;
|
||||
message.success('已复制优惠券信息,请修改后保存');
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ShopCoupon) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeShopCoupon(row.shopCouponId)
|
||||
if (row.issuedCount && row.issuedCount > 0) {
|
||||
message.warning('该优惠券已有用户领取,无法删除');
|
||||
return;
|
||||
}
|
||||
|
||||
const hide = message.loading('删除中...', 0);
|
||||
removeShopCoupon(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
@@ -303,17 +552,29 @@
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有已发放的优惠券
|
||||
const issuedCoupons = selection.value.filter(item => item.issuedCount && item.issuedCount > 0);
|
||||
if (issuedCoupons.length > 0) {
|
||||
message.warning(`选中的优惠券中有 ${issuedCoupons.length} 个已被用户领取,无法删除`);
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的记录吗?',
|
||||
title: '批量删除确认',
|
||||
content: `确定要删除选中的 ${selection.value.length} 个优惠券吗?此操作不可恢复。`,
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
okText: '确定删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: () => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchShopCoupon(selection.value.map((d) => d.shopCouponId))
|
||||
const hide = message.loading('批量删除中...', 0);
|
||||
removeBatchShopCoupon(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
selection.value = [];
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
@@ -324,11 +585,6 @@
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopCoupon) => {
|
||||
return {
|
||||
@@ -339,10 +595,11 @@
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
},
|
||||
// 行样式
|
||||
class: record.isExpire === 1 ? 'expired-row' : ''
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -351,4 +608,103 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.shop-coupon-container {
|
||||
.search-container {
|
||||
background: #fafafa;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.search-form {
|
||||
.ant-form-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.batch-actions {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.coupon-table {
|
||||
.coupon-name {
|
||||
text-align: left;
|
||||
|
||||
.coupon-description {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-value {
|
||||
.value-amount {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #f5222d;
|
||||
}
|
||||
|
||||
.value-discount {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #fa8c16;
|
||||
}
|
||||
|
||||
.value-free {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.value-condition {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.expire-info {
|
||||
.expire-detail {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.usage-info {
|
||||
.usage-text {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.expired-row {
|
||||
background-color: #fff2f0;
|
||||
|
||||
td {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-table) {
|
||||
.ant-table-tbody > tr:hover > td {
|
||||
background-color: #e6f7ff;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-progress) {
|
||||
.ant-progress-text {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ant-alert) {
|
||||
.ant-alert-message {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -696,4 +696,3 @@
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
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-form-item label="关联商品" name="goodsId">
|
||||
<a-select placeholder="请选择关联商品" v-model:value="form.goodsId">
|
||||
<a-select-option
|
||||
v-for="item in goodsList"
|
||||
:key="item.goodsId"
|
||||
@@ -33,7 +33,7 @@
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item label="数量" name="num">
|
||||
<a-form-item label="生成数量" name="num">
|
||||
<a-input-number v-model:value="form.num" :min="0" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
@@ -95,7 +95,7 @@
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
num: undefined
|
||||
num: 1000
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
|
||||
@@ -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,97 +14,216 @@
|
||||
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-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="请输入礼品卡名称"
|
||||
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"
|
||||
>
|
||||
<template #suffix>
|
||||
<a-button 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"
|
||||
@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">
|
||||
<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-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="请选择领取时间"
|
||||
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"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- <a-form-item label="操作人ID" name="operatorUserId">-->
|
||||
<!-- <a-input-number-->
|
||||
<!-- :min="1"-->
|
||||
<!-- placeholder="请输入操作人用户ID"-->
|
||||
<!-- v-model:value="form.operatorUserId"-->
|
||||
<!-- style="width: 300px"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
|
||||
<a-form-item label="备注信息" name="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.comments"
|
||||
placeholder="请输入备注信息"
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
show-count
|
||||
/>
|
||||
</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>
|
||||
|
||||
<!-- 礼品卡预览 -->
|
||||
<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 :color="getStatusColor()">{{ getStatusText() }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gift-card-body">
|
||||
<div class="gift-card-code">
|
||||
<span class="code-label">卡密:</span>
|
||||
<span class="code-value">{{ form.code || '未设置' }}</span>
|
||||
</div>
|
||||
<div class="gift-card-goods" v-if="selectedGoods">
|
||||
<span class="goods-label">关联商品:</span>
|
||||
<span class="goods-name">{{ selectedGoods.name }}</span>
|
||||
<a-tag color="blue" style="margin-left: 8px;">¥{{ selectedGoods.price }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gift-card-footer">
|
||||
<div class="gift-card-info">
|
||||
<span v-if="form.takeTime">领取时间:{{ formatTime(form.takeTime) }}</span>
|
||||
<span v-else>未领取</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
@@ -112,21 +231,17 @@
|
||||
<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 { 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 { 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 props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
@@ -146,32 +261,34 @@
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const images = ref<ItemType[]>([]);
|
||||
|
||||
// 用户信息
|
||||
// 表单数据
|
||||
const form = reactive<ShopGift>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
goodsId: undefined,
|
||||
takeTime: undefined,
|
||||
operatorUserId: undefined,
|
||||
isShow: undefined,
|
||||
status: undefined,
|
||||
comments: undefined,
|
||||
sortNumber: undefined,
|
||||
isShow: true,
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100,
|
||||
userId: undefined,
|
||||
deleted: undefined,
|
||||
deleted: 0,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
shopGiftId: undefined,
|
||||
shopGiftName: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
num: 1
|
||||
});
|
||||
|
||||
// 商品列表
|
||||
const goodsList = ref<ShopGoods[]>([]);
|
||||
// 商品加载状态
|
||||
const goodsLoading = ref(false);
|
||||
// 选中的商品
|
||||
const selectedGoods = ref<ShopGoods | null>(null);
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
@@ -179,28 +296,131 @@
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
shopGiftName: [
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写礼品卡名称',
|
||||
message: '请输入礼品卡名称',
|
||||
trigger: 'blur'
|
||||
},
|
||||
{
|
||||
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 chooseImage = (data: FileRecord) => {
|
||||
images.value.push({
|
||||
uid: data.id,
|
||||
url: data.path,
|
||||
status: 'done'
|
||||
});
|
||||
form.image = data.path;
|
||||
/* 生成密钥 */
|
||||
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 onDeleteItem = (index: number) => {
|
||||
images.value.splice(index, 1);
|
||||
form.image = '';
|
||||
/* 搜索商品 */
|
||||
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);
|
||||
@@ -217,6 +437,19 @@
|
||||
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) => {
|
||||
@@ -228,27 +461,73 @@
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
console.error('保存失败:', e);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
.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,
|
||||
(visible) => {
|
||||
async (visible) => {
|
||||
if (visible) {
|
||||
images.value = [];
|
||||
await getGoodsList();
|
||||
|
||||
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.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: 1
|
||||
});
|
||||
|
||||
selectedGoods.value = null;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
@@ -258,3 +537,129 @@
|
||||
{ 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, #ff9a9e 0%, #fecfef 50%, #fecfef 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: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-body {
|
||||
margin-bottom: 16px;
|
||||
|
||||
.gift-card-code {
|
||||
margin-bottom: 12px;
|
||||
|
||||
.code-label {
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.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: #666;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
margin-left: 8px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.gift-card-footer {
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
||||
|
||||
.gift-card-info {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
: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>
|
||||
|
||||
Reference in New Issue
Block a user