Commit 6ed7b3a6 by 芋道源码 Committed by Gitee

!270 合并商城最新代码

Merge pull request !270 from 芋道源码/dev
parents 04a94ad9 532b2377
...@@ -20,8 +20,8 @@ export interface Sku { ...@@ -20,8 +20,8 @@ export interface Sku {
stock?: number // 库存 stock?: number // 库存
weight?: number // 商品重量,单位:kg 千克 weight?: number // 商品重量,单位:kg 千克
volume?: number // 商品体积,单位:m^3 平米 volume?: number // 商品体积,单位:m^3 平米
firstBrokerageRecord?: number | string // 一级分销的佣金 firstBrokeragePrice?: number | string // 一级分销的佣金
secondBrokerageRecord?: number | string // 二级分销的佣金 secondBrokeragePrice?: number | string // 二级分销的佣金
salesCount?: number // 商品销量 salesCount?: number // 商品销量
} }
......
...@@ -7,17 +7,16 @@ export interface BargainActivityVO { ...@@ -7,17 +7,16 @@ export interface BargainActivityVO {
startTime?: Date startTime?: Date
endTime?: Date endTime?: Date
status?: number status?: number
userSize?: number // 达到该人数,才能砍到低价 helpMaxCount?: number // 达到该人数,才能砍到低价
bargainCount?: number // 最大帮砍次数 bargainCount?: number // 最大帮砍次数
totalLimitCount?: number // 最大购买次数 totalLimitCount?: number // 最大购买次数
spuId: number spuId: number
skuId: number skuId: number
bargainFirstPrice: number // 砍价起始价格,单位分 bargainFirstPrice: number // 砍价起始价格,单位分
bargainPrice: number // 砍价底价 bargainMinPrice: number // 砍价底价
stock: number // 活动库存 stock: number // 活动库存
randomMinPrice?: number // 用户每次砍价的最小金额,单位:分 randomMinPrice?: number // 用户每次砍价的最小金额,单位:分
randomMaxPrice?: number // 用户每次砍价的最大金额,单位:分 randomMaxPrice?: number // 用户每次砍价的最大金额,单位:分
successCount?: number // 砍价成功数量
} }
// 砍价活动所需属性。选择的商品和属性的时候使用方便使用活动的通用封装 // 砍价活动所需属性。选择的商品和属性的时候使用方便使用活动的通用封装
...@@ -25,7 +24,7 @@ export interface BargainProductVO { ...@@ -25,7 +24,7 @@ export interface BargainProductVO {
spuId: number spuId: number
skuId: number skuId: number
bargainFirstPrice: number // 砍价起始价格,单位分 bargainFirstPrice: number // 砍价起始价格,单位分
bargainPrice: number // 砍价底价 bargainMinPrice: number // 砍价底价
stock: number // 活动库存 stock: number // 活动库存
} }
...@@ -58,6 +57,11 @@ export const updateBargainActivity = async (data: BargainActivityVO) => { ...@@ -58,6 +57,11 @@ export const updateBargainActivity = async (data: BargainActivityVO) => {
return await request.put({ url: '/promotion/bargain-activity/update', data }) return await request.put({ url: '/promotion/bargain-activity/update', data })
} }
// 关闭砍价活动
export const closeBargainActivity = async (id: number) => {
return await request.put({ url: '/promotion/bargain-activity/close?id=' + id })
}
// 删除砍价活动 // 删除砍价活动
export const deleteBargainActivity = async (id: number) => { export const deleteBargainActivity = async (id: number) => {
return await request.delete({ url: '/promotion/bargain-activity/delete?id=' + id }) return await request.delete({ url: '/promotion/bargain-activity/delete?id=' + id })
......
import request from '@/config/axios'
export interface BargainHelpVO {
id: number
record: number
userId: number
reducePrice: number
endTime: Date
}
// 查询砍价记录列表
export const getBargainHelpPage = async (params) => {
return await request.get({ url: `/promotion/bargain-help/page`, params })
}
import request from '@/config/axios'
export interface BargainRecordVO {
id: number
activityId: number
userId: number
spuId: number
skuId: number
bargainFirstPrice: number
bargainPrice: number
status: number
orderId: number
endTime: Date
}
// 查询砍价记录列表
export const getBargainRecordPage = async (params) => {
return await request.get({ url: `/promotion/bargain-record/page`, params })
}
...@@ -55,6 +55,11 @@ export const updateCombinationActivity = async (data: CombinationActivityVO) => ...@@ -55,6 +55,11 @@ export const updateCombinationActivity = async (data: CombinationActivityVO) =>
return await request.put({ url: '/promotion/combination-activity/update', data }) return await request.put({ url: '/promotion/combination-activity/update', data })
} }
// 关闭拼团活动
export const closeCombinationActivity = async (id: number) => {
return await request.put({ url: '/promotion/bargain-combination/close?id=' + id })
}
// 删除拼团活动 // 删除拼团活动
export const deleteCombinationActivity = async (id: number) => { export const deleteCombinationActivity = async (id: number) => {
return await request.delete({ url: '/promotion/combination-activity/delete?id=' + id }) return await request.delete({ url: '/promotion/combination-activity/delete?id=' + id })
......
import request from '@/config/axios'
export interface CombinationRecordVO {
id: number // 拼团记录编号
activityId: number // 拼团活动编号
nickname: string // 用户昵称
avatar: string // 用户头像
headId: number // 团长编号
expireTime: string // 过期时间
userSize: number // 可参团人数
userCount: number // 已参团人数
status: number // 拼团状态
spuName: string // 商品名字
picUrl: string // 商品图片
virtualGroup: boolean // 是否虚拟成团
startTime: string // 开始时间 (订单付款后开始的时间)
endTime: string // 结束时间(成团时间/失败时间)
}
// 查询拼团记录列表
export const getCombinationRecordPage = async (params) => {
return await request.get({ url: '/promotion/combination-record/page', params })
}
// 查询一个拼团的完整拼团记录
export const getCombinationRecordPageByHeadId = async (params) => {
return await request.get({ url: '/promotion/combination-record/page-by-headId', params })
}
// 获得拼团记录的概要信息
export const getCombinationRecordSummary = async () => {
return await request.get({ url: '/promotion/combination-record/get-summary' })
}
...@@ -57,6 +57,11 @@ export const updateSeckillActivity = async (data: SeckillActivityVO) => { ...@@ -57,6 +57,11 @@ export const updateSeckillActivity = async (data: SeckillActivityVO) => {
return await request.put({ url: '/promotion/seckill-activity/update', data }) return await request.put({ url: '/promotion/seckill-activity/update', data })
} }
// 关闭秒杀活动
export const closeSeckillActivity = async (id: number) => {
return await request.put({ url: '/promotion/seckill-activity/close?id=' + id })
}
// 删除秒杀活动 // 删除秒杀活动
export const deleteSeckillActivity = async (id: number) => { export const deleteSeckillActivity = async (id: number) => {
return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id }) return await request.delete({ url: '/promotion/seckill-activity/delete?id=' + id })
......
import request from '@/config/axios'
import dayjs from 'dayjs'
import { TradeStatisticsComparisonRespVO } from '@/api/mall/statistics/trade'
import { formatDate } from '@/utils/formatTime'
/** 会员分析 Request VO */
export interface MemberAnalyseReqVO {
times: [dayjs.ConfigType, dayjs.ConfigType]
}
/** 会员分析 Response VO */
export interface MemberAnalyseRespVO {
visitorCount: number
orderUserCount: number
payUserCount: number
atv: number
comparison: TradeStatisticsComparisonRespVO<MemberAnalyseComparisonRespVO>
}
/** 会员分析对照数据 Response VO */
export interface MemberAnalyseComparisonRespVO {
userCount: number
activeUserCount: number
rechargeUserCount: number
}
/** 会员地区统计 Response VO */
export interface MemberAreaStatisticsRespVO {
areaId: number
areaName: string
userCount: number
orderCreateCount: number
orderPayCount: number
orderPayPrice: number
}
/** 会员性别统计 Response VO */
export interface MemberSexStatisticsRespVO {
sex: number
userCount: number
}
/** 会员统计 Response VO */
export interface MemberSummaryRespVO {
userCount: number
rechargeUserCount: number
rechargePrice: number
expensePrice: number
}
/** 会员终端统计 Response VO */
export interface MemberTerminalStatisticsRespVO {
terminal: number
userCount: number
}
// 查询会员统计
export const getMemberSummary = () => {
return request.get<MemberSummaryRespVO>({
url: '/statistics/member/summary'
})
}
// 查询会员分析数据
export const getMemberAnalyse = (params: MemberAnalyseReqVO) => {
return request.get<MemberAnalyseRespVO>({
url: '/statistics/member/analyse',
params: { times: [formatDate(params.times[0]), formatDate(params.times[1])] }
})
}
// 按照省份,查询会员统计列表
export const getMemberAreaStatisticsList = () => {
return request.get<MemberAreaStatisticsRespVO[]>({
url: '/statistics/member/get-area-statistics-list'
})
}
// 按照性别,查询会员统计列表
export const getMemberSexStatisticsList = () => {
return request.get<MemberSexStatisticsRespVO[]>({
url: '/statistics/member/get-sex-statistics-list'
})
}
// 按照终端,查询会员统计列表
export const getMemberTerminalStatisticsList = () => {
return request.get<MemberTerminalStatisticsRespVO[]>({
url: '/statistics/member/get-terminal-statistics-list'
})
}
import request from '@/config/axios'
import dayjs from 'dayjs'
import { formatDate } from '@/utils/formatTime'
/** 交易统计对照 Response VO */
export interface TradeStatisticsComparisonRespVO<T> {
value: T
reference: T
}
/** 交易统计 Response VO */
export interface TradeSummaryRespVO {
yesterdayOrderCount: number
monthOrderCount: number
yesterdayPayPrice: number
monthPayPrice: number
}
/** 交易状况 Request VO */
export interface TradeTrendReqVO {
times: [dayjs.ConfigType, dayjs.ConfigType]
}
/** 交易状况统计 Response VO */
export interface TradeTrendSummaryRespVO {
time: string
turnover: number
orderPayPrice: number
rechargePrice: number
expensePrice: number
balancePrice: number
brokerageSettlementPrice: number
orderRefundPrice: number
}
// 查询交易统计
export const getTradeStatisticsSummary = () => {
return request.get<TradeStatisticsComparisonRespVO<TradeSummaryRespVO>>({
url: '/statistics/trade/summary'
})
}
// 获得交易状况统计
export const getTradeTrendSummary = (params: TradeTrendReqVO) => {
return request.get<TradeStatisticsComparisonRespVO<TradeTrendSummaryRespVO>>({
url: '/statistics/trade/trend/summary',
params: formatDateParam(params)
})
}
// 获得交易状况明细
export const getTradeTrendList = (params: TradeTrendReqVO) => {
return request.get<TradeTrendSummaryRespVO[]>({
url: '/statistics/trade/trend/list',
params: formatDateParam(params)
})
}
// 导出交易状况明细
export const exportTradeTrend = (params: TradeTrendReqVO) => {
return request.download({
url: '/statistics/trade/trend/export-excel',
params: formatDateParam(params)
})
}
/** 时间参数需要格式化, 确保接口能识别 */
const formatDateParam = (params: TradeTrendReqVO) => {
return { times: [formatDate(params.times[0]), formatDate(params.times[1])] } as TradeTrendReqVO
}
...@@ -4,13 +4,13 @@ export interface ConfigVO { ...@@ -4,13 +4,13 @@ export interface ConfigVO {
brokerageEnabled: boolean brokerageEnabled: boolean
brokerageEnabledCondition: number brokerageEnabledCondition: number
brokerageBindMode: number brokerageBindMode: number
brokeragePostUrls: string brokeragePosterUrls: string
brokerageFirstPercent: number brokerageFirstPercent: number
brokerageSecondPercent: number brokerageSecondPercent: number
brokerageWithdrawMinPrice: number brokerageWithdrawMinPrice: number
brokerageBankNames: string brokerageBankNames: string
brokerageFrozenDays: number brokerageFrozenDays: number
brokerageWithdrawType: string brokerageWithdrawTypes: string
} }
// 查询交易中心配置详情 // 查询交易中心配置详情
......
...@@ -41,15 +41,22 @@ export interface OrderVO { ...@@ -41,15 +41,22 @@ export interface OrderVO {
refundPrice?: number | null // 退款金额 refundPrice?: number | null // 退款金额
couponId?: number | null // 优惠劵编号 couponId?: number | null // 优惠劵编号
couponPrice?: number | null // 优惠劵减免金额 couponPrice?: number | null // 优惠劵减免金额
vipPrice?: number | null // VIP 减免金额
pointPrice?: number | null // 积分抵扣的金额 pointPrice?: number | null // 积分抵扣的金额
receiverAreaName?: string //收件人地区名字 receiverAreaName?: string //收件人地区名字
items?: OrderItemRespVO[] // 订单项列表 items?: OrderItemRespVO[] // 订单项列表
// 用户信息 // 下单用户信息
user?: { user?: {
id?: number | null id?: number | null
nickname?: string nickname?: string
avatar?: string avatar?: string
} }
// 推广用户信息
brokerageUser?: {
id?: number | null
nickname?: string
avatar?: string
}
// 订单操作日志 // 订单操作日志
logs?: OrderLogRespVO[] logs?: OrderLogRespVO[]
} }
...@@ -114,21 +121,26 @@ export interface DeliveryVO { ...@@ -114,21 +121,26 @@ export interface DeliveryVO {
} }
// 订单发货 // 订单发货
export const delivery = async (data: DeliveryVO) => { export const deliveryOrder = async (data: DeliveryVO) => {
return await request.put({ url: `/trade/order/delivery`, data }) return await request.put({ url: `/trade/order/delivery`, data })
} }
// 订单备注 // 订单备注
export const updateRemark = async (data: any) => { export const updateOrderRemark = async (data: any) => {
return await request.put({ url: `/trade/order/update-remark`, data }) return await request.put({ url: `/trade/order/update-remark`, data })
} }
// 订单调价 // 订单调价
export const updatePrice = async (data: any) => { export const updateOrderPrice = async (data: any) => {
return await request.put({ url: `/trade/order/update-price`, data }) return await request.put({ url: `/trade/order/update-price`, data })
} }
// 修改订单地址 // 修改订单地址
export const updateAddress = async (data: any) => { export const updateOrderAddress = async (data: any) => {
return await request.put({ url: `/trade/order/update-address`, data }) return await request.put({ url: `/trade/order/update-address`, data })
} }
// 订单核销
export const pickUpOrder = async (id: number) => {
return await request.put({ url: `/trade/order/pick-up?id=${id}` })
}
...@@ -2,18 +2,18 @@ import request from '@/config/axios' ...@@ -2,18 +2,18 @@ import request from '@/config/axios'
export interface ConfigVO { export interface ConfigVO {
id: number id: number
tradeDeductEnable: number pointTradeDeductEnable: number
tradeDeductUnitPrice: number pointTradeDeductUnitPrice: number
tradeDeductMaxPrice: number pointTradeDeductMaxPrice: number
tradeGivePoint: number pointTradeGivePoint: number
} }
// 查询积分设置详情 // 查询积分设置详情
export const getConfig = async () => { export const getConfig = async () => {
return await request.get({ url: `/member/point/config/get` }) return await request.get({ url: `/member/config/get` })
} }
// 新增修改积分设置 // 新增修改积分设置
export const saveConfig = async (data: ConfigVO) => { export const saveConfig = async (data: ConfigVO) => {
return await request.put({ url: `/member/point/config/save`, data }) return await request.put({ url: `/member/config/save`, data })
} }
import request from '@/config/axios' import request from '@/config/axios'
export interface SignInConfigVO { export interface SignInConfigVO {
id: number id?: number
day: number | null day?: number
point: number | null point?: number
enable: boolean | null experience?: number
status?: number
} }
// 查询积分签到规则列表 // 查询积分签到规则列表
......
...@@ -41,3 +41,13 @@ export const updateUser = async (data: UserVO) => { ...@@ -41,3 +41,13 @@ export const updateUser = async (data: UserVO) => {
export const updateUserLevel = async (data: any) => { export const updateUserLevel = async (data: any) => {
return await request.put({ url: `/member/user/update-level`, data }) return await request.put({ url: `/member/user/update-level`, data })
} }
// 修改会员用户积分
export const updateUserPoint = async (data: any) => {
return await request.put({ url: `/member/user/update-point`, data })
}
// 修改会员用户余额
export const updateUserBalance = async (data: any) => {
return await request.put({ url: `/member/user/update-balance`, data })
}
import request from '@/config/axios'
/** 用户钱包查询参数 */
export interface PayWalletUserReqVO {
userId: number
userType: number
}
/** 钱包 VO */
export interface WalletVO {
id: number
userId: number
userType: number
balance: number
totalExpense: number
totalRecharge: number
freezePrice: number
}
/** 查询用户钱包详情 */
export const getWallet = async (params: PayWalletUserReqVO) => {
return await request.get<WalletVO>({ url: `/pay/wallet/get`, params })
}
...@@ -5,14 +5,6 @@ export const getAreaTree = async () => { ...@@ -5,14 +5,6 @@ export const getAreaTree = async () => {
return await request.get({ url: '/system/area/tree' }) return await request.get({ url: '/system/area/tree' })
} }
export const getChildrenArea = async (id: number) => {
return await request.get({ url: '/system/area/get-children?id=' + id })
}
export const getAreaListByIds = async (ids) => {
return await request.get({ url: '/system/area/get-by-ids?ids=' + ids })
}
// 获得 IP 对应的地区名 // 获得 IP 对应的地区名
export const getAreaByIp = async (ip: string) => { export const getAreaByIp = async (ip: string) => {
return await request.get({ url: '/system/area/get-by-ip?ip=' + ip }) return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })
......
{"type":"FeatureCollection","features":[{"type":"Feature","id":"710000","properties":{"id":"710000","cp":[121.509062,24.044332],"name":"台湾","childNum":6},"geometry":{"type":"MultiPolygon","coordinates":[["@@°Ü¯Û"],["@@ƛĴÕƊÉɼģºðʀ\\ƎsÆNŌÔĚäœnÜƤɊĂǀĆĴžĤNJŨxĚĮǂƺòƌ‚–âÔ®ĮXŦţƸZûЋƕƑGđ¨ĭMó·ęcëƝɉlÝƯֹÅŃ^Ó·śŃNjƏďíåɛGɉ™¿@ăƑŽ¥ĘWǬÏĶŁâ"],["@@\\p|WoYG¿¥I†j@¢"],["@@…¡‰@ˆV^RqˆBbAŒnTXeRz¤Lž«³I"],["@@ÆEE—„kWqë @œ"],["@@fced"],["@@„¯ɜÄèaì¯ØǓIġĽ"],["@@çûĖ롖hòř "]],"encodeOffsets":[[[122886,24033]],[[123335,22980]],[[122375,24193]],[[122518,24117]],[[124427,22618]],[[124862,26043]],[[126259,26318]],[[127671,26683]]]}},{"type":"Feature","id":"130000","properties":{"id":"130000","cp":[114.502461,38.045474],"name":"河北","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@o~†Z]‚ªr‰ºc_ħ²G¼s`jΟnüsœłNX_“M`ǽÓnUK…Ĝēs¤­©yrý§uģŒc†JŠ›e"],["@@U`Ts¿m‚"],["@@oºƋÄd–eVŽDJj£€J|Ådz•Ft~žKŨ¸IÆv|”‡¢r}膎onb˜}`RÎÄn°ÒdÞ²„^®’lnÐèĄlðӜ×]ªÆ}LiĂ±Ö`^°Ç¶p®đDcœŋ`–ZÔ’¶êqvFƚ†N®ĆTH®¦O’¾ŠIbÐã´BĐɢŴÆíȦp–ĐÞXR€·nndOž¤’OÀĈƒ­Qg˜µFo|gȒęSWb©osx|hYh•gŃfmÖĩnº€T̒Sp›¢dYĤ¶UĈjl’ǐpäìë|³kÛfw²Xjz~ÂqbTŠÑ„ěŨ@|oM‡’zv¢ZrÃVw¬ŧˏfŒ°ÐT€ªqŽs{Sž¯r æÝlNd®²Ğ džiGʂJ™¼lr}~K¨ŸƐÌWö€™ÆŠzRš¤lêmĞL΄’@¡|q]SvK€ÑcwpÏρ†ĿćènĪWlĄkT}ˆJ”¤~ƒÈT„d„™pddʾĬŠ”ŽBVt„EÀ¢ôPĎƗè@~‚k–ü\\rÊĔÖæW_§¼F˜†´©òDòj’ˆYÈrbĞāøŀG{ƀ|¦ðrb|ÀH`pʞkv‚GpuARhÞÆǶgƊTǼƹS£¨¡ù³ŘÍ]¿Ây™ôEP xX¶¹܇O¡“gÚ¡IwÃ鑦ÅB‡Ï|ǰ…N«úmH¯‹âŸDùŽyŜžŲIÄuШDž•¸dɂ‡‚FŸƒ•›Oh‡đ©OŸ›iÃ`ww^ƒÌkŸ‘ÑH«ƇǤŗĺtFu…{Z}Ö@U‡´…ʚLg®¯Oı°ÃwŸ ^˜—€VbÉs‡ˆmA…ê]]w„§›RRl£‡ȭµu¯b{ÍDěïÿȧŽuT£ġƒěŗƃĝ“Q¨fV†Ƌ•ƅn­a@‘³@šď„yýIĹÊKšŭfċŰóŒxV@tˆƯŒJ”]eƒR¾fe|rHA˜|h~Ėƍl§ÏŠlTíb ØoˆÅbbx³^zÃ͚¶Sj®A”yÂhðk`š«P€”ˈµEF†Û¬Y¨Ļrõqi¼‰Wi°§’б´°^[ˆÀ|ĠO@ÆxO\\tŽa\\tĕtû{ġŒȧXýĪÓjùÎRb›š^ΛfK[ݏděYfíÙTyŽuUSyŌŏů@Oi½’éŅ­aVcř§ax¹XŻác‡žWU£ôãºQ¨÷Ñws¥qEH‰Ù|‰›šYQoŕÇyáĂ£MðoťÊ‰P¡mšWO¡€v†{ôvîēÜISpÌhp¨ ‘j†deŔQÖj˜X³à™Ĉ[n`Yp@Už–cM`’RKhŒEbœ”pŞlNut®Etq‚nsÁŠgA‹iú‹oH‡qCX‡”hfgu“~ϋWP½¢G^}¯ÅīGCŸÑ^ãziMáļMTÃƘrMc|O_ž¯Ŏ´|‡morDkO\\mĆJfl@c̬¢aĦtRıҙ¾ùƀ^juųœK­ƒUFy™—Ɲ…›īÛ÷ąV×qƥV¿aȉd³B›qPBm›aËđŻģm“Å®Vйd^K‡KoŸnYg“¯Xhqa”Ldu¥•ÍpDž¡KąÅƒkĝęěhq‡}HyÓ]¹ǧ£…Í÷¿qáµ§š™g‘¤o^á¾ZE‡¤i`ij{n•ƒOl»ŸWÝĔįhg›F[¿¡—ßkOüš_‰€ū‹i„DZàUtėGylƒ}ŒÓM}€jpEC~¡FtoQi‘šHkk{Ãmï‚"]],"encodeOffsets":[[[119712,40641]],[[121616,39981]],[[116462,37237]]]}},{"type":"Feature","id":"140000","properties":{"id":"140000","cp":[111.849248,36.857014],"name":"山西","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@Þĩ҃S‰ra}Á€yWix±Üe´lè“ßÓǏok‘ćiµVZģ¡coœ‘TS˹ĪmnÕńe–hZg{gtwªpXaĚThȑp{¶Eh—®RćƑP¿£‘Pmc¸mQÝW•ďȥoÅîɡųAďä³aωJ‘½¥PG­ąSM­™…EÅruµé€‘Yӎ•Ō_d›ĒCo­Èµ]¯_²ÕjāŽK~©ÅØ^ԛkïçămϑk]­±ƒcݯÑÃmQÍ~_a—pm…~ç¡q“ˆu{JÅŧ·Ls}–EyÁÆcI{¤IiCfUc•ƌÃp§]웫vD@¡SÀ‘µM‚ÅwuŽYY‡¡DbÑc¡hƒ×]nkoQdaMç~eD•ÛtT‰©±@¥ù@É¡‰ZcW|WqOJmĩl«ħşvOÓ«IqăV—¥ŸD[mI~Ó¢cehiÍ]Ɠ~ĥqXŠ·eƷœn±“}v•[ěďŽŕ]_‘œ•`‰¹ƒ§ÕōI™o©b­s^}Ét±ū«³p£ÿ·Wµ|¡¥ăFÏs׌¥ŅxŸÊdÒ{ºvĴÎêÌɊ²¶€ü¨|ÞƸµȲ‘LLúÉƎ¤ϊęĔV`„_bª‹S^|ŸdŠzY|dz¥p†ZbÆ£¶ÒK}tĦÔņƠ‚PYzn€ÍvX¶Ěn ĠÔ„zý¦ª˜÷žÑĸَUȌ¸‚dòÜJð´’ìúNM¬ŒXZ´‘¤ŊǸ_tldIš{¦ƀðĠȤ¥NehXnYG‚‡R° ƬDj¬¸|CĞ„Kq‚ºfƐiĺ©ª~ĆOQª ¤@ìǦɌ²æBŒÊ”TœŸ˜ʂōĖ’šĴŞ–ȀœÆÿȄlŤĒö„t”νî¼ĨXhŒ‘˜|ªM¤Ðz"],"encodeOffsets":[[116874,41716]]}},{"type":"Feature","id":"150000","properties":{"id":"150000","cp":[111.670801,41.818311],"name":"内蒙古","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@¯PqƒFB…‰|S•³C|kñ•H‹d‘iÄ¥sˆʼnő…PóÑÑE^‘ÅPpy_YtS™hQ·aHwsOnʼnÚs©iqj›‰€USiº]ïWš‰«gW¡A–Rë¥_ŽsgÁnUI«m‰…„‹]j‡vV¼euhwqA„aW˜ƒ_µj…»çjioQR¹ēÃßt@r³[ÛlćË^ÍÉáG“›OUۗOB±•XŸkŇ¹£k|e]ol™ŸkVͼÕqtaÏõjgÁ£§U^Œ”RLˆËnX°Ç’Bz†^~wfvˆypV ¯„ƫĉ˭ȫƗŷɿÿĿƑ˃ĝÿÃǃßËőó©ǐȍŒĖM×ÍEyx‹þp]Évïè‘vƀnÂĴÖ@‚‰†V~Ĉv¦wĖt—ējyÄDXÄxGQuv_›i¦aBçw‘˛wD™©{ŸtāmQ€{EJ§KPśƘƿ¥@‰sCT•É}ɃwˆƇy±ŸgÑ“}T[÷kÐ禫…SÒ¥¸ëBX½‰HáŵÀğtSÝÂa[ƣ°¯¦P]£ġ“–“Òk®G²„èQ°óMq}EŠóƐÇ\\ƒ‡@áügQ͋u¥Fƒ“T՛¿Jû‡]|mvāÎYua^WoÀa·­ząÒot×¶CLƗi¯¤mƎHNJ¤îìɾŊìTdåwsRÖgĒųúÍġäÕ}Q¶—ˆ¿A•†‹[¡Œ{d×uQAƒ›M•xV‹vMOmăl«ct[wº_šÇʊŽŸjb£ĦS_é“QZ“_lwgOiýe`YYLq§IÁˆdz£ÙË[ÕªuƏ³ÍT—s·bÁĽäė[›b[ˆŗfãcn¥îC¿÷µ[ŏÀQ­ōšĉm¿Á^£mJVm‡—L[{Ï_£›F¥Ö{ŹA}…×Wu©ÅaųijƳhB{·TQqÙIķˑZđ©Yc|M¡…L•eVUóK_QWk’_ĥ‘¿ãZ•»X\\ĴuUƒè‡lG®ěłTĠğDєOrÍd‚ÆÍz]‹±…ŭ©ŸÅ’]ŒÅÐ}UË¥©Tċ™ïxgckfWgi\\ÏĒ¥HkµE˜ë{»ÏetcG±ahUiñiWsɁˆ·c–C‚Õk]wȑ|ća}w…VaĚ᠞ŒG°ùnM¬¯†{ÈˆÐÆA’¥ÄêJxÙ¢”hP¢Ûˆº€µwWOŸóFŽšÁz^ÀŗÎú´§¢T¤ǻƺSė‰ǵhÝÅQgvBHouʝl_o¿Ga{ïq{¥|ſĿHĂ÷aĝÇq‡Z‘ñiñC³ª—…»E`¨åXēÕqÉû[l•}ç@čƘóO¿¡ƒFUsA‰“ʽīccšocƒ‚ƒÇS}„“£‡IS~ălkĩXçmĈ…ŀЂoÐdxÒuL^T{r@¢‘žÍƒĝKén£kQ™‰yšÅõËXŷƏL§~}kqš»IHėDžjĝŸ»ÑÞoŸå°qTt|r©ÏS‹¯·eŨĕx«È[eMˆ¿yuˆ‘pN~¹ÏyN£{©’—g‹ħWí»Í¾s“əšDž_ÃĀɗ±ą™ijĉʍŌŷ—S›É“A‹±åǥɋ@럣R©ąP©}ĹªƏj¹erƒLDĝ·{i«ƫC£µsKCš…GS|úþX”gp›{ÁX¿Ÿć{ƱȏñZáĔyoÁhA™}ŅĆfdʼn„_¹„Y°ėǩÑ¡H¯¶oMQqð¡Ë™|‘Ñ`ƭŁX½·óۓxğįÅcQ‡ˆ“ƒs«tȋDžF“Ÿù^i‘t«Č¯[›hAi©á¥ÇĚ×l|¹y¯YȵƓ‹ñǙµï‚ċ™Ļ|Dœ™üȭ¶¡˜›oŽäÕG\\ďT¿Òõr¯œŸLguÏYęRƩšɷŌO\\İТæ^Ŋ IJȶȆbÜGŽĝ¬¿ĚVĎgª^íu½jÿĕęjık@Ľƒ]ėl¥Ë‡ĭûÁ„ƒėéV©±ćn©­ȇžÍq¯½•YÃÔʼn“ÉNѝÅÝy¹NqáʅDǡËñ­ƁYÅy̱os§ȋµʽǘǏƬɱà‘ưN¢ƔÊuľýľώȪƺɂļžxœZĈ}ÌʼnŪ˜ĺœŽĭFЛĽ̅ȣͽÒŵìƩÇϋÿȮǡŏçƑůĕ~Ǎ›¼ȳÐUf†dIxÿ\\G ˆzâɏÙOº·pqy£†@ŒŠqþ@Ǟ˽IBäƣzsÂZ†ÁàĻdñ°ŕzéØűzșCìDȐĴĺf®ŽÀľưø@ɜÖÞKĊŇƄ§‚͑těï͡VAġÑÑ»d³öǍÝXĉĕÖ{þĉu¸ËʅğU̎éhɹƆ̗̮ȘNJ֥ड़ࡰţાíϲäʮW¬®ҌeרūȠkɬɻ̼ãüfƠSצɩςåȈHϚÎKdzͲOðÏȆƘ¼CϚǚ࢚˼ФԂ¤ƌžĞ̪Qʤ´¼mȠJˀŸƲÀɠmǐnǔĎȆÞǠN~€ʢĜ‚¶ƌĆĘźʆȬ˪ĚǏĞGȖƴƀj`ĢçĶāàŃºē̃ĖćšYŒÀŎüôQÐÂŎŞdžŞêƖš˜oˆDĤÕºÑǘÛˤ³̀gńƘĔÀ^žªƂ`ªt¾äƚêĦĀ¼Ð€Ĕǎ¨Ȕ»͠^ˮÊȦƤøxRrŜH¤¸ÂxDĝŒ|ø˂˜ƮÐ¬ɚwɲFjĔ²Äw°dždÀɞ_ĸdîàŎjʜêTĞªŌ‡ŜWÈ|tqĢUB~´°ÎFC•ŽU¼pĀēƄN¦¾O¶ŠłKĊOj“Ě”j´ĜYp˜{¦„ˆSĚÍ\\Tš×ªV–÷Ší¨ÅDK°ßtŇĔKš¨ǵÂcḷ̌ĚǣȄĽF‡lġUĵœŇ‹ȣFʉɁƒMğįʏƶɷØŭOǽ«ƽū¹Ʊő̝Ȩ§ȞʘĖiɜɶʦ}¨֪ࠜ̀ƇǬ¹ǨE˦ĥªÔêFŽxúQ„Er´W„rh¤Ɛ \\talĈDJ˜Ü|[Pll̚¸ƎGú´Pž¬W¦†^¦–H]prR“n|or¾wLVnÇIujkmon£cX^Bh`¥V”„¦U¤¸}€xRj–[^xN[~ªŠxQ„‚[`ªHÆÂExx^wšN¶Ê˜|¨ì†˜€MrœdYp‚oRzNy˜ÀDs~€bcfÌ`L–¾n‹|¾T‚°c¨È¢a‚r¤–`[|òDŞĔöxElÖdH„ÀI`„Ď\\Àì~ƎR¼tf•¦^¢ķ¶e”ÐÚMŒptgj–„ɡČÅyġLû™ŇV®ŠÄÈƀ†Ď°P|ªVV†ªj–¬ĚÒêp¬–E|ŬÂc|ÀtƐK fˆ{ĘFǜƌXƲąo½Ę‘\\¥–o}›Ûu£ç­kX‘{uĩ«āíÓUŅßŢq€Ť¥lyň[€oi{¦‹L‡ń‡ðFȪȖ”ĒL„¿Ì‹ˆfŒ£K£ʺ™oqNŸƒwğc`ue—tOj×°KJ±qƒÆġm‰Ěŗos¬…qehqsuœƒH{¸kH¡Š…ÊRǪÇƌbȆ¢´ä܍¢NìÉʖ¦â©Ġu¦öČ^â£Ăh–šĖMÈÄw‚\\fŦ°W ¢¾luŸD„wŠ\\̀ʉÌÛM…Ā[bӞEn}¶Vc…ê“sƒ"]],"encodeOffsets":[[[129102,52189]]]}},{"type":"Feature","id":"210000","properties":{"id":"210000","cp":[123.429096,41.796767],"name":"辽宁","childNum":16},"geometry":{"type":"MultiPolygon","coordinates":[["@@L–Ž@@s™a"],["@@MnNm"],["@@d‚c"],["@@eÀ‚C@b‚“‰"],["@@f‡…Xwkbr–Ä`qg"],["@@^jtW‘Q"],["@@~ Y]c"],["@@G`ĔN^_¿Z‚ÃM"],["@@iX¶B‹Y"],["@@„YƒZ"],["@@L_{Epf"],["@@^WqCT\\"],["@@\\[“‹§t|”¤_"],["@@m`n_"],["@@Ïxnj{q_×^Giip"],["@@@œé^B†‡ntˆaÊU—˜Ÿ]x ¯ÄPIJ­°h€ʙK³†VˆÕ@Y~†|EvĹsDŽ¦­L^p²ŸÒG ’Ël]„xxÄ_˜fT¤Ď¤cŽœP„–C¨¸TVjbgH²sdÎdHt`Bˆ—²¬GJję¶[ÐhjeXdlwhšðSȦªVÊπ‹Æ‘Z˜ÆŶ®²†^ŒÎyÅÎcPqń“ĚDMħĜŁH­ˆk„çvV[ij¼W–‚YÀäĦ’‘`XlžR`žôLUVžfK–¢†{NZdĒª’YĸÌÚJRr¸SA|ƴgŴĴÆbvªØX~†źBŽ|¦ÕœEž¤Ð`\\|Kˆ˜UnnI]¤ÀÂĊnŎ™R®Ő¿¶\\ÀøíDm¦ÎbŨab‰œaĘ\\ľã‚¸a˜tÎSƐ´©v\\ÖÚÌǴ¤Â‡¨JKr€Z_Z€fjþhPkx€`Y”’RIŒjJcVf~sCN¤ ˆE‚œhæm‰–sHy¨SðÑÌ\\\\ŸĐRZk°IS§fqŒßýáЍÙÉÖ[^¯ǤŲ„ê´\\¦¬ĆPM¯£Ÿˆ»uïpùzEx€žanµyoluqe¦W^£ÊL}ñrkqWňûP™‰UP¡ôJŠoo·ŒU}£Œ„[·¨@XŒĸŸ“‹‹DXm­Ûݏº‡›GU‹CÁª½{íĂ^cj‡k“¶Ã[q¤“LÉö³cux«zZfƒ²BWÇ®Yß½ve±ÃC•ý£W{Ú^’q^sÑ·¨‹ÍOt“¹·C¥‡GD›rí@wÕKţ݋˜Ÿ«V·i}xËÍ÷‘i©ĝ‡ɝǡ]ƒˆ{c™±OW‹³Ya±Ÿ‰_穂Hžĕoƫ€Ňqƒr³‰Lys[„ñ³¯OS–ďOMisZ†±ÅFC¥Pq{‚Ã[Pg}\\—¿ghćO…•k^ģÁFıĉĥM­oEqqZûěʼn³F‘¦oĵ—hŸÕP{¯~TÍlª‰N‰ßY“Ð{Ps{ÃVU™™eĎwk±ʼnVÓ½ŽJãÇÇ»Jm°dhcÀff‘dF~ˆ€ĀeĖ€d`sx² šƒ®EżĀdQ‹Âd^~ăÔHˆ¦\\›LKpĄVez¤NP ǹӗR™ÆąJSh­a[¦´Âghwm€BÐ¨źhI|žVVŽ—Ž|p] Â¼èNä¶ÜBÖ¼“L`‚¼bØæŒKV”ŸpoœúNZÞÒKxpw|ÊEMnzEQšŽIZ”ŽZ‡NBˆčÚFÜçmĩ‚WĪñt‘ÞĵÇñZ«uD‚±|Əlij¥ãn·±PmÍa‰–da‡ CL‡Ǒkùó¡³Ï«QaċϑOÃ¥ÕđQȥċƭy‹³ÃA"]],"encodeOffsets":[[[123686,41445]],[[126019,40435]],[[124393,40128]],[[126117,39963]],[[125322,40140]],[[126686,40700]],[[126041,40374]],[[125584,40168]],[[125453,40165]],[[125362,40214]],[[125280,40291]],[[125774,39997]],[[125976,40496]],[[125822,39993]],[[125509,40217]],[[122731,40949]]]}},{"type":"Feature","id":"220000","properties":{"id":"220000","cp":[125.3245,43.886841],"name":"吉林","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@‘p䔳PClƒFbbÍzš€wBG’ĭ€Z„Åi“»ƒlY­ċ²SgŽkÇ£—^S‰“qd¯•‹R…©éŽ£¯S†\\cZ¹iűƏCuƍÓX‡oR}“M^o•£…R}oªU­F…uuXHlEŕ‡€Ï©¤ÛmTŽþ¤D–²ÄufàÀ­XXȱAe„yYw¬dvõ´KÊ£”\\rµÄl”iˆdā]|DÂVŒœH¹ˆÞ®ÜWnŒC”Œķ W‹§@\\¸‹ƒ~¤‹Vp¸‰póIO¢ŠVOšŇürXql~òÉK]¤¥Xrfkvzpm¶bwyFoúvð‡¼¤ N°ąO¥«³[ƒéǡű_°Õ\\ÚÊĝŽþâőàerR¨­JYlďQ[ ÏYëЧTGz•tnŠß¡gFkMŸāGÁ¤ia É‰™È¹`\\xs€¬dĆkNnuNUŠ–užP@‚vRY¾•–\\¢…ŒGªóĄ~RãÖÎĢù‚đŴÕhQŽxtcæëSɽʼníëlj£ƍG£nj°KƘµDsØÑpyƸ®¿bXp‚]vbÍZuĂ{nˆ^IüœÀSք”¦EŒvRÎûh@℈[‚Əȉô~FNr¯ôçR±ƒ­HÑl•’Ģ–^¤¢‚OðŸŒævxsŒ]ÞÁTĠs¶¿âƊGW¾ìA¦·TѬ†è¥€ÏÐJ¨¼ÒÖ¼ƒƦɄxÊ~S–tD@ŠĂ¼Ŵ¡jlºWžvЉˆzƦZЎ²CH— „Axiukd‹ŒGgetqmcžÛ£Ozy¥cE}|…¾cZ…k‚‰¿uŐã[oxGikfeäT@…šSUwpiÚFM©’£è^ڟ‚`@v¶eň†f h˜eP¶žt“äOlÔUgƒÞzŸU`lœ}ÔÆUvØ_Ō¬Öi^ĉi§²ÃŠB~¡Ĉ™ÚEgc|DC_Ȧm²rBx¼MÔ¦ŮdĨÃâYx‘ƘDVÇĺĿg¿cwÅ\\¹˜¥Yĭlœ¤žOv†šLjM_a W`zļMž·\\swqÝSA‡š—q‰Śij¯Š‘°kŠRē°wx^Đkǂғ„œž“œŽ„‹\\]˜nrĂ}²ĊŲÒøãh·M{yMzysěnĒġV·°“G³¼XÀ““™¤¹i´o¤ŃšŸÈ`̃DzÄUĞd\\i֚ŒˆmÈBĤÜɲDEh LG¾ƀľ{WaŒYÍȏĢĘÔRîĐj‹}Ǟ“ccj‡oUb½š{“h§Ǿ{K‹ƖµÎ÷žGĀÖŠåưÎs­l›•yiē«‹`姝H¥Ae^§„GK}iã\\c]v©ģZ“mÃ|“[M}ģTɟĵ‘Â`À–çm‰‘FK¥ÚíÁbXš³ÌQґHof{‰]e€pt·GŋĜYünĎųVY^’˜ydõkÅZW„«WUa~U·Sb•wGçǑ‚“iW^q‹F‚“›uNĝ—·Ew„‹UtW·Ýďæ©PuqEzwAV•—XR‰ãQ`­©GŒM‡ehc›c”ďϝd‡©ÑW_ϗYƅŒ»…é\\ƒɹ~ǙG³mØ©BšuT§Ĥ½¢Ã_ý‘L¡‘ýŸqT^rme™\\Pp•ZZbƒyŸ’uybQ—efµ]UhĿDCmûvašÙNSkCwn‰cćfv~…Y‹„ÇG"],"encodeOffsets":[[130196,42528]]}},{"type":"Feature","id":"230000","properties":{"id":"230000","cp":[128.642464,46.756967],"name":"黑龙江","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@UƒµNÿ¥īè灋•HÍøƕ¶LŒǽ|g¨|”™Ža¾pViˆdd”~ÈiŒíďÓQġėǐZ΋ŽXb½|ſÃH½ŸKFgɱCģÛÇA‡n™‹jÕc[VĝDZÃ˄Ç_™ £ń³pŽj£º”š¿”»WH´¯”U¸đĢmžtĜyzzNN|g¸÷äűѱĉā~mq^—Œ[ƒ”››”ƒǁÑďlw]¯xQĔ‰¯l‰’€°řĴrŠ™˜BˆÞTxr[tޏĻN_yŸX`biN™Ku…P›£k‚ZĮ—¦[ºxÆÀdhŽĹŀUÈƗCw’áZħÄŭcÓ¥»NAw±qȥnD`{ChdÙFćš}¢‰A±Äj¨]ĊÕjŋ«×`VuÓś~_kŷVÝyh„“VkÄãPs”Oµ—fŸge‚Ň…µf@u_Ù ÙcŸªNªÙEojVx™T@†ãSefjlwH\\pŏäÀvŠŽlY†½d{†F~¦dyz¤PÜndsrhf‹HcŒvlwjFœ£G˜±DύƥY‡yϊu¹XikĿ¦ÏqƗǀOŜ¨LI|FRĂn sª|Cš˜zxAè¥bœfudTrFWÁ¹Am|˜ĔĕsķÆF‡´Nš‰}ć…UŠÕ@Áijſmužç’uð^ÊýowŒFzØÎĕNőžǏȎôªÌŒDŽàĀÄ˄ĞŀƒʀĀƘŸˮȬƬĊ°ƒUŸzou‡xe]}Ž…AyȑW¯ÌmK‡“Q]‹Īºif¸ÄX|sZt|½ÚUΠlkš^p{f¤lˆºlÆW –€A²˜PVܜPH”Êâ]ÎĈÌÜk´\\@qàsĔÄQºpRij¼èi†`¶—„bXƒrBgxfv»ŽuUiˆŒ^v~”J¬mVp´£Œ´VWrnP½ì¢BX‚¬h™ŠðX¹^TjVœŠriªj™tŊÄm€tPGx¸bgRšŽsT`ZozÆO]’ÒFô҆Oƒ‡ŊŒvŞ”p’cGŒêŠsx´DR–Œ{A†„EOr°Œ•žx|íœbˆ³Wm~DVjºéNN†Ëܲɶ­GƒxŷCStŸ}]ûō•SmtuÇÃĕN•™āg»šíT«u}ç½BĵÞʣ¥ëÊ¡Mێ³ãȅ¡ƋaǩÈÉQ‰†G¢·lG|›„tvgrrf«†ptęŘnŠÅĢr„I²¯LiØsPf˜_vĠd„xM prʹšL¤‹¤‡eˌƒÀđK“žïÙVY§]I‡óáĥ]ķ†Kˆ¥Œj|pŇ\\kzţ¦šnņäÔVĂîά|vW’®l¤èØr‚˜•xm¶ă~lÄƯĄ̈́öȄEÔ¤ØQĄ–Ą»ƢjȦOǺ¨ìSŖÆƬy”Qœv`–cwƒZSÌ®ü±DŽ]ŀç¬B¬©ńzƺŷɄeeOĨS’Œfm Ċ‚ƀP̎ēz©Ċ‚ÄÕÊmgŸÇsJ¥ƔˆŊśæ’΁Ñqv¿íUOµª‰ÂnĦÁ_½ä@ê텣P}Ġ[@gġ}g“ɊדûÏWXá¢užƻÌsNͽƎÁ§č՛AēeL³àydl›¦ĘVçŁpśdžĽĺſʃQíÜçÛġԏsĕ¬—Ǹ¯YßċġHµ ¡eå`ļƒrĉŘóƢFì“ĎWøxÊk†”ƈdƬv|–I|·©NqńRŀƒ¤é”eŊœŀ›ˆàŀU²ŕƀB‚Q£Ď}L¹Îk@©ĈuǰųǨ”Ú§ƈnTËÇéƟÊcfčŤ^Xm‡—HĊĕË«W·ċëx³ǔķÐċJā‚wİ_ĸ˜Ȁ^ôWr­°oú¬Ħ…ŨK~”ȰCĐ´Ƕ£’fNÎèâw¢XnŮeÂÆĶŽ¾¾xäLĴĘlļO¤ÒĨA¢Êɚ¨®‚ØCÔ ŬGƠ”ƦYĜ‡ĘÜƬDJ—g_ͥœ@čŅĻA“¶¯@wÎqC½Ĉ»NŸăëK™ďÍQ“Ùƫ[«Ãí•gßÔÇOÝáW‘ñuZ“¯ĥ€Ÿŕā¡ÑķJu¤E Ÿå¯°WKɱ_d_}}vyŸõu¬ï¹ÓU±½@gÏ¿rýD‰†g…Cd‰µ—°MFYxw¿CG£‹Rƛ½Õ{]L§{qqąš¿BÇƻğëšܭNJË|c²}Fµ}›ÙRsÓpg±ŠQNqǫŋRwŕnéÑÉKŸ†«SeYR…ŋ‹@{¤SJ}šD Ûǖ֍Ÿ]gr¡µŷjqWÛham³~S«“„›Þ]"]],"encodeOffsets":[[[134456,44547]]]}},{"type":"Feature","id":"320000","properties":{"id":"320000","cp":[119.767413,33.041544],"name":"江苏","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@cþÅPiŠ`ZŸRu¥É\\]~°ŽY`µ†Óƒ^phÁbnÀşúŽòa–ĬºTÖŒb‚˜e¦¦€{¸ZâćNpŒ©žHr|^ˆmjhŠSEb\\afv`sz^lkŽlj‹Ätg‹¤D˜­¾Xš¿À’|ДiZ„ȀåB·î}GL¢õcßjaŸyBFµÏC^ĭ•cÙt¿sğH]j{s©HM¢ƒQnDÀ©DaÜތ·jgàiDbPufjDk`dPOîƒhw¡ĥ‡¥šG˜ŸP²ĐobºrY†„î¶aHŢ´ ]´‚rılw³r_{£DB_Ûdåuk|ˆŨ¯F Cºyr{XFy™e³Þċ‡¿Â™kĭB¿„MvÛpm`rÚã”@ƹhågËÖƿxnlč¶Åì½Ot¾dJlŠVJʜǀœŞqvnOŠ^ŸJ”Z‘ż·Q}ê͎ÅmµÒ]Žƍ¦Dq}¬R^èĂ´ŀĻĊIԒtžIJyQŐĠMNtœR®òLh‰›Ěs©»œ}OӌGZz¶A\\jĨFˆäOĤ˜HYš†JvÞHNiÜaϚɖnFQlšNM¤ˆB´ĄNöɂtp–Ŭdf先‹qm¿QûŠùއÚb¤uŃJŴu»¹Ą•lȖħŴw̌ŵ²ǹǠ͛hĭłƕrçü±Y™xci‡tğ®jű¢KOķ•Coy`å®VTa­_Ā]ŐÝɞï²ʯÊ^]afYǸÃĆēĪȣJđ͍ôƋĝÄ͎ī‰çÛɈǥ£­ÛmY`ó£Z«§°Ó³QafusNıDž_k}¢m[ÝóDµ—¡RLčiXy‡ÅNïă¡¸iĔϑNÌŕoēdōîåŤûHcs}~Ûwbù¹£¦ÓCt‹OPrƒE^ÒoŠg™ĉIµžÛÅʹK…¤½phMŠü`o怆ŀ"],"encodeOffsets":[[121740,32276]]}},{"type":"Feature","id":"330000","properties":{"id":"330000","cp":[120.153576,29.287459],"name":"浙江","childNum":45},"geometry":{"type":"MultiPolygon","coordinates":[["@@E^dQ]K"],["@@jX^j‡"],["@@sfŠbU‡"],["@@qP\\xz[ck"],["@@‘Rƒ¢‚FX}°[s_"],["@@Cbœ\\—}"],["@@e|v\\la{u"],["@@v~u}"],["@@QxÂF¯}"],["@@¹nŒvÞs¯o"],["@@rSkUEj"],["@@bi­ZŒP"],["@@p[}INf"],["@@À¿€"],["@@¹dnbŒ…"],["@@rSŸBnR"],["@@g~h}"],["@@FlEk"],["@@OdPc"],["@@v[u\\"],["@@FjâL~wyoo~›sµL–\\"],["@@¬e¹aNˆ"],["@@\\nÔ¡q]L³ë\\ÿ®ŒQ֎"],["@@ÊA­©[¬"],["@@KxŒv­"],["@@@hlIk]"],["@@pW{o||j"],["@@Md|_mC"],["@@¢…X£ÏylD¼XˆtH"],["@@hlÜ[LykAvyfw^Ež›¤"],["@@fp¤Mus“R"],["@@®_ma~•LÁ¬šZ"],["@@iM„xZ"],["@@ZcYd"],["@@Z~dOSo|A¿qZv"],["@@@`”EN¡v"],["@@|–TY{"],["@@@n@m"],["@@XWkCT\\"],["@@ºwšZRkĕWO¢"],["@@™X®±Grƪ\\ÔáXq{‹"],["@@ůTG°ĄLHm°UC‹"],["@@¤Ž€aÜx~}dtüGæţŎíĔcŖpMËВj碷ðĄÆMzˆjWKĎ¢Q¶˜À_꒔_Bı€i«pZ€gf€¤Nrq]§ĂN®«H±‡yƳí¾×ŸīàLłčŴǝĂíÀBŖÕªˆŠÁŖHŗʼnåqûõi¨hÜ·ƒñt»¹ýv_[«¸m‰YL¯‰Qª…mĉÅdMˆ•gÇjcº«•ęœ¬­K­´ƒB«Âącoċ\\xKd¡gěŧ«®á’[~ıxu·Å”KsËɏc¢Ù\\ĭƛëbf¹­ģSƒĜkáƉÔ­ĈZB{ŠaM‘µ‰fzʼnfåÂŧįƋǝÊĕġć£g³ne­ą»@­¦S®‚\\ßðCšh™iqªĭiAu‡A­µ”_W¥ƣO\\lċĢttC¨£t`ˆ™PZäuXßBs‡Ļyek€OđġĵHuXBšµ]׌‡­­\\›°®¬F¢¾pµ¼kŘó¬Wät’¸|@ž•L¨¸µr“ºù³Ù~§WI‹ŸZWŽ®’±Ð¨ÒÉx€`‰²pĜ•rOògtÁZ}þÙ]„’¡ŒŸFK‚wsPlU[}¦Rvn`hq¬\\”nQ´ĘRWb”‚_ rtČFI֊kŠŠĦPJ¶ÖÀÖJĈĄTĚòžC ²@Pú…Øzœ©PœCÈÚœĒ±„hŖ‡l¬â~nm¨f©–iļ«m‡nt–u†ÖZÜÄj“ŠLŽ®E̜Fª²iÊxبžIÈhhst"],["@@o\\V’zRZ}y"],["@@†@°¡mۛGĕ¨§Ianá[ýƤjfæ‡ØL–•äGr™"]],"encodeOffsets":[[[125592,31553]],[[125785,31436]],[[125729,31431]],[[125513,31380]],[[125223,30438]],[[125115,30114]],[[124815,29155]],[[124419,28746]],[[124095,28635]],[[124005,28609]],[[125000,30713]],[[125111,30698]],[[125078,30682]],[[125150,30684]],[[124014,28103]],[[125008,31331]],[[125411,31468]],[[125329,31479]],[[125626,30916]],[[125417,30956]],[[125254,30976]],[[125199,30997]],[[125095,31058]],[[125083,30915]],[[124885,31015]],[[125218,30798]],[[124867,30838]],[[124755,30788]],[[124802,30809]],[[125267,30657]],[[125218,30578]],[[125200,30562]],[[124968,30474]],[[125167,30396]],[[124955,29879]],[[124714,29781]],[[124762,29462]],[[124325,28754]],[[123990,28459]],[[125366,31477]],[[125115,30363]],[[125369,31139]],[[122495,31878]],[[125329,30690]],[[125192,30787]]]}},{"type":"Feature","id":"340000","properties":{"id":"340000","cp":[117.283042,31.26119],"name":"安徽","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@^iuLX^"],["@@‚e©Ehl"],["@@°ZÆëϵmkǀwÌÕæhºgBĝâqÙĊz›ÖgņtÀÁÊÆá’hEz|WzqD¹€Ÿ°E‡ŧl{ævÜcA`¤C`|´qžxIJkq^³³ŸGšµbƒíZ…¹qpa±ď OH—¦™Ħˆx¢„gPícOl_iCveaOjCh߸i݋bÛªCC¿€m„RV§¢A|t^iĠGÀtÚs–d]ĮÐDE¶zAb àiödK¡~H¸íæAžǿYƒ“j{ď¿‘™À½W—®£ChŒÃsiŒkkly]_teu[bFa‰Tig‡n{]Gqªo‹ĈMYá|·¥f¥—őaSÕė™NµñĞ«ImŒ_m¿Âa]uĜp …Z_§{Cƒäg¤°r[_Yj‰ÆOdý“[ŽI[á·¥“Q_n‡ùgL¾mv™ˊBÜÆ¶ĊJhšp“c¹˜O]iŠ]œ¥ jtsggJǧw×jÉ©±›EFˍ­‰Ki”ÛÃÕYv…s•ˆm¬njĻª•§emná}k«ŕˆƒgđ²Ù›DǤ›í¡ªOy›†×Où±@DŸñSęćăÕIÕ¿IµĥO‰‰jNÕËT¡¿tNæŇàåyķrĕq§ÄĩsWÆßŽF¶žX®¿‰mŒ™w…RIޓfßoG‘³¾©uyH‘į{Ɓħ¯AFnuP…ÍÔzšŒV—dàôº^Ðæd´€‡oG¤{S‰¬ćxã}›ŧ×Kǥĩ«žÕOEзÖdÖsƘѨ[’Û^Xr¢¼˜§xvěƵ`K”§ tÒ´Cvlo¸fzŨð¾NY´ı~ÉĔē…ßúLÃϖ_ÈÏ|]ÂÏFl”g`bšežž€n¾¢pU‚h~ƴ˶_‚r sĄ~cž”ƈ]|r c~`¼{À{ȒiJjz`îÀT¥Û³…]’u}›f…ïQl{skl“oNdŸjŸäËzDvčoQŠďHI¦rb“tHĔ~BmlRš—V_„ħTLnñH±’DžœL‘¼L˜ªl§Ťa¸ŒĚlK²€\\RòvDcÎJbt[¤€D@®hh~kt°ǾzÖ@¾ªdb„YhüóZ ň¶vHrľ\\ʗJuxAT|dmÀO„‹[ÃԋG·ĚąĐlŪÚpSJ¨ĸˆLvÞcPæķŨŽ®mАˆálŸwKhïgA¢ųƩޖ¤OȜm’°ŒK´"]],"encodeOffsets":[[[121722,32278]],[[119475,30423]],[[119168,35472]]]}},{"type":"Feature","id":"350000","properties":{"id":"350000","cp":[118.306239,26.075302],"name":"福建","childNum":18},"geometry":{"type":"MultiPolygon","coordinates":[["@@“zht´‡]"],["@@aj^~ĆG—©O"],["@@ed¨„C}}i"],["@@@vˆPGsQ"],["@@‰sBz‚ddW]Q"],["@@SލQ“{"],["@@NŽVucW"],["@@qptBAq"],["@@‰’¸[mu"],["@@Q\\pD]_"],["@@jSwUadpF"],["@@eXª~ƒ•"],["@@AjvFso"],["@@fT–›_Çí\\Ÿ™—v|ba¦jZÆy€°"],["@@IjJi"],["@@wJI€ˆxš«¼AoNe{M­"],["@@K‰±¡Óˆ”ČäeZ"],["@@k¡¹Eh~c®wBk‹UplÀ¡I•~Māe£bN¨gZý¡a±Öcp©PhžI”Ÿ¢Qq…ÇGj‹|¥U™ g[Ky¬ŏ–v@OpˆtÉEŸF„\\@ åA¬ˆV{Xģ‰ĐBy…cpě…¼³Ăp·¤ƒ¥o“hqqÚ¡ŅLsƒ^ᗞ§qlŸÀhH¨MCe»åÇGD¥zPO£čÙkJA¼ß–ėu›ĕeûҍiÁŧSW¥˜QŠûŗ½ùěcݧSùĩąSWó«íęACµ›eR—åǃRCÒÇZÍ¢‹ź±^dlsŒtjD¸•‚ZpužÔâÒH¾oLUêÃÔjjēò´ĄW‚ƛ…^Ñ¥‹ĦŸ@Çò–ŠmŒƒOw¡õyJ†yD}¢ďÑÈġfŠZd–a©º²z£šN–ƒjD°Ötj¶¬ZSÎ~¾c°¶Ðm˜x‚O¸¢Pl´žSL|¥žA†ȪĖM’ņIJg®áIJČĒü` ŽQF‡¬h|ÓJ@zµ |ê³È ¸UÖŬŬÀEttĸr‚]€˜ðŽM¤ĶIJHtÏ A’†žĬkvsq‡^aÎbvŒd–™fÊòSD€´Z^’xPsÞrv‹ƞŀ˜jJd×ŘÉ ®A–ΦĤd€xĆqAŒ†ZR”ÀMźŒnĊ»ŒİÐZ— YX–æJŠyĊ²ˆ·¶q§·–K@·{s‘Xãô«lŗ¶»o½E¡­«¢±¨Yˆ®Ø‹¶^A™vWĶGĒĢžPlzfˆļŽtàAvWYãšO_‡¤sD§ssČġ[kƤPX¦Ž`¶“ž®ˆBBvĪjv©šjx[L¥àï[F…¼ÍË»ğV`«•Ip™}ccÅĥZE‹ãoP…´B@ŠD—¸m±“z«Ƴ—¿å³BRضˆœWlâþäą`“]Z£Tc— ĹGµ¶H™m@_©—kŒ‰¾xĨ‡ôȉðX«½đCIbćqK³Á‹Äš¬OAwã»aLʼn‡ËĥW[“ÂGI—ÂNxij¤D¢ŽîĎÎB§°_JœGsƒ¥E@…¤uć…P‘å†cuMuw¢BI¿‡]zG¹guĮck\\_"]],"encodeOffsets":[[[123250,27563]],[[122541,27268]],[[123020,27189]],[[122916,27125]],[[122887,26845]],[[122808,26762]],[[122568,25912]],[[122778,26197]],[[122515,26757]],[[122816,26587]],[[123388,27005]],[[122450,26243]],[[122578,25962]],[[121255,25103]],[[120987,24903]],[[122339,25802]],[[121042,25093]],[[122439,26024]]]}},{"type":"Feature","id":"360000","properties":{"id":"360000","cp":[115.592151,27.676493],"name":"江西","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@ĢĨƐgÂMD~ņªe^\\^§„ý©j׍cZ†Ø¨zdÒa¶ˆlҍJŒìõ`oz÷@¤u޸´†ôęöY¼‰HČƶajlÞƩ¥éZ[”|h}^U Œ ¥p„ĄžƦO lt¸Æ €Q\\€ŠaÆ|CnÂOjt­ĚĤd’ÈŒF`’¶„@Ð딠¦ōҞ¨Sêv†HĢûXD®…QgėWiØPÞìºr¤dž€NĠ¢l–•ĄtZoœCƞÔºCxrpĠV®Ê{f_Y`_ƒeq’’®Aot`@o‚DXfkp¨|Šs¬\\D‘ÄSfè©Hn¬…^DhÆyøJh“ØxĢĀLʈ„ƠPżċĄwȠ̦G®ǒĤäTŠÆ~ĦwŠ«|TF¡Šn€c³Ïå¹]ĉđxe{ÎӐ†vOEm°BƂĨİ|G’vz½ª´€H’àp”eJ݆Qšxn‹ÀŠW­žEµàXÅĪt¨ÃĖrÄwÀFÎ|ňÓMå¼ibµ¯»åDT±m[“r«_gŽmQu~¥V\\OkxtL E¢‹ƒ‘Ú^~ýê‹Pó–qo슱_Êw§ÑªåƗ⼋mĉŹ‹¿NQ“…YB‹ąrwģcÍ¥B•Ÿ­ŗÊcØiI—žƝĿuŒqtāwO]‘³YCñTeɕš‹caub͈]trlu€ī…B‘ПGsĵıN£ï—^ķqss¿FūūV՟·´Ç{éĈý‰ÿ›OEˆR_ŸđûIċâJh­ŅıN‘ȩĕB…¦K{Tk³¡OP·wn—µÏd¯}½TÍ«YiµÕsC¯„iM•¤™­•¦¯P|ÿUHv“he¥oFTu‰õ\\ŽOSs‹MòđƇiaºćXŸĊĵà·çhƃ÷ǜ{‘ígu^›đg’m[×zkKN‘¶Õ»lčÓ{XSƉv©_ÈëJbVk„ĔVÀ¤P¾ºÈMÖxlò~ªÚàGĂ¢B„±’ÌŒK˜y’áV‡¼Ã~­…`g›ŸsÙfI›Ƌlę¹e|–~udjˆuTlXµf`¿JdŠ[\\˜„L‚‘²"],"encodeOffsets":[[116689,26234]]}},{"type":"Feature","id":"370000","properties":{"id":"370000","cp":[118.000923,36.275807],"name":"山东","childNum":13},"geometry":{"type":"MultiPolygon","coordinates":[["@@Xjd]{K"],["@@itbFHy"],["@@HlGk"],["@@T‚ŒGŸy"],["@@K¬˜•‹U"],["@@WdXc"],["@@PtOs"],["@@•LnXhc"],["@@ppVƒu]Or"],["@@cdzAUa"],["@@udRhnCI‡"],["@@ˆoIƒpR„"],["@@Ľč{fzƤî’Kš–ÎMĮ]†—ZFˆ½Y]â£ph’™š¶¨râøÀ†ÎǨ¤^ºÄ”Gzˆ~grĚĜlĞÆ„LĆdž¢Îo¦–cv“Kb€gr°Wh”mZp ˆL]LºcU‰Æ­n”żĤÌǜbAnrOAœ´žȊcÀbƦUØrĆUÜøœĬƞ†š˜Ez„VL®öØBkŖÝĐ˹ŧ̄±ÀbÎɜnb²ĦhņBĖ›žįĦåXćì@L¯´ywƕCéõė ƿ¸‘lµ¾Z|†ZWyFYŸ¨Mf~C¿`€à_RÇzwƌfQnny´INoƬˆèôº|sT„JUš›‚L„îVj„ǎ¾Ē؍‚Dz²XPn±ŴPè¸ŔLƔÜƺ_T‘üÃĤBBċȉöA´fa„˜M¨{«M`‡¶d¡ô‰Ö°šmȰBÔjjŒ´PM|”c^d¤u•ƒ¤Û´Œä«ƢfPk¶Môlˆ]Lb„}su^ke{lC‘…M•rDŠÇ­]NÑFsmoõľH‰yGă{{çrnÓE‰‹ƕZGª¹Fj¢ïW…uøCǷ돡ąuhÛ¡^Kx•C`C\\bÅxì²ĝÝ¿_N‰īCȽĿåB¥¢·IŖÕy\\‡¹kx‡Ã£Č×GDyÕ¤ÁçFQ¡„KtŵƋ]CgÏAùSed‡cÚź—ŠuYfƒyMmhUWpSyGwMPqŀ—›Á¼zK›¶†G•­Y§Ëƒ@–´śÇµƕBmœ@Io‚g——Z¯u‹TMx}C‘‰VK‚ï{éƵP—™_K«™pÛÙqċtkkù]gŽ‹Tğwo•ɁsMõ³ă‡AN£™MRkmEʕč™ÛbMjÝGu…IZ™—GPģ‡ãħE[iµBEuŸDPԛ~ª¼ętŠœ]ŒûG§€¡QMsğNPŏįzs£Ug{đJĿļā³]ç«Qr~¥CƎÑ^n¶ÆéÎR~ݏY’I“] P‰umŝrƿ›‰›Iā‹[x‰edz‹L‘¯v¯s¬ÁY…~}…ťuٌg›ƋpÝĄ_ņī¶ÏSR´ÁP~ž¿Cyžċßdwk´Ss•X|t‰`Ä Èð€AªìÎT°¦Dd–€a^lĎDĶÚY°Ž`ĪŴǒˆ”àŠv\\ebŒZH„ŖR¬ŢƱùęO•ÑM­³FۃWp[ƒ"]],"encodeOffsets":[[[123806,39303]],[[123821,39266]],[[123742,39256]],[[123702,39203]],[[123649,39066]],[[123847,38933]],[[123580,38839]],[[123894,37288]],[[123043,36624]],[[123344,38676]],[[123522,38857]],[[123628,38858]],[[118260,36742]]]}},{"type":"Feature","id":"410000","properties":{"id":"410000","cp":[113.665412,33.757975],"name":"河南","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@•ýL™ùµP³swIÓxcŢĞð†´E®žÚPt†ĴXØx¶˜@«ŕŕQGƒ‹Yfa[şu“ßǩ™đš_X³ijÕčC]kbc•¥CS¯ëÍB©÷‹–³­Siˆ_}m˜YTtž³xlàcȂzÀD}ÂOQ³ÐTĨ¯†ƗòËŖ[hœł‹Ŧv~††}ÂZž«¤lPǕ£ªÝŴÅR§ØnhcŒtâk‡nύ­ľŹUÓÝdKuķ‡I§oTũÙďkęĆH¸ÓŒ\\ăŒ¿PcnS{wBIvɘĽ[GqµuŸŇôYgûƒZcaŽ©@½Õǽys¯}lgg@­C\\£as€IdÍuCQñ[L±ęk·‹ţb¨©kK—’»›KC²‘òGKmĨS`ƒ˜UQ™nk}AGē”sqaJ¥ĐGR‰ĎpCuÌy ã iMc”plk|tRk†ðœev~^‘´†¦ÜŽSí¿_iyjI|ȑ|¿_»d}qŸ^{“Ƈdă}Ÿtqµ`Ƴĕg}V¡om½fa™Ço³TTj¥„tĠ—Ry”K{ùÓjuµ{t}uËR‘iŸvGŠçJFjµŠÍyqΘàQÂFewixGw½Yŷpµú³XU›½ġy™łå‰kÚwZXˆ·l„¢Á¢K”zO„Λ΀jc¼htoDHr…|­J“½}JZ_¯iPq{tę½ĕ¦Zpĵø«kQ…Ťƒ]MÛfaQpě±ǽ¾]u­Fu‹÷nƒ™čįADp}AjmcEǒaª³o³ÆÍSƇĈÙDIzˑ赟^ˆKLœ—i—Þñ€[œƒaA²zz‰Ì÷Dœ|[šíijgf‚ÕÞd®|`ƒĆ~„oĠƑô³Ŋ‘D×°¯CsŠøÀ«ì‰UMhTº¨¸ǡîS–Ô„DruÂÇZ•ÖEŽ’vPZ„žW”~؋ÐtĄE¢¦Ðy¸bŠô´oŬ¬Ž²Ês~€€]®tªašpŎJ¨Öº„_ŠŔ–`’Ŗ^Ѝ\\Ĝu–”~m²Ƹ›¸fW‰ĦrƔ}Î^gjdfÔ¡J}\\n C˜¦þWxªJRÔŠu¬ĨĨmF†dM{\\d\\ŠYÊ¢ú@@¦ª²SŠÜsC–}fNècbpRmlØ^g„d¢aÒ¢CZˆZxvÆ¶N¿’¢T@€uCœ¬^ĊðÄn|žlGl’™Rjsp¢ED}€Fio~ÔNŽ‹„~zkĘHVsDzßjƒŬŒŠŢ`Pûàl¢˜\\ÀœEhŽİgÞē X¼Pk–„|m"],"encodeOffsets":[[118256,37017]]}},{"type":"Feature","id":"420000","properties":{"id":"420000","cp":[113.298572,30.684355],"name":"湖北","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@AB‚"],["@@lskt"],["@@¾«}{ra®pîÃ\\™›{øCŠËyyB±„b\\›ò˜Ý˜jK›‡L ]ĎĽÌ’JyÚCƈćÎT´Å´pb©È‘dFin~BCo°BĎĚømvŒ®E^vǾ½Ĝ²Ro‚bÜeNŽ„^ĺ£R†¬lĶ÷YoĖ¥Ě¾|sOr°jY`~I”¾®I†{GqpCgyl{‡£œÍƒÍyPL“¡ƒ¡¸kW‡xYlÙæŠšŁĢzœ¾žV´W¶ùŸo¾ZHxjwfx„GNÁ•³Xéæl¶‰EièIH‰ u’jÌQ~v|sv¶Ôi|ú¢Fh˜Qsğ¦ƒSiŠBg™ÐE^ÁÐ{–čnOÂȞUÎóĔ†ÊēIJ}Z³½Mŧïeyp·uk³DsѨŸL“¶_œÅuèw»—€¡WqÜ]\\‘Ò§tƗcÕ¸ÕFÏǝĉăxŻČƟO‡ƒKÉġÿ×wg”÷IÅzCg†]m«ªGeçÃTC’«[‰t§{loWeC@ps_Bp‘­r‘„f_``Z|ei¡—oċMqow€¹DƝӛDYpûs•–‹Ykıǃ}s¥ç³[§ŸcYЧHK„«Qy‰]¢“wwö€¸ïx¼ņ¾Xv®ÇÀµRĠЋžHMž±cÏd„ƒǍũȅȷ±DSyúĝ£ŤĀàtÖÿï[îb\\}pĭÉI±Ñy…¿³x¯N‰o‰|¹H™ÏÛm‹júË~Tš•u˜ęjCöAwě¬R’đl¯ Ñb­‰ŇT†Ŀ_[Œ‘IčĄʿnM¦ğ\\É[T·™k¹œ©oĕ@A¾w•ya¥Y\\¥Âaz¯ãÁ¡k¥ne£Ûw†E©Êō¶˓uoj_Uƒ¡cF¹­[Wv“P©w—huÕyBF“ƒ`R‹qJUw\\i¡{jŸŸEPïÿ½fć…QÑÀQ{ž‚°‡fLԁ~wXg—ītêݾ–ĺ‘Hdˆ³fJd]‹HJ²…E€ƒoU¥†HhwQsƐ»Xmg±çve›]Dm͂PˆoCc¾‹_h”–høYrŊU¶eD°Č_N~øĹĚ·`z’]Äþp¼…äÌQŒv\\rCŒé¾TnkžŐڀÜa‡“¼ÝƆ̶Ûo…d…ĔňТJq’Pb ¾|JŒ¾fXŠƐîĨ_Z¯À}úƲ‹N_ĒĊ^„‘ĈaŐyp»CÇĕKŠšñL³ŠġMŒ²wrIÒŭxjb[œžn«øœ˜—æˆàƒ ^²­h¯Ú€ŐªÞ¸€Y²ĒVø}Ā^İ™´‚LŠÚm„¥ÀJÞ{JVŒųÞŃx×sxxƈē ģMř–ÚðòIf–Ċ“Œ\\Ʈ±ŒdʧĘD†vČ_Àæ~DŒċ´A®µ†¨ØLV¦êHÒ¤"]],"encodeOffsets":[[[113712,34000]],[[115612,30507]],[[113649,34054]]]}},{"type":"Feature","id":"430000","properties":{"id":"430000","cp":[111.782279,28.09409],"name":"湖南","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@—n„FTs"],["@@ßÅÆá‰½ÔXr—†CO™“…ËR‘ïÿĩ­TooQyšÓ[‹ŅBE¬–ÎÓXa„į§Ã¸G °ITxp‰úxÚij¥Ïš–̾ŠedžÄ©ĸG…œàGh‚€M¤–Â_U}Ċ}¢pczfŠþg¤€”ÇòAV‘‹M"],["@@©K—ƒA·³CQ±Á«³BUŠƑ¹AŠtćOw™D]ŒJiØSm¯b£‘ylƒ›X…HËѱH•«–‘C^õľA–Å§¤É¥„ïyuǙuA¢^{ÌC´­¦ŷJ£^[†“ª¿‡ĕ~•Ƈ…•N… skóā‡¹¿€ï]ă~÷O§­@—Vm¡‹Qđ¦¢Ĥ{ºjԏŽŒª¥nf´•~ÕoŸž×Ûą‹MąıuZœmZcÒ IJβSÊDŽŶ¨ƚƒ’CÖŎªQؼrŭŽ­«}NÏürʬŒmjr€@ĘrTW ­SsdHzƓ^ÇÂyUi¯DÅYlŹu{hTœ}mĉ–¹¥ě‰Dÿë©ıÓ[Oº£ž“¥ót€ł¹MՄžƪƒ`Pš…Di–ÛUоÅ‌ìˆU’ñB“È£ýhe‰dy¡oċ€`pfmjP~‚kZa…ZsÐd°wj§ƒ@€Ĵ®w~^‚kÀÅKvNmX\\¨a“”сqvíó¿F„¤¡@ũÑVw}S@j}¾«pĂr–ªg àÀ²NJ¶¶Dô…K‚|^ª†Ž°LX¾ŴäPᜣEXd›”^¶›IJÞܓ~‘u¸ǔ˜Ž›MRhsR…e†`ÄofIÔ\\Ø  i”ćymnú¨cj ¢»–GČìƊÿШXeĈ¾Oð Fi ¢|[jVxrIQŒ„_E”zAN¦zLU`œcªx”OTu RLÄ¢dV„i`p˔vŎµªÉžF~ƒØ€d¢ºgİàw¸Áb[¦Zb¦–z½xBĖ@ªpº›šlS¸Ö\\Ĕ[N¥ˀmĎă’J\\‹ŀ`€…ňSڊĖÁĐiO“Ĝ«BxDõĚiv—ž–S™Ì}iùŒžÜnšÐºGŠ{Šp°M´w†ÀÒzJ²ò¨ oTçüöoÛÿñŽőФ‚ùTz²CȆȸǎۃƑÐc°dPÎŸğ˶[Ƚu¯½WM¡­Éž“’B·rížnZŸÒ `‡¨GA¾\\pē˜XhÆRC­üWGġu…T靧Ŏѝ©ò³I±³}_‘‹EÃħg®ęisÁPDmÅ{‰b[Rşs·€kPŸŽƥƒóRo”O‹ŸVŸ~]{g\\“êYƪ¦kÝbiċƵŠGZ»Ěõ…ó·³vŝž£ø@pyö_‹ëŽIkѵ‡bcѧy…×dY؎ªiþž¨ƒ[]f]Ņ©C}ÁN‡»hĻħƏ’ĩ"]],"encodeOffsets":[[[115640,30489]],[[112543,27312]],[[116690,26230]]]}},{"type":"Feature","id":"440000","properties":{"id":"440000","cp":[113.280637,23.125178],"name":"广东","childNum":24},"geometry":{"type":"MultiPolygon","coordinates":[["@@QdˆAua"],["@@ƒlxDLo"],["@@sbhNLo"],["@@Ă āŸ"],["@@WltO[["],["@@Krœ]S"],["@@e„„I]y"],["@@I|„Mym"],["@@ƒÛ³LSŒž¼Y"],["@@nvºB–ëui©`¾"],["@@zdšÛ›Jw®"],["@@†°…¯"],["@@a yAª¸ËJIx،@€ĀHAmßV¡o•fu•o"],["@@šs‰ŗÃÔėAƁ›ZšÄ ~°ČP‚‹äh"],["@@‹¶Ý’Ì‚vmĞh­ı‡Q"],["@@HœŠdSjĒ¢D}war…“u«ZqadYM"],["@@elŒ\\LqqU"],["@@~rMo\\"],["@@f„^ƒC"],["@@øPªoj÷ÍÝħXČx”°Q¨ıXNv"],["@@gÇƳˆŽˆ”oˆŠˆ[~tly"],["@@E–ÆC¿‘"],["@@OŽP"],["@@w‹†đóg‰™ĝ—[³‹¡VÙæÅöM̳¹pÁaËýý©D©Ü“JŹƕģGą¤{Ùū…ǘO²«BƱéA—Ò‰ĥ‡¡«BhlmtÃPµyU¯uc“d·w_bŝcīímGOŽ|KP’ȏ‡ŹãŝIŕŭŕ@Óoo¿ē‹±ß}Ž…ŭ‚ŸIJWÈCőâUâǙI›ğʼn©I›ijEׅÁ”³Aó›wXJþ±ÌŒÜӔĨ£L]ĈÙƺZǾĆĖMĸĤfŒÎĵl•ŨnȈ‘ĐtF”Š–FĤ–‚êk¶œ^k°f¶gŠŽœ}®Fa˜f`vXŲxl˜„¦–ÔÁ²¬ÐŸ¦pqÊ̲ˆi€XŸØRDÎ}†Ä@ZĠ’s„x®AR~®ETtĄZ†–ƈfŠŠHâÒÐA†µ\\S¸„^wĖkRzŠalŽŜ|E¨ÈNĀňZTŒ’pBh£\\ŒĎƀuXĖtKL–¶G|Ž»ĺEļĞ~ÜĢÛĊrˆO˜Ùîvd]nˆ¬VœÊĜ°R֟pM††–‚ƂªFbwžEÀˆ˜©Œž\\…¤]ŸI®¥D³|ˎ]CöAŤ¦…æ’´¥¸Lv¼€•¢ĽBaô–F~—š®²GÌҐEY„„œzk¤’°ahlV՞I^‹šCxĈPŽsB‰ƒºV‰¸@¾ªR²ĨN]´_eavSi‡vc•}p}Đ¼ƌkJœÚe thœ†_¸ ºx±ò_xN›Ë‹²‘@ƒă¡ßH©Ùñ}wkNÕ¹ÇO½¿£ĕ]ly_WìIžÇª`ŠuTÅxYĒÖ¼k֞’µ‚MžjJÚwn\\h‘œĒv]îh|’È›Ƅøègž¸Ķß ĉĈWb¹ƀdéƌNTtP[ŠöSvrCZžžaGuœbo´ŖÒÇА~¡zCI…özx¢„Pn‹•‰Èñ @ŒĥÒ¦†]ƞŠV}³ăĔñiiÄÓVépKG½Ä‘ÓávYo–C·sit‹iaÀy„ŧΡÈYDÑům}‰ý|m[węõĉZÅxUO}÷N¹³ĉo_qtă“qwµŁYلǝŕ¹tïÛUïmRCº…ˆĭ|µ›ÕÊK™½R‘ē ó]‘–GªęAx–»HO£|ām‡¡diď×YïYWªʼnOeÚtĐ«zđ¹T…ā‡úE™á²\\‹ķÍ}jYàÙÆſ¿Çdğ·ùTßÇţʄ¡XgWÀLJğ·¿ÃˆOj YÇ÷Qě‹i"]],"encodeOffsets":[[[117381,22988]],[[116552,22934]],[[116790,22617]],[[116973,22545]],[[116444,22536]],[[116931,22515]],[[116496,22490]],[[116453,22449]],[[113301,21439]],[[118726,21604]],[[118709,21486]],[[113210,20816]],[[115482,22082]],[[113171,21585]],[[113199,21590]],[[115232,22102]],[[115739,22373]],[[115134,22184]],[[113056,21175]],[[119573,21271]],[[119957,24020]],[[115859,22356]],[[116561,22649]],[[116285,22746]]]}},{"type":"Feature","id":"450000","properties":{"id":"450000","cp":[108.320004,22.82402],"name":"广西","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@H– TQ§•A"],["@@ĨʪƒLƒƊDÎĹĐCǦė¸zÚGn£¾›rªŀÜt¬@֛ڈSx~øOŒ˜ŶÐÂæȠ\\„ÈÜObĖw^oބLf¬°bI lTØB̈F£Ć¹gñĤaY“t¿¤VSñœK¸¤nM†¼‚JE±„½¸šŠño‹ÜCƆæĪ^ŠĚQÖ¦^‡ˆˆf´Q†üÜʝz¯šlzUĺš@쇀p¶n]sxtx¶@„~ÒĂJb©gk‚{°‚~c°`ԙ¬rV\\“la¼¤ôá`¯¹LC†ÆbŒxEræO‚v[H­˜„[~|aB£ÖsºdAĐzNÂðsŽÞƔ…Ĥªbƒ–ab`ho¡³F«èVloޤ™ÔRzpp®SŽĪº¨ÖƒºN…ij„d`’a”¦¤F³ºDÎńĀìŠCžĜº¦Ċ•~nS›|gźvZkCÆj°zVÈÁƔ]LÊFZg…čP­kini«‹qǀcz͔Y®¬Ů»qR×ō©DՄ‘§ƙǃŵTÉĩ±ŸıdÑnYY›IJvNĆÌØÜ Öp–}e³¦m‹©iÓ|¹Ÿħņ›|ª¦QF¢Â¬ʖovg¿em‡^ucà÷gՎuŒíÙćĝ}FϼĹ{µHK•sLSđƃr‹č¤[Ag‘oS‹ŇYMÿ§Ç{Fśbky‰lQxĕƒ]T·¶[B…ÑÏGáşşƇe€…•ăYSs­FQ}­Bƒw‘tYğÃ@~…C̀Q ×W‡j˱rÉ¥oÏ ±«ÓÂ¥•ƒ€k—ŽwWűŒmcih³K›~‰µh¯e]lµ›él•E쉕E“ďs‡’mǖŧē`ãògK_ÛsUʝ“ćğ¶hŒöŒO¤Ǜn³Žc‘`¡y‹¦C‘ez€YŠwa™–‘[ďĵűMę§]X˜Î_‚훘Û]é’ÛUćİÕBƣ±…dƒy¹T^džûÅÑŦ·‡PĻþÙ`K€¦˜…¢ÍeœĥR¿Œ³£[~Œäu¼dl‰t‚†W¸oRM¢ď\\zœ}Æzdvň–{ÎXF¶°Â_„ÒÂÏL©Ö•TmuŸ¼ãl‰›īkiqéfA„·Êµ\\őDc¥ÝF“y›Ôć˜c€űH_hL܋êĺШc}rn`½„Ì@¸¶ªVLŒŠhŒ‹\\•Ţĺk~ŽĠið°|gŒtTĭĸ^x‘vK˜VGréAé‘bUu›MJ‰VÃO¡…qĂXËS‰ģãlýàŸ_ju‡YÛÒB†œG^˜é֊¶§ŽƒEG”ÅzěƒƯ¤Ek‡N[kdåucé¬dnYpAyČ{`]þ¯T’bÜÈk‚¡Ġ•vŒàh„ÂƄ¢Jî¶²"]],"encodeOffsets":[[[111707,21520]],[[107619,25527]]]}},{"type":"Feature","id":"460000","properties":{"id":"460000","cp":[109.83119,19.031971],"name":"海南","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@š¦Ŝil¢”XƦ‘ƞò–ïè§ŞCêɕrŧůÇąĻõ™·ĉ³œ̅kÇm@ċȧƒŧĥ‰Ľʉ­ƅſ“ȓÒ˦ŝE}ºƑ[ÍĜȋ gÎfǐÏĤ¨êƺ\\Ɔ¸ĠĎvʄȀœÐ¾jNðĀÒRŒšZdž™zÐŘΰH¨Ƣb²_Ġ "],"encodeOffsets":[[112750,20508]]}},{"type":"Feature","id":"510000","properties":{"id":"510000","cp":[104.065735,30.659462],"name":"四川","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@LqKr"],["@@Š[ĻéV£ž_ţġñpG •réÏ·~ąSfy×͂·ºſƽiÍıƣıĻmHH}siaX@iǰÁÃ×t«ƒ­Tƒ¤J–JJŒyJ•ÈŠ`Ohߦ¡uËhIyCjmÿw…ZG……Ti‹SˆsO‰žB²ŸfNmsPaˆ{M{ŠõE‘^Hj}gYpaeuž¯‘oáwHjÁ½M¡pM“–uå‡mni{fk”\\oƒÎqCw†EZ¼K›ĝŠƒAy{m÷L‡wO×SimRI¯rK™õBS«sFe‡]fµ¢óY_ÆPRcue°Cbo׌bd£ŌIHgtrnyPt¦foaXďx›lBowz‹_{ÊéWiêE„GhܸºuFĈIxf®Ž•Y½ĀǙ]¤EyŸF²ċ’w¸¿@g¢§RGv»–áŸW`ÃĵJwi]t¥wO­½a[׈]`Ãi­üL€¦LabbTÀå’c}Íh™Æhˆ‹®BH€î|Ék­¤S†y£„ia©taį·Ɖ`ō¥Uh“O…ƒĝLk}©Fos‰´›Jm„µlŁu—…ø–nÑJWΪ–YÀïAetTžŅ‚ӍG™Ë«bo‰{ıwodƟ½ƒžOġܑµxàNÖ¾P²§HKv¾–]|•B‡ÆåoZ`¡Ø`ÀmºĠ~ÌЧnDž¿¤]wğ@sƒ‰rğu‰~‘Io”[é±¹ ¿žſđӉ@q‹gˆ¹zƱřaí°KtǤV»Ã[ĩǭƑ^ÇÓ@ỗs›Zϕ‹œÅĭ€Ƌ•ěpwDóÖሯneQˌq·•GCœýS]xŸ·ý‹q³•O՜Œ¶Qzßti{ř‰áÍÇWŝŭñzÇW‹pç¿JŒ™‚Xœĩè½cŒF–ÂLiVjx}\\N†ŇĖ¥Ge–“JA¼ÄHfÈu~¸Æ«dE³ÉMA|b˜Ò…˜ćhG¬CM‚õŠ„ƤąAvƒüV€éŀ‰_V̳ĐwQj´·ZeÈÁ¨X´Æ¡Qu·»Ÿ“˜ÕZ³ġqDo‰y`L¬gdp°şŠp¦ėìÅĮZްIä”h‚‘ˆzŠĵœf²å ›ĚрKp‹IN|‹„Ñz]ń……·FU×é»R³™MƒÉ»GM«€ki€™ér™}Ã`¹ăÞmȝnÁîRǀ³ĜoİzŔwǶVÚ£À]ɜ»ĆlƂ²Ġ…þTº·àUȞÏʦ¶†I’«dĽĢdĬ¿–»Ĕ׊h\\c¬†ä²GêëĤł¥ÀǿżÃÆMº}BÕĢyFVvw–ˆxBèĻĒ©Ĉ“tCĢɽŠȣ¦āæ·HĽî“ôNԓ~^¤Ɗœu„œ^s¼{TA¼ø°¢İªDè¾Ň¶ÝJ‘®Z´ğ~Sn|ªWÚ©òzPOȸ‚bð¢|‹øĞŠŒœŒQìÛÐ@Ğ™ǎRS¤Á§d…i“´ezÝúØã]Hq„kIŸþËQǦÃsǤ[E¬ÉŪÍxXƒ·ÖƁİlƞ¹ª¹|XÊwn‘ÆƄmÀêErĒtD®ċæcQƒ”E®³^ĭ¥©l}äQto˜ŖÜqƎkµ–„ªÔĻĴ¡@Ċ°B²Èw^^RsºT£ڿœQP‘JvÄz„^Đ¹Æ¯fLà´GC²‘dt˜­ĀRt¼¤ĦOðğfÔðDŨŁĞƘïžPȆ®âbMüÀXZ ¸£@Ś›»»QÉ­™]d“sÖ×_͖_ÌêŮPrĔĐÕGĂeZÜîĘqBhtO ¤tE[h|Y‹Ô‚ZśÎs´xº±UŒ’ñˆt|O’ĩĠºNbgþŠJy^dÂY Į„]Řz¦gC‚³€R`Šz’¢AjŒ¸CL„¤RÆ»@­Ŏk\\Ç´£YW}z@Z}‰Ã¶“oû¶]´^N‡Ò}èN‚ª–P˜Íy¹`S°´†ATe€VamdUĐwʄvĮÕ\\ƒu‹Æŗ¨Yp¹àZÂm™Wh{á„}WØǍ•Éüw™ga§áCNęÎ[ĀÕĪgÖɪX˜øx¬½Ů¦¦[€—„NΆL€ÜUÖ´òrÙŠxR^–†J˜k„ijnDX{Uƒ~ET{ļº¦PZc”jF²Ė@Žp˜g€ˆ¨“B{ƒu¨ŦyhoÚD®¯¢˜ WòàFΤ¨GDäz¦kŮPœġq˚¥À]€Ÿ˜eŽâÚ´ªKxī„Pˆ—Ö|æ[xäJÞĥ‚s’NÖ½ž€I†¬nĨY´®Ð—ƐŠ€mD™ŝuäđđEb…e’e_™v¡}ìęNJē}q”É埁T¯µRs¡M@}ůa†a­¯wvƉåZwž\\Z{åû^›"]],"encodeOffsets":[[[108815,30935]],[[110617,31811]]]}},{"type":"Feature","id":"520000","properties":{"id":"520000","cp":[106.713478,26.578343],"name":"贵州","childNum":3},"geometry":{"type":"MultiPolygon","coordinates":[["@@†G\\†lY£‘in"],["@@q‚|ˆ‚mc¯tχVSÎ"],["@@hÑ£Is‡NgßH†›HªķÃh_¹ƒ¡ĝħń¦uيùŽgS¯JHŸ|sÝÅtÁïyMDč»eÕtA¤{b\\}—ƒG®u\\åPFq‹wÅaD…žK°ºâ_£ùbµ”mÁ‹ÛœĹM[q|hlaªāI}тƒµ@swtwm^oµˆD鼊yV™ky°ÉžûÛR…³‚‡eˆ‡¥]RՋěħ[ƅåÛDpŒ”J„iV™™‰ÂF²I…»mN·£›LbÒYb—WsÀbŽ™pki™TZĄă¶HŒq`……ĥ_JŸ¯ae«ƒKpÝx]aĕÛPƒÇȟ[ÁåŵÏő—÷Pw}‡TœÙ@Õs«ĿÛq©½œm¤ÙH·yǥĘĉBµĨÕnđ]K„©„œá‹ŸG纍§Õßg‡ǗĦTèƤƺ{¶ÉHÎd¾ŚÊ·OÐjXWrãLyzÉAL¾ę¢bĶėy_qMĔąro¼hĊžw¶øV¤w”²Ĉ]ʚKx|`ź¦ÂÈdr„cȁbe¸›`I¼čTF´¼Óýȃr¹ÍJ©k_șl³´_pН`oÒh޶pa‚^ÓĔ}D»^Xyœ`d˜[Kv…JPhèhCrĂĚÂ^Êƌ wˆZL­Ġ£šÁbrzOIl’MM”ĪŐžËr×ÎeŦŽtw|Œ¢mKjSǘňĂStÎŦEtqFT†¾†E쬬ôxÌO¢Ÿ KгŀºäY†„”PVgŎ¦Ŋm޼VZwVlŒ„z¤…ž£Tl®ctĽÚó{G­A‡ŒÇgeš~Αd¿æaSba¥KKûj®_ć^\\ؾbP®¦x^sxjĶI_Ä X‚⼕Hu¨Qh¡À@Ëô}ޱžGNìĎlT¸ˆ…`V~R°tbÕĊ`¸úÛtπFDu€[ƒMfqGH·¥yA‰ztMFe|R‚_Gk†ChZeÚ°to˜v`x‹b„ŒDnÐ{E}šZ˜è€x—†NEފREn˜[Pv@{~rĆAB§‚EO¿|UZ~ì„Uf¨J²ĂÝÆ€‚sª–B`„s¶œfvö¦ŠÕ~dÔq¨¸º»uù[[§´sb¤¢zþFœ¢Æ…Àhˆ™ÂˆW\\ıŽËI݊o±ĭŠ£þˆÊs}¡R]ŒěƒD‚g´VG¢‚j±®è†ºÃmpU[Á›‘Œëº°r›ÜbNu¸}Žº¼‡`ni”ºÔXĄ¤¼Ôdaµ€Á_À…†ftQQgœR—‘·Ǔ’v”}Ýלĵ]µœ“Wc¤F²›OĩųãW½¯K‚©…]€{†LóµCIµ±Mß¿hŸ•©āq¬o‚½ž~@i~TUxŪÒ¢@ƒ£ÀEîôruń‚”“‚b[§nWuMÆLl¿]x}ij­€½"]],"encodeOffsets":[[[112158,27383]],[[112105,27474]],[[112095,27476]]]}},{"type":"Feature","id":"530000","properties":{"id":"530000","cp":[101.512251,24.740609],"name":"云南","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@[„ùx½}ÑRH‘YīĺûsÍn‘iEoã½Ya²ė{c¬ĝg•ĂsA•ØÅwď‚õzFjw}—«Dx¿}UũlŸê™@•HÅ­F‰¨ÇoJ´Ónũuą¡Ã¢pÒŌ“Ø TF²‚xa²ËX€‚cʋlHîAßËŁkŻƑŷÉ©h™W­æßU‡“Ës¡¦}•teèÆ¶StǀÇ}Fd£j‹ĈZĆÆ‹¤T‚č\\Dƒ}O÷š£Uˆ§~ŃG™‚åŃDĝ¸œTsd¶¶Bªš¤u¢ŌĎo~t¾ÍŶÒtD¦Ú„iôö‰€z›ØX²ghįh½Û±¯€ÿm·zR¦Ɵ`ªŊÃh¢rOԍ´£Ym¼èêf¯ŪĽn„†cÚbŒw\\zlvWžªâˆ ¦g–mĿBş£¢ƹřbĥkǫßeeZkÙIKueT»sVesb‘aĕ  ¶®dNœĄÄpªyސ¼—„³BE˜®l‡ŽGœŭCœǶwêżĔÂe„pÍÀQƞpC„–¼ŲÈ­AÎô¶R„ä’Q^Øu¬°š_Èôc´¹ò¨P΢hlϦ´Ħ“Æ´sâDŽŲPnÊD^¯°’Upv†}®BP̪–jǬx–Söwlfòªv€qĸ|`H€­viļ€ndĜ­Ćhň•‚em·FyށqóžSᝑ³X_ĞçêtryvL¤§z„¦c¦¥jnŞk˜ˆlD¤øz½ĜàžĂŧMÅ|áƆàÊcðÂF܎‚áŢ¥\\\\º™İøÒÐJĴ‡„îD¦zK²ǏÎEh~’CD­hMn^ÌöÄ©ČZÀžaü„fɭyœpį´ěFűk]Ôě¢qlÅĆÙa¶~Äqššê€ljN¬¼H„ÊšNQ´ê¼VظE††^ŃÒyŒƒM{ŒJLoÒœęæŸe±Ķ›y‰’‡gã“¯JYÆĭĘëo¥Š‰o¯hcK«z_pŠrC´ĢÖY”—¼ v¸¢RŽÅW³Â§fǸYi³xR´ďUˊ`êĿU„û€uĆBƒƣö‰N€DH«Ĉg†——Ñ‚aB{ÊNF´¬c·Åv}eÇÃGB»”If•¦HňĕM…~[iwjUÁKE•Ž‹¾dĪçW›šI‹èÀŒoÈXòyŞŮÈXâÎŚŠj|àsRy‹µÖ›–Pr´þŒ ¸^wþTDŔ–Hr¸‹žRÌmf‡żÕâCôox–ĜƌÆĮŒ›Ð–œY˜tâŦÔ@]ÈǮƒ\\μģUsȯLbîƲŚºyh‡rŒŠ@ĒԝƀŸÀ²º\\êp“’JŠ}ĠvŠqt„Ġ@^xÀ£È†¨mËÏğ}n¹_¿¢×Y_æpˆÅ–A^{½•Lu¨GO±Õ½ßM¶w’ÁĢۂP‚›Ƣ¼pcIJxŠ|ap̬HšÐŒŊSfsðBZ¿©“XÏÒK•k†÷Eû¿‰S…rEFsÕūk”óVǥʼniTL‚¡n{‹uxţÏh™ôŝ¬ğōN“‘NJkyPaq™Âğ¤K®‡YŸxÉƋÁ]āęDqçgOg†ILu—\\_gz—]W¼ž~CÔē]bµogpў_oď`´³Țkl`IªºÎȄqÔþž»E³ĎSJ»œ_f·‚adÇqƒÇc¥Á_Źw{™L^ɱćx“U£µ÷xgĉp»ĆqNē`rĘzaĵĚ¡K½ÊBzyäKXqiWPÏɸ½řÍcÊG|µƕƣG˛÷Ÿk°_^ý|_zċBZocmø¯hhcæ\\lˆMFlư£Ĝ„ÆyH“„F¨‰µêÕ]—›HA…àӄ^it `þßäkŠĤÎT~Wlÿ¨„ÔPzUC–NVv [jâôDôď[}ž‰z¿–msSh‹¯{jïğl}šĹ[–őŒ‰gK‹©U·µË@¾ƒm_~q¡f¹…ÅË^»‘f³ø}Q•„¡Ö˳gͱ^ǁ…\\ëÃA_—¿bW›Ï[¶ƛ鏝£F{īZgm@|kHǭƁć¦UĔťƒ×ë}ǝƒeďºȡȘÏíBə£āĘPªij¶“ʼnÿ‡y©n‰ď£G¹¡I›Š±LÉĺÑdĉ܇W¥˜‰}g˜Á†{aqÃ¥aŠıęÏZ—ï`"],"encodeOffsets":[[104636,22969]]}},{"type":"Feature","id":"540000","properties":{"id":"540000","cp":[89.132212,30.860361],"name":"西藏","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@hžľxŽŖ‰xƒÒVކºÅâAĪÝȆµę¯Ňa±r_w~uSÕň‘qOj]ɄQ…£Z……UDûoY’»©M[‹L¼qãË{V͕çWViŽ]ë©Ä÷àyƛh›ÚU°ŒŒa”d„cQƒ~Mx¥™cc¡ÙaSyF—ցk­ŒuRýq¿Ôµ•QĽ³aG{¿FµëªéĜÿª@¬·–K‰·àariĕĀ«V»Ŷ™Ĵū˜gèLǴŇƶaf‹tŒèBŚ£^Šâ†ǐÝ®–šM¦ÁǞÿ¬LhŸŽJ¾óƾƺcxw‹f]Y…´ƒ¦|œQLn°aœdĊ…œ\\¨o’œǀÍŎœ´ĩĀd`tÊQŞŕ|‚¨C^©œĈ¦„¦ÎJĊ{ŽëĎjª²rЉšl`¼Ą[t|¦St辉PŒÜK¸€d˜Ƅı]s¤—î_v¹ÎVòŦj˜£Əsc—¬_Ğ´|٘¦Avަw`ăaÝaa­¢e¤ı²©ªSªšÈMĄwžÉØŔì@T‘¤—Ę™\\õª@”þo´­xA s”ÂtŎKzó´ÇĊµ¢rž^nĊ­Æ¬×üGž¢‚³ {âĊ]š™G‚~bÀgVjzlhǶf€žOšfdЉªB]pj„•TO–tĊ‚n¤}®¦ƒČ¥d¢¼»ddš”Y¼Žt—¢eȤJ¤}Ǿ¡°§¤AГlc@ĝ”sªćļđAç‡wx•UuzEÖġ~AN¹ÄÅȀݦ¿ģŁéì±H…ãd«g[؉¼ēÀ•cīľġ¬cJ‘µ…ÐʥVȝ¸ßS¹†ý±ğkƁ¼ą^ɛ¤Ûÿ‰b[}¬ōõÃ]ËNm®g@•Bg}ÍF±ǐyL¥íCˆƒIij€Ï÷њį[¹¦[⚍EÛïÁÉdƅß{âNÆāŨߝ¾ě÷yC£‡k­´ÓH@¹†TZ¥¢įƒ·ÌAЧ®—Zc…v½ŸZ­¹|ŕWZqgW“|ieZÅYVӁqdq•bc²R@†c‡¥Rã»Ge†ŸeƃīQ•}J[ғK…¬Ə|o’ėjġĠÑN¡ð¯EBčnwôɍėªƒ²•CλŹġǝʅįĭạ̃ūȹ]ΓͧgšsgȽóϧµǛ†ęgſ¶ҍć`ĘąŌJޚä¤rÅň¥ÖÁUětęuůÞiĊÄÀ\\Æs¦ÓRb|Â^řÌkÄŷ¶½÷‡f±iMݑ›‰@ĥ°G¬ÃM¥n£Øą‚ğ¯ß”§aëbéüÑOčœk£{\\‘eµª×M‘šÉfm«Ƒ{Å׃Gŏǩãy³©WÑăû‚··‘Q—òı}¯ã‰I•éÕÂZ¨īès¶ZÈsŽæĔTŘvŽgÌsN@îá¾ó@‰˜ÙwU±ÉT廣TđŸWxq¹Zo‘b‹s[׌¯cĩv‡Œėŧ³BM|¹k‰ªħ—¥TzNYnݍßpęrñĠĉRS~½ŠěVVе‚õ‡«ŒM££µB•ĉ¥áºae~³AuĐh`Ü³ç@BۘïĿa©|z²Ý¼D”£à貋ŸƒIƒû›I ā€óK¥}rÝ_Á´éMaň¨€~ªSĈ½Ž½KÙóĿeƃÆBŽ·¬ën×W|Uº}LJrƳ˜lŒµ`bÔ`QˆˆÐÓ@s¬ñIŒÍ@ûws¡åQÑßÁ`ŋĴ{Ī“T•ÚÅTSij‚‹Yo|Ç[ǾµMW¢ĭiÕØ¿@˜šMh…pÕ]j†éò¿OƇĆƇp€êĉâlØw–ěsˆǩ‚ĵ¸c…bU¹ř¨WavquSMzeo_^gsÏ·¥Ó@~¯¿RiīB™Š\\”qTGªÇĜçPoŠÿfñòą¦óQīÈáP•œābß{ƒZŗĸIæÅ„hnszÁCËìñšÏ·ąĚÝUm®ó­L·ăU›Èíoù´Êj°ŁŤ_uµ^‘°Œìǖ@tĶĒ¡Æ‡M³Ģ«˜İĨÅ®ğ†RŽāð“ggheÆ¢z‚Ê©Ô\\°ÝĎz~ź¤Pn–MĪÖB£Ÿk™n鄧żćŠ˜ĆK„ǰ¼L¶è‰âz¨u¦¥LDĘz¬ýÎmĘd¾ß”Fz“hg²™Fy¦ĝ¤ċņbΛ@y‚Ąæm°NĮZRÖíŽJ²öLĸÒ¨Y®ƌÐV‰à˜tt_ڀÂyĠzž]Ţh€zĎ{†ĢX”ˆc|šÐqŽšfO¢¤ög‚ÌHNŽ„PKŖœŽ˜Uú´xx[xˆvĐCûŠìÖT¬¸^}Ìsòd´_އKgžLĴ…ÀBon|H@–Êx˜—¦BpŰˆŌ¿fµƌA¾zLjRxжF”œkĄźRzŀˆ~¶[”´Hnª–VƞuĒ­È¨ƎcƽÌm¸ÁÈM¦x͊ëÀxdžB’šú^´W†£–d„kɾĬpœw‚˂ØɦļĬIŚœÊ•n›Ŕa¸™~J°î”lɌxĤÊÈðhÌ®‚g˜T´øŽàCˆŽÀ^ªerrƘdž¢İP|Ė ŸWœªĦ^¶´ÂL„aT±üWƜ˜ǀRšŶUńšĖ[QhlLüA†‹Ü\\†qR›Ą©"],"encodeOffsets":[[90849,37210]]}},{"type":"Feature","id":"610000","properties":{"id":"610000","cp":[108.948024,34.263161],"name":"陕西","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@˜p¢—ȮµšûG™Ħ}Ħšðǚ¶òƄ€jɂz°{ºØkÈęâ¦jª‚Bg‚\\œċ°s¬Ž’]jžú ‚E”Ȍdž¬s„t‡”RˆÆdĠݎwܔ¸ôW¾ƮłÒ_{’Ìšû¼„jº¹¢GǪÒ¯ĘƒZ`ºŊƒecņąš~BÂgzpâēòYǠȰÌTΨÂWœ|fcŸă§uF—Œ@NŸ¢XLƒŠRMº[ğȣſï|¥J™kc`sʼnǷ’Y¹‹W@µ÷K…ãï³ÛIcñ·VȋڍÒķø©—þ¥ƒy‚ÓŸğęmWµÎumZyOŅƟĥÓ~sÑL¤µaŅY¦ocyZ{‰y c]{ŒTa©ƒ`U_Ěē£ωÊƍKù’K¶ȱÝƷ§{û»ÅÁȹÍéuij|¹cÑd‘ŠìUYƒŽO‘uF–ÕÈYvÁCqӃT•Ǣí§·S¹NgŠV¬ë÷Át‡°Dد’C´ʼnƒópģ}„ċcE˅FŸŸéGU¥×K…§­¶³B‹Č}C¿åċ`wġB·¤őcƭ²ő[Å^axwQO…ÿEËߌ•ĤNĔŸwƇˆÄŠńwĪ­Šo[„_KÓª³“ÙnK‰Çƒěœÿ]ď€ă_d©·©Ýŏ°Ù®g]±„Ÿ‡ß˜å›—¬÷m\\›iaǑkěX{¢|ZKlçhLt€Ňîŵ€œè[€É@ƉĄEœ‡tƇÏ˜³­ħZ«mJ…›×¾‘MtÝĦ£IwÄå\\Õ{‡˜ƒOwĬ©LÙ³ÙgBƕŀr̛ĢŭO¥lãyC§HÍ£ßEñŸX¡—­°ÙCgpťz‘ˆb`wI„vA|§”‡—hoĕ@E±“iYd¥OϹS|}F@¾oAO²{tfžÜ—¢Fǂ҈W²°BĤh^Wx{@„¬‚­F¸¡„ķn£P|ŸªĴ@^ĠĈæb–Ôc¶l˜Yi…–^Mi˜cϰÂ[ä€vï¶gv@À“Ĭ·lJ¸sn|¼u~a]’ÆÈtŌºJp’ƒþ£KKf~ЦUbyäIšĺãn‡Ô¿^­žŵMT–hĠܤko¼Ŏìąǜh`[tŒRd²IJ_œXPrɲ‰l‘‚XžiL§àƒ–¹ŽH˜°Ȧqº®QC—bA†„ŌJ¸ĕÚ³ĺ§ `d¨YjžiZvRĺ±öVKkjGȊĐePОZmļKÀ€‚[ŠŽ`ösìh†ïÎoĬdtKÞ{¬èÒÒBŒÔpIJÇĬJŊ¦±J«ˆY§‹@·pH€µàåVKe›pW†ftsAÅqC·¬ko«pHÆuK@oŸHĆۄķhx“e‘n›S³àǍrqƶRbzy€¸ËАl›¼EºpĤ¼Œx¼½~Ğ’”à@†ÚüdK^ˆmÌSj"],"encodeOffsets":[[110234,38774]]}},{"type":"Feature","id":"620000","properties":{"id":"620000","cp":[103.823557,36.058039],"name":"甘肃","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@VuUv"],["@@ũ‹EĠtt~nkh`Q‰¦ÅÄÜdw˜Ab×ĠąJˆ¤DüègĺqBqœj°lI¡ĨÒ¤úSHbš‡ŠjΑBаaZˆ¢KJŽ’O[|A£žDx}Nì•HUnrk„ kp€¼Y kMJn[aG‚áÚÏ[½rc†}aQxOgsPMnUs‡nc‹Z…ž–sKúvA›t„Þġ’£®ĀYKdnFwš¢JE°”Latf`¼h¬we|€Æ‡šbj}GA€·~WŽ”—`†¢MC¤tL©IJ°qdf”O‚“bÞĬ¹ttu`^ZúE`Œ[@„Æsîz®¡’C„ƳƜG²“R‘¢R’m”fŽwĸg܃‚ą G@pzJM½mŠhVy¸uÈÔO±¨{LfæU¶ßGĂq\\ª¬‡²I‚¥IʼnÈīoı‹ÓÑAçÑ|«LÝcspīðÍg…të_õ‰\\ĉñLYnĝg’ŸRǡÁiHLlõUĹ²uQjYi§Z_c¨Ÿ´ĹĖÙ·ŋI…ƒaBD˜­R¹ȥr—¯G•ºß„K¨jWk’ɱŠOq›Wij\\a­‹Q\\sg_ĆǛōëp»£lğۀgS•ŶN®À]ˆÓäm™ĹãJaz¥V}‰Le¤L„ýo‘¹IsŋÅÇ^‘Žbz…³tmEÁ´aйcčecÇN•ĊãÁ\\蝗dNj•]j†—ZµkÓda•ćå]ğij@ ©O{¤ĸm¢ƒE·®ƒ«|@Xwg]A챝‡XǁÑdzªc›wQÚŝñsÕ³ÛV_ýƒ˜¥\\ů¥©¾÷w—Ž©WÕÊĩhÿÖÁRo¸V¬âDb¨šhûx–Ê×nj~Zâƒg|šXÁnßYoº§ZÅŘvŒ[„ĭÖʃuďxcVbnUSf…B¯³_Tzº—ΕO©çMÑ~Mˆ³]µ^püµ”ŠÄY~y@X~¤Z³€[Èōl@®Å¼£QKƒ·Di‹¡By‘ÿ‰Q_´D¥hŗyƒ^ŸĭÁZ]cIzý‰ah¹MĪğP‘s{ò‡‹‘²Vw¹t³Ŝˁ[ŽÑ}X\\gsFŸ£sPAgěp×ëfYHāďÖqēŭOÏë“dLü•\\iŒ”t^c®šRʺ¶—¢H°mˆ‘rYŸ£BŸ¹čIoľu¶uI]vģSQ{ƒUŻ”Å}QÂ|̋°ƅ¤ĩŪU ęĄžÌZҞ\\v˜²PĔ»ƢNHƒĂyAmƂwVmž`”]ȏb•”H`‰Ì¢²ILvĜ—H®¤Dlt_„¢JJÄämèÔDëþgºƫ™”aʎÌrêYi~ ÎݤNpÀA¾Ĕ¼b…ð÷’Žˆ‡®‚”üs”zMzÖĖQdȨý†v§Tè|ªH’þa¸|šÐ ƒwKĢx¦ivr^ÿ ¸l öæfƟĴ·PJv}n\\h¹¶v†·À|\\ƁĚN´Ĝ€çèÁz]ġ¤²¨QÒŨTIl‡ªťØ}¼˗ƦvÄùØE‹’«Fï˛Iq”ōŒTvāÜŏ‚íÛߜÛV—j³âwGăÂíNOŠˆŠPìyV³ʼnĖýZso§HіiYw[߆\\X¦¥c]ÔƩÜ·«j‡ÐqvÁ¦m^ċ±R™¦΋ƈťĚgÀ»IïĨʗƮްƝ˜ĻþÍAƉſ±tÍEÕÞāNU͗¡\\ſčåÒʻĘm ƭÌŹöʥ’ëQ¤µ­ÇcƕªoIýˆ‰Iɐ_mkl³ă‰Ɠ¦j—¡Yz•Ňi–}Msßõ–īʋ —}ƒÁVmŸ_[n}eı­Uĥ¼‘ª•I{ΧDӜƻėoj‘qYhĹT©oūĶ£]ďxĩ‹ǑMĝ‰q`B´ƃ˺Ч—ç~™²ņj@”¥@đ´ί}ĥtPńǾV¬ufӃÉC‹tÓ̻‰…¹£G³€]ƖƾŎĪŪĘ̖¨ʈĢƂlɘ۪üºňUðǜȢƢż̌ȦǼ‚ĤŊɲĖ­Kq´ï¦—ºĒDzņɾªǀÞĈĂD†½ĄĎÌŗĞrôñnŽœN¼â¾ʄľԆ|DŽŽ֦ज़ȗlj̘̭ɺƅêgV̍ʆĠ·ÌĊv|ýĖÕWĊǎÞ´õ¼cÒÒBĢ͢UĜð͒s¨ňƃLĉÕÝ@ɛƯ÷¿Ľ­ĹeȏijëCȚDŲyê×Ŗyò¯ļcÂßY…tÁƤyAã˾J@ǝrý‹‰@¤…rz¸oP¹ɐÚyᐇHŸĀ[Jw…cVeȴϜ»ÈŽĖ}ƒŰŐèȭǢόĀƪÈŶë;Ñ̆ȤМľĮEŔ—ĹŊũ~ËUă{ŸĻƹɁύȩþĽvĽƓÉ@ē„ĽɲßǐƫʾǗĒpäWÐxnsÀ^ƆwW©¦cÅ¡Ji§vúF¶Ž¨c~c¼īŒeXǚ‹\\đ¾JŽwÀďksãA‹fÕ¦L}wa‚o”Z’‹D½†Ml«]eÒÅaɲáo½FõÛ]ĻÒ¡wYR£¢rvÓ®y®LF‹LzĈ„ôe]gx}•|KK}xklL]c¦£fRtív¦†PĤoH{tK"]],"encodeOffsets":[[[108619,36299]],[[108589,36341]]]}},{"type":"Feature","id":"630000","properties":{"id":"630000","cp":[96.778916,35.623178],"name":"青海","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@InJm"],["@@CƒÆ½OŃĦsΰ~dz¦@@“Ņiš±è}ؘƄ˹A³r_ĞŠǒNΌĐw¤^ŬĵªpĺSZg’rpiƼĘԛ¨C|͖J’©Ħ»®VIJ~f\\m `Un„˜~ʌŸ•ĬàöNt•~ňjy–¢Zi˜Ɣ¥ĄŠk´nl`JʇŠJþ©pdƖ®È£¶ìRʦ‘źõƮËnŸʼėæÑƀĎ[‚˜¢VÎĂMÖÝÎF²sƊƀÎBļýƞ—¯ʘƭðħ¼Jh¿ŦęΌƇš¥²Q]Č¥nuÂÏriˆ¸¬ƪÛ^Ó¦d€¥[Wà…x\\ZŽjҕ¨GtpþYŊĕ´€zUO뇉P‰îMĄÁxH´á˜iÜUà›îÜՁĂÛSuŎ‹r“œJð̬EŒ‘FÁú×uÃÎkr“Ē{V}İ«O_ÌËĬ©ŽÓŧSRѱ§Ģ£^ÂyèçěM³Ƃę{[¸¿u…ºµ[gt£¸OƤĿéYŸõ·kŸq]juw¥Dĩƍ€õÇPéĽG‘ž©ã‡¤G…uȧþRcÕĕNy“yût“ˆ­‡ø‘†ï»a½ē¿BMoᣟÍj}éZËqbʍš“Ƭh¹ìÿÓAçãnIáI`ƒks£CG­ě˜Uy×Cy•…’Ÿ@¶ʡÊBnāzG„ơMē¼±O÷õJËĚăVŸĪũƆ£Œ¯{ËL½Ìzż“„VR|ĠTbuvJvµhĻĖH”Aëáa…­OÇðñęNw‡…œľ·L›mI±íĠĩPÉ×®ÿs—’cB³±JKßĊ«`…ađ»·QAmO’‘Vţéÿ¤¹SQt]]Çx€±¯A@ĉij¢Ó祖•ƒl¶ÅÛr—ŕspãRk~¦ª]Į­´“FR„åd­ČsCqđéFn¿Åƃm’Éx{W©ºƝºįkÕƂƑ¸wWūЩÈFž£\\tÈ¥ÄRÈýÌJ ƒlGr^×äùyÞ³fj”c†€¨£ÂZ|ǓMĝšÏ@ëÜőR‹›ĝ‰Œ÷¡{aïȷPu°ËXÙ{©TmĠ}Y³’­ÞIňµç½©C¡į÷¯B»|St»›]vƒųƒs»”}MÓ ÿʪƟǭA¡fs˜»PY¼c¡»¦c„ċ­¥£~msĉP•–Siƒ^o©A‰Šec‚™PeǵŽkg‚yUi¿h}aH™šĉ^|ᴟ¡HØûÅ«ĉ®]m€¡qĉ¶³ÈyôōLÁst“BŸ®wn±ă¥HSò뚣˜S’ë@לÊăxÇN©™©T±ª£IJ¡fb®ÞbŽb_Ą¥xu¥B—ž{łĝ³«`d˜Ɛt—¤ťiñžÍUuºí`£˜^tƃIJc—·ÛLO‹½Šsç¥Ts{ă\\_»™kϊ±q©čiìĉ|ÍIƒ¥ć¥›€]ª§D{ŝŖÉR_sÿc³Īō›ƿΑ›§p›[ĉ†›c¯bKm›R¥{³„Z†e^ŽŒwx¹dƽŽôIg §Mĕ ƹĴ¿—ǣÜ̓]‹Ý–]snåA{‹eŒƭ`ǻŊĿ\\ijŬű”YÂÿ¬jĖqŽßbЏ•L«¸©@ěĀ©ê¶ìÀEH|´bRľž–Ó¶rÀQþ‹vl®Õ‚E˜TzÜdb ˜hw¤{LR„ƒd“c‹b¯‹ÙVgœ‚ƜßzÃô쮍^jUèXΖ|UäÌ»rKŽ\\ŒªN‘¼pZCü†VY††¤ɃRi^rPҒTÖ}|br°qňb̰ªiƶGQ¾²„x¦PœmlŜ‘[Ĥ¡ΞsĦŸÔÏâ\\ªÚŒU\\f…¢N²§x|¤§„xĔsZPòʛ²SÐqF`ª„VƒÞŜĶƨVZŒÌL`ˆ¢dŐIqr\\oäõ–F礻Ŷ×h¹]Clـ\\¦ďÌį¬řtTӺƙgQÇÓHţĒ”´ÃbEÄlbʔC”|CˆŮˆk„Ʈ[ʼ¬ňœ´KŮÈΰÌζƶlð”ļA†TUvdTŠG†º̼ŠÔ€ŒsÊDԄveOg"]],"encodeOffsets":[[[105308,37219]],[[95370,40081]]]}},{"type":"Feature","id":"640000","properties":{"id":"640000","cp":[106.278179,37.26637],"name":"宁夏","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@KëÀęĞ«OęȿȕŸı]ʼn¡åįÕÔ«Ǵõƪ™ĚQÐZhv K°›öqÀѐS[ÃÖHƖčË‡nL]ûc…Ùß@‚“ĝ‘¾}w»»‹oģF¹œ»kÌÏ·{zPƒ§B­¢íyÅt@ƒ@áš]Yv_ssģ¼i߁”ĻL¾ġsKD£¡N_…“˜X¸}B~Haiˆ™Åf{«x»ge_bs“KF¯¡Ix™mELcÿZ¤­Ģ‘ƒÝœsuBLù•t†ŒYdˆmVtNmtOPhRw~bd…¾qÐ\\âÙH\\bImlNZŸ»loƒŸqlVm–Gā§~QCw¤™{A\\‘PKŸNY‡¯bF‡kC¥’sk‹Šs_Ã\\ă«¢ħkJi¯r›rAhĹûç£CU‡ĕĊ_ԗBixÅُĄnªÑaM~ħpOu¥sîeQ¥¤^dkKwlL~{L~–hw^‚ófćƒKyEŒ­K­zuÔ¡qQ¤xZÑ¢^ļöܾEpž±âbÊÑÆ^fk¬…NC¾‘Œ“YpxbK~¥Že֎ŒäBlt¿Đx½I[ĒǙŒWž‹f»Ĭ}d§dµùEuj¨‚IÆ¢¥dXªƅx¿]mtÏwßR͌X¢͎vÆzƂZò®ǢÌʆCrâºMÞzžÆMҔÊÓŊZľ–r°Î®Ȉmª²ĈUªĚøºˆĮ¦ÌĘk„^FłĬhĚiĀ˾iİbjÕ"],["@@mfwěwMrŢªv@G‰"]],"encodeOffsets":[[[109366,40242]],[[108600,36303]]]}},{"type":"Feature","id":"650000","properties":{"id":"650000","cp":[85.617733,40.792818],"name":"新疆","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@QØĔ²X¨”~ǘBºjʐߨvK”ƔX¨vĊOžÃƒ·¢i@~c—‡ĝe_«”Eš“}QxgɪëÏÃ@sÅyXoŖ{ô«ŸuX…ê•Îf`œC‚¹ÂÿÐGĮÕĞXŪōŸMźÈƺQèĽôe|¿ƸJR¤ĘEjcUóº¯Ĩ_ŘÁMª÷Ð¥Oéȇ¿ÖğǤǷÂF҇zÉx[]­Ĥĝ‰œ¦EP}ûƥé¿İƷTėƫœŕƅ™ƱB»Đ±’ēO…¦E–•}‘`cȺrĦáŖuҞª«IJ‡πdƺÏØZƴwʄ¤ĖGЙǂZ̓èH¶}ÚZצʥĪï|ÇĦMŔ»İĝLj‹ì¥Βœba­¯¥ǕǚkĆŵĦɑĺƯxūД̵nơʃĽá½M»›òmqóŘĝč˾ăC…ćāƿÝɽ©DZŅ¹đ¥˜³ðLrÁ®ɱĕģʼnǻ̋ȥơŻǛȡVï¹Ň۩ûkɗġƁ§ʇė̕ĩũƽō^ƕŠUv£ƁQï“Ƶkŏ½ΉÃŭdzLқʻ«ƭ\\lƒ‡ŭD‡“{ʓDkaFÃÄa“³ŤđÔGRÈƚhSӹŚsİ«ĐË[¥ÚDkº^Øg¼ŵ¸£EÍö•€ůʼnT¡c_‡ËKY‹ƧUśĵ„݃U_©rETÏʜ±OñtYw獃{£¨uM³x½şL©Ùá[ÓÐĥ Νtģ¢\\‚ś’nkO›w¥±ƒT»ƷFɯàĩÞáB¹Æ…ÑUw„੍žĽw[“mG½Èå~‡Æ÷QyŠěCFmĭZī—ŵVÁ™ƿQƛ—ûXS²‰b½KϽĉS›©ŷXĕŸ{ŽĕK·¥Ɨcqq©f¿]‡ßDõU³h—­gËÇïģÉɋw“k¯í}I·šœbmœÉ–ř›īJɥĻˁ×xo›ɹī‡l•c…¤³Xù]‘™DžA¿w͉ì¥wÇN·ÂËnƾƍdǧđ®Ɲv•Um©³G\\“}µĿ‡QyŹl㓛µEw‰LJQ½yƋBe¶ŋÀů‡ož¥A—˜Éw@•{Gpm¿Aij†ŽKLhˆ³`ñcËtW‚±»ÕS‰ëüÿďD‡u\\wwwù³—V›LŕƒOMËGh£õP¡™er™Ïd{“‡ġWÁ…č|yšg^ğyÁzÙs`—s|ÉåªÇ}m¢Ń¨`x¥’ù^•}ƒÌ¥H«‰Yªƅ”Aйn~Ꝛf¤áÀz„gŠÇDIԝ´AňĀ҄¶ûEYospõD[{ù°]u›Jq•U•|Soċxţ[õÔĥkŋÞŭZ˺óYËüċrw €ÞkrťË¿XGÉbřaDü·Ē÷Aê[Ää€I®BÕИÞ_¢āĠpŠÛÄȉĖġDKwbm‡ÄNô‡ŠfœƫVÉvi†dz—H‘‹QµâFšù­Âœ³¦{YGžƒd¢ĚÜO „€{Ö¦ÞÍÀPŒ^b–ƾŠlŽ[„vt×ĈÍE˨¡Đ~´î¸ùÎh€uè`¸ŸHÕŔVºwĠââWò‡@{œÙNÝ´ə²ȕn{¿¥{l—÷eé^e’ďˆXj©î\\ªÑò˜Üìc\\üqˆÕ[Č¡xoÂċªbØ­Œø|€¶ȴZdÆÂšońéŒGš\\”¼C°ÌƁn´nxšÊOĨ’ہƴĸ¢¸òTxÊǪMīИÖŲÃɎOvˆʦƢ~FއRěò—¿ġ~åŊœú‰Nšžš¸qŽ’Ę[Ĕ¶ÂćnÒPĒÜvúĀÊbÖ{Äî¸~Ŕünp¤ÂH¾œĄYÒ©ÊfºmԈĘcDoĬMŬ’˜S¤„s²‚”ʘچžȂVŦ –ŽèW°ªB|IJXŔþÈJĦÆæFĚêŠYĂªĂ]øªŖNÞüA€’fɨJ€˜¯ÎrDDšĤ€`€mz\\„§~D¬{vJÂ˜«lµĂb–¤p€ŌŰNĄ¨ĊXW|ų ¿¾ɄĦƐMT”‡òP˜÷fØĶK¢ȝ˔Sô¹òEð­”`Ɩ½ǒÂň×äı–§ĤƝ§C~¡‚hlå‚ǺŦŞkâ’~}ŽFøàIJaĞ‚fƠ¥Ž„Ŕdž˜®U¸ˆźXœv¢aƆúŪtŠųƠjd•ƺŠƺÅìnrh\\ĺ¯äɝĦ]èpĄ¦´LƞĬŠ´ƤǬ˼Ēɸ¤rºǼ²¨zÌPðŀbþ¹ļD¢¹œ\\ĜÑŚŸ¶ZƄ³àjĨoâŠȴLʉȮŒĐ­ĚăŽÀêZǚŐ¤qȂ\\L¢ŌİfÆs|zºeªÙæ§΢{Ā´ƐÚ¬¨Ĵà²łhʺKÞºÖTŠiƢ¾ªì°`öøu®Ê¾ãØ"],"encodeOffsets":[[88824,50096]]}},{"type":"Feature","id":"110000","properties":{"id":"110000","cp":[116.405285,39.904989],"name":"北京","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@ĽOÁ›ûtŷmiÍt_H»Ĩ±d`й­{bw…Yr“³S]§§o¹€qGtm_Sŧ€“oa›‹FLg‘QN_•dV€@Zom_ć\\ߚc±x¯oœRcfe…£’o§ËgToÛJíĔóu…|wP¤™XnO¢ÉˆŦ¯rNÄā¤zâŖÈRpŢZŠœÚ{GŠrFt¦Òx§ø¹RóäV¤XdˆżâºWbwڍUd®bêņ¾‘jnŎGŃŶŠnzÚSeîĜZczî¾i]͜™QaúÍÔiþĩȨWĢ‹ü|Ėu[qb[swP@ÅğP¿{\\‡¥A¨Ï‘Ѩj¯ŠX\\¯œMK‘pA³[H…īu}}"],"encodeOffsets":[[120023,41045]]}},{"type":"Feature","id":"120000","properties":{"id":"120000","cp":[117.190182,39.125596],"name":"天津","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@ŬgX§Ü«E…¶Ḟ“¬O_™ïlÁg“z±AXe™µÄĵ{¶]gitgšIj·›¥îakS€‰¨ÐƎk}ĕ{gB—qGf{¿a†U^fI“ư‹³õ{YƒıëNĿžk©ïËZŏ‘R§òoY×Ógc…ĥs¡bġ«@dekąI[nlPqCnp{ˆō³°`{PNdƗqSÄĻNNâyj]äžÒD ĬH°Æ]~¡HO¾ŒX}ÐxŒgp“gWˆrDGˆŒpù‚Š^L‚ˆrzWxˆZ^¨´T\\|~@I‰zƒ–bĤ‹œjeĊªz£®Ĕvě€L†mV¾Ô_ȔNW~zbĬvG†²ZmDM~”~"],"encodeOffsets":[[120237,41215]]}},{"type":"Feature","id":"310000","properties":{"id":"310000","cp":[121.472644,31.231706],"name":"上海","childNum":6},"geometry":{"type":"MultiPolygon","coordinates":[["@@ɧư¬EpƸÁxc‡"],["@@©„ªƒ"],["@@”MA‹‘š"],["@@Qp݁E§ÉC¾"],["@@bŝՕÕEȣÚƥêImɇǦèÜĠŒÚžÃƌÃ͎ó"],["@@ǜûȬɋŠŭ™×^‰sYŒɍDŋ‘ŽąñCG²«ªč@h–_p¯A{‡oloY€¬j@IJ`•gQڛhr|ǀ^MIJvtbe´R¯Ô¬¨YŽô¤r]ì†Ƭį"]],"encodeOffsets":[[[124702,32062]],[[124547,32200]],[[124808,31991]],[[124726,32110]],[[124903,32376]],[[124438,32149]]]}},{"type":"Feature","id":"500000","properties":{"id":"500000","cp":[107.304962,29.533155],"name":"重庆","childNum":2},"geometry":{"type":"MultiPolygon","coordinates":[["@@vjG~nGŘŬĶȂƀƾ¹¸ØÎezĆT¸}êЖqHŸðqĖ䒊¥^CƒIj–²p…\\_ æüY|[YxƊæuž°xb®…Űb@~¢NQt°¶‚S栓Ê~rljĔëĚ¢~šuf`‘‚†fa‚ĔJåĊ„nÖ]„jƎćÊ@Š£¾a®£Ű{ŶĕF‹ègLk{Y|¡ĜWƔtƬJÑxq‹±ĢN´‰òK‰™–LÈüD|s`ŋ’ć]ƒÃ‰`đŒMûƱ½~Y°ħ`ƏíW‰½eI‹½{aŸ‘OIrÏ¡ĕŇa†p†µÜƅġ‘œ^ÖÛbÙŽŏml½S‹êqDu[R‹ãË»†ÿw`»y‘¸_ĺę}÷`M¯ċfCVµqʼn÷Z•gg“Œ`d½pDO‡ÎCnœ^uf²ènh¼WtƏxRGg¦…pV„†FI±ŽG^ŒIc´ec‡’G•ĹÞ½sëĬ„h˜xW‚}Kӈe­Xsbk”F¦›L‘ØgTkïƵNï¶}Gy“w\\oñ¡nmĈzjŸ•@™Óc£»Wă¹Ój“_m»ˆ¹·~MvÛaqœ»­‰êœ’\\ÂoVnŽÓØÍ™²«‹bq¿efE „€‹Ĝ^Qž~ Évý‡ş¤²Į‰pEİ}zcĺƒL‹½‡š¿gņ›¡ýE¡ya£³t\\¨\\vú»¼§·Ñr_oÒý¥u‚•_n»_ƒ•At©Þűā§IVeëƒY}{VPÀFA¨ąB}q@|Ou—\\Fm‰QF݅Mw˜å}]•€|FmϋCaƒwŒu_p—¯sfÙgY…DHl`{QEfNysBЦzG¸rHe‚„N\\CvEsÐùÜ_·ÖĉsaQ¯€}_U‡†xÃđŠq›NH¬•Äd^ÝŰR¬ã°wećJEž·vÝ·Hgƒ‚éFXjÉê`|yŒpxkAwœWĐpb¥eOsmzwqChóUQl¥F^laf‹anòsr›EvfQdÁUVf—ÎvÜ^efˆtET¬ôA\\œ¢sJŽnQTjP؈xøK|nBz‰„œĞ»LY‚…FDxӄvr“[ehľš•vN”¢o¾NiÂxGp⬐z›bfZo~hGi’]öF|‰|Nb‡tOMn eA±ŠtPT‡LjpYQ|†SH††YĀxinzDJ€Ìg¢và¥Pg‰_–ÇzII‹€II•„£®S¬„Øs쐣ŒN"],["@@ifjN@s"]],"encodeOffsets":[[[109628,30765]],[[111725,31320]]]}},{"type":"Feature","id":"810000","properties":{"id":"810000","cp":[114.173355,22.320048],"name":"香港","childNum":5},"geometry":{"type":"MultiPolygon","coordinates":[["@@AlBk"],["@@mŽn"],["@@EpFo"],["@@ea¢pl¸Eõ¹‡hj[ƒ]ÔCΖ@lj˜¡uBXŸ…•´‹AI¹…[‹yDUˆ]W`çwZkmc–…M›žp€Åv›}I‹oJlcaƒfёKްä¬XJmРđhI®æÔtSHn€Eˆ„ÒrÈc"],["@@rMUw‡AS®€e"]],"encodeOffsets":[[[117111,23002]],[[117072,22876]],[[117045,22887]],[[116975,23082]],[[116882,22747]]]}},{"type":"Feature","id":"820000","properties":{"id":"820000","cp":[113.54909,22.198951],"name":"澳门","childNum":1},"geometry":{"type":"Polygon","coordinates":["@@kÊd°å§s"],"encodeOffsets":[[116279,22639]]}}],"UTF8Encoding":true}
\ No newline at end of file
...@@ -11,21 +11,21 @@ const { getPrefixCls } = useDesign() ...@@ -11,21 +11,21 @@ const { getPrefixCls } = useDesign()
const prefixCls = getPrefixCls('count-to') const prefixCls = getPrefixCls('count-to')
const props = defineProps({ const props = defineProps({
startVal: propTypes.number.def(0), startVal: propTypes.number.def(0), // 开始播放值
endVal: propTypes.number.def(2021), endVal: propTypes.number.def(2021), // 最终值
duration: propTypes.number.def(3000), duration: propTypes.number.def(3000), // 动画时长
autoplay: propTypes.bool.def(true), autoplay: propTypes.bool.def(true), // 是否自动播放动画, 默认播放
decimals: propTypes.number.validate((value: number) => value >= 0).def(0), decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // 显示的小数位数, 默认不显示小数
decimal: propTypes.string.def('.'), decimal: propTypes.string.def('.'), // 小数分隔符号, 默认为点
separator: propTypes.string.def(','), separator: propTypes.string.def(','), // 数字每三位的分隔符, 默认为逗号
prefix: propTypes.string.def(''), prefix: propTypes.string.def(''), // 前缀, 数值前面显示的内容
suffix: propTypes.string.def(''), suffix: propTypes.string.def(''), // 后缀, 数值后面显示的内容
useEasing: propTypes.bool.def(true), useEasing: propTypes.bool.def(true), // 是否使用缓动效果, 默认启用
easingFn: { easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>, type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) { default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
} } // 缓动函数
} }
}) })
......
...@@ -32,6 +32,7 @@ const getIconifyStyle = computed(() => { ...@@ -32,6 +32,7 @@ const getIconifyStyle = computed(() => {
const { color, size } = props const { color, size } = props
return { return {
fontSize: `${size}px`, fontSize: `${size}px`,
height: '1em',
color color
} }
}) })
......
...@@ -52,10 +52,10 @@ export default defineComponent({ ...@@ -52,10 +52,10 @@ export default defineComponent({
return ( return (
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}> <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
{meta?.icon && breadcrumbIcon.value ? ( {meta?.icon && breadcrumbIcon.value ? (
<> <div class="flex items-center">
<Icon icon={meta.icon} class="mr-[2px]" svgClass="inline-block"></Icon> <Icon icon={meta.icon} class="mr-[2px]" svgClass="inline-block"></Icon>
{t(v?.meta?.title)} {t(v?.meta?.title)}
</> </div>
) : ( ) : (
t(v?.meta?.title) t(v?.meta?.title)
)} )}
...@@ -114,9 +114,10 @@ $prefix-cls: #{$elNamespace}-breadcrumb; ...@@ -114,9 +114,10 @@ $prefix-cls: #{$elNamespace}-breadcrumb;
} }
} }
} }
:deep(&__item):last-child { :deep(&__item):last-child {
.#{$prefix-cls}__inner { .#{$prefix-cls}__inner {
display: flex;
align-items: center;
color: var(--el-text-color-placeholder); color: var(--el-text-color-placeholder);
&:hover { &:hover {
......
...@@ -107,7 +107,7 @@ export const useRenderLayout = () => { ...@@ -107,7 +107,7 @@ export const useRenderLayout = () => {
></ToolHeader> ></ToolHeader>
{tagsView.value ? ( {tagsView.value ? (
<TagsView class="layout-border__bottom layout-border__top"></TagsView> <TagsView class="layout-border__top layout-border__bottom"></TagsView>
) : undefined} ) : undefined}
</div> </div>
...@@ -121,13 +121,13 @@ export const useRenderLayout = () => { ...@@ -121,13 +121,13 @@ export const useRenderLayout = () => {
const renderTopLeft = () => { const renderTopLeft = () => {
return ( return (
<> <>
<div class="flex items-center bg-[var(--top-header-bg-color)] relative layout-border__bottom dark:bg-[var(--el-bg-color)]"> <div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom dark:bg-[var(--el-bg-color)]">
{logo.value ? <Logo class="custom-hover"></Logo> : undefined} {logo.value ? <Logo class="custom-hover"></Logo> : undefined}
<ToolHeader class="flex-1"></ToolHeader> <ToolHeader class="flex-1"></ToolHeader>
</div> </div>
<div class="absolute top-[var(--logo-height)+1px] left-0 w-full h-[calc(100%-1px-var(--logo-height))] flex"> <div class="absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex">
<Menu class="!h-full relative layout-border__right"></Menu> <Menu class="relative layout-border__right !h-full"></Menu>
<div <div
class={[ class={[
`${prefixCls}-content`, `${prefixCls}-content`,
...@@ -187,7 +187,7 @@ export const useRenderLayout = () => { ...@@ -187,7 +187,7 @@ export const useRenderLayout = () => {
]} ]}
> >
{logo.value ? <Logo class="custom-hover"></Logo> : undefined} {logo.value ? <Logo class="custom-hover"></Logo> : undefined}
<Menu class="flex-1 px-10px h-[var(--top-tool-height)]"></Menu> <Menu class="h-[var(--top-tool-height)] flex-1 px-10px"></Menu>
<ToolHeader></ToolHeader> <ToolHeader></ToolHeader>
</div> </div>
<div <div
...@@ -233,12 +233,12 @@ export const useRenderLayout = () => { ...@@ -233,12 +233,12 @@ export const useRenderLayout = () => {
const renderCutMenu = () => { const renderCutMenu = () => {
return ( return (
<> <>
<div class="flex items-center bg-[var(--top-header-bg-color)] relative layout-border__bottom"> <div class="relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom">
{logo.value ? <Logo class="custom-hover !pr-15px"></Logo> : undefined} {logo.value ? <Logo class="custom-hover !pr-15px"></Logo> : undefined}
<ToolHeader class="flex-1"></ToolHeader> <ToolHeader class="flex-1"></ToolHeader>
</div> </div>
<div class="absolute top-[var(--logo-height)] left-0 w-[calc(100%-2px)] h-[calc(100%-var(--logo-height))] flex"> <div class="absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex">
<TabMenu></TabMenu> <TabMenu></TabMenu>
<div <div
class={[ class={[
......
...@@ -18,7 +18,8 @@ import { ...@@ -18,7 +18,8 @@ import {
AriaComponent, AriaComponent,
ParallelComponent, ParallelComponent,
LegendComponent, LegendComponent,
ToolboxComponent ToolboxComponent,
VisualMapComponent
} from 'echarts/components' } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers' import { CanvasRenderer } from 'echarts/renderers'
...@@ -32,6 +33,7 @@ echarts.use([ ...@@ -32,6 +33,7 @@ echarts.use([
PolarComponent, PolarComponent,
AriaComponent, AriaComponent,
ParallelComponent, ParallelComponent,
VisualMapComponent,
BarChart, BarChart,
LineChart, LineChart,
PieChart, PieChart,
......
...@@ -331,9 +331,8 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -331,9 +331,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
] ]
}, },
{ {
path: '/product', path: '/mall/product', // 商品中心
component: Layout, component: Layout,
name: 'Product',
meta: { meta: {
hidden: true hidden: true
}, },
...@@ -347,12 +346,12 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -347,12 +346,12 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true, hidden: true,
canTo: true, canTo: true,
icon: 'ep:edit', icon: 'ep:edit',
title: '添加商品', title: '商品添加',
activeMenu: '/product/product-spu' activeMenu: '/mall/product/spu'
} }
}, },
{ {
path: 'spu/edit/:spuId(\\d+)', path: 'spu/edit/:id(\\d+)',
component: () => import('@/views/mall/product/spu/form/index.vue'), component: () => import('@/views/mall/product/spu/form/index.vue'),
name: 'ProductSpuEdit', name: 'ProductSpuEdit',
meta: { meta: {
...@@ -360,12 +359,12 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -360,12 +359,12 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true, hidden: true,
canTo: true, canTo: true,
icon: 'ep:edit', icon: 'ep:edit',
title: '编辑商品', title: '商品编辑',
activeMenu: '/product/product-spu' activeMenu: '/mall/product/spu'
} }
}, },
{ {
path: 'spu/detail/:spuId(\\d+)', path: 'spu/detail/:id(\\d+)',
component: () => import('@/views/mall/product/spu/form/index.vue'), component: () => import('@/views/mall/product/spu/form/index.vue'),
name: 'ProductSpuDetail', name: 'ProductSpuDetail',
meta: { meta: {
...@@ -374,7 +373,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -374,7 +373,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
canTo: true, canTo: true,
icon: 'ep:view', icon: 'ep:view',
title: '商品详情', title: '商品详情',
activeMenu: '/product/product-spu' activeMenu: '/mall/product/spu'
} }
}, },
{ {
...@@ -393,24 +392,23 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -393,24 +392,23 @@ const remainingRouter: AppRouteRecordRaw[] = [
] ]
}, },
{ {
path: '/trade', path: '/mall/trade', // 交易中心
component: Layout, component: Layout,
name: 'Order',
meta: { meta: {
hidden: true hidden: true
}, },
children: [ children: [
{ {
path: 'order/detail/:orderId(\\d+)', path: 'order/detail/:id(\\d+)',
component: () => import('@/views/mall/trade/order/detail/index.vue'), component: () => import('@/views/mall/trade/order/detail/index.vue'),
name: 'TradeOrderDetail', name: 'TradeOrderDetail',
meta: { title: '订单详情', icon: '', activeMenu: '/trade/trade/order' } meta: { title: '订单详情', icon: 'ep:view', activeMenu: '/mall/trade/order' }
}, },
{ {
path: 'after-sale/detail/:orderId(\\d+)', path: 'after-sale/detail/:id(\\d+)',
component: () => import('@/views/mall/trade/afterSale/detail/index.vue'), component: () => import('@/views/mall/trade/afterSale/detail/index.vue'),
name: 'TradeAfterSaleDetail', name: 'TradeAfterSaleDetail',
meta: { title: '退款详情', icon: '', activeMenu: '/trade/trade/after-sale' } meta: { title: '退款详情', icon: 'ep:view', activeMenu: '/mall/trade/after-sale' }
} }
] ]
}, },
......
...@@ -4,12 +4,20 @@ ...@@ -4,12 +4,20 @@
* 枚举类 * 枚举类
*/ */
// ========== COMMON 模块 ==========
// 全局通用状态枚举 // 全局通用状态枚举
export const CommonStatusEnum = { export const CommonStatusEnum = {
ENABLE: 0, // 开启 ENABLE: 0, // 开启
DISABLE: 1 // 禁用 DISABLE: 1 // 禁用
} }
// 全局用户类型枚举
export const UserTypeEnum = {
MEMBER: 1, // 会员
ADMIN: 2 // 管理员
}
// ========== SYSTEM 模块 ==========
/** /**
* 菜单的类型枚举 * 菜单的类型枚举
*/ */
...@@ -39,6 +47,25 @@ export const SystemDataScopeEnum = { ...@@ -39,6 +47,25 @@ export const SystemDataScopeEnum = {
} }
/** /**
* 用户的社交平台的类型枚举
*/
export const SystemUserSocialTypeEnum = {
DINGTALK: {
title: '钉钉',
type: 20,
source: 'dingtalk',
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
},
WECHAT_ENTERPRISE: {
title: '企业微信',
type: 30,
source: 'wechat_enterprise',
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
}
}
// ========== INFRA 模块 ==========
/**
* 代码生成模板类型 * 代码生成模板类型
*/ */
export const InfraCodegenTemplateTypeEnum = { export const InfraCodegenTemplateTypeEnum = {
...@@ -65,24 +92,7 @@ export const InfraApiErrorLogProcessStatusEnum = { ...@@ -65,24 +92,7 @@ export const InfraApiErrorLogProcessStatusEnum = {
IGNORE: 2 // 已忽略 IGNORE: 2 // 已忽略
} }
/** // ========== PAY 模块 ==========
* 用户的社交平台的类型枚举
*/
export const SystemUserSocialTypeEnum = {
DINGTALK: {
title: '钉钉',
type: 20,
source: 'dingtalk',
img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'
},
WECHAT_ENTERPRISE: {
title: '企业微信',
type: 30,
source: 'wechat_enterprise',
img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'
}
}
/** /**
* 支付渠道枚举 * 支付渠道枚举
*/ */
...@@ -177,6 +187,7 @@ export const PayOrderStatusEnum = { ...@@ -177,6 +187,7 @@ export const PayOrderStatusEnum = {
} }
} }
// ========== MALL - 商品模块 ==========
/** /**
* 商品 SPU 状态 * 商品 SPU 状态
*/ */
...@@ -195,6 +206,7 @@ export const ProductSpuStatusEnum = { ...@@ -195,6 +206,7 @@ export const ProductSpuStatusEnum = {
} }
} }
// ========== MALL - 营销模块 ==========
/** /**
* 优惠劵模板的有限期类型的枚举 * 优惠劵模板的有限期类型的枚举
*/ */
...@@ -273,17 +285,22 @@ export const PromotionDiscountTypeEnum = { ...@@ -273,17 +285,22 @@ export const PromotionDiscountTypeEnum = {
} }
} }
// ========== MALL - 交易模块 ==========
/** /**
* 分销关系绑定模式枚举 * 分销关系绑定模式枚举
*/ */
export const BrokerageBindModeEnum = { export const BrokerageBindModeEnum = {
ANYTIME: { ANYTIME: {
mode: 0, mode: 1,
name: '没有推广人' name: '首次绑定'
}, },
REGISTER: { REGISTER: {
mode: 1, mode: 2,
name: '新用户' name: '注册绑定'
},
OVERRIDE: {
mode: 3,
name: '覆盖绑定'
} }
} }
/** /**
...@@ -291,11 +308,11 @@ export const BrokerageBindModeEnum = { ...@@ -291,11 +308,11 @@ export const BrokerageBindModeEnum = {
*/ */
export const BrokerageEnabledConditionEnum = { export const BrokerageEnabledConditionEnum = {
ALL: { ALL: {
condition: 0, condition: 1,
name: '人人分销' name: '人人分销'
}, },
ADMIN: { ADMIN: {
condition: 1, condition: 2,
name: '指定分销' name: '指定分销'
} }
} }
...@@ -358,3 +375,42 @@ export const BrokerageWithdrawTypeEnum = { ...@@ -358,3 +375,42 @@ export const BrokerageWithdrawTypeEnum = {
name: '支付宝' name: '支付宝'
} }
} }
/**
* 配送方式枚举
*/
export const DeliveryTypeEnum = {
EXPRESS: {
type: 1,
name: '快递发货'
},
PICK_UP: {
type: 2,
name: '到店自提'
}
}
/**
* 交易订单 - 状态
*/
export const TradeOrderStatusEnum = {
UNPAID: {
status: 0,
name: '待支付'
},
UNDELIVERED: {
status: 10,
name: '待发货'
},
DELIVERED: {
status: 20,
name: '已发货'
},
COMPLETED: {
status: 30,
name: '已完成'
},
CANCELED: {
status: 40,
name: '已取消'
}
}
...@@ -24,7 +24,7 @@ export const getDictOptions = (dictType: string) => { ...@@ -24,7 +24,7 @@ export const getDictOptions = (dictType: string) => {
return dictStore.getDictByType(dictType) || [] return dictStore.getDictByType(dictType) || []
} }
export const getIntDictOptions = (dictType: string) => { export const getIntDictOptions = (dictType: string): DictDataType[] => {
const dictOption: DictDataType[] = [] const dictOption: DictDataType[] = []
const dictOptions: DictDataType[] = getDictOptions(dictType) const dictOptions: DictDataType[] = getDictOptions(dictType)
dictOptions.forEach((dict: DictDataType) => { dictOptions.forEach((dict: DictDataType) => {
...@@ -180,5 +180,7 @@ export enum DICT_TYPE { ...@@ -180,5 +180,7 @@ export enum DICT_TYPE {
PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态 PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态
PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式 PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式
PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态 PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态
PROMOTION_CONDITION_TYPE = 'promotion_condition_type' // 营销的条件类型枚举 PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举
PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态
PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status' // 拼团记录的状态
} }
import dayjs from 'dayjs' import dayjs from 'dayjs'
/** /**
* 日期快捷选项适用于 el-date-picker
*/
export const defaultShortcuts = [
{
text: '今天',
value: () => {
return new Date()
}
},
{
text: '昨天',
value: () => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
return [date, date]
}
},
{
text: '最近七天',
value: () => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
return [date, new Date()]
}
},
{
text: '最近 30 天',
value: () => {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24 * 30)
return [date, new Date()]
}
},
{
text: '本月',
value: () => {
const date = new Date()
date.setDate(1) // 设置为当前月的第一天
return [date, new Date()]
}
},
{
text: '今年',
value: () => {
const date = new Date()
return [new Date(`${date.getFullYear()}-01-01`), date]
}
}
]
/**
* 时间日期转换 * 时间日期转换
* @param date 当前时间,new Date() 格式 * @param date 当前时间,new Date() 格式
* @param format 需要转换的时间格式字符串 * @param format 需要转换的时间格式字符串
...@@ -11,7 +62,7 @@ import dayjs from 'dayjs' ...@@ -11,7 +62,7 @@ import dayjs from 'dayjs'
* @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ" * @description format 季度 + 星期 + 几周:"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ"
* @returns 返回拼接后的时间字符串 * @returns 返回拼接后的时间字符串
*/ */
export function formatDate(date: Date | number, format?: string): string { export function formatDate(date: dayjs.ConfigType, format?: string): string {
// 日期不存在,则返回空 // 日期不存在,则返回空
if (!date) { if (!date) {
return '' return ''
...@@ -221,3 +272,68 @@ export function convertDate(param: Date | string) { ...@@ -221,3 +272,68 @@ export function convertDate(param: Date | string) {
} }
return param return param
} }
/**
* 指定的两个日期, 是否为同一天
* @param a 日期 A
* @param b 日期 B
*/
export function isSameDay(a: dayjs.ConfigType, b: dayjs.ConfigType): boolean {
if (!a || !b) return false
const aa = dayjs(a)
const bb = dayjs(b)
return aa.year() == bb.year() && aa.month() == bb.month() && aa.day() == bb.day()
}
/**
* 获取一天的开始时间、截止时间
* @param date 日期
* @param days 天数
*/
export function getDayRange(
date: dayjs.ConfigType,
days: number
): [dayjs.ConfigType, dayjs.ConfigType] {
const day = dayjs(date).add(days, 'd')
return getDateRange(day, day)
}
/**
* 获取最近7天的开始时间、截止时间
*/
export function getLast7Days(): [dayjs.ConfigType, dayjs.ConfigType] {
const lastWeekDay = dayjs().subtract(7, 'd')
const yesterday = dayjs().subtract(1, 'd')
return getDateRange(lastWeekDay, yesterday)
}
/**
* 获取最近30天的开始时间、截止时间
*/
export function getLast30Days(): [dayjs.ConfigType, dayjs.ConfigType] {
const lastMonthDay = dayjs().subtract(30, 'd')
const yesterday = dayjs().subtract(1, 'd')
return getDateRange(lastMonthDay, yesterday)
}
/**
* 获取最近1年的开始时间、截止时间
*/
export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
const lastYearDay = dayjs().subtract(1, 'y')
const yesterday = dayjs().subtract(1, 'd')
return getDateRange(lastYearDay, yesterday)
}
/**
* 获取指定日期的开始时间、截止时间
* @param beginDate 开始日期
* @param endDate 截止日期
*/
export function getDateRange(
beginDate: dayjs.ConfigType,
endDate: dayjs.ConfigType
): [dayjs.ConfigType, dayjs.ConfigType] {
return [dayjs(beginDate).startOf('d'), dayjs(endDate).endOf('d')]
}
import { fenToYuan } from '@/utils' import { floatToFixed2 } from '@/utils'
import { TableColumnCtx } from 'element-plus'
// 格式化金额【分转元】 // 格式化金额【分转元】
export const fenToYuanFormat = ( // @ts-ignore
row: any, export const fenToYuanFormat = (_, __, cellValue: any, ___) => {
column: TableColumnCtx<any>, return `¥${floatToFixed2(cellValue)}`
cellValue: any,
index: number
) => {
return `¥${fenToYuan(cellValue)}`
} }
...@@ -224,12 +224,12 @@ export const convertToInteger = (num: number | string | undefined): number => { ...@@ -224,12 +224,12 @@ export const convertToInteger = (num: number | string | undefined): number => {
* 元转分 * 元转分
*/ */
export const yuanToFen = (amount: string | number): number => { export const yuanToFen = (amount: string | number): number => {
return Math.round(Number(amount) * 100) return convertToInteger(amount)
} }
/** /**
* 分转元 * 分转元
*/ */
export const fenToYuan = (amount: string | number): number => { export const fenToYuan = (price: string | number): number => {
return Number((Number(amount) / 100).toFixed(2)) return formatToFraction(price)
} }
...@@ -13,7 +13,8 @@ export const defaultProps = { ...@@ -13,7 +13,8 @@ export const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
value: 'id', value: 'id',
isLeaf: 'leaf' isLeaf: 'leaf',
emitPath: false // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值
} }
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config) const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)
...@@ -377,10 +378,10 @@ export const treeToString = (tree: any[], nodeId) => { ...@@ -377,10 +378,10 @@ export const treeToString = (tree: any[], nodeId) => {
function performAThoroughValidation(arr) { function performAThoroughValidation(arr) {
for (const item of arr) { for (const item of arr) {
if (item.id === nodeId) { if (item.id === nodeId) {
str += `/${item.name}` str += ` / ${item.name}`
return true return true
} else if (typeof item.children !== 'undefined' && item.children.length !== 0) { } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {
str += `/${item.name}` str += ` / ${item.name}`
if (performAThoroughValidation(item.children)) { if (performAThoroughValidation(item.children)) {
return true return true
} }
......
...@@ -35,14 +35,14 @@ ...@@ -35,14 +35,14 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" row-key="id" default-expand-all> <el-table v-loading="loading" :data="list" row-key="id" default-expand-all>
<el-table-column label="分类名称" prop="name" sortable /> <el-table-column label="名称" min-width="240" prop="name" sortable />
<el-table-column label="移动端分类图" align="center" prop="picUrl"> <el-table-column label="分类图标" align="center" min-width="80" prop="picUrl">
<template #default="scope"> <template #default="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-30px" /> <img v-if="scope.row.picUrl" :src="scope.row.picUrl" alt="移动端分类图" class="h-36px" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="分类排序" align="center" prop="sort" /> <el-table-column label="排序" align="center" min-width="150" prop="sort" />
<el-table-column label="开启状态" align="center" prop="status"> <el-table-column label="状态" align="center" min-width="150" prop="status">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template> </template>
......
...@@ -59,9 +59,8 @@ ...@@ -59,9 +59,8 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="false">
<el-table-column label="评论编号" align="center" prop="id" min-width="60" /> <el-table-column label="评论编号" align="center" prop="id" min-width="50" />
<el-table-column label="用户名称" align="center" prop="userNickname" width="80" /> <el-table-column label="商品信息" align="center" min-width="400">
<el-table-column label="商品信息" align="center" min-width="300">
<template #default="scope"> <template #default="scope">
<div class="row flex items-center gap-x-4px"> <div class="row flex items-center gap-x-4px">
<el-image <el-image
...@@ -82,10 +81,10 @@ ...@@ -82,10 +81,10 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="评分星级" align="center" prop="scores" width="80" /> <el-table-column label="用户名称" align="center" prop="userNickname" width="100" />
<el-table-column label="描述星级" align="center" prop="descriptionScores" width="80" /> <el-table-column label="商品评分" align="center" prop="descriptionScores" width="90" />
<el-table-column label="服务星级" align="center" prop="benefitScores" width="80" /> <el-table-column label="服务评分" align="center" prop="benefitScores" width="90" />
<el-table-column label="评论内容" align="center" prop="content" min-width="80"> <el-table-column label="评论内容" align="center" prop="content" min-width="210">
<template #default="scope"> <template #default="scope">
<p>{{ scope.row.content }}</p> <p>{{ scope.row.content }}</p>
<div class="flex justify-center gap-x-4px"> <div class="flex justify-center gap-x-4px">
...@@ -105,7 +104,7 @@ ...@@ -105,7 +104,7 @@
label="回复内容" label="回复内容"
align="center" align="center"
prop="replyContent" prop="replyContent"
min-width="100" min-width="250"
show-overflow-tooltip show-overflow-tooltip
/> />
<el-table-column <el-table-column
...@@ -113,7 +112,7 @@ ...@@ -113,7 +112,7 @@
align="center" align="center"
prop="createTime" prop="createTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170" width="180"
/> />
<el-table-column label="是否展示" align="center" width="80px"> <el-table-column label="是否展示" align="center" width="80px">
<template #default="scope"> <template #default="scope">
......
...@@ -53,8 +53,8 @@ ...@@ -53,8 +53,8 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column align="center" label="编号" prop="id" /> <el-table-column align="center" label="编号" min-width="60" prop="id" />
<el-table-column align="center" label="名称" prop="name" /> <el-table-column align="center" label="属性名称" prop="name" min-width="150" />
<el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" /> <el-table-column :show-overflow-tooltip="true" align="center" label="备注" prop="remark" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
...@@ -165,7 +165,7 @@ const handleDelete = async (id: number) => { ...@@ -165,7 +165,7 @@ const handleDelete = async (id: number) => {
/** 跳转商品属性列表 */ /** 跳转商品属性列表 */
const goValueList = (id: number) => { const goValueList = (id: number) => {
push({ path: '/product/property/value/' + id }) push({ name: 'ProductPropertyValue', params: { propertyId: id } })
} }
/** 初始化 **/ /** 初始化 **/
......
...@@ -45,8 +45,8 @@ ...@@ -45,8 +45,8 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="编号" align="center" prop="id" /> <el-table-column label="编号" align="center" min-width="60" prop="id" />
<el-table-column label="名称" align="center" prop="name" :show-overflow-tooltip="true" /> <el-table-column label="属性值名称" align="center" min-width="150" prop="name" />
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" /> <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
<el-table-column <el-table-column
label="创建时间" label="创建时间"
......
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
<el-table-column align="center" label="一级返佣(元)" min-width="168"> <el-table-column align="center" label="一级返佣(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number <el-input-number
v-model="row.firstBrokerageRecord" v-model="row.firstBrokeragePrice"
:min="0" :min="0"
:precision="2" :precision="2"
:step="0.1" :step="0.1"
...@@ -91,7 +91,7 @@ ...@@ -91,7 +91,7 @@
<el-table-column align="center" label="二级返佣(元)" min-width="168"> <el-table-column align="center" label="二级返佣(元)" min-width="168">
<template #default="{ row }"> <template #default="{ row }">
<el-input-number <el-input-number
v-model="row.secondBrokerageRecord" v-model="row.secondBrokeragePrice"
:min="0" :min="0"
:precision="2" :precision="2"
:step="0.1" :step="0.1"
...@@ -181,12 +181,12 @@ ...@@ -181,12 +181,12 @@
<template v-if="formData!.subCommissionType"> <template v-if="formData!.subCommissionType">
<el-table-column align="center" label="一级返佣(元)" min-width="80"> <el-table-column align="center" label="一级返佣(元)" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.firstBrokerageRecord }} {{ row.firstBrokeragePrice }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="二级返佣(元)" min-width="80"> <el-table-column align="center" label="二级返佣(元)" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.secondBrokerageRecord }} {{ row.secondBrokeragePrice }}
</template> </template>
</el-table-column> </el-table-column>
</template> </template>
...@@ -295,8 +295,8 @@ const skuList = ref<Sku[]>([ ...@@ -295,8 +295,8 @@ const skuList = ref<Sku[]>([
stock: 0, // 库存 stock: 0, // 库存
weight: 0, // 商品重量 weight: 0, // 商品重量
volume: 0, // 商品体积 volume: 0, // 商品体积
firstBrokerageRecord: 0, // 一级分销的佣金 firstBrokeragePrice: 0, // 一级分销的佣金
secondBrokerageRecord: 0 // 二级分销的佣金 secondBrokeragePrice: 0 // 二级分销的佣金
} }
]) // 批量添加时的临时数据 ]) // 批量添加时的临时数据
...@@ -415,8 +415,8 @@ const generateTableData = (propertyList: any[]) => { ...@@ -415,8 +415,8 @@ const generateTableData = (propertyList: any[]) => {
stock: 0, stock: 0,
weight: 0, weight: 0,
volume: 0, volume: 0,
firstBrokerageRecord: 0, firstBrokeragePrice: 0,
secondBrokerageRecord: 0 secondBrokeragePrice: 0
} }
// 如果存在属性相同的 sku 则不做处理 // 如果存在属性相同的 sku 则不做处理
const index = formData.value!.skus!.findIndex( const index = formData.value!.skus!.findIndex(
...@@ -491,8 +491,8 @@ watch( ...@@ -491,8 +491,8 @@ watch(
stock: 0, stock: 0,
weight: 0, weight: 0,
volume: 0, volume: 0,
firstBrokerageRecord: 0, firstBrokeragePrice: 0,
secondBrokerageRecord: 0 secondBrokeragePrice: 0
} }
] ]
} }
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
</el-table-column> </el-table-column>
<el-table-column align="center" label="销售价(元)" min-width="80"> <el-table-column align="center" label="销售价(元)" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
{{ row.price }} {{ fenToYuan(row.price) }}
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
...@@ -36,6 +36,7 @@ ...@@ -36,6 +36,7 @@
import { ElTable } from 'element-plus' import { ElTable } from 'element-plus'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import { propTypes } from '@/utils/propTypes' import { propTypes } from '@/utils/propTypes'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'SkuTableSelect' }) defineOptions({ name: 'SkuTableSelect' })
......
...@@ -15,15 +15,14 @@ ...@@ -15,15 +15,14 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="商品分类" prop="categoryId"> <el-form-item label="商品分类" prop="categoryId">
<el-tree-select <el-cascader
v-model="formData.categoryId" v-model="formData.categoryId"
:data="categoryList" :options="categoryList"
:props="defaultProps" :props="defaultProps"
check-strictly
class="w-1/1" class="w-1/1"
node-key="id" clearable
placeholder="请选择商品分类" placeholder="请选择商品分类"
@change="categoryNodeClick" filterable
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -74,8 +73,6 @@ ...@@ -74,8 +73,6 @@
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
<!-- TODO 可能情况:善品录入后选择运费发现下拉选择中没有对应的模版 这里需不需要做添加运费模版后选择的功能 -->
<!-- <el-button class="ml-20px">运费模板</el-button>-->
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
...@@ -102,7 +99,7 @@ ...@@ -102,7 +99,7 @@
<el-form-item label="分销类型" props="subCommissionType"> <el-form-item label="分销类型" props="subCommissionType">
<el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType"> <el-radio-group v-model="formData.subCommissionType" @change="changeSubCommissionType">
<el-radio :label="false">默认设置</el-radio> <el-radio :label="false">默认设置</el-radio>
<el-radio :label="true" class="radio">自行设置</el-radio> <el-radio :label="true" class="radio">单独设置</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -117,7 +114,7 @@ ...@@ -117,7 +114,7 @@
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="formData.specType" label="商品属性"> <el-form-item v-if="formData.specType" label="商品属性">
<el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加规格</el-button> <el-button class="mb-10px mr-15px" @click="attributesAddFormRef.open">添加属性</el-button>
<ProductAttributes :propertyList="propertyList" @success="generateSkus" /> <ProductAttributes :propertyList="propertyList" @success="generateSkus" />
</el-form-item> </el-form-item>
<template v-if="formData.specType && propertyList.length > 0"> <template v-if="formData.specType && propertyList.length > 0">
...@@ -139,7 +136,7 @@ ...@@ -139,7 +136,7 @@
<!-- 情况二:详情 --> <!-- 情况二:详情 -->
<Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema"> <Descriptions v-if="isDetail" :data="formData" :schema="allSchemas.detailSchema">
<template #categoryId="{ row }"> {{ categoryString(row.categoryId) }}</template> <template #categoryId="{ row }"> {{ formatCategoryName(row.categoryId) }}</template>
<template #brandId="{ row }"> <template #brandId="{ row }">
{{ brandList.find((item) => item.id === row.brandId)?.name }} {{ brandList.find((item) => item.id === row.brandId)?.name }}
</template> </template>
...@@ -150,7 +147,7 @@ ...@@ -150,7 +147,7 @@
{{ row.specType ? '多规格' : '单规格' }} {{ row.specType ? '多规格' : '单规格' }}
</template> </template>
<template #subCommissionType="{ row }"> <template #subCommissionType="{ row }">
{{ row.subCommissionType ? '自行设置' : '默认设置' }} {{ row.subCommissionType ? '单独设置' : '默认设置' }}
</template> </template>
<template #picUrl="{ row }"> <template #picUrl="{ row }">
<el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" /> <el-image :src="row.picUrl" class="h-60px w-60px" @click="imagePreview(row.picUrl)" />
...@@ -206,17 +203,17 @@ const ruleConfig: RuleConfig[] = [ ...@@ -206,17 +203,17 @@ const ruleConfig: RuleConfig[] = [
{ {
name: 'price', name: 'price',
rule: (arg) => arg >= 0.01, rule: (arg) => arg >= 0.01,
message: '商品销售价格必须大于等于 0.01 !!!' message: '商品销售价格必须大于等于 0.01 !!!'
}, },
{ {
name: 'marketPrice', name: 'marketPrice',
rule: (arg) => arg >= 0.01, rule: (arg) => arg >= 0.01,
message: '商品市场价格必须大于等于 0.01 !!!' message: '商品市场价格必须大于等于 0.01 !!!'
}, },
{ {
name: 'costPrice', name: 'costPrice',
rule: (arg) => arg >= 0.01, rule: (arg) => arg >= 0.01,
message: '商品成本价格必须大于等于 0.01 !!!' message: '商品成本价格必须大于等于 0.01 !!!'
} }
] ]
...@@ -332,8 +329,8 @@ defineExpose({ validate }) ...@@ -332,8 +329,8 @@ defineExpose({ validate })
const changeSubCommissionType = () => { const changeSubCommissionType = () => {
// 默认为零,类型切换后也要重置为零 // 默认为零,类型切换后也要重置为零
for (const item of formData.skus) { for (const item of formData.skus) {
item.firstBrokerageRecord = 0 item.firstBrokeragePrice = 0
item.secondBrokerageRecord = 0 item.secondBrokeragePrice = 0
} }
} }
...@@ -352,30 +349,18 @@ const onChangeSpec = () => { ...@@ -352,30 +349,18 @@ const onChangeSpec = () => {
stock: 0, stock: 0,
weight: 0, weight: 0,
volume: 0, volume: 0,
firstBrokerageRecord: 0, firstBrokeragePrice: 0,
secondBrokerageRecord: 0 secondBrokeragePrice: 0
} }
] ]
} }
const categoryList = ref([]) // 分类树 const categoryList = ref([]) // 分类树
/** /** 获取分类的节点的完整结构 */
* 选择分类时触发校验 const formatCategoryName = (categoryId) => {
*/
const categoryNodeClick = () => {
if (!checkSelectedNode(categoryList.value, formData.categoryId)) {
formData.categoryId = null
message.warning('必须选择二级及以下节点!!')
}
}
/**
* 获取分类的节点的完整结构
*
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
return treeToString(categoryList.value, categoryId) return treeToString(categoryList.value, categoryId)
} }
const brandList = ref([]) // 精简商品品牌列表 const brandList = ref([]) // 精简商品品牌列表
const deliveryTemplateList = ref([]) // 运费模版 const deliveryTemplateList = ref([]) // 运费模版
onMounted(async () => { onMounted(async () => {
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<!-- TODO tag展示暂时不考虑排序 --> <!-- TODO @puhui999:tag展示暂时不考虑排序;支持拖动排序 -->
<el-form-item label="活动优先级"> <el-form-item label="活动优先级">
<el-tag>默认</el-tag> <el-tag>默认</el-tag>
<el-tag class="ml-2" type="success">秒杀</el-tag> <el-tag class="ml-2" type="success">秒杀</el-tag>
......
...@@ -82,8 +82,8 @@ const formData = ref<ProductSpuApi.Spu>({ ...@@ -82,8 +82,8 @@ const formData = ref<ProductSpuApi.Spu>({
stock: 0, // 库存 stock: 0, // 库存
weight: 0, // 商品重量 weight: 0, // 商品重量
volume: 0, // 商品体积 volume: 0, // 商品体积
firstBrokerageRecord: 0, // 一级分销的佣金 firstBrokeragePrice: 0, // 一级分销的佣金
secondBrokerageRecord: 0 // 二级分销的佣金 secondBrokeragePrice: 0 // 二级分销的佣金
} }
], ],
description: '', // 商品详情 description: '', // 商品详情
...@@ -102,7 +102,7 @@ const getDetail = async () => { ...@@ -102,7 +102,7 @@ const getDetail = async () => {
if ('ProductSpuDetail' === name) { if ('ProductSpuDetail' === name) {
isDetail.value = true isDetail.value = true
} }
const id = params.spuId as unknown as number const id = params.id as unknown as number
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
...@@ -112,15 +112,15 @@ const getDetail = async () => { ...@@ -112,15 +112,15 @@ const getDetail = async () => {
item.price = floatToFixed2(item.price) item.price = floatToFixed2(item.price)
item.marketPrice = floatToFixed2(item.marketPrice) item.marketPrice = floatToFixed2(item.marketPrice)
item.costPrice = floatToFixed2(item.costPrice) item.costPrice = floatToFixed2(item.costPrice)
item.firstBrokerageRecord = floatToFixed2(item.firstBrokerageRecord) item.firstBrokeragePrice = floatToFixed2(item.firstBrokeragePrice)
item.secondBrokerageRecord = floatToFixed2(item.secondBrokerageRecord) item.secondBrokeragePrice = floatToFixed2(item.secondBrokeragePrice)
} else { } else {
// 回显价格分转元 // 回显价格分转元
item.price = formatToFraction(item.price) item.price = formatToFraction(item.price)
item.marketPrice = formatToFraction(item.marketPrice) item.marketPrice = formatToFraction(item.marketPrice)
item.costPrice = formatToFraction(item.costPrice) item.costPrice = formatToFraction(item.costPrice)
item.firstBrokerageRecord = formatToFraction(item.firstBrokerageRecord) item.firstBrokeragePrice = formatToFraction(item.firstBrokeragePrice)
item.secondBrokerageRecord = formatToFraction(item.secondBrokerageRecord) item.secondBrokeragePrice = formatToFraction(item.secondBrokeragePrice)
} }
}) })
formData.value = res formData.value = res
...@@ -149,8 +149,8 @@ const submitForm = async () => { ...@@ -149,8 +149,8 @@ const submitForm = async () => {
item.price = convertToInteger(item.price) item.price = convertToInteger(item.price)
item.marketPrice = convertToInteger(item.marketPrice) item.marketPrice = convertToInteger(item.marketPrice)
item.costPrice = convertToInteger(item.costPrice) item.costPrice = convertToInteger(item.costPrice)
item.firstBrokerageRecord = convertToInteger(item.firstBrokerageRecord) item.firstBrokeragePrice = convertToInteger(item.firstBrokeragePrice)
item.secondBrokerageRecord = convertToInteger(item.secondBrokerageRecord) item.secondBrokeragePrice = convertToInteger(item.secondBrokeragePrice)
}) })
// 处理轮播图列表 // 处理轮播图列表
const newSliderPicUrls: any[] = [] const newSliderPicUrls: any[] = []
...@@ -161,7 +161,7 @@ const submitForm = async () => { ...@@ -161,7 +161,7 @@ const submitForm = async () => {
deepCopyFormData.sliderPicUrls = newSliderPicUrls deepCopyFormData.sliderPicUrls = newSliderPicUrls
// 校验都通过后提交表单 // 校验都通过后提交表单
const data = deepCopyFormData as ProductSpuApi.Spu const data = deepCopyFormData as ProductSpuApi.Spu
const id = params.spuId as unknown as number const id = params.id as unknown as number
if (!id) { if (!id) {
await ProductSpuApi.createSpu(data) await ProductSpuApi.createSpu(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
......
...@@ -18,15 +18,14 @@ ...@@ -18,15 +18,14 @@
/> />
</el-form-item> </el-form-item>
<el-form-item label="商品分类" prop="categoryId"> <el-form-item label="商品分类" prop="categoryId">
<el-tree-select <el-cascader
v-model="queryParams.categoryId" v-model="queryParams.categoryId"
:data="categoryList" :options="categoryList"
:props="defaultProps" :props="defaultProps"
check-strictly
class="w-1/1" class="w-1/1"
node-key="id" clearable
placeholder="请选择商品分类" placeholder="请选择商品分类"
@change="nodeClick" filterable
/> />
</el-form-item> </el-form-item>
<el-form-item label="创建时间" prop="createTime"> <el-form-item label="创建时间" prop="createTime">
...@@ -78,7 +77,7 @@ ...@@ -78,7 +77,7 @@
/> />
</el-tabs> </el-tabs>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column type="expand" width="30"> <el-table-column type="expand">
<template #default="{ row }"> <template #default="{ row }">
<el-form class="spu-table-expand" label-position="left"> <el-form class="spu-table-expand" label-position="left">
<el-row> <el-row>
...@@ -86,17 +85,17 @@ ...@@ -86,17 +85,17 @@
<el-row> <el-row>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="商品分类:"> <el-form-item label="商品分类:">
<span>{{ categoryString(row.categoryId) }}</span> <span>{{ formatCategoryName(row.categoryId) }}</span>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="市场价:"> <el-form-item label="市场价:">
<span>{{ floatToFixed2(row.marketPrice) }}</span> <span>{{ fenToYuan(row.marketPrice) }}</span>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="成本价:"> <el-form-item label="成本价:">
<span>{{ floatToFixed2(row.costPrice) }}</span> <span>{{ fenToYuan(row.costPrice) }}</span>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
...@@ -106,9 +105,8 @@ ...@@ -106,9 +105,8 @@
<el-col :span="24"> <el-col :span="24">
<el-row> <el-row>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="收藏:"> <el-form-item label="浏览量:">
<!-- TODO 没有这个属性,暂时写死 5 个 --> <span>{{ row.browseCount }}</span>
<span>5</span>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
...@@ -122,7 +120,7 @@ ...@@ -122,7 +120,7 @@
</el-form> </el-form>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column key="id" align="center" label="商品编号" prop="id" /> <el-table-column align="center" label="商品编号" min-width="60" prop="id" />
<el-table-column label="商品图" min-width="80"> <el-table-column label="商品图" min-width="80">
<template #default="{ row }"> <template #default="{ row }">
<el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" /> <el-image :src="row.picUrl" class="h-30px w-30px" @click="imagePreview(row.picUrl)" />
...@@ -130,7 +128,7 @@ ...@@ -130,7 +128,7 @@
</el-table-column> </el-table-column>
<el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" /> <el-table-column :show-overflow-tooltip="true" label="商品名称" min-width="300" prop="name" />
<el-table-column align="center" label="商品售价" min-width="90" prop="price"> <el-table-column align="center" label="商品售价" min-width="90" prop="price">
<template #default="{ row }"> {{ floatToFixed2(row.price) }}</template> <template #default="{ row }"> {{ fenToYuan(row.price) }}</template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="销量" min-width="90" prop="salesCount" /> <el-table-column align="center" label="销量" min-width="90" prop="salesCount" />
<el-table-column align="center" label="库存" min-width="90" prop="stock" /> <el-table-column align="center" label="库存" min-width="90" prop="stock" />
...@@ -152,7 +150,7 @@ ...@@ -152,7 +150,7 @@
active-text="上架" active-text="上架"
inactive-text="下架" inactive-text="下架"
inline-prompt inline-prompt
@change="changeStatus(row)" @change="handleStatusChange(row)"
/> />
</template> </template>
<template v-else> <template v-else>
...@@ -191,7 +189,7 @@ ...@@ -191,7 +189,7 @@
v-hasPermi="['product:spu:update']" v-hasPermi="['product:spu:update']"
link link
type="primary" type="primary"
@click="changeStatus(row, ProductSpuStatusEnum.DISABLE.status)" @click="handleStatus02Change(row, ProductSpuStatusEnum.DISABLE.status)"
> >
恢复到仓库 恢复到仓库
</el-button> </el-button>
...@@ -201,7 +199,7 @@ ...@@ -201,7 +199,7 @@
v-hasPermi="['product:spu:update']" v-hasPermi="['product:spu:update']"
link link
type="primary" type="primary"
@click="changeStatus(row, ProductSpuStatusEnum.RECYCLE.status)" @click="handleStatus02Change(row, ProductSpuStatusEnum.RECYCLE.status)"
> >
加入回收站 加入回收站
</el-button> </el-button>
...@@ -220,12 +218,11 @@ ...@@ -220,12 +218,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { TabsPaneContext } from 'element-plus' import { TabsPaneContext } from 'element-plus'
import { cloneDeep } from 'lodash-es'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { checkSelectedNode, defaultProps, handleTree, treeToString } from '@/utils/tree' import { defaultProps, handleTree, treeToString } from '@/utils/tree'
import { ProductSpuStatusEnum } from '@/utils/constants' import { ProductSpuStatusEnum } from '@/utils/constants'
import { floatToFixed2 } from '@/utils' import { fenToYuan } from '@/utils'
import download from '@/utils/download' import download from '@/utils/download'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import * as ProductCategoryApi from '@/api/mall/product/category' import * as ProductCategoryApi from '@/api/mall/product/category'
...@@ -254,7 +251,7 @@ const tabsData = ref([ ...@@ -254,7 +251,7 @@ const tabsData = ref([
}, },
{ {
count: 0, count: 0,
name: '已经售空商品', name: '已售罄商品',
type: 2 type: 2
}, },
{ {
...@@ -303,43 +300,37 @@ const getList = async () => { ...@@ -303,43 +300,37 @@ const getList = async () => {
} }
} }
/** /** 添加到仓库 / 回收站的状态 */
* 更改 SPU 状态 const handleStatus02Change = async (row, newStatus: number) => {
* try {
* @param row // 二次确认
* @param status 更改前的值 const text = newStatus === ProductSpuStatusEnum.RECYCLE.status ? '加入到回收站' : '恢复到仓库'
*/ await message.confirm(`确认要"${row.name}"${text}吗?`)
const changeStatus = async (row, status?: number) => { // 发起修改
const deepCopyValue = cloneDeep(unref(row)) await ProductSpuApi.updateStatus({ id: row.id, status: newStatus })
if (typeof status !== 'undefined') deepCopyValue.status = status message.success(text + '成功')
// 刷新 tabs 数据
await getTabsCount()
// 刷新列表
await getList()
} catch {}
}
/** 更新上架/下架状态 */
const handleStatusChange = async (row) => {
try { try {
let text = '' // 二次确认
switch (deepCopyValue.status) { const text = row.status ? '上架' : '下架'
case ProductSpuStatusEnum.DISABLE.status: await message.confirm(`确认要${text}"${row.name}"吗?`)
text = ProductSpuStatusEnum.DISABLE.name // 发起修改
break await ProductSpuApi.updateStatus({ id: row.id, status: row.status })
case ProductSpuStatusEnum.ENABLE.status: message.success(text + '成功')
text = ProductSpuStatusEnum.ENABLE.name
break
case ProductSpuStatusEnum.RECYCLE.status:
text = `加入${ProductSpuStatusEnum.RECYCLE.name}`
break
}
await message.confirm(
deepCopyValue.status === -1
? `确认要将[${row.name}]${text}吗?`
: row.status === -1 // 再判断一次原对象是否等于-1,例: 把回收站中的商品恢复到仓库中,事件触发时原对象status为-1 深拷贝对象status被赋值为0
? `确认要将[${row.name}]恢复到仓库吗?`
: `确认要${text}[${row.name}]吗?`
)
await ProductSpuApi.updateStatus({ id: deepCopyValue.id, status: deepCopyValue.status })
message.success('更新状态成功')
// 刷新 tabs 数据 // 刷新 tabs 数据
await getTabsCount() await getTabsCount()
// 刷新列表 // 刷新列表
await getList() await getList()
} catch { } catch {
// 取消更改状态时回显数据 // 异常时,需要重置回之前的值
row.status = row.status =
row.status === ProductSpuStatusEnum.DISABLE.status row.status === ProductSpuStatusEnum.DISABLE.status
? ProductSpuStatusEnum.ENABLE.status ? ProductSpuStatusEnum.ENABLE.status
...@@ -380,26 +371,20 @@ const resetQuery = () => { ...@@ -380,26 +371,20 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
/** /** 新增或修改 */
* 新增或修改
*
* @param id 商品 SPU 编号
*/
const openForm = (id?: number) => { const openForm = (id?: number) => {
// 修改 // 修改
if (typeof id === 'number') { if (typeof id === 'number') {
push({ name: 'ProductSpuEdit', params: { spuId: id } }) push({ name: 'ProductSpuEdit', params: { id } })
return return
} }
// 新增 // 新增
push({ name: 'ProductSpuAdd' }) push({ name: 'ProductSpuAdd' })
} }
/** /** 查看商品详情 */
* 查看商品详情
*/
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'ProductSpuDetail', params: { spuId: id } }) push({ name: 'ProductSpuDetail', params: { id } })
} }
/** 导出按钮操作 */ /** 导出按钮操作 */
...@@ -417,6 +402,12 @@ const handleExport = async () => { ...@@ -417,6 +402,12 @@ const handleExport = async () => {
} }
} }
const categoryList = ref() // 分类树
/** 获取分类的节点的完整结构 */
const formatCategoryName = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
// 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。 // 监听路由变化更新列表,解决商品保存后,列表不刷新的问题。
watch( watch(
() => currentRoute.value, () => currentRoute.value,
...@@ -425,25 +416,6 @@ watch( ...@@ -425,25 +416,6 @@ watch(
} }
) )
const categoryList = ref() // 分类树
/**
* 获取分类的节点的完整结构
* @param categoryId 分类id
*/
const categoryString = (categoryId) => {
return treeToString(categoryList.value, categoryId)
}
/**
* 校验所选是否为二级及以下节点
*/
const nodeClick = () => {
if (!checkSelectedNode(categoryList.value, queryParams.value.categoryId)) {
queryParams.value.categoryId = null
message.warning('必须选择二级及以下节点!!')
}
}
/** 初始化 **/ /** 初始化 **/
onMounted(async () => { onMounted(async () => {
await getTabsCount() await getTabsCount()
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
<el-table-column align="center" label="砍价底价(元)" min-width="168"> <el-table-column align="center" label="砍价底价(元)" min-width="168">
<template #default="{ row: sku }"> <template #default="{ row: sku }">
<el-input-number <el-input-number
v-model="sku.productConfig.bargainPrice" v-model="sku.productConfig.bargainMinPrice"
:min="0" :min="0"
:precision="2" :precision="2"
:step="0.1" :step="0.1"
...@@ -61,6 +61,7 @@ import { SpuAndSkuList, SpuProperty, SpuSelect } from '@/views/mall/promotion/co ...@@ -61,6 +61,7 @@ import { SpuAndSkuList, SpuProperty, SpuSelect } from '@/views/mall/promotion/co
import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components' import { getPropertyList, RuleConfig } from '@/views/mall/product/spu/components'
import * as ProductSpuApi from '@/api/mall/product/spu' import * as ProductSpuApi from '@/api/mall/product/spu'
import { convertToInteger, formatToFraction } from '@/utils' import { convertToInteger, formatToFraction } from '@/utils'
import { cloneDeep } from 'lodash-es'
defineOptions({ name: 'PromotionBargainActivityForm' }) defineOptions({ name: 'PromotionBargainActivityForm' })
...@@ -86,7 +87,7 @@ const ruleConfig: RuleConfig[] = [ ...@@ -86,7 +87,7 @@ const ruleConfig: RuleConfig[] = [
message: '商品砍价起始价格不能小于 0 !!!' message: '商品砍价起始价格不能小于 0 !!!'
}, },
{ {
name: 'productConfig.bargainPrice', name: 'productConfig.bargainMinPrice',
rule: (arg) => arg >= 0, rule: (arg) => arg >= 0,
message: '商品砍价底价不能小于 0 !!!' message: '商品砍价底价不能小于 0 !!!'
}, },
...@@ -123,14 +124,14 @@ const getSpuDetails = async ( ...@@ -123,14 +124,14 @@ const getSpuDetails = async (
spuId: spu.id!, spuId: spu.id!,
skuId: sku.id!, skuId: sku.id!,
bargainFirstPrice: 1, bargainFirstPrice: 1,
bargainPrice: 1, bargainMinPrice: 1,
stock: 1 stock: 1
} }
if (typeof products !== 'undefined') { if (typeof products !== 'undefined') {
const product = products.find((item) => item.skuId === sku.id) const product = products.find((item) => item.skuId === sku.id)
if (product) { if (product) {
product.bargainFirstPrice = formatToFraction(product.bargainFirstPrice) product.bargainFirstPrice = formatToFraction(product.bargainFirstPrice)
product.bargainPrice = formatToFraction(product.bargainPrice) product.bargainMinPrice = formatToFraction(product.bargainMinPrice)
} }
config = product || config config = product || config
} }
...@@ -173,7 +174,7 @@ const open = async (type: string, id?: number) => { ...@@ -173,7 +174,7 @@ const open = async (type: string, id?: number) => {
spuId: data.spuId!, spuId: data.spuId!,
skuId: data.skuId, skuId: data.skuId,
bargainFirstPrice: data.bargainFirstPrice, // 砍价起始价格,单位分 bargainFirstPrice: data.bargainFirstPrice, // 砍价起始价格,单位分
bargainPrice: data.bargainPrice, // 砍价底价 bargainMinPrice: data.bargainMinPrice, // 砍价底价
stock: data.stock // 活动库存 stock: data.stock // 活动库存
} }
] ]
...@@ -204,12 +205,12 @@ const submitForm = async () => { ...@@ -204,12 +205,12 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formRef.value.formModel as BargainActivityApi.BargainActivityVO const data = cloneDeep(formRef.value.formModel) as BargainActivityApi.BargainActivityVO
const products = spuAndSkuListRef.value.getSkuConfigs('productConfig') const products = spuAndSkuListRef.value.getSkuConfigs('productConfig')
products.forEach((item: BargainProductVO) => { products.forEach((item: BargainProductVO) => {
// 砍价价格元转分 // 砍价价格元转分
item.bargainFirstPrice = convertToInteger(item.bargainFirstPrice) item.bargainFirstPrice = convertToInteger(item.bargainFirstPrice)
item.bargainPrice = convertToInteger(item.bargainPrice) item.bargainMinPrice = convertToInteger(item.bargainMinPrice)
}) })
// 用户每次砍价金额分转元, 元转分 // 用户每次砍价金额分转元, 元转分
data.randomMinPrice = convertToInteger(data.randomMinPrice) data.randomMinPrice = convertToInteger(data.randomMinPrice)
......
...@@ -6,7 +6,7 @@ export const rules = reactive({ ...@@ -6,7 +6,7 @@ export const rules = reactive({
name: [required], name: [required],
startTime: [required], startTime: [required],
endTime: [required], endTime: [required],
userSize: [required], helpMaxCount: [required],
bargainCount: [required], bargainCount: [required],
singleLimitCount: [required] singleLimitCount: [required]
}) })
...@@ -72,7 +72,7 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -72,7 +72,7 @@ const crudSchemas = reactive<CrudSchema[]>([
}, },
{ {
label: '砍价人数', label: '砍价人数',
field: 'userSize', field: 'helpMaxCount',
isSearch: false, isSearch: false,
form: { form: {
component: 'InputNumber', component: 'InputNumber',
...@@ -133,20 +133,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -133,20 +133,6 @@ const crudSchemas = reactive<CrudSchema[]>([
} }
}, },
{ {
label: '砍价成功数量',
field: 'successCount',
isSearch: false,
isForm: false
},
{
label: '活动状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true,
isForm: false
},
{
label: '拼团商品', label: '拼团商品',
field: 'spuId', field: 'spuId',
isSearch: false, isSearch: false,
...@@ -155,11 +141,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -155,11 +141,6 @@ const crudSchemas = reactive<CrudSchema[]>([
span: 24 span: 24
} }
} }
},
{
label: '操作',
field: 'action',
isForm: false
} }
]) ])
export const { allSchemas } = useCrudSchemas(crudSchemas) export const { allSchemas } = useCrudSchemas(crudSchemas)
<template> <template>
<!-- 搜索工作栏 -->
<ContentWrap> <ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams"> <!-- 搜索工作栏 -->
<!-- 新增等操作按钮 --> <el-form
<template #actionMore> class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="活动名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入活动名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择活动状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button <el-button
v-hasPermi="['promotion:bargain-activity:create']"
plain
type="primary" type="primary"
plain
@click="openForm('create')" @click="openForm('create')"
v-hasPermi="['promotion:bargain-activity:create']"
> >
<Icon class="mr-5px" icon="ep:plus" /> <Icon icon="ep:plus" class="mr-5px" /> 新增
新增
</el-button> </el-button>
</template> </el-form-item>
</Search> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<Table <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
v-model:currentPage="tableObject.currentPage" <el-table-column label="活动编号" prop="id" min-width="80" />
v-model:pageSize="tableObject.pageSize" <el-table-column label="活动名称" prop="name" min-width="140" />
:columns="allSchemas.tableColumns" <el-table-column label="活动时间" min-width="210">
:data="tableObject.tableList" <template #default="scope">
:loading="tableObject.loading" {{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
:pagination="{ ~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
total: tableObject.total </template>
}" </el-table-column>
> <el-table-column label="商品图片" prop="spuName" min-width="80">
<template #spuId="{ row }"> <template #default="scope">
<el-image <el-image
:src="row.picUrl" :src="scope.row.picUrl"
class="mr-5px h-30px w-30px align-middle" class="h-40px w-40px"
@click="imagePreview(row.picUrl)" :preview-src-list="[scope.row.picUrl]"
/> preview-teleported
<span class="align-middle">{{ row.spuName }}</span> />
</template> </template>
<template #action="{ row }"> </el-table-column>
<el-button <el-table-column label="商品标题" prop="spuName" min-width="300" />
v-hasPermi="['promotion:bargain-activity:update']" <el-table-column
link label="起始价格"
type="primary" prop="bargainFirstPrice"
@click="openForm('update', row.id)" min-width="100"
> :formatter="fenToYuanFormat"
编辑 />
</el-button> <el-table-column
<el-button label="砍价底价"
v-hasPermi="['promotion:bargain-activity:delete']" prop="bargainMinPrice"
link min-width="100"
type="danger" :formatter="fenToYuanFormat"
@click="handleDelete(row.id)" />
> <el-table-column label="总砍价人数" prop="recordUserCount" min-width="100" />
删除 <el-table-column label="成功砍价人数" prop="recordSuccessUserCount" min-width="110" />
</el-button> <el-table-column label="助力人数" prop="helpUserCount" min-width="100" />
</template> <el-table-column label="活动状态" align="center" prop="status" min-width="100">
</Table> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="库存" align="center" prop="stock" min-width="80" />
<el-table-column label="总库存" align="center" prop="totalStock" min-width="80" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['promotion:bargain-activity:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleClose(scope.row.id)"
v-if="scope.row.status === 0"
v-hasPermi="['promotion:bargain-activity:close']"
>
关闭
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-else
v-hasPermi="['promotion:bargain-activity:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<BargainActivityForm ref="formRef" @success="getList" /> <BargainActivityForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" setup>
import { allSchemas } from './bargainActivity.data' <script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivity' import * as BargainActivityApi from '@/api/mall/promotion/bargain/bargainActivity'
import BargainActivityForm from './BargainActivityForm.vue' import BargainActivityForm from './BargainActivityForm.vue'
import { createImageViewer } from '@/components/ImageViewer' import { formatDate } from '@/utils/formatTime'
import { sortTableColumns } from '@/hooks/web/useCrudSchemas' import { fenToYuanFormat } from '@/utils/formatter'
defineOptions({ name: 'PromotionBargainActivity' }) defineOptions({ name: 'PromotionBargainActivity' })
// tableObject:表格的属性对象,可获得分页大小、条数等属性 const message = useMessage() // 消息弹窗
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 const { t } = useI18n() // 国际化
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
const { tableObject, tableMethods } = useTable({ const loading = ref(true) // 列表的加载中
getListApi: BargainActivityApi.getBargainActivityPage, // 分页接口 const total = ref(0) // 列表的总页数
delListApi: BargainActivityApi.deleteBargainActivity // 删除接口 const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null
}) })
// 获得表格的各种操作 const queryFormRef = ref() // 搜索的表单
const { getList, setSearchParams } = tableMethods const exportLoading = ref(false) // 导出的加载中
/** 商品图预览 */ /** 查询列表 */
const imagePreview = (imgUrl: string) => { const getList = async () => {
createImageViewer({ loading.value = true
urlList: [imgUrl] try {
}) const data = await BargainActivityApi.getBargainActivityPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
} }
/** 添加/修改操作 */ /** 添加/修改操作 */
...@@ -93,15 +197,35 @@ const openForm = (type: string, id?: number) => { ...@@ -93,15 +197,35 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
// TODO 芋艿:这里要改下
/** 关闭按钮操作 */
const handleClose = async (id: number) => {
try {
// 关闭的二次确认
await message.confirm('确认关闭该砍价活动吗?')
// 发起关闭
await BargainActivityApi.closeSeckillActivity(id)
message.success('关闭成功')
// 刷新列表
await getList()
} catch {}
}
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = (id: number) => { const handleDelete = async (id: number) => {
tableMethods.delList(id, false) try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await BargainActivityApi.closeBargainActivity(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
} }
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(async () => {
// 获得活动列表 await getList()
sortTableColumns(allSchemas.tableColumns, 'spuId')
getList()
}) })
</script> </script>
<template>
<Dialog v-model="dialogVisible" title="助力列表">
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="用户编号" prop="userId" min-width="80px" />
<el-table-column label="用户头像" prop="avatar" min-width="80px">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="用户昵称" prop="nickname" min-width="100px" />
<el-table-column
label="砍价金额"
prop="reducePrice"
min-width="100px"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="助力时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import * as BargainHelpApi from '@/api/mall/promotion/bargain/bargainHelp'
import { fenToYuanFormat } from '@/utils/formatter'
/** 助力列表 */
defineOptions({ name: 'BargainRecordListDialog' })
const message = useMessage() // 消息弹窗
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
recordId: undefined
})
const queryFormRef = ref() // 搜索的表单
/** 打开弹窗 */
const dialogVisible = ref(false) // 弹窗的是否展示
const open = async (recordId: any) => {
dialogVisible.value = true
queryParams.recordId = recordId
resetQuery()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BargainHelpApi.getBargainHelpPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
</script>
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="砍价状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择砍价状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['promotion:bargain-record:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['promotion:bargain-record:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" min-width="50" prop="id" />
<el-table-column label="发起用户" min-width="120">
<template #default="scope">
<el-image
:src="scope.row.avatar"
class="h-20px w-20px"
:preview-src-list="[scope.row.avatar]"
preview-teleported
/>
{{ scope.row.nickname }}
</template>
</el-table-column>
<el-table-column
label="发起时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="砍价活动" min-width="150" prop="activity.name" />
<el-table-column
label="最低价"
min-width="100"
prop="activity.bargainMinPrice"
:formatter="fenToYuanFormat"
/>
<el-table-column
label="当前价"
min-width="100"
prop="bargainPrice"
:formatter="fenToYuanFormat"
/>
<el-table-column label="总砍价次数" min-width="100" prop="activity.helpMaxCount" />
<el-table-column label="剩余砍价次数" min-width="100" prop="helpCount" />
<el-table-column label="砍价状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_BARGAIN_RECORD_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="结束时间"
align="center"
prop="endTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="订单编号" align="center" prop="orderId" />
<el-table-column label="操作" align="center">
<template #default="scope">
<el-button
link
type="primary"
@click="openRecordListDialog(scope.row.id)"
v-hasPermi="['promotion:bargain-help:query']"
>
助力
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗 -->
<BargainRecordListDialog ref="recordListDialogRef" />
</template>
<script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as BargainRecordApi from '@/api/mall/promotion/bargain/bargainRecord'
import { fenToYuanFormat } from '@/utils/formatter'
import BargainRecordListDialog from './BargainRecordListDialog.vue'
defineOptions({ name: 'PromotionBargainRecord' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
status: null,
createTime: []
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await BargainRecordApi.getBargainRecordPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 打开[助力]弹窗 */
const recordListDialogRef = ref()
const openRecordListDialog = (id?: number) => {
recordListDialogRef.value.open(id)
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
...@@ -167,7 +167,7 @@ const submitForm = async () => { ...@@ -167,7 +167,7 @@ const submitForm = async () => {
products.forEach((item: CombinationActivityApi.CombinationProductVO) => { products.forEach((item: CombinationActivityApi.CombinationProductVO) => {
item.combinationPrice = convertToInteger(item.combinationPrice) item.combinationPrice = convertToInteger(item.combinationPrice)
}) })
const data = formRef.value.formModel as CombinationActivityApi.CombinationActivityVO const data = cloneDeep(formRef.value.formModel) as CombinationActivityApi.CombinationActivityVO
data.products = products data.products = products
// 真正提交 // 真正提交
if (formType.value === 'create') { if (formType.value === 'create') {
......
...@@ -9,7 +9,8 @@ export const rules = reactive({ ...@@ -9,7 +9,8 @@ export const rules = reactive({
startTime: [required], startTime: [required],
endTime: [required], endTime: [required],
userSize: [required], userSize: [required],
limitDuration: [required] limitDuration: [required],
virtualGroup: [required]
}) })
// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/ // CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
...@@ -115,30 +116,15 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -115,30 +116,15 @@ const crudSchemas = reactive<CrudSchema[]>([
} }
}, },
{ {
label: '购买人数', label: '虚拟成团',
field: 'userSize', field: 'virtualGroup',
isSearch: false, dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,
isForm: false dictClass: 'boolean',
},
{
label: '开团组数',
field: 'totalCount',
isSearch: false,
isForm: false
},
{
label: '成团组数',
field: 'successCount',
isSearch: false,
isForm: false
},
{
label: '活动状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isSearch: true, isSearch: true,
isForm: false form: {
component: 'Radio',
value: false
}
}, },
{ {
label: '拼团商品', label: '拼团商品',
...@@ -149,11 +135,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -149,11 +135,6 @@ const crudSchemas = reactive<CrudSchema[]>([
span: 24 span: 24
} }
} }
},
{
label: '操作',
field: 'action',
isForm: false
} }
]) ])
export const { allSchemas } = useCrudSchemas(crudSchemas) export const { allSchemas } = useCrudSchemas(crudSchemas)
<template> <template>
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
<!-- 搜索工作栏 -->
<ContentWrap> <ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams"> <!-- 搜索工作栏 -->
<!-- 新增等操作按钮 --> <el-form
<template #actionMore> class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="活动名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入活动名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择活动状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button <el-button
v-hasPermi="['promotion:combination-activity:create']"
plain
type="primary" type="primary"
plain
@click="openForm('create')" @click="openForm('create')"
v-hasPermi="['promotion:combination-activity:create']"
> >
<Icon class="mr-5px" icon="ep:plus" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
</template> </el-form-item>
</Search> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<Table <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
v-model:currentPage="tableObject.currentPage" <el-table-column label="活动编号" prop="id" min-width="80" />
v-model:pageSize="tableObject.pageSize" <el-table-column label="活动名称" prop="name" min-width="140" />
:columns="allSchemas.tableColumns" <el-table-column label="活动时间" min-width="210">
:data="tableObject.tableList" <template #default="scope">
:loading="tableObject.loading" {{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
:pagination="{ ~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
total: tableObject.total </template>
}" </el-table-column>
> <el-table-column label="商品图片" prop="spuName" min-width="80">
<template #spuId="{ row }"> <template #default="scope">
<el-image <el-image
:src="row.picUrl" :src="scope.row.picUrl"
class="mr-5px h-30px w-30px align-middle" class="h-40px w-40px"
@click="imagePreview(row.picUrl)" :preview-src-list="[scope.row.picUrl]"
/> preview-teleported
<span class="align-middle">{{ row.spuName }}</span> />
</template> </template>
<template #action="{ row }"> </el-table-column>
<el-button <el-table-column label="商品标题" prop="spuName" min-width="300" />
v-hasPermi="['promotion:combination-activity:update']" <el-table-column
link label="原价"
type="primary" prop="marketPrice"
@click="openForm('update', row.id)" min-width="100"
> :formatter="fenToYuanFormat"
编辑 />
</el-button> <el-table-column label="拼团价" prop="seckillPrice" min-width="100">
<el-button <template #default="scope">
v-hasPermi="['promotion:combination-activity:delete']" {{ formatCombinationPrice(scope.row.products) }}
link </template>
type="danger" </el-table-column>
@click="handleDelete(row.id)" <el-table-column label="开团组数" prop="groupCount" min-width="100" />
> <el-table-column label="成团组数" prop="groupSuccessCount" min-width="100" />
删除 <el-table-column label="购买次数" prop="recordCount" min-width="100" />
</el-button> <el-table-column label="活动状态" align="center" prop="status" min-width="100">
</template> <template #default="scope">
</Table> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['promotion:combination-activity:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleClose(scope.row.id)"
v-if="scope.row.status === 0"
v-hasPermi="['promotion:combination-activity:close']"
>
关闭
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-else
v-hasPermi="['promotion:combination-activity:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<CombinationActivityForm ref="formRef" @success="getList" /> <CombinationActivityForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" setup>
import { allSchemas } from './combinationActivity.data' <script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity' import * as CombinationActivityApi from '@/api/mall/promotion/combination/combinationActivity'
import CombinationActivityForm from './CombinationActivityForm.vue' import CombinationActivityForm from './CombinationActivityForm.vue'
import { sortTableColumns } from '@/hooks/web/useCrudSchemas' import { formatDate } from '@/utils/formatTime'
import { createImageViewer } from '@/components/ImageViewer' import { fenToYuanFormat } from '@/utils/formatter'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'PromotionBargainActivity' })
defineOptions({ name: 'PromotionCombinationActivity' }) const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
// tableObject:表格的属性对象,可获得分页大小、条数等属性 const loading = ref(true) // 列表的加载中
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 const total = ref(0) // 列表的总页数
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/ const list = ref([]) // 列表的数据
const { tableObject, tableMethods } = useTable({ const queryParams = reactive({
getListApi: CombinationActivityApi.getCombinationActivityPage, // 分页接口 pageNo: 1,
delListApi: CombinationActivityApi.deleteCombinationActivity // 删除接口 pageSize: 10,
name: null,
status: null
}) })
// 获得表格的各种操作 const queryFormRef = ref() // 搜索的表单
const { getList, setSearchParams } = tableMethods const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CombinationActivityApi.getCombinationActivityPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 商品图预览 */ /** 重置按钮操作 */
const imagePreview = (imgUrl: string) => { const resetQuery = () => {
createImageViewer({ queryFormRef.value.resetFields()
urlList: [imgUrl] handleQuery()
})
} }
/** 添加/修改操作 */ /** 添加/修改操作 */
...@@ -94,15 +195,40 @@ const openForm = (type: string, id?: number) => { ...@@ -94,15 +195,40 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
// TODO 芋艿:这里要改下
/** 关闭按钮操作 */
const handleClose = async (id: number) => {
try {
// 关闭的二次确认
await message.confirm('确认关闭该秒杀活动吗?')
// 发起关闭
await CombinationActivityApi.closeCombinationActivity(id)
message.success('关闭成功')
// 刷新列表
await getList()
} catch {}
}
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = (id: number) => { const handleDelete = async (id: number) => {
tableMethods.delList(id, false) try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await CombinationActivityApi.deleteCombinationActivity(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
const formatCombinationPrice = (products) => {
const combinationPrice = Math.min(...products.map((item) => item.combinationPrice))
return `¥${fenToYuan(combinationPrice)}`
} }
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(async () => {
// 获得活动列表 await getList()
sortTableColumns(allSchemas.tableColumns, 'spuId')
getList()
}) })
</script> </script>
<template>
<Dialog v-model="dialogVisible" title="拼团列表" width="950">
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list">
<el-table-column align="center" label="编号" prop="id" min-width="50" />
<el-table-column align="center" label="头像" prop="avatar" min-width="80">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column align="center" label="昵称" prop="nickname" min-width="100" />
<el-table-column align="center" label="开团团长" prop="headId" min-width="100">
<template #default="{ row }: { row: CombinationRecordApi.CombinationRecordVO }">
<el-tag> {{ row.headId === 0 ? '团长' : '团员' }} </el-tag>
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="参团时间"
prop="createTime"
width="180"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="结束时间"
prop="endTime"
width="180"
/>
<el-table-column align="center" label="拼团状态" prop="status" min-width="150">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.PROMOTION_COMBINATION_RECORD_STATUS"
:value="scope.row.status"
/>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</Dialog>
</template>
<script lang="ts" setup>
import { dateFormatter } from '@/utils/formatTime'
import * as CombinationRecordApi from '@/api/mall/promotion/combination/combinationRecord'
import { DICT_TYPE } from '@/utils/dict'
import { createImageViewer } from '@/components/ImageViewer'
/** 助力列表 */
defineOptions({ name: 'CombinationRecordListDialog' })
const message = useMessage() // 消息弹窗
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
headId: undefined
})
/** 打开弹窗 */
const dialogVisible = ref(false) // 弹窗的是否展示
const open = async (headId: any) => {
dialogVisible.value = true
queryParams.headId = headId
await getList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CombinationRecordApi.getCombinationRecordPageByHeadId(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
</script>
<template> <template>
<div></div> <!-- 统计信息展示 -->
<el-row :gutter="12">
<el-col :span="6">
<ContentWrap class="h-[110px] pb-0!">
<div class="flex items-center">
<div
class="h-[50px] w-[50px] flex items-center justify-center"
style="color: rgb(24, 144, 255); background-color: rgba(24, 144, 255, 0.1)"
>
<Icon :size="23" icon="fa:user-times" />
</div>
<div class="ml-[20px]">
<div class="mb-8px text-14px text-gray-400">参与人数(个)</div>
<CountTo
:duration="2600"
:end-val="recordSummary.userCount"
:start-val="0"
class="text-20px"
/>
</div>
</div>
</ContentWrap>
</el-col>
<el-col :span="6">
<ContentWrap class="h-[110px]">
<div class="flex items-center">
<div
class="h-[50px] w-[50px] flex items-center justify-center"
style="color: rgb(162, 119, 255); background-color: rgba(162, 119, 255, 0.1)"
>
<Icon :size="23" icon="fa:user-plus" />
</div>
<div class="ml-[20px]">
<div class="mb-8px text-14px text-gray-400">成团数量(个)</div>
<CountTo
:duration="2600"
:end-val="recordSummary.successCount"
:start-val="0"
class="text-20px"
/>
</div>
</div>
</ContentWrap>
</el-col>
<el-col :span="6">
<ContentWrap class="h-[110px]">
<div class="flex items-center">
<div
class="h-[50px] w-[50px] flex items-center justify-center"
style="color: rgb(162, 119, 255); background-color: rgba(162, 119, 255, 0.1)"
>
<Icon :size="23" icon="fa:user-plus" />
</div>
<div class="ml-[20px]">
<div class="mb-8px text-14px text-gray-400">虚拟成团(个)</div>
<CountTo
:duration="2600"
:end-val="recordSummary.virtualGroupCount"
:start-val="0"
class="text-20px"
/>
</div>
</div>
</ContentWrap>
</el-col>
</el-row>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
:shortcuts="defaultShortcuts"
class="!w-240px"
end-placeholder="结束日期"
start-placeholder="开始日期"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item label="拼团状态" prop="status">
<el-select v-model="queryParams.status" class="!w-240px" clearable placeholder="全部">
<el-option
v-for="(dict, index) in getIntDictOptions(
DICT_TYPE.PROMOTION_COMBINATION_RECORD_STATUS
)"
:key="index"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery">
<Icon class="mr-5px" icon="ep:search" />
搜索
</el-button>
<el-button @click="resetQuery">
<Icon class="mr-5px" icon="ep:refresh" />
重置
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 分页列表数据展示 -->
<ContentWrap>
<el-table v-loading="loading" :data="pageList">
<el-table-column align="center" label="编号" prop="id" min-width="50" />
<el-table-column align="center" label="头像" prop="avatar" min-width="80">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column align="center" label="昵称" prop="nickname" min-width="100" />
<el-table-column align="center" label="开团团长" prop="headId" min-width="100">
<template #default="{ row }: { row: CombinationRecordApi.CombinationRecordVO }">
{{
row.headId ? pageList.find((item) => item.id === row.headId)?.nickname : row.nickname
}}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="开团时间"
prop="startTime"
width="180"
/>
<el-table-column
align="center"
label="拼团商品"
prop="type"
show-overflow-tooltip
min-width="300"
>
<template #defaul="{ row }">
<el-image
:src="row.picUrl"
class="mr-5px h-30px w-30px align-middle"
@click="imagePreview(row.picUrl)"
/>
<span class="align-middle">{{ row.spuName }}</span>
</template>
</el-table-column>
<el-table-column align="center" label="几人团" prop="userSize" min-width="100" />
<el-table-column align="center" label="参与人数" prop="userCount" min-width="100" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="参团时间"
prop="createTime"
width="180"
/>
<el-table-column
:formatter="dateFormatter"
align="center"
label="结束时间"
prop="endTime"
width="180"
/>
<el-table-column align="center" label="拼团状态" prop="status" min-width="150">
<template #default="scope">
<dict-tag
:type="DICT_TYPE.PROMOTION_COMBINATION_RECORD_STATUS"
:value="scope.row.status"
/>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作">
<template #default="scope">
<el-button
v-hasPermi="['promotion:combination-record:query']"
link
type="primary"
@click="openRecordListDialog(scope.row)"
>
查看拼团
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗 -->
<CombinationRecordListDialog ref="combinationRecordListRef" />
</template> </template>
<script lang="ts" setup>
import CombinationRecordListDialog from './CombinationRecordListDialog.vue'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter, defaultShortcuts } from '@/utils/formatTime'
import { createImageViewer } from '@/components/ImageViewer'
import * as CombinationRecordApi from '@/api/mall/promotion/combination/combinationRecord'
defineOptions({ name: 'PromotionCombinationRecord' })
const queryParams = ref({
status: undefined, // 拼团状态
createTime: undefined, // 创建时间
pageSize: 10,
pageNo: 1
})
const queryFormRef = ref() // 搜索的表单
const combinationRecordListRef = ref() // 查询表单 Ref
const loading = ref(true) // 列表的加载中
const total = ref(0) // 总记录数
const pageList = ref<CombinationRecordApi.CombinationRecordVO[]>([]) // 分页数据
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await CombinationRecordApi.getCombinationRecordPage(queryParams.value)
pageList.value = data.list as CombinationRecordApi.CombinationRecordVO[]
total.value = data.total
} finally {
loading.value = false
}
}
// 拼团统计数据
const recordSummary = ref({
successCount: 0,
userCount: 0,
virtualGroupCount: 0
})
/** 获得拼团记录统计信息 */
const getSummary = async () => {
recordSummary.value = await CombinationRecordApi.getCombinationRecordSummary()
}
const openRecordListDialog = (row: CombinationRecordApi.CombinationRecordVO) => {
combinationRecordListRef.value?.open(row.headId)
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 商品图预览 */
const imagePreview = (imgUrl: string) => {
createImageViewer({
urlList: [imgUrl]
})
}
<script lang="ts" name="CombinationRecord" setup></script> /** 初始化 **/
onMounted(async () => {
await getSummary()
await getList()
})
</script>
...@@ -9,7 +9,7 @@ export const discountFormat = (row: CouponTemplateVO) => { ...@@ -9,7 +9,7 @@ export const discountFormat = (row: CouponTemplateVO) => {
return `¥${floatToFixed2(row.discountPrice)}` return `¥${floatToFixed2(row.discountPrice)}`
} }
if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) { if (row.discountType === PromotionDiscountTypeEnum.PERCENT.type) {
return `${row.discountPrice}%` return `${row.discountPercent}%`
} }
return '未知【' + row.discountType + '】' return '未知【' + row.discountType + '】'
} }
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
@keyup="handleQuery" @keyup="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="创建时间" prop="createTime"> <el-form-item label="领取时间" prop="createTime">
<el-date-picker <el-date-picker
v-model="queryParams.createTime" v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss"
...@@ -50,12 +50,17 @@ ...@@ -50,12 +50,17 @@
<!-- 列表 --> <!-- 列表 -->
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="会员信息" align="center" prop="nickname" /> <el-table-column label="会员昵称" align="center" min-width="100" prop="nickname" />
<!-- TODO 芋艿:以后支持头像,支持跳转 --> <el-table-column label="优惠券名称" align="center" min-width="140" prop="name" />
<el-table-column label="优惠劵" align="center" prop="name" /> <el-table-column label="类型" align="center" prop="discountType">
<el-table-column label="优惠券类型" align="center" prop="discountType"> <template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE" :value="scope.row.productScope" />
</template>
</el-table-column>
<el-table-column label="优惠" min-width="100" prop="discount">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" /> <dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
{{ discountFormat(scope.row) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="领取方式" align="center" prop="takeType"> <el-table-column label="领取方式" align="center" prop="takeType">
...@@ -109,6 +114,7 @@ ...@@ -109,6 +114,7 @@
import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon/coupon' import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon/coupon'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { discountFormat } from '@/views/mall/promotion/coupon/formatter'
defineOptions({ name: 'PromotionCoupon' }) defineOptions({ name: 'PromotionCoupon' })
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
@keyup="handleQuery" @keyup="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="优惠类型" prop="discountType"> <el-form-item label="优惠类型" prop="discountType">
<el-select <el-select
v-model="queryParams.discountType" v-model="queryParams.discountType"
class="!w-240px" class="!w-240px"
...@@ -71,14 +71,6 @@ ...@@ -71,14 +71,6 @@
> >
<Icon class="mr-5px" icon="ep:plus" /> 新增 <Icon class="mr-5px" icon="ep:plus" /> 新增
</el-button> </el-button>
<el-button
plain
type="success"
@click="$router.push('/promotion/coupon')"
v-hasPermi="['promotion:coupon:query']"
>
<Icon icon="ep:operation" class="mr-5px" />会员优惠劵
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
...@@ -86,17 +78,29 @@ ...@@ -86,17 +78,29 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="优惠券名称" align="center" prop="name" /> <el-table-column label="优惠券名称" min-width="140" prop="name" />
<el-table-column label="优惠券类型" align="center" prop="discountType"> <el-table-column label="类型" min-width="80" prop="productScope">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_PRODUCT_SCOPE" :value="scope.row.productScope" />
</template>
</el-table-column>
<el-table-column label="优惠" min-width="100" prop="discount">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" /> <dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
{{ discountFormat(scope.row) }}
</template>
</el-table-column>
<el-table-column label="领取方式" min-width="100" prop="takeType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE" :value="scope.row.takeType" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
label="优惠金额 / 折扣" label="使用时间"
align="center" align="center"
prop="discount" prop="validityType"
:formatter="discountFormat" width="185"
:formatter="validityTypeFormat"
/> />
<el-table-column label="发放数量" align="center" prop="totalCount" /> <el-table-column label="发放数量" align="center" prop="totalCount" />
<el-table-column <el-table-column
...@@ -111,13 +115,6 @@ ...@@ -111,13 +115,6 @@
prop="takeLimitCount" prop="takeLimitCount"
:formatter="takeLimitCountFormat" :formatter="takeLimitCountFormat"
/> />
<el-table-column
label="有效期限"
align="center"
prop="validityType"
width="190"
:formatter="validityTypeFormat"
/>
<el-table-column label="状态" align="center" prop="status"> <el-table-column label="状态" align="center" prop="status">
<template #default="scope"> <template #default="scope">
<el-switch <el-switch
......
<template> <template>
<doc-alert title="功能开启" url="https://doc.iocoder.cn/mall/build/" />
<!-- 搜索工作栏 -->
<ContentWrap> <ContentWrap>
<Search :schema="allSchemas.searchSchema" @reset="setSearchParams" @search="setSearchParams"> <!-- 搜索工作栏 -->
<!-- 新增等操作按钮 --> <el-form
<template #actionMore> class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="活动名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入活动名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="活动状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择活动状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button <el-button
v-hasPermi="['promotion:seckill-activity:create']"
plain
type="primary" type="primary"
plain
@click="openForm('create')" @click="openForm('create')"
v-hasPermi="['promotion:seckill-activity:create']"
> >
<Icon class="mr-5px" icon="ep:plus" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
</template> </el-form-item>
</Search> </el-form>
</ContentWrap> </ContentWrap>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<Table <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
v-model:currentPage="tableObject.currentPage" <el-table-column label="活动编号" prop="id" min-width="80" />
v-model:pageSize="tableObject.pageSize" <el-table-column label="活动名称" prop="name" min-width="140" />
:columns="allSchemas.tableColumns" <el-table-column
:data="tableObject.tableList" label="秒杀时段"
:expand="true" prop="configIds"
:loading="tableObject.loading" width="220px"
:pagination="{ :show-overflow-tooltip="false"
total: tableObject.total >
}" <template #default="scope">
@expand-change="expandChange" <el-tag v-for="(configId, index) in scope.row.configIds" :key="index" class="mr-5px">
> {{ formatConfigNames(configId) }}
<template #expand> 展示活动商品和商品相关属性活动配置</template> </el-tag>
<template #spuId="{ row }"> </template>
<el-image </el-table-column>
:src="row.picUrl" <el-table-column label="活动时间" min-width="210">
class="mr-5px h-30px w-30px align-middle" <template #default="scope">
@click="imagePreview(row.picUrl)" {{ formatDate(scope.row.startTime, 'YYYY-MM-DD') }}
/> ~ {{ formatDate(scope.row.endTime, 'YYYY-MM-DD') }}
<span class="align-middle">{{ row.spuName }}</span> </template>
</template> </el-table-column>
<template #configIds="{ row }"> <el-table-column label="商品图片" prop="spuName" min-width="80">
<el-tag v-for="(name, index) in convertSeckillConfigNames(row)" :key="index" class="mr-5px"> <template #default="scope">
{{ name }} <el-image
</el-tag> :src="scope.row.picUrl"
</template> class="h-40px w-40px"
<template #action="{ row }"> :preview-src-list="[scope.row.picUrl]"
<el-button preview-teleported
v-hasPermi="['promotion:seckill-activity:update']" />
link </template>
type="primary" </el-table-column>
@click="openForm('update', row.id)" <el-table-column label="商品标题" prop="spuName" min-width="300" />
> <el-table-column
编辑 label="原价"
</el-button> prop="marketPrice"
<el-button min-width="100"
v-hasPermi="['promotion:seckill-activity:delete']" :formatter="fenToYuanFormat"
link />
type="danger" <el-table-column label="原价" prop="marketPrice" min-width="100" />
@click="handleDelete(row.id)" <el-table-column label="秒杀价" prop="seckillPrice" min-width="100">
> <template #default="scope">
删除 {{ formatSeckillPrice(scope.row.products) }}
</el-button> </template>
</template> </el-table-column>
</Table> <el-table-column label="活动状态" align="center" prop="status" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="库存" align="center" prop="stock" min-width="80" />
<el-table-column label="总库存" align="center" prop="totalStock" min-width="80" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['promotion:seckill-activity:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleClose(scope.row.id)"
v-if="scope.row.status === 0"
v-hasPermi="['promotion:seckill-activity:close']"
>
关闭
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-else
v-hasPermi="['promotion:seckill-activity:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap> </ContentWrap>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<SeckillActivityForm ref="formRef" @success="getList" /> <SeckillActivityForm ref="formRef" @success="getList" />
</template> </template>
<script lang="ts" setup>
import { allSchemas } from './seckillActivity.data' <script setup lang="ts">
import { getSimpleSeckillConfigList } from '@/api/mall/promotion/seckill/seckillConfig' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity' import * as SeckillActivityApi from '@/api/mall/promotion/seckill/seckillActivity'
import * as SeckillConfigApi from '@/api/mall/promotion/seckill/seckillConfig'
import SeckillActivityForm from './SeckillActivityForm.vue' import SeckillActivityForm from './SeckillActivityForm.vue'
import { createImageViewer } from '@/components/ImageViewer' import { formatDate } from '@/utils/formatTime'
import { sortTableColumns } from '@/hooks/web/useCrudSchemas' import { fenToYuanFormat } from '@/utils/formatter'
import { fenToYuan } from '@/utils'
defineOptions({ name: 'PromotionSeckillActivity' }) defineOptions({ name: 'SeckillActivity' })
// tableObject:表格的属性对象,可获得分页大小、条数等属性 const message = useMessage() // 消息弹窗
// tableMethods:表格的操作对象,可进行获得分页、删除记录等操作 const { t } = useI18n() // 国际化
// 详细可见:https://doc.iocoder.cn/vue3/crud-schema/
const { tableObject, tableMethods } = useTable({ const loading = ref(true) // 列表的加载中
getListApi: SeckillActivityApi.getSeckillActivityPage, // 分页接口 const total = ref(0) // 列表的总页数
delListApi: SeckillActivityApi.deleteSeckillActivity // 删除接口 const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: null,
status: null
}) })
// 获得表格的各种操作 const queryFormRef = ref() // 搜索的表单
const { getList, setSearchParams } = tableMethods const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await SeckillActivityApi.getSeckillActivityPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
...@@ -96,37 +208,47 @@ const openForm = (type: string, id?: number) => { ...@@ -96,37 +208,47 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
/** 删除按钮操作 */ /** 关闭按钮操作 */
const handleDelete = (id: number) => { const handleClose = async (id: number) => {
tableMethods.delList(id, false) try {
// 关闭的二次确认
await message.confirm('确认关闭该秒杀活动吗?')
// 发起关闭
await SeckillActivityApi.closeSeckillActivity(id)
message.success('关闭成功')
// 刷新列表
await getList()
} catch {}
} }
/** 商品图预览 */ /** 删除按钮操作 */
const imagePreview = (imgUrl: string) => { const handleDelete = async (id: number) => {
createImageViewer({ try {
urlList: [imgUrl] // 删除的二次确认
}) await message.delConfirm()
// 发起删除
await SeckillActivityApi.deleteSeckillActivity(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
} }
const configList = ref([]) // 时段配置精简列表 const configList = ref([]) // 时段配置精简列表
const convertSeckillConfigNames = computed( const formatConfigNames = (configId) => {
() => (row) => const config = configList.value.find((item) => item.id === configId)
configList.value return config != null ? `${config.name}[${config.startTime} ~ ${config.endTime}]` : ''
?.filter((item) => row.configIds.includes(item.id)) }
?.map((config) => config.name)
)
const expandChange = (row, expandedRows) => { const formatSeckillPrice = (products) => {
// TODO puhui:等 CRUD 完事后弄 const seckillPrice = Math.min(...products.map((item) => item.seckillPrice))
console.log(row, expandedRows) return `¥${fenToYuan(seckillPrice)}`
} }
/** 初始化 **/ /** 初始化 **/
onMounted(async () => { onMounted(async () => {
// 获得活动列表
sortTableColumns(allSchemas.tableColumns, 'spuId')
await getList() await getList()
// 获得秒杀时间段 // 获得秒杀时间段
configList.value = await getSimpleSeckillConfigList() configList.value = await SeckillConfigApi.getSimpleSeckillConfigList()
}) })
</script> </script>
...@@ -95,42 +95,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -95,42 +95,6 @@ const crudSchemas = reactive<CrudSchema[]>([
} }
}, },
{ {
label: '新增订单数',
field: 'orderCount',
isForm: false,
form: {
component: 'InputNumber',
value: 0
},
table: {
width: 120
}
},
{
label: '付款人数',
field: 'userCount',
isForm: false,
form: {
component: 'InputNumber',
value: 0
},
table: {
width: 120
}
},
{
label: '订单实付金额',
field: 'totalPrice',
isForm: false,
form: {
component: 'InputNumber',
value: 0
},
table: {
width: 120
}
},
{
label: '总限购数量', label: '总限购数量',
field: 'totalLimitCount', field: 'totalLimitCount',
form: { form: {
...@@ -164,26 +128,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -164,26 +128,6 @@ const crudSchemas = reactive<CrudSchema[]>([
} }
}, },
{ {
label: '秒杀库存',
field: 'stock',
isForm: false,
form: {
component: 'InputNumber',
value: 0
},
table: {
width: 120
}
},
{
label: '秒杀总库存',
field: 'totalStock',
isForm: false,
table: {
width: 120
}
},
{
label: '秒杀活动商品', label: '秒杀活动商品',
field: 'spuId', field: 'spuId',
isTable: true, isTable: true,
...@@ -198,37 +142,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -198,37 +142,6 @@ const crudSchemas = reactive<CrudSchema[]>([
} }
}, },
{ {
label: '创建时间',
field: 'createTime',
formatter: dateFormatter,
search: {
component: 'DatePicker',
componentProps: {
valueFormat: 'YYYY-MM-DD HH:mm:ss',
type: 'daterange',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]
}
},
isForm: false,
table: {
width: 120
}
},
{
label: '状态',
field: 'status',
dictType: DICT_TYPE.COMMON_STATUS,
dictClass: 'number',
isForm: false,
isSearch: true,
form: {
component: 'Radio'
},
table: {
width: 80
}
},
{
label: '备注', label: '备注',
field: 'remark', field: 'remark',
isSearch: false, isSearch: false,
...@@ -245,15 +158,6 @@ const crudSchemas = reactive<CrudSchema[]>([ ...@@ -245,15 +158,6 @@ const crudSchemas = reactive<CrudSchema[]>([
table: { table: {
width: 300 width: 300
} }
},
{
label: '操作',
field: 'action',
isForm: false,
table: {
width: 120,
fixed: 'right'
}
} }
]) ])
export const { allSchemas } = useCrudSchemas(crudSchemas) export const { allSchemas } = useCrudSchemas(crudSchemas)
<template>
<div class="flex flex-col">
<el-row :gutter="16" class="summary">
<el-col :sm="6" :xs="12" v-loading="loading">
<TradeTrendValue
title="累计会员数"
icon="fa-solid:users"
icon-color="bg-blue-100"
icon-bg-color="text-blue-500"
:value="summary?.userCount || 0"
/>
</el-col>
<el-col :sm="6" :xs="12" v-loading="loading">
<TradeTrendValue
title="累计充值人数"
icon="fa-solid:user"
icon-color="bg-purple-100"
icon-bg-color="text-purple-500"
:value="summary?.rechargeUserCount || 0"
/>
</el-col>
<el-col :sm="6" :xs="12" v-loading="loading">
<TradeTrendValue
title="累计充值金额"
icon="fa-solid:money-check-alt"
icon-color="bg-yellow-100"
icon-bg-color="text-yellow-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(summary?.rechargePrice || 0)"
/>
</el-col>
<el-col :sm="6" :xs="12" v-loading="loading">
<TradeTrendValue
title="累计消费金额"
icon="fa-solid:yen-sign"
icon-color="bg-green-100"
icon-bg-color="text-green-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(summary?.expensePrice || 0)"
/>
</el-col>
</el-row>
<el-row :gutter="16" class="mb-4">
<el-col :md="18" :sm="24">
<el-card shadow="never">
<template #header>
<div class="flex flex-row items-center justify-between">
<span>会员概览</span>
<!-- 查询条件 -->
<div class="my--2 flex flex-row items-center gap-2">
<el-radio-group v-model="shortcutDays" @change="handleDateTypeChange">
<el-radio-button :label="1">昨天</el-radio-button>
<el-radio-button :label="7">最近7天</el-radio-button>
<el-radio-button :label="30">最近30天</el-radio-button>
</el-radio-group>
<el-date-picker
v-model="queryParams.times"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:shortcuts="shortcuts"
class="!w-240px"
@change="getMemberAnalyse"
/>
</div>
</div>
</template>
<div class="min-w-225 py-1.75" v-loading="analyseLoading">
<div class="relative h-24 flex">
<div class="h-full w-75% bg-blue-50 <lg:w-35% <xl:w-55%">
<div class="ml-15 h-full flex flex-col justify-center">
<div class="font-bold">
注册用户数量:{{ analyseData?.comparison?.value?.userCount || 0 }}
</div>
<div class="mt-2 text-3.5">
环比增长率:{{
calculateRelativeRate(
analyseData?.comparison?.value?.userCount,
analyseData?.comparison?.reference?.userCount
)
}}%
</div>
</div>
</div>
<div
class="trapezoid1 ml--38.5 mt-1.5 h-full w-77 flex flex-col items-center justify-center bg-blue-5 text-3.5 text-white"
>
<span class="text-6 font-bold">{{ analyseData?.visitorCount || 0 }}</span>
<span>访客</span>
</div>
</div>
<div class="relative h-24 flex">
<div class="h-full w-75% flex bg-cyan-50 <lg:w-35% <xl:w-55%">
<div class="ml-15 h-full flex flex-col justify-center">
<div class="font-bold">
活跃用户数量:{{ analyseData?.comparison?.value?.activeUserCount || 0 }}
</div>
<div class="mt-2 text-3.5">
环比增长率:{{
calculateRelativeRate(
analyseData?.comparison?.value?.activeUserCount,
analyseData?.comparison?.reference?.activeUserCount
)
}}%
</div>
</div>
</div>
<div
class="trapezoid2 ml--28 mt-1.7 h-25 w-56 flex flex-col items-center justify-center bg-cyan-5 text-3.5 text-white"
>
<span class="text-6 font-bold">{{ analyseData?.orderUserCount || 0 }}</span>
<span>下单</span>
</div>
</div>
<div class="relative h-24 flex">
<div class="w-75% flex bg-slate-50 <lg:w-35% <xl:w-55%">
<div class="ml-15 h-full flex flex-row gap-x-16">
<div class="flex flex-col justify-center">
<div class="font-bold">
充值用户数量:{{ analyseData?.comparison?.value?.rechargeUserCount || 0 }}
</div>
<div class="mt-2 text-3.5">
环比增长率:{{
calculateRelativeRate(
analyseData?.comparison?.value?.rechargeUserCount,
analyseData?.comparison?.reference?.rechargeUserCount
)
}}%
</div>
</div>
<div class="flex flex-col justify-center">
<div class="font-bold">客单价:{{ fenToYuan(analyseData?.atv || 0) }}</div>
</div>
</div>
</div>
<div
class="trapezoid3 ml--18 mt-3.25 h-23 w-36 flex flex-col items-center justify-center bg-slate-5 text-3.5 text-white"
>
<span class="text-6 font-bold">{{ analyseData?.payUserCount || 0 }}</span>
<span>成交用户</span>
</div>
</div>
</div>
</el-card>
</el-col>
<el-col :md="6" :sm="24">
<el-card shadow="never" header="会员终端" v-loading="loading">
<Echart :height="300" :options="terminalChartOptions" />
</el-card>
</el-col>
</el-row>
<el-row :gutter="16">
<el-col :md="18" :sm="24">
<el-card shadow="never" header="会员地域分布">
<el-row v-loading="loading">
<el-col :span="10">
<Echart :height="300" :options="areaChartOptions" />
</el-col>
<el-col :span="14">
<el-table :data="areaStatisticsList" :height="300">
<el-table-column
label="省份"
prop="areaName"
align="center"
min-width="80"
show-overflow-tooltip
sortable
:sort-method="(obj1, obj2) => obj1.areaName.localeCompare(obj2.areaName, 'zh-CN')"
/>
<el-table-column
label="会员数量"
prop="userCount"
align="center"
min-width="105"
sortable
/>
<el-table-column
label="订单创建数量"
prop="orderCreateCount"
align="center"
min-width="135"
sortable
/>
<el-table-column
label="订单支付数量"
prop="orderPayCount"
align="center"
min-width="135"
sortable
/>
<el-table-column
label="订单支付金额"
prop="orderPayPrice"
align="center"
min-width="135"
sortable
:formatter="fenToYuanFormat"
/>
</el-table>
</el-col>
</el-row>
</el-card>
</el-col>
<el-col :md="6" :sm="24">
<el-card shadow="never" header="会员性别比例" v-loading="loading">
<Echart :height="300" :options="sexChartOptions" />
</el-card>
</el-col>
</el-row>
</div>
</template>
<script lang="ts" setup>
import * as TradeMemberApi from '@/api/mall/statistics/member'
import TradeTrendValue from '../trade/components/TradeTrendValue.vue'
import { EChartsOption } from 'echarts'
import china from '@/assets/map/json/china.json'
import dayjs from 'dayjs'
import { fenToYuan } from '@/utils'
import * as DateUtil from '@/utils/formatTime'
import {
MemberAnalyseRespVO,
MemberAreaStatisticsRespVO,
MemberSexStatisticsRespVO,
MemberAnalyseReqVO,
MemberSummaryRespVO,
MemberTerminalStatisticsRespVO
} from '@/api/mall/statistics/member'
import { DICT_TYPE, DictDataType, getIntDictOptions } from '@/utils/dict'
import echarts from '@/plugins/echarts'
import { fenToYuanFormat } from '@/utils/formatter'
/** 会员统计 */
defineOptions({ name: 'MemberStatistics' })
const loading = ref(true) // 加载中
const analyseLoading = ref(true) // 会员概览加载中
const queryParams = reactive<MemberAnalyseReqVO>({ times: ['', ''] }) // 会员概览查询参数
const shortcutDays = ref(7) // 日期快捷天数(单选按钮组), 默认7天
const summary = ref<MemberSummaryRespVO>() // 会员统计数据
const analyseData = ref<MemberAnalyseRespVO>() // 会员分析数据
const areaStatisticsList = shallowRef<MemberAreaStatisticsRespVO[]>() // 省份会员统计
// 注册地图
echarts?.registerMap('china', china!)
/** 日期快捷选择 */
const shortcuts = [
{
text: '昨天',
value: () => DateUtil.getDayRange(new Date(), -1)
},
{
text: '最近7天',
value: () => DateUtil.getLast7Days()
},
{
text: '本月',
value: () => [dayjs().startOf('M'), dayjs().subtract(1, 'd')]
},
{
text: '最近30天',
value: () => DateUtil.getLast30Days()
},
{
text: '最近1年',
value: () => DateUtil.getLast1Year()
}
]
/** 会员终端统计图配置 */
const terminalChartOptions = reactive<EChartsOption>({
tooltip: {
trigger: 'item',
confine: true,
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'right'
},
roseType: 'area',
series: [
{
name: '会员终端',
type: 'pie',
label: {
show: false
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
/** 会员性别统计图配置 */
const sexChartOptions = reactive<EChartsOption>({
tooltip: {
trigger: 'item',
confine: true,
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'right'
},
roseType: 'area',
series: [
{
name: '会员性别',
type: 'pie',
label: {
show: false
},
labelLine: {
show: false
},
data: []
}
]
}) as EChartsOption
const areaChartOptions = reactive<EChartsOption>({
tooltip: {
trigger: 'item',
formatter: (params: any) => {
return `${params?.data?.areaName || params?.name}<br/>
会员数量:${params?.data?.userCount || 0}<br/>
订单创建数量:${params?.data?.orderCreateCount || 0}<br/>
订单支付数量:${params?.data?.orderPayCount || 0}<br/>
订单支付金额:${fenToYuan(params?.data?.orderPayPrice || 0)}`
}
},
visualMap: {
text: ['高', '低'],
realtime: false,
calculable: true,
top: 'middle',
inRange: {
color: ['#fff', '#3b82f6']
}
},
series: [
{
name: '会员地域分布',
type: 'map',
map: 'china',
roam: false,
selectedMode: false,
data: []
}
]
}) as EChartsOption
/** 计算环比 */
const calculateRelativeRate = (value?: number, reference?: number) => {
// 防止除0
if (!reference) return 0
return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
}
/** 设置时间范围 */
function setTimes() {
const beginDate = dayjs().subtract(shortcutDays.value, 'd')
const yesterday = dayjs().subtract(1, 'd')
queryParams.times = DateUtil.getDateRange(beginDate, yesterday)
}
/** 处理会员概览查询(日期单选按钮组选择后) */
const handleDateTypeChange = async () => {
// 设置时间范围
setTimes()
// 查询数据
await getMemberAnalyse()
}
/** 查询会员统计 */
const getMemberSummary = async () => {
summary.value = await TradeMemberApi.getMemberSummary()
}
/** 按照省份,查询会员统计列表 */
const getMemberAreaStatisticsList = async () => {
const list = await TradeMemberApi.getMemberAreaStatisticsList()
areaStatisticsList.value = list.map((item: MemberAreaStatisticsRespVO) => {
return {
...item,
areaName: item.areaName
.replace('维吾尔自治区', '')
.replace('壮族自治区', '')
.replace('回族自治区', '')
.replace('自治区', '')
.replace('省', '')
}
})
let min = 0
let max = 0
areaChartOptions.series[0].data = areaStatisticsList.value.map((item) => {
min = Math.min(min, item.orderPayCount)
max = Math.max(max, item.orderPayCount)
return { ...item, name: item.areaName, value: item.orderPayCount || 0 }
})
areaChartOptions.visualMap.min = min
areaChartOptions.visualMap.max = max
}
/** 按照性别,查询会员统计列表 */
const getMemberSexStatisticsList = async () => {
const list = await TradeMemberApi.getMemberSexStatisticsList()
const dictDataList = getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)
sexChartOptions.series[0].data = dictDataList.map((dictData: DictDataType) => {
const userCount = list.find((item: MemberSexStatisticsRespVO) => item.sex === dictData.value)
?.userCount
return {
name: dictData.label,
value: userCount || 0
}
})
}
/** 按照终端,查询会员统计列表 */
const getMemberTerminalStatisticsList = async () => {
const list = await TradeMemberApi.getMemberTerminalStatisticsList()
const dictDataList = getIntDictOptions(DICT_TYPE.TERMINAL)
terminalChartOptions.series![0].data = dictDataList.map((dictData: DictDataType) => {
const userCount = list.find(
(item: MemberTerminalStatisticsRespVO) => item.terminal === dictData.value
)?.userCount
return {
name: dictData.label,
value: userCount || 0
}
})
}
/** 查询会员概览数据列表 */
const getMemberAnalyse = async () => {
analyseLoading.value = true
const times = queryParams.times
// 开始与截止在同一天的, 环比出不来, 需要延长一天
if (DateUtil.isSameDay(times[0], times[1])) {
// 前天
times[0] = DateUtil.formatDate(dayjs(times[0]).subtract(1, 'd'))
}
// 查询数据
analyseData.value = await TradeMemberApi.getMemberAnalyse({ times })
analyseLoading.value = false
}
/** 初始化 **/
onMounted(async () => {
loading.value = true
await Promise.all([
getMemberSummary(),
getMemberTerminalStatisticsList(),
getMemberAreaStatisticsList(),
getMemberSexStatisticsList(),
handleDateTypeChange()
])
loading.value = false
})
</script>
<style lang="scss" scoped>
.summary {
.el-col {
margin-bottom: 1rem;
}
}
.trapezoid1 {
transform: perspective(5em) rotateX(-11deg);
}
.trapezoid2 {
transform: perspective(7em) rotateX(-20deg);
}
.trapezoid3 {
transform: perspective(3em) rotateX(-13deg);
}
</style>
<template>
<div class="flex flex-col gap-2 bg-[var(--el-bg-color-overlay)] p-6">
<div class="flex items-center justify-between text-gray-500">
<span>{{ title }}</span>
<el-tooltip :content="tooltip" placement="top-start" v-if="tooltip">
<Icon icon="ep:warning" />
</el-tooltip>
</div>
<div class="mb-4 text-3xl">
<CountTo :prefix="prefix" :end-val="value" :decimals="decimals" />
</div>
<div class="flex flex-row gap-1 text-sm">
<span class="text-gray-500">环比</span>
<span :class="toNumber(percent) > 0 ? 'text-red-500' : 'text-green-500'">
{{ Math.abs(toNumber(percent)) }}%
<Icon :icon="toNumber(percent) > 0 ? 'ep:caret-top' : 'ep:caret-bottom'" class="!text-sm" />
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { toNumber } from 'lodash-es'
/** 交易统计值组件 */
defineOptions({ name: 'TradeStatisticValue' })
defineProps({
tooltip: propTypes.string.def(''),
title: propTypes.string.def(''),
prefix: propTypes.string.def(''),
value: propTypes.number.def(0),
decimals: propTypes.number.def(0),
percent: propTypes.oneOfType([Number, String]).def(0)
})
</script>
<template>
<div class="flex flex-row items-center gap-3 rounded bg-[var(--el-bg-color-overlay)] p-4">
<div
class="h-12 w-12 flex flex-shrink-0 items-center justify-center rounded-1"
:class="`${iconColor} ${iconBgColor}`"
>
<Icon :icon="icon" class="!text-6" />
</div>
<div class="flex flex-col gap-1">
<div class="flex items-center gap-1 text-gray-500">
<span class="text-3.5">{{ title }}</span>
<el-tooltip :content="tooltip" placement="top-start" v-if="tooltip">
<Icon icon="ep:warning" class="item-center flex !text-3" />
</el-tooltip>
</div>
<div class="flex flex-row items-baseline gap-2">
<div class="text-7">
<CountTo :prefix="prefix" :end-val="value" :decimals="decimals" />
</div>
<span
v-if="percent != undefined"
:class="toNumber(percent) > 0 ? 'text-red-500' : 'text-green-500'"
>
<span class="text-sm">{{ Math.abs(toNumber(percent)) }}%</span>
<Icon
:icon="toNumber(percent) > 0 ? 'ep:caret-top' : 'ep:caret-bottom'"
class="ml-0.5 !text-3"
/>
</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { toNumber } from 'lodash-es'
/** 交易状况统计值组件 */
defineOptions({ name: 'TradeTrendValue' })
defineProps({
title: propTypes.string.def(''),
tooltip: propTypes.string.def(''),
icon: propTypes.string.def(''),
iconColor: propTypes.string.def(''),
iconBgColor: propTypes.string.def(''),
prefix: propTypes.string.def(''),
value: propTypes.number.def(0),
decimals: propTypes.number.def(0),
percent: propTypes.oneOfType([Number, String]).def(undefined)
})
</script>
<template>
<div class="flex flex-col">
<el-row :gutter="16" class="summary">
<el-col :sm="6" :xs="12">
<TradeStatisticValue
tooltip="昨日订单数量"
title="昨日订单数量"
:value="summary?.value?.yesterdayOrderCount || 0"
:percent="
calculateRelativeRate(
summary?.value?.yesterdayOrderCount,
summary?.reference?.yesterdayOrderCount
)
"
/>
</el-col>
<el-col :sm="6" :xs="12">
<TradeStatisticValue
tooltip="本月订单数量"
title="本月订单数量"
:value="summary?.value?.monthOrderCount || 0"
:percent="
calculateRelativeRate(
summary?.value?.monthOrderCount,
summary?.reference?.monthOrderCount
)
"
/>
</el-col>
<el-col :sm="6" :xs="12">
<TradeStatisticValue
tooltip="昨日支付金额"
title="昨日支付金额"
prefix="¥"
:decimals="2"
:value="fenToYuan(summary?.value?.yesterdayPayPrice || 0)"
:percent="
calculateRelativeRate(
summary?.value?.yesterdayPayPrice,
summary?.reference?.yesterdayPayPrice
)
"
/>
</el-col>
<el-col :sm="6" :xs="12">
<TradeStatisticValue
tooltip="本月支付金额"
title="本月支付金额"
prefix="¥"
::decimals="2"
:value="fenToYuan(summary?.value?.monthPayPrice || 0)"
:percent="
calculateRelativeRate(summary?.value?.monthPayPrice, summary?.reference?.monthPayPrice)
"
/>
</el-col>
</el-row>
<el-card shadow="never">
<template #header>
<!-- 标题 -->
<div class="flex flex-row items-center justify-between">
<span>交易状况</span>
<!-- 查询条件 -->
<div class="flex flex-row items-center gap-2">
<el-radio-group v-model="shortcutDays" @change="handleDateTypeChange">
<el-radio-button :label="1">昨天</el-radio-button>
<el-radio-button :label="7">最近7天</el-radio-button>
<el-radio-button :label="30">最近30天</el-radio-button>
</el-radio-group>
<el-date-picker
v-model="queryParams.times"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
:shortcuts="shortcuts"
class="!w-240px"
@change="getTradeTrendData"
/>
<el-button
class="ml-4"
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['statistics:trade:export']"
>
<Icon icon="ep:download" class="mr-1" />导出
</el-button>
</div>
</div>
</template>
<!-- 统计值 -->
<el-row :gutter="16">
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="营业额"
tooltip="商品支付金额、充值金额"
icon="fa-solid:yen-sign"
icon-color="bg-blue-100"
icon-bg-color="text-blue-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.turnover || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.turnover,
trendSummary?.reference?.turnover
)
"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="商品支付金额"
tooltip="用户购买商品的实际支付金额,包括微信支付、余额支付、支付宝支付、线下支付金额(拼团商品在成团之后计入,线下支付订单在后台确认支付后计入)"
icon="fa-solid:shopping-cart"
icon-color="bg-purple-100"
icon-bg-color="text-purple-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.orderPayPrice || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.orderPayPrice,
trendSummary?.reference?.orderPayPrice
)
"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="充值金额"
tooltip="用户成功充值的金额"
icon="fa-solid:money-check-alt"
icon-color="bg-yellow-100"
icon-bg-color="text-yellow-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.rechargePrice || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.rechargePrice,
trendSummary?.reference?.rechargePrice
)
"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="支出金额"
tooltip="余额支付金额、支付佣金金额、商品退款金额"
icon="ep:warning-filled"
icon-color="bg-green-100"
icon-bg-color="text-green-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.expensePrice || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.expensePrice,
trendSummary?.reference?.expensePrice
)
"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="余额支付金额"
tooltip="用户下单时使用余额实际支付的金额"
icon="fa-solid:wallet"
icon-color="bg-cyan-100"
icon-bg-color="text-cyan-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.balancePrice || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.balancePrice,
trendSummary?.reference?.balancePrice
)
"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="支付佣金金额"
tooltip="后台给推广员支付的推广佣金,以实际支付为准"
icon="fa-solid:award"
icon-color="bg-yellow-100"
icon-bg-color="text-yellow-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.brokerageSettlementPrice || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.brokerageSettlementPrice,
trendSummary?.reference?.brokerageSettlementPrice
)
"
/>
</el-col>
<el-col :md="6" :sm="12" :xs="24">
<TradeTrendValue
title="商品退款金额"
tooltip="用户成功退款的商品金额"
icon="fa-solid:times-circle"
icon-color="bg-blue-100"
icon-bg-color="text-blue-500"
prefix="¥"
:decimals="2"
:value="fenToYuan(trendSummary?.value?.orderRefundPrice || 0)"
:percent="
calculateRelativeRate(
trendSummary?.value?.orderRefundPrice,
trendSummary?.reference?.orderRefundPrice
)
"
/>
</el-col>
</el-row>
<!-- 折线图 -->
<el-skeleton :loading="trendLoading" animated>
<Echart :height="500" :options="lineChartOptions" />
</el-skeleton>
</el-card>
</div>
</template>
<script lang="ts" setup>
import * as TradeStatisticsApi from '@/api/mall/statistics/trade'
import TradeStatisticValue from './components/TradeStatisticValue.vue'
import TradeTrendValue from './components/TradeTrendValue.vue'
import { EChartsOption } from 'echarts'
import {
TradeStatisticsComparisonRespVO,
TradeSummaryRespVO,
TradeTrendReqVO,
TradeTrendSummaryRespVO
} from '@/api/mall/statistics/trade'
import dayjs from 'dayjs'
import { fenToYuan } from '@/utils'
import * as DateUtil from '@/utils/formatTime'
import download from '@/utils/download'
/** 交易统计 */
defineOptions({ name: 'TradeStatistics' })
const message = useMessage() // 消息弹窗
const loading = ref(true) // 加载中
const trendLoading = ref(true) // 交易状态加载中
const exportLoading = ref(false) // 导出的加载中
const queryParams = reactive<TradeTrendReqVO>({ times: ['', ''] }) // 交易状况查询参数
const shortcutDays = ref(7) // 日期快捷天数(单选按钮组), 默认7天
const summary = ref<TradeStatisticsComparisonRespVO<TradeSummaryRespVO>>() // 交易统计数据
const trendSummary = ref<TradeStatisticsComparisonRespVO<TradeTrendSummaryRespVO>>() // 交易状况统计数据
/** 日期快捷选择 */
const shortcuts = [
{
text: '昨天',
value: () => DateUtil.getDayRange(new Date(), -1)
},
{
text: '最近7天',
value: () => DateUtil.getLast7Days()
},
{
text: '本月',
value: () => [dayjs().startOf('M'), dayjs().subtract(1, 'd')]
},
{
text: '最近30天',
value: () => DateUtil.getLast30Days()
},
{
text: '最近1年',
value: () => DateUtil.getLast1Year()
}
]
/** 折线图配置 */
const lineChartOptions = reactive<EChartsOption>({
dataset: {
dimensions: ['date', 'turnover', 'orderPayPrice', 'rechargePrice', 'expensePrice'],
source: []
},
grid: {
left: 20,
right: 20,
bottom: 20,
top: 80,
containLabel: true
},
legend: {
top: 50
},
series: [
{ name: '营业额', type: 'line', smooth: true },
{ name: '商品支付金额', type: 'line', smooth: true },
{ name: '充值金额', type: 'line', smooth: true },
{ name: '支出金额', type: 'line', smooth: true }
],
toolbox: {
feature: {
// 数据区域缩放
dataZoom: {
yAxisIndex: false // Y轴不缩放
},
brush: {
type: ['lineX', 'clear'] // 区域缩放按钮、还原按钮
},
saveAsImage: { show: true, name: '交易状况' } // 保存为图片
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
xAxis: {
type: 'category',
boundaryGap: false,
axisTick: {
show: false
}
},
yAxis: {
axisTick: {
show: false
}
}
}) as EChartsOption
/** 计算环比 */
const calculateRelativeRate = (value?: number, reference?: number) => {
// 防止除0
if (!reference) return 0
return ((100 * ((value || 0) - reference)) / reference).toFixed(0)
}
/** 设置时间范围 */
function setTimes() {
const beginDate = dayjs().subtract(shortcutDays.value, 'd')
const yesterday = dayjs().subtract(1, 'd')
queryParams.times = DateUtil.getDateRange(beginDate, yesterday)
}
/** 处理交易状况查询(日期单选按钮组选择后) */
const handleDateTypeChange = async () => {
// 设置时间范围
setTimes()
// 查询数据
await getTradeTrendData()
}
/** 处理交易状况查询 */
const getTradeTrendData = async () => {
trendLoading.value = true
await Promise.all([getTradeTrendSummary(), getTradeTrendList()])
trendLoading.value = false
}
/** 查询交易统计 */
const getTradeStatisticsSummary = async () => {
summary.value = await TradeStatisticsApi.getTradeStatisticsSummary()
}
/** 查询交易状况数据统计 */
const getTradeTrendSummary = async () => {
loading.value = true
trendSummary.value = await TradeStatisticsApi.getTradeTrendSummary(queryParams)
loading.value = false
}
/** 查询交易状况数据列表 */
const getTradeTrendList = async () => {
const times = queryParams.times
// 开始与截止在同一天的, 折线图出不来, 需要延长一天
if (DateUtil.isSameDay(times[0], times[1])) {
// 前天
times[0] = DateUtil.formatDate(dayjs(times[0]).subtract(1, 'd'))
}
// 查询数据
const list = await TradeStatisticsApi.getTradeTrendList({ times })
// 处理数据
for (let item of list) {
item.turnover = fenToYuan(item.turnover)
item.orderPayPrice = fenToYuan(item.orderPayPrice)
item.rechargePrice = fenToYuan(item.rechargePrice)
item.expensePrice = fenToYuan(item.expensePrice)
}
// 更新 Echarts 数据
if (lineChartOptions.dataset && lineChartOptions.dataset['source']) {
lineChartOptions.dataset['source'] = list
}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await TradeStatisticsApi.exportTradeTrend(queryParams)
download.excel(data, '交易状况.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/
onMounted(async () => {
await getTradeStatisticsSummary()
await handleDateTypeChange()
})
</script>
<style lang="scss" scoped>
.summary {
.el-col {
margin-bottom: 1rem;
}
}
</style>
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
<el-descriptions-item label="配送方式: "> <el-descriptions-item label="配送方式: ">
<dict-tag :type="DICT_TYPE.TRADE_DELIVERY_TYPE" :value="formData.order.deliveryType" /> <dict-tag :type="DICT_TYPE.TRADE_DELIVERY_TYPE" :value="formData.order.deliveryType" />
</el-descriptions-item> </el-descriptions-item>
<!-- TODO 营销活动待实现 -->
<el-descriptions-item label="订单类型: "> <el-descriptions-item label="订单类型: ">
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="formData.order.type" /> <dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="formData.order.type" />
</el-descriptions-item> </el-descriptions-item>
...@@ -29,8 +28,7 @@ ...@@ -29,8 +28,7 @@
<el-descriptions-item label="付款方式: "> <el-descriptions-item label="付款方式: ">
<dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="formData.order.payChannelCode" /> <dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="formData.order.payChannelCode" />
</el-descriptions-item> </el-descriptions-item>
<!-- TODO 芋艿:待实现:跳转会员 --> <el-descriptions-item label="买家: ">{{ formData?.user?.nickname }}</el-descriptions-item>
<!-- <el-descriptions-item label="买家: ">{{ formData.user.nickname }}</el-descriptions-item> -->
</el-descriptions> </el-descriptions>
<!-- 售后信息 --> <!-- 售后信息 -->
...@@ -46,7 +44,7 @@ ...@@ -46,7 +44,7 @@
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="formData.way" /> <dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_WAY" :value="formData.way" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="退款金额: "> <el-descriptions-item label="退款金额: ">
{{ floatToFixed2(formData.refundPrice) }} {{ fenToYuan(formData.refundPrice) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="退款原因: ">{{ formData.applyReason }}</el-descriptions-item> <el-descriptions-item label="退款原因: ">{{ formData.applyReason }}</el-descriptions-item>
<el-descriptions-item label="补充描述: "> <el-descriptions-item label="补充描述: ">
...@@ -92,7 +90,7 @@ ...@@ -92,7 +90,7 @@
<el-descriptions-item labelClassName="no-colon"> <el-descriptions-item labelClassName="no-colon">
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="15"> <el-col :span="15">
<el-table :data="formData.items" border> <el-table :data="[formData.orderItem]" border>
<el-table-column label="商品" prop="spuName" width="auto"> <el-table-column label="商品" prop="spuName" width="auto">
<template #default="{ row }"> <template #default="{ row }">
{{ row.spuName }} {{ row.spuName }}
...@@ -102,19 +100,11 @@ ...@@ -102,19 +100,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="商品原价" prop="price" width="150"> <el-table-column label="商品原价" prop="price" width="150">
<template #default="{ row }">{{ floatToFixed2(row.price) }}</template> <template #default="{ row }">{{ fenToYuan(row.price) }} </template>
</el-table-column> </el-table-column>
<el-table-column label="数量" prop="count" width="100" /> <el-table-column label="数量" prop="count" width="100" />
<el-table-column label="合计" prop="payPrice" width="150"> <el-table-column label="合计" prop="payPrice" width="150">
<template #default="{ row }">{{ floatToFixed2(row.payPrice) }}</template> <template #default="{ row }">{{ fenToYuan(row.payPrice) }}</template>
</el-table-column>
<el-table-column label="售后状态" prop="afterSaleStatus" width="120">
<template #default="{ row }">
<dict-tag
:type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS"
:value="row.afterSaleStatus"
/>
</template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-col> </el-col>
...@@ -122,6 +112,8 @@ ...@@ -122,6 +112,8 @@
</el-row> </el-row>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<!-- 操作日志 -->
<el-descriptions title="售后日志"> <el-descriptions title="售后日志">
<el-descriptions-item labelClassName="no-colon"> <el-descriptions-item labelClassName="no-colon">
<el-timeline> <el-timeline>
...@@ -153,7 +145,7 @@ ...@@ -153,7 +145,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as AfterSaleApi from '@/api/mall/trade/afterSale/index' import * as AfterSaleApi from '@/api/mall/trade/afterSale/index'
import { floatToFixed2 } from '@/utils' import { fenToYuan } from '@/utils'
import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict' import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import UpdateAuditReasonForm from '@/views/mall/trade/afterSale/form/AfterSaleDisagreeForm.vue' import UpdateAuditReasonForm from '@/views/mall/trade/afterSale/form/AfterSaleDisagreeForm.vue'
...@@ -191,7 +183,7 @@ const getUserTypeColor = (type: number) => { ...@@ -191,7 +183,7 @@ const getUserTypeColor = (type: number) => {
/** 获得详情 */ /** 获得详情 */
const getDetail = async () => { const getDetail = async () => {
const id = params.orderId as unknown as number const id = params.id as unknown as number
if (id) { if (id) {
const res = await AfterSaleApi.getAfterSale(id) const res = await AfterSaleApi.getAfterSale(id)
// 没有表单信息则关闭页面返回 // 没有表单信息则关闭页面返回
...@@ -204,44 +196,56 @@ const getDetail = async () => { ...@@ -204,44 +196,56 @@ const getDetail = async () => {
} }
/** 同意售后 */ /** 同意售后 */
const agree = () => { const agree = async () => {
message.confirm('是否同意售后?').then(() => { try {
AfterSaleApi.agree(formData.value.id) // 二次确认
await message.confirm('是否同意售后?')
await AfterSaleApi.agree(formData.value.id)
// 提示成功
message.success(t('common.success')) message.success(t('common.success'))
getDetail() await getDetail()
}) } catch {}
} }
/** 拒绝售后 */ /** 拒绝售后 */
const disagree = () => { const disagree = async () => {
updateAuditReasonFormRef.value?.open(formData.value) updateAuditReasonFormRef.value?.open(formData.value)
} }
/** 确认收货 */ /** 确认收货 */
const receive = () => { const receive = async () => {
message.confirm('是否确认收货?').then(() => { try {
AfterSaleApi.receive(formData.value.id) // 二次确认
await message.confirm('是否确认收货?')
await AfterSaleApi.receive(formData.value.id)
// 提示成功
message.success(t('common.success')) message.success(t('common.success'))
getDetail() await getDetail()
}) } catch {}
} }
/** 拒绝收货 */ /** 拒绝收货 */
const refuse = () => { const refuse = async () => {
message.confirm('是否拒绝收货?').then(() => { try {
AfterSaleApi.refuse(formData.value.id) // 二次确认
await message.confirm('是否拒绝收货?')
await AfterSaleApi.refuse(formData.value.id)
// 提示成功
message.success(t('common.success')) message.success(t('common.success'))
getDetail() await getDetail()
}) } catch {}
} }
/** 确认退款 */ /** 确认退款 */
const refund = () => { const refund = async () => {
message.confirm('是否确认退款?').then(() => { try {
AfterSaleApi.refund(formData.value.id) // 二次确认
await message.confirm('是否确认退款?')
await AfterSaleApi.refund(formData.value.id)
// 提示成功
message.success(t('common.success')) message.success(t('common.success'))
getDetail() await getDetail()
}) } catch {}
} }
/** 图片预览 */ /** 图片预览 */
......
...@@ -135,17 +135,16 @@ ...@@ -135,17 +135,16 @@
</el-table-column> </el-table-column>
<el-table-column align="center" label="订单金额" prop="refundPrice"> <el-table-column align="center" label="订单金额" prop="refundPrice">
<template #default="scope"> <template #default="scope">
<span>{{ floatToFixed2(scope.row.refundPrice) }}</span> <span>{{ fenToYuan(scope.row.refundPrice) }} </span>
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO 芋艿:未来要加个会员链接 -->
<el-table-column align="center" label="买家" prop="user.nickname" /> <el-table-column align="center" label="买家" prop="user.nickname" />
<el-table-column align="center" label="申请时间" prop="createTime" width="180"> <el-table-column align="center" label="申请时间" prop="createTime" width="180">
<template #default="scope"> <template #default="scope">
<span>{{ formatDate(scope.row.createTime) }}</span> <span>{{ formatDate(scope.row.createTime) }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="售后状态"> <el-table-column align="center" label="售后状态" width="100">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.TRADE_AFTER_SALE_STATUS" :value="scope.row.status" />
</template> </template>
...@@ -177,7 +176,7 @@ import { formatDate } from '@/utils/formatTime' ...@@ -177,7 +176,7 @@ import { formatDate } from '@/utils/formatTime'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import { TabsPaneContext } from 'element-plus' import { TabsPaneContext } from 'element-plus'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { floatToFixed2 } from '@/utils' import { fenToYuan } from '@/utils'
defineOptions({ name: 'TradeAfterSale' }) defineOptions({ name: 'TradeAfterSale' })
...@@ -240,7 +239,7 @@ const tabClick = async (tab: TabsPaneContext) => { ...@@ -240,7 +239,7 @@ const tabClick = async (tab: TabsPaneContext) => {
/** 处理退款 */ /** 处理退款 */
const openAfterSaleDetail = (id: number) => { const openAfterSaleDetail = (id: number) => {
push({ name: 'TradeAfterSaleDetail', params: { orderId: id } }) push({ name: 'TradeAfterSaleDetail', params: { id } })
} }
/** 查看订单详情 */ /** 查看订单详情 */
......
...@@ -96,14 +96,14 @@ ...@@ -96,14 +96,14 @@
align="center" align="center"
prop="unfreezeTime" prop="unfreezeTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
<el-table-column <el-table-column
label="创建时间" label="创建时间"
align="center" align="center"
prop="createTime" prop="createTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
......
...@@ -77,7 +77,7 @@ ...@@ -77,7 +77,7 @@
align="center" align="center"
prop="createTime" prop="createTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
......
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
align="center" align="center"
prop="bindUserTime" prop="bindUserTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 展示上级推广人的信息 -->
<el-descriptions v-if="bindUser" :column="1" border> <el-descriptions v-if="bindUser" :column="1" border>
<el-descriptions-item label="头像"> <el-descriptions-item label="头像">
<el-avatar :src="bindUser.avatar" /> <el-avatar :src="bindUser.avatar" />
...@@ -79,7 +80,7 @@ const submitForm = async () => { ...@@ -79,7 +80,7 @@ const submitForm = async () => {
if (!formRef) return if (!formRef) return
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
if (!valid) return if (!valid) return
// 未查找到合适的上级
if (!bindUser.value) { if (!bindUser.value) {
message.error('请先查询并确认推广人') message.error('请先查询并确认推广人')
return return
...@@ -116,7 +117,6 @@ const handleGetUser = async () => { ...@@ -116,7 +117,6 @@ const handleGetUser = async () => {
message.error('不能绑定自己为推广人') message.error('不能绑定自己为推广人')
return return
} }
formLoading.value = true formLoading.value = true
bindUser.value = await BrokerageUserApi.getBrokerageUser(formData.value.bindUserId) bindUser.value = await BrokerageUserApi.getBrokerageUser(formData.value.bindUserId)
if (!bindUser.value) { if (!bindUser.value) {
......
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
align="center" align="center"
prop="brokerageTime" prop="brokerageTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
<el-table-column label="上级推广员编号" align="center" prop="bindUserId" width="150px" /> <el-table-column label="上级推广员编号" align="center" prop="bindUserId" width="150px" />
<el-table-column <el-table-column
...@@ -117,7 +117,7 @@ ...@@ -117,7 +117,7 @@
align="center" align="center"
prop="bindUserTime" prop="bindUserTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
<el-table-column label="操作" align="center" width="150px" fixed="right"> <el-table-column label="操作" align="center" width="150px" fixed="right">
<template #default="scope"> <template #default="scope">
...@@ -204,7 +204,7 @@ const queryParams = reactive({ ...@@ -204,7 +204,7 @@ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
bindUserId: null, bindUserId: null,
brokerageEnabled: null, brokerageEnabled: true,
createTime: [] createTime: []
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
...@@ -281,7 +281,7 @@ const handleClearBindUser = async (row: BrokerageUserApi.BrokerageUserVO) => { ...@@ -281,7 +281,7 @@ const handleClearBindUser = async (row: BrokerageUserApi.BrokerageUserVO) => {
} catch {} } catch {}
} }
/** 推广资格 开通/关闭 */ /** 推广资格开通/关闭 */
const handleBrokerageEnabledChange = async (row: BrokerageUserApi.BrokerageUserVO) => { const handleBrokerageEnabledChange = async (row: BrokerageUserApi.BrokerageUserVO) => {
try { try {
// 二次确认 // 二次确认
......
...@@ -104,8 +104,8 @@ ...@@ -104,8 +104,8 @@
<template #default="scope"> <template #default="scope">
<div v-if="scope.row.type === BrokerageWithdrawTypeEnum.WALLET.type"> 余额 </div> <div v-if="scope.row.type === BrokerageWithdrawTypeEnum.WALLET.type"> 余额 </div>
<div v-else> <div v-else>
{{ getDictLabel(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE, scope.row.type) }}账号: {{ getDictLabel(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE, scope.row.type) }}
{{ scope.row.accountNo }} <span v-if="scope.row.accountNo">账号:{{ scope.row.accountNo }}</span>
</div> </div>
<template v-if="scope.row.type === BrokerageWithdrawTypeEnum.BANK.type"> <template v-if="scope.row.type === BrokerageWithdrawTypeEnum.BANK.type">
<div>真实姓名:{{ scope.row.name }}</div> <div>真实姓名:{{ scope.row.name }}</div>
...@@ -117,14 +117,16 @@ ...@@ -117,14 +117,16 @@
</template> </template>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="收款码" align="left" prop="accountQrCodeUrl" width="70px"> <el-table-column label="收款码" align="left" prop="accountQrCodeUrl" min-width="70px">
<template #default="scope"> <template #default="scope">
<el-image <el-image
v-if="scope.row.accountQrCodeUrl"
:src="scope.row.accountQrCodeUrl" :src="scope.row.accountQrCodeUrl"
class="w-40px h-40px" class="w-40px h-40px"
:preview-src-list="[scope.row.accountQrCodeUrl]" :preview-src-list="[scope.row.accountQrCodeUrl]"
preview-teleported preview-teleported
/> />
<span v-else></span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
...@@ -132,7 +134,7 @@ ...@@ -132,7 +134,7 @@
align="left" align="left"
prop="createTime" prop="createTime"
:formatter="dateFormatter" :formatter="dateFormatter"
width="170px" width="180px"
/> />
<el-table-column label="备注" align="left" prop="remark" /> <el-table-column label="备注" align="left" prop="remark" />
<el-table-column label="状态" align="left" prop="status" min-width="120px"> <el-table-column label="状态" align="left" prop="status" min-width="120px">
......
...@@ -10,8 +10,43 @@ ...@@ -10,8 +10,43 @@
<el-form-item label="hideId" v-show="false"> <el-form-item label="hideId" v-show="false">
<el-input v-model="formData.id" /> <el-input v-model="formData.id" />
</el-form-item> </el-form-item>
<el-tabs> <el-tabs>
<!-- 售后 -->
<el-tab-pane label="售后">
<el-form-item label="退款理由" prop="afterSaleRefundReasons">
<el-select
v-model="formData.afterSaleRefundReasons"
allow-create
filterable
multiple
placeholder="请直接输入退款理由"
>
<el-option
v-for="reason in formData.afterSaleRefundReasons"
:key="reason"
:label="reason"
:value="reason"
/>
</el-select>
</el-form-item>
<el-form-item label="退货理由" prop="afterSaleReturnReasons">
<el-select
v-model="formData.afterSaleReturnReasons"
allow-create
filterable
multiple
placeholder="请直接输入退货理由"
>
<el-option
v-for="reason in formData.afterSaleReturnReasons"
:key="reason"
:label="reason"
:value="reason"
/>
</el-select>
</el-form-item>
</el-tab-pane>
<!-- 配送 -->
<el-tab-pane label="配送"> <el-tab-pane label="配送">
<el-form-item label="启用包邮" prop="deliveryExpressFreeEnabled"> <el-form-item label="启用包邮" prop="deliveryExpressFreeEnabled">
<el-switch v-model="formData.deliveryExpressFreeEnabled" style="user-select: none" /> <el-switch v-model="formData.deliveryExpressFreeEnabled" style="user-select: none" />
...@@ -22,10 +57,18 @@ ...@@ -22,10 +57,18 @@
v-model="formData.deliveryExpressFreePrice" v-model="formData.deliveryExpressFreePrice"
placeholder="请输入满额包邮" placeholder="请输入满额包邮"
class="!w-xs" class="!w-xs"
:precision="2"
:min="0"
/> />
<el-text class="w-full" size="small" type="info"> 商城商品满多少金额即可包邮 </el-text> <el-text class="w-full" size="small" type="info">
商城商品满多少金额即可包邮,单位:元
</el-text>
</el-form-item>
<el-form-item label="启用门店自提" prop="deliveryPickUpEnabled">
<el-switch v-model="formData.deliveryPickUpEnabled" style="user-select: none" />
</el-form-item> </el-form-item>
</el-tab-pane> </el-tab-pane>
<!-- 分销 -->
<el-tab-pane label="分销"> <el-tab-pane label="分销">
<el-form-item label="分佣启用" prop="brokerageEnabled"> <el-form-item label="分佣启用" prop="brokerageEnabled">
<el-switch v-model="formData.brokerageEnabled" style="user-select: none" /> <el-switch v-model="formData.brokerageEnabled" style="user-select: none" />
...@@ -59,16 +102,16 @@ ...@@ -59,16 +102,16 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
没有推广人:只要用户没有推广人,随时都可以绑定推广关系 首次绑定:只要用户没有推广人,随时都可以绑定推广关系
</el-text> </el-text>
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
新用户:只有新用户注册时或首次进入系统时才可以绑定推广关系 注册绑定:只有新用户注册时或首次进入系统时才可以绑定推广关系
</el-text> </el-text>
</el-form-item> </el-form-item>
<el-form-item label="分销海报图"> <el-form-item label="分销海报图">
<UploadImgs v-model="formData.brokeragePostUrls" width="75px" height="125px" /> <UploadImgs v-model="formData.brokeragePosterUrls" width="75px" height="125px" />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
个人中心分销海报图片,建议尺寸600x1000 个人中心分销海报图片,建议尺寸 600x1000
</el-text> </el-text>
</el-form-item> </el-form-item>
<el-form-item label="一级返佣比例" prop="brokerageFirstPercent"> <el-form-item label="一级返佣比例" prop="brokerageFirstPercent">
...@@ -76,6 +119,8 @@ ...@@ -76,6 +119,8 @@
v-model="formData.brokerageFirstPercent" v-model="formData.brokerageFirstPercent"
placeholder="请输入一级返佣比例" placeholder="请输入一级返佣比例"
class="!w-xs" class="!w-xs"
:min="0"
:max="100"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
订单交易成功后给推广人返佣的百分比 订单交易成功后给推广人返佣的百分比
...@@ -86,6 +131,8 @@ ...@@ -86,6 +131,8 @@
v-model="formData.brokerageSecondPercent" v-model="formData.brokerageSecondPercent"
placeholder="请输入二级返佣比例" placeholder="请输入二级返佣比例"
class="!w-xs" class="!w-xs"
:min="0"
:max="100"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
订单交易成功后给推广人的推荐人返佣的百分比 订单交易成功后给推广人的推荐人返佣的百分比
...@@ -96,6 +143,7 @@ ...@@ -96,6 +143,7 @@
v-model="formData.brokerageFrozenDays" v-model="formData.brokerageFrozenDays"
placeholder="请输入佣金冻结天数" placeholder="请输入佣金冻结天数"
class="!w-xs" class="!w-xs"
:min="0"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
防止用户退款,佣金被提现了,所以需要设置佣金冻结时间,单位:天 防止用户退款,佣金被提现了,所以需要设置佣金冻结时间,单位:天
...@@ -106,6 +154,8 @@ ...@@ -106,6 +154,8 @@
v-model="formData.brokerageWithdrawMinPrice" v-model="formData.brokerageWithdrawMinPrice"
placeholder="请输入提现最低金额" placeholder="请输入提现最低金额"
class="!w-xs" class="!w-xs"
:precision="2"
:min="0"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
用户提现最低金额限制,单位:元 用户提现最低金额限制,单位:元
...@@ -116,13 +166,16 @@ ...@@ -116,13 +166,16 @@
v-model="formData.brokerageWithdrawFeePercent" v-model="formData.brokerageWithdrawFeePercent"
placeholder="请输入提现手续费" placeholder="请输入提现手续费"
class="!w-xs" class="!w-xs"
:min="0"
:max="100"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
提现手续费百分比,范围0-100,0为无提现手续费,例:设置10,即收取10%手续费,提现100元,到账90元,10元手续费 提现手续费百分比,范围 0-100,0 为无提现手续费。例:设置 10,即收取 10% 手续费,提现
10 元,到账 9 元,1 元手续费
</el-text> </el-text>
</el-form-item> </el-form-item>
<el-form-item label="提现方式" prop="brokerageWithdrawType"> <el-form-item label="提现方式" prop="brokerageWithdrawTypes">
<el-checkbox-group v-model="formData.brokerageWithdrawType"> <el-checkbox-group v-model="formData.brokerageWithdrawTypes">
<el-checkbox <el-checkbox
v-for="dict in getIntDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE)" v-for="dict in getIntDictOptions(DICT_TYPE.BROKERAGE_WITHDRAW_TYPE)"
:key="dict.value" :key="dict.value"
...@@ -146,7 +199,7 @@ ...@@ -146,7 +199,7 @@
</el-form-item> </el-form-item>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<!-- 保存 -->
<el-form-item> <el-form-item>
<el-button type="primary" @click="submitForm" :loading="formLoading"> 保存 </el-button> <el-button type="primary" @click="submitForm" :loading="formLoading"> 保存 </el-button>
</el-form-item> </el-form-item>
...@@ -156,7 +209,6 @@ ...@@ -156,7 +209,6 @@
<script setup lang="ts"> <script setup lang="ts">
import * as ConfigApi from '@/api/mall/trade/config' import * as ConfigApi from '@/api/mall/trade/config'
import { BrokerageBindModeEnum, BrokerageEnabledConditionEnum } from '@/utils/constants'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
defineOptions({ name: 'TradeConfig' }) defineOptions({ name: 'TradeConfig' })
...@@ -167,19 +219,22 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加 ...@@ -167,19 +219,22 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加
const formRef = ref() const formRef = ref()
const formData = ref({ const formData = ref({
id: null, id: null,
deliveryExpressFreeEnabled: true, afterSaleRefundReasons: [],
afterSaleReturnReasons: [],
deliveryExpressFreeEnabled: false,
deliveryExpressFreePrice: 0, deliveryExpressFreePrice: 0,
brokerageEnabled: true, deliveryPickUpEnabled: false,
brokerageEnabledCondition: BrokerageEnabledConditionEnum.ALL.condition, brokerageEnabled: false,
brokerageBindMode: BrokerageBindModeEnum.ANYTIME.mode, brokerageEnabledCondition: undefined,
brokeragePostUrls: [], brokerageBindMode: undefined,
brokeragePosterUrls: [],
brokerageFirstPercent: 0, brokerageFirstPercent: 0,
brokerageSecondPercent: 0, brokerageSecondPercent: 0,
brokerageWithdrawMinPrice: 0, brokerageWithdrawMinPrice: 0,
brokerageWithdrawFeePercent: 0, brokerageWithdrawFeePercent: 0,
brokerageBankNames: [], brokerageBankNames: [],
brokerageFrozenDays: 0, brokerageFrozenDays: 0,
brokerageWithdrawType: [] brokerageWithdrawTypes: []
}) })
const formRules = reactive({ const formRules = reactive({
deliveryExpressFreePrice: [{ required: true, message: '满额包邮不能为空', trigger: 'blur' }], deliveryExpressFreePrice: [{ required: true, message: '满额包邮不能为空', trigger: 'blur' }],
...@@ -193,7 +248,7 @@ const formRules = reactive({ ...@@ -193,7 +248,7 @@ const formRules = reactive({
brokerageWithdrawFeePercent: [{ required: true, message: '提现手续费不能为空', trigger: 'blur' }], brokerageWithdrawFeePercent: [{ required: true, message: '提现手续费不能为空', trigger: 'blur' }],
brokerageBankNames: [{ required: true, message: '提现银行不能为空', trigger: 'blur' }], brokerageBankNames: [{ required: true, message: '提现银行不能为空', trigger: 'blur' }],
brokerageFrozenDays: [{ required: true, message: '佣金冻结时间不能为空', trigger: 'blur' }], brokerageFrozenDays: [{ required: true, message: '佣金冻结时间不能为空', trigger: 'blur' }],
brokerageWithdrawType: [ brokerageWithdrawTypes: [
{ {
required: true, required: true,
message: '提现方式不能为空', message: '提现方式不能为空',
...@@ -211,10 +266,15 @@ const submitForm = async () => { ...@@ -211,10 +266,15 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as ConfigApi.ConfigVO const data = {
data.brokeragePostUrls = formData.value.brokeragePostUrls.map((item: any) => { ...formData.value
} as unknown as ConfigApi.ConfigVO
data.brokeragePosterUrls = formData.value.brokeragePosterUrls.map((item: any) => {
return item?.url ? item.url : item return item?.url ? item.url : item
}) })
// 金额放大
data.deliveryExpressFreePrice = data.deliveryExpressFreePrice * 100
data.brokerageWithdrawMinPrice = data.brokerageWithdrawMinPrice * 100
await ConfigApi.saveTradeConfig(data) await ConfigApi.saveTradeConfig(data)
message.success('保存成功') message.success('保存成功')
} finally { } finally {
...@@ -228,8 +288,11 @@ const getConfig = async () => { ...@@ -228,8 +288,11 @@ const getConfig = async () => {
try { try {
const data = await ConfigApi.getTradeConfig() const data = await ConfigApi.getTradeConfig()
if (data != null) { if (data != null) {
data.brokeragePostUrls = data.brokeragePostUrls.map((url) => ({ url })) data.brokeragePosterUrls = data.brokeragePosterUrls.map((url) => ({ url }))
formData.value = data formData.value = data
// 金额缩小
formData.value.deliveryExpressFreePrice = data.deliveryExpressFreePrice / 100
formData.value.brokerageWithdrawMinPrice = data.brokerageWithdrawMinPrice / 100
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
......
...@@ -7,17 +7,17 @@ ...@@ -7,17 +7,17 @@
label-width="120px" label-width="120px"
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item label="快递公司编码" prop="code"> <el-form-item label="公司编码" prop="code">
<el-input v-model="formData.code" placeholder="请输入快递编码" /> <el-input v-model="formData.code" placeholder="请输入快递编码" />
</el-form-item> </el-form-item>
<el-form-item label="快递公司名称" prop="name"> <el-form-item label="公司名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入快递名称" /> <el-input v-model="formData.name" placeholder="请输入快递名称" />
</el-form-item> </el-form-item>
<el-form-item label="快递公司 logo" prop="logo"> <el-form-item label="公司 logo" prop="logo">
<UploadImg v-model="formData.logo" :limit="1" :is-show-tip="false" /> <UploadImg v-model="formData.logo" :limit="1" :is-show-tip="false" />
<div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div> <div style="font-size: 10px" class="pl-10px">推荐 180x180 图片分辨率</div>
</el-form-item> </el-form-item>
<el-form-item label="分类排序" prop="sort"> <el-form-item label="排序" prop="sort">
<el-input-number v-model="formData.sort" controls-position="right" :min="0" /> <el-input-number v-model="formData.sort" controls-position="right" :min="0" />
</el-form-item> </el-form-item>
<el-form-item label="开启状态" prop="status"> <el-form-item label="开启状态" prop="status">
......
...@@ -53,11 +53,11 @@ ...@@ -53,11 +53,11 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="快递公司编号" prop="code" /> <el-table-column label="公司编码" prop="code" />
<el-table-column label="快递公司名称" prop="name" /> <el-table-column label="公司名称" prop="name" />
<el-table-column label="快递公司 logo " prop="logo"> <el-table-column label="公司 logo " prop="logo">
<template #default="scope"> <template #default="scope">
<img v-if="scope.row.logo" :src="scope.row.logo" alt="快递公司logo" class="h-100px" /> <img v-if="scope.row.logo" :src="scope.row.logo" alt="公司logo" class="h-40px" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="排序" align="center" prop="sort" /> <el-table-column label="排序" align="center" prop="sort" />
......
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="80%"> <Dialog :title="dialogTitle" v-model="dialogVisible" width="1300px">
<el-form <el-form
ref="formRef" ref="formRef"
:model="formData" :model="formData"
...@@ -21,23 +21,19 @@ ...@@ -21,23 +21,19 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item label="运费" prop="templateCharge"> <el-form-item label="运费" prop="charges">
<el-table border style="width: 100%" :data="formData.templateCharge"> <el-table border style="width: 100%" :data="formData.charges">
<el-table-column align="center" label="区域" width="180"> <el-table-column align="center" label="区域" width="360">
<template #default="{ row }"> <template #default="{ row }">
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 --> <el-cascader
<el-tree-select
v-model="row.areaIds" v-model="row.areaIds"
:load="loadChargeArea" :options="areaTree"
:props="defaultProps" :props="defaultProps2"
node-key="id" class="w-1/1"
multiple clearable
check-strictly placeholder="请选择商品分类"
show-checkbox filterable
lazy collapse-tags
check-on-click-node
:render-after-expand="false"
:cache-data="areaCache"
/> />
</template> </template>
</el-table-column> </el-table-column>
...@@ -85,23 +81,19 @@ ...@@ -85,23 +81,19 @@
<Icon icon="ep:plus" class="mr-5px" /> 添加区域 <Icon icon="ep:plus" class="mr-5px" /> 添加区域
</el-button> </el-button>
</el-form-item> </el-form-item>
<el-form-item label="包邮区域" prop="templateFree"> <el-form-item label="包邮区域" prop="frees">
<el-table border style="width: 100%" :data="formData.templateFree"> <el-table border style="width: 100%" :data="formData.frees">
<el-table-column align="center" label="区域"> <el-table-column align="center" label="区域" width="360">
<template #default="{ row }"> <template #default="{ row }">
<!-- 区域数据太多,用赖加载方式,要不然性能有问题 --> <el-cascader
<el-tree-select
v-model="row.areaIds" v-model="row.areaIds"
multiple :options="areaTree"
lazy :props="defaultProps2"
:load="loadFreeArea" class="w-1/1"
:props="defaultProps" clearable
node-key="id" placeholder="请选择商品分类"
check-strictly filterable
show-checkbox collapse-tags
check-on-click-node
:render-after-expand="true"
:cache-data="areaCache"
/> />
</template> </template>
</el-table-column> </el-table-column>
...@@ -140,13 +132,18 @@ ...@@ -140,13 +132,18 @@
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate' import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
import * as AreaApi from '@/api/system/area'
import { defaultProps } from '@/utils/tree' import { defaultProps } from '@/utils/tree'
import { yuanToFen, fenToYuan } from '@/utils' import { yuanToFen, fenToYuan } from '@/utils'
import { getChildrenArea, getAreaListByIds } from '@/api/system/area'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const defaultProps2 = {
...defaultProps,
multiple: true
}
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
...@@ -156,8 +153,8 @@ const formData = ref({ ...@@ -156,8 +153,8 @@ const formData = ref({
name: '', name: '',
chargeMode: 1, chargeMode: 1,
sort: 0, sort: 0,
templateCharge: [], charges: [],
templateFree: [] frees: []
}) })
const columnTitleMap = new Map() const columnTitleMap = new Map()
const columnTitle = ref({ const columnTitle = ref({
...@@ -171,9 +168,6 @@ const formRules = reactive({ ...@@ -171,9 +168,6 @@ const formRules = reactive({
sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }] sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const areaCache = ref([]) // 由于区域节点懒加载,已选区域节点需要缓存展示
// TODO @jason:配送的时候,只允许选择省市级别,不允许选择区;如果这样的话,是不是打开弹窗,直接把城市都请求过来;
// TODO @jaosn:因为只有省市两级,感觉就不用特殊做全国逻辑;选择全国,就默认把子节点都选择上;另外,选择父节点,要把子节点选中哈;
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, id?: number) => { const open = async (type: string, id?: number) => {
...@@ -187,30 +181,14 @@ const open = async (type: string, id?: number) => { ...@@ -187,30 +181,14 @@ const open = async (type: string, id?: number) => {
formLoading.value = true formLoading.value = true
formData.value = await DeliveryExpressTemplateApi.getDeliveryExpressTemplate(id) formData.value = await DeliveryExpressTemplateApi.getDeliveryExpressTemplate(id)
columnTitle.value = columnTitleMap.get(formData.value.chargeMode) columnTitle.value = columnTitleMap.get(formData.value.chargeMode)
const chargeAreaIds = [] formData.value.charges.forEach((item) => {
const freeAreaIds = [] // 前端价格以元展示
formData.value.templateCharge.forEach((item) => {
for (let i = 0; i < item.areaIds.length; i++) {
if (!chargeAreaIds.includes(item.areaIds[i])) {
chargeAreaIds.push(item.areaIds[i])
}
}
//前端价格以元展示
item.startPrice = fenToYuan(item.startPrice) item.startPrice = fenToYuan(item.startPrice)
item.extraPrice = fenToYuan(item.extraPrice) item.extraPrice = fenToYuan(item.extraPrice)
}) })
formData.value.templateFree.forEach((item) => { formData.value.frees.forEach((item) => {
for (let i = 0; i < item.areaIds.length; i++) {
if (!chargeAreaIds.includes(item.areaIds[i]) && !freeAreaIds.includes(item.areaIds[i])) {
freeAreaIds.push(item.areaIds[i])
}
}
item.freePrice = fenToYuan(item.freePrice) item.freePrice = fenToYuan(item.freePrice)
}) })
// 已选的区域节点
const areaIds = chargeAreaIds.concat(freeAreaIds)
// 区域节点,懒加载方式。已选节点需要缓存展示
areaCache.value = await getAreaListByIds(areaIds.join(','))
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
...@@ -228,14 +206,13 @@ const submitForm = async () => { ...@@ -228,14 +206,13 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO const data = cloneDeep(formData.value) as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
// 前端价格以元展示,提交到后端。用分计算 // 前端价格以元展示,提交到后端。用分计算
// TODO @jason:不能直接这样改,要复制出来改。不然后端操作失败,数据已经被改了 data.charges.forEach((item) => {
data.templateCharge.forEach((item) => {
item.startPrice = yuanToFen(item.startPrice) item.startPrice = yuanToFen(item.startPrice)
item.extraPrice = yuanToFen(item.extraPrice) item.extraPrice = yuanToFen(item.extraPrice)
}) })
data.templateFree.forEach((item) => { data.frees.forEach((item) => {
item.freePrice = yuanToFen(item.freePrice) item.freePrice = yuanToFen(item.freePrice)
}) })
if (formType.value === 'create') { if (formType.value === 'create') {
...@@ -259,7 +236,7 @@ const resetForm = () => { ...@@ -259,7 +236,7 @@ const resetForm = () => {
id: undefined, id: undefined,
name: '', name: '',
chargeMode: 1, chargeMode: 1,
templateCharge: [ charges: [
{ {
areaIds: [1], areaIds: [1],
startCount: 2, startCount: 2,
...@@ -268,7 +245,7 @@ const resetForm = () => { ...@@ -268,7 +245,7 @@ const resetForm = () => {
extraPrice: 10 extraPrice: 10
} }
], ],
templateFree: [], frees: [],
sort: 0 sort: 0
} }
columnTitle.value = columnTitleMap.get(1) columnTitle.value = columnTitleMap.get(1)
...@@ -279,37 +256,10 @@ const resetForm = () => { ...@@ -279,37 +256,10 @@ const resetForm = () => {
const changeChargeMode = (chargeMode: number) => { const changeChargeMode = (chargeMode: number) => {
columnTitle.value = columnTitleMap.get(chargeMode) columnTitle.value = columnTitleMap.get(chargeMode)
} }
const defaultArea = [{ id: 1, name: '全国', disabled: false }]
/** 初始化数据 */ /** 初始化数据 */
// TODO @jason:是不是不用写这样一个初始化方法,columnTitleMap 直接就可以了呀 const areaTree = ref([])
// const columnTitleMap = {
// '1': {
// startCountTitle: '首件',
// extraCountTitle: '续件',
// freeCountTitle: '包邮件数'
// },
// '2': {
// startCountTitle: '首件重量(kg)',
// extraCountTitle: '续件重量(kg)',
// freeCountTitle: '包邮重量(kg)'
// },
// '3': {
// startCountTitle: '首件体积(m³)',
// extraCountTitle: '续件体积(m³)',
// freeCountTitle: '包邮体积(m³)'
// }
// }
const initData = async () => { const initData = async () => {
// TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
// formLoading.value = true
// try {
// const data = await getAreaTree()
// areaTree = data
// console.log('areaTree', areaTree)
// } finally {
// formLoading.value = false
// }
// 表头标题和计费方式的映射 // 表头标题和计费方式的映射
columnTitleMap.set(1, { columnTitleMap.set(1, {
startCountTitle: '首件', startCountTitle: '首件',
...@@ -326,77 +276,14 @@ const initData = async () => { ...@@ -326,77 +276,14 @@ const initData = async () => {
extraCountTitle: '续件体积(m³)', extraCountTitle: '续件体积(m³)',
freeCountTitle: '包邮体积(m³)' freeCountTitle: '包邮体积(m³)'
}) })
// 加载区域数据
areaTree.value = await AreaApi.getAreaTree()
} }
/** 懒加载运费区域树 */
const loadChargeArea = async (node, resolve) => {
//已选区域需要禁止再次选择
const areaIds = []
formData.value.templateCharge.forEach((item) => {
if (item.areaIds.length > 0) {
item.areaIds.forEach((areaId) => areaIds.push(areaId))
}
})
if (node.isLeaf) return resolve([])
const length = node.data.length
if (length === 0) {
const data = cloneDeep(defaultArea)
const item = data[0]
if (areaIds.includes(item.id)) {
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
// TODO @jason:先不做这个功能哈。
//item.disabled = true
}
resolve(data)
} else {
const id = node.data.id
const data = await getChildrenArea(id)
data.forEach((item) => {
if (areaIds.includes(item.id)) {
//item.disabled = true
}
})
resolve(data)
}
}
/** 懒加载包邮区域树 */
const loadFreeArea = async (node, resolve) => {
if (node.isLeaf) return resolve([])
//已选区域需要禁止再次选择
const areaIds = []
formData.value.templateFree.forEach((item) => {
if (item.areaIds.length > 0) {
item.areaIds.forEach((areaId) => areaIds.push(areaId))
}
})
const length = node.data.length
if (length === 0) {
// 为空,从全国开始选择。全国 id == 1
const data = cloneDeep(defaultArea)
const item = data[0]
if (areaIds.includes(item.id)) {
//item.disabled = true
}
resolve(data)
} else {
const id = node.data.id
const data = await getChildrenArea(id)
// 已选区域需要禁止再次选择
data.forEach((item) => {
if (areaIds.includes(item.id)) {
// TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
// TODO @jason:先不做这个功能哈。
//item.disabled = true
}
})
resolve(data)
}
}
/** 添加计费区域 */ /** 添加计费区域 */
const addChargeArea = () => { const addChargeArea = () => {
const data = formData.value const data = formData.value
data.templateCharge.push({ data.charges.push({
areaIds: [], areaIds: [],
startCount: 1, startCount: 1,
startPrice: 1, startPrice: 1,
...@@ -408,13 +295,13 @@ const addChargeArea = () => { ...@@ -408,13 +295,13 @@ const addChargeArea = () => {
/** 删除计费区域 */ /** 删除计费区域 */
const deleteChargeArea = (index) => { const deleteChargeArea = (index) => {
const data = formData.value const data = formData.value
data.templateCharge.splice(index, 1) data.charges.splice(index, 1)
} }
/** 添加包邮区域 */ /** 添加包邮区域 */
const addFreeArea = () => { const addFreeArea = () => {
const data = formData.value const data = formData.value
data.templateFree.push({ data.frees.push({
areaIds: [], areaIds: [],
freeCount: 1, freeCount: 1,
freePrice: 1 freePrice: 1
...@@ -424,7 +311,7 @@ const addFreeArea = () => { ...@@ -424,7 +311,7 @@ const addFreeArea = () => {
/** 删除包邮区域 */ /** 删除包邮区域 */
const deleteFreeArea = (index) => { const deleteFreeArea = (index) => {
const data = formData.value const data = formData.value
data.templateFree.splice(index, 1) data.frees.splice(index, 1)
} }
/** 初始化 **/ /** 初始化 **/
......
...@@ -51,14 +51,14 @@ ...@@ -51,14 +51,14 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="编号" prop="id" /> <el-table-column label="编号" min-width="60" prop="id" />
<el-table-column label="模板名称" prop="name" /> <el-table-column label="模板名称" min-width="100" prop="name" />
<el-table-column label="计费方式" prop="chargeMode" align="center"> <el-table-column label="计费方式" prop="chargeMode" min-width="100" align="center">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.EXPRESS_CHARGE_MODE" :value="scope.row.chargeMode" /> <dict-tag :type="DICT_TYPE.EXPRESS_CHARGE_MODE" :value="scope.row.chargeMode" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="排序" prop="sort" /> <el-table-column label="排序" min-width="100" prop="sort" />
<el-table-column <el-table-column
label="创建时间" label="创建时间"
align="center" align="center"
......
...@@ -51,7 +51,7 @@ ...@@ -51,7 +51,7 @@
<el-row> <el-row>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="门店所在地区" prop="areaId"> <el-form-item label="门店所在地区" prop="areaId">
<el-cascader v-model="formData.areaId" :options="areaList" :props="areaTreeProps" /> <el-cascader v-model="formData.areaId" :options="areaList" :props="defaultProps" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
...@@ -99,7 +99,7 @@ ...@@ -99,7 +99,7 @@
</el-col> </el-col>
</el-row> </el-row>
<el-form-item label="获取经纬度"> <el-form-item label="获取经纬度">
<el-button type="primary" @click="mapDialogVisible.value = true">获取</el-button> <el-button type="primary" @click="mapDialogVisible = true">获取</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> <template #footer>
...@@ -121,8 +121,9 @@ ...@@ -121,8 +121,9 @@
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore' import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps } from '@/utils/tree'
import { getAreaTree } from '@/api/system/area' import { getAreaTree } from '@/api/system/area'
import * as ConfigApi from '@/api/infra/config' import * as ConfigApi from '@/api/mall/trade/config'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
...@@ -161,12 +162,6 @@ const formRules = reactive({ ...@@ -161,12 +162,6 @@ const formRules = reactive({
status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }] status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const areaTreeProps = {
children: 'children',
label: 'name',
value: 'id',
emitPath: false
}
const areaList = ref() // 区域树 const areaList = ref() // 区域树
const tencentLbsUrl = ref('') // 腾讯位置服务 url const tencentLbsUrl = ref('') // 腾讯位置服务 url
...@@ -244,16 +239,8 @@ const selectAddress = function (loc: any): void { ...@@ -244,16 +239,8 @@ const selectAddress = function (loc: any): void {
mapDialogVisible.value = false mapDialogVisible.value = false
} }
/** 初始化数据 */ /** 初始化腾讯地图 */
const initData = async () => { const initTencentLbsMap = async () => {
formLoading.value = true
try {
const data = await getAreaTree()
areaList.value = data
} finally {
formLoading.value = false
}
// TODO @jason:要不创建一个 initTencentLbsMap
window.selectAddress = selectAddress window.selectAddress = selectAddress
window.addEventListener( window.addEventListener(
'message', 'message',
...@@ -267,17 +254,16 @@ const initData = async () => { ...@@ -267,17 +254,16 @@ const initData = async () => {
}, },
false false
) )
const data = await ConfigApi.getConfigKey('tencent.lbs.key') const data = await ConfigApi.getTradeConfig()
let key = '' const key = data.tencentLbsKey
if (data && data.length > 0) {
key = data
}
tencentLbsUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${key}&referer=myapp` tencentLbsUrl.value = `https://apis.map.qq.com/tools/locpicker?type=1&key=${key}&referer=myapp`
} }
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(async () => {
initData() areaList.value = await getAreaTree()
// 加载地图
await initTencentLbsMap()
}) })
</script> </script>
<style lang="scss"> <style lang="scss">
......
...@@ -65,16 +65,21 @@ ...@@ -65,16 +65,21 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list"> <el-table v-loading="loading" :data="list">
<el-table-column label="编号" prop="id" /> <el-table-column label="编号" min-width="80" prop="id" />
<el-table-column label="门店 logo" prop="logo"> <el-table-column label="门店 logo" min-width="100" prop="logo">
<template #default="scope"> <template #default="scope">
<img v-if="scope.row.logo" :src="scope.row.logo" alt="门店 logo" class="h-100px" /> <img v-if="scope.row.logo" :src="scope.row.logo" alt="门店 logo" class="h-50px" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="门店名称" prop="name" /> <el-table-column label="门店名称" min-width="150" prop="name" />
<el-table-column label="门店手机" prop="phone" /> <el-table-column label="门店手机" min-width="100" prop="phone" />
<el-table-column align="center" label="门店详细地址" prop="detailAddress" /> <el-table-column label="地址" min-width="100" prop="detailAddress" />
<el-table-column align="center" label="开启状态" prop="status"> <el-table-column label="营业时间" min-width="180">
<template #default="scope">
{{ scope.row.openingTime }} ~ {{ scope.row.closingTime }}
</template>
</el-table-column>
<el-table-column align="center" label="开启状态" min-width="100" prop="status">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template> </template>
......
...@@ -3,35 +3,21 @@ ...@@ -3,35 +3,21 @@
<!-- 订单信息 --> <!-- 订单信息 -->
<el-descriptions title="订单信息"> <el-descriptions title="订单信息">
<el-descriptions-item label="订单号: ">{{ formData.no }}</el-descriptions-item> <el-descriptions-item label="订单号: ">{{ formData.no }}</el-descriptions-item>
<el-descriptions-item label="配送方式: "> <el-descriptions-item label="买家: ">{{ formData?.user?.nickname }}</el-descriptions-item>
<dict-tag :type="DICT_TYPE.TRADE_DELIVERY_TYPE" :value="formData.deliveryType!" />
</el-descriptions-item>
<!-- TODO 营销活动待实现 -->
<el-descriptions-item label="营销活动: ">秒杀活动</el-descriptions-item>
<el-descriptions-item label="订单类型: "> <el-descriptions-item label="订单类型: ">
<dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="formData.type!" /> <dict-tag :type="DICT_TYPE.TRADE_ORDER_TYPE" :value="formData.type!" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="收货人: ">{{ formData.receiverName }}</el-descriptions-item>
<el-descriptions-item label="买家留言: ">{{ formData.userRemark }}</el-descriptions-item>
<el-descriptions-item label="订单来源: "> <el-descriptions-item label="订单来源: ">
<dict-tag :type="DICT_TYPE.TERMINAL" :value="formData.terminal!" /> <dict-tag :type="DICT_TYPE.TERMINAL" :value="formData.terminal!" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="联系电话: ">{{ formData.receiverMobile }}</el-descriptions-item> <el-descriptions-item label="买家留言: ">{{ formData.userRemark }}</el-descriptions-item>
<el-descriptions-item label="商家备注: ">{{ formData.remark }}</el-descriptions-item> <el-descriptions-item label="商家备注: ">{{ formData.remark }}</el-descriptions-item>
<el-descriptions-item label="支付单号: ">{{ formData.payOrderId }}</el-descriptions-item> <el-descriptions-item label="支付单号: ">{{ formData.payOrderId }}</el-descriptions-item>
<el-descriptions-item label="付款方式: "> <el-descriptions-item label="付款方式: ">
<dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="formData.payChannelCode!" /> <dict-tag :type="DICT_TYPE.PAY_CHANNEL_CODE" :value="formData.payChannelCode!" />
</el-descriptions-item> </el-descriptions-item>
<!-- <el-descriptions-item label="买家: ">{{ formData.user.nickname }}</el-descriptions-item> --> <el-descriptions-item label="推广用户: " v-if="formData.brokerageUser">
<!-- TODO @puhui999:待实现:跳转会员 --> {{ formData.brokerageUser?.nickname }}
<el-descriptions-item label="收货地址: ">
{{ formData.receiverAreaName }} {{ formData.receiverDetailAddress }}
<el-link
v-clipboard:copy="formData.receiverAreaName + ' ' + formData.receiverDetailAddress"
v-clipboard:success="clipboardSuccess"
icon="ep:document-copy"
type="primary"
/>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
...@@ -41,16 +27,40 @@ ...@@ -41,16 +27,40 @@
<dict-tag :type="DICT_TYPE.TRADE_ORDER_STATUS" :value="formData.status!" /> <dict-tag :type="DICT_TYPE.TRADE_ORDER_STATUS" :value="formData.status!" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label-class-name="no-colon"> <el-descriptions-item label-class-name="no-colon">
<el-button v-if="formData.status! === 0" type="primary" @click="updatePrice"> <el-button
v-if="formData.status! === TradeOrderStatusEnum.UNPAID.status"
type="primary"
@click="updatePrice"
>
调整价格 调整价格
</el-button> </el-button>
<el-button type="primary" @click="remark">备注</el-button> <el-button type="primary" @click="remark">备注</el-button>
<el-button v-if="formData.status! === 10" type="primary" @click="delivery"> <!-- 待发货 -->
发货 <template v-if="formData.status! === TradeOrderStatusEnum.UNDELIVERED.status">
</el-button> <!-- 快递发货 -->
<el-button v-if="formData.status! === 10" type="primary" @click="updateAddress"> <el-button
修改地址 v-if="formData.deliveryType === DeliveryTypeEnum.EXPRESS.type"
</el-button> type="primary"
@click="delivery"
>
发货
</el-button>
<el-button
v-if="formData.deliveryType === DeliveryTypeEnum.EXPRESS.type"
type="primary"
@click="updateAddress"
>
修改地址
</el-button>
<!-- 到店自提 -->
<el-button
v-if="formData.deliveryType === DeliveryTypeEnum.PICK_UP.type"
type="primary"
@click="handlePickUp"
>
核销
</el-button>
</template>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label><span style="color: red">提醒: </span></template> <template #label><span style="color: red">提醒: </span></template>
...@@ -75,11 +85,11 @@ ...@@ -75,11 +85,11 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="商品原价" prop="price" width="150"> <el-table-column label="商品原价" prop="price" width="150">
<template #default="{ row }">{{ floatToFixed2(row.price) }}</template> <template #default="{ row }">{{ fenToYuan(row.price) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="数量" prop="count" width="100" /> <el-table-column label="数量" prop="count" width="100" />
<el-table-column label="合计" prop="payPrice" width="150"> <el-table-column label="合计" prop="payPrice" width="150">
<template #default="{ row }">{{ floatToFixed2(row.payPrice) }}</template> <template #default="{ row }">{{ fenToYuan(row.payPrice) }}</template>
</el-table-column> </el-table-column>
<el-table-column label="售后状态" prop="afterSaleStatus" width="120"> <el-table-column label="售后状态" prop="afterSaleStatus" width="120">
<template #default="{ row }"> <template #default="{ row }">
...@@ -95,64 +105,91 @@ ...@@ -95,64 +105,91 @@
</el-row> </el-row>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<el-descriptions :column="6"> <el-descriptions :column="4">
<!-- 第一层 -->
<el-descriptions-item label="商品总额: "> <el-descriptions-item label="商品总额: ">
{{ floatToFixed2(formData.totalPrice!) }} {{ fenToYuan(formData.totalPrice!) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="运费金额: "> <el-descriptions-item label="运费金额: ">
{{ floatToFixed2(formData.deliveryPrice!) }} {{ fenToYuan(formData.deliveryPrice!) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="订单调价: "> <el-descriptions-item label="订单调价: ">
{{ floatToFixed2(formData.adjustPrice!) }}元 {{ fenToYuan(formData.adjustPrice!) }} 元
</el-descriptions-item>
<el-descriptions-item v-for="item in 1" :key="item" label-class-name="no-colon" />
<!-- 第二层 -->
<el-descriptions-item>
<template #label><span style="color: red">优惠劵优惠: </span></template>
{{ fenToYuan(formData.couponPrice!) }} 元
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label><span style="color: red">商品优惠: </span></template> <template #label><span style="color: red">VIP 优惠: </span></template>
{{ floatToFixed2(formData.couponPrice!) }} {{ fenToYuan(formData.vipPrice!) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label><span style="color: red">订单优惠: </span></template> <template #label><span style="color: red">活动优惠: </span></template>
{{ floatToFixed2(formData.discountPrice!) }} {{ fenToYuan(formData.discountPrice!) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label><span style="color: red">积分抵扣: </span></template> <template #label><span style="color: red">积分抵扣: </span></template>
{{ floatToFixed2(formData.pointPrice!) }} {{ fenToYuan(formData.pointPrice!) }}
</el-descriptions-item> </el-descriptions-item>
<!-- 第三层 -->
<el-descriptions-item v-for="item in 5" :key="item" label-class-name="no-colon" /> <el-descriptions-item v-for="item in 3" :key="item" label-class-name="no-colon" />
<!-- 占位 -->
<el-descriptions-item label="应付金额: "> <el-descriptions-item label="应付金额: ">
{{ floatToFixed2(formData.payPrice!) }} {{ fenToYuan(formData.payPrice!) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
<!-- TODO 芋艿:需要改改 --> <!-- 物流信息 -->
<el-descriptions :column="4" title="物流信息"> <el-descriptions :column="4" title="收货信息">
<el-descriptions-item label="物流公司: "> <el-descriptions-item label="配送方式: ">
{{ deliveryExpressList.find((item) => item.id === formData.logisticsId)?.name }} <dict-tag :type="DICT_TYPE.TRADE_DELIVERY_TYPE" :value="formData.deliveryType!" />
</el-descriptions-item>
<el-descriptions-item label="运单号: ">{{ formData.logisticsNo }}</el-descriptions-item>
<el-descriptions-item label="发货时间: ">
{{ formatDate(formData.deliveryTime!) }}
</el-descriptions-item>
<el-descriptions-item label="物流状态: ">
<!-- TODO 物流状态怎么获取? -->
<dict-tag :type="DICT_TYPE.TRADE_ORDER_STATUS" :value="formData.deliveryStatus!" />
</el-descriptions-item>
<!-- 占位 4 -->
<el-descriptions-item v-for="item in 4" :key="item" label-class-name="no-colon" />
<el-descriptions-item label="物流详情: ">
<el-timeline>
<el-timeline-item
v-for="(express, index) in expressTrackList"
:key="index"
:timestamp="formatDate(express.time)"
>
{{ express.content }}
</el-timeline-item>
</el-timeline>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="收货人: ">{{ formData.receiverName }}</el-descriptions-item>
<el-descriptions-item label="联系电话: ">{{ formData.receiverMobile }}</el-descriptions-item>
<!-- 快递配送 -->
<div v-if="formData.deliveryType === DeliveryTypeEnum.EXPRESS.type">
<el-descriptions-item label="收货地址: " v-if="formData.receiverDetailAddress">
{{ formData.receiverAreaName }} {{ formData.receiverDetailAddress }}
<el-link
v-clipboard:copy="formData.receiverAreaName + ' ' + formData.receiverDetailAddress"
v-clipboard:success="clipboardSuccess"
icon="ep:document-copy"
type="primary"
/>
</el-descriptions-item>
<el-descriptions-item label="物流公司: " v-if="formData.logisticsId">
{{ deliveryExpressList.find((item) => item.id === formData.logisticsId)?.name }}
</el-descriptions-item>
<el-descriptions-item label="运单号: " v-if="formData.logisticsId">
{{ formData.logisticsNo }}
</el-descriptions-item>
<el-descriptions-item label="发货时间: " v-if="formatDate.deliveryTime">
{{ formatDate(formData.deliveryTime) }}
</el-descriptions-item>
<el-descriptions-item v-for="item in 2" :key="item" label-class-name="no-colon" />
<el-descriptions-item label="物流详情: " v-if="expressTrackList.length > 0">
<el-timeline>
<el-timeline-item
v-for="(express, index) in expressTrackList"
:key="index"
:timestamp="formatDate(express.time)"
>
{{ express.content }}
</el-timeline-item>
</el-timeline>
</el-descriptions-item>
</div>
<!-- 自提门店 -->
<div v-if="formData.deliveryType === DeliveryTypeEnum.PICK_UP.type">
<el-descriptions-item label="自提门店: " v-if="formData.pickUpStoreId">
{{ pickUpStore?.name }}
</el-descriptions-item>
</div>
</el-descriptions> </el-descriptions>
<!-- 订单日志 -->
<el-descriptions title="订单操作日志"> <el-descriptions title="订单操作日志">
<el-descriptions-item labelClassName="no-colon"> <el-descriptions-item labelClassName="no-colon">
<el-timeline> <el-timeline>
...@@ -187,7 +224,7 @@ ...@@ -187,7 +224,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as TradeOrderApi from '@/api/mall/trade/order' import * as TradeOrderApi from '@/api/mall/trade/order'
import { floatToFixed2 } from '@/utils' import { fenToYuan } from '@/utils'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict' import { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict'
import OrderUpdateRemarkForm from '@/views/mall/trade/order/form/OrderUpdateRemarkForm.vue' import OrderUpdateRemarkForm from '@/views/mall/trade/order/form/OrderUpdateRemarkForm.vue'
...@@ -196,6 +233,8 @@ import OrderUpdateAddressForm from '@/views/mall/trade/order/form/OrderUpdateAdd ...@@ -196,6 +233,8 @@ import OrderUpdateAddressForm from '@/views/mall/trade/order/form/OrderUpdateAdd
import OrderUpdatePriceForm from '@/views/mall/trade/order/form/OrderUpdatePriceForm.vue' import OrderUpdatePriceForm from '@/views/mall/trade/order/form/OrderUpdatePriceForm.vue'
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express' import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import { DeliveryTypeEnum, TradeOrderStatusEnum } from '@/utils/constants'
import * as DeliveryPickUpStoreApi from '@/api/mall/trade/delivery/pickUpStore'
defineOptions({ name: 'TradeOrderDetail' }) defineOptions({ name: 'TradeOrderDetail' })
...@@ -240,14 +279,27 @@ const updatePrice = () => { ...@@ -240,14 +279,27 @@ const updatePrice = () => {
updatePriceFormRef.value?.open(formData.value) updatePriceFormRef.value?.open(formData.value)
} }
/** 核销 */
const handlePickUp = async () => {
try {
// 二次确认
await message.confirm('确认核销订单吗?')
// 提交
await TradeOrderApi.pickUpOrder(formData.value.id!)
message.success('核销成功')
// 刷新列表
await getDetail()
} catch {}
}
/** 获得详情 */ /** 获得详情 */
const { params } = useRoute() // 查询参数 const { params } = useRoute() // 查询参数
const getDetail = async () => { const getDetail = async () => {
const id = params.orderId as unknown as number const id = params.id as unknown as number
if (id) { if (id) {
const res = (await TradeOrderApi.getOrder(id)) as TradeOrderApi.OrderVO const res = (await TradeOrderApi.getOrder(id)) as TradeOrderApi.OrderVO
// 没有表单信息则关闭页面返回 // 没有表单信息则关闭页面返回
if (res === null) { if (!res) {
message.error('交易订单不存在') message.error('交易订单不存在')
close() close()
} }
...@@ -271,10 +323,20 @@ const clipboardSuccess = () => { ...@@ -271,10 +323,20 @@ const clipboardSuccess = () => {
/** 初始化 **/ /** 初始化 **/
const deliveryExpressList = ref([]) // 物流公司 const deliveryExpressList = ref([]) // 物流公司
const expressTrackList = ref([]) // 物流详情 const expressTrackList = ref([]) // 物流详情
const pickUpStore = ref({}) // 自提门店
onMounted(async () => { onMounted(async () => {
await getDetail() await getDetail()
deliveryExpressList.value = await DeliveryExpressApi.getSimpleDeliveryExpressList() // 如果配送方式为快递,则查询物流公司
expressTrackList.value = await TradeOrderApi.getExpressTrackList(formData.value.id!) if (formData.value.deliveryType === DeliveryTypeEnum.EXPRESS.type) {
deliveryExpressList.value = await DeliveryExpressApi.getSimpleDeliveryExpressList()
if (form.value.logisticsId) {
expressTrackList.value = await TradeOrderApi.getExpressTrackList(formData.value.id!)
}
} else if (formData.value.deliveryType === DeliveryTypeEnum.PICK_UP.type) {
pickUpStore.value = await DeliveryPickUpStoreApi.getDeliveryPickUpStore(
formData.value.pickUpStoreId
)
}
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
...@@ -312,7 +374,7 @@ onMounted(async () => { ...@@ -312,7 +374,7 @@ onMounted(async () => {
// 时间线样式调整 // 时间线样式调整
:deep(.el-timeline) { :deep(.el-timeline) {
margin: 10px 0px 0px 160px; margin: 10px 0 0 160px;
.el-timeline-item__wrapper { .el-timeline-item__wrapper {
position: relative; position: relative;
......
...@@ -54,7 +54,7 @@ const open = async (row: TradeOrderApi.OrderVO) => { ...@@ -54,7 +54,7 @@ const open = async (row: TradeOrderApi.OrderVO) => {
resetForm() resetForm()
// 设置数据 // 设置数据
copyValueToTarget(formData.value, row) copyValueToTarget(formData.value, row)
if (row.logisticsId === null || row.logisticsId === 0) { if (row.logisticsId === 0) {
expressType.value = 'none' expressType.value = 'none'
} }
dialogVisible.value = true dialogVisible.value = true
...@@ -73,7 +73,7 @@ const submitForm = async () => { ...@@ -73,7 +73,7 @@ const submitForm = async () => {
data.logisticsId = 0 data.logisticsId = 0
data.logisticsNo = '' data.logisticsNo = ''
} }
await TradeOrderApi.delivery(data) await TradeOrderApi.deliveryOrder(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
dialogVisible.value = false dialogVisible.value = false
// 发送操作成功的事件 // 发送操作成功的事件
......
...@@ -69,7 +69,7 @@ const submitForm = async () => { ...@@ -69,7 +69,7 @@ const submitForm = async () => {
formLoading.value = true formLoading.value = true
try { try {
const data = unref(formData) const data = unref(formData)
await TradeOrderApi.updateAddress(data) await TradeOrderApi.updateOrderAddress(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
dialogVisible.value = false dialogVisible.value = false
// 发送操作成功的事件 // 发送操作成功的事件
......
...@@ -69,7 +69,7 @@ const submitForm = async () => { ...@@ -69,7 +69,7 @@ const submitForm = async () => {
data.adjustPrice = convertToInteger(data.adjustPrice) data.adjustPrice = convertToInteger(data.adjustPrice)
delete data.payPrice delete data.payPrice
delete data.newPayPrice delete data.newPayPrice
await TradeOrderApi.updatePrice(data) await TradeOrderApi.updateOrderPrice(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
dialogVisible.value = false dialogVisible.value = false
// 发送操作成功的事件 // 发送操作成功的事件
......
...@@ -49,7 +49,7 @@ const submitForm = async () => { ...@@ -49,7 +49,7 @@ const submitForm = async () => {
formLoading.value = true formLoading.value = true
try { try {
const data = unref(formData) const data = unref(formData)
await TradeOrderApi.updateRemark(data) await TradeOrderApi.updateOrderRemark(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
dialogVisible.value = false dialogVisible.value = false
// 发送操作成功的事件 // 发送操作成功的事件
......
...@@ -74,7 +74,11 @@ ...@@ -74,7 +74,11 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="queryParams.deliveryType === 1" label="快递公司"> <el-form-item
v-if="queryParams.deliveryType === DeliveryTypeEnum.EXPRESS.type"
label="快递公司"
prop="logisticsId"
>
<el-select v-model="queryParams.logisticsId" class="!w-280px" clearable placeholder="全部"> <el-select v-model="queryParams.logisticsId" class="!w-280px" clearable placeholder="全部">
<el-option <el-option
v-for="item in deliveryExpressList" v-for="item in deliveryExpressList"
...@@ -84,7 +88,11 @@ ...@@ -84,7 +88,11 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="queryParams.deliveryType === 2" label="自提门店"> <el-form-item
v-if="queryParams.deliveryType === DeliveryTypeEnum.PICK_UP.type"
label="自提门店"
prop="pickUpStoreId"
>
<el-select <el-select
v-model="queryParams.pickUpStoreId" v-model="queryParams.pickUpStoreId"
class="!w-280px" class="!w-280px"
...@@ -100,26 +108,37 @@ ...@@ -100,26 +108,37 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<!-- TODO puhui 聚合搜索等售后结束后实现--> <el-form-item
<!-- TODO puhui999:尽量不要用 .k 这样的参数,完整拼写,有完整的业务含义 --> v-if="queryParams.deliveryType === DeliveryTypeEnum.PICK_UP.type"
label="核销码"
prop="pickUpVerifyCode"
>
<el-input
v-model="queryParams.pickUpVerifyCode"
class="!w-280px"
clearable
placeholder="请输入自提核销码"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="聚合搜索"> <el-form-item label="聚合搜索">
<el-input <el-input
v-show="true" v-show="true"
v-model="queryParams[queryType.k]" v-model="queryParams[queryType.queryParam]"
class="!w-280px" class="!w-280px"
clearable clearable
placeholder="请输入" placeholder="请输入"
> >
<template #prepend> <template #prepend>
<el-select <el-select
v-model="queryType.k" v-model="queryType.queryParam"
class="!w-110px" class="!w-110px"
clearable clearable
placeholder="全部" placeholder="全部"
@change="inputChangeSelect" @change="inputChangeSelect"
> >
<el-option <el-option
v-for="dict in searchList" v-for="dict in dynamicSearchList"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
...@@ -234,7 +253,10 @@ ...@@ -234,7 +253,10 @@
<el-table-column label="买家/收货人" min-width="160"> <el-table-column label="买家/收货人" min-width="160">
<template #default> <template #default>
<!-- 快递发货 --> <!-- 快递发货 -->
<div v-if="scope.row.deliveryType === 1" class="flex flex-col"> <div
v-if="scope.row.deliveryType === DeliveryTypeEnum.EXPRESS.type"
class="flex flex-col"
>
<span>买家:{{ scope.row.user.nickname }}</span> <span>买家:{{ scope.row.user.nickname }}</span>
<span> <span>
收货人:{{ scope.row.receiverName }} {{ scope.row.receiverMobile }} 收货人:{{ scope.row.receiverName }} {{ scope.row.receiverMobile }}
...@@ -242,7 +264,10 @@ ...@@ -242,7 +264,10 @@
</span> </span>
</div> </div>
<!-- 自提 --> <!-- 自提 -->
<div v-if="scope.row.deliveryType === 2" class="flex flex-col"> <div
v-if="scope.row.deliveryType === DeliveryTypeEnum.PICK_UP.type"
class="flex flex-col"
>
<span> <span>
门店名称: 门店名称:
{{ pickUpStoreList.find((p) => p.id === scope.row.pickUpStoreId)?.name }} {{ pickUpStoreList.find((p) => p.id === scope.row.pickUpStoreId)?.name }}
...@@ -273,7 +298,7 @@ ...@@ -273,7 +298,7 @@
<el-table-column align="center" fixed="right" label="操作" width="160"> <el-table-column align="center" fixed="right" label="操作" width="160">
<template #default> <template #default>
<!-- TODO 权限后续补齐 --> <!-- TODO 权限后续补齐 -->
<div class="flex justify-center items-center"> <div class="flex items-center justify-center">
<el-button link type="primary" @click="openDetail(scope.row.id)"> <el-button link type="primary" @click="openDetail(scope.row.id)">
<Icon icon="ep:notification" /> <Icon icon="ep:notification" />
详情 详情
...@@ -287,7 +312,10 @@ ...@@ -287,7 +312,10 @@
<el-dropdown-menu> <el-dropdown-menu>
<!-- 如果是【快递】,并且【未发货】,则展示【发货】按钮 --> <!-- 如果是【快递】,并且【未发货】,则展示【发货】按钮 -->
<el-dropdown-item <el-dropdown-item
v-if="scope.row.deliveryType === 1 && scope.row.status === 10" v-if="
scope.row.deliveryType === DeliveryTypeEnum.EXPRESS.type &&
scope.row.status === TradeOrderStatusEnum.UNDELIVERED.status
"
command="delivery" command="delivery"
> >
<Icon icon="ep:takeaway-box" /> <Icon icon="ep:takeaway-box" />
...@@ -332,6 +360,7 @@ import { formatDate } from '@/utils/formatTime' ...@@ -332,6 +360,7 @@ import { formatDate } from '@/utils/formatTime'
import { floatToFixed2 } from '@/utils' import { floatToFixed2 } from '@/utils'
import { createImageViewer } from '@/components/ImageViewer' import { createImageViewer } from '@/components/ImageViewer'
import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express' import * as DeliveryExpressApi from '@/api/mall/trade/delivery/express'
import { DeliveryTypeEnum, TradeOrderStatusEnum } from '@/utils/constants'
defineOptions({ name: 'TradeOrder' }) defineOptions({ name: 'TradeOrder' })
...@@ -352,13 +381,13 @@ const queryParams = ref({ ...@@ -352,13 +381,13 @@ const queryParams = ref({
type: null, // 订单类型 type: null, // 订单类型
deliveryType: null, // 配送方式 deliveryType: null, // 配送方式
logisticsId: null, // 快递公司 logisticsId: null, // 快递公司
pickUpStoreId: null // 自提门店 pickUpStoreId: null, // 自提门店
pickUpVerifyCode: null // 自提核销码
}) })
const queryType = reactive({ k: '' }) // 订单搜索类型 k const queryType = reactive({ queryParam: '' }) // 订单搜索类型 queryParam
// 订单聚合搜索 select 类型配置 // 订单聚合搜索 select 类型配置(动态搜索)
// TODO @puhui999:dynamicSearchList,动态搜索;其它相关的变量和方法,都可以朝着这个变量靠哈;这样更容易理解; const dynamicSearchList = ref([
const searchList = ref([
{ value: 'no', label: '订单号' }, { value: 'no', label: '订单号' },
{ value: 'userId', label: '用户UID' }, { value: 'userId', label: '用户UID' },
{ value: 'userNickname', label: '用户昵称' }, { value: 'userNickname', label: '用户昵称' },
...@@ -369,7 +398,7 @@ const searchList = ref([ ...@@ -369,7 +398,7 @@ const searchList = ref([
* @param val * @param val
*/ */
const inputChangeSelect = (val: string) => { const inputChangeSelect = (val: string) => {
searchList.value dynamicSearchList.value
.filter((item) => item.value !== val) .filter((item) => item.value !== val)
?.forEach((item1) => { ?.forEach((item1) => {
// 清除集合搜索无用属性 // 清除集合搜索无用属性
...@@ -443,6 +472,7 @@ const handleQuery = async () => { ...@@ -443,6 +472,7 @@ const handleQuery = async () => {
const resetQuery = () => { const resetQuery = () => {
queryFormRef.value?.resetFields() queryFormRef.value?.resetFields()
queryParams.value = { queryParams.value = {
pickUpVerifyCode: null, // 自提核销码
pageNo: 1, // 页数 pageNo: 1, // 页数
pageSize: 10, // 每页显示数量 pageSize: 10, // 每页显示数量
status: null, // 订单状态 status: null, // 订单状态
...@@ -466,7 +496,7 @@ const imagePreview = (imgUrl: string) => { ...@@ -466,7 +496,7 @@ const imagePreview = (imgUrl: string) => {
/** 查看订单详情 */ /** 查看订单详情 */
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'TradeOrderDetail', params: { orderId: id } }) push({ name: 'TradeOrderDetail', params: { id } })
} }
/** 操作分发 */ /** 操作分发 */
......
...@@ -13,13 +13,13 @@ ...@@ -13,13 +13,13 @@
<el-tabs> <el-tabs>
<el-tab-pane label="积分"> <el-tab-pane label="积分">
<el-form-item label="积分抵扣" prop="tradeDeductEnable"> <el-form-item label="积分抵扣" prop="pointTradeDeductEnable">
<el-switch v-model="formData.tradeDeductEnable" style="user-select: none" /> <el-switch v-model="formData.pointTradeDeductEnable" style="user-select: none" />
<el-text class="w-full" size="small" type="info">下单积分是否抵用订单金额</el-text> <el-text class="w-full" size="small" type="info">下单积分是否抵用订单金额</el-text>
</el-form-item> </el-form-item>
<el-form-item label="积分抵扣" prop="tradeDeductUnitPrice"> <el-form-item label="积分抵扣" prop="pointTradeDeductUnitPrice">
<el-input-number <el-input-number
v-model="computedTradeDeductUnitPrice" v-model="computedPointTradeDeductUnitPrice"
placeholder="请输入积分抵扣金额" placeholder="请输入积分抵扣金额"
:precision="2" :precision="2"
/> />
...@@ -27,18 +27,18 @@ ...@@ -27,18 +27,18 @@
积分抵用比例(1 积分抵多少金额),单位:元 积分抵用比例(1 积分抵多少金额),单位:元
</el-text> </el-text>
</el-form-item> </el-form-item>
<el-form-item label="积分抵扣最大值" prop="tradeDeductMaxPrice"> <el-form-item label="积分抵扣最大值" prop="pointTradeDeductMaxPrice">
<el-input-number <el-input-number
v-model="formData.tradeDeductMaxPrice" v-model="formData.pointTradeDeductMaxPrice"
placeholder="请输入积分抵扣最大值" placeholder="请输入积分抵扣最大值"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
单次下单积分使用上限,0 不限制 单次下单积分使用上限,0 不限制
</el-text> </el-text>
</el-form-item> </el-form-item>
<el-form-item label="1 元赠送多少分" prop="tradeGivePoint"> <el-form-item label="1 元赠送多少分" prop="pointTradeGivePoint">
<el-input-number <el-input-number
v-model="formData.tradeGivePoint" v-model="formData.pointTradeGivePoint"
placeholder="请输入 1 元赠送多少积分" placeholder="请输入 1 元赠送多少积分"
/> />
<el-text class="w-full" size="small" type="info"> <el-text class="w-full" size="small" type="info">
...@@ -55,9 +55,9 @@ ...@@ -55,9 +55,9 @@
</ContentWrap> </ContentWrap>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as ConfigApi from '@/api/member/point/config' import * as ConfigApi from '@/api/member/config'
defineOptions({ name: 'MemberPointConfig' }) defineOptions({ name: 'MemberConfig' })
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
...@@ -66,17 +66,17 @@ const dialogVisible = ref(false) // 弹窗的是否展示 ...@@ -66,17 +66,17 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
tradeDeductEnable: true, pointTradeDeductEnable: true,
tradeDeductUnitPrice: 0, pointTradeDeductUnitPrice: 0,
tradeDeductMaxPrice: 0, pointTradeDeductMaxPrice: 0,
tradeGivePoint: 0 pointTradeGivePoint: 0
}) })
// 创建一个计算属性,用于将 tradeDeductUnitPrice 显示为带两位小数的形式 // 创建一个计算属性,用于将 pointTradeDeductUnitPrice 显示为带两位小数的形式
const computedTradeDeductUnitPrice = computed({ const computedPointTradeDeductUnitPrice = computed({
get: () => (formData.value.tradeDeductUnitPrice / 100).toFixed(2), get: () => (formData.value.pointTradeDeductUnitPrice / 100).toFixed(2),
set: (newValue) => { set: (newValue: number) => {
formData.value.tradeDeductUnitPrice = Math.round(newValue * 100) formData.value.pointTradeDeductUnitPrice = Math.round(newValue * 100)
} }
}) })
......
...@@ -13,8 +13,11 @@ ...@@ -13,8 +13,11 @@
只允许设置 1-7,默认签到 7 天为一个周期 只允许设置 1-7,默认签到 7 天为一个周期
</el-text> </el-text>
</el-form-item> </el-form-item>
<el-form-item label="签到分数" prop="point"> <el-form-item label="奖励积分" prop="point">
<el-input-number v-model="formData.point" :precision="0" /> <el-input-number v-model="formData.point" :min="0" :precision="0" />
</el-form-item>
<el-form-item label="奖励经验" prop="experience">
<el-input-number v-model="formData.experience" :min="0" :precision="0" />
</el-form-item> </el-form-item>
<el-form-item label="开启状态" prop="status"> <el-form-item label="开启状态" prop="status">
<el-radio-group v-model="formData.status"> <el-radio-group v-model="formData.status">
...@@ -46,12 +49,30 @@ const dialogVisible = ref(false) // 弹窗的是否展示 ...@@ -46,12 +49,30 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({ const formData = ref<SignInConfigApi.SignInConfigVO>({} as SignInConfigApi.SignInConfigVO)
id: undefined, // 奖励校验规则
day: undefined, const awardValidator = (rule: any, _value: any, callback: any) => {
point: undefined if (!formData.value.point && !formData.value.experience) {
callback(new Error('奖励积分与奖励经验至少配置一个'))
return
}
// 清除另一个字段的错误提示
const otherAwardField = rule?.field === 'point' ? 'experience' : 'point'
formRef.value.validateField(otherAwardField, () => null)
callback()
}
const formRules = reactive({
day: [{ required: true, message: '签到天数不能空', trigger: 'blur' }],
point: [
{ required: true, message: '奖励积分不能空', trigger: 'blur' },
{ validator: awardValidator, trigger: 'blur' }
],
experience: [
{ required: true, message: '奖励经验不能空', trigger: 'blur' },
{ validator: awardValidator, trigger: 'blur' }
]
}) })
const formRules = reactive({})
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
/** 打开弹窗 */ /** 打开弹窗 */
...@@ -82,14 +103,11 @@ const submitForm = async () => { ...@@ -82,14 +103,11 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as SignInConfigApi.SignInConfigVO
if (formType.value === 'create') { if (formType.value === 'create') {
//默认新创建的自动启动 await SignInConfigApi.createSignInConfig(formData.value)
data.enable = true
await SignInConfigApi.createSignInConfig(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else { } else {
await SignInConfigApi.updateSignInConfig(data) await SignInConfigApi.updateSignInConfig(formData.value)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false dialogVisible.value = false
...@@ -105,7 +123,8 @@ const resetForm = () => { ...@@ -105,7 +123,8 @@ const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
day: undefined, day: undefined,
point: undefined, point: 0,
experience: 0,
status: CommonStatusEnum.ENABLE status: CommonStatusEnum.ENABLE
} }
formRef.value?.resetFields() formRef.value?.resetFields()
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
prop="day" prop="day"
:formatter="(_, __, cellValue) => ['第', cellValue, '天'].join(' ')" :formatter="(_, __, cellValue) => ['第', cellValue, '天'].join(' ')"
/> />
<el-table-column label="获得积分" align="center" prop="point" /> <el-table-column label="奖励积分" align="center" prop="point" />
<el-table-column label="奖励经验" align="center" prop="experience" />
<el-table-column label="状态" align="center" prop="status"> <el-table-column label="状态" align="center" prop="status">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
......
<template>
<Dialog title="修改用户积分" v-model="dialogVisible" width="600">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item label="用户编号" prop="id">
<el-input v-model="formData.id" class="!w-240px" disabled />
</el-form-item>
<el-form-item label="用户昵称" prop="nickname">
<el-input v-model="formData.nickname" class="!w-240px" disabled />
</el-form-item>
<el-form-item label="变动前积分" prop="point">
<el-input-number v-model="formData.point" class="!w-240px" disabled />
</el-form-item>
<el-form-item label="变动类型" prop="changeType">
<el-radio-group v-model="formData.changeType">
<el-radio :label="1">增加</el-radio>
<el-radio :label="-1">减少</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="变动积分" prop="changePoint">
<el-input-number v-model="formData.changePoint" class="!w-240px" :min="0" :precision="0" />
</el-form-item>
<el-form-item label="变动后积分">
<el-input-number v-model="pointResult" class="!w-240px" disabled />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as UserApi from '@/api/member/user'
/** 修改用户积分表单 */
defineOptions({ name: 'UpdatePointForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formData = ref({
id: undefined,
nickname: undefined,
point: 0,
changePoint: 0,
changeType: 1
})
const formRules = reactive({
changePoint: [{ required: true, message: '变动积分不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (id?: number) => {
dialogVisible.value = true
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await UserApi.getUser(id)
formData.value.changeType = 1 // 默认增加积分
formData.value.changePoint = 0 // 变动积分默认0
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
if (formData.value.changePoint < 1) {
message.error('变动积分不能小于 1')
return
}
if (pointResult.value < 0) {
message.error('变动后的积分不能小于 0')
return
}
// 提交请求
formLoading.value = true
try {
await UserApi.updateUserPoint({
id: formData.value.id,
point: formData.value.changePoint * formData.value.changeType
})
message.success(t('common.updateSuccess'))
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
nickname: undefined,
levelId: undefined,
reason: undefined
}
formRef.value?.resetFields()
}
/** 变动后的积分 */
const pointResult = computed(
() => formData.value.point + formData.value.changePoint * formData.value.changeType
)
</script>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'GrowthList'
})
</script>
<!-- TODO @梦:可以读取 member_experience_log 表 -->
<template>
<div>成长值列表</div>
</template>
<style scoped lang="scss"></style>
...@@ -24,31 +24,57 @@ ...@@ -24,31 +24,57 @@
</template> </template>
{{ user.totalPoint || 0 }} {{ user.totalPoint || 0 }}
</el-descriptions-item> </el-descriptions-item>
<!-- TODO @疯狂:从 wallet 读取下对应字段 -->
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 当前余额 " icon="svg-icon:member_balance" /> <descriptions-item-label label=" 当前余额 " icon="svg-icon:member_balance" />
</template> </template>
{{ 0 }} {{ fenToYuan(wallet.balance || 0) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 支出金额 " icon="svg-icon:member_expenditure_balance" /> <descriptions-item-label label=" 支出金额 " icon="svg-icon:member_expenditure_balance" />
</template> </template>
{{ 0 }} {{ fenToYuan(wallet.totalExpense || 0) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template #label> <template #label>
<descriptions-item-label label=" 充值金额 " icon="svg-icon:member_recharge_balance" /> <descriptions-item-label label=" 充值金额 " icon="svg-icon:member_recharge_balance" />
</template> </template>
{{ 0 }} {{ fenToYuan(wallet.totalRecharge || 0) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { DescriptionsItemLabel } from '@/components/Descriptions' import { DescriptionsItemLabel } from '@/components/Descriptions'
import * as UserApi from '@/api/member/user' import * as UserApi from '@/api/member/user'
const { user } = defineProps<{ user: UserApi.UserVO }>() import * as WalletApi from '@/api/pay/wallet'
import { UserTypeEnum } from '@/utils/constants'
import { fenToYuan } from '@/utils'
const props = defineProps<{ user: UserApi.UserVO }>() // 用户信息
const WALLET_INIT_DATA = {
balance: 0,
totalExpense: 0,
totalRecharge: 0
} as WalletApi.WalletVO // 钱包初始化数据
const wallet = ref<WalletApi.WalletVO>(WALLET_INIT_DATA) // 钱包信息
/** 查询用户钱包信息 */
const getUserWallet = async () => {
if (!props.user.id) {
wallet.value = WALLET_INIT_DATA
return
}
const params = { userId: props.user.id, userType: UserTypeEnum.MEMBER }
wallet.value = (await WalletApi.getWallet(params)) || WALLET_INIT_DATA
}
/** 监听用户编号变化 */
watch(
() => props.user.id,
() => getUserWallet(),
{ immediate: true }
)
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.cell-item { .cell-item {
......
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="85px"
>
<el-form-item label="用户类型" prop="level">
<el-radio-group v-model="queryParams.level" @change="handleQuery">
<el-radio-button checked>全部</el-radio-button>
<el-radio-button label="1">一级推广人</el-radio-button>
<el-radio-button label="2">二级推广人</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="绑定时间" prop="bindUserTime">
<el-date-picker
v-model="queryParams.bindUserTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="用户编号" align="center" prop="id" min-width="80px" />
<el-table-column label="头像" align="center" prop="avatar" width="70px">
<template #default="scope">
<el-avatar :src="scope.row.avatar" />
</template>
</el-table-column>
<el-table-column label="昵称" align="center" prop="nickname" min-width="80px" />
<el-table-column label="等级" align="center" prop="level" min-width="80px">
<template #default="scope">
<el-tag v-if="scope.row.bindUserId === bindUserId">一级</el-tag>
<el-tag v-else>二级</el-tag>
</template>
</el-table-column>
<el-table-column
label="绑定时间"
align="center"
prop="bindUserTime"
:formatter="dateFormatter"
width="170px"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime'
import * as BrokerageUserApi from '@/api/mall/trade/brokerage/user'
/** 推广人列表 */
defineOptions({ name: 'UserBrokerageList' })
const { bindUserId }: { bindUserId: number } = defineProps({
bindUserId: {
type: Number,
required: true
}
}) //用户编号
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
bindUserId: null,
level: '',
bindUserTime: []
})
const queryFormRef = ref() // 搜索的表单
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
queryParams.bindUserId = bindUserId
const data = await BrokerageUserApi.getBrokerageUserPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** 初始化 **/
onMounted(() => {
getList()
})
</script>
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
ref="queryFormRef"
:inline="true"
:model="queryParams"
class="-mb-15px"
label-width="68px"
>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"> <Icon icon="ep:search" class="mr-5px" />搜索 </el-button>
<el-button @click="resetQuery"> <Icon icon="ep:refresh" class="mr-5px" />重置 </el-button>
</el-form-item>
</el-form>
</ContentWrap>
<ContentWrap>
<!-- Tab 选项:真正的内容在 Lab -->
<el-tabs v-model="activeTab" type="card" @tab-change="onTabChange">
<el-tab-pane
v-for="tab in statusTabs"
:key="tab.value"
:label="tab.label"
:name="tab.value"
/>
</el-tabs>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="优惠劵" align="center" prop="name" />
<el-table-column label="优惠券类型" align="center" prop="discountType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_DISCOUNT_TYPE" :value="scope.row.discountType" />
</template>
</el-table-column>
<el-table-column label="领取方式" align="center" prop="takeType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_TAKE_TYPE" :value="scope.row.takeType" />
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.PROMOTION_COUPON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="领取时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column
label="使用时间"
align="center"
prop="useTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
v-hasPermi="['promotion:coupon:delete']"
type="danger"
link
@click="handleDelete(scope.row.id)"
>
回收
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit="queryParams.pageSize"
v-model:page="queryParams.pageNo"
:total="total"
@pagination="getList"
/>
</ContentWrap>
</template>
<script setup lang="ts" name="UserCouponList">
import { deleteCoupon, getCouponPage } from '@/api/mall/promotion/coupon/coupon'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
defineOptions({ name: 'UserCouponList' })
const { userId }: { userId: number } = defineProps({
userId: {
type: Number,
required: true
}
}) //用户编号
const message = useMessage() // 消息弹窗
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 字典表格数据
// 查询参数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
createTime: [],
status: undefined,
userIds: undefined
})
const queryFormRef = ref() // 搜索的表单
const activeTab = ref('all') // Tab 筛选
const statusTabs = reactive([
{
label: '全部',
value: 'all'
}
])
/** 查询列表 */
const getList = async () => {
loading.value = true
// 执行查询
try {
queryParams.userIds = userId
const data = await getCouponPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields()
handleQuery()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 二次确认
await message.confirm(
'回收将会收回会员领取的待使用的优惠券,已使用的将无法回收,确定要回收所选优惠券吗?'
)
// 发起删除
await deleteCoupon(id)
message.notifySuccess('回收成功')
// 重新加载列表
await getList()
} catch {}
}
/** tab 切换 */
const onTabChange = (tabName) => {
queryParams.status = tabName === 'all' ? undefined : tabName
getList()
}
/** 初始化 **/
onMounted(() => {
getList()
// 设置 statuses 过滤
for (const dict of getIntDictOptions(DICT_TYPE.PROMOTION_COUPON_STATUS)) {
statusTabs.push({
label: dict.label,
value: dict.value as string
})
}
})
</script>
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
</el-col> </el-col>
<!-- 右上角:账户信息 --> <!-- 右上角:账户信息 -->
<el-col :span="10" class="detail-info-item"> <el-col :span="10" class="detail-info-item">
<el-card shadow="never"> <el-card shadow="never" class="h-full">
<template #header> <template #header>
<CardTitle title="账户信息" /> <CardTitle title="账户信息" />
</template> </template>
...@@ -24,34 +24,37 @@ ...@@ -24,34 +24,37 @@
</el-card> </el-card>
</el-col> </el-col>
<!-- 下边:账户明细 --> <!-- 下边:账户明细 -->
<!-- TODO 芋艿:【订单管理】【售后管理】【收藏记录】【优惠劵】 --> <!-- TODO 芋艿:【订单管理】【售后管理】【收藏记录】-->
<el-card header="账户明细" style="width: 100%; margin-top: 20px" shadow="never"> <el-card header="账户明细" style="width: 100%; margin-top: 20px" shadow="never">
<template #header> <template #header>
<CardTitle title="账户明细" /> <CardTitle title="账户明细" />
</template> </template>
<el-tabs v-model="activeName"> <el-tabs>
<el-tab-pane label="积分" name="point"> <el-tab-pane label="积分">
<UserPointList :user-id="id" /> <UserPointList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="签到" name="sign" lazy> <el-tab-pane label="签到" lazy>
<UserSignList :user-id="id" /> <UserSignList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="成长值" name="experience" lazy> <el-tab-pane label="成长值" lazy>
<UserExperienceRecordList :user-id="id" <UserExperienceRecordList :user-id="id" />
/></el-tab-pane> </el-tab-pane>
<!-- TODO @jason:增加一个余额变化; --> <!-- TODO @jason:增加一个余额变化; -->
<el-tab-pane label="余额" name="fourth">余额(WIP)</el-tab-pane> <el-tab-pane label="余额" lazy>余额(WIP)</el-tab-pane>
<el-tab-pane label="收货地址" name="address" lazy> <el-tab-pane label="收货地址" lazy>
<UserAddressList :user-id="id" /> <UserAddressList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="订单管理" name="order" lazy> <el-tab-pane label="订单管理" lazy>
<UserOrderList :user-id="id" /> <UserOrderList :user-id="id" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="售后管理" name="fourth">售后管理(WIP)</el-tab-pane> <el-tab-pane label="售后管理" lazy>售后管理(WIP)</el-tab-pane>
<el-tab-pane label="收藏记录" name="fourth">收藏记录(WIP)</el-tab-pane> <el-tab-pane label="收藏记录" lazy>收藏记录(WIP)</el-tab-pane>
<!-- TODO @疯狂:优惠劵的读取 --> <el-tab-pane label="优惠劵" lazy>
<el-tab-pane label="优惠劵" name="fourth">优惠劵(WIP)</el-tab-pane> <UserCouponList :user-id="id" />
<!-- TODO @疯狂:增加获得分校用户;直接查询出所有;需要体现出是一级还是二级;用户编号、昵称、级别、绑定时间 --> </el-tab-pane>
<el-tab-pane label="推广用户" lazy>
<UserBrokerageList :bind-user-id="id" />
</el-tab-pane>
</el-tabs> </el-tabs>
</el-card> </el-card>
</el-row> </el-row>
...@@ -63,22 +66,23 @@ ...@@ -63,22 +66,23 @@
<script setup lang="ts"> <script setup lang="ts">
import * as UserApi from '@/api/member/user' import * as UserApi from '@/api/member/user'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import UserBasicInfo from './UserBasicInfo.vue'
import UserForm from '@/views/member/user/UserForm.vue' import UserForm from '@/views/member/user/UserForm.vue'
import UserAccountInfo from './UserAccountInfo.vue' import UserAccountInfo from './UserAccountInfo.vue'
import UserAddressList from './UserAddressList.vue' import UserAddressList from './UserAddressList.vue'
import UserBasicInfo from './UserBasicInfo.vue'
import UserBrokerageList from './UserBrokerageList.vue'
import UserCouponList from './UserCouponList.vue'
import UserExperienceRecordList from './UserExperienceRecordList.vue'
import UserOrderList from './UserOrderList.vue'
import UserPointList from './UserPointList.vue' import UserPointList from './UserPointList.vue'
import UserSignList from './UserSignList.vue' import UserSignList from './UserSignList.vue'
import UserExperienceRecordList from './UserExperienceRecordList.vue'
import { CardTitle } from '@/components/Card/index' import { CardTitle } from '@/components/Card/index'
import UserOrderList from '@/views/member/user/detail/UserOrderList.vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
defineOptions({ name: 'MemberDetail' }) defineOptions({ name: 'MemberDetail' })
const activeName = ref('point') // 账户明细 选中的 tabs
const loading = ref(true) // 加载中 const loading = ref(true) // 加载中
const user = ref<UserApi.UserVO>({}) const user = ref<UserApi.UserVO>({} as UserApi.UserVO)
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
......
...@@ -117,28 +117,56 @@ ...@@ -117,28 +117,56 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180px" width="180px"
/> />
<el-table-column label="操作" align="center" width="180px" fixed="right"> <el-table-column
label="操作"
align="center"
width="100px"
fixed="right"
:show-overflow-tooltip="false"
>
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="openDetail(scope.row.id)">详情</el-button> <div class="flex items-center justify-center">
<el-button <el-button link type="primary" @click="openDetail(scope.row.id)">详情</el-button>
link <el-dropdown
type="primary" @command="(command) => handleCommand(command, scope.row)"
@click="openForm('update', scope.row.id)" v-hasPermi="[
v-hasPermi="['member:user:update']" 'member:user:update',
> 'member:user:update-level',
编辑 'member:user:update-point',
</el-button> 'member:user:update-balance'
<!-- todo @jason:增加一个【修改余额】 --> ]"
<!-- todo @疯狂:增加一个【修改积分】,表单是:radio 增加/减少;input 具体的变化积分 --> >
<!-- todo 放到更多菜单中 --> <el-button type="primary" link><Icon icon="ep:d-arrow-right" /> 更多</el-button>
<el-button <template #dropdown>
link <el-dropdown-menu>
type="primary" <el-dropdown-item
@click="updateLevelFormRef.open(scope.row.id)" command="handleUpdate"
v-hasPermi="['member:user:update-level']" v-if="checkPermi(['member:user:update'])"
> >
修改等级 编辑
</el-button> </el-dropdown-item>
<el-dropdown-item
command="handleUpdateLevel"
v-if="checkPermi(['member:user:update-level'])"
>
修改等级
</el-dropdown-item>
<el-dropdown-item
command="handleUpdatePoint"
v-if="checkPermi(['member:user:update-point'])"
>
修改积分
</el-dropdown-item>
<el-dropdown-item
command="handleUpdateBlance"
v-if="checkPermi(['member:user:update-balance'])"
>
修改余额(WIP)
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
...@@ -154,7 +182,9 @@ ...@@ -154,7 +182,9 @@
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<UserForm ref="formRef" @success="getList" /> <UserForm ref="formRef" @success="getList" />
<!-- 修改用户等级弹窗 --> <!-- 修改用户等级弹窗 -->
<UpdateLevelForm ref="updateLevelFormRef" @success="getList" /> <UserLevelUpdateForm ref="updateLevelFormRef" @success="getList" />
<!-- 修改用户积分弹窗 -->
<UserPointUpdateForm ref="updatePointFormRef" @success="getList" />
<!-- 发送优惠券弹窗 --> <!-- 发送优惠券弹窗 -->
<CouponSendForm ref="couponSendFormRef" /> <CouponSendForm ref="couponSendFormRef" />
</template> </template>
...@@ -166,8 +196,10 @@ import UserForm from './UserForm.vue' ...@@ -166,8 +196,10 @@ import UserForm from './UserForm.vue'
import MemberTagSelect from '@/views/member/tag/components/MemberTagSelect.vue' import MemberTagSelect from '@/views/member/tag/components/MemberTagSelect.vue'
import MemberLevelSelect from '@/views/member/level/components/MemberLevelSelect.vue' import MemberLevelSelect from '@/views/member/level/components/MemberLevelSelect.vue'
import MemberGroupSelect from '@/views/member/group/components/MemberGroupSelect.vue' import MemberGroupSelect from '@/views/member/group/components/MemberGroupSelect.vue'
import UpdateLevelForm from '@/views/member/user/UpdateLevelForm.vue' import UserLevelUpdateForm from './UserLevelUpdateForm.vue'
import UserPointUpdateForm from './UserPointUpdateForm.vue'
import CouponSendForm from '@/views/mall/promotion/coupon/components/CouponSendForm.vue' import CouponSendForm from '@/views/mall/promotion/coupon/components/CouponSendForm.vue'
import { checkPermi } from '@/utils/permission'
defineOptions({ name: 'MemberUser' }) defineOptions({ name: 'MemberUser' })
...@@ -189,6 +221,7 @@ const queryParams = reactive({ ...@@ -189,6 +221,7 @@ const queryParams = reactive({
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const updateLevelFormRef = ref() // 修改会员等级表单 const updateLevelFormRef = ref() // 修改会员等级表单
const updatePointFormRef = ref() // 修改会员积分表单
const selectedIds = ref<number[]>([]) // 表格的选中 ID 数组 const selectedIds = ref<number[]>([]) // 表格的选中 ID 数组
/** 查询列表 */ /** 查询列表 */
...@@ -242,6 +275,26 @@ const openCoupon = () => { ...@@ -242,6 +275,26 @@ const openCoupon = () => {
couponSendFormRef.value.open(selectedIds.value) couponSendFormRef.value.open(selectedIds.value)
} }
/** 操作分发 */
const handleCommand = (command: string, row: UserApi.UserVO) => {
switch (command) {
case 'handleUpdate':
openForm('update', row.id)
break
case 'handleUpdateLevel':
updateLevelFormRef.value.open(row.id)
break
case 'handleUpdatePoint':
updatePointFormRef.value.open(row.id)
break
case 'handleUpdateBlance':
// todo @jason:增加一个【修改余额】
break
default:
break
}
}
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment