调整
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ArticleCollect, ArticleCollectParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageArticleCollect(params: ArticleCollectParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ArticleCollect>>>(
|
||||
MODULES_API_URL + '/cms/article-collect/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listArticleCollect(params?: ArticleCollectParam) {
|
||||
const res = await request.get<ApiResult<ArticleCollect[]>>(
|
||||
MODULES_API_URL + '/cms/article-collect',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addArticleCollect(data: ArticleCollect) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-collect',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateArticleCollect(data: ArticleCollect) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-collect',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeArticleCollect(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-collect/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchArticleCollect(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-collect/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getArticleCollect(id: number) {
|
||||
const res = await request.get<ApiResult<ArticleCollect>>(
|
||||
MODULES_API_URL + '/cms/article-collect/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface ArticleCollect {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
articleId?: number;
|
||||
//
|
||||
userId?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface ArticleCollectParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { ArticleComment, ArticleCommentParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageArticleComment(params: ArticleCommentParam) {
|
||||
const res = await request.get<ApiResult<PageResult<ArticleComment>>>(
|
||||
MODULES_API_URL + '/cms/article-comment/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listArticleComment(params?: ArticleCommentParam) {
|
||||
const res = await request.get<ApiResult<ArticleComment[]>>(
|
||||
MODULES_API_URL + '/cms/article-comment',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addArticleComment(data: ArticleComment) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-comment',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateArticleComment(data: ArticleComment) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-comment',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeArticleComment(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-comment/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchArticleComment(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/cms/article-comment/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getArticleComment(id: number) {
|
||||
const res = await request.get<ApiResult<ArticleComment>>(
|
||||
MODULES_API_URL + '/cms/article-comment/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface ArticleComment {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
articleId?: number;
|
||||
//
|
||||
userId?: number;
|
||||
//
|
||||
content?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface ArticleCommentParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Coupon, CouponParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageCoupon(params: CouponParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Coupon>>>(
|
||||
MODULES_API_URL + '/shop/coupon/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listCoupon(params?: CouponParam) {
|
||||
const res = await request.get<ApiResult<Coupon[]>>(
|
||||
MODULES_API_URL + '/shop/coupon',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addCoupon(data: Coupon) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/coupon',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateCoupon(data: Coupon) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/coupon',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeCoupon(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/coupon/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchCoupon(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/coupon/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getCoupon(id: number) {
|
||||
const res = await request.get<ApiResult<Coupon>>(
|
||||
MODULES_API_URL + '/shop/coupon/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface Coupon {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
title?: string;
|
||||
// 0满减 1折扣
|
||||
type?: number;
|
||||
// 过期类型0领取计算 1固定时间
|
||||
expireType?: number;
|
||||
// 起始金额
|
||||
startAmount?: string;
|
||||
// 抵扣金额
|
||||
reduceAmount?: string;
|
||||
// 折扣比例
|
||||
reduceRate?: string;
|
||||
// 过期天数
|
||||
expireDay?: number;
|
||||
// 过期时间(固定)
|
||||
expireEndDate?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface CouponParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { DealerWithdraw, DealerWithdrawParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询分销商提现明细表
|
||||
*/
|
||||
export async function pageDealerWithdraw(params: DealerWithdrawParam) {
|
||||
const res = await request.get<ApiResult<PageResult<DealerWithdraw>>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询分销商提现明细表列表
|
||||
*/
|
||||
export async function listDealerWithdraw(params?: DealerWithdrawParam) {
|
||||
const res = await request.get<ApiResult<DealerWithdraw[]>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分销商提现明细表
|
||||
*/
|
||||
export async function addDealerWithdraw(data: DealerWithdraw) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改分销商提现明细表
|
||||
*/
|
||||
export async function updateDealerWithdraw(data: DealerWithdraw) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分销商提现明细表
|
||||
*/
|
||||
export async function removeDealerWithdraw(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除分销商提现明细表
|
||||
*/
|
||||
export async function removeBatchDealerWithdraw(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询分销商提现明细表
|
||||
*/
|
||||
export async function getDealerWithdraw(id: number) {
|
||||
const res = await request.get<ApiResult<DealerWithdraw>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出分销商提现明细
|
||||
*/
|
||||
export async function exportDealerWithdraw(params?: DealerWithdrawParam) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/shop/dealer-withdraw/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
/**
|
||||
* 分销商提现明细表
|
||||
*/
|
||||
export interface DealerWithdraw {
|
||||
// 主键ID
|
||||
id?: number;
|
||||
//
|
||||
orderNo?: string;
|
||||
// 分销商用户ID
|
||||
userId?: number;
|
||||
// 提现金额
|
||||
money?: string;
|
||||
// 打款方式 (10微信 20支付宝 30银行卡)
|
||||
payType?: number;
|
||||
// 支付宝姓名
|
||||
alipayName?: string;
|
||||
// 支付宝账号
|
||||
alipayAccount?: string;
|
||||
// 开户行名称
|
||||
bankName?: string;
|
||||
// 银行开户名
|
||||
bankAccount?: string;
|
||||
// 银行卡号
|
||||
bankCard?: string;
|
||||
// 申请状态 (10待审核 20审核通过 30驳回 40已打款)
|
||||
applyStatus?: number;
|
||||
// 审核时间
|
||||
auditTime?: number;
|
||||
// 驳回原因
|
||||
rejectReason?: string;
|
||||
// 来源客户端(APP、H5、小程序等)
|
||||
platform?: string;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 用户
|
||||
user?: User;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销商提现明细表搜索条件
|
||||
*/
|
||||
export interface DealerWithdrawParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
userId?: number;
|
||||
applyStatus?: number | number[];
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { Merchant, MerchantParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询商户
|
||||
*/
|
||||
export async function pageMerchant(params: MerchantParam) {
|
||||
const res = await request.get<ApiResult<PageResult<Merchant>>>(
|
||||
MODULES_API_URL + '/shop/merchant/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询商户列表
|
||||
*/
|
||||
export async function listMerchant(params?: MerchantParam) {
|
||||
const res = await request.get<ApiResult<Merchant[]>>(
|
||||
MODULES_API_URL + '/shop/merchant',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询商户精简列表
|
||||
*/
|
||||
export async function listMerchantMini(params?: MerchantParam) {
|
||||
const res = await request.get<ApiResult<Merchant[]>>(
|
||||
MODULES_API_URL + '/shop/merchant/mini',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加商户
|
||||
*/
|
||||
export async function addMerchant(data: Merchant) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/merchant',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改商户
|
||||
*/
|
||||
export async function updateMerchant(data: Merchant) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/merchant',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除商户
|
||||
*/
|
||||
export async function removeMerchant(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/merchant/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除商户
|
||||
*/
|
||||
export async function removeBatchMerchant(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/merchant/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量新增商户
|
||||
*/
|
||||
export async function addBatchMerchant(data: Merchant[]) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/merchant/batch',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询商户
|
||||
*/
|
||||
export async function getMerchant(id: number) {
|
||||
const res = await request.get<ApiResult<Merchant>>(
|
||||
MODULES_API_URL + '/shop/merchant/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 商户
|
||||
*/
|
||||
export interface Merchant {
|
||||
// ID
|
||||
id?: number;
|
||||
// 编码
|
||||
code?: string;
|
||||
areaName?: string;
|
||||
chain?: string;
|
||||
// 商户名称
|
||||
name?: string;
|
||||
// 商户图标
|
||||
image?: string;
|
||||
// 商户手机号
|
||||
phone?: string;
|
||||
// 商户姓名
|
||||
realName?: string;
|
||||
// 经纬度
|
||||
lngAndLat?: string;
|
||||
//
|
||||
lng?: string;
|
||||
//
|
||||
lat?: string;
|
||||
// 所在省份
|
||||
province?: string;
|
||||
// 所在城市
|
||||
city?: string;
|
||||
// 所在辖区
|
||||
region?: string;
|
||||
// 详细地址
|
||||
address?: string;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 所有人
|
||||
userId?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 状态
|
||||
status?: number;
|
||||
// 排序号
|
||||
sortNumber?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 商户搜索条件
|
||||
*/
|
||||
export interface MerchantParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
chain?: string;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { UserCoupon, UserCouponParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageUserCoupon(params: UserCouponParam) {
|
||||
const res = await request.get<ApiResult<PageResult<UserCoupon>>>(
|
||||
MODULES_API_URL + '/shop/user-coupon/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listUserCoupon(params?: UserCouponParam) {
|
||||
const res = await request.get<ApiResult<UserCoupon[]>>(
|
||||
MODULES_API_URL + '/shop/user-coupon',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addUserCoupon(data: UserCoupon) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-coupon',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function userCouponSend(data: UserCoupon) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-coupon/send',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateUserCoupon(data: UserCoupon) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-coupon',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeUserCoupon(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-coupon/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchUserCoupon(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-coupon/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getUserCoupon(id: number) {
|
||||
const res = await request.get<ApiResult<UserCoupon>>(
|
||||
MODULES_API_URL + '/shop/user-coupon/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface UserCoupon {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
couponId?: number;
|
||||
//
|
||||
orderId?: number;
|
||||
//
|
||||
used?: number;
|
||||
// 使用时间
|
||||
usedTime?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface UserCouponParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { UserParentList, UserParentListParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageUserParentList(params: UserParentListParam) {
|
||||
const res = await request.get<ApiResult<PageResult<UserParentList>>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listUserParentList(params?: UserParentListParam) {
|
||||
const res = await request.get<ApiResult<UserParentList[]>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addUserParentList(data: UserParentList) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateUserParentList(data: UserParentList) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeUserParentList(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchUserParentList(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getUserParentList(id: number) {
|
||||
const res = await request.get<ApiResult<UserParentList>>(
|
||||
MODULES_API_URL + '/shop/user-parent-list/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface UserParentList {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
uid?: number;
|
||||
//
|
||||
parentId?: number;
|
||||
// 管理员备注
|
||||
remark?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface UserParentListParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { UserPointLog, UserPointLogParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageUserPointLog(params: UserPointLogParam) {
|
||||
const res = await request.get<ApiResult<PageResult<UserPointLog>>>(
|
||||
MODULES_API_URL + '/shop/user-point-log/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listUserPointLog(params?: UserPointLogParam) {
|
||||
const res = await request.get<ApiResult<UserPointLog[]>>(
|
||||
MODULES_API_URL + '/shop/user-point-log',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addUserPointLog(data: UserPointLog) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-point-log',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateUserPointLog(data: UserPointLog) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-point-log',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeUserPointLog(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-point-log/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchUserPointLog(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/shop/user-point-log/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getUserPointLog(id: number) {
|
||||
const res = await request.get<ApiResult<UserPointLog>>(
|
||||
MODULES_API_URL + '/shop/user-point-log/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 用户积分记录
|
||||
*/
|
||||
export interface UserPointLog {
|
||||
// ID
|
||||
id?: number;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 积分说明
|
||||
msg?: string;
|
||||
// 管理员备注
|
||||
remark?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface UserPointLogParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
userId?: number;
|
||||
msg?: string;
|
||||
remark?: string;
|
||||
status?: number | number[];
|
||||
deleted?: number;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { SysArea, SysAreaParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageSysArea(params: SysAreaParam) {
|
||||
const res = await request.get<ApiResult<PageResult<SysArea>>>(
|
||||
MODULES_API_URL + '/sys/sys-area/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listSysArea(params?: SysAreaParam) {
|
||||
const res = await request.get<ApiResult<SysArea[]>>(
|
||||
MODULES_API_URL + '/sys/sys-area',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addSysArea(data: SysArea) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/sys/sys-area',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateSysArea(data: SysArea) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/sys/sys-area',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeSysArea(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/sys/sys-area/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchSysArea(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/sys/sys-area/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getSysArea(id: number) {
|
||||
const res = await request.get<ApiResult<SysArea>>(
|
||||
MODULES_API_URL + '/sys/sys-area/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface SysArea {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
name?: string;
|
||||
//
|
||||
fid?: number;
|
||||
//
|
||||
levelId?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface SysAreaParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult } from '@/api';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
export interface AddressInfo {
|
||||
lng?: string | number;
|
||||
lat?: string | number;
|
||||
lon?: string | number;
|
||||
longitude?: string | number;
|
||||
latitude?: string | number;
|
||||
province?: string;
|
||||
prov?: string;
|
||||
city?: string;
|
||||
region?: string;
|
||||
district?: string;
|
||||
county?: string;
|
||||
detail?: string;
|
||||
addressDetail?: string;
|
||||
address?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 地址解析
|
||||
*/
|
||||
export async function getAddressInfo(address: string) {
|
||||
const res = await request.post<ApiResult<any>>(
|
||||
MODULES_API_URL + '/yxe/ai/getAddress',
|
||||
{
|
||||
address
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type {
|
||||
YxeChronicDiseaseCollection,
|
||||
YxeChronicDiseaseCollectionParam
|
||||
} from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询慢病收集
|
||||
*/
|
||||
export async function pageYxeChronicDiseaseCollection(
|
||||
params: YxeChronicDiseaseCollectionParam
|
||||
) {
|
||||
const res = await request.get<
|
||||
ApiResult<PageResult<YxeChronicDiseaseCollection>>
|
||||
>(MODULES_API_URL + '/yxe/yxe-chronic-disease-collection/page', {
|
||||
params
|
||||
});
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询慢病收集列表
|
||||
*/
|
||||
export async function listYxeChronicDiseaseCollection(
|
||||
params?: YxeChronicDiseaseCollectionParam
|
||||
) {
|
||||
const res = await request.get<ApiResult<YxeChronicDiseaseCollection[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加慢病收集
|
||||
*/
|
||||
export async function addYxeChronicDiseaseCollection(
|
||||
data: YxeChronicDiseaseCollection
|
||||
) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改慢病收集
|
||||
*/
|
||||
export async function updateYxeChronicDiseaseCollection(
|
||||
data: YxeChronicDiseaseCollection
|
||||
) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除慢病收集
|
||||
*/
|
||||
export async function removeYxeChronicDiseaseCollection(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除慢病收集
|
||||
*/
|
||||
export async function removeBatchYxeChronicDiseaseCollection(
|
||||
data: (number | undefined)[]
|
||||
) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询慢病收集
|
||||
*/
|
||||
export async function getYxeChronicDiseaseCollection(id: number) {
|
||||
const res = await request.get<ApiResult<YxeChronicDiseaseCollection>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出慢病收集
|
||||
*/
|
||||
export async function exportYxeChronicDiseaseCollection(
|
||||
params?: YxeChronicDiseaseCollectionParam
|
||||
) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-collection/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 慢病收集
|
||||
*/
|
||||
export interface YxeChronicDiseaseCollection {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
name?: string;
|
||||
//
|
||||
gender?: string;
|
||||
//
|
||||
phone?: string;
|
||||
//
|
||||
birthday?: string;
|
||||
// 是否高血压
|
||||
highBloodPressure?: number;
|
||||
// 血压值(如:120/80)
|
||||
highBloodPressureValue?: string;
|
||||
// 收缩压
|
||||
systolicPressure?: string;
|
||||
// 舒张压
|
||||
diastolicPressure?: string;
|
||||
// 是否高血糖
|
||||
highBloodSugar?: number;
|
||||
// 空腹血糖
|
||||
fastingBloodGlucose?: string;
|
||||
// 餐后2小时血糖
|
||||
postprandial2hBloodGlucose?: string;
|
||||
// 糖化血红蛋白
|
||||
hba1c?: string;
|
||||
// 是否高血脂
|
||||
hyperlipidemia?: number;
|
||||
// 总胆固醇
|
||||
totalCholesterol?: string;
|
||||
// 甘油三酯
|
||||
triglyceride?: string;
|
||||
// 低密度脂蛋白
|
||||
ldlC?: string;
|
||||
// 高密度脂蛋白
|
||||
hdlC?: string;
|
||||
// 血红蛋白数值
|
||||
hemoglobinValue?: string;
|
||||
// 贫血程度
|
||||
anemiaLevel?: string;
|
||||
// 地贫类型
|
||||
thalassemiaType?: string;
|
||||
// 病史
|
||||
medicalHistory?: string;
|
||||
// 用药史
|
||||
medicationHistory?: string;
|
||||
// 用药追踪
|
||||
tracking?: string;
|
||||
//
|
||||
userId?: number;
|
||||
// 连锁名称
|
||||
chainName?: string;
|
||||
// 门店名称
|
||||
merchantName?: string;
|
||||
// 状态, 0待审核 1通过 2驳回
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 慢病收集搜索条件
|
||||
*/
|
||||
export interface YxeChronicDiseaseCollectionParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
name?: string;
|
||||
phone?: string;
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type {
|
||||
YxeChronicDiseaseProduct,
|
||||
YxeChronicDiseaseProductParam
|
||||
} from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询慢病产品
|
||||
*/
|
||||
export async function pageYxeChronicDiseaseProduct(
|
||||
params: YxeChronicDiseaseProductParam
|
||||
) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeChronicDiseaseProduct>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询慢病产品列表
|
||||
*/
|
||||
export async function listYxeChronicDiseaseProduct(
|
||||
params?: YxeChronicDiseaseProductParam
|
||||
) {
|
||||
const res = await request.get<ApiResult<YxeChronicDiseaseProduct[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加慢病产品
|
||||
*/
|
||||
export async function addYxeChronicDiseaseProduct(
|
||||
data: YxeChronicDiseaseProduct
|
||||
) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载导入模板
|
||||
*/
|
||||
export async function downloadTemplateYxeChronicDiseaseProduct() {
|
||||
const res = await request.get<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product/import-template'
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改慢病产品
|
||||
*/
|
||||
export async function updateYxeChronicDiseaseProduct(
|
||||
data: YxeChronicDiseaseProduct
|
||||
) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除慢病产品
|
||||
*/
|
||||
export async function removeYxeChronicDiseaseProduct(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除慢病产品
|
||||
*/
|
||||
export async function removeBatchYxeChronicDiseaseProduct(
|
||||
data: (number | undefined)[]
|
||||
) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询慢病产品
|
||||
*/
|
||||
export async function getYxeChronicDiseaseProduct(id: number) {
|
||||
const res = await request.get<ApiResult<YxeChronicDiseaseProduct>>(
|
||||
MODULES_API_URL + '/yxe/yxe-chronic-disease-product/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 慢病产品
|
||||
*/
|
||||
export interface YxeChronicDiseaseProduct {
|
||||
id?: number;
|
||||
// 连锁
|
||||
chain?: string;
|
||||
// 产品名
|
||||
productName?: string;
|
||||
// 规格粒/片
|
||||
specCount?: number;
|
||||
// 说明书每天使用/次
|
||||
dailyUsageCount?: number;
|
||||
// 每次几粒/片
|
||||
dosagePerTime?: number;
|
||||
// 1盒服用几天
|
||||
boxUsageDays?: number;
|
||||
// 状态, 0禁用, 1启用
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 创建时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeChronicDiseaseProductParam extends PageParam {
|
||||
id?: number;
|
||||
chain?: string;
|
||||
productName?: string;
|
||||
status?: number;
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeGoods, YxeGoodsCoverZipResult, YxeGoodsParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeGoods(params: YxeGoodsParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeGoods>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeGoods(params?: YxeGoodsParam) {
|
||||
const res = await request.get<ApiResult<YxeGoods[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeGoods(data: YxeGoods) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传商品封面 zip
|
||||
*/
|
||||
export async function uploadYxeGoodsCoverZip(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const res = await request.post<ApiResult<YxeGoodsCoverZipResult>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods/cover/zip',
|
||||
formData
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return (
|
||||
res.data.data ?? {
|
||||
totalFiles: 0,
|
||||
updatedCount: 0,
|
||||
skippedCount: 0,
|
||||
notFoundTitles: []
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeGoods(data: YxeGoods) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeGoods(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeGoods(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeGoods(id: number) {
|
||||
const res = await request.get<ApiResult<YxeGoods>>(
|
||||
MODULES_API_URL + '/yxe/yxe-goods/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { PageParam } from "@/api";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeGoods {
|
||||
//
|
||||
id?: number;
|
||||
// 分类
|
||||
cate?: string;
|
||||
chain?: string;
|
||||
// 编号
|
||||
code?: string;
|
||||
// 品名
|
||||
title?: string;
|
||||
// 规格
|
||||
spec?: string;
|
||||
// 厂家
|
||||
maker?: string;
|
||||
// 厂家简称
|
||||
makerAbb?: string;
|
||||
//
|
||||
cover?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
//
|
||||
startTime?: string;
|
||||
//
|
||||
endTime?: string;
|
||||
// 奖励金额
|
||||
extra_income?: string;
|
||||
// 额外奖励
|
||||
extraIncome?: string;
|
||||
//
|
||||
extraStartTime?: string;
|
||||
//
|
||||
extraEndTime?: string;
|
||||
}
|
||||
|
||||
export interface YxeGoodsCoverZipResult {
|
||||
totalFiles: number;
|
||||
updatedCount: number;
|
||||
skippedCount: number;
|
||||
notFoundTitles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeGoodsParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type {
|
||||
YxeProduct,
|
||||
YxeProductCoverZipResult,
|
||||
YxeProductParam
|
||||
} from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeProduct(params: YxeProductParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeProduct>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeProduct(params?: YxeProductParam) {
|
||||
const res = await request.get<ApiResult<YxeProduct[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询分组列表
|
||||
*/
|
||||
export async function listYxeProductGroup(params?: YxeProductParam) {
|
||||
const res = await request.get<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product/group',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeProduct(data: YxeProduct) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传商品封面 zip
|
||||
*/
|
||||
export async function uploadYxeProductCoverZip(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const res = await request.post<ApiResult<YxeProductCoverZipResult>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product/cover/zip',
|
||||
formData
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return (
|
||||
res.data.data ?? {
|
||||
totalFiles: 0,
|
||||
updatedCount: 0,
|
||||
skippedCount: 0,
|
||||
notFoundTitles: []
|
||||
}
|
||||
);
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeProduct(data: YxeProduct) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeProduct(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeProduct(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeProduct(id: number) {
|
||||
const res = await request.get<ApiResult<YxeProduct>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import type { PageParam } from "@/api";
|
||||
|
||||
export interface YxeProductExtraIncomeItem {
|
||||
// 额外奖励
|
||||
extraIncome?: string;
|
||||
// 额外奖励开始时间
|
||||
extraStartTime?: string;
|
||||
// 额外奖励结束时间
|
||||
extraEndTime?: string;
|
||||
// 兼容后端可能返回的字段
|
||||
income?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeProduct {
|
||||
//
|
||||
id?: number;
|
||||
merchantId?: number;
|
||||
type?: number;
|
||||
// 编号
|
||||
code?: string;
|
||||
// 品名
|
||||
title?: string;
|
||||
// 规格
|
||||
spec?: string;
|
||||
// 厂家
|
||||
maker?: string;
|
||||
cover?: string;
|
||||
merchantCode?: string;
|
||||
// 厂家简称
|
||||
makerAbb?: string;
|
||||
cate?: string;
|
||||
//
|
||||
income?: string;
|
||||
taskIncome?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 所属连锁
|
||||
chain?: string;
|
||||
// 分享奖励活动开始时间
|
||||
startTime?: string;
|
||||
// 分享奖励活动结束时间
|
||||
endTime?: string;
|
||||
// 额外奖励
|
||||
extraIncome?: string;
|
||||
// 额外奖励开始时间
|
||||
extraStartTime?: string;
|
||||
// 额外奖励结束时间
|
||||
extraEndTime?: string;
|
||||
// 额外奖励列表
|
||||
extraIncomeList?: YxeProductExtraIncomeItem[];
|
||||
}
|
||||
|
||||
export interface YxeProductCoverZipResult {
|
||||
totalFiles: number;
|
||||
updatedCount: number;
|
||||
skippedCount: number;
|
||||
notFoundTitles: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeProductParam extends PageParam {
|
||||
id?: number;
|
||||
type?: number;
|
||||
keywords?: string;
|
||||
merchantId?: number;
|
||||
chain?: string;
|
||||
cate?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeProductExtraIncome, YxeProductExtraIncomeParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeProductExtraIncome(params: YxeProductExtraIncomeParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeProductExtraIncome>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeProductExtraIncome(params?: YxeProductExtraIncomeParam) {
|
||||
const res = await request.get<ApiResult<YxeProductExtraIncome[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeProductExtraIncome(data: YxeProductExtraIncome) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeProductExtraIncome(data: YxeProductExtraIncome) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeProductExtraIncome(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeProductExtraIncome(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeProductExtraIncome(id: number) {
|
||||
const res = await request.get<ApiResult<YxeProductExtraIncome>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-extra-income/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeProductExtraIncome {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
productId?: number;
|
||||
//
|
||||
startTime?: string;
|
||||
//
|
||||
endTime?: string;
|
||||
//
|
||||
income?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeProductExtraIncomeParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeProductMerchantExtraIncome, YxeProductMerchantExtraIncomeParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeProductMerchantExtraIncome(params: YxeProductMerchantExtraIncomeParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeProductMerchantExtraIncome>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeProductMerchantExtraIncome(params?: YxeProductMerchantExtraIncomeParam) {
|
||||
const res = await request.get<ApiResult<YxeProductMerchantExtraIncome[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeProductMerchantExtraIncome(data: YxeProductMerchantExtraIncome) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeProductMerchantExtraIncome(data: YxeProductMerchantExtraIncome) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeProductMerchantExtraIncome(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeProductMerchantExtraIncome(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeProductMerchantExtraIncome(id: number) {
|
||||
const res = await request.get<ApiResult<YxeProductMerchantExtraIncome>>(
|
||||
MODULES_API_URL + '/yxe/yxe-product-merchant-extra-income/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import { Merchant } from "@/api/shop/merchant/model";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeProductMerchantExtraIncome {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
productId?: number;
|
||||
//
|
||||
merchantId?: number;
|
||||
//
|
||||
startTime?: string;
|
||||
//
|
||||
endTime?: string;
|
||||
//
|
||||
income?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
merchant?: Merchant;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeProductMerchantExtraIncomeParam extends PageParam {
|
||||
id?: number;
|
||||
productId?: number;
|
||||
merchantId?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeReimburse, YxeReimburseParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询报销
|
||||
*/
|
||||
export async function pageYxeReimburse(params: YxeReimburseParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeReimburse>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询报销列表
|
||||
*/
|
||||
export async function listYxeReimburse(params?: YxeReimburseParam) {
|
||||
const res = await request.get<ApiResult<YxeReimburse[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加报销
|
||||
*/
|
||||
export async function addYxeReimburse(data: YxeReimburse) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改报销
|
||||
*/
|
||||
export async function updateYxeReimburse(data: YxeReimburse) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除报销
|
||||
*/
|
||||
export async function removeYxeReimburse(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除报销
|
||||
*/
|
||||
export async function removeBatchYxeReimburse(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询报销
|
||||
*/
|
||||
export async function getYxeReimburse(id: number) {
|
||||
const res = await request.get<ApiResult<YxeReimburse>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出报销
|
||||
*/
|
||||
export async function exportYxeReimburse(params?: YxeReimburseParam) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-reimburse/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
/**
|
||||
* 报销
|
||||
*/
|
||||
export interface YxeReimburse {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
userId?: number;
|
||||
//
|
||||
startDate?: string;
|
||||
//
|
||||
endDate?: string;
|
||||
// 交通费
|
||||
transportCosts?: string;
|
||||
// 住宿费
|
||||
liveCosts?: string;
|
||||
// 其他费用
|
||||
otherCosts?: string;
|
||||
// 凭证
|
||||
images?: string;
|
||||
// 出发省
|
||||
fromProvince?: string;
|
||||
// 出发市
|
||||
fromCity?: string;
|
||||
// 出发区
|
||||
fromRegion?: string;
|
||||
// 目的省
|
||||
toProvince?: string;
|
||||
// 目的市
|
||||
toCity?: string;
|
||||
// 目的区
|
||||
toRegion?: string;
|
||||
// 备注
|
||||
remark?: string;
|
||||
// 状态, 0待审核 1通过 2驳回
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 提交人
|
||||
user?: User;
|
||||
}
|
||||
|
||||
/**
|
||||
* 报销搜索条件
|
||||
*/
|
||||
export interface YxeReimburseParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
userId?: number;
|
||||
status?: number | number[];
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
startDateStart?: string;
|
||||
endDateEnd?: string;
|
||||
fromAddress?: string;
|
||||
toAddress?: string;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeSelfReport, YxeSelfReportParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
export async function pageYxeSelfReport(params: YxeSelfReportParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeSelfReport>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function listYxeSelfReport(params?: YxeSelfReportParam) {
|
||||
const res = await request.get<ApiResult<YxeSelfReport[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function addYxeSelfReport(data: YxeSelfReport) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function updateYxeSelfReport(data: YxeSelfReport) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function removeYxeSelfReport(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function removeBatchYxeSelfReport(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function getYxeSelfReport(id: number) {
|
||||
const res = await request.get<ApiResult<YxeSelfReport>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function exportYxeSelfReport(params?: YxeSelfReportParam) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-self-report/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
export interface YxeSelfReport {
|
||||
id?: number;
|
||||
userId?: number;
|
||||
merchantId?: number;
|
||||
merchantName?: string;
|
||||
chainId?: string;
|
||||
chainName?: string;
|
||||
date?: string;
|
||||
activityTime?: string;
|
||||
image?: string;
|
||||
clockPhotos?: string;
|
||||
boothPhotos?: string;
|
||||
salesGroups?: string;
|
||||
receiptPhoto?: string;
|
||||
summary?: string;
|
||||
rating?: string | number;
|
||||
lat?: string;
|
||||
lng?: string;
|
||||
location?: string;
|
||||
reward?: number | string;
|
||||
sortNumber?: number;
|
||||
comments?: string;
|
||||
status?: number;
|
||||
rejectReason?: string;
|
||||
deleted?: number;
|
||||
tenantId?: number;
|
||||
createTime?: string;
|
||||
updateTime?: string;
|
||||
user?: User;
|
||||
realName?: string;
|
||||
userGroup?: string;
|
||||
nickname?: string;
|
||||
phone?: string;
|
||||
merchant?: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
chain?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
region?: string;
|
||||
address?: string;
|
||||
lng?: string | number;
|
||||
lat?: string | number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface YxeSelfReportParam extends PageParam {
|
||||
id?: number;
|
||||
idList?: string;
|
||||
keywords?: string;
|
||||
realName?: string;
|
||||
userGroup?: string;
|
||||
chainName?: string;
|
||||
merchantName?: string;
|
||||
activityTime?: string;
|
||||
status?: number | number[];
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeService, YxeServiceParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询客服
|
||||
*/
|
||||
export async function pageYxeService(params: YxeServiceParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeService>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客服列表
|
||||
*/
|
||||
export async function listYxeService(params?: YxeServiceParam) {
|
||||
const res = await request.get<ApiResult<YxeService[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客服
|
||||
*/
|
||||
export async function addYxeService(data: YxeService) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改客服
|
||||
*/
|
||||
export async function updateYxeService(data: YxeService) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客服
|
||||
*/
|
||||
export async function removeYxeService(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除客服
|
||||
*/
|
||||
export async function removeBatchYxeService(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询客服
|
||||
*/
|
||||
export async function getYxeService(id: number) {
|
||||
const res = await request.get<ApiResult<YxeService>>(
|
||||
MODULES_API_URL + '/yxe/yxe-service/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
* 客服
|
||||
*/
|
||||
export interface YxeService {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
type?: string;
|
||||
//
|
||||
title?: string;
|
||||
//
|
||||
content?: string;
|
||||
// 状态, 0上架, 1下架
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客服搜索条件
|
||||
*/
|
||||
export interface YxeServiceParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeShare, YxeShareParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeShare(params: YxeShareParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeShare>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeShare(params?: YxeShareParam) {
|
||||
const res = await request.get<ApiResult<YxeShare[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeShare(data: YxeShare) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeShare(data: YxeShare) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeShare(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeShare(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeShare(id: number) {
|
||||
const res = await request.get<ApiResult<YxeShare>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
*/
|
||||
export async function exportYxeShare(params?: YxeShareParam) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import type { YxeShareDetail } from '@/api/yxe/yxeShareDetail/model';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeShare {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
userId?: number;
|
||||
//
|
||||
merchantId?: number;
|
||||
//
|
||||
merchantName?: string;
|
||||
//
|
||||
orderNo?: string;
|
||||
//
|
||||
amount?: string;
|
||||
// 小票图片
|
||||
image?: string;
|
||||
// 溯源码
|
||||
sourceCode?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 所属连锁
|
||||
chain?: string;
|
||||
// 提交人员
|
||||
user?: User;
|
||||
// 提交人姓名
|
||||
realName?: string;
|
||||
// 提交人昵称
|
||||
nickname?: string;
|
||||
// 提交人手机号
|
||||
phone?: string;
|
||||
merchant?: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
chain?: string;
|
||||
};
|
||||
// 详情列表
|
||||
detailList?: YxeShareDetail[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeShareParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
chain?: string;
|
||||
merchantId?: number;
|
||||
status?: number | number[];
|
||||
userId?: number;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeShareDetail, YxeShareDetailParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeShareDetail(params: YxeShareDetailParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeShareDetail>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeShareDetail(params?: YxeShareDetailParam) {
|
||||
const res = await request.get<ApiResult<YxeShareDetail[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeShareDetail(data: YxeShareDetail) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeShareDetail(data: YxeShareDetail) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeShareDetail(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeShareDetail(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeShareDetail(id: number) {
|
||||
const res = await request.get<ApiResult<YxeShareDetail>>(
|
||||
MODULES_API_URL + '/yxe/yxe-share-detail/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeShareDetail {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
shareId?: number;
|
||||
//
|
||||
goodsName?: string;
|
||||
//
|
||||
spec?: string;
|
||||
//
|
||||
num?: string;
|
||||
// 溯源码
|
||||
sourceCode?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeShareDetailParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeTask, YxeTaskParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeTask(params: YxeTaskParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeTask>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeTask(params?: YxeTaskParam) {
|
||||
const res = await request.get<ApiResult<YxeTask[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeTask(data: YxeTask) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeTask(data: YxeTask) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeTask(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeTask(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeTask(id: number) {
|
||||
const res = await request.get<ApiResult<YxeTask>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
export async function exportYxeTask(params?: YxeTaskParam) {
|
||||
const res = await request.post<ApiResult<any>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeTask {
|
||||
//
|
||||
id?: number;
|
||||
// 分类id
|
||||
cateId?: number;
|
||||
// 名称
|
||||
title?: string;
|
||||
// 需巡店数量
|
||||
storeNum?: number;
|
||||
// 金额
|
||||
reward?: string;
|
||||
extraReward?: string;
|
||||
// 店铺id
|
||||
merchantId?: number;
|
||||
// 数量
|
||||
num?: number;
|
||||
// 详情
|
||||
detail?: string;
|
||||
policy?: string;
|
||||
// 日期
|
||||
date?: string;
|
||||
taskStartTime?: string;
|
||||
taskEndTime?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 指定人员ID
|
||||
appointUserId?: number;
|
||||
userGroup?: string;
|
||||
// 指定人员手机号(提交字段)
|
||||
phone?: string;
|
||||
// 指定人员姓名
|
||||
realName?: string;
|
||||
// 指定人员手机号(前端表单字段)
|
||||
userPhone?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0上架, 1下架
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 关联产品
|
||||
productList?: Array<{
|
||||
goodsId?: number;
|
||||
productId?: number;
|
||||
num?: number;
|
||||
amount?: string;
|
||||
income?: string;
|
||||
reward?: string;
|
||||
goods?: {
|
||||
title?: string;
|
||||
extra_income?: string;
|
||||
extraIncome?: string;
|
||||
};
|
||||
product?: {
|
||||
title?: string;
|
||||
income?: string;
|
||||
};
|
||||
}>;
|
||||
// 关联产品(接口字段拼写)
|
||||
procutList?: Array<{
|
||||
goodsId?: number;
|
||||
productId?: number;
|
||||
num?: number;
|
||||
amount?: string;
|
||||
income?: string;
|
||||
reward?: string;
|
||||
goods?: {
|
||||
title?: string;
|
||||
extra_income?: string;
|
||||
extraIncome?: string;
|
||||
};
|
||||
product?: {
|
||||
title?: string;
|
||||
income?: string;
|
||||
};
|
||||
}>;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
chain?: string;
|
||||
appointUser?: User;
|
||||
merchant?: {
|
||||
name?: string;
|
||||
chain?: string;
|
||||
};
|
||||
// 指定人员
|
||||
user?: {
|
||||
realName?: string;
|
||||
nickname?: string;
|
||||
phone?: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeTaskParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
cateIdList?: string;
|
||||
chain?: string;
|
||||
merchantId?: number;
|
||||
taskStartTime?: string;
|
||||
taskStartTimeStart?: string;
|
||||
taskStartTimeEnd?: string;
|
||||
appointUserId?: number;
|
||||
userGroup?: string;
|
||||
userPhone?: string;
|
||||
status?: number | number[];
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeTaskCate, YxeTaskCateParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeTaskCate(params: YxeTaskCateParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeTaskCate>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeTaskCate(params?: YxeTaskCateParam) {
|
||||
const res = await request.get<ApiResult<YxeTaskCate[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeTaskCate(data: YxeTaskCate) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeTaskCate(data: YxeTaskCate) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeTaskCate(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeTaskCate(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeTaskCate(id: number) {
|
||||
const res = await request.get<ApiResult<YxeTaskCate>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-cate/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeTaskCate {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
title?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0待审核 1通过 2驳回
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeTaskCateParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeTaskProduct, YxeTaskProductParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*/
|
||||
export async function pageYxeTaskProduct(params: YxeTaskProductParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeTaskProduct>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*/
|
||||
export async function listYxeTaskProduct(params?: YxeTaskProductParam) {
|
||||
const res = await request.get<ApiResult<YxeTaskProduct[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加
|
||||
*/
|
||||
export async function addYxeTaskProduct(data: YxeTaskProduct) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
*/
|
||||
export async function updateYxeTaskProduct(data: YxeTaskProduct) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*/
|
||||
export async function removeYxeTaskProduct(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export async function removeBatchYxeTaskProduct(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询
|
||||
*/
|
||||
export async function getYxeTaskProduct(id: number) {
|
||||
const res = await request.get<ApiResult<YxeTaskProduct>>(
|
||||
MODULES_API_URL + '/yxe/yxe-task-product/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { PageParam } from '@/api';
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface YxeTaskProduct {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
taskId?: number;
|
||||
//
|
||||
goodsId?: number;
|
||||
num?: number;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 状态, 0正常, 1冻结
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索条件
|
||||
*/
|
||||
export interface YxeTaskProductParam extends PageParam {
|
||||
id?: number;
|
||||
keywords?: string;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeUserTask, YxeUserTaskParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询用户任务
|
||||
*/
|
||||
export async function pageYxeUserTask(params: YxeUserTaskParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeUserTask>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户任务列表
|
||||
*/
|
||||
export async function listYxeUserTask(params?: YxeUserTaskParam) {
|
||||
const res = await request.get<ApiResult<YxeUserTask[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加用户任务
|
||||
*/
|
||||
export async function addYxeUserTask(data: YxeUserTask) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户任务
|
||||
*/
|
||||
export async function updateYxeUserTask(data: YxeUserTask) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除用户任务
|
||||
*/
|
||||
export async function removeYxeUserTask(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除用户任务
|
||||
*/
|
||||
export async function removeBatchYxeUserTask(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询用户任务
|
||||
*/
|
||||
export async function getYxeUserTask(id: number) {
|
||||
const res = await request.get<ApiResult<YxeUserTask>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出用户任务
|
||||
*/
|
||||
export async function exportYxeUserTask(params?: YxeUserTaskParam) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-user-task/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
/**
|
||||
* 用户任务
|
||||
*/
|
||||
export interface YxeUserTask {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
taskId?: number;
|
||||
//
|
||||
image?: string;
|
||||
//
|
||||
video?: string;
|
||||
// 用户ID
|
||||
userId?: number;
|
||||
userGroup?: string;
|
||||
//
|
||||
lat?: string;
|
||||
//
|
||||
lng?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0待审核 1通过 2驳回
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
inClockTime?: string;
|
||||
outClockTime?: string;
|
||||
submitTime?: string;
|
||||
summary?: string;
|
||||
rating?: number | string;
|
||||
clockPhotos?: string;
|
||||
boothPhotos?: string;
|
||||
salesGroups?: string;
|
||||
receiptPhoto?: string;
|
||||
income?: number | string;
|
||||
user?: User;
|
||||
task?: {
|
||||
id?: number;
|
||||
title?: string;
|
||||
reward?: number | string;
|
||||
income?: number | string;
|
||||
extraReward?: number | string;
|
||||
chain?: string;
|
||||
merchantId?: number;
|
||||
taskStartTime?: string;
|
||||
taskEndTime?: string;
|
||||
appointUserId?: number;
|
||||
merchant?: {
|
||||
id?: number;
|
||||
name?: string;
|
||||
chain?: string;
|
||||
province?: string;
|
||||
city?: string;
|
||||
region?: string;
|
||||
address?: string;
|
||||
lngAndLat?: string;
|
||||
lng?: string | number;
|
||||
lat?: string | number;
|
||||
};
|
||||
appointUser?: User;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户任务搜索条件
|
||||
*/
|
||||
export interface YxeUserTaskParam extends PageParam {
|
||||
id?: number;
|
||||
idList?: string;
|
||||
keywords?: string;
|
||||
cateIdList?: string;
|
||||
userId?: number;
|
||||
userGroup?: string;
|
||||
chain?: string;
|
||||
merchantId?: number;
|
||||
taskStartTimeStart?: string;
|
||||
taskStartTimeEnd?: string;
|
||||
status?: number | number[];
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import request from '@/utils/request';
|
||||
import type { ApiResult, PageResult } from '@/api';
|
||||
import type { YxeVisitList, YxeVisitListParam } from './model';
|
||||
import { MODULES_API_URL } from '@/config/setting';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
|
||||
/**
|
||||
* 分页查询访问列表
|
||||
*/
|
||||
export async function pageYxeVisitList(params: YxeVisitListParam) {
|
||||
const res = await request.get<ApiResult<PageResult<YxeVisitList>>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list/page',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询访问列表列表
|
||||
*/
|
||||
export async function listYxeVisitList(params?: YxeVisitListParam) {
|
||||
const res = await request.get<ApiResult<YxeVisitList[]>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list',
|
||||
{
|
||||
params
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加访问列表
|
||||
*/
|
||||
export async function addYxeVisitList(data: YxeVisitList) {
|
||||
const res = await request.post<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改访问列表
|
||||
*/
|
||||
export async function updateYxeVisitList(data: YxeVisitList) {
|
||||
const res = await request.put<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list',
|
||||
data
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除访问列表
|
||||
*/
|
||||
export async function removeYxeVisitList(id?: number) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list/' + id
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除访问列表
|
||||
*/
|
||||
export async function removeBatchYxeVisitList(data: (number | undefined)[]) {
|
||||
const res = await request.delete<ApiResult<unknown>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list/batch',
|
||||
{
|
||||
data
|
||||
}
|
||||
);
|
||||
if (res.data.code === 0) {
|
||||
return res.data.message;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据id查询访问列表
|
||||
*/
|
||||
export async function getYxeVisitList(id: number) {
|
||||
const res = await request.get<ApiResult<YxeVisitList>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list/' + id
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出访问列表
|
||||
*/
|
||||
export async function exportYxeVisitList(params?: YxeVisitListParam) {
|
||||
const res = await request.post<ApiResult<FileRecord>>(
|
||||
MODULES_API_URL + '/yxe/yxe-visit-list/export',
|
||||
params
|
||||
);
|
||||
if (res.data.code === 0 && res.data.data) {
|
||||
return res.data.data;
|
||||
}
|
||||
return Promise.reject(new Error(res.data.message));
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { PageParam } from '@/api';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
/**
|
||||
* 访问列表
|
||||
*/
|
||||
export interface YxeVisitList {
|
||||
//
|
||||
id?: number;
|
||||
//
|
||||
userId?: number;
|
||||
//
|
||||
lat?: string;
|
||||
//
|
||||
lng?: string;
|
||||
// 位置
|
||||
location?: string;
|
||||
// 拜访人员
|
||||
name?: string;
|
||||
// 沟通内容
|
||||
content?: string;
|
||||
// 小结
|
||||
summary?: string;
|
||||
//
|
||||
photos?: string;
|
||||
// 排序(数字越小越靠前)
|
||||
sortNumber?: number;
|
||||
// 备注
|
||||
comments?: string;
|
||||
// 状态, 0上架, 1下架
|
||||
status?: number;
|
||||
// 是否删除, 0否, 1是
|
||||
deleted?: number;
|
||||
// 租户id
|
||||
tenantId?: number;
|
||||
// 注册时间
|
||||
createTime?: string;
|
||||
// 修改时间
|
||||
updateTime?: string;
|
||||
// 提交人
|
||||
user?: User;
|
||||
// 提交人姓名
|
||||
realName?: string;
|
||||
userGroup?: string;
|
||||
// 提交人昵称
|
||||
nickname?: string;
|
||||
// 提交人手机号
|
||||
phone?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问列表搜索条件
|
||||
*/
|
||||
export interface YxeVisitListParam extends PageParam {
|
||||
id?: number;
|
||||
idList?: string;
|
||||
keywords?: string;
|
||||
realName?: string;
|
||||
userGroup?: string;
|
||||
status?: number | number[];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
type ExportFile = {
|
||||
url?: string;
|
||||
downloadUrl?: string;
|
||||
};
|
||||
|
||||
export function openExportFile(file?: ExportFile) {
|
||||
const target = file?.downloadUrl || file?.url;
|
||||
if (!target) {
|
||||
throw new Error('未获取到导出文件地址');
|
||||
}
|
||||
window.open(target);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="articleId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.articleId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0正常, 1冻结" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="1">显示</a-radio>
|
||||
<a-radio :value="0">隐藏</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>
|
||||
</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 { addArticleCollect, updateArticleCollect } from '@/api/cms/articleCollect';
|
||||
import { ArticleCollect } from '@/api/cms/articleCollect/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: ArticleCollect | 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 form = reactive<ArticleCollect>({
|
||||
id: undefined,
|
||||
articleId: undefined,
|
||||
userId: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateArticleCollect : addArticleCollect;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -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,245 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ArticleCollectEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 ArticleCollectEdit from './components/articleCollectEdit.vue';
|
||||
import { pageArticleCollect, removeArticleCollect, removeBatchArticleCollect } from '@/api/cms/articleCollect';
|
||||
import type { ArticleCollect, ArticleCollectParam } from '@/api/cms/articleCollect/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ArticleCollect[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ArticleCollect | 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 pageArticleCollect({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'articleId',
|
||||
key: 'articleId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0正常, 1冻结',
|
||||
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: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ArticleCollectParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ArticleCollect) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ArticleCollect) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeArticleCollect(row.id)
|
||||
.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);
|
||||
removeBatchArticleCollect(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ArticleCollect) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ArticleCollect'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,173 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="articleId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.articleId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="content">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.content"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0正常, 1冻结" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="1">显示</a-radio>
|
||||
<a-radio :value="0">隐藏</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>
|
||||
</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 { addArticleComment, updateArticleComment } from '@/api/cms/articleComment';
|
||||
import { ArticleComment } from '@/api/cms/articleComment/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: ArticleComment | 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 form = reactive<ArticleComment>({
|
||||
id: undefined,
|
||||
articleId: undefined,
|
||||
userId: undefined,
|
||||
content: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateArticleComment : addArticleComment;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -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,251 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<ArticleCommentEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 ArticleCommentEdit from './components/articleCommentEdit.vue';
|
||||
import { pageArticleComment, removeArticleComment, removeBatchArticleComment } from '@/api/cms/articleComment';
|
||||
import type { ArticleComment, ArticleCommentParam } from '@/api/cms/articleComment/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<ArticleComment[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<ArticleComment | 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 pageArticleComment({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'articleId',
|
||||
key: 'articleId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'userId',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'content',
|
||||
key: 'content',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0正常, 1冻结',
|
||||
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: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ArticleCommentParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ArticleComment) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: ArticleComment) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeArticleComment(row.id)
|
||||
.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);
|
||||
removeBatchArticleComment(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ArticleComment) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ArticleComment'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,238 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="orderNo">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.orderNo"
|
||||
/>
|
||||
</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="提现金额" name="money">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入提现金额"
|
||||
v-model:value="form.money"
|
||||
/>
|
||||
</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-form-item>
|
||||
<a-form-item label="支付宝姓名" name="alipayName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入支付宝姓名"
|
||||
v-model:value="form.alipayName"
|
||||
/>
|
||||
</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>
|
||||
</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 { addDealerWithdraw, updateDealerWithdraw } from '@/api/shop/dealerWithdraw';
|
||||
import { DealerWithdraw } from '@/api/shop/dealerWithdraw/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: DealerWithdraw | 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 form = reactive<DealerWithdraw>({
|
||||
id: undefined,
|
||||
orderNo: 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,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateDealerWithdraw : addDealerWithdraw;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,196 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="exportData">
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
<a-select
|
||||
v-model:value="where.userId"
|
||||
show-search
|
||||
allow-clear
|
||||
:filter-option="false"
|
||||
optionFilterProp="label"
|
||||
:loading="userLoading"
|
||||
:options="userOptions"
|
||||
placeholder="请选择用户"
|
||||
style="width: 220px"
|
||||
@search="onSearchUser"
|
||||
@focus="loadUserOptions()"
|
||||
@change="search"
|
||||
/>
|
||||
<a-select
|
||||
v-model:value="where.applyStatus"
|
||||
allow-clear
|
||||
placeholder="请选择申请状态"
|
||||
style="width: 180px"
|
||||
>
|
||||
<a-select-option :value="0">待审核</a-select-option>
|
||||
<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-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { SelectProps } from 'ant-design-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import type { User as SystemUser } from '@/api/system/user/model';
|
||||
import type { DealerWithdrawParam } from '@/api/shop/dealerWithdraw/model';
|
||||
|
||||
defineProps<{
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: DealerWithdrawParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'export'): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<DealerWithdrawParam>({
|
||||
userId: undefined,
|
||||
applyStatus: undefined
|
||||
});
|
||||
|
||||
const userOptions = ref<SelectProps['options']>([]);
|
||||
const userLoading = ref(false);
|
||||
let userSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let userSearchSeed = 0;
|
||||
|
||||
const buildUserOption = (item: SystemUser) => {
|
||||
if (item.userId == null) {
|
||||
return null;
|
||||
}
|
||||
const phone = String(item.phone ?? '').trim();
|
||||
const name = String(item.realName ?? item.nickname ?? '').trim();
|
||||
const contact = phone || `ID:${item.userId}`;
|
||||
return {
|
||||
value: item.userId,
|
||||
label: name ? `${name} (${contact})` : contact
|
||||
};
|
||||
};
|
||||
|
||||
const ensureCurrentUserOption = (userId?: number) => {
|
||||
if (userId == null) {
|
||||
return;
|
||||
}
|
||||
const exists = userOptions.value?.some((item) => item?.value === userId);
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
userOptions.value = [
|
||||
{
|
||||
value: userId,
|
||||
label: `ID:${userId}`
|
||||
},
|
||||
...(userOptions.value || [])
|
||||
];
|
||||
};
|
||||
|
||||
const loadUserOptions = (keyword?: string) => {
|
||||
const currentSeed = ++userSearchSeed;
|
||||
const trimmed = String(keyword ?? '').trim();
|
||||
const previousSelectedOption =
|
||||
where.userId == null
|
||||
? undefined
|
||||
: (userOptions.value || []).find(
|
||||
(item) => item?.value === where.userId
|
||||
);
|
||||
userLoading.value = true;
|
||||
const requests = trimmed
|
||||
? [
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
phone: trimmed
|
||||
}),
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
realName: trimmed
|
||||
})
|
||||
]
|
||||
: [
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20
|
||||
})
|
||||
];
|
||||
Promise.all(requests)
|
||||
.then((results) => {
|
||||
if (currentSeed !== userSearchSeed) {
|
||||
return;
|
||||
}
|
||||
const map = new Map<number, { value: number; label: string }>();
|
||||
results.forEach((res) => {
|
||||
(res?.list || []).forEach((item) => {
|
||||
const option = buildUserOption(item as SystemUser);
|
||||
if (option && !map.has(option.value)) {
|
||||
map.set(option.value, option);
|
||||
}
|
||||
});
|
||||
});
|
||||
userOptions.value = Array.from(map.values());
|
||||
if (
|
||||
previousSelectedOption &&
|
||||
where.userId != null &&
|
||||
!map.has(where.userId)
|
||||
) {
|
||||
userOptions.value = [previousSelectedOption, ...userOptions.value];
|
||||
}
|
||||
ensureCurrentUserOption(where.userId);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (currentSeed !== userSearchSeed) {
|
||||
return;
|
||||
}
|
||||
message.error(e.message ?? '查询用户失败');
|
||||
})
|
||||
.finally(() => {
|
||||
if (currentSeed === userSearchSeed) {
|
||||
userLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSearchUser = (value: string) => {
|
||||
if (userSearchTimer) {
|
||||
clearTimeout(userSearchTimer);
|
||||
}
|
||||
userSearchTimer = setTimeout(() => {
|
||||
loadUserOptions(value);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
emit('search', { ...where });
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
emit('export');
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (userSearchTimer) {
|
||||
clearTimeout(userSearchTimer);
|
||||
userSearchTimer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,437 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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"
|
||||
@export="exportData"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'userId'">
|
||||
<p>{{ record.user.nickname }}</p>
|
||||
<p>{{ record.user.phone }}</p>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<template v-if="record.applyStatus === 0">
|
||||
<a @click="approve(record)">通过</a>
|
||||
<a-divider type="vertical" />
|
||||
<a class="ele-text-danger" @click="openReject(record)"
|
||||
>拒绝</a
|
||||
>
|
||||
</template>
|
||||
<template v-else-if="record.applyStatus === 1">
|
||||
<a @click="markPaid(record)">已打款</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="ele-text-secondary">--</span>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<DealerWithdrawEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
@done="reload"
|
||||
/>
|
||||
<!-- 拒绝原因弹窗 -->
|
||||
<a-modal
|
||||
v-model:visible="showReject"
|
||||
:confirm-loading="rejectLoading"
|
||||
:maskClosable="false"
|
||||
title="拒绝原因"
|
||||
@ok="submitReject"
|
||||
@cancel="closeReject"
|
||||
>
|
||||
<a-textarea
|
||||
v-model:value="rejectReason"
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入拒绝原因"
|
||||
/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</div>
|
||||
</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 DealerWithdrawEdit from './components/dealerWithdrawEdit.vue';
|
||||
import {
|
||||
pageDealerWithdraw,
|
||||
removeBatchDealerWithdraw,
|
||||
updateDealerWithdraw,
|
||||
exportDealerWithdraw
|
||||
} from '@/api/shop/dealerWithdraw';
|
||||
import type {
|
||||
DealerWithdraw,
|
||||
DealerWithdrawParam
|
||||
} from '@/api/shop/dealerWithdraw/model';
|
||||
import { openExportFile } from '@/utils/export-file';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<DealerWithdraw[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<DealerWithdraw | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 是否显示拒绝原因弹窗
|
||||
const showReject = ref(false);
|
||||
// 拒绝原因
|
||||
const rejectReason = ref('');
|
||||
// 拒绝提交状态
|
||||
const rejectLoading = ref(false);
|
||||
// 当前操作数据
|
||||
const actionCurrent = ref<DealerWithdraw | null>(null);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageDealerWithdraw({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90
|
||||
},
|
||||
// {
|
||||
// title: '',
|
||||
// dataIndex: 'orderNo',
|
||||
// key: 'orderNo',
|
||||
// align: 'center',
|
||||
// },
|
||||
{
|
||||
title: '用户',
|
||||
key: 'userId',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
dataIndex: 'money',
|
||||
key: 'money',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '打款方式',
|
||||
dataIndex: 'payType',
|
||||
key: 'payType',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 10:
|
||||
return '微信';
|
||||
case 20:
|
||||
return '支付宝';
|
||||
case 30:
|
||||
return '银行卡';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
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: '申请状态',
|
||||
dataIndex: 'applyStatus',
|
||||
key: 'applyStatus',
|
||||
align: 'center',
|
||||
customRender: ({ text }) => {
|
||||
switch (text) {
|
||||
case 0:
|
||||
return '待审核';
|
||||
case 1:
|
||||
return '审核通过';
|
||||
case 2:
|
||||
return '驳回';
|
||||
case 3:
|
||||
return '已打款';
|
||||
default:
|
||||
return '未知';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
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 HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: DealerWithdrawParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: DealerWithdraw) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.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);
|
||||
removeBatchDealerWithdraw(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
const hide = message.loading('导出中..', 0);
|
||||
tableRef.value?.doRequest(({ where, orders, filters }) => {
|
||||
exportDealerWithdraw({
|
||||
...(where || {}),
|
||||
...(orders || {}),
|
||||
...(filters || {})
|
||||
})
|
||||
.then((file) => {
|
||||
hide();
|
||||
openExportFile(file);
|
||||
message.success('导出成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* 审核通过 */
|
||||
const approve = (row: DealerWithdraw) => {
|
||||
updateStatus(row, 1);
|
||||
};
|
||||
|
||||
/* 打开拒绝弹窗 */
|
||||
const openReject = (row: DealerWithdraw) => {
|
||||
actionCurrent.value = row;
|
||||
rejectReason.value = '';
|
||||
showReject.value = true;
|
||||
};
|
||||
|
||||
/* 关闭拒绝弹窗 */
|
||||
const closeReject = () => {
|
||||
showReject.value = false;
|
||||
rejectReason.value = '';
|
||||
actionCurrent.value = null;
|
||||
};
|
||||
|
||||
/* 提交拒绝 */
|
||||
const submitReject = () => {
|
||||
const reason = rejectReason.value.trim();
|
||||
if (!reason) {
|
||||
message.error('请输入拒绝原因');
|
||||
return;
|
||||
}
|
||||
if (!actionCurrent.value) {
|
||||
message.error('数据异常,请重试');
|
||||
return;
|
||||
}
|
||||
rejectLoading.value = true;
|
||||
updateDealerWithdraw({
|
||||
id: actionCurrent.value.id,
|
||||
applyStatus: 2,
|
||||
rejectReason: reason
|
||||
})
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
closeReject();
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
rejectLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
/* 标记已打款 */
|
||||
const markPaid = (row: DealerWithdraw) => {
|
||||
updateStatus(row, 3);
|
||||
};
|
||||
|
||||
/* 更新申请状态 */
|
||||
const updateStatus = (row: DealerWithdraw, status: number) => {
|
||||
if (!row.id) {
|
||||
message.error('数据异常,请重试');
|
||||
return;
|
||||
}
|
||||
const hide = message.loading('请求中..', 0);
|
||||
updateDealerWithdraw({
|
||||
id: row.id,
|
||||
applyStatus: status
|
||||
})
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: DealerWithdraw) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DealerWithdraw'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,394 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="name">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入商户名称"
|
||||
v-model:value="form.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属连锁" name="chain">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入所属连锁"
|
||||
v-model:value="form.chain"
|
||||
/>
|
||||
</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="所属片区">
|
||||
<a-select v-model:value="form.areaName">
|
||||
<a-select-option v-for="item in areaDictList" :key="item.dictDataId" :value="item.dictDataCode">
|
||||
{{ item.dictDataCode }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item-->
|
||||
<!-- label="商户图标"-->
|
||||
<!-- name="image">-->
|
||||
<!-- <SelectFile-->
|
||||
<!-- :placeholder="`请选择图片`"-->
|
||||
<!-- :limit="1"-->
|
||||
<!-- :data="images"-->
|
||||
<!-- @done="chooseImage"-->
|
||||
<!-- @del="onDeleteItem"-->
|
||||
<!-- />-->
|
||||
<!-- </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="realName">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入营运经理"
|
||||
v-model:value="form.realName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="省市区" name="province">
|
||||
<a-cascader
|
||||
v-model:value="selectedArea"
|
||||
:options="areaList"
|
||||
:fieldNames="{ label: 'name', value: 'name' }"
|
||||
placeholder="请选择省市区"
|
||||
@change="onAreaChange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="详细地址" name="address">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入详细地址"
|
||||
v-model:value="form.address"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="经纬度" name="lngAndLat">
|
||||
<div style="display: flex; gap: 8px; width: 100%;">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入经纬度"
|
||||
v-model:value="form.lngAndLat"
|
||||
style="flex: 1;"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
:loading="fetchingLngLat"
|
||||
:disabled="!canFetchLngLat || fetchingLngLat"
|
||||
@click="fetchLngLat"
|
||||
>
|
||||
获取经纬度
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="经度" name="lng">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.lng"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="纬度" name="lat">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.lat"
|
||||
/>
|
||||
</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>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed } from "vue";
|
||||
import { Form, message } from "ant-design-vue";
|
||||
import { assignObject, uuid } from "ele-admin-pro";
|
||||
import { addMerchant, updateMerchant } from "@/api/shop/merchant";
|
||||
import { Merchant } from "@/api/shop/merchant/model";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { FormInstance } from "ant-design-vue/es/form";
|
||||
import { Area } from "@/api/system/area/model";
|
||||
import { listArea } from "@/api/system/area";
|
||||
import { DictData } from "@/api/system/dict-data/model";
|
||||
import { listDictData } from "@/api/system/dict-data";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Merchant | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "done"): void;
|
||||
(e: "update:visible", visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const TIANDITU_TOKEN = "71f234f251d4cb1feeb3c293a0f21709";
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
const fetchingLngLat = ref(false);
|
||||
const selectedArea = ref<string[]>([]);
|
||||
const areaList = ref<Area[]>([]);
|
||||
// 是否显示最大化切换按钮
|
||||
const maxable = ref(true);
|
||||
// 表格选中数据
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<Merchant>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
code: undefined,
|
||||
areaName: undefined,
|
||||
chain: undefined,
|
||||
image: undefined,
|
||||
phone: undefined,
|
||||
realName: undefined,
|
||||
lngAndLat: undefined,
|
||||
lng: undefined,
|
||||
lat: undefined,
|
||||
province: undefined,
|
||||
city: undefined,
|
||||
region: undefined,
|
||||
address: undefined,
|
||||
comments: undefined,
|
||||
userId: undefined,
|
||||
deleted: undefined,
|
||||
status: undefined,
|
||||
sortNumber: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined
|
||||
});
|
||||
|
||||
const normalizeText = (value?: string) => (value ?? "").trim();
|
||||
|
||||
const getAreaList = async () => {
|
||||
if (areaList.value.length) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
areaList.value = await listArea({ level: 3});
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || "获取省市区失败");
|
||||
}
|
||||
};
|
||||
|
||||
const onAreaChange = (value?: string[]) => {
|
||||
const [province, city, region] = value ?? [];
|
||||
form.province = province;
|
||||
form.city = city;
|
||||
form.region = region;
|
||||
};
|
||||
|
||||
const canFetchLngLat = computed(() => {
|
||||
return Boolean(
|
||||
normalizeText(form.province) &&
|
||||
normalizeText(form.city) &&
|
||||
normalizeText(form.region) &&
|
||||
normalizeText(form.address)
|
||||
);
|
||||
});
|
||||
|
||||
const validateLngAndLat = async (_rule: unknown, value?: string) => {
|
||||
if (!normalizeText(form.address) || normalizeText(value)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject("请输入经纬度");
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit("update:visible", value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入商户名称",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
code: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入商户编码",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
chain: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入所属连锁",
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
province: [
|
||||
{
|
||||
required: true,
|
||||
message: "请选择省市区",
|
||||
trigger: "change"
|
||||
}
|
||||
],
|
||||
lngAndLat: [
|
||||
{
|
||||
validator: validateLngAndLat,
|
||||
trigger: "change"
|
||||
}
|
||||
],
|
||||
lng: [
|
||||
{
|
||||
validator: validateLngAndLat,
|
||||
trigger: "blur"
|
||||
}
|
||||
],
|
||||
lat: [
|
||||
{
|
||||
validator: validateLngAndLat,
|
||||
trigger: "change"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
const fetchLngLat = async () => {
|
||||
if (!canFetchLngLat.value) {
|
||||
message.warning("请先选择省市区并填写详细地址");
|
||||
return;
|
||||
}
|
||||
fetchingLngLat.value = true;
|
||||
try {
|
||||
const area = `${normalizeText(form.province)}${normalizeText(form.city)}${normalizeText(form.region)}`;
|
||||
const payload = {
|
||||
keyWord: normalizeText(form.address),
|
||||
level: 18,
|
||||
address: area
|
||||
};
|
||||
const url = `https://api.tianditu.gov.cn/geocoder?ds=${encodeURIComponent(
|
||||
JSON.stringify(payload)
|
||||
)}&tk=${TIANDITU_TOKEN}`;
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error("获取经纬度失败,请稍后再试");
|
||||
}
|
||||
const data = await response.json();
|
||||
const location = data?.location || data?.result?.location;
|
||||
const lng = location?.lon ?? location?.lng ?? location?.longitude;
|
||||
const lat = location?.lat ?? location?.latitude;
|
||||
if (!lng || !lat) {
|
||||
throw new Error(data?.msg || "未获取到经纬度");
|
||||
}
|
||||
form.lng = String(lng);
|
||||
form.lat = String(lat);
|
||||
form.lngAndLat = `${form.lng},${form.lat}`;
|
||||
message.success("已获取经纬度");
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || "获取经纬度失败");
|
||||
} finally {
|
||||
fetchingLngLat.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateMerchant : addMerchant;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit("done");
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const areaDictList = ref<DictData[]>([]);
|
||||
const getAreaDictList = async () => {
|
||||
areaDictList.value = await listDictData({ dictId: 174 });
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (visible) => {
|
||||
if (visible) {
|
||||
await getAreaList();
|
||||
await getAreaDictList();
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
const areaValue = [form.province, form.city, form.region].filter(Boolean);
|
||||
selectedArea.value = areaValue.length ? (areaValue as string[]) : [];
|
||||
} else {
|
||||
resetFields();
|
||||
selectedArea.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,352 @@
|
||||
<!-- 商户批量导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="560"
|
||||
:footer="null"
|
||||
title="批量导入门店"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doImport"
|
||||
style="padding: 24px 0; margin-bottom: 12px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-hint">将 Excel 文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
<div class="ele-text-center" style="margin-top: 8px;">
|
||||
<a
|
||||
href="https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/%E9%97%A8%E5%BA%97%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
模板下载
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="progress.total" class="ele-text-center" style="margin-top: 8px;">
|
||||
已处理 {{ progress.success + progress.fail }}/{{ progress.total }},
|
||||
成功 {{ progress.success }},失败 {{ progress.fail }}
|
||||
</div>
|
||||
<div v-if="errorMessages.length" class="ele-text-danger" style="margin-top: 8px;">
|
||||
<div>失败明细(展示最新 5 条):</div>
|
||||
<div v-for="(item, index) in latestErrorMessages" :key="index">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { message } from "ant-design-vue";
|
||||
import { CloudUploadOutlined } from "@ant-design/icons-vue";
|
||||
import * as XLSX from "xlsx";
|
||||
import { addMerchant } from "@/api/shop/merchant";
|
||||
import type { Merchant } from "@/api/shop/merchant/model";
|
||||
import { getAddressInfo } from "@/api/yxe/ai";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "done"): void;
|
||||
(e: "update:visible", visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 是否打开弹窗
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const progress = ref({ total: 0, success: 0, fail: 0 });
|
||||
const errorMessages = ref<string[]>([]);
|
||||
const latestErrorMessages = computed(() =>
|
||||
errorMessages.value.slice(-5).reverse()
|
||||
);
|
||||
|
||||
const normalize = (value?: unknown) => String(value ?? "").trim();
|
||||
|
||||
const parseAddressInfo = (content?: string) => {
|
||||
const raw = normalize(content).replace(/^\uFEFF/, "");
|
||||
if (!raw) {
|
||||
throw new Error("地址解析结果为空");
|
||||
}
|
||||
|
||||
const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
|
||||
const body = fenced?.[1] ? fenced[1].trim() : raw;
|
||||
const firstBrace = body.indexOf("{");
|
||||
const lastBrace = body.lastIndexOf("}");
|
||||
const jsonLike =
|
||||
firstBrace >= 0 && lastBrace > firstBrace
|
||||
? body.slice(firstBrace, lastBrace + 1)
|
||||
: body;
|
||||
|
||||
const tryParse = (text: string) => {
|
||||
const parsed = JSON.parse(text);
|
||||
if (typeof parsed === "string") {
|
||||
return JSON.parse(parsed);
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const candidates = [
|
||||
jsonLike,
|
||||
jsonLike.replace(/,\s*([}\]])/g, "$1"),
|
||||
jsonLike
|
||||
.replace(/([{,]\s*)([A-Za-z_][A-Za-z0-9_]*)(\s*:)/g, '$1"$2"$3')
|
||||
.replace(/'/g, '"')
|
||||
.replace(/,\s*([}\]])/g, "$1")
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
return tryParse(candidate);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`地址解析结果不是有效 JSON:${body}`);
|
||||
};
|
||||
|
||||
const readRows = async (file: File) => {
|
||||
const data = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(data, { type: "array" });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
const rows = XLSX.utils.sheet_to_json<any[]>(sheet, {
|
||||
header: 1,
|
||||
defval: ""
|
||||
}) as Array<string[]>;
|
||||
if (!rows.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const header = rows[0].map((item) => normalize(item).toLowerCase());
|
||||
const headerIndex = (aliases: string[]) => {
|
||||
const aliasSet = aliases.map((item) => item.toLowerCase());
|
||||
return header.findIndex((item) => aliasSet.includes(item));
|
||||
};
|
||||
|
||||
const chainIndex = headerIndex(["所属连锁", "连锁", "chain"]);
|
||||
const codeIndex = headerIndex(["门店编码", "商户编号", "id", "code", "id(code)", "编号"]);
|
||||
const nameIndex = headerIndex(["门店名称", "名称", "name", "门店名称(name)"]);
|
||||
const areaNameIndex = headerIndex(["片区", "所属片区", "areaName", "area"]);
|
||||
const phoneIndex = headerIndex(["联系方式", "联系电话", "手机", "phone", "联系方式(phone)"]);
|
||||
const realNameIndex = headerIndex([
|
||||
"营运经理",
|
||||
"负责人",
|
||||
"联系人",
|
||||
"realName",
|
||||
"营运经理(realName)"
|
||||
]);
|
||||
const provinceIndex = headerIndex(["省份", "省", "province", "省份(province)"]);
|
||||
const cityIndex = headerIndex(["市级", "城市", "市", "city", "市级(city)"]);
|
||||
const regionIndex = headerIndex([
|
||||
"区/县级",
|
||||
"区县",
|
||||
"区",
|
||||
"县",
|
||||
"region",
|
||||
"区/县级(region)"
|
||||
]);
|
||||
const addressIndex = headerIndex([
|
||||
"门店具体地址",
|
||||
"门店具体地址(addressfull)",
|
||||
"门店具体地址(按营业执照填写)",
|
||||
"门店具体地址 (按营业执照填写)",
|
||||
"门店具体地址(按营业执照填写)",
|
||||
"详细地址",
|
||||
"地址",
|
||||
"addressFull",
|
||||
"门店具体地址(addressFull)"
|
||||
]);
|
||||
|
||||
const hasHeader =
|
||||
chainIndex >= 0 ||
|
||||
codeIndex >= 0 ||
|
||||
nameIndex >= 0 ||
|
||||
areaNameIndex >= 0 ||
|
||||
phoneIndex >= 0 ||
|
||||
realNameIndex >= 0 ||
|
||||
provinceIndex >= 0 ||
|
||||
cityIndex >= 0 ||
|
||||
regionIndex >= 0 ||
|
||||
addressIndex >= 0;
|
||||
const dataRows = hasHeader ? rows.slice(1) : rows;
|
||||
const getByIndex = (row: string[], index: number, fallback: number) => {
|
||||
const value = index >= 0 ? row[index] : row[fallback];
|
||||
return normalize(value);
|
||||
};
|
||||
|
||||
return dataRows
|
||||
.map((row, i) => {
|
||||
const chain = getByIndex(row, chainIndex, 0);
|
||||
const code = getByIndex(row, codeIndex, 1);
|
||||
const name = getByIndex(row, nameIndex, 2);
|
||||
const areaName = getByIndex(row, areaNameIndex, 3);
|
||||
const realName = getByIndex(row, realNameIndex, 4);
|
||||
const phone = getByIndex(row, phoneIndex, 5);
|
||||
const province = getByIndex(row, provinceIndex, 6);
|
||||
const city = getByIndex(row, cityIndex, 7);
|
||||
const region = getByIndex(row, regionIndex, 8);
|
||||
const addressFull = getByIndex(row, addressIndex, 9);
|
||||
return {
|
||||
rowNumber: hasHeader ? i + 2 : i + 1,
|
||||
chain,
|
||||
code,
|
||||
name,
|
||||
areaName,
|
||||
phone,
|
||||
realName,
|
||||
province,
|
||||
city,
|
||||
region,
|
||||
addressFull
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(row) =>
|
||||
row.chain ||
|
||||
row.code ||
|
||||
row.name ||
|
||||
row.areaName ||
|
||||
row.phone ||
|
||||
row.realName ||
|
||||
row.province ||
|
||||
row.city ||
|
||||
row.region ||
|
||||
row.addressFull
|
||||
);
|
||||
};
|
||||
|
||||
const resolveAddressInfo = async (addressFull: string) => {
|
||||
const res = await getAddressInfo(addressFull);
|
||||
const info = parseAddressInfo(res?.output?.choices?.[0]?.message?.content);
|
||||
const lng = info?.lng ?? info?.lon ?? info?.longitude;
|
||||
const lat = info?.lat ?? info?.latitude;
|
||||
const province = info?.province;
|
||||
const city = info?.city;
|
||||
const region = info?.region;
|
||||
const detail = info?.detail;
|
||||
|
||||
if (!lng || !lat) {
|
||||
throw new Error("未获取到经纬度");
|
||||
}
|
||||
|
||||
return {
|
||||
lng: String(lng),
|
||||
lat: String(lat),
|
||||
province: province,
|
||||
city: city,
|
||||
region: region,
|
||||
detail: detail
|
||||
};
|
||||
};
|
||||
|
||||
const doImport = async ({ 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;
|
||||
progress.value = { total: 0, success: 0, fail: 0 };
|
||||
errorMessages.value = [];
|
||||
|
||||
try {
|
||||
const rows = await readRows(file as File);
|
||||
if (!rows.length) {
|
||||
message.warning("未读取到可导入的数据");
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.value.total = rows.length;
|
||||
for (const row of rows) {
|
||||
try {
|
||||
if (
|
||||
!row.name ||
|
||||
!row.province ||
|
||||
!row.city
|
||||
) {
|
||||
throw new Error("门店名称、省份、市级为空");
|
||||
}
|
||||
let lng: string | undefined;
|
||||
let lat: string | undefined;
|
||||
let province = row.province;
|
||||
let city = row.city;
|
||||
let region = row.region;
|
||||
let detail = row.addressFull;
|
||||
|
||||
if (row.addressFull) {
|
||||
const resolved = await resolveAddressInfo(row.addressFull);
|
||||
lng = resolved.lng;
|
||||
lat = resolved.lat;
|
||||
province = resolved.province || province;
|
||||
city = resolved.city || city;
|
||||
region = resolved.region || region;
|
||||
detail = resolved.detail || detail;
|
||||
}
|
||||
|
||||
const merchantData: Merchant = {
|
||||
chain: row.chain,
|
||||
code: row.code || undefined,
|
||||
name: row.name,
|
||||
areaName: row.areaName,
|
||||
phone: row.phone,
|
||||
realName: row.realName,
|
||||
lng,
|
||||
lat,
|
||||
lngAndLat: lng && lat ? `${lng},${lat}` : undefined,
|
||||
province: province,
|
||||
city: city,
|
||||
region: region,
|
||||
address: detail || undefined
|
||||
};
|
||||
await addMerchant(merchantData);
|
||||
progress.value.success += 1;
|
||||
} catch (e) {
|
||||
progress.value.fail += 1;
|
||||
const err = e as Error;
|
||||
errorMessages.value.push(`第${row.rowNumber}行:${err.message || "导入失败"}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.value.fail > 0) {
|
||||
message.warning(
|
||||
`导入完成:成功${progress.value.success}条,失败${progress.value.fail}条`
|
||||
);
|
||||
} else {
|
||||
message.success(`导入完成:成功${progress.value.success}条`);
|
||||
}
|
||||
if (progress.value.success > 0) {
|
||||
emit("done");
|
||||
}
|
||||
updateVisible(false);
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || "导入失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit("update:visible", value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-button type="primary" @click="openImport">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
批量导入
|
||||
</a-button>
|
||||
<a-popconfirm title="确定要批量删除选择的记录吗?" @confirm="removeBatch">
|
||||
<a-button type="primary" danger :disabled="!props.selection?.length">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
批量删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<a-input-search
|
||||
v-model:value="where.keywords"
|
||||
allow-clear
|
||||
placeholder="名称筛选"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
/>
|
||||
<a-input-search
|
||||
v-model:value="where.chain"
|
||||
allow-clear
|
||||
placeholder="连锁筛选"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
/>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
UploadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { MerchantParam } from '@/api/shop/merchant/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: MerchantParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'import'): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<MerchantParam>({
|
||||
keywords: undefined,
|
||||
chain: undefined
|
||||
});
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
search();
|
||||
};
|
||||
|
||||
const openImport = () => {
|
||||
emit('import');
|
||||
};
|
||||
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,264 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<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"
|
||||
:pageSizeOptions="['10', '20', '50', '100', '200', '500', '1000']"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
@import="openImport"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'province'">
|
||||
<span>{{ record.province }}{{ record.city }}{{ record.region }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<MerchantEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<!-- 批量导入弹窗 -->
|
||||
<MerchantImport v-model:visible="showImport" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 MerchantEdit from './components/merchantEdit.vue';
|
||||
import MerchantImport from './components/merchantImport.vue';
|
||||
import { pageMerchant, removeMerchant, removeBatchMerchant } from '@/api/shop/merchant';
|
||||
import type { Merchant, MerchantParam } from '@/api/shop/merchant/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Merchant[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<Merchant | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 是否显示批量导入弹窗
|
||||
const showImport = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageMerchant({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '编码',
|
||||
dataIndex: 'code',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '所属连锁',
|
||||
dataIndex: 'chain',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '所属片区',
|
||||
dataIndex: 'areaName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '商户名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '联系方式',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '营运经理',
|
||||
dataIndex: 'realName',
|
||||
key: 'realName',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '所在区域',
|
||||
key: 'province',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '详细地址',
|
||||
dataIndex: 'address',
|
||||
key: 'address',
|
||||
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?: MerchantParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Merchant) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量导入弹窗 */
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Merchant) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeMerchant(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchMerchant(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Merchant) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Merchant'
|
||||
};
|
||||
</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,185 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="uid">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.uid"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="parentId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="管理员备注" name="remark">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入管理员备注"
|
||||
v-model:value="form.remark"
|
||||
/>
|
||||
</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="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入描述"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</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 { addUserParentList, updateUserParentList } from '@/api/shop/userParentList';
|
||||
import { UserParentList } from '@/api/shop/userParentList/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: UserParentList | 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 form = reactive<UserParentList>({
|
||||
id: undefined,
|
||||
uid: undefined,
|
||||
parentId: undefined,
|
||||
remark: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateUserParentList : addUserParentList;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<UserParentListEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 UserParentListEdit from './components/userParentListEdit.vue';
|
||||
import { pageUserParentList, removeUserParentList, removeBatchUserParentList } from '@/api/shop/userParentList';
|
||||
import type { UserParentList, UserParentListParam } from '@/api/shop/userParentList/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<UserParentList[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<UserParentList | 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 pageUserParentList({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'uid',
|
||||
key: 'uid',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'parentId',
|
||||
key: 'parentId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '管理员备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '排序(数字越小越靠前)',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
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: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParentListParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: UserParentList) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: UserParentList) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeUserParentList(row.id)
|
||||
.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);
|
||||
removeBatchUserParentList(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: UserParentList) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'UserParentList'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,232 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-select
|
||||
v-model:value="where.userId"
|
||||
show-search
|
||||
allow-clear
|
||||
:filter-option="false"
|
||||
optionFilterProp="label"
|
||||
:loading="userLoading"
|
||||
:options="userOptions"
|
||||
placeholder="请选择用户"
|
||||
style="width: 220px"
|
||||
@search="onSearchUser"
|
||||
@focus="loadUserOptions()"
|
||||
@change="search"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value.trim="where.msg"
|
||||
allow-clear
|
||||
placeholder="请输入积分说明"
|
||||
style="width: 220px"
|
||||
@pressEnter="search"
|
||||
/>
|
||||
<a-select
|
||||
v-model:value="where.status"
|
||||
allow-clear
|
||||
placeholder="请选择状态"
|
||||
style="width: 160px"
|
||||
>
|
||||
<a-select-option :value="0">正常</a-select-option>
|
||||
<a-select-option :value="1">冻结</a-select-option>
|
||||
</a-select>
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['请选择开始日期', '请选择结束日期']"
|
||||
style="width: 260px"
|
||||
/>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
<a-popconfirm title="确定要批量删除选择的记录吗?" @confirm="removeBatch">
|
||||
<a-button type="primary" danger :disabled="!props.selection?.length">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
<span>批量删除</span>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { SelectProps } from 'ant-design-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import type { User as SystemUser } from '@/api/system/user/model';
|
||||
import type { UserPointLogParam } from '@/api/shop/userPointLog/model';
|
||||
|
||||
const props = defineProps<{
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: UserPointLogParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<UserPointLogParam>({
|
||||
userId: undefined,
|
||||
msg: undefined,
|
||||
status: undefined,
|
||||
createTimeStart: undefined,
|
||||
createTimeEnd: undefined
|
||||
});
|
||||
|
||||
const dateRange = ref<[string, string] | []>([]);
|
||||
const userOptions = ref<SelectProps['options']>([]);
|
||||
const userLoading = ref(false);
|
||||
let userSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let userSearchSeed = 0;
|
||||
|
||||
const buildUserOption = (item: SystemUser) => {
|
||||
if (item.userId == null) {
|
||||
return null;
|
||||
}
|
||||
const phone = String(item.phone ?? '').trim();
|
||||
const name = String(item.realName ?? item.nickname ?? '').trim();
|
||||
const contact = phone || `ID:${item.userId}`;
|
||||
return {
|
||||
value: item.userId,
|
||||
label: name ? `${name} (${contact})` : contact
|
||||
};
|
||||
};
|
||||
|
||||
const ensureCurrentUserOption = (userId?: number) => {
|
||||
if (userId == null) {
|
||||
return;
|
||||
}
|
||||
const exists = userOptions.value?.some((item) => item?.value === userId);
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
userOptions.value = [
|
||||
{
|
||||
value: userId,
|
||||
label: `ID:${userId}`
|
||||
},
|
||||
...(userOptions.value || [])
|
||||
];
|
||||
};
|
||||
|
||||
const loadUserOptions = (keyword?: string) => {
|
||||
const currentSeed = ++userSearchSeed;
|
||||
const trimmed = String(keyword ?? '').trim();
|
||||
const previousSelectedOption =
|
||||
where.userId == null
|
||||
? undefined
|
||||
: (userOptions.value || []).find(
|
||||
(item) => item?.value === where.userId
|
||||
);
|
||||
userLoading.value = true;
|
||||
const requests = trimmed
|
||||
? [
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
phone: trimmed
|
||||
}),
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
realName: trimmed
|
||||
})
|
||||
]
|
||||
: [
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20
|
||||
})
|
||||
];
|
||||
Promise.all(requests)
|
||||
.then((results) => {
|
||||
if (currentSeed !== userSearchSeed) {
|
||||
return;
|
||||
}
|
||||
const map = new Map<number, { value: number; label: string }>();
|
||||
results.forEach((res) => {
|
||||
(res?.list || []).forEach((item) => {
|
||||
const option = buildUserOption(item as SystemUser);
|
||||
if (option && !map.has(option.value)) {
|
||||
map.set(option.value, option);
|
||||
}
|
||||
});
|
||||
});
|
||||
userOptions.value = Array.from(map.values());
|
||||
if (
|
||||
previousSelectedOption &&
|
||||
where.userId != null &&
|
||||
!map.has(where.userId)
|
||||
) {
|
||||
userOptions.value = [previousSelectedOption, ...userOptions.value];
|
||||
}
|
||||
ensureCurrentUserOption(where.userId);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (currentSeed !== userSearchSeed) {
|
||||
return;
|
||||
}
|
||||
message.error(e.message ?? '查询用户失败');
|
||||
})
|
||||
.finally(() => {
|
||||
if (currentSeed === userSearchSeed) {
|
||||
userLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSearchUser = (value: string) => {
|
||||
if (userSearchTimer) {
|
||||
clearTimeout(userSearchTimer);
|
||||
}
|
||||
userSearchTimer = setTimeout(() => {
|
||||
loadUserOptions(value);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const syncCreateTimeRange = () => {
|
||||
const [startDate, endDate] = Array.isArray(dateRange.value)
|
||||
? dateRange.value
|
||||
: [];
|
||||
where.createTimeStart = startDate ? `${startDate} 00:00:00` : undefined;
|
||||
where.createTimeEnd = endDate ? `${endDate} 23:59:59` : undefined;
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
syncCreateTimeRange();
|
||||
emit('search', { ...where });
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
dateRange.value = [];
|
||||
search();
|
||||
};
|
||||
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (userSearchTimer) {
|
||||
clearTimeout(userSearchTimer);
|
||||
userSearchTimer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,190 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="用户ID" name="userId">
|
||||
<a-input-number
|
||||
:min="1"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入用户ID"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="积分说明" name="msg">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入积分说明"
|
||||
v-model:value="form.msg"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="管理员备注" name="remark">
|
||||
<a-textarea
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
placeholder="请输入管理员备注"
|
||||
v-model:value="form.remark"
|
||||
/>
|
||||
</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="comments">
|
||||
<a-textarea
|
||||
:rows="3"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态" 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>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { addUserPointLog, updateUserPointLog } from '@/api/shop/userPointLog';
|
||||
import type { UserPointLog } from '@/api/shop/userPointLog/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { 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?: UserPointLog | 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 form = reactive<UserPointLog>({
|
||||
id: undefined,
|
||||
userId: undefined,
|
||||
msg: undefined,
|
||||
remark: undefined,
|
||||
sortNumber: undefined,
|
||||
comments: undefined,
|
||||
status: 0,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
userId: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入用户ID'
|
||||
}
|
||||
],
|
||||
msg: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入积分说明'
|
||||
}
|
||||
],
|
||||
status: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请选择状态'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
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
|
||||
? updateUserPointLog
|
||||
: addUserPointLog;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
form.status = 0;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
v-model:selection="selection"
|
||||
: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 === 'userId'">
|
||||
<span>{{ record.userId ?? '-' }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">正常</a-tag>
|
||||
<a-tag v-else-if="record.status === 1" color="red">冻结</a-tag>
|
||||
<span v-else>-</span>
|
||||
</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>
|
||||
|
||||
<UserPointLogEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
@done="reload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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 UserPointLogEdit from './components/userPointLogEdit.vue';
|
||||
import {
|
||||
pageUserPointLog,
|
||||
removeUserPointLog,
|
||||
removeBatchUserPointLog
|
||||
} from '@/api/shop/userPointLog';
|
||||
import type {
|
||||
UserPointLog,
|
||||
UserPointLogParam
|
||||
} from '@/api/shop/userPointLog/model';
|
||||
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
const selection = ref<UserPointLog[]>([]);
|
||||
const current = ref<UserPointLog | null>(null);
|
||||
const showEdit = ref(false);
|
||||
const showMove = ref(false);
|
||||
const loading = ref(true);
|
||||
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters && filters.status !== void 0) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
return pageUserPointLog({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userId',
|
||||
align: 'center',
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: '积分说明',
|
||||
dataIndex: 'msg',
|
||||
key: 'msg',
|
||||
align: 'center',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '管理员备注',
|
||||
dataIndex: 'remark',
|
||||
key: 'remark',
|
||||
align: 'center',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
align: 'center',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
align: 'center',
|
||||
sorter: true,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime',
|
||||
key: 'updateTime',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) =>
|
||||
text ? toDateString(text, 'yyyy-MM-dd HH:mm:ss') : '-'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
const reload = (where?: UserPointLogParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where });
|
||||
};
|
||||
|
||||
const openEdit = (row?: UserPointLog) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
const remove = (row: UserPointLog) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeUserPointLog(row.id)
|
||||
.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);
|
||||
removeBatchUserPointLog(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
const customRow = (record: UserPointLog) => {
|
||||
return {
|
||||
onClick: () => {
|
||||
return record;
|
||||
},
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'UserPointLog'
|
||||
};
|
||||
</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,149 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="name">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="fid">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.fid"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="levelId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.levelId"
|
||||
/>
|
||||
</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 { addSysArea, updateSysArea } from '@/api/sys/sysArea';
|
||||
import { SysArea } from '@/api/sys/sysArea/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: SysArea | 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 form = reactive<SysArea>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
fid: undefined,
|
||||
levelId: undefined,
|
||||
tenantId: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateSysArea : addSysArea;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<SysAreaEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 SysAreaEdit from './components/sysAreaEdit.vue';
|
||||
import { pageSysArea, removeSysArea, removeBatchSysArea } from '@/api/sys/sysArea';
|
||||
import type { SysArea, SysAreaParam } from '@/api/sys/sysArea/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<SysArea[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<SysArea | 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 pageSysArea({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'fid',
|
||||
key: 'fid',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'levelId',
|
||||
key: 'levelId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: SysAreaParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: SysArea) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: SysArea) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeSysArea(row.id)
|
||||
.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);
|
||||
removeBatchSysArea(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: SysArea) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'SysArea'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,289 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="760"
|
||||
:visible="props.visible"
|
||||
:maskClosable="false"
|
||||
:confirm-loading="saving"
|
||||
title="金币充值"
|
||||
:body-style="{ paddingBottom: '10px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 6, xs: 24 } : { flex: '100px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 18, sm: 17, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="用户">
|
||||
<div class="user-brief">
|
||||
<span class="user-name">{{ userName || "未选择用户" }}</span>
|
||||
<span v-if="props.data?.userId" class="user-id">ID: {{ props.data?.userId }}</span>
|
||||
</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="增减方式" name="type">
|
||||
<a-radio-group v-model:value="form.type">
|
||||
<a-radio value="+">增加</a-radio>
|
||||
<a-radio value="-">扣减</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item label="金币数量" name="num">
|
||||
<a-input-number
|
||||
class="ele-fluid"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
placeholder="请输入金币数量"
|
||||
v-model:value="form.num"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注" name="desc">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.desc"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<a-divider orientation="left">金币记录</a-divider>
|
||||
<a-table
|
||||
size="small"
|
||||
:loading="logLoading"
|
||||
:columns="logColumns"
|
||||
:data-source="logs"
|
||||
:pagination="pagination"
|
||||
row-key="id"
|
||||
@change="handleTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'type'">
|
||||
<a-tag :color="record.type === '+' ? 'green' : 'red'">
|
||||
{{ record.type === "+" ? "增加" : "减少" }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'num'">
|
||||
<span :class="record.type === '+' ? 'text-add' : 'text-minus'">
|
||||
{{ record.type === "+" ? "+" : "-" }}{{ record.num }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'createTime'">
|
||||
{{ record.createTime ? toDateString(record.createTime, "yyyy-MM-dd HH:mm") : "-" }}
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from "vue";
|
||||
import { message } from "ant-design-vue/es";
|
||||
import type { FormInstance, Rule } from "ant-design-vue/es/form";
|
||||
import type { TablePaginationConfig } from "ant-design-vue/es/table/interface";
|
||||
import { toDateString } from "ele-admin-pro";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { addGxCoinLog, pageGxCoinLog } from "@/api/gx/gxCoinLog";
|
||||
import type { GxCoinLog, GxCoinLogParam } from "@/api/gx/gxCoinLog/model";
|
||||
import type { User } from "@/api/system/user/model";
|
||||
|
||||
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 formRef = ref<FormInstance | null>(null);
|
||||
const saving = ref(false);
|
||||
const logLoading = ref(false);
|
||||
const logs = ref<GxCoinLog[]>([]);
|
||||
|
||||
const pagination = reactive<TablePaginationConfig>({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
showTotal: (total) => `共 ${total} 条`
|
||||
});
|
||||
|
||||
const form = reactive<{
|
||||
type: string;
|
||||
num?: number;
|
||||
desc?: string;
|
||||
}>({
|
||||
type: "+",
|
||||
num: undefined,
|
||||
desc: ""
|
||||
});
|
||||
|
||||
const logColumns = [
|
||||
{
|
||||
title: "时间",
|
||||
dataIndex: "createTime",
|
||||
key: "createTime",
|
||||
width: 180
|
||||
},
|
||||
{
|
||||
title: "类型",
|
||||
dataIndex: "type",
|
||||
key: "type",
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: "金币",
|
||||
dataIndex: "num",
|
||||
key: "num",
|
||||
width: 120
|
||||
},
|
||||
{
|
||||
title: "描述",
|
||||
dataIndex: "desc",
|
||||
key: "desc",
|
||||
ellipsis: true
|
||||
}
|
||||
];
|
||||
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
type: [{ required: true, message: "请选择增减方式", trigger: "change" }],
|
||||
num: [
|
||||
{ required: true, message: "请输入金币数量", trigger: "blur" },
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
if (value && value > 0) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject("金币数量需大于0");
|
||||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const userName = computed(() => {
|
||||
const target = props.data;
|
||||
if (!target) {
|
||||
return "";
|
||||
}
|
||||
return target.realName || target.nickname || target.username || "";
|
||||
});
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit("update:visible", value);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
form.type = "+";
|
||||
form.num = undefined;
|
||||
form.desc = "";
|
||||
formRef.value?.clearValidate();
|
||||
};
|
||||
|
||||
const loadLogs = (resetPage = false) => {
|
||||
if (resetPage) {
|
||||
pagination.current = 1;
|
||||
}
|
||||
const userId = props.data?.userId;
|
||||
if (!userId) {
|
||||
logs.value = [];
|
||||
pagination.total = 0;
|
||||
return;
|
||||
}
|
||||
logLoading.value = true;
|
||||
const params: GxCoinLogParam = {
|
||||
page: pagination.current || 1,
|
||||
limit: pagination.pageSize || 10,
|
||||
userId
|
||||
};
|
||||
pageGxCoinLog(params)
|
||||
.then((res) => {
|
||||
logs.value = res.list || [];
|
||||
pagination.total = res.count || 0;
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
logLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const handleTableChange = (pag: TablePaginationConfig) => {
|
||||
pagination.current = pag.current || 1;
|
||||
pagination.pageSize = pag.pageSize || 10;
|
||||
loadLogs();
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value.validate().then(() => {
|
||||
const userId = props.data?.userId;
|
||||
if (!userId) {
|
||||
message.error("未找到用户信息");
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
const payload: GxCoinLog = {
|
||||
userId,
|
||||
type: form.type,
|
||||
num: form.num,
|
||||
desc: form.desc,
|
||||
status: 1,
|
||||
isAdmin: true
|
||||
};
|
||||
addGxCoinLog(payload)
|
||||
.then((msg) => {
|
||||
message.success(msg);
|
||||
emit("done");
|
||||
loadLogs(true);
|
||||
resetForm();
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
saving.value = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
resetForm();
|
||||
loadLogs(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-brief {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
color: #999;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.text-add {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.text-minus {
|
||||
color: #f5222d;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="exportData">
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
<a-input
|
||||
v-model:value="where.name"
|
||||
allow-clear
|
||||
placeholder="请输入姓名"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="where.phone"
|
||||
allow-clear
|
||||
placeholder="请输入手机号"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-button type="primary" @click="reload">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { YxeChronicDiseaseCollectionParam } from '@/api/yxe/yxeChronicDiseaseCollection/model';
|
||||
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: YxeChronicDiseaseCollectionParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'export'): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<YxeChronicDiseaseCollectionParam>({
|
||||
name: '',
|
||||
phone: ''
|
||||
});
|
||||
|
||||
const reload = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
emit('export');
|
||||
};
|
||||
</script>
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
title="详情"
|
||||
:width="980"
|
||||
:footer="null"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="detailLoading">
|
||||
<a-space direction="vertical" class="ele-fluid">
|
||||
<a-descriptions bordered :column="{ xs: 1, sm: 2 }">
|
||||
<a-descriptions-item label="ID">
|
||||
{{ formatText(detailData?.id) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="用户ID">
|
||||
{{ formatText(detailData?.userId) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="姓名">
|
||||
{{ formatText(detailData?.name) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">
|
||||
{{ formatText(detailData?.gender) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="手机号">
|
||||
{{ formatText(detailData?.phone) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="出生年月日">
|
||||
{{ formatText(detailData?.birthday) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="连锁">
|
||||
{{ formatText(detailData?.chainName) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="门店">
|
||||
{{ formatText(detailData?.merchantName) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag v-if="detailData?.status === 0">待审核</a-tag>
|
||||
<a-tag v-else-if="detailData?.status === 1" color="green">
|
||||
通过
|
||||
</a-tag>
|
||||
<a-tag v-else-if="detailData?.status === 2" color="red">
|
||||
驳回
|
||||
</a-tag>
|
||||
<span v-else class="ele-text-secondary">-</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="添加时间">
|
||||
{{ formatDateTime(detailData?.createTime) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="修改时间">
|
||||
{{ formatDateTime(detailData?.updateTime) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="血红蛋白数值">
|
||||
{{ formatText(detailData?.hemoglobinValue) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="贫血程度">
|
||||
{{ formatText(detailData?.anemiaLevel) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="地贫类型">
|
||||
{{ formatText(detailData?.thalassemiaType) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="是否高血压">
|
||||
<a-tag v-if="isYes(detailData?.highBloodPressure)" color="green">
|
||||
是
|
||||
</a-tag>
|
||||
<a-tag v-else color="red">否</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="血压值">
|
||||
{{ formatText(detailData?.highBloodPressureValue) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="收缩压">
|
||||
{{ formatText(detailData?.systolicPressure) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="舒张压">
|
||||
{{ formatText(detailData?.diastolicPressure) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="是否高血糖">
|
||||
<a-tag v-if="isYes(detailData?.highBloodSugar)" color="green">
|
||||
是
|
||||
</a-tag>
|
||||
<a-tag v-else color="red">否</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="空腹血糖">
|
||||
{{ formatText(detailData?.fastingBloodGlucose) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="餐后2小时血糖">
|
||||
{{ formatText(detailData?.postprandial2hBloodGlucose) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="糖化血红蛋白(HbA1c)">
|
||||
{{ formatText(detailData?.hba1c) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="是否高血脂">
|
||||
<a-tag v-if="isYes(detailData?.hyperlipidemia)" color="green">
|
||||
是
|
||||
</a-tag>
|
||||
<a-tag v-else color="red">否</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="总胆固醇(TC)">
|
||||
{{ formatText(detailData?.totalCholesterol) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="甘油三酯(TG)">
|
||||
{{ formatText(detailData?.triglyceride) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="低密度脂蛋白(LDL-C)">
|
||||
{{ formatText(detailData?.ldlC) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="高密度脂蛋白(HDL-C)">
|
||||
{{ formatText(detailData?.hdlC) }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="病史" :span="2">
|
||||
<div class="detail-text">
|
||||
{{ formatText(detailData?.medicalHistory) }}
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="用药史" :span="2">
|
||||
<div class="detail-text">
|
||||
{{ formatText(detailData?.medicationHistory) }}
|
||||
</div>
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-card size="small" title="用药追踪">
|
||||
<a-table
|
||||
v-if="trackingRows.length"
|
||||
:columns="trackingColumns"
|
||||
:data-source="trackingRows"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 960 }"
|
||||
size="small"
|
||||
row-key="rowKey"
|
||||
/>
|
||||
<div v-else-if="trackingRawText" class="detail-text">
|
||||
{{ trackingRawText }}
|
||||
</div>
|
||||
<span v-else class="ele-text-secondary">-</span>
|
||||
</a-card>
|
||||
</a-space>
|
||||
</a-spin>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import { getYxeChronicDiseaseCollection } from '@/api/yxe/yxeChronicDiseaseCollection';
|
||||
import type { YxeChronicDiseaseCollection } from '@/api/yxe/yxeChronicDiseaseCollection/model';
|
||||
|
||||
type TrackingItem = {
|
||||
productId?: number;
|
||||
productName?: string;
|
||||
goodsCode?: string;
|
||||
dailyUsageCount?: number | string;
|
||||
dosagePerTime?: number | string;
|
||||
boxUsageDays?: number | string;
|
||||
purchaseTime?: string;
|
||||
boxCount?: number | string;
|
||||
estimatedFinishTime?: string;
|
||||
};
|
||||
|
||||
type TrackingRow = TrackingItem & {
|
||||
rowKey: string;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
data?: YxeChronicDiseaseCollection | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}>();
|
||||
|
||||
const detailLoading = ref(false);
|
||||
const detailData = ref<YxeChronicDiseaseCollection | null>(null);
|
||||
let detailRequestSeed = 0;
|
||||
|
||||
const trackingColumns = [
|
||||
{
|
||||
title: '商品ID',
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '商品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
align: 'center',
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '商品编码',
|
||||
dataIndex: 'goodsCode',
|
||||
key: 'goodsCode',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '每日次数',
|
||||
dataIndex: 'dailyUsageCount',
|
||||
key: 'dailyUsageCount',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '每次剂量',
|
||||
dataIndex: 'dosagePerTime',
|
||||
key: 'dosagePerTime',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '单盒可用天数',
|
||||
dataIndex: 'boxUsageDays',
|
||||
key: 'boxUsageDays',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '购买时间',
|
||||
dataIndex: 'purchaseTime',
|
||||
key: 'purchaseTime',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '购买盒数',
|
||||
dataIndex: 'boxCount',
|
||||
key: 'boxCount',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '预计用完时间',
|
||||
dataIndex: 'estimatedFinishTime',
|
||||
key: 'estimatedFinishTime',
|
||||
align: 'center'
|
||||
}
|
||||
];
|
||||
|
||||
const formatText = (value: unknown) => {
|
||||
if (value === null || value === undefined) {
|
||||
return '-';
|
||||
}
|
||||
const text = String(value).trim();
|
||||
return text ? text : '-';
|
||||
};
|
||||
|
||||
const formatDateTime = (value?: string) => {
|
||||
return value ? toDateString(value, 'yyyy-MM-dd HH:mm:ss') : '-';
|
||||
};
|
||||
|
||||
const isYes = (value: unknown) => value === 1 || value === '1';
|
||||
|
||||
const parseTrackingList = (value?: string) => {
|
||||
const raw = String(value ?? '').trim();
|
||||
if (!raw) {
|
||||
return [] as TrackingRow[];
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!Array.isArray(parsed)) {
|
||||
return [] as TrackingRow[];
|
||||
}
|
||||
return parsed.map((item, index) => ({
|
||||
...(item as TrackingItem),
|
||||
rowKey: `${item?.productId ?? 'tracking'}-${index}`
|
||||
}));
|
||||
} catch (error) {
|
||||
return [] as TrackingRow[];
|
||||
}
|
||||
};
|
||||
|
||||
const trackingRows = computed(() =>
|
||||
parseTrackingList(detailData.value?.tracking)
|
||||
);
|
||||
const trackingRawText = computed(() => {
|
||||
const raw = String(detailData.value?.tracking ?? '').trim();
|
||||
if (!raw) {
|
||||
return '';
|
||||
}
|
||||
return trackingRows.value.length ? '' : raw;
|
||||
});
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const resetDetailState = () => {
|
||||
detailRequestSeed += 1;
|
||||
detailLoading.value = false;
|
||||
detailData.value = null;
|
||||
};
|
||||
|
||||
const loadDetail = async (row?: YxeChronicDiseaseCollection | null) => {
|
||||
const currentRow = row ?? null;
|
||||
detailData.value = currentRow;
|
||||
if (!currentRow?.id) {
|
||||
detailLoading.value = false;
|
||||
return;
|
||||
}
|
||||
const requestSeed = ++detailRequestSeed;
|
||||
detailLoading.value = true;
|
||||
try {
|
||||
const data = await getYxeChronicDiseaseCollection(currentRow.id);
|
||||
if (requestSeed !== detailRequestSeed) {
|
||||
return;
|
||||
}
|
||||
detailData.value = data;
|
||||
} catch (e: any) {
|
||||
if (requestSeed !== detailRequestSeed) {
|
||||
return;
|
||||
}
|
||||
message.error(e.message);
|
||||
detailData.value = currentRow;
|
||||
} finally {
|
||||
if (requestSeed === detailRequestSeed) {
|
||||
detailLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [props.visible, props.data?.id] as const,
|
||||
([visible]) => {
|
||||
if (!visible) {
|
||||
resetDetailState();
|
||||
return;
|
||||
}
|
||||
loadDetail(props.data);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.detail-text {
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="name">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.name"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="gender">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.gender"
|
||||
/>
|
||||
</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="birthday">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.birthday"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否高血压" name="highBloodPressure">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否高血压"
|
||||
v-model:value="form.highBloodPressure"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否高血糖" name="highBloodSugar">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否高血糖"
|
||||
v-model:value="form.highBloodSugar"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="是否高血脂" name="hyperlipidemia">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入是否高血脂"
|
||||
v-model:value="form.hyperlipidemia"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="病史" name="medicalHistory">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入病史"
|
||||
v-model:value="form.medicalHistory"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="用药史" name="medicationHistory">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入用药史"
|
||||
v-model:value="form.medicationHistory"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="userId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.userId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="状态, 0待审核 1通过 2驳回" name="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="1">显示</a-radio>
|
||||
<a-radio :value="0">隐藏</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>
|
||||
</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 { addYxeChronicDiseaseCollection, updateYxeChronicDiseaseCollection } from '@/api/yxe/yxeChronicDiseaseCollection';
|
||||
import { YxeChronicDiseaseCollection } from '@/api/yxe/yxeChronicDiseaseCollection/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: YxeChronicDiseaseCollection | 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 form = reactive<YxeChronicDiseaseCollection>({
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
gender: undefined,
|
||||
phone: undefined,
|
||||
birthday: undefined,
|
||||
highBloodPressure: undefined,
|
||||
highBloodSugar: undefined,
|
||||
hyperlipidemia: undefined,
|
||||
medicalHistory: undefined,
|
||||
medicationHistory: undefined,
|
||||
userId: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateYxeChronicDiseaseCollection : addYxeChronicDiseaseCollection;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
:scroll="{ x: 960 }"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
@export="exportData"
|
||||
/>
|
||||
</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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openDetail(record)">详情</a>
|
||||
<a-divider type="vertical" />
|
||||
<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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<YxeChronicDiseaseCollectionEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
@done="reload"
|
||||
/>
|
||||
|
||||
<YxeChronicDiseaseCollectionDetail
|
||||
v-model:visible="showDetail"
|
||||
:data="detailCurrent"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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 type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import YxeChronicDiseaseCollectionEdit from './components/yxeChronicDiseaseCollectionEdit.vue';
|
||||
import YxeChronicDiseaseCollectionDetail from './components/yxeChronicDiseaseCollectionDetail.vue';
|
||||
import {
|
||||
pageYxeChronicDiseaseCollection,
|
||||
removeYxeChronicDiseaseCollection,
|
||||
removeBatchYxeChronicDiseaseCollection,
|
||||
exportYxeChronicDiseaseCollection
|
||||
} from '@/api/yxe/yxeChronicDiseaseCollection';
|
||||
import type {
|
||||
YxeChronicDiseaseCollection,
|
||||
YxeChronicDiseaseCollectionParam
|
||||
} from '@/api/yxe/yxeChronicDiseaseCollection/model';
|
||||
import { openExportFile } from '@/utils/export-file';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<YxeChronicDiseaseCollection[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<YxeChronicDiseaseCollection | null>(null);
|
||||
// 当前详情数据
|
||||
const detailCurrent = ref<YxeChronicDiseaseCollection | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示详情弹窗
|
||||
const showDetail = 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 pageYxeChronicDiseaseCollection({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '性别',
|
||||
dataIndex: 'gender',
|
||||
key: 'gender',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '连锁',
|
||||
dataIndex: 'chainName',
|
||||
key: 'chainName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '门店',
|
||||
dataIndex: 'merchantName',
|
||||
key: 'merchantName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 180,
|
||||
fixed: 'right'
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: YxeChronicDiseaseCollectionParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: YxeChronicDiseaseCollection) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开详情弹窗 */
|
||||
const openDetail = (row: YxeChronicDiseaseCollection) => {
|
||||
detailCurrent.value = row;
|
||||
showDetail.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: YxeChronicDiseaseCollection) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeYxeChronicDiseaseCollection(row.id)
|
||||
.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);
|
||||
removeBatchYxeChronicDiseaseCollection(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
const hide = message.loading('导出中..', 0);
|
||||
tableRef.value?.doRequest(({ where, orders, filters }) => {
|
||||
exportYxeChronicDiseaseCollection({
|
||||
...(where || {}),
|
||||
...(orders || {}),
|
||||
...(filters || {})
|
||||
})
|
||||
.then((file) => {
|
||||
hide();
|
||||
openExportFile(file);
|
||||
message.success('导出成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: YxeChronicDiseaseCollection) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openDetail(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'YxeChronicDiseaseCollection'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<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-button @click="onImport">批量导入</a-button>
|
||||
<a-button danger :disabled="!selection?.length" @click="remove">
|
||||
批量删除
|
||||
</a-button>
|
||||
<a-select
|
||||
v-model:value="where.chain"
|
||||
show-search
|
||||
allow-clear
|
||||
optionFilterProp="label"
|
||||
:options="chainOptions"
|
||||
:loading="chainLoading"
|
||||
placeholder="请选择连锁"
|
||||
style="width: 180px"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value="where.productName"
|
||||
allow-clear
|
||||
placeholder="请输入产品名"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-button type="primary" @click="reload">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { message, type SelectProps } from 'ant-design-vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { YxeChronicDiseaseProductParam } from '@/api/yxe/yxeChronicDiseaseProduct/model';
|
||||
import { listDictData } from '@/api/system/dict-data';
|
||||
import type { DictData } from '@/api/system/dict-data/model';
|
||||
|
||||
defineProps<{
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: YxeChronicDiseaseProductParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'import'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<YxeChronicDiseaseProductParam>({
|
||||
chain: '',
|
||||
productName: ''
|
||||
});
|
||||
const chainOptions = ref<SelectProps['options']>([]);
|
||||
const chainLoading = ref(false);
|
||||
|
||||
type ChainOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
const normalizeText = (value?: unknown) => String(value ?? '').trim();
|
||||
|
||||
const buildChainOption = (item: DictData): ChainOption | null => {
|
||||
const code = normalizeText(item.dictDataCode);
|
||||
const name = normalizeText(item.dictDataName);
|
||||
const value = code || name;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
value,
|
||||
label: name || code
|
||||
};
|
||||
};
|
||||
|
||||
const loadChainOptions = async () => {
|
||||
chainLoading.value = true;
|
||||
try {
|
||||
const list = await listDictData({ dictId: 175 });
|
||||
chainOptions.value = (list || [])
|
||||
.map((item: DictData) => buildChainOption(item))
|
||||
.filter((item): item is ChainOption => !!item);
|
||||
} catch (e) {
|
||||
message.error((e as Error).message);
|
||||
} finally {
|
||||
chainLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
const onImport = () => {
|
||||
emit('import');
|
||||
};
|
||||
|
||||
const remove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadChainOptions();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="720"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:maskClosable="false"
|
||||
:title="isUpdate ? '编辑慢病产品' : '添加慢病产品'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 7, sm: 6, xs: 24 } : { flex: '160px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 16, sm: 18, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="连锁" name="chain">
|
||||
<a-select
|
||||
v-model:value="form.chain"
|
||||
show-search
|
||||
allow-clear
|
||||
optionFilterProp="label"
|
||||
:options="chainOptions"
|
||||
:loading="chainLoading"
|
||||
placeholder="请选择连锁"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="产品名" name="productName">
|
||||
<a-input
|
||||
v-model:value="form.productName"
|
||||
allow-clear
|
||||
placeholder="请输入产品名"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="规格粒/片" name="specCount">
|
||||
<a-input-number
|
||||
v-model:value="form.specCount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入规格粒/片"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="说明书每天使用/次" name="dailyUsageCount">
|
||||
<a-input-number
|
||||
v-model:value="form.dailyUsageCount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="1"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入说明书每天使用次数"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="每次几粒/片" name="dosagePerTime">
|
||||
<a-input-number
|
||||
v-model:value="form.dosagePerTime"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:step="0.5"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入每次使用粒/片数"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="1盒服用几天">
|
||||
<a-input
|
||||
:value="boxUsageDaysText"
|
||||
readonly
|
||||
placeholder="根据规格和用量自动计算"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { Form, message, type SelectProps } from 'ant-design-vue';
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
import type { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import {
|
||||
addYxeChronicDiseaseProduct,
|
||||
updateYxeChronicDiseaseProduct
|
||||
} from '@/api/yxe/yxeChronicDiseaseProduct';
|
||||
import type { YxeChronicDiseaseProduct } from '@/api/yxe/yxeChronicDiseaseProduct/model';
|
||||
import { listDictData } from '@/api/system/dict-data';
|
||||
import type { DictData } from '@/api/system/dict-data/model';
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const useForm = Form.useForm;
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const isUpdate = ref(false);
|
||||
const loading = ref(false);
|
||||
const chainOptions = ref<SelectProps['options']>([]);
|
||||
const chainLoading = ref(false);
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
data?: YxeChronicDiseaseProduct | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const form = reactive<YxeChronicDiseaseProduct>({
|
||||
id: undefined,
|
||||
chain: undefined,
|
||||
productName: undefined,
|
||||
specCount: undefined,
|
||||
dailyUsageCount: undefined,
|
||||
dosagePerTime: undefined,
|
||||
boxUsageDays: undefined,
|
||||
status: 1,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined
|
||||
});
|
||||
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
productName: [
|
||||
{ required: true, type: 'string', message: '请输入产品名', trigger: 'blur' }
|
||||
],
|
||||
chain: [
|
||||
{ required: true, type: 'string', message: '请输入连锁', trigger: 'blur' }
|
||||
],
|
||||
specCount: [
|
||||
{ required: true, type: 'number', message: '请输入规格粒/片', trigger: 'blur' }
|
||||
],
|
||||
dailyUsageCount: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入说明书每天使用/次',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
dosagePerTime: [
|
||||
{
|
||||
required: true,
|
||||
type: 'number',
|
||||
message: '请输入每次几粒/片',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
type ChainOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
code?: string;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
const normalizeText = (value?: unknown) => String(value ?? '').trim();
|
||||
|
||||
const buildChainOption = (item: DictData): ChainOption | null => {
|
||||
const code = normalizeText(item.dictDataCode);
|
||||
const name = normalizeText(item.dictDataName);
|
||||
const value = code || name;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
value,
|
||||
label: name || code,
|
||||
code: code || undefined,
|
||||
name: name || undefined
|
||||
};
|
||||
};
|
||||
|
||||
const ensureCurrentChainOption = (value?: string) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const exists = (chainOptions.value || []).some((item) => {
|
||||
return [item?.value, item?.label]
|
||||
.map((optionValue) => normalizeText(optionValue))
|
||||
.filter(Boolean)
|
||||
.includes(target);
|
||||
});
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
chainOptions.value = [
|
||||
...(chainOptions.value || []),
|
||||
{
|
||||
value: target,
|
||||
label: target
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const loadChainOptions = async () => {
|
||||
chainLoading.value = true;
|
||||
try {
|
||||
const list = await listDictData({ dictId: 175 });
|
||||
chainOptions.value = (list || [])
|
||||
.map((item: DictData) => buildChainOption(item))
|
||||
.filter((item): item is ChainOption => !!item);
|
||||
ensureCurrentChainOption(form.chain);
|
||||
} catch (e) {
|
||||
message.error((e as Error).message);
|
||||
} finally {
|
||||
chainLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const calculateBoxUsageDays = () => {
|
||||
const specCount = Number(form.specCount);
|
||||
const dailyUsageCount = Number(form.dailyUsageCount);
|
||||
const dosagePerTime = Number(form.dosagePerTime);
|
||||
const denominator = dailyUsageCount * dosagePerTime;
|
||||
if (
|
||||
!Number.isFinite(specCount) ||
|
||||
!Number.isFinite(dailyUsageCount) ||
|
||||
!Number.isFinite(dosagePerTime) ||
|
||||
specCount <= 0 ||
|
||||
denominator <= 0
|
||||
) {
|
||||
form.boxUsageDays = undefined;
|
||||
return;
|
||||
}
|
||||
form.boxUsageDays = Number((specCount / denominator).toFixed(2));
|
||||
};
|
||||
|
||||
const boxUsageDaysText = computed(() => {
|
||||
return form.boxUsageDays != null ? `${form.boxUsageDays} 天` : '';
|
||||
});
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
calculateBoxUsageDays();
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const request = isUpdate.value
|
||||
? updateYxeChronicDiseaseProduct
|
||||
: addYxeChronicDiseaseProduct;
|
||||
request({ ...form })
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [form.specCount, form.dailyUsageCount, form.dosagePerTime],
|
||||
() => {
|
||||
calculateBoxUsageDays();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
loadChainOptions();
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
ensureCurrentChainOption(props.data.chain);
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
form.status = 1;
|
||||
}
|
||||
calculateBoxUsageDays();
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="560"
|
||||
:footer="null"
|
||||
title="批量导入慢病产品"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doImport"
|
||||
style="padding: 24px 0; margin-bottom: 12px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-hint">将 Excel 文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
<div class="ele-text-center">
|
||||
<a href="" @click.prevent="downloadTemplate">模板下载</a>
|
||||
</div>
|
||||
<div class="ele-text-secondary ele-text-center" style="margin-top: 4px">
|
||||
导入字段顺序:连锁、产品名、规格粒/片、说明书每天使用/次、每次几粒/片
|
||||
</div>
|
||||
<div v-if="progress.total" class="ele-text-center" style="margin-top: 8px">
|
||||
已处理 {{ progress.success + progress.fail }}/{{ progress.total }}, 成功
|
||||
{{ progress.success }},失败 {{ progress.fail }}
|
||||
</div>
|
||||
<div
|
||||
v-if="errorMessages.length"
|
||||
class="ele-text-danger"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<div>失败明细(最多展示 5 条):</div>
|
||||
<div v-for="(item, index) in errorMessages.slice(0, 5)" :key="index">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
import {
|
||||
addYxeChronicDiseaseProduct,
|
||||
downloadTemplateYxeChronicDiseaseProduct
|
||||
} from '@/api/yxe/yxeChronicDiseaseProduct';
|
||||
import type { YxeChronicDiseaseProduct } from '@/api/yxe/yxeChronicDiseaseProduct/model';
|
||||
import { openExportFile } from '@/utils/export-file';
|
||||
|
||||
type ImportRow = {
|
||||
rowNumber: number;
|
||||
chain: string;
|
||||
productName: string;
|
||||
specCount: string;
|
||||
dailyUsageCount: string;
|
||||
dosagePerTime: string;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const progress = ref({ total: 0, success: 0, fail: 0 });
|
||||
const errorMessages = ref<string[]>([]);
|
||||
|
||||
const normalize = (value?: unknown) => String(value ?? '').trim();
|
||||
const normalizeKey = (value?: unknown) => normalize(value).toLowerCase();
|
||||
|
||||
const readRows = async (file: File): Promise<ImportRow[]> => {
|
||||
const data = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
const rows = XLSX.utils.sheet_to_json<any[]>(sheet, {
|
||||
header: 1,
|
||||
defval: '',
|
||||
raw: false
|
||||
}) as Array<unknown[]>;
|
||||
|
||||
if (!rows.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const header = rows[0].map((item) => normalizeKey(item));
|
||||
const headerIndex = (aliases: string[]) => {
|
||||
const aliasSet = aliases.map((item) => item.toLowerCase());
|
||||
return header.findIndex((item) => aliasSet.includes(item));
|
||||
};
|
||||
|
||||
const chainIndex = headerIndex(['连锁', '所属连锁', 'chain']);
|
||||
const productNameIndex = headerIndex(['产品名', '品名', '产品名称', 'productname']);
|
||||
const specCountIndex = headerIndex(['规格粒/片', '规格', 'speccount']);
|
||||
const dailyUsageCountIndex = headerIndex([
|
||||
'说明书每天使用/次',
|
||||
'每天使用/次',
|
||||
'dailyusagecount'
|
||||
]);
|
||||
const dosagePerTimeIndex = headerIndex([
|
||||
'每次几粒/片',
|
||||
'每次用量',
|
||||
'dosagepertime'
|
||||
]);
|
||||
|
||||
const hasHeader =
|
||||
chainIndex >= 0 ||
|
||||
productNameIndex >= 0 ||
|
||||
specCountIndex >= 0 ||
|
||||
dailyUsageCountIndex >= 0 ||
|
||||
dosagePerTimeIndex >= 0;
|
||||
|
||||
const getByIndex = (row: unknown[], index: number, fallback: number) => {
|
||||
const value = index >= 0 ? row[index] : row[fallback];
|
||||
return normalize(value);
|
||||
};
|
||||
|
||||
return (hasHeader ? rows.slice(1) : rows)
|
||||
.map((row, index) => ({
|
||||
rowNumber: hasHeader ? index + 2 : index + 1,
|
||||
chain: getByIndex(row, chainIndex, 0),
|
||||
productName: getByIndex(row, productNameIndex, 1),
|
||||
specCount: getByIndex(row, specCountIndex, 2),
|
||||
dailyUsageCount: getByIndex(row, dailyUsageCountIndex, 3),
|
||||
dosagePerTime: getByIndex(row, dosagePerTimeIndex, 4)
|
||||
}))
|
||||
.filter(
|
||||
(row) =>
|
||||
row.chain ||
|
||||
row.productName ||
|
||||
row.specCount ||
|
||||
row.dailyUsageCount ||
|
||||
row.dosagePerTime
|
||||
);
|
||||
};
|
||||
|
||||
const parseNumber = (value: string, fieldName: string, rowNumber: number) => {
|
||||
const num = Number(value);
|
||||
if (!Number.isFinite(num) || num <= 0) {
|
||||
throw new Error(`第${rowNumber}行:${fieldName}必须是大于0的数字`);
|
||||
}
|
||||
return num;
|
||||
};
|
||||
|
||||
const buildProductData = (row: ImportRow): YxeChronicDiseaseProduct => {
|
||||
if (!row.chain) {
|
||||
throw new Error(`第${row.rowNumber}行:连锁不能为空`);
|
||||
}
|
||||
if (!row.productName) {
|
||||
throw new Error(`第${row.rowNumber}行:产品名不能为空`);
|
||||
}
|
||||
|
||||
return {
|
||||
chain: row.chain,
|
||||
productName: row.productName,
|
||||
specCount: parseNumber(row.specCount, '规格粒/片', row.rowNumber),
|
||||
dailyUsageCount: parseNumber(
|
||||
row.dailyUsageCount,
|
||||
'说明书每天使用/次',
|
||||
row.rowNumber
|
||||
),
|
||||
dosagePerTime: parseNumber(row.dosagePerTime, '每次几粒/片', row.rowNumber),
|
||||
status: 1
|
||||
};
|
||||
};
|
||||
|
||||
const doImport = async ({ 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;
|
||||
progress.value = { total: 0, success: 0, fail: 0 };
|
||||
errorMessages.value = [];
|
||||
|
||||
try {
|
||||
const rows = await readRows(file as File);
|
||||
if (!rows.length) {
|
||||
message.warning('未读取到可导入的数据');
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.value.total = rows.length;
|
||||
|
||||
for (const row of rows) {
|
||||
try {
|
||||
await addYxeChronicDiseaseProduct(buildProductData(row));
|
||||
progress.value.success += 1;
|
||||
} catch (e) {
|
||||
progress.value.fail += 1;
|
||||
const err = e as Error;
|
||||
errorMessages.value.push(err.message || `第${row.rowNumber}行:导入失败`);
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.value.fail > 0) {
|
||||
message.warning(
|
||||
`导入完成:成功${progress.value.success}条,失败${progress.value.fail}条`
|
||||
);
|
||||
} else {
|
||||
message.success(`导入完成:成功${progress.value.success}条`);
|
||||
updateVisible(false);
|
||||
}
|
||||
|
||||
if (progress.value.success > 0) {
|
||||
emit('done');
|
||||
}
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || '导入失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const downloadTemplate = async () => {
|
||||
try {
|
||||
const file = await downloadTemplateYxeChronicDiseaseProduct();
|
||||
openExportFile(file);
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || '模板下载失败');
|
||||
}
|
||||
};
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<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
|
||||
:selection="selection"
|
||||
@search="reload"
|
||||
@add="openEdit"
|
||||
@import="openImport"
|
||||
@remove="removeBatch"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'boxUsageDays'">
|
||||
{{ record.boxUsageDays ?? '-' }}
|
||||
</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>
|
||||
|
||||
<YxeChronicDiseaseProductEdit
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
@done="reload"
|
||||
/>
|
||||
<YxeChronicDiseaseProductImport
|
||||
v-model:visible="showImport"
|
||||
@done="reload"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</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 {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import Search from './components/search.vue';
|
||||
import YxeChronicDiseaseProductEdit from './components/yxeChronicDiseaseProductEdit.vue';
|
||||
import YxeChronicDiseaseProductImport from './components/yxeChronicDiseaseProductImport.vue';
|
||||
import {
|
||||
pageYxeChronicDiseaseProduct,
|
||||
removeBatchYxeChronicDiseaseProduct,
|
||||
removeYxeChronicDiseaseProduct
|
||||
} from '@/api/yxe/yxeChronicDiseaseProduct';
|
||||
import type {
|
||||
YxeChronicDiseaseProduct,
|
||||
YxeChronicDiseaseProductParam
|
||||
} from '@/api/yxe/yxeChronicDiseaseProduct/model';
|
||||
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
const selection = ref<YxeChronicDiseaseProduct[]>([]);
|
||||
const current = ref<YxeChronicDiseaseProduct | null>(null);
|
||||
const showEdit = ref(false);
|
||||
const showImport = ref(false);
|
||||
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageYxeChronicDiseaseProduct({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: 90,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '连锁',
|
||||
dataIndex: 'chain',
|
||||
key: 'chain',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '产品名',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '规格粒/片',
|
||||
dataIndex: 'specCount',
|
||||
key: 'specCount',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '说明书每天使用/次',
|
||||
dataIndex: 'dailyUsageCount',
|
||||
key: 'dailyUsageCount',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '每次几粒/片',
|
||||
dataIndex: 'dosagePerTime',
|
||||
key: 'dosagePerTime',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '1盒服用几天',
|
||||
dataIndex: 'boxUsageDays',
|
||||
key: 'boxUsageDays',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '添加时间',
|
||||
dataIndex: 'createTime',
|
||||
key: 'createTime',
|
||||
sorter: true,
|
||||
align: 'center',
|
||||
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm:ss')
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
const reload = (where?: YxeChronicDiseaseProductParam) => {
|
||||
selection.value = [];
|
||||
tableRef.value?.reload({ where });
|
||||
};
|
||||
|
||||
const openEdit = (row?: YxeChronicDiseaseProduct) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
const remove = (row: YxeChronicDiseaseProduct) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeYxeChronicDiseaseProduct(row.id)
|
||||
.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);
|
||||
removeBatchYxeChronicDiseaseProduct(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const customRow = (record: YxeChronicDiseaseProduct) => {
|
||||
return {
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,119 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-button type="primary" @click="openImport">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>导入</span>
|
||||
</a-button>
|
||||
<a-upload
|
||||
accept=".zip"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUploadCoverZip"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>上传封面压缩包</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-popconfirm
|
||||
title="确定要删除选中的记录吗?"
|
||||
:disabled="!props.selection?.length"
|
||||
@confirm="removeBatch"
|
||||
>
|
||||
<a-button
|
||||
danger
|
||||
type="primary"
|
||||
class="ele-btn-icon"
|
||||
:disabled="!props.selection?.length"
|
||||
>
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
<span>批量删除</span>
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="请输入编号/品名/厂家关键词"
|
||||
style="width: 260px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
UploadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import type { YxeGoodsParam } from '@/api/yxe/yxeGoods/model';
|
||||
import { watch } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: YxeGoodsParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'import'): void;
|
||||
(e: 'coverZip', file: File): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<YxeGoodsParam>({
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const openImport = () => {
|
||||
emit('import');
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
emit('search', { ...where });
|
||||
};
|
||||
|
||||
const beforeUploadCoverZip = (file: File) => {
|
||||
emit('coverZip', file);
|
||||
return false;
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.selection,
|
||||
() => {}
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,295 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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">
|
||||
<a-form-item label="分类" name="cate">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分类"
|
||||
v-model:value="form.cate"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="所属连锁" name="chain">
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
allow-clear
|
||||
:loading="chainLoading"
|
||||
:options="chainOptions"
|
||||
placeholder="请选择所属连锁"
|
||||
v-model:value="form.chain"
|
||||
@change="onChangeChain"
|
||||
/>
|
||||
</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="品名" name="title">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入品名"
|
||||
v-model:value="form.title"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="规格" name="spec">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入规格"
|
||||
v-model:value="form.spec"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="厂家" name="maker">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入厂家"
|
||||
v-model:value="form.maker"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="厂家简称" name="makerAbb">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入厂家简称"
|
||||
v-model:value="form.makerAbb"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图片" name="cover">
|
||||
<a-space>
|
||||
<a-image
|
||||
v-if="form.cover"
|
||||
:src="form.cover"
|
||||
style="width: 100px; height: 100px; object-fit: cover"
|
||||
/>
|
||||
<UploadFile v-model:value="form.cover" />
|
||||
</a-space>
|
||||
</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, type SelectProps } from "ant-design-vue";
|
||||
import { assignObject, uuid } from "ele-admin-pro";
|
||||
import { addYxeGoods, updateYxeGoods } from "@/api/yxe/yxeGoods";
|
||||
import { YxeGoods } from "@/api/yxe/yxeGoods/model";
|
||||
import { useThemeStore } from "@/store/modules/theme";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { FormInstance } from "ant-design-vue/es/form";
|
||||
import { FileRecord } from "@/api/system/file/model";
|
||||
import type { DictData } from "@/api/system/dict-data/model";
|
||||
import { listDictData } from "@/api/system/dict-data";
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: YxeGoods | 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 chainOptions = ref<SelectProps["options"]>([]);
|
||||
const chainLoading = ref(false);
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<YxeGoods>({
|
||||
id: undefined,
|
||||
cate: undefined,
|
||||
chain: undefined,
|
||||
code: undefined,
|
||||
title: undefined,
|
||||
spec: undefined,
|
||||
maker: undefined,
|
||||
makerAbb: undefined,
|
||||
cover: undefined,
|
||||
sortNumber: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
extra_income: undefined,
|
||||
extraIncome: undefined,
|
||||
extraStartTime: undefined,
|
||||
extraEndTime: undefined
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit("update:visible", value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form,
|
||||
extra_income: form.extra_income || form.extraIncome,
|
||||
extraIncome: form.extra_income || form.extraIncome
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateYxeGoods : addYxeGoods;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit("done");
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
};
|
||||
|
||||
type ChainOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
code?: string;
|
||||
name?: string;
|
||||
};
|
||||
const normalizeText = (value?: unknown) => String(value ?? '').trim();
|
||||
const buildChainOption = (item: DictData): ChainOption | null => {
|
||||
const code = normalizeText(item.dictDataCode);
|
||||
const name = normalizeText(item.dictDataName);
|
||||
const value = code || name;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
value,
|
||||
label: name || code,
|
||||
code: code || undefined,
|
||||
name: name || undefined
|
||||
};
|
||||
};
|
||||
|
||||
const ensureCurrentChainOption = (value?: string) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const exists = (chainOptions.value || []).some((item) => {
|
||||
const option = item as ChainOption;
|
||||
return [option.value, option.code, option.name]
|
||||
.map((optionValue) => normalizeText(optionValue))
|
||||
.filter(Boolean)
|
||||
.includes(target);
|
||||
});
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
chainOptions.value = [
|
||||
...(chainOptions.value || []),
|
||||
{
|
||||
value: target,
|
||||
label: target,
|
||||
code: target,
|
||||
name: target
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const resolveChainValue = (value?: string) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return undefined;
|
||||
}
|
||||
const matched = (chainOptions.value || []).find((item) => {
|
||||
const option = item as ChainOption;
|
||||
return [option.value, option.code, option.name]
|
||||
.map((optionValue) => normalizeText(optionValue))
|
||||
.filter(Boolean)
|
||||
.includes(target);
|
||||
}) as ChainOption | undefined;
|
||||
return matched?.value ?? target;
|
||||
};
|
||||
|
||||
const loadChainOptions = async () => {
|
||||
chainLoading.value = true;
|
||||
try {
|
||||
const list = await listDictData({ dictId: 175 });
|
||||
chainOptions.value = (list || [])
|
||||
.map((item: DictData) => buildChainOption(item))
|
||||
.filter((item): item is ChainOption => !!item);
|
||||
ensureCurrentChainOption(form.chain);
|
||||
form.chain = resolveChainValue(form.chain);
|
||||
} catch (e) {
|
||||
message.error((e as Error).message);
|
||||
} finally {
|
||||
chainLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (visible) => {
|
||||
if (visible) {
|
||||
await loadChainOptions();
|
||||
if (props.data) {
|
||||
assignObject(form, props.data);
|
||||
form.extra_income = form.extra_income || form.extraIncome;
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,249 @@
|
||||
<!-- 商品导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="560"
|
||||
:footer="null"
|
||||
title="批量导入商品"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doImport"
|
||||
style="padding: 24px 0; margin-bottom: 12px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-hint">将 Excel 文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
<div class="ele-text-center">
|
||||
<a href="" @click.prevent="downloadTemplate">模板下载</a>
|
||||
</div>
|
||||
<div class="ele-text-secondary ele-text-center" style="margin-top: 4px">
|
||||
导入字段顺序:厂家简称、产品编码、分类、连锁、品名、规格、厂家、奖励金额
|
||||
</div>
|
||||
<div v-if="progress.total" class="ele-text-center" style="margin-top: 8px">
|
||||
已处理 {{ progress.success + progress.fail }}/{{ progress.total }}, 成功
|
||||
{{ progress.success }},失败 {{ progress.fail }}
|
||||
</div>
|
||||
<div
|
||||
v-if="errorMessages.length"
|
||||
class="ele-text-danger"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<div>失败明细(最多展示 5 条):</div>
|
||||
<div v-for="(item, index) in errorMessages.slice(0, 5)" :key="index">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { addYxeGoods } from '@/api/yxe/yxeGoods';
|
||||
import type { YxeGoods } from '@/api/yxe/yxeGoods/model';
|
||||
|
||||
type ImportRow = {
|
||||
rowNumber: number;
|
||||
makerAbb: string;
|
||||
code: string;
|
||||
cate: string;
|
||||
chain: string;
|
||||
title: string;
|
||||
spec: string;
|
||||
maker: string;
|
||||
extra_income: string;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const progress = ref({ total: 0, success: 0, fail: 0 });
|
||||
const errorMessages = ref<string[]>([]);
|
||||
|
||||
const normalize = (value?: unknown) => String(value ?? '').trim();
|
||||
const normalizeKey = (value?: unknown) => normalize(value).toLowerCase();
|
||||
|
||||
const readRows = async (file: File): Promise<ImportRow[]> => {
|
||||
const data = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
const rows = XLSX.utils.sheet_to_json<any[]>(sheet, {
|
||||
header: 1,
|
||||
defval: '',
|
||||
raw: false
|
||||
}) as Array<unknown[]>;
|
||||
|
||||
if (!rows.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const header = rows[0].map((item) => normalizeKey(item));
|
||||
const headerIndex = (aliases: string[]) => {
|
||||
const aliasSet = aliases.map((item) => item.toLowerCase());
|
||||
return header.findIndex((item) => aliasSet.includes(item));
|
||||
};
|
||||
|
||||
const makerAbbIndex = headerIndex(['厂家简称', '简称', 'makerabb']);
|
||||
const codeIndex = headerIndex([
|
||||
'产品编码',
|
||||
'商品编码',
|
||||
'商品编号',
|
||||
'编号',
|
||||
'code'
|
||||
]);
|
||||
const cateIndex = headerIndex(['分类', '分类名称', 'cate', 'category']);
|
||||
const chainIndex = headerIndex(['连锁', '所属连锁', 'chain']);
|
||||
const titleIndex = headerIndex(['品名', '商品名称', '名称', 'title']);
|
||||
const specIndex = headerIndex(['规格', 'spec']);
|
||||
const makerIndex = headerIndex(['厂家', '生产厂家', 'maker']);
|
||||
const extraIncomeIndex = headerIndex([
|
||||
'奖励金额',
|
||||
'额外奖励',
|
||||
'extra_income',
|
||||
'extraincome'
|
||||
]);
|
||||
|
||||
const hasHeader =
|
||||
makerAbbIndex >= 0 ||
|
||||
codeIndex >= 0 ||
|
||||
cateIndex >= 0 ||
|
||||
chainIndex >= 0 ||
|
||||
titleIndex >= 0 ||
|
||||
specIndex >= 0 ||
|
||||
makerIndex >= 0 ||
|
||||
extraIncomeIndex >= 0;
|
||||
|
||||
const getByIndex = (row: unknown[], index: number, fallback: number) => {
|
||||
const value = index >= 0 ? row[index] : row[fallback];
|
||||
return normalize(value);
|
||||
};
|
||||
|
||||
return (hasHeader ? rows.slice(1) : rows)
|
||||
.map((row, index) => ({
|
||||
rowNumber: hasHeader ? index + 2 : index + 1,
|
||||
makerAbb: getByIndex(row, makerAbbIndex, 0),
|
||||
code: getByIndex(row, codeIndex, 1),
|
||||
cate: getByIndex(row, cateIndex, 2),
|
||||
chain: getByIndex(row, chainIndex, 3),
|
||||
title: getByIndex(row, titleIndex, 4),
|
||||
spec: getByIndex(row, specIndex, 5),
|
||||
maker: getByIndex(row, makerIndex, 6),
|
||||
extra_income: getByIndex(row, extraIncomeIndex, 7)
|
||||
}))
|
||||
.filter(
|
||||
(row) =>
|
||||
row.makerAbb ||
|
||||
row.code ||
|
||||
row.cate ||
|
||||
row.chain ||
|
||||
row.title ||
|
||||
row.spec ||
|
||||
row.maker ||
|
||||
row.extra_income
|
||||
);
|
||||
};
|
||||
|
||||
const doImport = async ({ 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;
|
||||
progress.value = { total: 0, success: 0, fail: 0 };
|
||||
errorMessages.value = [];
|
||||
|
||||
try {
|
||||
const rows = await readRows(file as File);
|
||||
if (!rows.length) {
|
||||
message.warning('未读取到可导入的数据');
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.value.total = rows.length;
|
||||
|
||||
for (const row of rows) {
|
||||
try {
|
||||
const data: YxeGoods = {
|
||||
makerAbb: row.makerAbb,
|
||||
code: row.code,
|
||||
cate: row.cate,
|
||||
chain: row.chain,
|
||||
title: row.title,
|
||||
spec: row.spec,
|
||||
maker: row.maker,
|
||||
extra_income: row.extra_income || undefined,
|
||||
extraIncome: row.extra_income || undefined
|
||||
};
|
||||
await addYxeGoods(data);
|
||||
progress.value.success += 1;
|
||||
} catch (e) {
|
||||
progress.value.fail += 1;
|
||||
const err = e as Error;
|
||||
errorMessages.value.push(
|
||||
`第${row.rowNumber}行:${err.message || '导入失败'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.value.fail > 0) {
|
||||
message.warning(
|
||||
`导入完成:成功${progress.value.success}条,失败${progress.value.fail}条`
|
||||
);
|
||||
} else {
|
||||
message.success(`导入完成:成功${progress.value.success}条`);
|
||||
updateVisible(false);
|
||||
}
|
||||
|
||||
if (progress.value.success > 0) {
|
||||
emit('done');
|
||||
}
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || '导入失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const downloadTemplate = () => {
|
||||
const workbook = XLSX.utils.book_new();
|
||||
const sheet = XLSX.utils.aoa_to_sheet([
|
||||
['厂家简称', '产品编码', '分类', '连锁', '品名', '规格', '厂家', '奖励金额']
|
||||
]);
|
||||
XLSX.utils.book_append_sheet(workbook, sheet, '商品导入模板');
|
||||
XLSX.writeFile(workbook, '药销儿商品导入模板.xlsx');
|
||||
};
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,303 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<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"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
@import="openImport"
|
||||
@coverZip="handleCoverZipUpload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cover'">
|
||||
<a-image
|
||||
:src="record.cover"
|
||||
v-if="record.cover"
|
||||
style="width: 50px; height: 50px; object-fit: cover"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<YxeGoodsEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
<YxeGoodsImport v-model:visible="showImport" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-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 YxeGoodsEdit from './components/yxeGoodsEdit.vue';
|
||||
import YxeGoodsImport from './components/yxeGoodsImport.vue';
|
||||
import {
|
||||
pageYxeGoods,
|
||||
removeYxeGoods,
|
||||
removeBatchYxeGoods,
|
||||
uploadYxeGoodsCoverZip
|
||||
} from '@/api/yxe/yxeGoods';
|
||||
import type { YxeGoods, YxeGoodsParam } from '@/api/yxe/yxeGoods/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<YxeGoods[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<YxeGoods | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示导入弹窗
|
||||
const showImport = 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 pageYxeGoods({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'cate',
|
||||
key: 'cate',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '连锁',
|
||||
dataIndex: 'chain',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '编号',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '品名',
|
||||
dataIndex: 'title',
|
||||
key: 'title',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '规格',
|
||||
dataIndex: 'spec',
|
||||
key: 'spec',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '厂家',
|
||||
dataIndex: 'maker',
|
||||
key: 'maker',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '厂家简称',
|
||||
dataIndex: 'makerAbb',
|
||||
key: 'makerAbb',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '封面',
|
||||
dataIndex: 'cover',
|
||||
key: 'cover',
|
||||
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?: YxeGoodsParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: YxeGoods) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开导入弹窗 */
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
const handleCoverZipUpload = (file: File) => {
|
||||
const fileName = file.name?.toLowerCase?.() ?? '';
|
||||
if (!fileName.endsWith('.zip')) {
|
||||
message.error('只能选择 zip 压缩包');
|
||||
return false;
|
||||
}
|
||||
const hide = message.loading('封面压缩包处理中..', 0);
|
||||
uploadYxeGoodsCoverZip(file)
|
||||
.then((result) => {
|
||||
hide();
|
||||
message.success(
|
||||
`封面压缩包处理完成,识别${result.totalFiles ?? 0}个文件,更新${
|
||||
result.updatedCount ?? 0
|
||||
}条记录,跳过${result.skippedCount ?? 0}个文件`
|
||||
);
|
||||
if (result.notFoundTitles?.length) {
|
||||
message.warning(
|
||||
`有 ${result.notFoundTitles.length} 个商品标题未匹配`
|
||||
);
|
||||
}
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || '封面压缩包处理失败');
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: YxeGoods) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeYxeGoods(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeBatchYxeGoods(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: YxeGoods) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'YxeGoods'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,158 @@
|
||||
<!-- 搜索表单 -->
|
||||
<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-button type="primary" @click="openImport">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>导入</span>
|
||||
</a-button>
|
||||
<a-upload
|
||||
accept=".zip"
|
||||
:show-upload-list="false"
|
||||
:before-upload="beforeUploadCoverZip"
|
||||
>
|
||||
<a-button type="primary">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>上传封面压缩包</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-input-search
|
||||
v-model:value="where.keywords"
|
||||
allow-clear
|
||||
placeholder="请输入名称"
|
||||
@pressEnter="reload"
|
||||
@search="reload"
|
||||
/>
|
||||
<a-input-search
|
||||
v-model:value="where.chain"
|
||||
allow-clear
|
||||
placeholder="请输入连锁"
|
||||
@pressEnter="reload"
|
||||
@search="reload"
|
||||
/>
|
||||
<!-- <a-select-->
|
||||
<!-- v-model:value="where.merchantId"-->
|
||||
<!-- allow-clear-->
|
||||
<!-- show-search-->
|
||||
<!-- optionFilterProp="label"-->
|
||||
<!-- :loading="merchantLoading"-->
|
||||
<!-- :options="merchantOptions"-->
|
||||
<!-- placeholder="请选择门店"-->
|
||||
<!-- style="min-width: 300px"-->
|
||||
<!-- @change="reload"-->
|
||||
<!-- />-->
|
||||
<a-input-search
|
||||
v-model:value="where.cate"
|
||||
allow-clear
|
||||
placeholder="请输入分类"
|
||||
@pressEnter="reload"
|
||||
@search="reload"
|
||||
/>
|
||||
<a-button type="primary" @click="reload">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
<a-popconfirm title="确定要批量删除选择的记录吗?" @confirm="removeBatch">
|
||||
<a-button type="primary" danger :disabled="!selection?.length">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
</template>
|
||||
批量删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
UploadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { listMerchant } from '@/api/shop/merchant';
|
||||
import type { Merchant } from '@/api/shop/merchant/model';
|
||||
import type { SelectProps } from 'ant-design-vue';
|
||||
import type { YxeProductParam } from '@/api/yxe/yxeProduct/model';
|
||||
|
||||
defineProps<{
|
||||
// 选中的角色
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: YxeProductParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'import'): void;
|
||||
(e: 'coverZip', file: File): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<YxeProductParam>({
|
||||
keywords: '',
|
||||
merchantId: undefined,
|
||||
chain: undefined,
|
||||
cate: undefined
|
||||
});
|
||||
|
||||
const merchantOptions = ref<SelectProps['options']>([]);
|
||||
const merchantLoading = ref(false);
|
||||
|
||||
// 新增
|
||||
const add = () => {
|
||||
emit('add');
|
||||
};
|
||||
|
||||
const openImport = () => {
|
||||
emit('import');
|
||||
};
|
||||
|
||||
const beforeUploadCoverZip = (file: File) => {
|
||||
emit('coverZip', file);
|
||||
return false;
|
||||
};
|
||||
|
||||
const removeBatch = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
reload();
|
||||
};
|
||||
|
||||
const loadMerchantOptions = () => {
|
||||
merchantLoading.value = true;
|
||||
listMerchant()
|
||||
.then((list) => {
|
||||
merchantOptions.value = (list || []).map((item: Merchant) => ({
|
||||
value: item.id,
|
||||
label: item.name ?? `${item.id ?? ''}`
|
||||
}));
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
merchantLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadMerchantOptions();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,725 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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">
|
||||
<a-form-item label="所属连锁" name="chain">
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
allow-clear
|
||||
:loading="chainLoading"
|
||||
:options="chainOptions"
|
||||
placeholder="请选择所属连锁"
|
||||
v-model:value="form.chain"
|
||||
@change="onChangeChain"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- <a-form-item label="所属门店" name="merchantId">-->
|
||||
<!-- <a-select-->
|
||||
<!-- show-search-->
|
||||
<!-- optionFilterProp="label"-->
|
||||
<!-- allow-clear-->
|
||||
<!-- :disabled="!form.chain"-->
|
||||
<!-- :loading="merchantLoading"-->
|
||||
<!-- :options="merchantOptions"-->
|
||||
<!-- :placeholder="form.chain ? '请选择门店' : '请先选择所属连锁'"-->
|
||||
<!-- v-model:value="form.merchantId"-->
|
||||
<!-- @change="onChangeMerchant"-->
|
||||
<!-- />-->
|
||||
<!-- </a-form-item>-->
|
||||
<a-form-item label="商品">
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
allow-clear
|
||||
:loading="goodsLoading"
|
||||
:options="goodsOptions"
|
||||
placeholder="请选择商品"
|
||||
v-model:value="selectedGoodsId"
|
||||
@change="onChangeGoods"
|
||||
/>
|
||||
</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="图片" name="cover">
|
||||
<a-space>
|
||||
<a-image
|
||||
v-if="form.cover"
|
||||
:src="form.cover"
|
||||
style="width: 100px; height: 100px; object-fit: cover"
|
||||
/>
|
||||
<UploadFile v-model:value="form.cover" />
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
<a-form-item label="品名" name="title">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入品名"
|
||||
v-model:value="form.title"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="规格" name="spec">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入规格"
|
||||
v-model:value="form.spec"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分类" name="cate">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入分类"
|
||||
v-model:value="form.cate"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="厂家" name="maker">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入厂家"
|
||||
v-model:value="form.maker"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="厂家简称" name="makerAbb">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入厂家简称"
|
||||
v-model:value="form.makerAbb"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="任务奖励" name="taskIncome" v-if="type === 0">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.taskIncome"
|
||||
/>
|
||||
</a-form-item>
|
||||
<template v-if="type === 1">
|
||||
<a-form-item label="晒单政策" name="income">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.income"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="分享有效期" name="startTime">
|
||||
<a-range-picker
|
||||
class="ele-fluid"
|
||||
value-format="YYYY-MM-DD HH:mm"
|
||||
:show-time="{ format: 'HH:mm' }"
|
||||
:placeholder="['请选择开始时间', '请选择结束时间']"
|
||||
v-model:value="shareTimeRange"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="额外奖励活动" name="extraIncomeList">
|
||||
<a-space direction="vertical" class="ele-fluid" size="small">
|
||||
<div
|
||||
v-if="!extraIncomeRows.length"
|
||||
class="ele-text-secondary extra-income-empty"
|
||||
>
|
||||
暂无额外奖励,请点击下方添加
|
||||
</div>
|
||||
<div
|
||||
v-for="(item, index) in extraIncomeRows"
|
||||
:key="item.rowId"
|
||||
class="extra-income-row"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入额外奖励"
|
||||
class="extra-income-input"
|
||||
v-model:value="item.income"
|
||||
/>
|
||||
<a-range-picker
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:show-time="{ format: 'HH:mm:ss' }"
|
||||
:placeholder="['请选择开始时间', '请选择结束时间']"
|
||||
class="extra-income-range"
|
||||
:value="getExtraTimeRange(item)"
|
||||
@update:value="(value) => updateExtraTimeRange(item, value)"
|
||||
/>
|
||||
<a-button
|
||||
type="text"
|
||||
danger
|
||||
@click="removeExtraIncomeRow(index)"
|
||||
:disabled="extraIncomeRows.length <= 1"
|
||||
>
|
||||
删除
|
||||
</a-button>
|
||||
</div>
|
||||
<a-button type="dashed" block @click="addExtraIncomeRow()">
|
||||
添加一行
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, reactive, watch } from 'vue';
|
||||
import { Form, message, type SelectProps } from 'ant-design-vue';
|
||||
import { assignObject, toDateString } from 'ele-admin-pro';
|
||||
import { addYxeProduct, updateYxeProduct } from '@/api/yxe/yxeProduct';
|
||||
import { YxeProduct } from '@/api/yxe/yxeProduct/model';
|
||||
import { FormInstance } from 'ant-design-vue/es/form';
|
||||
import { listMerchantMini } from '@/api/shop/merchant';
|
||||
import type { Merchant } from '@/api/shop/merchant/model';
|
||||
import { listYxeGoods } from '@/api/yxe/yxeGoods';
|
||||
import type { YxeGoods } from '@/api/yxe/yxeGoods/model';
|
||||
import { YxeProductExtraIncome } from '@/api/yxe/yxeProductExtraIncome/model';
|
||||
import { listDictData } from '@/api/system/dict-data';
|
||||
import type { DictData } from '@/api/system/dict-data/model';
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
type: number;
|
||||
// 修改回显的数据
|
||||
data?: YxeProduct | 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 chainOptions = ref<SelectProps['options']>([]);
|
||||
const chainLoading = ref(false);
|
||||
const goodsOptions = ref<SelectProps['options']>([]);
|
||||
const goodsLoading = ref(false);
|
||||
const goodsList = ref<YxeGoods[]>([]);
|
||||
const selectedGoodsId = ref<number>();
|
||||
|
||||
// 用户信息
|
||||
const form = reactive<YxeProduct>({
|
||||
id: undefined,
|
||||
merchantId: undefined,
|
||||
type: undefined,
|
||||
chain: undefined,
|
||||
code: undefined,
|
||||
title: undefined,
|
||||
cover: undefined,
|
||||
spec: undefined,
|
||||
maker: undefined,
|
||||
cate: undefined,
|
||||
makerAbb: undefined,
|
||||
income: undefined,
|
||||
taskIncome: undefined,
|
||||
sortNumber: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
extraIncome: undefined,
|
||||
extraStartTime: undefined,
|
||||
extraEndTime: undefined
|
||||
});
|
||||
const extraIncomeRows = ref<YxeProductExtraIncome[]>([]);
|
||||
|
||||
const createExtraIncomeRow = (
|
||||
row?: Partial<YxeProductExtraIncome>
|
||||
): YxeProductExtraIncome => ({
|
||||
income: row?.income,
|
||||
startTime: row?.startTime,
|
||||
endTime: row?.endTime
|
||||
});
|
||||
|
||||
const addExtraIncomeRow = (row?: Partial<YxeProductExtraIncome>) => {
|
||||
extraIncomeRows.value.push(createExtraIncomeRow(row));
|
||||
};
|
||||
|
||||
const removeExtraIncomeRow = (index: number) => {
|
||||
extraIncomeRows.value.splice(index, 1);
|
||||
if (!extraIncomeRows.value.length) {
|
||||
addExtraIncomeRow();
|
||||
}
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
taskIncome: [{ required: true, message: '请输入任务奖励' }]
|
||||
});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
const shareTimeRange = computed({
|
||||
get: () => {
|
||||
const start = formatMinuteValue(form.startTime);
|
||||
const end = formatMinuteValue(form.endTime);
|
||||
if (!start && !end) {
|
||||
return undefined;
|
||||
}
|
||||
return start && end ? [start, end] : undefined;
|
||||
},
|
||||
set: (value?: string[]) => {
|
||||
form.startTime = value?.[0];
|
||||
form.endTime = value?.[1];
|
||||
}
|
||||
});
|
||||
|
||||
const formatMinuteValue = (value?: string) => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return toDateString(value, 'yyyy-MM-dd HH:mm');
|
||||
};
|
||||
|
||||
const getExtraTimeRange = (row: YxeProductExtraIncome) => {
|
||||
if (!row.startTime && !row.startTime) {
|
||||
return undefined;
|
||||
}
|
||||
return [row.startTime, row.endTime];
|
||||
};
|
||||
|
||||
const updateExtraTimeRange = (
|
||||
row: YxeProductExtraIncome,
|
||||
value?: string[]
|
||||
) => {
|
||||
row.startTime = value?.[0];
|
||||
row.endTime = value?.[1];
|
||||
};
|
||||
|
||||
const initExtraIncomeRows = (data?: YxeProduct | null) => {
|
||||
const rows =
|
||||
data?.extraIncomeList?.map((item) =>
|
||||
createExtraIncomeRow({
|
||||
income: item.income ?? item.income,
|
||||
startTime: item.startTime ?? item.startTime,
|
||||
endTime: item.endTime ?? item.endTime
|
||||
})
|
||||
) ?? [];
|
||||
const filteredRows = rows.filter(
|
||||
(item) => item.income || item.startTime || item.endTime
|
||||
);
|
||||
if (filteredRows.length) {
|
||||
extraIncomeRows.value = filteredRows;
|
||||
return;
|
||||
}
|
||||
if (data?.extraIncome || data?.extraStartTime || data?.extraEndTime) {
|
||||
extraIncomeRows.value = [
|
||||
createExtraIncomeRow({
|
||||
income: data.income,
|
||||
endTime: data.endTime,
|
||||
startTime: data.startTime
|
||||
})
|
||||
];
|
||||
return;
|
||||
}
|
||||
extraIncomeRows.value = [createExtraIncomeRow()];
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const formData = {
|
||||
...form,
|
||||
type: props.type,
|
||||
extraIncomeList: extraIncomeRows.value
|
||||
.filter((item) => item.income || item.startTime || item.endTime)
|
||||
.map((item) => ({
|
||||
income: item.income,
|
||||
startTime: item.startTime,
|
||||
endTime: item.endTime
|
||||
}))
|
||||
};
|
||||
delete formData.extraIncome;
|
||||
delete formData.extraStartTime;
|
||||
delete formData.extraEndTime;
|
||||
const saveOrUpdate = isUpdate.value ? updateYxeProduct : addYxeProduct;
|
||||
saveOrUpdate(formData)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const merchantOptions = ref<SelectProps['options']>([]);
|
||||
const merchantLoading = ref(false);
|
||||
const merchantList = ref<Merchant[]>([]);
|
||||
const merchantLoaded = ref(false);
|
||||
const merchantLoadedChain = ref('');
|
||||
type ChainOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
code?: string;
|
||||
name?: string;
|
||||
};
|
||||
type YxeProductEditData = YxeProduct & {
|
||||
merchant?: Merchant | null;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
extraStartDate?: string;
|
||||
extraEndDate?: string;
|
||||
};
|
||||
|
||||
const normalizeText = (value?: unknown) => String(value ?? '').trim();
|
||||
|
||||
const buildChainOption = (item: DictData): ChainOption | null => {
|
||||
const code = normalizeText(item.dictDataCode);
|
||||
const name = normalizeText(item.dictDataName);
|
||||
const value = code || name;
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
value,
|
||||
label: name || code,
|
||||
code: code || undefined,
|
||||
name: name || undefined
|
||||
};
|
||||
};
|
||||
|
||||
const ensureCurrentChainOption = (value?: string) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const exists = (chainOptions.value || []).some((item) => {
|
||||
const option = item as ChainOption;
|
||||
return [option.value, option.code, option.name]
|
||||
.map((optionValue) => normalizeText(optionValue))
|
||||
.filter(Boolean)
|
||||
.includes(target);
|
||||
});
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
chainOptions.value = [
|
||||
...(chainOptions.value || []),
|
||||
{
|
||||
value: target,
|
||||
label: target,
|
||||
code: target,
|
||||
name: target
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const resolveChainValue = (value?: string) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return undefined;
|
||||
}
|
||||
const matched = (chainOptions.value || []).find((item) => {
|
||||
const option = item as ChainOption;
|
||||
return [option.value, option.code, option.name]
|
||||
.map((optionValue) => normalizeText(optionValue))
|
||||
.filter(Boolean)
|
||||
.includes(target);
|
||||
}) as ChainOption | undefined;
|
||||
return matched?.value ?? target;
|
||||
};
|
||||
|
||||
const loadChainOptions = async () => {
|
||||
chainLoading.value = true;
|
||||
try {
|
||||
const list = await listDictData({ dictId: 175 });
|
||||
chainOptions.value = (list || [])
|
||||
.map((item: DictData) => buildChainOption(item))
|
||||
.filter((item): item is ChainOption => !!item);
|
||||
ensureCurrentChainOption(form.chain);
|
||||
form.chain = resolveChainValue(form.chain);
|
||||
} catch (e) {
|
||||
message.error((e as Error).message);
|
||||
} finally {
|
||||
chainLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const getChainCandidates = (value?: string) => {
|
||||
const target = normalizeText(value);
|
||||
if (!target) {
|
||||
return [];
|
||||
}
|
||||
const option = (chainOptions.value || []).find(
|
||||
(item) => normalizeText(item?.value) === target
|
||||
) as ChainOption | undefined;
|
||||
return Array.from(
|
||||
new Set(
|
||||
[
|
||||
target,
|
||||
normalizeText(option?.code),
|
||||
normalizeText(option?.name)
|
||||
].filter(Boolean)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const ensureMerchantListLoaded = async (chainValue?: string) => {
|
||||
const target = normalizeText(chainValue);
|
||||
if (!target) {
|
||||
merchantList.value = [];
|
||||
merchantLoaded.value = false;
|
||||
merchantLoadedChain.value = '';
|
||||
return;
|
||||
}
|
||||
if (merchantLoaded.value && merchantLoadedChain.value === target) {
|
||||
return;
|
||||
}
|
||||
merchantLoading.value = true;
|
||||
try {
|
||||
merchantList.value = await listMerchantMini({
|
||||
chain: target
|
||||
});
|
||||
merchantLoaded.value = true;
|
||||
merchantLoadedChain.value = target;
|
||||
} catch (e) {
|
||||
message.error((e as Error).message);
|
||||
} finally {
|
||||
merchantLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadMerchantOptions = async (chainValue?: string) => {
|
||||
const target = normalizeText(chainValue);
|
||||
if (!target) {
|
||||
merchantList.value = [];
|
||||
merchantLoaded.value = false;
|
||||
merchantLoadedChain.value = '';
|
||||
merchantOptions.value = [];
|
||||
return;
|
||||
}
|
||||
await ensureMerchantListLoaded(target);
|
||||
const candidates = getChainCandidates(target);
|
||||
merchantOptions.value = merchantList.value
|
||||
.filter((item: Merchant) => {
|
||||
const merchantChain = normalizeText(item.chain);
|
||||
return !!merchantChain && candidates.includes(merchantChain);
|
||||
})
|
||||
.map((item: Merchant) => ({
|
||||
value: item.id as number,
|
||||
label: item.name ?? `${item.id ?? ''}`,
|
||||
chain: item.chain,
|
||||
code: item.code
|
||||
}));
|
||||
};
|
||||
|
||||
const syncChainByMerchantId = async (merchantId?: number) => {
|
||||
if (merchantId == null) {
|
||||
merchantOptions.value = [];
|
||||
return;
|
||||
}
|
||||
if (!normalizeText(form.chain)) {
|
||||
return;
|
||||
}
|
||||
await ensureMerchantListLoaded(form.chain);
|
||||
const merchant = merchantList.value.find((item) => item.id === merchantId);
|
||||
if (!merchant) {
|
||||
return;
|
||||
}
|
||||
ensureCurrentChainOption(merchant.chain);
|
||||
form.chain = resolveChainValue(merchant.chain);
|
||||
await loadMerchantOptions(form.chain);
|
||||
form.merchantCode = merchant.code;
|
||||
};
|
||||
|
||||
const resolveMerchant = (data?: YxeProductEditData | null) => {
|
||||
const merchantName = normalizeText(data?.merchant?.name);
|
||||
const merchantCode = normalizeText(
|
||||
form.merchantCode || data?.merchant?.code
|
||||
);
|
||||
const merchantId = form.merchantId ?? data?.merchant?.id;
|
||||
return merchantList.value.find((item) => {
|
||||
if (merchantId != null && item.id === merchantId) {
|
||||
return true;
|
||||
}
|
||||
if (merchantCode && normalizeText(item.code) === merchantCode) {
|
||||
return true;
|
||||
}
|
||||
return !!merchantName && normalizeText(item.name) === merchantName;
|
||||
});
|
||||
};
|
||||
|
||||
const syncMerchantSelection = async (data?: YxeProductEditData | null) => {
|
||||
let merchant = data?.merchant ?? null;
|
||||
if (!merchant && !normalizeText(form.chain)) {
|
||||
return;
|
||||
}
|
||||
await ensureMerchantListLoaded(form.chain);
|
||||
merchant = merchant || resolveMerchant(data);
|
||||
if (!merchant) {
|
||||
return;
|
||||
}
|
||||
if (!form.chain) {
|
||||
ensureCurrentChainOption(merchant.chain);
|
||||
form.chain = resolveChainValue(merchant.chain);
|
||||
await loadMerchantOptions(form.chain);
|
||||
}
|
||||
form.merchantId = merchant.id;
|
||||
form.merchantCode = merchant.code;
|
||||
};
|
||||
|
||||
const loadGoodsOptions = () => {
|
||||
goodsLoading.value = true;
|
||||
listYxeGoods()
|
||||
.then((list) => {
|
||||
goodsList.value = list || [];
|
||||
goodsOptions.value = goodsList.value.map((item: YxeGoods) => ({
|
||||
value: item.id as number,
|
||||
label:
|
||||
`${item.code ?? ''} ${item.title ?? ''}`.trim() ||
|
||||
`${item.id ?? ''}`
|
||||
}));
|
||||
syncSelectedGoods();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
})
|
||||
.finally(() => {
|
||||
goodsLoading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const syncSelectedGoods = () => {
|
||||
const matched = goodsList.value.find(
|
||||
(item) =>
|
||||
normalizeText(item.code) &&
|
||||
normalizeText(item.code) === normalizeText(form.code)
|
||||
);
|
||||
selectedGoodsId.value = matched?.id;
|
||||
};
|
||||
|
||||
const onChangeChain = async (value?: string) => {
|
||||
form.merchantId = undefined;
|
||||
form.merchantCode = undefined;
|
||||
await loadMerchantOptions(value);
|
||||
};
|
||||
|
||||
const onChangeMerchant = (merchantId?: number) => {
|
||||
const merchant = merchantList.value.find((item) => item.id === merchantId);
|
||||
form.merchantCode = merchant?.code;
|
||||
};
|
||||
|
||||
const onChangeGoods = (goodsId?: number) => {
|
||||
if (goodsId == null) {
|
||||
return;
|
||||
}
|
||||
const goods = goodsList.value.find((item) => item.id === goodsId);
|
||||
if (!goods) {
|
||||
return;
|
||||
}
|
||||
form.code = goods.code;
|
||||
form.cate = goods.cate;
|
||||
form.title = goods.title;
|
||||
form.spec = goods.spec;
|
||||
form.maker = goods.maker;
|
||||
form.makerAbb = goods.makerAbb;
|
||||
form.cover = goods.cover;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
async (visible) => {
|
||||
if (visible) {
|
||||
loadGoodsOptions();
|
||||
if (props.data) {
|
||||
const currentData = { ...props.data } as YxeProductEditData;
|
||||
currentData.startTime =
|
||||
currentData.startTime || currentData.startDate;
|
||||
currentData.endTime = currentData.endTime || currentData.endDate;
|
||||
currentData.extraStartTime =
|
||||
currentData.extraStartTime || currentData.extraStartDate;
|
||||
currentData.extraEndTime =
|
||||
currentData.extraEndTime || currentData.extraEndDate;
|
||||
assignObject(form, currentData);
|
||||
await loadChainOptions();
|
||||
ensureCurrentChainOption(form.chain);
|
||||
form.chain = resolveChainValue(form.chain);
|
||||
if (form.chain) {
|
||||
await loadMerchantOptions(form.chain);
|
||||
await syncMerchantSelection(currentData);
|
||||
onChangeMerchant(form.merchantId);
|
||||
} else if (currentData.merchant || form.merchantCode) {
|
||||
await syncMerchantSelection(currentData);
|
||||
} else if (form.merchantId != null) {
|
||||
await syncChainByMerchantId(form.merchantId);
|
||||
} else {
|
||||
await loadMerchantOptions(form.chain);
|
||||
}
|
||||
initExtraIncomeRows(currentData);
|
||||
syncSelectedGoods();
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
await loadChainOptions();
|
||||
initExtraIncomeRows();
|
||||
merchantOptions.value = [];
|
||||
selectedGoodsId.value = undefined;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
extraIncomeRows.value = [createExtraIncomeRow()];
|
||||
merchantOptions.value = [];
|
||||
selectedGoodsId.value = undefined;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.extra-income-empty {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.extra-income-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.extra-income-input {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.extra-income-range {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,452 @@
|
||||
<!-- 商品导入弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="560"
|
||||
:footer="null"
|
||||
:title="modalTitle"
|
||||
:visible="visible"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-upload-dragger
|
||||
accept=".xls,.xlsx"
|
||||
:show-upload-list="false"
|
||||
:customRequest="doImport"
|
||||
style="padding: 24px 0; margin-bottom: 12px"
|
||||
>
|
||||
<p class="ant-upload-drag-icon">
|
||||
<cloud-upload-outlined />
|
||||
</p>
|
||||
<p class="ant-upload-hint">将 Excel 文件拖到此处,或点击上传</p>
|
||||
</a-upload-dragger>
|
||||
</a-spin>
|
||||
<div class="ele-text-center">
|
||||
<a
|
||||
v-if="isTaskType"
|
||||
:href="taskTemplateUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
模板下载
|
||||
</a>
|
||||
<a v-else href="" @click.prevent="downloadTemplate">模板下载</a>
|
||||
</div>
|
||||
<div class="ele-text-secondary ele-text-center" style="margin-top: 4px">
|
||||
导入字段顺序:{{ fieldOrderText }}
|
||||
</div>
|
||||
<div v-if="progress.total" class="ele-text-center" style="margin-top: 8px">
|
||||
已处理 {{ progress.success + progress.fail }}/{{ progress.total }}, 成功
|
||||
{{ progress.success }},失败 {{ progress.fail }}
|
||||
</div>
|
||||
<div
|
||||
v-if="errorMessages.length"
|
||||
class="ele-text-danger"
|
||||
style="margin-top: 8px"
|
||||
>
|
||||
<div>失败明细(最多展示 5 条):</div>
|
||||
<div v-for="(item, index) in errorMessages.slice(0, 5)" :key="index">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { CloudUploadOutlined } from '@ant-design/icons-vue';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { addYxeProduct } from '@/api/yxe/yxeProduct';
|
||||
import type { YxeProduct } from '@/api/yxe/yxeProduct/model';
|
||||
|
||||
type ImportRow = {
|
||||
rowNumber: number;
|
||||
chain: string;
|
||||
merchantName: string;
|
||||
code: string;
|
||||
category: string;
|
||||
title: string;
|
||||
spec: string;
|
||||
maker: string;
|
||||
makerAbb: string;
|
||||
income: string;
|
||||
taskIncome: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
extraIncome: string;
|
||||
extraStartTime: string;
|
||||
extraEndTime: string;
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 是否打开弹窗
|
||||
visible: boolean;
|
||||
// 商品类型: 0任务商品, 1分享商品
|
||||
type: number;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const progress = ref({ total: 0, success: 0, fail: 0 });
|
||||
const errorMessages = ref<string[]>([]);
|
||||
const isTaskType = computed(() => props.type === 0);
|
||||
const taskTemplateUrl =
|
||||
'https://yaodongxiao.oss-cn-guangzhou.aliyuncs.com/%E4%BB%BB%E5%8A%A1%E8%8D%AF%E5%93%81%E7%9B%AE%E5%BD%95%E5%AF%BC%E5%85%A5%E6%A8%A1%E6%9D%BF.xlsx';
|
||||
const modalTitle = computed(() =>
|
||||
isTaskType.value ? '批量导入任务商品' : '批量导入分享商品'
|
||||
);
|
||||
const fieldOrderText = computed(() =>
|
||||
isTaskType.value
|
||||
? '所属连锁、门店名称、厂家简称、产品编码、分类、品名、规格、厂家、任务奖励(taskIncome)'
|
||||
: '所属连锁、门店名称、厂家简称、产品编码、分类、品名、规格、厂家、分享奖励、分享奖励活动开始时间、分享奖励活动结束时间、额外奖励、额外奖励开始时间、额外奖励结束时间'
|
||||
);
|
||||
|
||||
const normalize = (value?: unknown) => String(value ?? '').trim();
|
||||
const normalizeKey = (value?: unknown) => normalize(value).toLowerCase();
|
||||
const pad = (value: number) => String(value).padStart(2, '0');
|
||||
const formatDateValue = (value: unknown) => {
|
||||
if (value instanceof Date) {
|
||||
const hasTime =
|
||||
value.getHours() > 0 ||
|
||||
value.getMinutes() > 0 ||
|
||||
value.getSeconds() > 0;
|
||||
const datePart = `${value.getFullYear()}-${pad(
|
||||
value.getMonth() + 1
|
||||
)}-${pad(value.getDate())}`;
|
||||
if (!hasTime) {
|
||||
return datePart;
|
||||
}
|
||||
return `${datePart} ${pad(value.getHours())}:${pad(
|
||||
value.getMinutes()
|
||||
)}:${pad(value.getSeconds())}`;
|
||||
}
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
const parsed = XLSX.SSF.parse_date_code(value);
|
||||
if (parsed) {
|
||||
const hasTime = parsed.H > 0 || parsed.M > 0 || parsed.S > 0;
|
||||
const datePart = `${parsed.y}-${pad(parsed.m)}-${pad(parsed.d)}`;
|
||||
if (!hasTime) {
|
||||
return datePart;
|
||||
}
|
||||
return `${datePart} ${pad(parsed.H)}:${pad(parsed.M)}:${pad(parsed.S)}`;
|
||||
}
|
||||
}
|
||||
return normalize(value);
|
||||
};
|
||||
|
||||
const readRows = async (file: File): Promise<ImportRow[]> => {
|
||||
const data = await file.arrayBuffer();
|
||||
const workbook = XLSX.read(data, { type: 'array', cellDates: true });
|
||||
const sheetName = workbook.SheetNames[0];
|
||||
const sheet = workbook.Sheets[sheetName];
|
||||
const rows = XLSX.utils.sheet_to_json<any[]>(sheet, {
|
||||
header: 1,
|
||||
defval: '',
|
||||
raw: false
|
||||
}) as Array<string[]>;
|
||||
if (!rows.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const header = rows[0].map((item) => normalizeKey(item));
|
||||
const headerIndex = (aliases: string[]) => {
|
||||
const aliasSet = aliases.map((item) => item.toLowerCase());
|
||||
return header.findIndex((item) => aliasSet.includes(item));
|
||||
};
|
||||
|
||||
const chainIndex = headerIndex(['所属连锁', '连锁', 'chain']);
|
||||
const merchantNameIndex = headerIndex([
|
||||
'门店名称',
|
||||
'门店编号',
|
||||
'门店编码',
|
||||
'merchantname',
|
||||
'merchantcode'
|
||||
]);
|
||||
const makerAbbIndex = headerIndex(['厂家简称', '简称', 'makerabb']);
|
||||
const codeIndex = headerIndex(['产品编码', '商品编号', '编号', 'code']);
|
||||
const categoryIndex = headerIndex([
|
||||
'分类',
|
||||
'分类名称',
|
||||
'category',
|
||||
'catename'
|
||||
]);
|
||||
const titleIndex = headerIndex(['品名', '名称', 'title']);
|
||||
const specIndex = headerIndex(['规格', 'spec']);
|
||||
const makerIndex = headerIndex(['厂家', 'maker']);
|
||||
const taskIncomeIndex = headerIndex([
|
||||
'任务奖励',
|
||||
'任务奖励(taskincome)',
|
||||
'任务奖励(taskincome)',
|
||||
'taskincome',
|
||||
'奖励金额',
|
||||
'奖金金额'
|
||||
]);
|
||||
const incomeIndex = headerIndex(['分享奖励', '晒单政策', 'income']);
|
||||
const startTimeIndex = headerIndex([
|
||||
'分享奖励活动开始时间',
|
||||
'分享奖励开始时间',
|
||||
'活动开始时间',
|
||||
'starttime',
|
||||
'startdate'
|
||||
]);
|
||||
const endTimeIndex = headerIndex([
|
||||
'分享奖励活动结束时间',
|
||||
'分享奖励结束时间',
|
||||
'活动结束时间',
|
||||
'endtime',
|
||||
'enddate'
|
||||
]);
|
||||
const extraIncomeIndex = headerIndex([
|
||||
'额外奖励',
|
||||
'额外加提',
|
||||
'extraincome'
|
||||
]);
|
||||
const extraStartTimeIndex = headerIndex([
|
||||
'额外奖励开始时间',
|
||||
'额外加提开始时间',
|
||||
'extrastarttime',
|
||||
'extrastartdate'
|
||||
]);
|
||||
const extraEndTimeIndex = headerIndex([
|
||||
'额外奖励结束时间',
|
||||
'额外加提结束时间',
|
||||
'extraendtime',
|
||||
'extraenddate'
|
||||
]);
|
||||
|
||||
const hasHeader = isTaskType.value
|
||||
? chainIndex >= 0 ||
|
||||
merchantNameIndex >= 0 ||
|
||||
makerAbbIndex >= 0 ||
|
||||
codeIndex >= 0 ||
|
||||
categoryIndex >= 0 ||
|
||||
titleIndex >= 0 ||
|
||||
specIndex >= 0 ||
|
||||
makerIndex >= 0 ||
|
||||
taskIncomeIndex >= 0
|
||||
: chainIndex >= 0 ||
|
||||
merchantNameIndex >= 0 ||
|
||||
makerAbbIndex >= 0 ||
|
||||
codeIndex >= 0 ||
|
||||
categoryIndex >= 0 ||
|
||||
titleIndex >= 0 ||
|
||||
specIndex >= 0 ||
|
||||
makerIndex >= 0 ||
|
||||
incomeIndex >= 0 ||
|
||||
startTimeIndex >= 0 ||
|
||||
endTimeIndex >= 0 ||
|
||||
extraIncomeIndex >= 0 ||
|
||||
extraStartTimeIndex >= 0 ||
|
||||
extraEndTimeIndex >= 0;
|
||||
const dataRows = hasHeader ? rows.slice(1) : rows;
|
||||
const getByIndex = (row: unknown[], index: number, fallback: number) => {
|
||||
const value = index >= 0 ? row[index] : row[fallback];
|
||||
return normalize(value);
|
||||
};
|
||||
|
||||
return dataRows
|
||||
.map((row, i) => {
|
||||
const chain = getByIndex(row, chainIndex, 0);
|
||||
const merchantName = getByIndex(row, merchantNameIndex, 1);
|
||||
const makerAbb = getByIndex(row, makerAbbIndex, 2);
|
||||
const code = getByIndex(row, codeIndex, 3);
|
||||
const category = getByIndex(row, categoryIndex, 4);
|
||||
const title = getByIndex(row, titleIndex, 5);
|
||||
const spec = getByIndex(row, specIndex, 6);
|
||||
const maker = getByIndex(row, makerIndex, 7);
|
||||
const taskIncome = getByIndex(row, taskIncomeIndex, 8);
|
||||
const income = getByIndex(row, incomeIndex, 8);
|
||||
const startTime = formatDateValue(
|
||||
startTimeIndex >= 0 ? row[startTimeIndex] : row[9]
|
||||
);
|
||||
const endTime = formatDateValue(
|
||||
endTimeIndex >= 0 ? row[endTimeIndex] : row[10]
|
||||
);
|
||||
const extraIncome = getByIndex(row, extraIncomeIndex, 11);
|
||||
const extraStartTime = formatDateValue(
|
||||
extraStartTimeIndex >= 0 ? row[extraStartTimeIndex] : row[12]
|
||||
);
|
||||
const extraEndTime = formatDateValue(
|
||||
extraEndTimeIndex >= 0 ? row[extraEndTimeIndex] : row[13]
|
||||
);
|
||||
return {
|
||||
rowNumber: hasHeader ? i + 2 : i + 1,
|
||||
chain,
|
||||
category,
|
||||
code,
|
||||
title,
|
||||
spec,
|
||||
maker,
|
||||
makerAbb,
|
||||
income,
|
||||
taskIncome,
|
||||
merchantName,
|
||||
startTime,
|
||||
endTime,
|
||||
extraIncome,
|
||||
extraStartTime,
|
||||
extraEndTime
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(row) =>
|
||||
row.chain ||
|
||||
row.category ||
|
||||
row.code ||
|
||||
row.title ||
|
||||
row.spec ||
|
||||
row.maker ||
|
||||
row.makerAbb ||
|
||||
row.taskIncome ||
|
||||
row.income ||
|
||||
row.merchantName ||
|
||||
row.startTime ||
|
||||
row.endTime ||
|
||||
row.extraIncome ||
|
||||
row.extraStartTime ||
|
||||
row.extraEndTime
|
||||
);
|
||||
};
|
||||
|
||||
const doImport = async ({ 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;
|
||||
progress.value = { total: 0, success: 0, fail: 0 };
|
||||
errorMessages.value = [];
|
||||
|
||||
try {
|
||||
const rows = await readRows(file as File);
|
||||
if (!rows.length) {
|
||||
message.warning('未读取到可导入的数据');
|
||||
return false;
|
||||
}
|
||||
|
||||
progress.value.total = rows.length;
|
||||
for (const row of rows) {
|
||||
try {
|
||||
const data: YxeProduct = {
|
||||
type: props.type,
|
||||
chain: row.chain || undefined,
|
||||
code: row.code || undefined,
|
||||
cate: row.category || undefined,
|
||||
title: row.title || undefined,
|
||||
spec: row.spec || undefined,
|
||||
maker: row.maker || undefined,
|
||||
makerAbb: row.makerAbb || undefined,
|
||||
merchantCode: row.merchantName || undefined
|
||||
};
|
||||
if (isTaskType.value) {
|
||||
data.taskIncome = row.taskIncome || undefined;
|
||||
} else {
|
||||
data.income = row.income || undefined;
|
||||
data.startTime = row.startTime || undefined;
|
||||
data.endTime = row.endTime || undefined;
|
||||
data.extraIncomeList =
|
||||
row.extraIncome || row.extraStartTime || row.extraEndTime
|
||||
? [
|
||||
{
|
||||
income: row.extraIncome,
|
||||
startTime: row.extraStartTime,
|
||||
endTime: row.extraEndTime
|
||||
}
|
||||
]
|
||||
: [];
|
||||
}
|
||||
await addYxeProduct(data);
|
||||
progress.value.success += 1;
|
||||
} catch (e) {
|
||||
progress.value.fail += 1;
|
||||
const err = e as Error;
|
||||
errorMessages.value.push(
|
||||
`第${row.rowNumber}行:${err.message || '导入失败'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (progress.value.fail > 0) {
|
||||
message.warning(
|
||||
`导入完成:成功${progress.value.success}条,失败${progress.value.fail}条`
|
||||
);
|
||||
} else {
|
||||
message.success(`导入完成:成功${progress.value.success}条`);
|
||||
updateVisible(false);
|
||||
}
|
||||
|
||||
if (progress.value.success > 0) {
|
||||
emit('done');
|
||||
}
|
||||
} catch (e) {
|
||||
const err = e as Error;
|
||||
message.error(err.message || '导入失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const downloadTemplate = () => {
|
||||
const workbook = XLSX.utils.book_new();
|
||||
const headers = isTaskType.value
|
||||
? [
|
||||
'所属连锁',
|
||||
'门店名称',
|
||||
'厂家简称',
|
||||
'产品编码',
|
||||
'分类',
|
||||
'品名',
|
||||
'规格',
|
||||
'厂家',
|
||||
'任务奖励(taskIncome)'
|
||||
]
|
||||
: [
|
||||
'所属连锁',
|
||||
'门店名称',
|
||||
'厂家简称',
|
||||
'产品编码',
|
||||
'分类',
|
||||
'品名',
|
||||
'规格',
|
||||
'厂家',
|
||||
'分享奖励',
|
||||
'分享奖励活动开始时间',
|
||||
'分享奖励活动结束时间',
|
||||
'额外奖励',
|
||||
'额外奖励开始时间',
|
||||
'额外奖励结束时间'
|
||||
];
|
||||
const sheet = XLSX.utils.aoa_to_sheet([headers]);
|
||||
XLSX.utils.book_append_sheet(
|
||||
workbook,
|
||||
sheet,
|
||||
isTaskType.value ? '任务商品导入模板' : '分享商品导入模板'
|
||||
);
|
||||
XLSX.writeFile(
|
||||
workbook,
|
||||
isTaskType.value
|
||||
? '药销儿任务商品导入模板.xlsx'
|
||||
: '药销儿分享商品导入模板.xlsx'
|
||||
);
|
||||
};
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="640"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑门店政策' : '新增门店政策'"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ flex: '88px' }"
|
||||
:wrapper-col="{ flex: '1' }"
|
||||
>
|
||||
<a-form-item label="门店" name="merchantId">
|
||||
<a-select
|
||||
show-search
|
||||
allow-clear
|
||||
optionFilterProp="label"
|
||||
:loading="merchantLoading"
|
||||
:options="merchantOptions"
|
||||
:placeholder="product?.chain ? '请选择门店' : '当前产品未配置连锁'"
|
||||
:disabled="!product?.chain"
|
||||
v-model:value="form.merchantId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="金额" name="income">
|
||||
<a-input-number
|
||||
class="ele-fluid"
|
||||
:min="0"
|
||||
:step="0.01"
|
||||
:precision="2"
|
||||
string-mode
|
||||
placeholder="请输入金额"
|
||||
v-model:value="form.income"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="有效期" name="timeRange">
|
||||
<a-range-picker
|
||||
class="ele-fluid"
|
||||
value-format="YYYY-MM-DD HH:mm"
|
||||
:show-time="{ format: 'HH:mm' }"
|
||||
:placeholder="['请选择开始时间', '请选择结束时间']"
|
||||
v-model:value="form.timeRange"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, watch } from 'vue';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import { Form, message, type Rule, type SelectProps } from 'ant-design-vue';
|
||||
import type { FormInstance } from 'ant-design-vue/es/form';
|
||||
import {
|
||||
addYxeProductMerchantExtraIncome,
|
||||
updateYxeProductMerchantExtraIncome
|
||||
} from '@/api/yxe/yxeProductMerchantExtraIncome';
|
||||
import type { YxeProductMerchantExtraIncome } from '@/api/yxe/yxeProductMerchantExtraIncome/model';
|
||||
import type { YxeProduct } from '@/api/yxe/yxeProduct/model';
|
||||
|
||||
type PolicyForm = YxeProductMerchantExtraIncome & {
|
||||
timeRange?: string[];
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
product?: YxeProduct | null;
|
||||
data?: YxeProductMerchantExtraIncome | null;
|
||||
merchantOptions?: SelectProps['options'];
|
||||
merchantLoading?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const createDefaultForm = (): PolicyForm => ({
|
||||
id: undefined,
|
||||
productId: undefined,
|
||||
merchantId: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
income: undefined,
|
||||
sortNumber: 0,
|
||||
status: 0,
|
||||
deleted: 0,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
timeRange: undefined
|
||||
});
|
||||
|
||||
const form = reactive<PolicyForm>(createDefaultForm());
|
||||
|
||||
const validateAmount: Rule['validator'] = async (_rule, value) => {
|
||||
if (value == null || value === '') {
|
||||
return Promise.reject('请输入金额');
|
||||
}
|
||||
if (!/^\d+(\.\d{1,2})?$/.test(String(value))) {
|
||||
return Promise.reject('金额最多支持两位小数');
|
||||
}
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const rules = reactive({
|
||||
merchantId: [{ required: true, message: '请选择门店', trigger: 'change' }],
|
||||
income: [{ required: true, validator: validateAmount, trigger: 'change' }],
|
||||
timeRange: [
|
||||
{
|
||||
required: true,
|
||||
type: 'array',
|
||||
message: '请选择有效期',
|
||||
trigger: 'change'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields } = useForm(form, rules);
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const initForm = (data?: YxeProductMerchantExtraIncome | null) => {
|
||||
const defaults = createDefaultForm();
|
||||
const timeRange =
|
||||
data?.startTime || data?.endTime
|
||||
? [
|
||||
formatMinuteValue(data?.startTime),
|
||||
formatMinuteValue(data?.endTime)
|
||||
].filter(Boolean)
|
||||
: undefined;
|
||||
Object.assign(form, defaults, data ?? {}, {
|
||||
productId: props.product?.id,
|
||||
timeRange: timeRange?.length === 2 ? (timeRange as string[]) : undefined
|
||||
});
|
||||
isUpdate.value = !!data?.id;
|
||||
};
|
||||
|
||||
const formatMinuteValue = (value?: string) => {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return toDateString(value, 'yyyy-MM-dd HH:mm');
|
||||
};
|
||||
|
||||
const save = async () => {
|
||||
if (!props.product?.id) {
|
||||
message.error('当前产品不存在,无法保存门店政策');
|
||||
return;
|
||||
}
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await formRef.value.validate();
|
||||
loading.value = true;
|
||||
const { timeRange, ...rest } = { ...form };
|
||||
const payload: YxeProductMerchantExtraIncome = {
|
||||
...rest,
|
||||
productId: props.product.id,
|
||||
startTime: timeRange?.[0],
|
||||
endTime: timeRange?.[1],
|
||||
status: rest.status ?? 0,
|
||||
deleted: rest.deleted ?? 0
|
||||
};
|
||||
const request = isUpdate.value
|
||||
? updateYxeProductMerchantExtraIncome
|
||||
: addYxeProductMerchantExtraIncome;
|
||||
const msg = await request(payload);
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
} catch (e) {
|
||||
if ((e as Error)?.message) {
|
||||
message.error((e as Error).message);
|
||||
}
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
initForm(props.data);
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,261 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:visible="visible"
|
||||
:width="1000"
|
||||
:maskClosable="false"
|
||||
:footer="null"
|
||||
:title="modalTitle"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-alert
|
||||
v-if="!product?.chain"
|
||||
type="warning"
|
||||
show-icon
|
||||
message="当前产品未配置所属连锁,无法新增门店政策"
|
||||
style="margin-bottom: 16px"
|
||||
/>
|
||||
<div class="toolbar">
|
||||
<a-space wrap>
|
||||
<a-button
|
||||
type="primary"
|
||||
@click="openEdit()"
|
||||
:disabled="!canCreatePolicy"
|
||||
>
|
||||
新增
|
||||
</a-button>
|
||||
<span class="ele-text-secondary">
|
||||
所属连锁:{{ product?.chain || "-" }}
|
||||
</span>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-table
|
||||
size="middle"
|
||||
:loading="loading"
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:row-key="rowKey"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 760 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'merchantName'">
|
||||
{{ getMerchantName(record) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'income'">
|
||||
{{ record.income || "-" }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'effectivePeriod'">
|
||||
{{ formatEffectivePeriod(record) }}
|
||||
</template>
|
||||
<template v-else-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>
|
||||
</a-table>
|
||||
|
||||
<YxeProductMerchantPolicyEdit
|
||||
v-model:visible="showEdit"
|
||||
:product="product"
|
||||
:data="current"
|
||||
:merchant-options="merchantOptions"
|
||||
:merchant-loading="merchantLoading"
|
||||
@done="loadData"
|
||||
/>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { message, type SelectProps } from 'ant-design-vue';
|
||||
import { toDateString } from 'ele-admin-pro';
|
||||
import { listMerchantMini } from '@/api/shop/merchant';
|
||||
import type { Merchant } from '@/api/shop/merchant/model';
|
||||
import {
|
||||
listYxeProductMerchantExtraIncome,
|
||||
removeYxeProductMerchantExtraIncome
|
||||
} from '@/api/yxe/yxeProductMerchantExtraIncome';
|
||||
import type { YxeProductMerchantExtraIncome } from '@/api/yxe/yxeProductMerchantExtraIncome/model';
|
||||
import type { YxeProduct } from '@/api/yxe/yxeProduct/model';
|
||||
import YxeProductMerchantPolicyEdit from './yxeProductMerchantPolicyEdit.vue';
|
||||
|
||||
type MerchantOption = NonNullable<SelectProps['options']>[number];
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean;
|
||||
product?: YxeProduct | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const merchantLoading = ref(false);
|
||||
const list = ref<YxeProductMerchantExtraIncome[]>([]);
|
||||
const merchantOptions = ref<SelectProps['options']>([]);
|
||||
const current = ref<YxeProductMerchantExtraIncome | null>(null);
|
||||
const showEdit = ref(false);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '门店名称',
|
||||
dataIndex: ['merchant', 'name'],
|
||||
key: 'merchantName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '金额',
|
||||
dataIndex: 'income',
|
||||
key: 'income',
|
||||
align: 'center',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '有效期',
|
||||
key: 'effectivePeriod',
|
||||
align: 'center',
|
||||
width: 360
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
width: 160
|
||||
}
|
||||
];
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
const title = String(props.product?.title ?? '').trim();
|
||||
return title ? `${title} - 门店政策配置` : '门店政策配置';
|
||||
});
|
||||
|
||||
const canCreatePolicy = computed(
|
||||
() => !!props.product?.id && !!String(props.product?.chain ?? '').trim()
|
||||
);
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const rowKey = (record: YxeProductMerchantExtraIncome) =>
|
||||
record.id ?? `${record.merchantId ?? ''}-${record.startTime ?? ''}`;
|
||||
|
||||
const getMerchantName = (record: YxeProductMerchantExtraIncome) => {
|
||||
if (record.merchant?.name) {
|
||||
return record.merchant.name;
|
||||
}
|
||||
const matched = (merchantOptions.value || []).find(
|
||||
(item) => item?.value === record.merchantId
|
||||
) as MerchantOption | undefined;
|
||||
return matched?.label ?? '-';
|
||||
};
|
||||
|
||||
const formatEffectivePeriod = (record: YxeProductMerchantExtraIncome) => {
|
||||
const start = record.startTime
|
||||
? toDateString(record.startTime, 'yyyy-MM-dd HH:mm')
|
||||
: '';
|
||||
const end = record.endTime
|
||||
? toDateString(record.endTime, 'yyyy-MM-dd HH:mm')
|
||||
: '';
|
||||
if (!start && !end) {
|
||||
return '-';
|
||||
}
|
||||
return [start, end].filter(Boolean).join(' ~ ');
|
||||
};
|
||||
|
||||
const loadMerchantOptions = async () => {
|
||||
const chain = String(props.product?.chain ?? '').trim();
|
||||
if (!chain) {
|
||||
merchantOptions.value = [];
|
||||
return;
|
||||
}
|
||||
merchantLoading.value = true;
|
||||
try {
|
||||
const merchants = await listMerchantMini({ chain });
|
||||
merchantOptions.value = (merchants || []).map((item: Merchant) => ({
|
||||
value: item.id as number,
|
||||
label: item.name ?? `${item.id ?? ''}`
|
||||
}));
|
||||
} catch (e) {
|
||||
message.error((e as Error).message || '获取门店列表失败');
|
||||
} finally {
|
||||
merchantLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadList = async () => {
|
||||
if (!props.product?.id) {
|
||||
list.value = [];
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
list.value = await listYxeProductMerchantExtraIncome({
|
||||
productId: props.product.id
|
||||
});
|
||||
} catch (e) {
|
||||
message.error((e as Error).message || '获取门店政策失败');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const loadData = async () => {
|
||||
await Promise.all([loadMerchantOptions(), loadList()]);
|
||||
};
|
||||
|
||||
const openEdit = (row?: YxeProductMerchantExtraIncome) => {
|
||||
if (!row && !canCreatePolicy.value) {
|
||||
message.warning('当前产品未配置所属连锁,无法新增门店政策');
|
||||
return;
|
||||
}
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const remove = async (row: YxeProductMerchantExtraIncome) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
try {
|
||||
const msg = await removeYxeProductMerchantExtraIncome(row.id);
|
||||
hide();
|
||||
message.success(msg);
|
||||
await loadList();
|
||||
} catch (e) {
|
||||
hide();
|
||||
message.error((e as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => [props.visible, props.product?.id, props.product?.chain],
|
||||
([visible]) => {
|
||||
if (visible) {
|
||||
loadData();
|
||||
} else {
|
||||
list.value = [];
|
||||
merchantOptions.value = [];
|
||||
current.value = null;
|
||||
showEdit.value = false;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,456 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:columns="type === 0 ? columns0 : columns1"
|
||||
:datasource="datasource"
|
||||
:customRow="customRow"
|
||||
v-model:selection="selection"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
:pageSizeOptions="['10', '20', '50', '100', '200', '500', '1000']"
|
||||
>
|
||||
<template #toolbar>
|
||||
<search
|
||||
@search="reload"
|
||||
:selection="selection"
|
||||
@add="openEdit"
|
||||
@remove="removeBatch"
|
||||
@batchMove="openMove"
|
||||
@import="openImport"
|
||||
@coverZip="handleCoverZipUpload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'cover'">
|
||||
<a-image
|
||||
:src="record.cover"
|
||||
v-if="record.cover"
|
||||
style="width: 50px; height: 50px; object-fit: cover"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" color="red">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'shareDate'">
|
||||
<span v-if="record.startTime || record.endTime">
|
||||
{{ formatDateRange(record.startTime, record.endTime) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'extraIncome'">
|
||||
<div v-if="getExtraIncomeList(record).length">
|
||||
<div
|
||||
v-for="(item, index) in getExtraIncomeList(record)"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.startTime }}~{{ item.endTime }}, {{ item.income }}元
|
||||
</div>
|
||||
</div>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a @click="openEdit(record)">修改</a>
|
||||
<template v-if="type === 1">
|
||||
<a-divider type="vertical" />
|
||||
<a @click="openMerchantPolicy(record)">门店政策配置</a>
|
||||
</template>
|
||||
<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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<YxeProductEdit
|
||||
v-model:visible="showEdit"
|
||||
:type="type"
|
||||
:data="current"
|
||||
@done="reload"
|
||||
/>
|
||||
<YxeProductImport v-model:visible="showImport" :type="type" @done="reload" />
|
||||
<YxeProductMerchantPolicyModal
|
||||
v-model:visible="showMerchantPolicy"
|
||||
:product="currentPolicyProduct"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { message } from "ant-design-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 YxeProductEdit from "./components/yxeProductEdit.vue";
|
||||
import YxeProductImport from "./components/yxeProductImport.vue";
|
||||
import YxeProductMerchantPolicyModal from "./components/yxeProductMerchantPolicyModal.vue";
|
||||
import {
|
||||
pageYxeProduct,
|
||||
removeYxeProduct,
|
||||
removeBatchYxeProduct,
|
||||
uploadYxeProductCoverZip
|
||||
} from "@/api/yxe/yxeProduct";
|
||||
import type {
|
||||
YxeProduct,
|
||||
YxeProductParam,
|
||||
YxeProductExtraIncomeItem
|
||||
} from "@/api/yxe/yxeProduct/model";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<YxeProduct[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<YxeProduct | null>(null);
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 是否显示导入弹窗
|
||||
const showImport = ref(false);
|
||||
// 是否显示门店政策配置弹窗
|
||||
const showMerchantPolicy = ref(false);
|
||||
// 是否显示批量移动弹窗
|
||||
const showMove = ref(false);
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 当前配置门店政策的产品
|
||||
const currentPolicyProduct = ref<YxeProduct | null>(null);
|
||||
|
||||
const route = useRoute();
|
||||
const type = parseInt(route.query.type as string) || 0;
|
||||
|
||||
const getExtraIncomeList = (
|
||||
record: YxeProduct
|
||||
): YxeProductExtraIncomeItem[] => {
|
||||
if (record.extraIncomeList?.length) {
|
||||
return record.extraIncomeList;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const formatDateRange = (startTime?: string, endTime?: string) => {
|
||||
const start = startTime ? toDateString(startTime, "yyyy-MM-dd HH:mm") : "";
|
||||
const end = endTime ? toDateString(endTime, "yyyy-MM-dd HH:mm") : "";
|
||||
return [start, end].filter(Boolean).join(" ~ ");
|
||||
};
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({
|
||||
page,
|
||||
limit,
|
||||
where,
|
||||
orders,
|
||||
filters
|
||||
}) => {
|
||||
if (filters) {
|
||||
where.status = filters.status;
|
||||
}
|
||||
where.type = type || 0;
|
||||
return pageYxeProduct({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns0 = ref<ColumnItem[]>([
|
||||
{
|
||||
title: "编号",
|
||||
dataIndex: "code",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "所属连锁",
|
||||
dataIndex: "chain",
|
||||
align: "center"
|
||||
},
|
||||
// {
|
||||
// title: "门店",
|
||||
// dataIndex: ["merchant", "name"],
|
||||
// align: "center"
|
||||
// },
|
||||
{
|
||||
title: "品名",
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "分类",
|
||||
dataIndex: "cate",
|
||||
key: "cate",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "图片",
|
||||
key: "cover",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "规格",
|
||||
dataIndex: "spec",
|
||||
key: "spec",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "厂家",
|
||||
dataIndex: "maker",
|
||||
key: "maker",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "厂家简称",
|
||||
dataIndex: "makerAbb",
|
||||
key: "makerAbb",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "奖金金额",
|
||||
dataIndex: "taskIncome",
|
||||
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: 280,
|
||||
fixed: "right",
|
||||
align: "center",
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
const columns1 = ref<ColumnItem[]>([
|
||||
{
|
||||
title: "编号",
|
||||
dataIndex: "code",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "所属连锁",
|
||||
dataIndex: "chain",
|
||||
align: "center"
|
||||
},
|
||||
// {
|
||||
// title: "门店",
|
||||
// dataIndex: ["merchant", "name"],
|
||||
// align: "center"
|
||||
// },
|
||||
{
|
||||
title: "品名",
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "分类",
|
||||
dataIndex: "cate",
|
||||
key: "cate",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "图片",
|
||||
key: "cover",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "规格",
|
||||
dataIndex: "spec",
|
||||
key: "spec",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "厂家",
|
||||
dataIndex: "maker",
|
||||
key: "maker",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "厂家简称",
|
||||
dataIndex: "makerAbb",
|
||||
key: "makerAbb",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "晒单政策",
|
||||
dataIndex: "income",
|
||||
key: "income",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "分享有效期",
|
||||
key: "shareDate",
|
||||
align: "center"
|
||||
},
|
||||
{
|
||||
title: "额外加提",
|
||||
key: "extraIncome",
|
||||
align: "center",
|
||||
width: 250
|
||||
},
|
||||
{
|
||||
title: "添加时间",
|
||||
dataIndex: "createTime",
|
||||
key: "createTime",
|
||||
align: "center",
|
||||
customRender: ({ text }) => toDateString(text, "yyyy-MM-dd HH:mm:ss")
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
width: 220,
|
||||
fixed: "right",
|
||||
align: "center",
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: YxeProductParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: YxeProduct) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 打开导入弹窗 */
|
||||
const openImport = () => {
|
||||
showImport.value = true;
|
||||
};
|
||||
|
||||
/* 打开门店政策配置弹窗 */
|
||||
const openMerchantPolicy = (row: YxeProduct) => {
|
||||
currentPolicyProduct.value = row;
|
||||
showMerchantPolicy.value = true;
|
||||
};
|
||||
|
||||
const handleCoverZipUpload = (file: File) => {
|
||||
const fileName = file.name?.toLowerCase?.() ?? "";
|
||||
if (!fileName.endsWith(".zip")) {
|
||||
message.error("只能选择 zip 压缩包");
|
||||
return false;
|
||||
}
|
||||
const hide = message.loading("封面压缩包处理中..", 0);
|
||||
uploadYxeProductCoverZip(file)
|
||||
.then((result) => {
|
||||
hide();
|
||||
message.success(
|
||||
`封面压缩包处理完成,识别${result.totalFiles ?? 0}个文件,更新${
|
||||
result.updatedCount ?? 0
|
||||
}条记录,跳过${result.skippedCount ?? 0}个文件`
|
||||
);
|
||||
if (result.notFoundTitles?.length) {
|
||||
message.warning(
|
||||
`有 ${result.notFoundTitles.length} 个商品标题未匹配`
|
||||
);
|
||||
}
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message || "封面压缩包处理失败");
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: YxeProduct) => {
|
||||
const hide = message.loading("请求中..", 0);
|
||||
removeYxeProduct(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error("请至少选择一条数据");
|
||||
return;
|
||||
}
|
||||
const hide = message.loading("请求中..", 0);
|
||||
removeBatchYxeProduct(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: YxeProduct) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: "YxeProduct"
|
||||
};
|
||||
</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,191 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="productId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.productId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="startTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.startTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="endTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.endTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="income">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.income"
|
||||
/>
|
||||
</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="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="1">显示</a-radio>
|
||||
<a-radio :value="0">隐藏</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>
|
||||
</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 { addYxeProductExtraIncome, updateYxeProductExtraIncome } from '@/api/yxe/yxeProductExtraIncome';
|
||||
import { YxeProductExtraIncome } from '@/api/yxe/yxeProductExtraIncome/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: YxeProductExtraIncome | 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 form = reactive<YxeProductExtraIncome>({
|
||||
id: undefined,
|
||||
productId: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
income: undefined,
|
||||
sortNumber: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateYxeProductExtraIncome : addYxeProductExtraIncome;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<YxeProductExtraIncomeEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 YxeProductExtraIncomeEdit from './components/yxeProductExtraIncomeEdit.vue';
|
||||
import { pageYxeProductExtraIncome, removeYxeProductExtraIncome, removeBatchYxeProductExtraIncome } from '@/api/yxe/yxeProductExtraIncome';
|
||||
import type { YxeProductExtraIncome, YxeProductExtraIncomeParam } from '@/api/yxe/yxeProductExtraIncome/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<YxeProductExtraIncome[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<YxeProductExtraIncome | 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 pageYxeProductExtraIncome({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'income',
|
||||
key: 'income',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '排序(数字越小越靠前)',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0正常, 1冻结',
|
||||
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: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: YxeProductExtraIncomeParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: YxeProductExtraIncome) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: YxeProductExtraIncome) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeYxeProductExtraIncome(row.id)
|
||||
.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);
|
||||
removeBatchYxeProductExtraIncome(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: YxeProductExtraIncome) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'YxeProductExtraIncome'
|
||||
};
|
||||
</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>
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
<!-- 编辑弹窗 -->
|
||||
<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="productId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.productId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="merchantId">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.merchantId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="startTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.startTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="endTime">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.endTime"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="" name="income">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入"
|
||||
v-model:value="form.income"
|
||||
/>
|
||||
</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="status">
|
||||
<a-radio-group v-model:value="form.status">
|
||||
<a-radio :value="1">显示</a-radio>
|
||||
<a-radio :value="0">隐藏</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>
|
||||
</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 { addYxeProductMerchantExtraIncome, updateYxeProductMerchantExtraIncome } from '@/api/yxe/yxeProductMerchantExtraIncome';
|
||||
import { YxeProductMerchantExtraIncome } from '@/api/yxe/yxeProductMerchantExtraIncome/model';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { storeToRefs } from 'pinia';
|
||||
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?: YxeProductMerchantExtraIncome | 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 form = reactive<YxeProductMerchantExtraIncome>({
|
||||
id: undefined,
|
||||
productId: undefined,
|
||||
merchantId: undefined,
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
income: undefined,
|
||||
sortNumber: undefined,
|
||||
status: undefined,
|
||||
deleted: undefined,
|
||||
tenantId: undefined,
|
||||
createTime: undefined,
|
||||
updateTime: undefined,
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
|
||||
});
|
||||
|
||||
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 ? updateYxeProductMerchantExtraIncome : addYxeProductMerchantExtraIncome;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
: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 === 1" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 0" 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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<YxeProductMerchantExtraIncomeEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</div>
|
||||
</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 YxeProductMerchantExtraIncomeEdit from './components/yxeProductMerchantExtraIncomeEdit.vue';
|
||||
import { pageYxeProductMerchantExtraIncome, removeYxeProductMerchantExtraIncome, removeBatchYxeProductMerchantExtraIncome } from '@/api/yxe/yxeProductMerchantExtraIncome';
|
||||
import type { YxeProductMerchantExtraIncome, YxeProductMerchantExtraIncomeParam } from '@/api/yxe/yxeProductMerchantExtraIncome/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<YxeProductMerchantExtraIncome[]>([]);
|
||||
// 当前编辑数据
|
||||
const current = ref<YxeProductMerchantExtraIncome | 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 pageYxeProductMerchantExtraIncome({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
// 表格列配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
align: 'center',
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'merchantId',
|
||||
key: 'merchantId',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'endTime',
|
||||
key: 'endTime',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'income',
|
||||
key: 'income',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '排序(数字越小越靠前)',
|
||||
dataIndex: 'sortNumber',
|
||||
key: 'sortNumber',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '状态, 0正常, 1冻结',
|
||||
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: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: YxeProductMerchantExtraIncomeParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ where: where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: YxeProductMerchantExtraIncome) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开批量移动弹窗 */
|
||||
const openMove = () => {
|
||||
showMove.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: YxeProductMerchantExtraIncome) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeYxeProductMerchantExtraIncome(row.id)
|
||||
.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);
|
||||
removeBatchYxeProductMerchantExtraIncome(selection.value.map((d) => d.id))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: YxeProductMerchantExtraIncome) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
// console.log(record);
|
||||
},
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
openEdit(record);
|
||||
}
|
||||
};
|
||||
};
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'YxeProductMerchantExtraIncome'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -0,0 +1,231 @@
|
||||
<!-- 搜索表单 -->
|
||||
<template>
|
||||
<a-space :size="10" style="flex-wrap: wrap">
|
||||
<a-button type="primary" class="ele-btn-icon" @click="exportData">
|
||||
<template #icon>
|
||||
<DownloadOutlined />
|
||||
</template>
|
||||
<span>导出</span>
|
||||
</a-button>
|
||||
<a-select
|
||||
v-model:value="where.userId"
|
||||
show-search
|
||||
allow-clear
|
||||
:filter-option="false"
|
||||
optionFilterProp="label"
|
||||
:loading="userLoading"
|
||||
:options="userOptions"
|
||||
placeholder="请选择用户"
|
||||
style="width: 220px"
|
||||
@search="onSearchUser"
|
||||
@focus="loadUserOptions()"
|
||||
@change="search"
|
||||
/>
|
||||
<a-range-picker
|
||||
v-model:value="dateRange"
|
||||
value-format="YYYY-MM-DD"
|
||||
:placeholder="['请选择开始日期', '请选择结束日期']"
|
||||
style="width: 260px"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value.trim="where.fromAddress"
|
||||
allow-clear
|
||||
placeholder="请输入出发点"
|
||||
style="width: 200px"
|
||||
@pressEnter="search"
|
||||
/>
|
||||
<a-input
|
||||
v-model:value.trim="where.toAddress"
|
||||
allow-clear
|
||||
placeholder="请输入目的地"
|
||||
style="width: 200px"
|
||||
@pressEnter="search"
|
||||
/>
|
||||
<a-select
|
||||
v-model:value="where.status"
|
||||
allow-clear
|
||||
placeholder="请选择状态"
|
||||
style="width: 160px"
|
||||
>
|
||||
<a-select-option :value="0">待审核</a-select-option>
|
||||
<a-select-option :value="1">通过</a-select-option>
|
||||
<a-select-option :value="2">拒绝</a-select-option>
|
||||
</a-select>
|
||||
<a-button type="primary" @click="search">查询</a-button>
|
||||
<a-button @click="reset">重置</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { DownloadOutlined } from '@ant-design/icons-vue';
|
||||
import { onBeforeUnmount, ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import type { SelectProps } from 'ant-design-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import type { User as SystemUser } from '@/api/system/user/model';
|
||||
import type { YxeReimburseParam } from '@/api/yxe/yxeReimburse/model';
|
||||
|
||||
defineProps<{
|
||||
selection?: [];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where?: YxeReimburseParam): void;
|
||||
(e: 'add'): void;
|
||||
(e: 'remove'): void;
|
||||
(e: 'batchMove'): void;
|
||||
(e: 'export'): void;
|
||||
}>();
|
||||
|
||||
const { where, resetFields } = useSearch<YxeReimburseParam>({
|
||||
userId: undefined,
|
||||
status: undefined,
|
||||
startDateStart: undefined,
|
||||
endDateEnd: undefined,
|
||||
fromAddress: undefined,
|
||||
toAddress: undefined
|
||||
});
|
||||
|
||||
const dateRange = ref<[string, string] | []>([]);
|
||||
const userOptions = ref<SelectProps['options']>([]);
|
||||
const userLoading = ref(false);
|
||||
let userSearchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let userSearchSeed = 0;
|
||||
|
||||
const buildUserOption = (item: SystemUser) => {
|
||||
if (item.userId == null) {
|
||||
return null;
|
||||
}
|
||||
const phone = String(item.phone ?? '').trim();
|
||||
const name = String(item.realName ?? item.nickname ?? '').trim();
|
||||
const contact = phone || `ID:${item.userId}`;
|
||||
return {
|
||||
value: item.userId,
|
||||
label: name ? `${name} (${contact})` : contact
|
||||
};
|
||||
};
|
||||
|
||||
const ensureCurrentUserOption = (userId?: number) => {
|
||||
if (userId == null) {
|
||||
return;
|
||||
}
|
||||
const exists = userOptions.value?.some((item) => item?.value === userId);
|
||||
if (exists) {
|
||||
return;
|
||||
}
|
||||
userOptions.value = [
|
||||
{
|
||||
value: userId,
|
||||
label: `ID:${userId}`
|
||||
},
|
||||
...(userOptions.value || [])
|
||||
];
|
||||
};
|
||||
|
||||
const loadUserOptions = (keyword?: string) => {
|
||||
const currentSeed = ++userSearchSeed;
|
||||
const trimmed = String(keyword ?? '').trim();
|
||||
const previousSelectedOption =
|
||||
where.userId == null
|
||||
? undefined
|
||||
: (userOptions.value || []).find(
|
||||
(item) => item?.value === where.userId
|
||||
);
|
||||
userLoading.value = true;
|
||||
const requests = trimmed
|
||||
? [
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
phone: trimmed
|
||||
}),
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20,
|
||||
realName: trimmed
|
||||
})
|
||||
]
|
||||
: [
|
||||
pageUsers({
|
||||
page: 1,
|
||||
limit: 20
|
||||
})
|
||||
];
|
||||
Promise.all(requests)
|
||||
.then((results) => {
|
||||
if (currentSeed !== userSearchSeed) {
|
||||
return;
|
||||
}
|
||||
const map = new Map<number, { value: number; label: string }>();
|
||||
results.forEach((res) => {
|
||||
(res?.list || []).forEach((item) => {
|
||||
const option = buildUserOption(item as SystemUser);
|
||||
if (option && !map.has(option.value)) {
|
||||
map.set(option.value, option);
|
||||
}
|
||||
});
|
||||
});
|
||||
userOptions.value = Array.from(map.values());
|
||||
if (
|
||||
previousSelectedOption &&
|
||||
where.userId != null &&
|
||||
!map.has(where.userId)
|
||||
) {
|
||||
userOptions.value = [previousSelectedOption, ...userOptions.value];
|
||||
}
|
||||
ensureCurrentUserOption(where.userId);
|
||||
})
|
||||
.catch((e) => {
|
||||
if (currentSeed !== userSearchSeed) {
|
||||
return;
|
||||
}
|
||||
message.error(e.message ?? '查询用户失败');
|
||||
})
|
||||
.finally(() => {
|
||||
if (currentSeed === userSearchSeed) {
|
||||
userLoading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onSearchUser = (value: string) => {
|
||||
if (userSearchTimer) {
|
||||
clearTimeout(userSearchTimer);
|
||||
}
|
||||
userSearchTimer = setTimeout(() => {
|
||||
loadUserOptions(value);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const syncDateRange = () => {
|
||||
const [startDate, endDate] = Array.isArray(dateRange.value)
|
||||
? dateRange.value
|
||||
: [];
|
||||
where.startDateStart = startDate || undefined;
|
||||
where.endDateEnd = endDate || undefined;
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
syncDateRange();
|
||||
emit('search', { ...where });
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
resetFields();
|
||||
dateRange.value = [];
|
||||
search();
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
syncDateRange();
|
||||
emit('export');
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (userSearchTimer) {
|
||||
clearTimeout(userSearchTimer);
|
||||
userSearchTimer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user