1301 lines
32 KiB
Plaintext
1301 lines
32 KiB
Plaintext
import { rpcOrValue } from '@/services/analytics/rpc.uts'
|
|
import supa from '@/components/supadb/aksupainstance.uts'
|
|
|
|
/**
|
|
* 优惠券模板模型
|
|
*/
|
|
export type CouponTemplate = {
|
|
id?: string
|
|
cid?: number
|
|
merchant_id?: string
|
|
name: string
|
|
description: string | null
|
|
coupon_type: number // 1:满减, 2:折扣, 3:免运费
|
|
discount_type: number // 1:固定金额, 2:百分比
|
|
discount_value: number
|
|
min_order_amount: number
|
|
max_discount_amount: number | null
|
|
total_quantity: number | null
|
|
per_user_limit: number
|
|
usage_limit: number
|
|
applicable_products: any[]
|
|
applicable_categories: any[]
|
|
start_time: string
|
|
end_time: string
|
|
status: number // 1:正常, 2:暂停, 3:已结束
|
|
created_at?: string
|
|
updated_at?: string
|
|
}
|
|
|
|
/**
|
|
* 积分统计模型
|
|
*/
|
|
export type IntegralStats = {
|
|
totals: {
|
|
current: number
|
|
income: number
|
|
expend: number
|
|
}
|
|
trend: Array<{
|
|
date_group: string
|
|
income: number
|
|
expend: number
|
|
}>
|
|
sources: Array<{
|
|
label: string
|
|
value: number
|
|
percent: number
|
|
}>
|
|
consumes: Array<{
|
|
label: string
|
|
value: number
|
|
percent: number
|
|
}>
|
|
}
|
|
|
|
export type CouponQuery = {
|
|
name?: string | null
|
|
type?: number | null
|
|
status?: number | null
|
|
page?: number
|
|
pageSize?: number
|
|
}
|
|
|
|
function getCurrentUid(): string | null {
|
|
try {
|
|
const session = supa.getSession()
|
|
return session?.user?.getString('id') ?? null
|
|
} catch (e) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 分页获取优惠券模板列表
|
|
*/
|
|
export async function fetchAdminCoupons(query?: CouponQuery): Promise<{ total: number; items: CouponTemplate[] }> {
|
|
let q = supa.from('ml_coupon_templates').select('*', { count: 'exact' })
|
|
|
|
if (query?.name != null && query.name !== '') {
|
|
q = q.ilike('name', `%${query.name}%`)
|
|
}
|
|
if (query?.type != null) {
|
|
q = q.eq('coupon_type', query.type)
|
|
}
|
|
if (query?.status != null) {
|
|
q = q.eq('status', query.status)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取优惠券列表失败:', error)
|
|
return { total: 0, items: [] as CouponTemplate[] }
|
|
}
|
|
|
|
return {
|
|
total: count ?? 0,
|
|
items: (data ?? []) as CouponTemplate[]
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取积分统计概况
|
|
*/
|
|
export async function fetchIntegralStats(startTime: string, endTime: string): Promise<IntegralStats | null> {
|
|
const res = await rpcOrValue('rpc_admin_get_integral_stats', {
|
|
p_start_time: startTime,
|
|
p_end_time: endTime
|
|
} as any)
|
|
|
|
return res as IntegralStats | null
|
|
}
|
|
|
|
/**
|
|
* 保存优惠券模板(新增/更新)
|
|
*/
|
|
export async function saveCouponTemplate(tpl: CouponTemplate): Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
|
|
const { error } = await supa
|
|
.from('ml_coupon_templates')
|
|
.upsert({
|
|
...tpl,
|
|
merchant_id: uid,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('保存优惠券模板失败:', error)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 签到奖励项
|
|
*/
|
|
export type SignInReward = {
|
|
day : number
|
|
points : number
|
|
}
|
|
|
|
/**
|
|
* 签到配置模型
|
|
*/
|
|
export type SignInConfig = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
is_enabled : boolean
|
|
daily_points : number
|
|
continuous_rewards : SignInReward[]
|
|
rules_description : string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取签到规则配置
|
|
*/
|
|
export async function fetchSignInConfig() : Promise<SignInConfig | null> {
|
|
const { data, error } = await supa
|
|
.from('ak_signin_configs')
|
|
.select('*')
|
|
.eq('id', 'signin_config')
|
|
.single()
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
// 如果是 406 或不存在,返回 null 由页面处理初始化
|
|
return null
|
|
}
|
|
return data as SignInConfig | null
|
|
}
|
|
|
|
/**
|
|
* 保存签到规则配置
|
|
*/
|
|
export async function saveSignInConfig(config : SignInConfig) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
|
|
const { error } = await supa
|
|
.from('ak_signin_configs')
|
|
.upsert({
|
|
...config,
|
|
id: 'signin_config',
|
|
merchant_id: uid,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('保存签到配置失败:', error)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 签到配置模型(含模式、提醒、积分、经验)
|
|
*/
|
|
export type CheckinConfig = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
is_open : boolean
|
|
mode : string // 'none' | 'week' | 'month'
|
|
notice_enabled : boolean
|
|
integral_reward : number
|
|
exp_reward : number
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取签到配置(扩展版)
|
|
*/
|
|
export async function fetchCheckinConfig() : Promise<CheckinConfig | null> {
|
|
const { data, error } = await supa
|
|
.from('ak_signin_configs')
|
|
.select('*')
|
|
.eq('id', 'signin_config')
|
|
.single()
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
return null
|
|
}
|
|
return data as CheckinConfig | null
|
|
}
|
|
|
|
/**
|
|
* 保存签到配置(扩展版)
|
|
*/
|
|
export async function saveCheckinConfig(config : CheckinConfig) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
|
|
const { error } = await supa
|
|
.from('ak_signin_configs')
|
|
.upsert({
|
|
...config,
|
|
id: 'signin_config',
|
|
merchant_id: uid,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('保存签到配置(扩展)失败:', error)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 切换优惠券状态
|
|
*/
|
|
export async function toggleCouponStatus(id: string, isOpen: boolean): Promise<boolean> {
|
|
const nextStatus = isOpen ? 1 : 2 // 1:正常, 2:暂停
|
|
const { error } = await supa
|
|
.from('ml_coupon_templates')
|
|
.update({ status: nextStatus, updated_at: new Date().toISOString() })
|
|
.eq('id', id)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('更新优惠券状态失败:', error)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* 秒杀活动模型
|
|
*/
|
|
export type SeckillActivity = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
title : string
|
|
single_limit : number
|
|
total_limit : number
|
|
product_count : number
|
|
time_range : string
|
|
start_date : string
|
|
end_date : string
|
|
status : boolean
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 拼团活动模型
|
|
*/
|
|
export type CombinationActivity = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
uid : string
|
|
nickname ?: string
|
|
avatar ?: string
|
|
product_id : string
|
|
title ?: string
|
|
cid ?: number
|
|
people : number
|
|
count_people : number
|
|
start_time : string
|
|
stop_time : string
|
|
status : string // ongoing, pending, ended
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取秒杀活动列表
|
|
*/
|
|
export async function fetchSeckillActivities(query ?: { search ?: string, status ?: boolean, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : SeckillActivity[] }> {
|
|
let q = supa.from('ak_seckill_activities').select('*', { count: 'exact' })
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.ilike('title', `%${query.search}%`)
|
|
}
|
|
if (query?.status != null) {
|
|
q = q.eq('status', query.status)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取秒杀列表失败:', error)
|
|
return { total: 0, items: [] as SeckillActivity[] }
|
|
}
|
|
return { total: count ?? 0, items: (data ?? []) as SeckillActivity[] }
|
|
}
|
|
|
|
/**
|
|
* 保存秒杀活动
|
|
*/
|
|
export async function saveSeckillActivity(activity : SeckillActivity) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_seckill_activities')
|
|
.upsert({ ...activity, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除秒杀活动
|
|
*/
|
|
export async function deleteSeckillActivity(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_seckill_activities').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取拼团活动列表
|
|
*/
|
|
export async function fetchCombinationActivities(query ?: { status ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : CombinationActivity[] }> {
|
|
// 关联用户和商品信息展示
|
|
let q = supa.from('ak_combination_activities').select('*, ak_users!uid(username, avatar_url), ml_products!product_id(name, cid)', { count: 'exact' })
|
|
if (query?.status != null && query.status !== '') {
|
|
q = q.eq('status', query.status)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取拼团列表失败:', error)
|
|
return { total: 0, items: [] as CombinationActivity[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : CombinationActivity => {
|
|
return {
|
|
...item,
|
|
nickname: item.ak_users?.username,
|
|
avatar: item.ak_users?.avatar_url,
|
|
title: item.ml_products?.name,
|
|
cid: item.ml_products?.cid
|
|
} as CombinationActivity
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 获取拼团统计数据
|
|
*/
|
|
export async function getCombinationStats() : Promise<{ participantCount : number, successCount : number }> {
|
|
const { data: participants, error: err1 } = await supa.from('ak_combination_activities').select('count_people').execute()
|
|
const { count: successCount, error: err2 } = await supa.from('ak_combination_activities').select('*', { count: 'exact', head: true }).eq('status', 'ended').execute()
|
|
|
|
let totalParticipants = 0
|
|
if (participants != null) {
|
|
participants.forEach((p: any) => {
|
|
totalParticipants += (p.count_people as number)
|
|
})
|
|
}
|
|
|
|
return {
|
|
participantCount: totalParticipants,
|
|
successCount: successCount ?? 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 立即成团
|
|
*/
|
|
export async function completeCombinationGroup(id : string) : Promise<boolean> {
|
|
const { error } = await supa
|
|
.from('ak_combination_activities')
|
|
.update({ status: 'ended', updated_at: new Date().toISOString() })
|
|
.eq('id', id)
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 砍价活动模型
|
|
*/
|
|
export type BargainActivity = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
product_id : string
|
|
product_name ?: string
|
|
product_image ?: string
|
|
title : string
|
|
min_price : number
|
|
stock : number
|
|
start_time : string
|
|
stop_time : string
|
|
status : boolean
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 团购活动模型
|
|
*/
|
|
export type GroupbuyActivity = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
product_id : string
|
|
product_name ?: string
|
|
product_image ?: string
|
|
title : string
|
|
price : number
|
|
people : number
|
|
stock : number
|
|
start_time : string
|
|
stop_time : string
|
|
status : boolean
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取砍价活动列表
|
|
*/
|
|
export async function fetchBargainActivities(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : BargainActivity[] }> {
|
|
let q = supa.from('ak_marketing_bargains').select('*, ml_products!product_id(name, main_image_url)', { count: 'exact' })
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.ilike('title', `%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取砍价列表失败:', error)
|
|
return { total: 0, items: [] as BargainActivity[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : BargainActivity => {
|
|
return {
|
|
...item,
|
|
product_name: item.ml_products?.name,
|
|
product_image: item.ml_products?.main_image_url
|
|
} as BargainActivity
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 保存砍价活动
|
|
*/
|
|
export async function saveBargainActivity(activity : BargainActivity) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_bargains')
|
|
.upsert({ ...activity, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除砍价活动
|
|
*/
|
|
export async function deleteBargainActivity(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_bargains').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取团购活动列表
|
|
*/
|
|
export async function fetchGroupbuyActivities(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : GroupbuyActivity[] }> {
|
|
let q = supa.from('ak_marketing_groupbuys').select('*, ml_products!product_id(name, main_image_url)', { count: 'exact' })
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.ilike('title', `%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取团购列表失败:', error)
|
|
return { total: 0, items: [] as GroupbuyActivity[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : GroupbuyActivity => {
|
|
return {
|
|
...item,
|
|
product_name: item.ml_products?.name,
|
|
product_image: item.ml_products?.main_image_url
|
|
} as GroupbuyActivity
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 保存团购活动
|
|
*/
|
|
export async function saveGroupbuyActivity(activity : GroupbuyActivity) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_groupbuys')
|
|
.upsert({ ...activity, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除团购活动
|
|
*/
|
|
export async function deleteGroupbuyActivity(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_groupbuys').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 抽奖活动模型
|
|
*/
|
|
export type LotteryActivity = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
name : string
|
|
type : number // 1积分抽奖, 2订单评价, 3订单支付
|
|
start_time : string
|
|
end_time : string
|
|
is_open : boolean
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
// 统计字段
|
|
memberCount ?: number
|
|
winningMemberCount ?: number
|
|
lotteryTimes ?: number
|
|
winningTimes ?: number
|
|
}
|
|
|
|
/**
|
|
* 抽奖奖品模型
|
|
*/
|
|
export type LotteryPrize = {
|
|
id ?: string
|
|
lottery_id : string
|
|
name : string
|
|
prize_type : string // points, balance, coupon, physical
|
|
amount : number
|
|
stock : number
|
|
probability : number
|
|
sort_order : number
|
|
}
|
|
|
|
/**
|
|
* 获取抽奖活动列表
|
|
*/
|
|
export async function fetchLotteryList(query ?: { search ?: string, status ?: number, type ?: number, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : LotteryActivity[] }> {
|
|
let q = supa.from('ak_marketing_lotteries').select('*', { count: 'exact' })
|
|
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.ilike('name', `%${query.search}%`)
|
|
}
|
|
if (query?.type != null && query.type !== 0) {
|
|
q = q.eq('type', query.type)
|
|
}
|
|
// status 过滤逻辑通常涉及时间判断,此处简化为 is_open
|
|
if (query?.status != null && query.status !== 0) {
|
|
q = q.eq('is_open', query.status === 1)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取抽奖列表失败:', error)
|
|
return { total: 0, items: [] as LotteryActivity[] }
|
|
}
|
|
|
|
return { total: count ?? 0, items: (data ?? []) as LotteryActivity[] }
|
|
}
|
|
|
|
/**
|
|
* 保存抽奖活动
|
|
*/
|
|
export async function saveLotteryActivity(activity : LotteryActivity) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_lotteries')
|
|
.upsert({ ...activity, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除抽奖活动
|
|
*/
|
|
export async function deleteLotteryActivity(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_lotteries').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 主播模型
|
|
*/
|
|
export type LiveAnchor = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
nickname : string
|
|
wechat : string | null
|
|
phone : string | null
|
|
avatar_url : string | null
|
|
status : boolean
|
|
created_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 直播间模型
|
|
*/
|
|
export type LiveRoom = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
anchor_id : string | null
|
|
name : string
|
|
background_url : string | null
|
|
share_img_url : string | null
|
|
start_time : string
|
|
end_time : string
|
|
sort : number
|
|
type : string
|
|
like_enabled : boolean
|
|
sale_enabled : boolean
|
|
comment_enabled : boolean
|
|
is_show : boolean
|
|
live_status : number // 1未开始, 2直播中, 3暂停, 4已结束
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
// 关联字段
|
|
anchor_nick ?: string
|
|
anchor_wechat ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取直播间列表
|
|
*/
|
|
export async function fetchLiveRooms(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : LiveRoom[] }> {
|
|
let q = supa.from('ak_marketing_live_rooms').select('*, ak_marketing_live_anchors(nickname, wechat)', { count: 'exact' })
|
|
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.or(`name.ilike.%${query.search}%,ak_marketing_live_anchors.nickname.ilike.%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('sort', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取直播间列表失败:', error)
|
|
return { total: 0, items: [] as LiveRoom[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : LiveRoom => {
|
|
return {
|
|
...item,
|
|
anchor_nick: item.ak_marketing_live_anchors?.nickname,
|
|
anchor_wechat: item.ak_marketing_live_anchors?.wechat
|
|
} as LiveRoom
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 保存直播间
|
|
*/
|
|
export async function saveLiveRoom(room : LiveRoom) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_live_rooms')
|
|
.upsert({ ...room, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除直播间
|
|
*/
|
|
export async function deleteLiveRoom(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_live_rooms').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取主播列表
|
|
*/
|
|
export async function fetchLiveAnchors() : Promise<LiveAnchor[]> {
|
|
const { data, error } = await supa
|
|
.from('ak_marketing_live_anchors')
|
|
.select('*')
|
|
.eq('status', true)
|
|
.order('created_at', { ascending: false })
|
|
.execute()
|
|
|
|
if (error != null) return [] as LiveAnchor[]
|
|
return (data ?? []) as LiveAnchor[]
|
|
}
|
|
|
|
/**
|
|
* 保存主播
|
|
*/
|
|
export async function saveLiveAnchor(anchor : LiveAnchor) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_live_anchors')
|
|
.upsert({ ...anchor, merchant_id: uid })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除主播
|
|
*/
|
|
export async function deleteLiveAnchor(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_live_anchors').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 会员卡类型模型
|
|
*/
|
|
export type MemberType = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
name : string
|
|
duration_days : number
|
|
price : number
|
|
discount_price : number
|
|
is_open : boolean
|
|
sort_order : number
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 会员权益模型
|
|
*/
|
|
export type MemberRight = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
name : string
|
|
description : string | null
|
|
icon_url : string | null
|
|
is_show : boolean
|
|
sort_order : number
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 会员基础配置模型
|
|
*/
|
|
export type MemberConfig = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
is_enabled : boolean
|
|
bg_img_url : string | null
|
|
expire_bg_img_url : string | null
|
|
rules_description : string | null
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取会员类型列表
|
|
*/
|
|
export async function fetchMemberTypes() : Promise<MemberType[]> {
|
|
const { data, error } = await supa
|
|
.from('ak_marketing_member_types')
|
|
.select('*')
|
|
.order('sort_order', { ascending: true })
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取会员类型失败:', error)
|
|
return [] as MemberType[]
|
|
}
|
|
return (data ?? []) as MemberType[]
|
|
}
|
|
|
|
/**
|
|
* 保存会员类型
|
|
*/
|
|
export async function saveMemberType(item : MemberType) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_member_types')
|
|
.upsert({ ...item, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除会员类型
|
|
*/
|
|
export async function deleteMemberType(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_member_types').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取会员权益列表
|
|
*/
|
|
export async function fetchMemberRights() : Promise<MemberRight[]> {
|
|
const { data, error } = await supa
|
|
.from('ak_marketing_member_rights')
|
|
.select('*')
|
|
.order('sort_order', { ascending: true })
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取会员权益失败:', error)
|
|
return [] as MemberRight[]
|
|
}
|
|
return (data ?? []) as MemberRight[]
|
|
}
|
|
|
|
/**
|
|
* 保存会员权益
|
|
*/
|
|
export async function saveMemberRight(item : MemberRight) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_member_rights')
|
|
.upsert({ ...item, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除会员权益
|
|
*/
|
|
export async function deleteMemberRight(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_marketing_member_rights').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取会员基础配置
|
|
*/
|
|
export async function fetchMemberConfig() : Promise<MemberConfig | null> {
|
|
const { data, error } = await supa
|
|
.from('ak_marketing_member_config')
|
|
.select('*')
|
|
.eq('id', 'member_config')
|
|
.single()
|
|
.execute()
|
|
|
|
if (error != null) return null
|
|
return data as MemberConfig | null
|
|
}
|
|
|
|
/**
|
|
* 保存会员基础配置
|
|
*/
|
|
export async function saveMemberConfig(config : MemberConfig) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_member_config')
|
|
.upsert({ ...config, id: 'member_config', merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 充值基础配置模型
|
|
*/
|
|
export type RechargeConfig = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
balance_enabled : boolean
|
|
recharge_notice : string | null
|
|
mp_recharge_enabled : boolean
|
|
min_recharge_amount : number
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 充值额度模板模型
|
|
*/
|
|
export type RechargeQuota = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
price : number
|
|
bonus_price : number
|
|
is_open : boolean
|
|
sort_order : number
|
|
created_at ?: string
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 充值记录模型
|
|
*/
|
|
export type RechargeRecord = {
|
|
id : string
|
|
uid : string
|
|
nickname ?: string
|
|
avatar ?: string
|
|
order_no : string
|
|
recharge_type : string
|
|
price : number
|
|
give_price : number
|
|
paid : number
|
|
pay_time : string | null
|
|
created_at : string
|
|
}
|
|
|
|
/**
|
|
* 新人礼配置模型
|
|
*/
|
|
export type NewcomerConfig = {
|
|
id ?: string
|
|
merchant_id ?: string
|
|
balance_reward : number
|
|
integral_reward : number
|
|
coupons_json : any[]
|
|
updated_at ?: string
|
|
}
|
|
|
|
/**
|
|
* 获取充值基础配置
|
|
*/
|
|
export async function fetchRechargeConfig() : Promise<RechargeConfig | null> {
|
|
const { data, error } = await supa
|
|
.from('ak_recharge_configs')
|
|
.select('*')
|
|
.eq('id', 'recharge_config')
|
|
.single()
|
|
.execute()
|
|
|
|
if (error != null) return null
|
|
return data as RechargeConfig | null
|
|
}
|
|
|
|
/**
|
|
* 获取新人礼配置
|
|
*/
|
|
export async function fetchNewcomerConfig() : Promise<NewcomerConfig | null> {
|
|
const { data, error } = await supa
|
|
.from('ak_marketing_newcomer_config')
|
|
.select('*')
|
|
.eq('id', 'newcomer_config')
|
|
.single()
|
|
.execute()
|
|
|
|
if (error != null) return null
|
|
return data as NewcomerConfig | null
|
|
}
|
|
|
|
/**
|
|
* 保存新人礼配置
|
|
*/
|
|
export async function saveNewcomerConfig(config : NewcomerConfig) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_marketing_newcomer_config')
|
|
.upsert({ ...config, id: 'newcomer_config', merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 签到记录模型
|
|
*/
|
|
export type SignInLog = {
|
|
id : string
|
|
uid : string
|
|
nickname ?: string
|
|
avatar ?: string
|
|
points : number
|
|
is_continuous_reward : boolean
|
|
created_at : string
|
|
}
|
|
|
|
/**
|
|
* 用户优惠券记录模型
|
|
*/
|
|
export type UserCouponRecord = {
|
|
id : string
|
|
uid : string
|
|
nickname ?: string
|
|
avatar ?: string
|
|
coupon_id : string
|
|
coupon_name ?: string
|
|
coupon_code : string
|
|
status : number // 1:未使用, 2:已使用, 3:已过期
|
|
received_at : string
|
|
used_at : string | null
|
|
}
|
|
|
|
/**
|
|
* 获取签到记录列表
|
|
*/
|
|
export async function fetchSignInRecords(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : SignInLog[] }> {
|
|
let q = supa.from('ak_marketing_signin_logs').select('*, ak_users!uid(username, avatar_url)', { count: 'exact' })
|
|
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.or(`ak_users.username.ilike.%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取签到记录失败:', error)
|
|
return { total: 0, items: [] as SignInLog[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : SignInLog => {
|
|
return {
|
|
...item,
|
|
nickname: item.ak_users?.username,
|
|
avatar: item.ak_users?.avatar_url
|
|
} as SignInLog
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 获取优惠券领取记录列表
|
|
*/
|
|
export async function fetchCouponReceiveRecords(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : UserCouponRecord[] }> {
|
|
let q = supa.from('ml_user_coupons').select('*, ak_users!uid(username, avatar_url), ml_coupon_templates!template_id(name)', { count: 'exact' })
|
|
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.or(`ak_users.username.ilike.%${query.search}%,coupon_code.ilike.%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('received_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取优惠券领取记录失败:', error)
|
|
return { total: 0, items: [] as UserCouponRecord[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : UserCouponRecord => {
|
|
return {
|
|
...item,
|
|
nickname: item.ak_users?.username,
|
|
avatar: item.ak_users?.avatar_url,
|
|
coupon_name: item.ml_coupon_templates?.name
|
|
} as UserCouponRecord
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 获取积分变动记录列表
|
|
*/
|
|
export async function fetchPointsRecords(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : any[] }> {
|
|
let q = supa.from('ml_user_bill').select('*, ak_users!uid(username, avatar_url)', { count: 'exact' })
|
|
q = q.eq('category', 'integral')
|
|
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.or(`ak_users.username.ilike.%${query.search}%,title.ilike.%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取积分记录失败:', error)
|
|
return { total: 0, items: [] as any[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : any => {
|
|
return {
|
|
...item,
|
|
nickname: item.ak_users?.username,
|
|
avatar: item.ak_users?.avatar_url
|
|
}
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 保存充值基础配置
|
|
*/
|
|
export async function saveRechargeConfig(config : RechargeConfig) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_recharge_configs')
|
|
.upsert({ ...config, id: 'recharge_config', merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取充值额度列表
|
|
*/
|
|
export async function fetchRechargeQuotas() : Promise<RechargeQuota[]> {
|
|
const { data, error } = await supa
|
|
.from('ak_recharge_quotas')
|
|
.select('*')
|
|
.order('sort_order', { ascending: true })
|
|
.execute()
|
|
|
|
if (error != null) return [] as RechargeQuota[]
|
|
return (data ?? []) as RechargeQuota[]
|
|
}
|
|
|
|
/**
|
|
* 保存充值额度
|
|
*/
|
|
export async function saveRechargeQuota(item : RechargeQuota) : Promise<boolean> {
|
|
const uid = getCurrentUid()
|
|
if (uid == null) return false
|
|
const { error } = await supa
|
|
.from('ak_recharge_quotas')
|
|
.upsert({ ...item, merchant_id: uid, updated_at: new Date().toISOString() })
|
|
.execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 删除充值额度
|
|
*/
|
|
export async function deleteRechargeQuota(id : string) : Promise<boolean> {
|
|
const { error } = await supa.from('ak_recharge_quotas').delete().eq('id', id).execute()
|
|
return error == null
|
|
}
|
|
|
|
/**
|
|
* 获取充值记录列表
|
|
*/
|
|
export async function fetchRechargeRecords(query ?: { search ?: string, page ?: number, pageSize ?: number }) : Promise<{ total : number, items : RechargeRecord[] }> {
|
|
// 关联用户昵称展示
|
|
let q = supa.from('ml_user_recharge').select('*, ak_users!uid(username, avatar_url)', { count: 'exact' })
|
|
|
|
if (query?.search != null && query.search !== '') {
|
|
q = q.or(`order_no.ilike.%${query.search}%,ak_users.username.ilike.%${query.search}%`)
|
|
}
|
|
|
|
const p = query?.page ?? 1
|
|
const ps = query?.pageSize ?? 20
|
|
const from = (p - 1) * ps
|
|
const to = from + ps - 1
|
|
|
|
const { data, error, count } = await q
|
|
.order('created_at', { ascending: false })
|
|
.range(from, to)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('获取充值记录失败:', error)
|
|
return { total: 0, items: [] as RechargeRecord[] }
|
|
}
|
|
|
|
const items = (data ?? []).map((item : any) : RechargeRecord => {
|
|
return {
|
|
...item,
|
|
nickname: item.ak_users?.username,
|
|
avatar: item.ak_users?.avatar_url
|
|
} as RechargeRecord
|
|
})
|
|
|
|
return { total: count ?? 0, items }
|
|
}
|
|
|
|
/**
|
|
* 删除优惠券模板
|
|
*/
|
|
export async function deleteCouponTemplate(id: string): Promise<boolean> {
|
|
const { error } = await supa
|
|
.from('ml_coupon_templates')
|
|
.delete()
|
|
.eq('id', id)
|
|
.execute()
|
|
|
|
if (error != null) {
|
|
console.error('删除优惠券失败:', error)
|
|
return false
|
|
}
|
|
return true
|
|
}
|