|
@@ -10,6 +10,7 @@ import {
|
|
|
distributeCoupon
|
|
distributeCoupon
|
|
|
} from "@/api/coupon";
|
|
} from "@/api/coupon";
|
|
|
import { distributeCoupons, getCouponDistributeRecords } from "@/api/marketing";
|
|
import { distributeCoupons, getCouponDistributeRecords } from "@/api/marketing";
|
|
|
|
|
+import { getUserList } from "@/api/user";
|
|
|
import ShopSelector from "../../components/ShopSelector.vue";
|
|
import ShopSelector from "../../components/ShopSelector.vue";
|
|
|
import ProductSelector from "../../components/ProductSelector.vue";
|
|
import ProductSelector from "../../components/ProductSelector.vue";
|
|
|
import DeviceSelector from "../../components/DeviceSelector.vue";
|
|
import DeviceSelector from "../../components/DeviceSelector.vue";
|
|
@@ -26,7 +27,8 @@ import {
|
|
|
ElInputNumber,
|
|
ElInputNumber,
|
|
|
ElTag,
|
|
ElTag,
|
|
|
ElRadioGroup,
|
|
ElRadioGroup,
|
|
|
- ElRadioButton
|
|
|
|
|
|
|
+ ElRadioButton,
|
|
|
|
|
+ ElAlert
|
|
|
} from "element-plus";
|
|
} from "element-plus";
|
|
|
import {
|
|
import {
|
|
|
initPagination,
|
|
initPagination,
|
|
@@ -40,12 +42,14 @@ interface SearchFormProps {
|
|
|
name: string;
|
|
name: string;
|
|
|
type: number | undefined;
|
|
type: number | undefined;
|
|
|
status: number | undefined;
|
|
status: number | undefined;
|
|
|
|
|
+ receiveType: string | undefined;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface CouponFormData {
|
|
interface CouponFormData {
|
|
|
id?: number;
|
|
id?: number;
|
|
|
name: string;
|
|
name: string;
|
|
|
activityId: string;
|
|
activityId: string;
|
|
|
|
|
+ receiveType: string; // Collect-主动领取,Release-系统发放
|
|
|
type: number | undefined;
|
|
type: number | undefined;
|
|
|
value: number | null;
|
|
value: number | null;
|
|
|
totalCount: number | null;
|
|
totalCount: number | null;
|
|
@@ -63,7 +67,8 @@ export function useCoupon() {
|
|
|
const form = reactive<SearchFormProps>({
|
|
const form = reactive<SearchFormProps>({
|
|
|
name: "",
|
|
name: "",
|
|
|
type: undefined,
|
|
type: undefined,
|
|
|
- status: undefined
|
|
|
|
|
|
|
+ status: undefined,
|
|
|
|
|
+ receiveType: undefined
|
|
|
});
|
|
});
|
|
|
const loading = ref(true);
|
|
const loading = ref(true);
|
|
|
const dataList = ref([]);
|
|
const dataList = ref([]);
|
|
@@ -72,8 +77,8 @@ export function useCoupon() {
|
|
|
const typeMap: Record<number, { text: string; type: string }> = {
|
|
const typeMap: Record<number, { text: string; type: string }> = {
|
|
|
1: { text: "满减券", type: "primary" },
|
|
1: { text: "满减券", type: "primary" },
|
|
|
2: { text: "折扣券", type: "success" },
|
|
2: { text: "折扣券", type: "success" },
|
|
|
- 3: { text: "现金券", type: "warning" },
|
|
|
|
|
- 4: { text: "兑换券", type: "info" }
|
|
|
|
|
|
|
+ 3: { text: "立减券", type: "warning" },
|
|
|
|
|
+ 4: { text: "商品券", type: "info" }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const statusMap: Record<number, { text: string; type: string }> = {
|
|
const statusMap: Record<number, { text: string; type: string }> = {
|
|
@@ -101,13 +106,24 @@ export function useCoupon() {
|
|
|
},
|
|
},
|
|
|
{
|
|
{
|
|
|
label: "类型",
|
|
label: "类型",
|
|
|
- prop: "type",
|
|
|
|
|
|
|
+ prop: "couponType",
|
|
|
minWidth: 100,
|
|
minWidth: 100,
|
|
|
cellRenderer: ({ row }) => {
|
|
cellRenderer: ({ row }) => {
|
|
|
- const item = typeMap[row.type] || { text: "未知", type: "info" };
|
|
|
|
|
|
|
+ const item = typeMap[row.couponType] || { text: "未知", type: "info" };
|
|
|
return <ElTag type={item.type}>{item.text}</ElTag>;
|
|
return <ElTag type={item.type}>{item.text}</ElTag>;
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ {
|
|
|
|
|
+ label: "发放类型",
|
|
|
|
|
+ prop: "receiveType",
|
|
|
|
|
+ minWidth: 110,
|
|
|
|
|
+ cellRenderer: ({ row }) => {
|
|
|
|
|
+ if (row.receiveType === 'Collect') {
|
|
|
|
|
+ return <ElTag type="success">主动领取</ElTag>;
|
|
|
|
|
+ }
|
|
|
|
|
+ return <ElTag type="primary">系统发放</ElTag>;
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
{
|
|
{
|
|
|
label: "优惠值",
|
|
label: "优惠值",
|
|
|
prop: "value",
|
|
prop: "value",
|
|
@@ -171,6 +187,7 @@ export function useCoupon() {
|
|
|
if (form.name) searchParams.name = form.name;
|
|
if (form.name) searchParams.name = form.name;
|
|
|
if (form.type !== undefined) searchParams.type = form.type;
|
|
if (form.type !== undefined) searchParams.type = form.type;
|
|
|
if (form.status !== undefined) searchParams.status = form.status;
|
|
if (form.status !== undefined) searchParams.status = form.status;
|
|
|
|
|
+ if (form.receiveType) searchParams.receiveType = form.receiveType;
|
|
|
|
|
|
|
|
const { data } = await getCouponList(searchParams);
|
|
const { data } = await getCouponList(searchParams);
|
|
|
if (data) {
|
|
if (data) {
|
|
@@ -206,35 +223,140 @@ export function useCoupon() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function handleDistribute(row) {
|
|
function handleDistribute(row) {
|
|
|
|
|
+ const distributeForm = reactive({
|
|
|
|
|
+ targetType: "all", // all-全部用户, specific-指定用户
|
|
|
|
|
+ userIds: []
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 用户列表状态
|
|
|
|
|
+ const userList = ref([]);
|
|
|
|
|
+ const userLoading = ref(false);
|
|
|
|
|
+ const userSearchKeyword = ref("");
|
|
|
|
|
+
|
|
|
|
|
+ // 加载用户列表
|
|
|
|
|
+ const loadUserList = async (keyword = "") => {
|
|
|
|
|
+ userLoading.value = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const { data } = await getUserList({
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ pageSize: 100,
|
|
|
|
|
+ phone: keyword || undefined,
|
|
|
|
|
+ nickname: keyword || undefined
|
|
|
|
|
+ });
|
|
|
|
|
+ if (data) {
|
|
|
|
|
+ userList.value = data.list || [];
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("加载用户列表失败:", error);
|
|
|
|
|
+ message("加载用户列表失败", { type: "error" });
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ userLoading.value = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 用户搜索
|
|
|
|
|
+ const handleUserSearch = (query: string) => {
|
|
|
|
|
+ userSearchKeyword.value = query;
|
|
|
|
|
+ if (query) {
|
|
|
|
|
+ loadUserList(query);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ loadUserList();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
addDialog({
|
|
addDialog({
|
|
|
- title: "定向发放优惠券",
|
|
|
|
|
- width: "600px",
|
|
|
|
|
|
|
+ title: `发放优惠券 - ${row.couponName || '未知优惠券'}`,
|
|
|
|
|
+ width: "700px",
|
|
|
draggable: true,
|
|
draggable: true,
|
|
|
fullscreen: deviceDetection(),
|
|
fullscreen: deviceDetection(),
|
|
|
|
|
+ onOpened: () => {
|
|
|
|
|
+ // 弹窗打开时加载用户列表
|
|
|
|
|
+ loadUserList();
|
|
|
|
|
+ },
|
|
|
contentRenderer: () => (
|
|
contentRenderer: () => (
|
|
|
<ElForm label-width="100px" size="default">
|
|
<ElForm label-width="100px" size="default">
|
|
|
- <ElFormItem label="发放类型">
|
|
|
|
|
- <ElRadioGroup>
|
|
|
|
|
- <ElRadioButton value="1">主动领取</ElRadioButton>
|
|
|
|
|
- <ElRadioButton value="2">系统发放</ElRadioButton>
|
|
|
|
|
- <ElRadioButton value="3">定向发放</ElRadioButton>
|
|
|
|
|
- </ElRadioGroup>
|
|
|
|
|
|
|
+ <ElFormItem label="优惠券">
|
|
|
|
|
+ <ElInput value={row.couponName} disabled />
|
|
|
|
|
+ </ElFormItem>
|
|
|
|
|
+ <ElFormItem label="发放总量">
|
|
|
|
|
+ <ElInput value={`${row.receivedCount || 0} / ${row.totalCount || 0}`} disabled />
|
|
|
</ElFormItem>
|
|
</ElFormItem>
|
|
|
- <ElFormItem label="目标用户">
|
|
|
|
|
- <ElSelect placeholder="请选择目标用户" class="w-full" multiple>
|
|
|
|
|
|
|
+ <ElFormItem label="目标用户" required>
|
|
|
|
|
+ <ElSelect
|
|
|
|
|
+ v-model={distributeForm.targetType}
|
|
|
|
|
+ placeholder="请选择目标用户"
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ onChange={() => {
|
|
|
|
|
+ // 切换时清空用户选择
|
|
|
|
|
+ if (distributeForm.targetType === 'all') {
|
|
|
|
|
+ distributeForm.userIds = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
<ElOption label="全部用户" value="all" />
|
|
<ElOption label="全部用户" value="all" />
|
|
|
- <ElOption label="新用户" value="new" />
|
|
|
|
|
- <ElOption label="VIP用户" value="vip" />
|
|
|
|
|
|
|
+ <ElOption label="指定用户" value="specific" />
|
|
|
</ElSelect>
|
|
</ElSelect>
|
|
|
</ElFormItem>
|
|
</ElFormItem>
|
|
|
- <ElFormItem label="发放数量">
|
|
|
|
|
- <ElInputNumber placeholder="请输入发放数量" class="w-full" min={1} max={10000} />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ {distributeForm.targetType === 'specific' && (
|
|
|
|
|
+ <ElFormItem label="选择用户" required>
|
|
|
|
|
+ <ElSelect
|
|
|
|
|
+ v-model={distributeForm.userIds}
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ multiple
|
|
|
|
|
+ filterable
|
|
|
|
|
+ remote
|
|
|
|
|
+ reserve-keyword
|
|
|
|
|
+ remote-show-suffix={true}
|
|
|
|
|
+ loading={userLoading.value}
|
|
|
|
|
+ onRemoteMethod={handleUserSearch}
|
|
|
|
|
+ placeholder="请输入用户名或手机号搜索"
|
|
|
|
|
+ >
|
|
|
|
|
+ {userList.value.map((user: any) => (
|
|
|
|
|
+ <ElOption
|
|
|
|
|
+ key={user.id}
|
|
|
|
|
+ label={`${user.nickname || user.phone || '未知用户'} (${user.phone || '无手机号'})`}
|
|
|
|
|
+ value={user.id}
|
|
|
|
|
+ />
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </ElSelect>
|
|
|
|
|
+ <div style="font-size: 12px; color: #9ca3af; margin-top: 4px;">
|
|
|
|
|
+ 已选择 {distributeForm.userIds.length} 个用户
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </ElFormItem>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <ElFormItem label="发放数量" required>
|
|
|
|
|
+ <ElInputNumber
|
|
|
|
|
+ placeholder="请输入发放数量"
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ min={1}
|
|
|
|
|
+ max={row.totalCount - (row.receivedCount || 0)}
|
|
|
|
|
+ />
|
|
|
</ElFormItem>
|
|
</ElFormItem>
|
|
|
|
|
+
|
|
|
|
|
+ <ElAlert
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ closable={false}
|
|
|
|
|
+ showIcon={true}
|
|
|
|
|
+ style={{marginTop: '8px'}}
|
|
|
|
|
+ >
|
|
|
|
|
+ {distributeForm.targetType === 'all'
|
|
|
|
|
+ ? '将向平台所有用户发放该优惠券'
|
|
|
|
|
+ : `将向已选择的 ${distributeForm.userIds.length} 个用户发放该优惠券`}
|
|
|
|
|
+ </ElAlert>
|
|
|
</ElForm>
|
|
</ElForm>
|
|
|
),
|
|
),
|
|
|
beforeSure: done => {
|
|
beforeSure: done => {
|
|
|
|
|
+ // 验证
|
|
|
|
|
+ if (distributeForm.targetType === 'specific' && distributeForm.userIds.length === 0) {
|
|
|
|
|
+ message("请选择至少一个用户", { type: "warning" });
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
message("发放成功", { type: "success" });
|
|
message("发放成功", { type: "success" });
|
|
|
done();
|
|
done();
|
|
|
|
|
+ onSearch();
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -445,7 +567,23 @@ export function useCoupon() {
|
|
|
<ElOption label="不关联活动" value="" />
|
|
<ElOption label="不关联活动" value="" />
|
|
|
</ElSelect>
|
|
</ElSelect>
|
|
|
</ElFormItem>
|
|
</ElFormItem>
|
|
|
|
|
+ <ElFormItem label="发放类型" required>
|
|
|
|
|
+ <ElRadioGroup v-model={formData.receiveType}>
|
|
|
|
|
+ <ElRadioButton value="Release">系统发放</ElRadioButton>
|
|
|
|
|
+ <ElRadioButton value="Collect">主动领取</ElRadioButton>
|
|
|
|
|
+ </ElRadioGroup>
|
|
|
|
|
+ </ElFormItem>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <ElAlert
|
|
|
|
|
+ type="info"
|
|
|
|
|
+ closable={false}
|
|
|
|
|
+ showIcon={true}
|
|
|
|
|
+ style={{marginTop: '12px'}}
|
|
|
|
|
+ >
|
|
|
|
|
+ {formData.receiveType === 'Collect'
|
|
|
|
|
+ ? '主动领取:将显示在用户端小程序领券中心,用户可自行领取'
|
|
|
|
|
+ : '系统发放:创建后可在列表中点击发放,选择指定用户或全部用户'}
|
|
|
|
|
+ </ElAlert>
|
|
|
</ElForm>
|
|
</ElForm>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -582,6 +720,7 @@ export function useCoupon() {
|
|
|
const formData = reactive<CouponFormData>({
|
|
const formData = reactive<CouponFormData>({
|
|
|
name: "",
|
|
name: "",
|
|
|
activityId: "",
|
|
activityId: "",
|
|
|
|
|
+ receiveType: "Release", // 默认为系统发放
|
|
|
type: undefined,
|
|
type: undefined,
|
|
|
value: null,
|
|
value: null,
|
|
|
totalCount: null,
|
|
totalCount: null,
|
|
@@ -604,14 +743,37 @@ export function useCoupon() {
|
|
|
beforeSure: async (done) => {
|
|
beforeSure: async (done) => {
|
|
|
if (!validateForm(formData)) return;
|
|
if (!validateForm(formData)) return;
|
|
|
try {
|
|
try {
|
|
|
- const { code } = await createCoupon(toRaw(formData));
|
|
|
|
|
|
|
+ // 将前端表单数据转换为后端 DTO 格式
|
|
|
|
|
+ const submitData = {
|
|
|
|
|
+ couponName: formData.name,
|
|
|
|
|
+ couponType: formData.type,
|
|
|
|
|
+ receiveType: formData.receiveType,
|
|
|
|
|
+ discountValue: formData.value,
|
|
|
|
|
+ totalCount: formData.totalCount,
|
|
|
|
|
+ validType: 1, // 固定时间
|
|
|
|
|
+ validStartTime: formData.validPeriod?.[0],
|
|
|
|
|
+ validEndTime: formData.validPeriod?.[1],
|
|
|
|
|
+ applyScope: formData.applyScope,
|
|
|
|
|
+ productScope: formData.productScope,
|
|
|
|
|
+ activityId: formData.activityId ? Number(formData.activityId) : null,
|
|
|
|
|
+ shopIds: formData.shopIds,
|
|
|
|
|
+ productIds: formData.productIds,
|
|
|
|
|
+ couponDesc: formData.description,
|
|
|
|
|
+ receiveLimit: 1
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const { code, message: errorMsg } = await createCoupon(submitData);
|
|
|
if (code === 200) {
|
|
if (code === 200) {
|
|
|
message("新增成功", { type: "success" });
|
|
message("新增成功", { type: "success" });
|
|
|
done();
|
|
done();
|
|
|
onSearch();
|
|
onSearch();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ message(errorMsg || "新增失败", { type: "error" });
|
|
|
}
|
|
}
|
|
|
- } catch (error) {
|
|
|
|
|
- message("新增失败", { type: "error" });
|
|
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ // 展示后端返回的具体错误信息
|
|
|
|
|
+ const errorMsg = error?.response?.data?.message || error?.message || "新增失败,请重试";
|
|
|
|
|
+ message(errorMsg, { type: "error" });
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|