admin模块接入数据库
This commit is contained in:
180
services/admin/cmsService.uts
Normal file
180
services/admin/cmsService.uts
Normal file
@@ -0,0 +1,180 @@
|
||||
import { rpcOrNull, rpcOrValue, rpcOrEmptyArray } from '@/services/analytics/rpc.uts'
|
||||
|
||||
/**
|
||||
* 文章分类类型
|
||||
*/
|
||||
export type ArticleCategory = {
|
||||
id: string
|
||||
name: string
|
||||
icon: string | null
|
||||
sort: number
|
||||
status: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章简要信息类型 (列表使用)
|
||||
*/
|
||||
export type ArticleItem = {
|
||||
id: string
|
||||
category_id: string
|
||||
category_name: string
|
||||
title: string
|
||||
author: string | null
|
||||
image: string | null
|
||||
description: string | null
|
||||
status: number
|
||||
views: number
|
||||
is_banner: boolean
|
||||
is_hot: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 文章详情类型
|
||||
*/
|
||||
export type ArticleDetail = {
|
||||
id: string
|
||||
category_id: string
|
||||
category_name: string
|
||||
title: string
|
||||
author: string | null
|
||||
image: string | null
|
||||
description: string | null
|
||||
content: string
|
||||
status: number
|
||||
views: number
|
||||
is_banner: boolean
|
||||
is_hot: boolean
|
||||
linked_product_id: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章分类列表
|
||||
*/
|
||||
export async function fetchArticleCategoryPage(
|
||||
page : number,
|
||||
pageSize : number,
|
||||
search : string | null = null
|
||||
) : Promise<{ total : number, items : Array<ArticleCategory> }> {
|
||||
const res = await rpcOrNull('rpc_admin_article_category_list', {
|
||||
p_page: page,
|
||||
p_page_size: pageSize,
|
||||
p_search: search
|
||||
} as UTSJSONObject)
|
||||
|
||||
if (res == null) return { total: 0, items: [] as Array<ArticleCategory> }
|
||||
return {
|
||||
total: (res as any).total as number,
|
||||
items: (res as any).items as Array<ArticleCategory>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文章分类
|
||||
*/
|
||||
export async function saveArticleCategory(
|
||||
id : string | null,
|
||||
name : string,
|
||||
icon : string | null,
|
||||
sort : number,
|
||||
status : number
|
||||
) : Promise<string | null> {
|
||||
const res = await rpcOrValue('rpc_admin_article_category_save', {
|
||||
p_id: id,
|
||||
p_name: name,
|
||||
p_icon: icon,
|
||||
p_sort: sort,
|
||||
p_status: status
|
||||
} as any)
|
||||
return res != null ? String(res) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章分类
|
||||
*/
|
||||
export async function deleteArticleCategory(id : string) : Promise<boolean> {
|
||||
const ok = await rpcOrValue('rpc_admin_article_category_delete', { p_id: id } as any)
|
||||
return ok === true
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文章分类状态
|
||||
*/
|
||||
export async function setArticleCategoryStatus(id : string, status : number) : Promise<boolean> {
|
||||
const ok = await rpcOrValue('rpc_admin_article_category_set_status', { p_id: id, p_status: status } as any)
|
||||
return ok === true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章列表
|
||||
*/
|
||||
export async function fetchArticlePage(
|
||||
page : number,
|
||||
pageSize : number,
|
||||
categoryId : string | null = null,
|
||||
status : number | null = null,
|
||||
search : string | null = null
|
||||
) : Promise<{ total : number, items : Array<ArticleItem> }> {
|
||||
const res = await rpcOrNull('rpc_admin_article_list', {
|
||||
p_page: page,
|
||||
p_page_size: pageSize,
|
||||
p_category_id: categoryId,
|
||||
p_status: status,
|
||||
p_search: search
|
||||
} as UTSJSONObject)
|
||||
|
||||
if (res == null) return { total: 0, items: [] as Array<ArticleItem> }
|
||||
return {
|
||||
total: (res as any).total as number,
|
||||
items: (res as any).items as Array<ArticleItem>
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章详情
|
||||
*/
|
||||
export async function fetchArticleDetail(id : string) : Promise<ArticleDetail | null> {
|
||||
const res = await rpcOrNull('rpc_admin_article_get_detail', { p_id: id } as any)
|
||||
return res as ArticleDetail | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文章内容
|
||||
*/
|
||||
export async function saveArticle(payload : any) : Promise<string | null> {
|
||||
const res = await rpcOrValue('rpc_admin_article_save', {
|
||||
p_id: payload.id ?? null,
|
||||
p_category_id: payload.category_id,
|
||||
p_title: payload.title,
|
||||
p_author: payload.author ?? null,
|
||||
p_image: payload.image ?? null,
|
||||
p_description: payload.description ?? null,
|
||||
p_content: payload.content,
|
||||
p_status: payload.status ?? 0,
|
||||
p_is_banner: payload.is_banner ?? false,
|
||||
p_is_hot: payload.is_hot ?? false,
|
||||
p_linked_product_id: payload.linked_product_id ?? null
|
||||
} as any)
|
||||
return res != null ? String(res) : null
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文章记录
|
||||
*/
|
||||
export async function deleteArticle(id : string) : Promise<boolean> {
|
||||
const ok = await rpcOrValue('rpc_admin_article_delete', { p_id: id } as any)
|
||||
return ok === true
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文章发布状态
|
||||
*/
|
||||
export async function setArticleStatus(id : string, status : number) : Promise<boolean> {
|
||||
const ok = await rpcOrValue('rpc_admin_article_set_status', { p_id: id, p_status: status } as any)
|
||||
return ok === true
|
||||
}
|
||||
199
services/admin/distributionService.uts
Normal file
199
services/admin/distributionService.uts
Normal file
@@ -0,0 +1,199 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
/**
|
||||
* 分销配置模型 (与 ak_distribution_config 表对齐)
|
||||
*/
|
||||
export type DistributionConfig = {
|
||||
id?: string
|
||||
// 分销模式
|
||||
is_enabled: boolean
|
||||
extract_type: string
|
||||
bind_type: string
|
||||
store_brokerage_binding_status: string
|
||||
brokerage_poster_status: string | null
|
||||
brokerage_level: number
|
||||
is_area_manager: boolean
|
||||
is_agent_apply: boolean
|
||||
is_commission_window: boolean
|
||||
|
||||
// 返佣设置
|
||||
is_self_brokerage: boolean
|
||||
is_member_brokerage: boolean
|
||||
brokerage_type: string
|
||||
is_promoter_brokerage: boolean
|
||||
promoter_brokerage_price: number
|
||||
promoter_brokerage_day_max: number
|
||||
store_brokerage_ratio: number
|
||||
store_brokerage_two_ratio: number
|
||||
extract_frozen_time: number
|
||||
|
||||
// 提现设置
|
||||
user_extract_min_price: number
|
||||
extract_bank_list: string
|
||||
extract_type_list: string[]
|
||||
wechat_extract_type: string
|
||||
alipay_extract_type: string
|
||||
user_extract_fee: number
|
||||
|
||||
updated_at?: string
|
||||
updated_by?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 推广员模型
|
||||
*/
|
||||
export type Promoter = {
|
||||
id: string
|
||||
nickname: string
|
||||
name: string
|
||||
phone: string
|
||||
avatar_url: string
|
||||
level: string
|
||||
userCount: number
|
||||
orderCount: number
|
||||
orderAmount: number
|
||||
commissionTotal: number
|
||||
withdrawnAmount: number
|
||||
withdrawCount: number
|
||||
unwithdrawnAmount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分销全局配置
|
||||
*/
|
||||
export async function getDistributionConfig(): Promise<DistributionConfig | null> {
|
||||
const { data, error } = await supa
|
||||
.from('ak_distribution_config')
|
||||
.select('*')
|
||||
.eq('id', 'global_config')
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取分销配置失败:', error)
|
||||
return null
|
||||
}
|
||||
return data as DistributionConfig | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存分销全局配置
|
||||
*/
|
||||
export async function saveDistributionConfig(config: DistributionConfig): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_distribution_config')
|
||||
.upsert({
|
||||
...config,
|
||||
id: 'global_config',
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存分销配置失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销等级模型
|
||||
*/
|
||||
export type DistributionLevel = {
|
||||
id?: string
|
||||
name: string
|
||||
level: number
|
||||
percent1: number
|
||||
percent2: number
|
||||
task_total: number
|
||||
task_finish: number
|
||||
is_visible: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分销等级列表
|
||||
*/
|
||||
export async function getDistributionLevelList(): Promise<DistributionLevel[]> {
|
||||
const { data, error } = await supa
|
||||
.from('ak_distribution_level')
|
||||
.select('*')
|
||||
.order('level', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取分销等级列表失败:', error)
|
||||
return [] as DistributionLevel[]
|
||||
}
|
||||
return data as DistributionLevel[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存/更新分销等级
|
||||
*/
|
||||
export async function saveDistributionLevel(level: DistributionLevel): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_distribution_level')
|
||||
.upsert({
|
||||
...level,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存分销等级失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分销等级
|
||||
*/
|
||||
export async function deleteDistributionLevel(id: string): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_distribution_level')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('删除分销等级失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推广员列表
|
||||
*/
|
||||
export type PromoterListParams = {
|
||||
search?: string | null
|
||||
page?: number
|
||||
pageSize?: number
|
||||
startTime?: string | null
|
||||
endTime?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取推广员列表(聚合统计)
|
||||
*/
|
||||
export async function getPromoterList(params?: PromoterListParams): Promise<Promoter[]> {
|
||||
const payload = {
|
||||
p_search: params?.search ?? null,
|
||||
p_page: params?.page ?? 1,
|
||||
p_page_size: params?.pageSize ?? 20,
|
||||
p_start_time: params?.startTime ?? null,
|
||||
p_end_time: params?.endTime ?? null
|
||||
} as any
|
||||
|
||||
const { data, error } = await supa
|
||||
.rpc('rpc_admin_get_promoter_list', payload as any)
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取推广员列表失败:', error)
|
||||
return [] as Promoter[]
|
||||
}
|
||||
return (data ?? []) as Promoter[]
|
||||
}
|
||||
139
services/admin/marketingService.uts
Normal file
139
services/admin/marketingService.uts
Normal file
@@ -0,0 +1,139 @@
|
||||
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 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 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 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 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
|
||||
}
|
||||
147
services/admin/productLabelService.uts
Normal file
147
services/admin/productLabelService.uts
Normal file
@@ -0,0 +1,147 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
/**
|
||||
* 商品标签分组模型
|
||||
*/
|
||||
export type ProductLabelGroup = {
|
||||
id?: string
|
||||
merchant_id?: string
|
||||
name: string
|
||||
sort_order: number
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 商品标签模型
|
||||
*/
|
||||
export type ProductLabel = {
|
||||
id?: string
|
||||
group_id: string | null
|
||||
merchant_id?: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
show_in_mobile: boolean
|
||||
sort_order: number
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有标签分组
|
||||
*/
|
||||
export async function fetchLabelGroups(): Promise<ProductLabelGroup[]> {
|
||||
const { data, error } = await supa
|
||||
.from('ak_product_label_groups')
|
||||
.select('*')
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取标签分组失败:', error)
|
||||
return [] as ProductLabelGroup[]
|
||||
}
|
||||
return (data ?? []) as ProductLabelGroup[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存标签分组(新增/更新)
|
||||
*/
|
||||
export async function saveLabelGroup(group: ProductLabelGroup): Promise<boolean> {
|
||||
const session = supa.getSession()
|
||||
const uid = session?.user?.getString('id')
|
||||
if (uid == null) return false
|
||||
|
||||
const { error } = await supa
|
||||
.from('ak_product_label_groups')
|
||||
.upsert({
|
||||
...group,
|
||||
merchant_id: uid,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存标签分组失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签分组
|
||||
*/
|
||||
export async function deleteLabelGroup(id: string): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_product_label_groups')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('删除标签分组失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签列表
|
||||
*/
|
||||
export async function fetchLabels(groupId: string | null = null): Promise<ProductLabel[]> {
|
||||
let query = supa.from('ak_product_labels').select('*')
|
||||
if (groupId != null) {
|
||||
query = query.eq('group_id', groupId)
|
||||
}
|
||||
|
||||
const { data, error } = await query
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取标签失败:', error)
|
||||
return [] as ProductLabel[]
|
||||
}
|
||||
return (data ?? []) as ProductLabel[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存标签(新增/更新)
|
||||
*/
|
||||
export async function saveLabel(label: ProductLabel): Promise<boolean> {
|
||||
const session = supa.getSession()
|
||||
const uid = session?.user?.getString('id')
|
||||
if (uid == null) return false
|
||||
|
||||
const { error } = await supa
|
||||
.from('ak_product_labels')
|
||||
.upsert({
|
||||
...label,
|
||||
merchant_id: uid,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存标签失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签
|
||||
*/
|
||||
export async function deleteLabel(id: string): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_product_labels')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('删除标签失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
119
services/admin/productMemberPriceService.uts
Normal file
119
services/admin/productMemberPriceService.uts
Normal file
@@ -0,0 +1,119 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
export type UserLevel = {
|
||||
id: string
|
||||
name: string
|
||||
level_weight: number
|
||||
discount_percent: number
|
||||
is_visible: boolean
|
||||
status: number
|
||||
deleted_at: string | null
|
||||
}
|
||||
|
||||
export type ProductSku = {
|
||||
id: string
|
||||
product_id: string
|
||||
sku_code: string
|
||||
specifications: any
|
||||
price: number
|
||||
stock: number
|
||||
status: number
|
||||
image_url: string | null
|
||||
}
|
||||
|
||||
export type ProductMemberPrice = {
|
||||
id?: string
|
||||
merchant_id?: string
|
||||
product_id: string
|
||||
sku_id: string
|
||||
level_id: string
|
||||
member_price: number
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export type MemberPriceMatrixRow = {
|
||||
sku: ProductSku
|
||||
pricesByLevel: Record<string, number | null>
|
||||
}
|
||||
|
||||
function getCurrentUid(): string | null {
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
const uid = session?.user?.getString('id')
|
||||
return uid ?? null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchActiveUserLevels(): Promise<UserLevel[]> {
|
||||
const { data, error } = await supa
|
||||
.from('ak_user_levels')
|
||||
.select('id,name,level_weight,discount_percent,is_visible,status,deleted_at')
|
||||
.is('deleted_at', null)
|
||||
.eq('status', 1)
|
||||
.eq('is_visible', true)
|
||||
.order('level_weight', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取会员等级失败:', error)
|
||||
return [] as UserLevel[]
|
||||
}
|
||||
return (data ?? []) as UserLevel[]
|
||||
}
|
||||
|
||||
export async function fetchProductSkus(productId: string): Promise<ProductSku[]> {
|
||||
const { data, error } = await supa
|
||||
.from('ml_product_skus')
|
||||
.select('id,product_id,sku_code,specifications,price,stock,status,image_url')
|
||||
.eq('product_id', productId)
|
||||
.order('created_at', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取 SKU 失败:', error)
|
||||
return [] as ProductSku[]
|
||||
}
|
||||
return (data ?? []) as ProductSku[]
|
||||
}
|
||||
|
||||
export async function fetchMemberPrices(productId: string): Promise<ProductMemberPrice[]> {
|
||||
const { data, error } = await supa
|
||||
.from('ak_product_member_prices')
|
||||
.select('id,product_id,sku_id,level_id,member_price,created_at,updated_at')
|
||||
.eq('product_id', productId)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取会员价失败:', error)
|
||||
return [] as ProductMemberPrice[]
|
||||
}
|
||||
return (data ?? []) as ProductMemberPrice[]
|
||||
}
|
||||
|
||||
export async function saveMemberPrices(productId: string, rows: Array<{ sku_id: string; level_id: string; member_price: number }>): Promise<boolean> {
|
||||
const uid = getCurrentUid()
|
||||
if (uid == null) return false
|
||||
|
||||
const payload = rows.map(r => ({
|
||||
merchant_id: uid,
|
||||
product_id: productId,
|
||||
sku_id: r.sku_id,
|
||||
level_id: r.level_id,
|
||||
member_price: r.member_price,
|
||||
updated_at: new Date().toISOString()
|
||||
}))
|
||||
|
||||
const { error } = await supa
|
||||
.from('ak_product_member_prices')
|
||||
.upsert(payload as any)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存会员价失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
85
services/admin/productProtectionService.uts
Normal file
85
services/admin/productProtectionService.uts
Normal file
@@ -0,0 +1,85 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
export type ProductProtection = {
|
||||
id?: string
|
||||
merchant_id?: string
|
||||
name: string
|
||||
description: string
|
||||
icon_url: string | null
|
||||
sort_order: number
|
||||
is_active: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
function getCurrentUid(): string | null {
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
const uid = session?.user?.getString('id')
|
||||
return uid ?? null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchProductProtections(): Promise<ProductProtection[]> {
|
||||
const { data, error } = await supa
|
||||
.from('ak_product_protections')
|
||||
.select('*')
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('获取商品保障失败:', error)
|
||||
return [] as ProductProtection[]
|
||||
}
|
||||
return (data ?? []) as ProductProtection[]
|
||||
}
|
||||
|
||||
export async function saveProductProtection(item: ProductProtection): Promise<boolean> {
|
||||
const uid = getCurrentUid()
|
||||
if (uid == null) return false
|
||||
|
||||
const { error } = await supa
|
||||
.from('ak_product_protections')
|
||||
.upsert({
|
||||
...item,
|
||||
merchant_id: uid,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存商品保障失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function deleteProductProtection(id: string): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_product_protections')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('删除商品保障失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function setProductProtectionActive(id: string, isActive: boolean): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ak_product_protections')
|
||||
.update({ is_active: isActive, updated_at: new Date().toISOString() })
|
||||
.eq('id', id)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('更新商品保障状态失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
85
services/admin/productReviewService.uts
Normal file
85
services/admin/productReviewService.uts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { rpcOrValue } from '@/services/analytics/rpc.uts'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
export type ProductReviewItem = {
|
||||
id: string
|
||||
product_id: string
|
||||
product_name: string
|
||||
product_image: string | null
|
||||
user_id: string
|
||||
username: string | null
|
||||
rating: number
|
||||
content: string | null
|
||||
merchant_reply: string | null
|
||||
status: number
|
||||
created_at: string
|
||||
total_count: number
|
||||
}
|
||||
|
||||
export type ProductReviewQuery = {
|
||||
searchProduct?: string | null
|
||||
searchUser?: string | null
|
||||
status?: number | null
|
||||
startTime?: string | null
|
||||
endTime?: string | null
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export async function fetchAdminProductReviews(query?: ProductReviewQuery): Promise<{ total: number; items: Array<ProductReviewItem> }> {
|
||||
const payload = {
|
||||
p_search_product: query?.searchProduct ?? null,
|
||||
p_search_user: query?.searchUser ?? null,
|
||||
p_status: query?.status ?? null,
|
||||
p_start_time: query?.startTime ?? null,
|
||||
p_end_time: query?.endTime ?? null,
|
||||
p_page: query?.page ?? 1,
|
||||
p_page_size: query?.pageSize ?? 20
|
||||
} as any
|
||||
|
||||
const res = await rpcOrValue('rpc_admin_get_product_reviews', payload as any)
|
||||
const arr = Array.isArray(res) ? (res as Array<any>) : ([] as Array<any>)
|
||||
const total = arr.length > 0 ? parseInt(String(arr[0]?.total_count ?? '0')) : 0
|
||||
return { total, items: arr as Array<ProductReviewItem> }
|
||||
}
|
||||
|
||||
export async function approveProductReview(id: string): Promise<boolean> {
|
||||
const { error } = await supa.from('ml_product_reviews').update({ status: 1, updated_at: new Date().toISOString() }).eq('id', id).execute()
|
||||
if (error != null) {
|
||||
console.error('审核通过失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function rejectProductReview(id: string): Promise<boolean> {
|
||||
const { error } = await supa.from('ml_product_reviews').update({ status: 3, updated_at: new Date().toISOString() }).eq('id', id).execute()
|
||||
if (error != null) {
|
||||
console.error('审核驳回/隐藏失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function replyProductReview(id: string, reply: string): Promise<boolean> {
|
||||
const { error } = await supa
|
||||
.from('ml_product_reviews')
|
||||
.update({ merchant_reply: reply, merchant_replied_at: new Date().toISOString(), updated_at: new Date().toISOString() })
|
||||
.eq('id', id)
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('回复失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function deleteProductReview(id: string): Promise<boolean> {
|
||||
const { error } = await supa.from('ml_product_reviews').update({ status: 2, updated_at: new Date().toISOString() }).eq('id', id).execute()
|
||||
if (error != null) {
|
||||
console.error('删除失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { rpcOrNull, rpcOrValue } from '@/services/analytics/rpc.uts'
|
||||
|
||||
export type AdminProduct = {
|
||||
@@ -9,7 +10,27 @@ export type AdminProduct = {
|
||||
sales: number
|
||||
status: number
|
||||
created_at: string
|
||||
category_id: string
|
||||
category_name: string
|
||||
subtitle: string | null
|
||||
description: string | null
|
||||
product_code: string | null
|
||||
unit: string | null
|
||||
image_urls: string[] | null
|
||||
video_urls: string[] | null
|
||||
tags: string[] | null
|
||||
attributes: any | null
|
||||
}
|
||||
|
||||
export type AdminProductSku = {
|
||||
id?: string
|
||||
product_id?: string
|
||||
sku_code: string
|
||||
specifications: any
|
||||
price: number
|
||||
stock: number
|
||||
status: number
|
||||
image_url: string | null
|
||||
}
|
||||
|
||||
export type ProductPageResult = {
|
||||
@@ -50,6 +71,87 @@ export async function fetchAdminProductPage(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品详情(包含 SKU 列表)
|
||||
*/
|
||||
export async function fetchAdminProductDetail(productId: string): Promise<{ product: AdminProduct | null, skus: AdminProductSku[] }> {
|
||||
const { data: productData, error: productError } = await supa
|
||||
.from('ml_products')
|
||||
.select('*, ml_categories(name)')
|
||||
.eq('id', productId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (productError != null) {
|
||||
console.error('获取商品详情失败:', productError)
|
||||
return { product: null, skus: [] }
|
||||
}
|
||||
|
||||
const { data: skuData, error: skuError } = await supa
|
||||
.from('ml_product_skus')
|
||||
.select('*')
|
||||
.eq('product_id', productId)
|
||||
.execute()
|
||||
|
||||
const product = productData as any
|
||||
if (product != null && product.ml_categories != null) {
|
||||
product.category_name = (product.ml_categories as any).name
|
||||
}
|
||||
|
||||
return {
|
||||
product: product as AdminProduct,
|
||||
skus: (skuData ?? []) as AdminProductSku[]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存商品(新增或更新,含 SKU 关联保存)
|
||||
*/
|
||||
export async function saveAdminProduct(product: Partial<AdminProduct>, skus: AdminProductSku[]): Promise<boolean> {
|
||||
const session = supa.getSession()
|
||||
const uid = session?.user?.getString('id')
|
||||
if (uid == null) return false
|
||||
|
||||
// 1. 保存商品主表
|
||||
const { data: savedProduct, error: productError } = await supa
|
||||
.from('ml_products')
|
||||
.upsert({
|
||||
...product,
|
||||
merchant_id: uid,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.select()
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (productError != null || savedProduct == null) {
|
||||
console.error('保存商品主表失败:', productError)
|
||||
return false
|
||||
}
|
||||
|
||||
const savedProductId = (savedProduct as any).id as string
|
||||
|
||||
// 2. 保存 SKU(简单策略:先删除旧的再插入新的,或使用 upsert)
|
||||
// 注意:如果涉及订单关联,不能简单的物理删除旧 SKU
|
||||
const skuPayload = skus.map(s => ({
|
||||
...s,
|
||||
product_id: savedProductId,
|
||||
updated_at: new Date().toISOString()
|
||||
}))
|
||||
|
||||
const { error: skuError } = await supa
|
||||
.from('ml_product_skus')
|
||||
.upsert(skuPayload as any)
|
||||
.execute()
|
||||
|
||||
if (skuError != null) {
|
||||
console.error('保存 SKU 失败:', skuError)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品状态汇总统计
|
||||
*/
|
||||
|
||||
125
services/admin/productSpecParamService.uts
Normal file
125
services/admin/productSpecParamService.uts
Normal file
@@ -0,0 +1,125 @@
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
|
||||
export type ProductSpecTemplate = {
|
||||
id?: string
|
||||
merchant_id?: string
|
||||
name: string
|
||||
specs: string
|
||||
attrs: string
|
||||
sort_order: number
|
||||
is_active: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export type ProductParamKV = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type ProductParamTemplate = {
|
||||
id?: string
|
||||
merchant_id?: string
|
||||
name: string
|
||||
sort_order: number
|
||||
params: ProductParamKV[]
|
||||
is_active: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
function getCurrentUid(): string | null {
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
const uid = session?.user?.getString('id')
|
||||
return uid ?? null
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------- Spec Templates --------------------
|
||||
export async function fetchSpecTemplates(search: string | null = null): Promise<ProductSpecTemplate[]> {
|
||||
let q = supa.from('ak_product_spec_templates').select('*')
|
||||
if (search != null && search.trim() !== '') {
|
||||
q = q.ilike('name', `%${search.trim()}%`)
|
||||
}
|
||||
const { data, error } = await q.order('sort_order', { ascending: true }).execute()
|
||||
if (error != null) {
|
||||
console.error('获取规格模板失败:', error)
|
||||
return [] as ProductSpecTemplate[]
|
||||
}
|
||||
return (data ?? []) as ProductSpecTemplate[]
|
||||
}
|
||||
|
||||
export async function saveSpecTemplate(tpl: ProductSpecTemplate): Promise<boolean> {
|
||||
const uid = getCurrentUid()
|
||||
if (uid == null) return false
|
||||
|
||||
const { error } = await supa
|
||||
.from('ak_product_spec_templates')
|
||||
.upsert({
|
||||
...tpl,
|
||||
merchant_id: uid,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存规格模板失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function deleteSpecTemplate(id: string): Promise<boolean> {
|
||||
const { error } = await supa.from('ak_product_spec_templates').delete().eq('id', id).execute()
|
||||
if (error != null) {
|
||||
console.error('删除规格模板失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// -------------------- Param Templates --------------------
|
||||
export async function fetchParamTemplates(search: string | null = null): Promise<ProductParamTemplate[]> {
|
||||
let q = supa.from('ak_product_param_templates').select('*')
|
||||
if (search != null && search.trim() !== '') {
|
||||
q = q.ilike('name', `%${search.trim()}%`)
|
||||
}
|
||||
const { data, error } = await q.order('sort_order', { ascending: true }).execute()
|
||||
if (error != null) {
|
||||
console.error('获取参数模板失败:', error)
|
||||
return [] as ProductParamTemplate[]
|
||||
}
|
||||
return (data ?? []) as ProductParamTemplate[]
|
||||
}
|
||||
|
||||
export async function saveParamTemplate(tpl: ProductParamTemplate): Promise<boolean> {
|
||||
const uid = getCurrentUid()
|
||||
if (uid == null) return false
|
||||
|
||||
const { error } = await supa
|
||||
.from('ak_product_param_templates')
|
||||
.upsert({
|
||||
...tpl,
|
||||
merchant_id: uid,
|
||||
updated_at: new Date().toISOString()
|
||||
})
|
||||
.execute()
|
||||
|
||||
if (error != null) {
|
||||
console.error('保存参数模板失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export async function deleteParamTemplate(id: string): Promise<boolean> {
|
||||
const { error } = await supa.from('ak_product_param_templates').delete().eq('id', id).execute()
|
||||
if (error != null) {
|
||||
console.error('删除参数模板失败:', error)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user