添加mock数据
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
// 内网环境 - 本地部署的 Supabase
|
||||
// IP: 192.168.1.62
|
||||
// Kong HTTP Port: 8000
|
||||
|
||||
|
||||
//自己的配置自己解开即可
|
||||
// export const SUPA_URL: string = 'http://192.168.1.61:18000'
|
||||
// export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
||||
@@ -10,8 +12,23 @@
|
||||
// export const SUPA_URL: string = 'http://192.168.1.61:18000'
|
||||
// export const SUPA_KEY: string = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJyb2xlIjogImFub24iLCAiaXNzIjogInN1cGFiYXNlIiwgImlhdCI6IDE3Njk4NDczMzQsICJleHAiOiAyMDg1MjA3MzM0fQ.js-2CS5_cUmf4iVv8aCmmx9iyFsQvLNDbt8YYOngeLU'
|
||||
|
||||
export const SUPA_URL: string = 'http://119.146.131.237:9126'
|
||||
export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
||||
// export const SUPA_URL: string = 'http://119.146.131.237:9126'
|
||||
// export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlLTEiLCJpYXQiOjE3Njk2NzY0OTgsImV4cCI6MTkyNzM1NjQ5OH0.ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
|
||||
|
||||
|
||||
// ★ 医疗项目专属 Supabase 实例
|
||||
// Studio 地址:http://119.146.131.237:9127/project/default/editor/26894?schema=public
|
||||
// Kong API 网关(REST/Auth/Storage):http://119.146.131.237:9126
|
||||
// SUPA_URL 必须填 Kong API 网关地址,不是 Studio 地址
|
||||
// ※ Connect 弹窗显示 localhost:8000 是 self-hosted 实例的默认元数据(SUPABASE_PUBLIC_URL 未改),
|
||||
// 不代表前端请求地址;前端始终用 http://119.146.131.237:9126
|
||||
export const SUPA_URL: string = 'http://119.146.131.237:9129'
|
||||
// anon (public) key,来自 Studio → Project Settings → API → Project API keys → anon / public
|
||||
// ※ 此 key 必须与 Kong 里 ANON_KEY 环境变量一致,否则 Kong 会返回 401 Invalid authentication credentials
|
||||
export const SUPA_KEY: string = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzY4ODMwNjI0LCJleHAiOjE5MjY1MTA2MjR9.mDVl-kIOdRK9v6VTxo0TDF8r7X7xk3PZXazaavHyVvg'
|
||||
|
||||
|
||||
|
||||
|
||||
// WebSocket 实时连接(内网使用 ws:// 而非 wss://)
|
||||
// export const WS_URL: string = 'ws://192.168.1.61:18000/realtime/v1/websocket'
|
||||
@@ -22,6 +39,10 @@ export const WS_URL: string = 'ws://192.168.1.61:18000/realtime/v1/websocket'
|
||||
// export const WS_URL: string = 'ws://119.146.131.237:9126/realtime/v1/websocket'
|
||||
//export const WS_URL: string = 'ws://localhost:18000/realtime/v1/websocket'
|
||||
|
||||
//hzb
|
||||
// 医疗项目 9127 实例 WebSocket(如需启用实时功能请取消注释并填写真实 key 后启用)
|
||||
// export const WS_URL: string = 'ws://119.146.131.237:9127/realtime/v1/websocket'
|
||||
|
||||
// 推送服务地址(用于本地调试,可改为 http://<your-ip>:7301)
|
||||
export const PUSH_SERVER_URL: string = 'http://192.168.1.62:7301'
|
||||
|
||||
@@ -46,4 +67,17 @@ export const HOME_REDIRECT: string = '/pages/main/index'
|
||||
export const TABORPAGE: string = '/pages/main/index'
|
||||
|
||||
// 测试模式:放开任意跳转(禁用启动页/登录/401 的强制重定向)
|
||||
export const IS_TEST_MODE: boolean = true
|
||||
export const IS_TEST_MODE: boolean = true
|
||||
|
||||
/**
|
||||
* 打印当前 Supabase 配置(key 脱敏,只打印前10位+后8位)
|
||||
* 在页面 onLoad 时调用,用于确认运行时实际使用的是哪套配置
|
||||
*/
|
||||
export function logSupaConfig() {
|
||||
const keyLen = SUPA_KEY.length
|
||||
const masked = keyLen > 20
|
||||
? SUPA_KEY.substring(0, 10) + '...' + SUPA_KEY.substring(keyLen - 8)
|
||||
: '(too short)'
|
||||
console.log('[SupaConfig] SUPA_URL :', SUPA_URL)
|
||||
console.log('[SupaConfig] SUPA_KEY :', masked)
|
||||
}
|
||||
@@ -78,6 +78,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, MOCK_BALANCE, MOCK_FINANCE_STATS, getMockFinanceRecords } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type RecordType = {
|
||||
id: string
|
||||
@@ -113,6 +114,10 @@
|
||||
|
||||
methods: {
|
||||
async initMerchantId() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
return
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
||||
@@ -120,6 +125,11 @@
|
||||
},
|
||||
|
||||
async loadBalance() {
|
||||
if (USE_MOCK) {
|
||||
this.balance = MOCK_BALANCE
|
||||
this.stats = MOCK_FINANCE_STATS
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await supa.from('ml_shops').select('balance').eq('merchant_id', this.merchantId).single().execute()
|
||||
|
||||
@@ -138,7 +148,12 @@
|
||||
|
||||
async loadRecords() {
|
||||
this.loading = true
|
||||
|
||||
if (USE_MOCK) {
|
||||
this.records = getMockFinanceRecords(this.currentTab) as RecordType[]
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_wallet_transactions')
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, getMockInventoryStats, MOCK_INVENTORY_PRODUCTS } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type ProductType = {
|
||||
id: string
|
||||
@@ -137,6 +138,10 @@
|
||||
|
||||
methods: {
|
||||
async initMerchantId() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
return
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
if (session != null && session.user != null) {
|
||||
@@ -151,7 +156,21 @@
|
||||
async loadProducts() {
|
||||
if (this.loading && this.page === 1) return
|
||||
this.loading = true
|
||||
|
||||
if (USE_MOCK) {
|
||||
const all = MOCK_INVENTORY_PRODUCTS
|
||||
const filtered: typeof all = []
|
||||
for (let i = 0; i < all.length; i++) {
|
||||
const p = all[i]
|
||||
if (this.currentFilter === 'low' && (p.total_stock <= 0 || p.total_stock > p.warning_stock)) continue
|
||||
if (this.currentFilter === 'out' && p.total_stock !== 0) continue
|
||||
filtered.push(p)
|
||||
}
|
||||
this.products = filtered as ProductType[]
|
||||
this.hasMore = false
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
let query = supa.from('ml_products')
|
||||
.select('id, name, main_image_url, total_stock, warning_stock')
|
||||
@@ -204,6 +223,10 @@
|
||||
},
|
||||
|
||||
async loadStats() {
|
||||
if (USE_MOCK) {
|
||||
this.stats = getMockInventoryStats()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await supa.from('ml_products').select('id, total_stock, warning_stock', { count: 'exact' }).eq('merchant_id', this.merchantId).execute()
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { USE_MOCK, MOCK_MEMBER_LEVELS, MOCK_SERVICE_USERS } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type MemberLevel = {
|
||||
id: string
|
||||
@@ -138,6 +139,11 @@
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = 'mock-merchant'
|
||||
this.loadLevels()
|
||||
return
|
||||
}
|
||||
this.merchantId = uni.getStorageSync('user_id') || ''
|
||||
this.loadLevels()
|
||||
},
|
||||
@@ -154,6 +160,10 @@
|
||||
this.loadUsers();
|
||||
},
|
||||
async loadLevels() {
|
||||
if (USE_MOCK) {
|
||||
this.levels = MOCK_MEMBER_LEVELS as MemberLevel[]
|
||||
return
|
||||
}
|
||||
const res = await supa.from('ml_member_levels').select('*').order('level_rank', { ascending: true }).execute()
|
||||
if (res.data != null) {
|
||||
const raw = res.data as any[]
|
||||
@@ -172,6 +182,10 @@
|
||||
},
|
||||
async loadUsers() {
|
||||
console.log('--- 启动 ak_users 全量加载 (不带 limit 限制) ---');
|
||||
if (USE_MOCK) {
|
||||
this.users = MOCK_SERVICE_USERS as UserInfo[]
|
||||
return
|
||||
}
|
||||
try {
|
||||
// 1. 移除 limit 限制或设置极大值,确保读到全部数据
|
||||
// 同时通过 count 参数确认数据库到底给了多少条
|
||||
|
||||
940
pages/mall/merchant/mock/merchant-mock-data.uts
Normal file
940
pages/mall/merchant/mock/merchant-mock-data.uts
Normal file
@@ -0,0 +1,940 @@
|
||||
/**
|
||||
* 机构端统一 mock 数据源
|
||||
*
|
||||
* 开关说明:
|
||||
* USE_MOCK = true → 各页面使用此文件数据,不请求 Supabase
|
||||
* USE_MOCK = false → 各页面走原有 Supabase 请求逻辑
|
||||
*
|
||||
* 联动锚点(改动此处会自动同步各页面):
|
||||
* 待接单订单数 = PENDING_SHIPMENT_COUNT = 3
|
||||
* 退款中订单数 = REFUND_COUNT = 2
|
||||
* 低库存商品数 = LOW_STOCK_COUNT = 3
|
||||
* 已售罄商品数 = OUT_OF_STOCK_COUNT = 2
|
||||
* 未回复评价数 = PENDING_REVIEW_COUNT = 4
|
||||
*/
|
||||
|
||||
// ─── Mock 总开关 ───────────────────────────────────────────────
|
||||
export const USE_MOCK = true
|
||||
|
||||
// ─── 稳定 mock merchantId(无登录时使用) ──────────────────────
|
||||
export const MOCK_MERCHANT_ID = 'mock-merchant-0001-0000-000000000001'
|
||||
|
||||
// ─── 联动锚点常量 ───────────────────────────────────────────────
|
||||
export const PENDING_SHIPMENT_COUNT = 3
|
||||
export const REFUND_COUNT = 2
|
||||
export const LOW_STOCK_COUNT = 3
|
||||
export const OUT_OF_STOCK_COUNT = 2
|
||||
export const PENDING_REVIEW_COUNT = 4
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 一、店铺信息
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockShopInfoType = {
|
||||
id: string
|
||||
merchant_id: string
|
||||
shop_name: string
|
||||
shop_logo: string
|
||||
shop_banner: string
|
||||
description: string
|
||||
contact_name: string
|
||||
contact_phone: string
|
||||
rating_avg: number
|
||||
total_sales: number
|
||||
status: number
|
||||
}
|
||||
|
||||
export const MOCK_SHOP_INFO: MockShopInfoType = {
|
||||
id: 'mock-shop-0001',
|
||||
merchant_id: MOCK_MERCHANT_ID,
|
||||
shop_name: '骅锋医养综合服务中心',
|
||||
shop_logo: '/static/images/default-shop.png',
|
||||
shop_banner: '/static/images/default-banner.png',
|
||||
description: '专注居家养老、慢病管理与康复护理一体化服务',
|
||||
contact_name: '李医师',
|
||||
contact_phone: '020-88888888',
|
||||
rating_avg: 4.8,
|
||||
total_sales: 386,
|
||||
status: 1
|
||||
}
|
||||
|
||||
export const MOCK_TODAY_STATS = {
|
||||
orders: 7,
|
||||
sales: 2340.00,
|
||||
visitors: 42,
|
||||
conversion: 17
|
||||
}
|
||||
|
||||
export const MOCK_PENDING_COUNTS = {
|
||||
pending_shipment: PENDING_SHIPMENT_COUNT,
|
||||
refund_requests: REFUND_COUNT,
|
||||
low_stock: LOW_STOCK_COUNT,
|
||||
pending_reviews: PENDING_REVIEW_COUNT
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 二、订单 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockOrderItemType = {
|
||||
id: string
|
||||
order_id: string
|
||||
product_id: string
|
||||
sku_id: string
|
||||
product_name: string
|
||||
sku_name: string
|
||||
price: number
|
||||
quantity: number
|
||||
image_url: string
|
||||
sku_snapshot: string
|
||||
}
|
||||
|
||||
export type MockOrderType = {
|
||||
id: string
|
||||
order_no: string
|
||||
user_id: string
|
||||
merchant_id: string
|
||||
order_status: number
|
||||
total_amount: number
|
||||
product_amount: number
|
||||
shipping_fee: number
|
||||
paid_amount: number
|
||||
shipping_address: string
|
||||
remark: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
items: MockOrderItemType[]
|
||||
}
|
||||
|
||||
// 服务订单 mock(status: 2=待接单/待上门, 3=服务中, 4=已完成)
|
||||
// 其中 待接单=3条, 服务中=4条, 已完成=8条 → tab计数对齐
|
||||
export const MOCK_ORDERS: MockOrderType[] = [
|
||||
// ── 待接单 3 条 ──────────────────────────────────────────
|
||||
{
|
||||
id: 'order-001', order_no: 'SV2026041300001',
|
||||
user_id: 'user-001', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 2, total_amount: 380.00, product_amount: 380.00,
|
||||
shipping_fee: 0, paid_amount: 380.00,
|
||||
shipping_address: '广州市天河区天河路123号 李奶奶',
|
||||
remark: '老人行动不便,请护士上午到访',
|
||||
created_at: '2026-04-13T08:15:00+08:00', updated_at: '2026-04-13T08:15:00+08:00',
|
||||
items: [{
|
||||
id: 'item-001-1', order_id: 'order-001', product_id: 'prod-001',
|
||||
sku_id: '', product_name: '居家上门护理(半日)', sku_name: '标准护理套餐',
|
||||
price: 380.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-002', order_no: 'SV2026041300002',
|
||||
user_id: 'user-002', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 2, total_amount: 260.00, product_amount: 260.00,
|
||||
shipping_fee: 0, paid_amount: 260.00,
|
||||
shipping_address: '广州市越秀区东风东路456号 张爷爷',
|
||||
remark: '高血压患者,需携带血压计',
|
||||
created_at: '2026-04-13T09:30:00+08:00', updated_at: '2026-04-13T09:30:00+08:00',
|
||||
items: [
|
||||
{
|
||||
id: 'item-002-1', order_id: 'order-002', product_id: 'prod-002',
|
||||
sku_id: '', product_name: '血压血糖监测服务', sku_name: '',
|
||||
price: 180.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
},
|
||||
{
|
||||
id: 'item-002-2', order_id: 'order-002', product_id: 'prod-003',
|
||||
sku_id: '', product_name: '慢病随访服务', sku_name: '',
|
||||
price: 80.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'order-003', order_no: 'SV2026041300003',
|
||||
user_id: 'user-003', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 2, total_amount: 520.00, product_amount: 520.00,
|
||||
shipping_fee: 0, paid_amount: 520.00,
|
||||
shipping_address: '广州市海珠区工业大道789号 王阿姨',
|
||||
remark: '术后第7天,需换药处理',
|
||||
created_at: '2026-04-13T10:00:00+08:00', updated_at: '2026-04-13T10:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-003-1', order_id: 'order-003', product_id: 'prod-004',
|
||||
sku_id: '', product_name: '术后伤口护理', sku_name: '标准换药套餐',
|
||||
price: 260.00, quantity: 2, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
|
||||
// ── 服务中 4 条 ──────────────────────────────────────────
|
||||
{
|
||||
id: 'order-004', order_no: 'SV2026041200004',
|
||||
user_id: 'user-004', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 3, total_amount: 1200.00, product_amount: 1200.00,
|
||||
shipping_fee: 0, paid_amount: 1200.00,
|
||||
shipping_address: '广州市番禺区市桥街101号 陈老先生',
|
||||
remark: '每周三次康复训练',
|
||||
created_at: '2026-04-12T14:00:00+08:00', updated_at: '2026-04-12T14:30:00+08:00',
|
||||
items: [{
|
||||
id: 'item-004-1', order_id: 'order-004', product_id: 'prod-005',
|
||||
sku_id: '', product_name: '上门康复护理(月套餐)', sku_name: '基础康复12次',
|
||||
price: 1200.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-005', order_no: 'SV2026041200005',
|
||||
user_id: 'user-005', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 3, total_amount: 450.00, product_amount: 450.00,
|
||||
shipping_fee: 0, paid_amount: 450.00,
|
||||
shipping_address: '广州市白云区棠景街202号 刘奶奶',
|
||||
remark: '糖尿病足护理,注意消毒',
|
||||
created_at: '2026-04-12T09:00:00+08:00', updated_at: '2026-04-12T11:00:00+08:00',
|
||||
items: [
|
||||
{
|
||||
id: 'item-005-1', order_id: 'order-005', product_id: 'prod-006',
|
||||
sku_id: '', product_name: '糖尿病足专项护理', sku_name: '',
|
||||
price: 280.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
},
|
||||
{
|
||||
id: 'item-005-2', order_id: 'order-005', product_id: 'prod-002',
|
||||
sku_id: '', product_name: '血压血糖监测服务', sku_name: '',
|
||||
price: 170.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'order-006', order_no: 'SV2026041100006',
|
||||
user_id: 'user-006', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 3, total_amount: 680.00, product_amount: 680.00,
|
||||
shipping_fee: 0, paid_amount: 680.00,
|
||||
shipping_address: '广州市荔湾区荔湾路303号 赵大伯',
|
||||
remark: '卒中后遗症,肢体功能训练',
|
||||
created_at: '2026-04-11T10:30:00+08:00', updated_at: '2026-04-11T14:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-006-1', order_id: 'order-006', product_id: 'prod-005',
|
||||
sku_id: '', product_name: '上门康复护理(月套餐)', sku_name: '进阶康复8次',
|
||||
price: 680.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-007', order_no: 'SV2026041100007',
|
||||
user_id: 'user-007', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 3, total_amount: 320.00, product_amount: 320.00,
|
||||
shipping_fee: 0, paid_amount: 320.00,
|
||||
shipping_address: '广州市黄埔区科学城404号 孙阿姨',
|
||||
remark: '鼻饲护理,按时喂食',
|
||||
created_at: '2026-04-11T08:00:00+08:00', updated_at: '2026-04-11T08:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-007-1', order_id: 'order-007', product_id: 'prod-007',
|
||||
sku_id: '', product_name: '鼻饲营养护理', sku_name: '',
|
||||
price: 320.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
|
||||
// ── 已完成 8 条 ──────────────────────────────────────────
|
||||
{
|
||||
id: 'order-008', order_no: 'SV2026041000008',
|
||||
user_id: 'user-001', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 180.00, product_amount: 180.00,
|
||||
shipping_fee: 0, paid_amount: 180.00,
|
||||
shipping_address: '广州市天河区天河路123号 李奶奶',
|
||||
remark: '', created_at: '2026-04-10T09:00:00+08:00', updated_at: '2026-04-10T12:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-008-1', order_id: 'order-008', product_id: 'prod-002',
|
||||
sku_id: '', product_name: '血压血糖监测服务', sku_name: '',
|
||||
price: 180.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-009', order_no: 'SV2026040900009',
|
||||
user_id: 'user-008', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 560.00, product_amount: 560.00,
|
||||
shipping_fee: 0, paid_amount: 560.00,
|
||||
shipping_address: '广州市增城区荔城街505号 郑爷爷',
|
||||
remark: '', created_at: '2026-04-09T10:00:00+08:00', updated_at: '2026-04-09T15:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-009-1', order_id: 'order-009', product_id: 'prod-008',
|
||||
sku_id: '', product_name: '术后营养指导(4次)', sku_name: '',
|
||||
price: 280.00, quantity: 2, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-010', order_no: 'SV2026040800010',
|
||||
user_id: 'user-009', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 260.00, product_amount: 260.00,
|
||||
shipping_fee: 0, paid_amount: 260.00,
|
||||
shipping_address: '广州市从化区街口街606号 吴阿姨',
|
||||
remark: '', created_at: '2026-04-08T14:00:00+08:00', updated_at: '2026-04-08T16:30:00+08:00',
|
||||
items: [{
|
||||
id: 'item-010-1', order_id: 'order-010', product_id: 'prod-003',
|
||||
sku_id: '', product_name: '慢病随访服务', sku_name: '',
|
||||
price: 130.00, quantity: 2, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-011', order_no: 'SV2026040700011',
|
||||
user_id: 'user-010', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 980.00, product_amount: 980.00,
|
||||
shipping_fee: 0, paid_amount: 980.00,
|
||||
shipping_address: '广州市南沙区南沙街707号 何老先生',
|
||||
remark: '', created_at: '2026-04-07T09:00:00+08:00', updated_at: '2026-04-07T17:00:00+08:00',
|
||||
items: [
|
||||
{
|
||||
id: 'item-011-1', order_id: 'order-011', product_id: 'prod-001',
|
||||
sku_id: '', product_name: '居家上门护理(半日)', sku_name: '',
|
||||
price: 380.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
},
|
||||
{
|
||||
id: 'item-011-2', order_id: 'order-011', product_id: 'prod-008',
|
||||
sku_id: '', product_name: '术后营养指导(4次)', sku_name: '',
|
||||
price: 300.00, quantity: 2, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'order-012', order_no: 'SV2026040600012',
|
||||
user_id: 'user-002', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 430.00, product_amount: 430.00,
|
||||
shipping_fee: 0, paid_amount: 430.00,
|
||||
shipping_address: '广州市越秀区东风东路456号 张爷爷',
|
||||
remark: '', created_at: '2026-04-06T10:00:00+08:00', updated_at: '2026-04-06T12:30:00+08:00',
|
||||
items: [{
|
||||
id: 'item-012-1', order_id: 'order-012', product_id: 'prod-009',
|
||||
sku_id: '', product_name: '中医调理推拿(3次)', sku_name: '',
|
||||
price: 430.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-013', order_no: 'SV2026040500013',
|
||||
user_id: 'user-011', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 350.00, product_amount: 350.00,
|
||||
shipping_fee: 0, paid_amount: 350.00,
|
||||
shipping_address: '广州市花都区新华街808号 冯奶奶',
|
||||
remark: '', created_at: '2026-04-05T14:30:00+08:00', updated_at: '2026-04-05T16:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-013-1', order_id: 'order-013', product_id: 'prod-006',
|
||||
sku_id: '', product_name: '糖尿病足专项护理', sku_name: '',
|
||||
price: 350.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-014', order_no: 'SV2026040400014',
|
||||
user_id: 'user-012', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 480.00, product_amount: 480.00,
|
||||
shipping_fee: 0, paid_amount: 480.00,
|
||||
shipping_address: '广州市番禺区大石街909号 蒋大伯',
|
||||
remark: '', created_at: '2026-04-04T09:00:00+08:00', updated_at: '2026-04-04T14:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-014-1', order_id: 'order-014', product_id: 'prod-004',
|
||||
sku_id: '', product_name: '术后伤口护理', sku_name: '',
|
||||
price: 240.00, quantity: 2, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-015', order_no: 'SV2026040300015',
|
||||
user_id: 'user-003', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 4, total_amount: 680.00, product_amount: 680.00,
|
||||
shipping_fee: 0, paid_amount: 680.00,
|
||||
shipping_address: '广州市海珠区工业大道789号 王阿姨',
|
||||
remark: '', created_at: '2026-04-03T10:00:00+08:00', updated_at: '2026-04-03T15:30:00+08:00',
|
||||
items: [{
|
||||
id: 'item-015-1', order_id: 'order-015', product_id: 'prod-005',
|
||||
sku_id: '', product_name: '上门康复护理(月套餐)', sku_name: '',
|
||||
price: 680.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
// 售后订单 mock(status: 0=退款中, 6=退款完成)
|
||||
// 退款中=2条, 退款完成=3条
|
||||
export const MOCK_AFTERSALE_ORDERS: MockOrderType[] = [
|
||||
{
|
||||
id: 'order-ref-001', order_no: 'SV2026041300R01',
|
||||
user_id: 'user-004', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 0, total_amount: 380.00, product_amount: 380.00,
|
||||
shipping_fee: 0, paid_amount: 380.00,
|
||||
shipping_address: '广州市番禺区市桥街101号 陈老先生',
|
||||
remark: '家属临时有事,申请取消本次上门服务',
|
||||
created_at: '2026-04-13T07:00:00+08:00', updated_at: '2026-04-13T07:30:00+08:00',
|
||||
items: [{
|
||||
id: 'item-ref-001-1', order_id: 'order-ref-001', product_id: 'prod-001',
|
||||
sku_id: '', product_name: '居家上门护理(半日)', sku_name: '',
|
||||
price: 380.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-ref-002', order_no: 'SV2026041200R02',
|
||||
user_id: 'user-005', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 0, total_amount: 180.00, product_amount: 180.00,
|
||||
shipping_fee: 0, paid_amount: 180.00,
|
||||
shipping_address: '广州市白云区棠景街202号 刘奶奶',
|
||||
remark: '重复下单需退一单',
|
||||
created_at: '2026-04-12T16:00:00+08:00', updated_at: '2026-04-12T16:10:00+08:00',
|
||||
items: [{
|
||||
id: 'item-ref-002-1', order_id: 'order-ref-002', product_id: 'prod-002',
|
||||
sku_id: '', product_name: '血压血糖监测服务', sku_name: '',
|
||||
price: 180.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-ref-003', order_no: 'SV2026041000R03',
|
||||
user_id: 'user-008', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 6, total_amount: 260.00, product_amount: 260.00,
|
||||
shipping_fee: 0, paid_amount: 260.00,
|
||||
shipping_address: '广州市增城区荔城街505号 郑爷爷',
|
||||
remark: '服务已退款', created_at: '2026-04-10T10:00:00+08:00', updated_at: '2026-04-11T09:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-ref-003-1', order_id: 'order-ref-003', product_id: 'prod-003',
|
||||
sku_id: '', product_name: '慢病随访服务', sku_name: '',
|
||||
price: 130.00, quantity: 2, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-ref-004', order_no: 'SV2026040800R04',
|
||||
user_id: 'user-009', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 6, total_amount: 430.00, product_amount: 430.00,
|
||||
shipping_fee: 0, paid_amount: 430.00,
|
||||
shipping_address: '广州市从化区街口街606号 吴阿姨',
|
||||
remark: '', created_at: '2026-04-08T09:00:00+08:00', updated_at: '2026-04-09T10:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-ref-004-1', order_id: 'order-ref-004', product_id: 'prod-009',
|
||||
sku_id: '', product_name: '中医调理推拿(3次)', sku_name: '',
|
||||
price: 430.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
id: 'order-ref-005', order_no: 'SV2026040600R05',
|
||||
user_id: 'user-010', merchant_id: MOCK_MERCHANT_ID,
|
||||
order_status: 6, total_amount: 320.00, product_amount: 320.00,
|
||||
shipping_fee: 0, paid_amount: 320.00,
|
||||
shipping_address: '广州市南沙区南沙街707号 何老先生',
|
||||
remark: '', created_at: '2026-04-06T14:00:00+08:00', updated_at: '2026-04-07T09:00:00+08:00',
|
||||
items: [{
|
||||
id: 'item-ref-005-1', order_id: 'order-ref-005', product_id: 'prod-007',
|
||||
sku_id: '', product_name: '鼻饲营养护理', sku_name: '',
|
||||
price: 320.00, quantity: 1, image_url: '/static/images/default-product.png', sku_snapshot: ''
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
// 最近5条订单(供 profile 使用,复用上面 MOCK_ORDERS 前5条数据结构)
|
||||
export type MockProfileOrderType = {
|
||||
id: string
|
||||
order_no: string
|
||||
order_status: number
|
||||
total_amount: number
|
||||
created_at: string
|
||||
items: MockOrderItemType[]
|
||||
}
|
||||
|
||||
export function getMockRecentOrders() : MockProfileOrderType[] {
|
||||
const result: MockProfileOrderType[] = []
|
||||
const src = MOCK_ORDERS
|
||||
const cnt = src.length < 5 ? src.length : 5
|
||||
for (let i = 0; i < cnt; i++) {
|
||||
result.push({
|
||||
id: src[i].id,
|
||||
order_no: src[i].order_no,
|
||||
order_status: src[i].order_status,
|
||||
total_amount: src[i].total_amount,
|
||||
created_at: src[i].created_at,
|
||||
items: src[i].items
|
||||
} as MockProfileOrderType)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 订单 tab 计数(由 MOCK_ORDERS 推导,不手写)
|
||||
export function getMockOrderTabCounts() : { pending: number, inprogress: number, completed: number, total: number } {
|
||||
let pending = 0, inprogress = 0, completed = 0
|
||||
for (let i = 0; i < MOCK_ORDERS.length; i++) {
|
||||
const s = MOCK_ORDERS[i].order_status
|
||||
if (s === 2) pending++
|
||||
else if (s === 3) inprogress++
|
||||
else if (s === 4) completed++
|
||||
}
|
||||
return { pending, inprogress, completed, total: MOCK_ORDERS.length }
|
||||
}
|
||||
|
||||
// 售后 tab 计数
|
||||
export function getMockAftersaleTabCounts() : { refunding: number, refunded: number, total: number } {
|
||||
let refunding = 0, refunded = 0
|
||||
for (let i = 0; i < MOCK_AFTERSALE_ORDERS.length; i++) {
|
||||
const s = MOCK_AFTERSALE_ORDERS[i].order_status
|
||||
if (s === 0) refunding++
|
||||
else if (s === 6) refunded++
|
||||
}
|
||||
return { refunding, refunded, total: MOCK_AFTERSALE_ORDERS.length }
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 三、库存 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockInventoryProductType = {
|
||||
id: string
|
||||
name: string
|
||||
main_image_url: string
|
||||
total_stock: number
|
||||
warning_stock: number
|
||||
}
|
||||
|
||||
// 12条,覆盖正常/低库存/售罄
|
||||
// 低库存3条(stock<=warning_stock且>0),售罄2条(stock=0)
|
||||
export const MOCK_INVENTORY_PRODUCTS: MockInventoryProductType[] = [
|
||||
{ id: 'prod-001', name: '居家上门护理(半日)', main_image_url: '/static/images/default-product.png', total_stock: 20, warning_stock: 5 },
|
||||
{ id: 'prod-002', name: '血压血糖监测服务', main_image_url: '/static/images/default-product.png', total_stock: 30, warning_stock: 8 },
|
||||
{ id: 'prod-003', name: '慢病随访服务', main_image_url: '/static/images/default-product.png', total_stock: 25, warning_stock: 5 },
|
||||
{ id: 'prod-004', name: '术后伤口护理', main_image_url: '/static/images/default-product.png', total_stock: 4, warning_stock: 5 }, // 低库存1
|
||||
{ id: 'prod-005', name: '上门康复护理(月套餐)', main_image_url: '/static/images/default-product.png', total_stock: 8, warning_stock: 10 }, // 低库存2
|
||||
{ id: 'prod-006', name: '糖尿病足专项护理', main_image_url: '/static/images/default-product.png', total_stock: 3, warning_stock: 5 }, // 低库存3
|
||||
{ id: 'prod-007', name: '鼻饲营养护理', main_image_url: '/static/images/default-product.png', total_stock: 0, warning_stock: 3 }, // 售罄1
|
||||
{ id: 'prod-008', name: '术后营养指导(4次)', main_image_url: '/static/images/default-product.png', total_stock: 0, warning_stock: 3 }, // 售罄2
|
||||
{ id: 'prod-009', name: '中医调理推拿(3次)', main_image_url: '/static/images/default-product.png', total_stock: 15, warning_stock: 5 },
|
||||
{ id: 'prod-010', name: '老年心理疏导服务', main_image_url: '/static/images/default-product.png', total_stock: 18, warning_stock: 5 },
|
||||
{ id: 'prod-011', name: '居家药品管理服务', main_image_url: '/static/images/default-product.png', total_stock: 22, warning_stock: 5 },
|
||||
{ id: 'prod-012', name: '紧急呼叫响应服务', main_image_url: '/static/images/default-product.png', total_stock: 50, warning_stock: 10 }
|
||||
]
|
||||
|
||||
// 从 mock 推导 stats(与页面 loadStats 逻辑完全一致)
|
||||
export function getMockInventoryStats() : { totalProducts: number, lowStock: number, outOfStock: number } {
|
||||
let total = 0, low = 0, out = 0
|
||||
for (let i = 0; i < MOCK_INVENTORY_PRODUCTS.length; i++) {
|
||||
const p = MOCK_INVENTORY_PRODUCTS[i]
|
||||
total++
|
||||
if (p.total_stock === 0) out++
|
||||
else if (p.total_stock <= p.warning_stock) low++
|
||||
}
|
||||
return { totalProducts: total, lowStock: low, outOfStock: out }
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 四、评价 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockReviewType = {
|
||||
id: string
|
||||
product_id: string
|
||||
product_name: string
|
||||
user_id: string
|
||||
user_name: string
|
||||
user_avatar: string
|
||||
rating: number
|
||||
content: string
|
||||
images: string // JSON 字符串数组,与 parseImages() 兼容
|
||||
reply: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
// 10条,4条待回复(reply=''),6条已回复(reply非空)
|
||||
// PENDING_REVIEW_COUNT = 4 与此处一致
|
||||
export const MOCK_REVIEWS: MockReviewType[] = [
|
||||
// ── 待回复 4 条 ──
|
||||
{
|
||||
id: 'review-001', product_id: 'prod-001', product_name: '居家上门护理(半日)',
|
||||
user_id: 'user-001', user_name: '李*敏', user_avatar: '',
|
||||
rating: 5, content: '护士非常专业,老人很配合,感谢!态度好服务细心,以后还会预约。',
|
||||
images: '[]', reply: '', created_at: '2026-04-13T11:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-002', product_id: 'prod-005', product_name: '上门康复护理(月套餐)',
|
||||
user_id: 'user-006', user_name: '赵*强', user_avatar: '',
|
||||
rating: 4, content: '康复师技术不错,父亲恢复得挺好,就是希望每次时长能再长一点。',
|
||||
images: '[]', reply: '', created_at: '2026-04-12T16:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-003', product_id: 'prod-002', product_name: '血压血糖监测服务',
|
||||
user_id: 'user-007', user_name: '孙*华', user_avatar: '',
|
||||
rating: 5, content: '每次都准时上门,测量数据详细,还会给我们讲解注意事项,非常满意。',
|
||||
images: '[]', reply: '', created_at: '2026-04-11T09:30:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-004', product_id: 'prod-009', product_name: '中医调理推拿(3次)',
|
||||
user_id: 'user-002', user_name: '张*国', user_avatar: '',
|
||||
rating: 4, content: '推拿师手法到位,推完肩颈轻松很多,就是预约时间有点难排。',
|
||||
images: '[]', reply: '', created_at: '2026-04-10T14:00:00+08:00'
|
||||
},
|
||||
// ── 已回复 6 条 ──
|
||||
{
|
||||
id: 'review-005', product_id: 'prod-004', product_name: '术后伤口护理',
|
||||
user_id: 'user-003', user_name: '王*兰', user_avatar: '',
|
||||
rating: 5, content: '术后第一次用这个服务,护士很专业,换药很痛但很快,恢复良好。',
|
||||
images: '[]', reply: '感谢您的信任!我们护理团队会继续为您提供专业服务,祝恢复顺利!',
|
||||
created_at: '2026-04-09T10:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-006', product_id: 'prod-006', product_name: '糖尿病足专项护理',
|
||||
user_id: 'user-005', user_name: '刘*珍', user_avatar: '',
|
||||
rating: 5, content: '护士很有耐心,每次都会叮嘱注意事项,妈妈的伤口恢复得很好。',
|
||||
images: '["\/static\/images\/default-product.png"]',
|
||||
reply: '您好!感谢反馈,糖尿病足护理需要长期坚持,我们会持续陪伴您和家人。',
|
||||
created_at: '2026-04-08T11:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-007', product_id: 'prod-008', product_name: '术后营养指导(4次)',
|
||||
user_id: 'user-009', user_name: '吴*秀', user_avatar: '',
|
||||
rating: 5, content: '营养师给出的饮食方案很合理,家人按方案调整饮食后状态好多了!',
|
||||
images: '[]', reply: '谢谢您的认可!合理饮食是术后恢复的关键,有任何问题随时联系我们。',
|
||||
created_at: '2026-04-07T09:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-008', product_id: 'prod-003', product_name: '慢病随访服务',
|
||||
user_id: 'user-008', user_name: '郑*明', user_avatar: '',
|
||||
rating: 4, content: '随访很规律,每次记录都很详细,给老人建立了完整的健康档案。',
|
||||
images: '[]', reply: '感谢您的评价!定期随访是管理慢性病的重要手段,我们会继续做好记录工作。',
|
||||
created_at: '2026-04-06T14:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-009', product_id: 'prod-007', product_name: '鼻饲营养护理',
|
||||
user_id: 'user-011', user_name: '冯*莲', user_avatar: '',
|
||||
rating: 5, content: '护士操作非常规范,每次来都会检查设备是否正常,很放心。',
|
||||
images: '[]', reply: '您好!鼻饲护理需要严格操作规范,感谢您的信任,我们会坚持做好每一次服务。',
|
||||
created_at: '2026-04-05T10:00:00+08:00'
|
||||
},
|
||||
{
|
||||
id: 'review-010', product_id: 'prod-001', product_name: '居家上门护理(半日)',
|
||||
user_id: 'user-012', user_name: '蒋*华', user_avatar: '',
|
||||
rating: 5, content: '服务很贴心,护士会提前电话确认时间,上门也十分准时,赞!',
|
||||
images: '[]', reply: '感谢您的五星好评!准时守约是我们的基本承诺,期待下次继续为您服务。',
|
||||
created_at: '2026-04-04T15:00:00+08:00'
|
||||
}
|
||||
]
|
||||
|
||||
// filter 逻辑(与页面 currentFilter 对齐)
|
||||
export function getMockReviews(filter: string) : MockReviewType[] {
|
||||
if (filter === 'pending') {
|
||||
const result: MockReviewType[] = []
|
||||
for (let i = 0; i < MOCK_REVIEWS.length; i++) {
|
||||
if (MOCK_REVIEWS[i].reply === '') result.push(MOCK_REVIEWS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (filter === 'replied') {
|
||||
const result: MockReviewType[] = []
|
||||
for (let i = 0; i < MOCK_REVIEWS.length; i++) {
|
||||
if (MOCK_REVIEWS[i].reply !== '') result.push(MOCK_REVIEWS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
return MOCK_REVIEWS
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 五、统计 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockTrendItemType = {
|
||||
day: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
// 趋势数据:today/week/month 三档
|
||||
export const MOCK_TREND_TODAY: MockTrendItemType[] = [
|
||||
{ day: '08时', amount: 260 }, { day: '09时', amount: 380 },
|
||||
{ day: '10时', amount: 520 }, { day: '11时', amount: 180 },
|
||||
{ day: '14时', amount: 350 }, { day: '15时', amount: 430 },
|
||||
{ day: '16时', amount: 220 }
|
||||
]
|
||||
|
||||
export const MOCK_TREND_WEEK: MockTrendItemType[] = [
|
||||
{ day: '周一', amount: 680 }, { day: '周二', amount: 1200 },
|
||||
{ day: '周三', amount: 980 }, { day: '周四', amount: 1560 },
|
||||
{ day: '周五', amount: 2340 }, { day: '周六', amount: 800 },
|
||||
{ day: '周日', amount: 420 }
|
||||
]
|
||||
|
||||
export const MOCK_TREND_MONTH: MockTrendItemType[] = [
|
||||
{ day: '4/1', amount: 2100 }, { day: '4/2', amount: 1800 },
|
||||
{ day: '4/3', amount: 2680 }, { day: '4/4', amount: 3200 },
|
||||
{ day: '4/5', amount: 1950 }, { day: '4/6', amount: 2460 },
|
||||
{ day: '4/7', amount: 980 }
|
||||
]
|
||||
|
||||
// 热门服务(ID与inventory对齐,由orders中统计派生)
|
||||
export type MockStatProductType = {
|
||||
id: string
|
||||
name: string
|
||||
image: string
|
||||
sales: number
|
||||
revenue: number
|
||||
}
|
||||
|
||||
export const MOCK_HOT_PRODUCTS: MockStatProductType[] = [
|
||||
{ id: 'prod-005', name: '上门康复护理(月套餐)', image: '/static/images/default-product.png', sales: 38, revenue: 45600 },
|
||||
{ id: 'prod-001', name: '居家上门护理(半日)', image: '/static/images/default-product.png', sales: 52, revenue: 19760 },
|
||||
{ id: 'prod-002', name: '血压血糖监测服务', image: '/static/images/default-product.png', sales: 67, revenue: 12060 },
|
||||
{ id: 'prod-004', name: '术后伤口护理', image: '/static/images/default-product.png', sales: 29, revenue: 13920 },
|
||||
{ id: 'prod-009', name: '中医调理推拿(3次)', image: '/static/images/default-product.png', sales: 24, revenue: 10320 }
|
||||
]
|
||||
|
||||
// 统计概览(today/week/month 三档)
|
||||
export function getMockStats(range: string) : { todaySales: string, todayOrders: number, todayVisitors: number, conversionRate: number } {
|
||||
if (range === 'today') {
|
||||
return { todaySales: '2340.00', todayOrders: 7, todayVisitors: 42, conversionRate: 17 }
|
||||
}
|
||||
if (range === 'week') {
|
||||
return { todaySales: '7980.00', todayOrders: 28, todayVisitors: 165, conversionRate: 17 }
|
||||
}
|
||||
// month
|
||||
return { todaySales: '31250.00', todayOrders: 98, todayVisitors: 620, conversionRate: 16 }
|
||||
}
|
||||
|
||||
export function getMockTrendData(range: string) : MockTrendItemType[] {
|
||||
if (range === 'today') return MOCK_TREND_TODAY
|
||||
if (range === 'week') return MOCK_TREND_WEEK
|
||||
return MOCK_TREND_MONTH
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 六、结算 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockRecordType = {
|
||||
id: string
|
||||
title: string
|
||||
amount: number
|
||||
type: string // 'order'=收支记录, 'withdraw'=结算/补贴记录
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export const MOCK_BALANCE = '12680.50'
|
||||
|
||||
export const MOCK_FINANCE_STATS = {
|
||||
todayRevenue: '2340.00',
|
||||
monthRevenue: '31250.00',
|
||||
pendingWithdraw: '4200.00'
|
||||
}
|
||||
|
||||
export const MOCK_FINANCE_RECORDS: MockRecordType[] = [
|
||||
// ── type=order 收支记录 ──
|
||||
{ id: 'rec-001', title: '订单收入 SV2026041300001', amount: 380.00, type: 'order', created_at: '2026-04-13T08:20:00+08:00' },
|
||||
{ id: 'rec-002', title: '订单收入 SV2026041300002', amount: 260.00, type: 'order', created_at: '2026-04-13T09:35:00+08:00' },
|
||||
{ id: 'rec-003', title: '订单收入 SV2026041300003', amount: 520.00, type: 'order', created_at: '2026-04-13T10:05:00+08:00' },
|
||||
{ id: 'rec-004', title: '退款扣减 SV2026041300R01', amount: -380.00, type: 'order', created_at: '2026-04-13T07:35:00+08:00' },
|
||||
{ id: 'rec-005', title: '退款扣减 SV2026041200R02', amount: -180.00, type: 'order', created_at: '2026-04-12T16:15:00+08:00' },
|
||||
{ id: 'rec-006', title: '订单收入 SV2026041200004', amount: 1200.00, type: 'order', created_at: '2026-04-12T14:05:00+08:00' },
|
||||
{ id: 'rec-007', title: '订单收入 SV2026041200005', amount: 450.00, type: 'order', created_at: '2026-04-12T09:05:00+08:00' },
|
||||
{ id: 'rec-008', title: '订单收入 SV2026041100006', amount: 680.00, type: 'order', created_at: '2026-04-11T10:35:00+08:00' },
|
||||
// ── type=withdraw 结算/补贴记录 ──
|
||||
{ id: 'rec-009', title: '长者关怀补贴拨付', amount: 2000.00, type: 'withdraw', created_at: '2026-04-10T09:00:00+08:00' },
|
||||
{ id: 'rec-010', title: '月度服务结算', amount: 8600.00, type: 'withdraw', created_at: '2026-04-01T10:00:00+08:00' },
|
||||
{ id: 'rec-011', title: '医保结算入账', amount: 3200.00, type: 'withdraw', created_at: '2026-03-28T14:00:00+08:00' },
|
||||
{ id: 'rec-012', title: '申请提现', amount: -5000.00, type: 'withdraw', created_at: '2026-03-25T09:00:00+08:00' },
|
||||
{ id: 'rec-013', title: '医保结算入账', amount: 2800.00, type: 'withdraw', created_at: '2026-03-15T10:00:00+08:00' }
|
||||
]
|
||||
|
||||
// tab 过滤:record 或 withdraw
|
||||
export function getMockFinanceRecords(tab: string) : MockRecordType[] {
|
||||
if (tab === 'record') {
|
||||
const result: MockRecordType[] = []
|
||||
for (let i = 0; i < MOCK_FINANCE_RECORDS.length; i++) {
|
||||
if (MOCK_FINANCE_RECORDS[i].type === 'order') result.push(MOCK_FINANCE_RECORDS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
if (tab === 'withdraw') {
|
||||
const result: MockRecordType[] = []
|
||||
for (let i = 0; i < MOCK_FINANCE_RECORDS.length; i++) {
|
||||
if (MOCK_FINANCE_RECORDS[i].type === 'withdraw') result.push(MOCK_FINANCE_RECORDS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
return MOCK_FINANCE_RECORDS
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 七、促销活动 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockPromotionType = {
|
||||
id: string
|
||||
name: string
|
||||
type: string // 'coupon' | 'seckill' | 'group'(与 tab 对应)
|
||||
discount_text: string
|
||||
start_time: string
|
||||
end_time: string
|
||||
status: number // 0=未开始, 1=进行中, 2=已结束
|
||||
received_count: number
|
||||
}
|
||||
|
||||
export const MOCK_PROMOTIONS: MockPromotionType[] = [
|
||||
// ── coupon 护理套餐券 ──
|
||||
{ id: 'promo-001', name: '居家护理体验满减券', type: 'coupon', discount_text: '满300减50', start_time: '2026-04-01T00:00:00+08:00', end_time: '2026-04-30T23:59:59+08:00', status: 1, received_count: 42 },
|
||||
{ id: 'promo-002', name: '康复护理月套餐优惠券', type: 'coupon', discount_text: '满1000减120', start_time: '2026-04-10T00:00:00+08:00', end_time: '2026-05-10T23:59:59+08:00', status: 1, received_count: 18 },
|
||||
{ id: 'promo-003', name: '护理新人首单优惠', type: 'coupon', discount_text: '首单立减80元', start_time: '2026-03-01T00:00:00+08:00', end_time: '2026-03-31T23:59:59+08:00', status: 2, received_count: 156 },
|
||||
// ── seckill 康复体验 ──
|
||||
{ id: 'promo-004', name: '上门康复体验特惠', type: 'seckill', discount_text: '原价380,限时198', start_time: '2026-04-13T10:00:00+08:00', end_time: '2026-04-13T22:00:00+08:00', status: 1, received_count: 7 },
|
||||
{ id: 'promo-005', name: '血压血糖检测体验价', type: 'seckill', discount_text: '9.9元体验', start_time: '2026-04-15T09:00:00+08:00', end_time: '2026-04-15T18:00:00+08:00', status: 0, received_count: 0 },
|
||||
{ id: 'promo-006', name: '长者推拿体验', type: 'seckill', discount_text: '原价150,体验价59', start_time: '2026-03-20T09:00:00+08:00', end_time: '2026-03-20T20:00:00+08:00', status: 2, received_count: 23 },
|
||||
// ── group 慢病管理服务包 ──
|
||||
{ id: 'promo-007', name: '高血压慢病三人拼团包', type: 'group', discount_text: '3人拼团享8.5折', start_time: '2026-04-05T00:00:00+08:00', end_time: '2026-04-20T23:59:59+08:00', status: 1, received_count: 9 },
|
||||
{ id: 'promo-008', name: '糖尿病管理服务季包', type: 'group', discount_text: '2人拼团9折', start_time: '2026-04-20T00:00:00+08:00', end_time: '2026-05-20T23:59:59+08:00', status: 0, received_count: 0 },
|
||||
{ id: 'promo-009', name: '慢病管理年度健康计划', type: 'group', discount_text: '3人拼团享7折', start_time: '2026-02-01T00:00:00+08:00', end_time: '2026-03-31T23:59:59+08:00', status: 2, received_count: 34 }
|
||||
]
|
||||
|
||||
// tab 过滤(与 currentTab 对应)
|
||||
export function getMockPromotions(tab: string) : MockPromotionType[] {
|
||||
const result: MockPromotionType[] = []
|
||||
for (let i = 0; i < MOCK_PROMOTIONS.length; i++) {
|
||||
if (MOCK_PROMOTIONS[i].type === tab) result.push(MOCK_PROMOTIONS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 订单筛选(供 orders.uvue 使用)
|
||||
export function getMockOrdersByStatus(status: number) : MockOrderType[] {
|
||||
if (status === -2) return MOCK_ORDERS // 全部
|
||||
const result: MockOrderType[] = []
|
||||
for (let i = 0; i < MOCK_ORDERS.length; i++) {
|
||||
if (MOCK_ORDERS[i].order_status === status) result.push(MOCK_ORDERS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function getMockAftersaleByStatus(status: number) : MockOrderType[] {
|
||||
if (status === -3) return MOCK_AFTERSALE_ORDERS // 全部售后
|
||||
const result: MockOrderType[] = []
|
||||
for (let i = 0; i < MOCK_AFTERSALE_ORDERS.length; i++) {
|
||||
if (MOCK_AFTERSALE_ORDERS[i].order_status === status) result.push(MOCK_AFTERSALE_ORDERS[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
// 八、关怀等级 & 服务对象 mock 数据
|
||||
// ════════════════════════════════════════════════════════════
|
||||
export type MockMemberLevelType = {
|
||||
id: string
|
||||
name: string
|
||||
discount_rate: number
|
||||
level_rank: number
|
||||
}
|
||||
|
||||
export type MockUserInfoType = {
|
||||
id: string
|
||||
username: string
|
||||
email: string
|
||||
nickname: string | null
|
||||
avatar_url: string | null
|
||||
phone: string | null
|
||||
tier_id: string | null
|
||||
tier_name: string | null
|
||||
}
|
||||
|
||||
// 关怀等级 4 档
|
||||
export const MOCK_MEMBER_LEVELS: MockMemberLevelType[] = [
|
||||
{ id: 'level-001', name: '普通关怀', discount_rate: 1.0, level_rank: 1 },
|
||||
{ id: 'level-002', name: '银发关怀', discount_rate: 0.95, level_rank: 2 },
|
||||
{ id: 'level-003', name: '金牌关怀', discount_rate: 0.90, level_rank: 3 },
|
||||
{ id: 'level-004', name: '白金尊享', discount_rate: 0.85, level_rank: 4 }
|
||||
]
|
||||
|
||||
// 服务对象 12 人:4 人有等级(tier_id 非空),8 人暂无等级
|
||||
// tier_name 通过 tier_id 与 MOCK_MEMBER_LEVELS 对应
|
||||
export const MOCK_SERVICE_USERS: MockUserInfoType[] = [
|
||||
// ── 已设置关怀等级 4 人(排在前面) ──
|
||||
{
|
||||
id: 'user-001', username: 'li_min',
|
||||
email: 'limin@example.com',
|
||||
nickname: '李敏奶奶',
|
||||
avatar_url: '',
|
||||
phone: '13800000001',
|
||||
tier_id: 'level-004',
|
||||
tier_name: '白金尊享'
|
||||
},
|
||||
{
|
||||
id: 'user-004', username: 'chen_lao',
|
||||
email: '',
|
||||
nickname: '陈伯伯',
|
||||
avatar_url: '',
|
||||
phone: '13800000004',
|
||||
tier_id: 'level-003',
|
||||
tier_name: '金牌关怀'
|
||||
},
|
||||
{
|
||||
id: 'user-005', username: 'liu_zhen',
|
||||
email: '',
|
||||
nickname: '刘珍阿姨',
|
||||
avatar_url: '',
|
||||
phone: '13800000005',
|
||||
tier_id: 'level-002',
|
||||
tier_name: '银发关怀'
|
||||
},
|
||||
{
|
||||
id: 'user-006', username: 'zhao_qiang',
|
||||
email: 'zhaoqiang@example.com',
|
||||
nickname: '赵强大伯',
|
||||
avatar_url: '',
|
||||
phone: '13800000006',
|
||||
tier_id: 'level-001',
|
||||
tier_name: '普通关怀'
|
||||
},
|
||||
// ── 未设置关怀等级 8 人 ──
|
||||
{
|
||||
id: 'user-002', username: 'zhang_guo',
|
||||
email: '',
|
||||
nickname: '张国爷爷',
|
||||
avatar_url: '',
|
||||
phone: '13800000002',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-003', username: 'wang_lan',
|
||||
email: '',
|
||||
nickname: '王兰阿姨',
|
||||
avatar_url: '',
|
||||
phone: '13800000003',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-007', username: 'sun_hua',
|
||||
email: 'sunhua@example.com',
|
||||
nickname: '孙华叔叔',
|
||||
avatar_url: '',
|
||||
phone: '13800000007',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-008', username: 'zheng_ming',
|
||||
email: '',
|
||||
nickname: '郑明爷爷',
|
||||
avatar_url: '',
|
||||
phone: '13800000008',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-009', username: 'wu_xiu',
|
||||
email: '',
|
||||
nickname: '吴秀奶奶',
|
||||
avatar_url: '',
|
||||
phone: '13800000009',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-010', username: 'he_lao',
|
||||
email: '',
|
||||
nickname: '何老先生',
|
||||
avatar_url: '',
|
||||
phone: '13800000010',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-011', username: 'feng_lian',
|
||||
email: '',
|
||||
nickname: '冯莲奶奶',
|
||||
avatar_url: '',
|
||||
phone: '13800000011',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
},
|
||||
{
|
||||
id: 'user-012', username: 'jiang_hua',
|
||||
email: '',
|
||||
nickname: '蒋华大伯',
|
||||
avatar_url: '',
|
||||
phone: '13800000012',
|
||||
tier_id: null,
|
||||
tier_name: null
|
||||
}
|
||||
]
|
||||
@@ -348,6 +348,7 @@
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import MerchantTabBar from '@/components/merchant-tabbar/MerchantTabBar.uvue'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, getMockOrdersByStatus, getMockAftersaleByStatus, getMockOrderTabCounts, getMockAftersaleTabCounts } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type OrderItemType = {
|
||||
id: string
|
||||
@@ -531,6 +532,10 @@
|
||||
},
|
||||
|
||||
async initMerchantId() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
return
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
if (session != null && session.user != null) {
|
||||
@@ -547,7 +552,14 @@
|
||||
async loadOrders() {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
|
||||
if (USE_MOCK) {
|
||||
const filtered = getMockOrdersByStatus(this.currentTab)
|
||||
this.orders = filtered as OrderType[]
|
||||
this.hasMore = false
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
let query = supa
|
||||
.from('ml_orders')
|
||||
@@ -664,6 +676,18 @@
|
||||
},
|
||||
|
||||
async loadOrderCounts() {
|
||||
if (USE_MOCK) {
|
||||
const c = getMockOrderTabCounts()
|
||||
this.orderTabs[0].count = c.pending
|
||||
this.orderTabs[1].count = c.inprogress
|
||||
this.orderTabs[2].count = c.completed
|
||||
this.orderTabs[3].count = c.total
|
||||
const ac = getMockAftersaleTabCounts()
|
||||
this.aftersaleTabs[0].count = ac.total
|
||||
this.aftersaleTabs[1].count = ac.refunding
|
||||
this.aftersaleTabs[2].count = ac.refunded
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_orders')
|
||||
@@ -740,6 +764,14 @@
|
||||
async loadAftersaleOrders() {
|
||||
if (this.aftersaleLoading) return
|
||||
this.aftersaleLoading = true
|
||||
if (USE_MOCK) {
|
||||
const filtered = getMockAftersaleByStatus(this.currentAftersaleTab)
|
||||
this.aftersaleOrders = filtered as OrderType[]
|
||||
this.aftersaleHasMore = false
|
||||
this.aftersaleLoading = false
|
||||
this.aftersaleRefreshing = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
let query = supa
|
||||
.from('ml_orders')
|
||||
|
||||
@@ -48,20 +48,6 @@
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="label">商品品牌</text>
|
||||
<picker
|
||||
class="picker"
|
||||
:range="brands"
|
||||
range-key="name"
|
||||
:value="brandIndex"
|
||||
@change="onBrandChange"
|
||||
>
|
||||
<view class="picker-value">
|
||||
{{ selectedBrand?.name || '请选择品牌' }}
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品图片 -->
|
||||
@@ -175,25 +161,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 会员阶梯价 -->
|
||||
<view class="section">
|
||||
<view class="section-title">长者关怀价/专属补贴价 (选填)</view>
|
||||
<view class="section-desc">若不填写则按照服务参考价或默认折扣计算</view>
|
||||
|
||||
<view v-for="(level, index) in memberLevels" :key="index" class="form-item">
|
||||
<text class="label">{{ level.name }}价格</text>
|
||||
<view class="price-input">
|
||||
<text class="unit">¥</text>
|
||||
<input
|
||||
class="input"
|
||||
v-model="level.price"
|
||||
type="digit"
|
||||
placeholder="长者关怀价"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 服务属性 -->
|
||||
<view class="section">
|
||||
<view class="section-title">服务属性</view>
|
||||
@@ -261,24 +228,16 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { SUPA_URL, logSupaConfig } from '@/ak/config.uts'
|
||||
|
||||
// 严格对齐 categories.sql 真实字段,禁止自行添加或兼容不存在的字段
|
||||
type CategoryType = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
type BrandType = {
|
||||
id: string
|
||||
name: string
|
||||
logo_url: string
|
||||
}
|
||||
|
||||
type MemberLevelType = {
|
||||
id: string
|
||||
name: string
|
||||
level_rank: number
|
||||
discount_rate: number
|
||||
price: string // 绑定输入框用
|
||||
icon: string
|
||||
description: string
|
||||
color: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -289,15 +248,10 @@
|
||||
categories: [] as CategoryType[],
|
||||
categoryIndex: -1,
|
||||
selectedCategory: null as CategoryType | null,
|
||||
brands: [] as BrandType[],
|
||||
brandIndex: -1,
|
||||
selectedBrand: null as BrandType | null,
|
||||
memberLevels: [] as MemberLevelType[],
|
||||
product: {
|
||||
name: '',
|
||||
subtitle: '',
|
||||
category_id: '',
|
||||
brand_id: '',
|
||||
main_image_url: '',
|
||||
imageList: [] as string[],
|
||||
base_price: '',
|
||||
@@ -317,7 +271,7 @@
|
||||
}
|
||||
},
|
||||
|
||||
onLoad(options: any) {
|
||||
async onLoad(options: any) {
|
||||
let productId = ''
|
||||
if (options) {
|
||||
const keys = Object.keys(options as object)
|
||||
@@ -341,18 +295,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ── 启动诊断日志 ──
|
||||
console.log('[product-edit] onLoad start')
|
||||
console.log('[product-edit] mode:', productId ? '编辑' : '新增')
|
||||
console.log('[product-edit] productId:', productId || '(none)')
|
||||
logSupaConfig()
|
||||
|
||||
if (productId && productId !== '') {
|
||||
this.productId = productId
|
||||
this.isEdit = true
|
||||
uni.setNavigationBarTitle({ title: '编辑服务' })
|
||||
this.loadProductDetail(productId)
|
||||
} else {
|
||||
uni.setNavigationBarTitle({ title: '发布服务' })
|
||||
}
|
||||
this.initMerchantId()
|
||||
this.loadCategories()
|
||||
this.loadBrands()
|
||||
this.loadMemberLevels()
|
||||
|
||||
// ── 先探活,通过后再加载业务数据 ──
|
||||
const endpointOk = await this.checkSupabaseEndpoint()
|
||||
if (!endpointOk) {
|
||||
console.error('[product-edit] Supabase 端点不可用,跳过业务数据加载')
|
||||
return
|
||||
}
|
||||
|
||||
await this.loadCategories()
|
||||
if (this.isEdit && productId !== '') {
|
||||
this.loadProductDetail(productId)
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -370,8 +338,79 @@
|
||||
}
|
||||
},
|
||||
|
||||
async loadMemberLevels() {
|
||||
/**
|
||||
* Supabase 端点联通检查:直接用真实业务表 categories 做 limit(1) 探测。
|
||||
* - 200:端点正常,anon 有读权限
|
||||
* - 401/403:端点可达,但权限不足(打日志,仍返回 true 表示端点在线)
|
||||
* - HTML/非 JSON:SUPA_URL 指向了非 PostgREST 服务 → false
|
||||
* - 网络错误:服务不可达 → false
|
||||
*/
|
||||
async checkSupabaseEndpoint() : Promise<boolean> {
|
||||
const baseUrl = supa.baseUrl
|
||||
console.log('[supa-check] SUPA_URL:', baseUrl)
|
||||
try {
|
||||
const response = await supa
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.limit(1)
|
||||
.execute()
|
||||
|
||||
const status = response.status
|
||||
console.log('[supa-check] categories probe status:', status)
|
||||
|
||||
// ak-req 遇到非 JSON 响应会包装 { raw: '...' }
|
||||
const d = response.data as any
|
||||
const rawText = (d != null && typeof d === 'object' && d['raw'] != null)
|
||||
? String(d['raw'])
|
||||
: null
|
||||
|
||||
if (rawText != null) {
|
||||
const looksHtml = rawText.trimStart().startsWith('<')
|
||||
console.error('[supa-check] ✗ 收到非 JSON 响应,SUPA_URL 可能指向了错误服务(反向代理/Studio UI)')
|
||||
console.error('[supa-check] content looks like html:', looksHtml)
|
||||
console.error('[supa-check] raw preview:', rawText.substring(0, 250))
|
||||
uni.showToast({ title: 'Supabase连接失败,查看日志', icon: 'none', duration: 4000 })
|
||||
return false
|
||||
}
|
||||
|
||||
if (status === 200) {
|
||||
const arr = response.data as any[]
|
||||
console.log('[supa-check] ✓ categories 可读,行数:', arr != null ? arr.length : 0)
|
||||
return true
|
||||
}
|
||||
if (status === 401 || status === 403) {
|
||||
console.error('[supa-check] ⚠ 端点可达但 categories 查询返回', status)
|
||||
console.error('[supa-check] ★ 这是认证层问题,不是 categories 表字段问题')
|
||||
console.error('[supa-check] 请运维核查 self-hosted 实例配置:')
|
||||
console.error('[supa-check] 1. SUPA_KEY 是否属于 SUPA_URL 对应的 Supabase 实例(9126 Kong API 网关)')
|
||||
console.error('[supa-check] 2. 9126 和 9127 是否属于同一套 docker-compose / 同一实例')
|
||||
console.error('[supa-check] 3. Kong 是否已 reload 最新 consumer 配置(ANON_KEY 与 SUPA_KEY 一致)')
|
||||
console.error('[supa-check] 4. categories 表是否有 RLS policy 允许 anon SELECT(认证通过后再查)')
|
||||
console.error('[supa-check] 5. SUPABASE_PUBLIC_URL 是否仍是 localhost:8000(Connect 显示值不代表前端请求地址)')
|
||||
console.error('[supa-check] error:', JSON.stringify(response.error))
|
||||
uni.showToast({ title: '认证失败(401),请运维核查 Supabase 配置', icon: 'none', duration: 4000 })
|
||||
// 401 = 认证不过,暂停后续请求避免日志污染
|
||||
return false
|
||||
}
|
||||
if (status === 404) {
|
||||
console.error('[supa-check] ⚠ categories 表不存在(404)')
|
||||
console.error('[supa-check] 请在 Supabase Studio 确认表名是否为 categories,schema 是否为 public')
|
||||
return true // PostgREST 在线,但表名/schema 可能有误
|
||||
}
|
||||
|
||||
console.log('[supa-check] status:', status, '— PostgREST 在线')
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error('[supa-check] ✗ 异常:', e)
|
||||
uni.showToast({ title: 'Supabase连接失败', icon: 'none', duration: 3000 })
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
async loadMemberLevels() {
|
||||
this.memberLevels = []
|
||||
try {
|
||||
console.log('[member-levels] request start, table: ml_member_levels')
|
||||
const response = await supa
|
||||
.from('ml_member_levels')
|
||||
.select('*')
|
||||
@@ -379,15 +418,22 @@
|
||||
.order('level_rank', { ascending: true })
|
||||
.execute()
|
||||
|
||||
console.log('[member-levels] response status:', response.status)
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('获取会员等级失败:', response.error)
|
||||
console.error('[member-levels] query error:', response.error)
|
||||
console.error('[member-levels] status:', response.status)
|
||||
const d = response.data as any
|
||||
if (d != null && typeof d === 'object' && d['raw'] != null) {
|
||||
console.error('[member-levels] raw preview:', String(d['raw']).substring(0, 200))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data as any[]
|
||||
console.log('[member-levels] raw length:', rawData != null ? rawData.length : 0)
|
||||
if (rawData == null) return
|
||||
|
||||
this.memberLevels = []
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
const item = rawData[i] as any
|
||||
this.memberLevels.push({
|
||||
@@ -398,93 +444,95 @@
|
||||
price: ''
|
||||
} as MemberLevelType)
|
||||
}
|
||||
console.log('[member-levels] loaded:', this.memberLevels.length)
|
||||
|
||||
// 如果是编辑模式,还需要加载已有的会员价
|
||||
if (this.isEdit) {
|
||||
this.loadMemberPrices()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取会员等级异常:', e)
|
||||
console.error('[member-levels] exception:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async loadMemberPrices() {
|
||||
// 严格对齐 categories.sql 真实字段:id, name, icon, description, color, created_at
|
||||
async loadCategories() {
|
||||
this.categories = []
|
||||
try {
|
||||
console.log('[categories] 开始从 categories 表读取分类(严格字段映射)...')
|
||||
const response = await supa
|
||||
.from('ml_product_member_prices')
|
||||
.select('*')
|
||||
.eq('product_id', this.productId)
|
||||
.from('categories')
|
||||
.select('id,name,icon,description,color,created_at')
|
||||
.limit(100)
|
||||
.execute()
|
||||
|
||||
console.log('[categories] response status:', response.status)
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('获取会员价失败:', response.error)
|
||||
console.error('[categories] 查询失败:', response.error)
|
||||
uni.showToast({ title: '分类加载失败', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data as any[]
|
||||
if (rawData == null || rawData.length == 0) return
|
||||
console.log('[categories] raw length:', rawData != null ? rawData.length : 0)
|
||||
|
||||
if (rawData == null || rawData.length === 0) {
|
||||
console.error('[categories] 返回数据为空,请检查:表名/RLS策略/anon key')
|
||||
uni.showToast({ title: '分类加载失败:数据为空', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 严格字段映射:仅接受 categories.sql 定义的 6 个字段
|
||||
// 不再做 category_id/uuid/category_name/title/label 等兼容回退
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
const item = rawData[i] as any
|
||||
const levelId = String(item['level_id'])
|
||||
const price = String(item['member_price'])
|
||||
|
||||
const index = this.memberLevels.findIndex(lv => lv.id === levelId)
|
||||
if (index >= 0) {
|
||||
this.memberLevels[index].price = price
|
||||
}
|
||||
|
||||
const catId = item['id'] != null ? String(item['id']) : ''
|
||||
const catName = item['name'] != null ? String(item['name']) : ''
|
||||
|
||||
// id 或 name 缺失则跳过(categories.sql 中两者均为 NOT NULL)
|
||||
if (catId === '' || catName === '') continue
|
||||
|
||||
this.categories.push({
|
||||
id: catId,
|
||||
name: catName,
|
||||
icon: item['icon'] != null ? String(item['icon']) : '',
|
||||
description: item['description'] != null ? String(item['description']) : '',
|
||||
color: item['color'] != null ? String(item['color']) : '',
|
||||
created_at: item['created_at'] != null ? String(item['created_at']) : ''
|
||||
} as CategoryType)
|
||||
}
|
||||
|
||||
console.log('[categories] 严格映射完成,共加载:', this.categories.length, '条')
|
||||
|
||||
// 编辑模式:分类加载完成后尝试回填
|
||||
if (this.isEdit && this.product.category_id) {
|
||||
this.tryCategoryBackfill()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取会员价异常:', e)
|
||||
console.error('[categories] 获取分类异常:', e)
|
||||
uni.showToast({ title: '分类加载异常', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
async loadCategories() {
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_categories')
|
||||
.select('id, name')
|
||||
.eq('is_active', true)
|
||||
.order('sort_order', { ascending: true })
|
||||
.execute()
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('获取分类失败:', response.error)
|
||||
}
|
||||
|
||||
const rawData = response.data as any[]
|
||||
if (rawData != null && rawData.length > 0) {
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
const item = rawData[i] as any
|
||||
this.categories.push({
|
||||
id: item['id'] != null ? String(item['id']) : '',
|
||||
name: item['name'] != null ? String(item['name']) : ''
|
||||
} as CategoryType)
|
||||
}
|
||||
} else {
|
||||
// 演示版默认医养分类
|
||||
this.categories = [
|
||||
{ id: 'med', name: '医疗服务' },
|
||||
{ id: 'drug', name: '药品器械' },
|
||||
{ id: 'care', name: '居家护理' },
|
||||
{ id: 'life', name: '生活服务' },
|
||||
{ id: 'health', name: '健康管理' }
|
||||
] as CategoryType[]
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取分类异常:', e)
|
||||
this.categories = [
|
||||
{ id: 'med', name: '医疗服务' },
|
||||
{ id: 'drug', name: '药品器械' },
|
||||
{ id: 'care', name: '居家护理' },
|
||||
{ id: 'life', name: '生活服务' },
|
||||
{ id: 'health', name: '健康管理' }
|
||||
] as CategoryType[]
|
||||
// 编辑态分类回填:categories 加载完成 AND product.category_id 已就位时调用
|
||||
tryCategoryBackfill() {
|
||||
if (!this.product.category_id || this.categories.length === 0) return
|
||||
const idx = this.categories.findIndex(c => c.id === this.product.category_id)
|
||||
if (idx >= 0) {
|
||||
this.categoryIndex = idx
|
||||
this.selectedCategory = this.categories[idx]
|
||||
console.log('[categories] 编辑态回填成功:', this.selectedCategory.name)
|
||||
} else {
|
||||
console.warn('[categories] 编辑态回填未找到匹配分类, category_id=', this.product.category_id)
|
||||
}
|
||||
},
|
||||
|
||||
async loadBrands() {
|
||||
this.brands = []
|
||||
try {
|
||||
console.log('[brands] request start, table: ml_brands')
|
||||
const response = await supa
|
||||
.from('ml_brands')
|
||||
.select('id, name, logo_url')
|
||||
@@ -492,12 +540,21 @@
|
||||
.order('name', { ascending: true })
|
||||
.execute()
|
||||
|
||||
console.log('[brands] response status:', response.status)
|
||||
|
||||
if (response.error != null) {
|
||||
console.error('获取品牌失败:', response.error)
|
||||
console.error('[brands] query error:', response.error)
|
||||
console.error('[brands] status:', response.status)
|
||||
// 检查是否是非 JSON 响应包裹
|
||||
const d = response.data as any
|
||||
if (d != null && typeof d === 'object' && d['raw'] != null) {
|
||||
console.error('[brands] raw preview:', String(d['raw']).substring(0, 200))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const rawData = response.data as any[]
|
||||
console.log('[brands] raw length:', rawData != null ? rawData.length : 0)
|
||||
if (rawData == null) return
|
||||
|
||||
for (let i = 0; i < rawData.length; i++) {
|
||||
@@ -508,8 +565,9 @@
|
||||
logo_url: item['logo_url'] != null ? String(item['logo_url']) : ''
|
||||
} as BrandType)
|
||||
}
|
||||
console.log('[brands] loaded:', this.brands.length)
|
||||
} catch (e) {
|
||||
console.error('获取品牌异常:', e)
|
||||
console.error('[brands] exception:', e)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -544,7 +602,6 @@
|
||||
this.product.name = getStr('name')
|
||||
this.product.subtitle = getStr('subtitle')
|
||||
this.product.category_id = getStr('category_id')
|
||||
this.product.brand_id = getStr('brand_id')
|
||||
this.product.main_image_url = getStr('main_image_url')
|
||||
this.product.imageList = this.parseImageUrls(getStr('image_urls'))
|
||||
this.product.base_price = getStr('base_price')
|
||||
@@ -562,18 +619,9 @@
|
||||
this.product.vip_discount_rate = getStr('vip_discount_rate')
|
||||
this.product.description = getStr('description')
|
||||
|
||||
// 尝试回填分类(若此时 categories 已加载则立即生效;若尚未完成则由 loadCategories 结尾再次触发)
|
||||
if (this.product.category_id) {
|
||||
this.categoryIndex = this.categories.findIndex(c => c.id === this.product.category_id)
|
||||
if (this.categoryIndex >= 0) {
|
||||
this.selectedCategory = this.categories[this.categoryIndex]
|
||||
}
|
||||
}
|
||||
|
||||
if (this.product.brand_id) {
|
||||
this.brandIndex = this.brands.findIndex(b => b.id === this.product.brand_id)
|
||||
if (this.brandIndex >= 0) {
|
||||
this.selectedBrand = this.brands[this.brandIndex]
|
||||
}
|
||||
this.tryCategoryBackfill()
|
||||
}
|
||||
} catch (e) {
|
||||
uni.hideLoading()
|
||||
@@ -599,13 +647,6 @@
|
||||
this.product.category_id = this.selectedCategory.id
|
||||
},
|
||||
|
||||
onBrandChange(e: any) {
|
||||
const index = e.detail.value as number
|
||||
this.brandIndex = index
|
||||
this.selectedBrand = this.brands[index]
|
||||
this.product.brand_id = this.selectedBrand.id
|
||||
},
|
||||
|
||||
chooseMainImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
@@ -706,7 +747,6 @@
|
||||
name: this.product.name,
|
||||
subtitle: this.product.subtitle,
|
||||
category_id: this.product.category_id,
|
||||
brand_id: this.product.brand_id || null,
|
||||
main_image_url: finalMainImage,
|
||||
image_urls: imageUrlsStr,
|
||||
base_price: this.product.base_price ? parseFloat(this.product.base_price) : 0,
|
||||
@@ -761,58 +801,6 @@
|
||||
response = insertResponse
|
||||
}
|
||||
|
||||
// 保存会员价
|
||||
let targetProductId = this.isEdit ? this.productId : ''
|
||||
if (response != null && response.data != null) {
|
||||
const responseData = response.data
|
||||
if (Array.isArray(responseData)) {
|
||||
const dataArr = responseData as any[]
|
||||
if (dataArr.length > 0) {
|
||||
const firstRow = dataArr[0] as UTSJSONObject
|
||||
if (firstRow['id'] != null) {
|
||||
targetProductId = String(firstRow['id'])
|
||||
}
|
||||
}
|
||||
} else if (responseData instanceof UTSJSONObject) {
|
||||
const dataObj = responseData as UTSJSONObject
|
||||
if (dataObj['id'] != null) {
|
||||
targetProductId = String(dataObj['id'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('最终目标产品ID:', targetProductId)
|
||||
|
||||
if (targetProductId && targetProductId !== '' && targetProductId !== 'undefined') {
|
||||
// 1. 先删除旧的会员价
|
||||
if (this.isEdit) {
|
||||
console.log('删除旧会员价:', targetProductId)
|
||||
await supa.from('ml_product_member_prices').delete().eq('product_id', targetProductId).execute()
|
||||
}
|
||||
|
||||
// 2. 插入新的会员价
|
||||
for (let i = 0; i < this.memberLevels.length; i++) {
|
||||
const level = this.memberLevels[i]
|
||||
if (level.price && level.price > 0) {
|
||||
const memberPriceData = {
|
||||
product_id: targetProductId,
|
||||
level_id: level.id,
|
||||
member_price: level.price,
|
||||
created_at: new Date().toISOString()
|
||||
} as UTSJSONObject
|
||||
|
||||
const insertRes = await supa
|
||||
.from('ml_product_member_prices')
|
||||
.insert(memberPriceData)
|
||||
.execute()
|
||||
|
||||
if (insertRes.error != null) {
|
||||
console.error('插入会员价失败', insertRes.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -143,11 +143,6 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 添加服务按钮 -->
|
||||
<view class="add-product-btn" @click="addProduct">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">添加服务项目</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import MerchantTabBar from '@/components/merchant-tabbar/MerchantTabBar.uvue'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, MOCK_SHOP_INFO, MOCK_TODAY_STATS, MOCK_PENDING_COUNTS, getMockRecentOrders } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
// ---- 复用 index 的类型定义 ----
|
||||
type ShopInfoType = {
|
||||
@@ -328,6 +329,10 @@
|
||||
},
|
||||
|
||||
async initMerchantId() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
return
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
if (session != null && session.user != null) {
|
||||
@@ -343,6 +348,35 @@
|
||||
|
||||
// 复用 index 的完整数据加载链
|
||||
async loadAllData() {
|
||||
if (USE_MOCK) {
|
||||
this.shopInfo = {
|
||||
id: MOCK_SHOP_INFO.id,
|
||||
merchant_id: MOCK_SHOP_INFO.merchant_id,
|
||||
shop_name: MOCK_SHOP_INFO.shop_name,
|
||||
shop_logo: MOCK_SHOP_INFO.shop_logo,
|
||||
shop_banner: MOCK_SHOP_INFO.shop_banner,
|
||||
description: MOCK_SHOP_INFO.description,
|
||||
contact_name: MOCK_SHOP_INFO.contact_name,
|
||||
contact_phone: MOCK_SHOP_INFO.contact_phone,
|
||||
rating_avg: MOCK_SHOP_INFO.rating_avg,
|
||||
total_sales: MOCK_SHOP_INFO.total_sales,
|
||||
status: MOCK_SHOP_INFO.status
|
||||
} as ShopInfoType
|
||||
this.todayStats = {
|
||||
orders: MOCK_TODAY_STATS.orders,
|
||||
sales: MOCK_TODAY_STATS.sales,
|
||||
visitors: MOCK_TODAY_STATS.visitors,
|
||||
conversion: MOCK_TODAY_STATS.conversion
|
||||
} as TodayStatsType
|
||||
this.pendingCounts = {
|
||||
pending_shipment: MOCK_PENDING_COUNTS.pending_shipment,
|
||||
refund_requests: MOCK_PENDING_COUNTS.refund_requests,
|
||||
low_stock: MOCK_PENDING_COUNTS.low_stock,
|
||||
pending_reviews: MOCK_PENDING_COUNTS.pending_reviews
|
||||
} as PendingCountsType
|
||||
this.recentOrders = getMockRecentOrders() as OrderType[]
|
||||
return
|
||||
}
|
||||
await this.loadMerchantData()
|
||||
await this.loadTodayStats()
|
||||
await this.loadPendingCounts()
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, getMockPromotions } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type PromotionType = {
|
||||
id: string
|
||||
@@ -75,21 +76,39 @@
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadPromotions()
|
||||
if (this.merchantId !== '') {
|
||||
this.loadPromotions()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async initMerchantId() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
this.loadPromotions()
|
||||
return
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
||||
} catch (e) {}
|
||||
this.loadPromotions()
|
||||
this.loadPromotions()
|
||||
},
|
||||
|
||||
async loadPromotions() {
|
||||
if (USE_MOCK) {
|
||||
this.promotions = getMockPromotions(this.currentTab)
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_coupon_templates')
|
||||
.select('*')
|
||||
.eq('merchant_id', this.merchantId)
|
||||
.eq('coupon_type', this.currentTab)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(50)
|
||||
.execute()
|
||||
@@ -118,7 +137,7 @@
|
||||
|
||||
this.promotions = promos
|
||||
} catch (e) {
|
||||
console.error('加载失败:', e)
|
||||
console.error('加载活动失败:', e)
|
||||
} finally {
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, getMockReviews } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type ReviewType = {
|
||||
id: string
|
||||
@@ -100,6 +101,11 @@
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
this.loadReviews()
|
||||
return
|
||||
}
|
||||
// 同步设置 merchantId,不用 async 包裹,避免 generator 内 this 绑定异常
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
@@ -117,6 +123,13 @@
|
||||
if (!this.merchantId || this.merchantId.split('-').length !== 5) return
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
if (USE_MOCK) {
|
||||
this.reviews = getMockReviews(this.currentFilter) as ReviewType[]
|
||||
this.hasMore = false
|
||||
this.loading = false
|
||||
this.refreshing = false
|
||||
return
|
||||
}
|
||||
try {
|
||||
let query = supa
|
||||
.from('ml_product_reviews')
|
||||
|
||||
1
pages/mall/merchant/sql/categories_rows.sql
Normal file
1
pages/mall/merchant/sql/categories_rows.sql
Normal file
@@ -0,0 +1 @@
|
||||
INSERT INTO "public"."categories" ("id", "name", "icon", "description", "color", "created_at") VALUES ('allergy', '抗过敏', '🤧', '过敏性疾病用药', '#9C27B0', '2026-01-30 01:48:28.396373+00'), ('child', '儿童用药', '👶', '儿童专用', '#00BCD4', '2026-01-28 09:23:10.344413+00'), ('chronic', '慢性疾病', '🫀', '长期管理', '#795548', '2026-01-28 09:23:10.344413+00'), ('cold', '感冒发烧', '🤧', '解热镇痛', '#2196F3', '2026-01-28 09:23:10.344413+00'), ('cough', '止咳化痰', '😷', '呼吸道疾病用药', '#2196F3', '2026-01-30 01:48:28.396373+00'), ('device', '医疗器械', '🩺', '医疗设备', '#607D8B', '2026-01-28 09:23:10.344413+00'), ('external', '外用药品', '🧴', '外用制剂', '#8BC34A', '2026-01-28 09:23:10.344413+00'), ('health', '健康食品', '🥗', '保健食品', '#FFC107', '2026-01-28 09:23:10.344413+00'), ('pain', '止痛消炎', '💊', '镇痛消炎', '#F44336', '2026-01-28 09:23:10.344413+00'), ('skin', '皮肤用药', '🤕', '皮肤护理', '#9C27B0', '2026-01-28 09:23:10.344413+00'), ('stomach', '肠胃用药', '🤢', '消化系统', '#4CAF50', '2026-01-28 09:23:10.344413+00'), ('vitamin', '维生素', '🍊', '营养补充', '#FF9800', '2026-01-28 09:23:10.344413+00');
|
||||
109
pages/mall/merchant/sql/create_ml_brands_and_member_levels.sql
Normal file
109
pages/mall/merchant/sql/create_ml_brands_and_member_levels.sql
Normal file
@@ -0,0 +1,109 @@
|
||||
-- =====================================================
|
||||
-- 在 Supabase Studio > SQL Editor 中执行此文件
|
||||
-- 解决 ml_brands / ml_member_levels 404 问题
|
||||
-- 字段名严格对齐前端 product-edit.uvue 代码
|
||||
-- =====================================================
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- 一、品牌表 ml_brands
|
||||
-- =====================================================
|
||||
-- 前端 loadBrands() 用到字段:id, name, logo_url, is_active
|
||||
-- 查询条件:is_active = true,排序:name asc
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.ml_brands (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(200) NOT NULL,
|
||||
logo_url TEXT,
|
||||
description TEXT,
|
||||
website VARCHAR(500),
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.ml_brands IS '品牌表';
|
||||
|
||||
-- RLS 策略:登录用户 / 匿名用户均可读
|
||||
ALTER TABLE public.ml_brands ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS "ml_brands anon select" ON public.ml_brands;
|
||||
CREATE POLICY "ml_brands anon select"
|
||||
ON public.ml_brands FOR SELECT
|
||||
TO authenticated, anon
|
||||
USING (true);
|
||||
|
||||
-- 初始化测试数据(可按需修改)
|
||||
INSERT INTO public.ml_brands (name, logo_url, description, is_active) VALUES
|
||||
('健民医药', '', '综合医药品牌', true),
|
||||
('同仁堂', '', '百年老字号中医药', true),
|
||||
('华润医疗', '', '华润集团旗下医疗品牌', true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- 二、会员等级表 ml_member_levels
|
||||
-- =====================================================
|
||||
-- 前端 loadMemberLevels() 用到字段:
|
||||
-- id, name, level_rank, discount_rate, is_active
|
||||
-- 查询条件:is_active = true,排序:level_rank asc
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.ml_member_levels (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name VARCHAR(100) NOT NULL,
|
||||
level_rank INTEGER NOT NULL DEFAULT 0, -- 等级排序(越小越低)
|
||||
discount_rate DECIMAL(5,4) NOT NULL DEFAULT 1.0000, -- 折扣率,0.85 = 85折
|
||||
min_amount DECIMAL(10,2) DEFAULT 0, -- 升级所需累计消费金额
|
||||
description TEXT,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.ml_member_levels IS '会员等级配置表';
|
||||
COMMENT ON COLUMN public.ml_member_levels.discount_rate IS '折扣率,0.85 表示 85 折,1.0 表示无折扣';
|
||||
COMMENT ON COLUMN public.ml_member_levels.level_rank IS '等级排序值,数值越小等级越低,用于前端排序';
|
||||
|
||||
-- RLS 策略:登录用户 / 匿名用户均可读
|
||||
ALTER TABLE public.ml_member_levels ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS "ml_member_levels anon select" ON public.ml_member_levels;
|
||||
CREATE POLICY "ml_member_levels anon select"
|
||||
ON public.ml_member_levels FOR SELECT
|
||||
TO authenticated, anon
|
||||
USING (true);
|
||||
|
||||
-- 初始化 5 档会员等级数据
|
||||
INSERT INTO public.ml_member_levels (name, level_rank, discount_rate, min_amount, description, is_active) VALUES
|
||||
('普通会员', 0, 1.0000, 0, '注册即享', true),
|
||||
('铜牌会员', 1, 0.9800, 500, '累计消费满500元', true),
|
||||
('银牌会员', 2, 0.9500, 2000, '累计消费满2000元', true),
|
||||
('金牌会员', 3, 0.9200, 5000, '累计消费满5000元', true),
|
||||
('钻石会员', 4, 0.8800, 10000, '累计消费满10000元',true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
|
||||
-- =====================================================
|
||||
-- 三、商品会员价关联表 ml_product_member_prices(如不存在则补建)
|
||||
-- =====================================================
|
||||
-- 前端 loadMemberPrices() 用到字段:product_id, level_id, member_price
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.ml_product_member_prices (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
product_id UUID NOT NULL,
|
||||
level_id UUID NOT NULL REFERENCES public.ml_member_levels(id) ON DELETE CASCADE,
|
||||
member_price DECIMAL(12,2) NOT NULL CHECK (member_price >= 0),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE (product_id, level_id)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.ml_product_member_prices IS '商品会员等级专属价格';
|
||||
|
||||
ALTER TABLE public.ml_product_member_prices ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
DROP POLICY IF EXISTS "ml_product_member_prices anon select" ON public.ml_product_member_prices;
|
||||
CREATE POLICY "ml_product_member_prices anon select"
|
||||
ON public.ml_product_member_prices FOR SELECT
|
||||
TO authenticated, anon
|
||||
USING (true);
|
||||
@@ -68,6 +68,7 @@
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { USE_MOCK, MOCK_MERCHANT_ID, getMockStats, getMockTrendData, MOCK_HOT_PRODUCTS } from '@/pages/mall/merchant/mock/merchant-mock-data.uts'
|
||||
|
||||
type ProductType = {
|
||||
id: string
|
||||
@@ -116,6 +117,10 @@
|
||||
|
||||
methods: {
|
||||
async initMerchantId() {
|
||||
if (USE_MOCK) {
|
||||
this.merchantId = MOCK_MERCHANT_ID
|
||||
return
|
||||
}
|
||||
try {
|
||||
const session = supa.getSession()
|
||||
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
|
||||
@@ -123,6 +128,13 @@
|
||||
},
|
||||
|
||||
async loadStatistics() {
|
||||
if (USE_MOCK) {
|
||||
const s = getMockStats(this.dateRange)
|
||||
this.stats = s
|
||||
this.trendData = getMockTrendData(this.dateRange)
|
||||
this.hotProducts = MOCK_HOT_PRODUCTS as ProductType[]
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await supa
|
||||
.from('ml_orders')
|
||||
|
||||
@@ -1,39 +1,275 @@
|
||||
mp.esm.js:126 TypeError: this.loadReviews is not a function
|
||||
at Proxy.onShow (reviews.uvue:107)
|
||||
at callWithErrorHandling (vue.runtime.esm.js:1356)
|
||||
at callWithAsyncErrorHandling (vue.runtime.esm.js:1363)
|
||||
at Array.hook.__weh.hook.__weh (vue.runtime.esm.js:2461)
|
||||
at invokeArrayFns (uni-shared.es.js:469)
|
||||
at Proxy.callHook (uni.mp.esm.js:944)
|
||||
at ai.mpOptions.<computed> [as onShow] (uni.mp.esm.js:984)
|
||||
at ai.<anonymous> (VM16334 WASubContext.js:1)
|
||||
at ai.s.__callPageLifeTime__ (VM16334 WASubContext.js:1)
|
||||
at VM16334 WASubContext.js:1(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
onError2 @ mp.esm.js:126
|
||||
[product-edit] onLoad start
|
||||
mp.esm.js:485 [product-edit] mode: 新增
|
||||
mp.esm.js:485 [product-edit] productId: (none)
|
||||
mp.esm.js:485 [SupaConfig] SUPA_URL : http://119.146.131.237:9129
|
||||
mp.esm.js:485 [SupaConfig] SUPA_KEY : eyJhbGciOi...aavHyVvg
|
||||
mp.esm.js:485 [supa-check] SUPA_URL: http://119.146.131.237:9129
|
||||
mp.esm.js:485 [AkSupaQueryBuilder] execute - 表: categories filter: null
|
||||
mp.esm.js:485 [ak-req] GET http://119.146.131.237:9129/rest/v1/categories?select=*&limit=1
|
||||
mp.esm.js:485 [ak-req] apikey: eyJhbG...yVvg | Authorization: Bearer eyJhbG...yVvg | auth-mode: anon-key | prefer: count=exact
|
||||
mp.esm.js:485 [supa-check] categories probe status: 206
|
||||
mp.esm.js:485 [supa-check] status: 206 — PostgREST 在线
|
||||
mp.esm.js:485 [categories] 开始从 categories 表读取分类(严格字段映射)...
|
||||
mp.esm.js:485 [AkSupaQueryBuilder] execute - 表: categories filter: null
|
||||
mp.esm.js:485 [ak-req] GET http://119.146.131.237:9129/rest/v1/categories?select=id%2Cname%2Cicon%2Cdescription%2Ccolor%2Ccreated_at&limit=100
|
||||
mp.esm.js:485 [ak-req] apikey: eyJhbG...yVvg | Authorization: Bearer eyJhbG...yVvg | auth-mode: anon-key | prefer: count=exact
|
||||
mp.esm.js:485 [categories] response status: 200
|
||||
mp.esm.js:485 [categories] raw length: 12
|
||||
mp.esm.js:485 [categories] 严格映射完成,共加载: 12 条
|
||||
mp.esm.js:485 [brands] request start, table: ml_brands
|
||||
mp.esm.js:485 [AkSupaQueryBuilder] execute - 表: ml_brands filter: is_active=eq.true
|
||||
mp.esm.js:485 [member-levels] request start, table: ml_member_levels
|
||||
mp.esm.js:485 [AkSupaQueryBuilder] execute - 表: ml_member_levels filter: is_active=eq.true
|
||||
mp.esm.js:485 [ak-req] GET http://119.146.131.237:9129/rest/v1/ml_brands?select=id%2C%20name%2C%20logo_url&order=name.asc&is_active=eq.true
|
||||
mp.esm.js:485 [ak-req] apikey: eyJhbG...yVvg | Authorization: Bearer eyJhbG...yVvg | auth-mode: anon-key | prefer: (none)
|
||||
mp.esm.js:485 [ak-req] GET http://119.146.131.237:9129/rest/v1/ml_member_levels?select=*&order=level_rank.asc&is_active=eq.true
|
||||
mp.esm.js:485 [ak-req] apikey: eyJhbG...yVvg | Authorization: Bearer eyJhbG...yVvg | auth-mode: anon-key | prefer: (none)
|
||||
uni.api.esm.js:1043 GET http://119.146.131.237:9129/rest/v1/ml_brands?select=id%2C%20name%2C%20logo_url&order=name.asc&is_active=eq.true 404 (Not Found)(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
(anonymous) @ uni.api.esm.js:1043
|
||||
invokeApi @ uni.api.esm.js:330
|
||||
promiseApi @ uni.api.esm.js:890
|
||||
(anonymous) @ ak-req.uts:214
|
||||
doOnce @ ak-req.uts:213
|
||||
_loop$ @ ak-req.uts:312
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
_ @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
request @ ak-req.uts:148
|
||||
_callee19$ @ aksupa.uts:1259
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
requestWithAutoRefresh @ aksupa.uts:1258
|
||||
_callee11$ @ aksupa.uts:1040
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
select @ aksupa.uts:955
|
||||
_callee$ @ aksupa.uts:377
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
execute @ aksupa.uts:356
|
||||
_callee7$ @ product-edit.uvue:626
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
loadBrands @ product-edit.uvue:617
|
||||
_callee$ @ product-edit.uvue:373
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
onLoad @ product-edit.uvue:326
|
||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||
hook.__weh.hook.__weh @ vue.runtime.esm.js:2461
|
||||
invokeArrayFns @ uni-shared.es.js:469
|
||||
callHook @ uni.mp.esm.js:944
|
||||
errorHandler @ vue.runtime.esm.js:5331
|
||||
methods.onLoad @ uni.mp.esm.js:1542
|
||||
Show 21 more frames
|
||||
uni.api.esm.js:1043 GET http://119.146.131.237:9129/rest/v1/ml_member_levels?select=*&order=level_rank.asc&is_active=eq.true 404 (Not Found)(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
(anonymous) @ uni.api.esm.js:1043
|
||||
invokeApi @ uni.api.esm.js:330
|
||||
promiseApi @ uni.api.esm.js:890
|
||||
(anonymous) @ ak-req.uts:214
|
||||
doOnce @ ak-req.uts:213
|
||||
_loop$ @ ak-req.uts:312
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
_ @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
request @ ak-req.uts:148
|
||||
_callee19$ @ aksupa.uts:1259
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
requestWithAutoRefresh @ aksupa.uts:1258
|
||||
_callee11$ @ aksupa.uts:1040
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
select @ aksupa.uts:955
|
||||
_callee$ @ aksupa.uts:377
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
execute @ aksupa.uts:356
|
||||
_callee4$ @ product-edit.uvue:473
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
loadMemberLevels @ product-edit.uvue:464
|
||||
_callee$ @ product-edit.uvue:374
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
onLoad @ product-edit.uvue:326
|
||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||
handleError @ vue.runtime.esm.js:1396
|
||||
callWithErrorHandling @ vue.runtime.esm.js:1358
|
||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||
hook.__weh.hook.__weh @ vue.runtime.esm.js:2461
|
||||
invokeArrayFns @ uni-shared.es.js:469
|
||||
callHook @ uni.mp.esm.js:944
|
||||
mpOptions.<computed> @ uni.mp.esm.js:984
|
||||
TypeError: this.loadReviews is not a function
|
||||
at Proxy._callee$ (reviews.uvue:116)
|
||||
at s (regeneratorRuntime.js?forceSync=true:1)
|
||||
at Generator.<anonymous> (regeneratorRuntime.js?forceSync=true:1)
|
||||
at Generator.next (regeneratorRuntime.js?forceSync=true:1)
|
||||
at tslib.es6.js:76
|
||||
at new Promise (<anonymous>)
|
||||
at Object.__awaiter (tslib.es6.js:72)
|
||||
at Proxy.initMerchantId (reviews.uvue:111)
|
||||
at Proxy.onLoad (reviews.uvue:103)
|
||||
at callWithErrorHandling (vue.runtime.esm.js:1356)(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
mp.esm.js:126 {reason: TypeError: this.loadReviews is not a function
|
||||
at Proxy._callee$ (weapp:///pages/mall/merchant/r…, promise: Promise}(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
methods.onLoad @ uni.mp.esm.js:1542
|
||||
Show 21 more frames
|
||||
mp.esm.js:485 [brands] response status: 404
|
||||
mp.esm.js:485 [brands] query error: UniError: 请求失败: 404
|
||||
at new UniError2 (weapp:///http://127.0.0.1:48893/appservice/common/vendor.js?t=wechat&s=1776040029386&v=c01e6f70ace82f72049bc4519dbb3205:8560:5)
|
||||
at Object.toUniError (weapp:///http://127.0.0.1:48893/appservice/utils/utils.js?t=wechat&s=1776040029386&v=170a1b511d4b4583a4ff38ba2eb804a7:77:18)
|
||||
at AkSupa._callee19$ (weapp:///http://127.0.0.1:48893/appservice/components/supadb/aksupa.js?t=wechat&s=1776040029386&v=b1c88035cdf7fd4aab11f1c0cd809cf3:1878:41)
|
||||
at s (weapp:///http://127.0.0.1:48893/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1588)
|
||||
at Generator.<anonymous> (weapp:///http://127.0.0.1:48893/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:2925)
|
||||
at Generator.next (weapp:///http://127.0.0.1:48893/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1951)
|
||||
at fulfilled (weapp:///http://127.0.0.1:48893/appservice/common/vendor.js?t=wechat&s=1776040029386&v=c01e6f70ace82f72049bc4519dbb3205:9601:24)(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
(anonymous) @ mp.esm.js:485
|
||||
__f__ @ uni.api.esm.js:591
|
||||
_callee7$ @ product-edit.uvue:631
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
loadBrands @ product-edit.uvue:617
|
||||
_callee$ @ product-edit.uvue:373
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
onLoad @ product-edit.uvue:326
|
||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||
hook.__weh.hook.__weh @ vue.runtime.esm.js:2461
|
||||
invokeArrayFns @ uni-shared.es.js:469
|
||||
callHook @ uni.mp.esm.js:944
|
||||
methods.onLoad @ uni.mp.esm.js:1542
|
||||
mp.esm.js:485 [brands] status: 404(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
(anonymous) @ mp.esm.js:485
|
||||
__f__ @ uni.api.esm.js:591
|
||||
_callee7$ @ product-edit.uvue:632
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
loadBrands @ product-edit.uvue:617
|
||||
_callee$ @ product-edit.uvue:373
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
onLoad @ product-edit.uvue:326
|
||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||
hook.__weh.hook.__weh @ vue.runtime.esm.js:2461
|
||||
invokeArrayFns @ uni-shared.es.js:469
|
||||
callHook @ uni.mp.esm.js:944
|
||||
methods.onLoad @ uni.mp.esm.js:1542
|
||||
mp.esm.js:485 [member-levels] response status: 404
|
||||
mp.esm.js:485 [member-levels] query error: UniError: 请求失败: 404
|
||||
at new UniError2 (weapp:///http://127.0.0.1:48893/appservice/common/vendor.js?t=wechat&s=1776040029386&v=c01e6f70ace82f72049bc4519dbb3205:8560:5)
|
||||
at Object.toUniError (weapp:///http://127.0.0.1:48893/appservice/utils/utils.js?t=wechat&s=1776040029386&v=170a1b511d4b4583a4ff38ba2eb804a7:77:18)
|
||||
at AkSupa._callee19$ (weapp:///http://127.0.0.1:48893/appservice/components/supadb/aksupa.js?t=wechat&s=1776040029386&v=b1c88035cdf7fd4aab11f1c0cd809cf3:1878:41)
|
||||
at s (weapp:///http://127.0.0.1:48893/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1588)
|
||||
at Generator.<anonymous> (weapp:///http://127.0.0.1:48893/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:2925)
|
||||
at Generator.next (weapp:///http://127.0.0.1:48893/appservice/@babel/runtime/helpers/regeneratorRuntime.js?forceSync=true:1:1951)
|
||||
at fulfilled (weapp:///http://127.0.0.1:48893/appservice/common/vendor.js?t=wechat&s=1776040029386&v=c01e6f70ace82f72049bc4519dbb3205:9601:24)(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
(anonymous) @ mp.esm.js:485
|
||||
__f__ @ uni.api.esm.js:591
|
||||
_callee4$ @ product-edit.uvue:478
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
loadMemberLevels @ product-edit.uvue:464
|
||||
_callee$ @ product-edit.uvue:374
|
||||
s @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
(anonymous) @ regeneratorRuntime.js?forceSync=true:1
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
fulfilled @ tslib.es6.js:73
|
||||
Promise.then (async)
|
||||
step @ tslib.es6.js:75
|
||||
(anonymous) @ tslib.es6.js:76
|
||||
__awaiter @ tslib.es6.js:72
|
||||
onLoad @ product-edit.uvue:326
|
||||
callWithErrorHandling @ vue.runtime.esm.js:1356
|
||||
callWithAsyncErrorHandling @ vue.runtime.esm.js:1363
|
||||
hook.__weh.hook.__weh @ vue.runtime.esm.js:2461
|
||||
invokeArrayFns @ uni-shared.es.js:469
|
||||
callHook @ uni.mp.esm.js:944
|
||||
methods.onLoad @ uni.mp.esm.js:1542
|
||||
mp.esm.js:485 [member-levels] status: 404(env: Windows,mp,1.06.2504030; lib: 3.14.2)
|
||||
@@ -30,6 +30,24 @@ function removeAuthStore(key : string) {
|
||||
uni.removeStorageSync(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全读取 UTSJSONObject 或普通 JS 对象的字段。
|
||||
* 微信小程序编译后 res.header / res.data / options.headers 均为普通 JS 对象,
|
||||
* 直接调用 .getString() / .set() 会抛 "not a function" 异常。
|
||||
* 此函数统一处理两种情况:先尝试 getString,不可用则 fallback 到 []。
|
||||
*/
|
||||
function safeStr(obj: any, key: string): string | null {
|
||||
if (obj == null) return null
|
||||
try {
|
||||
if (typeof obj.getString === 'function') {
|
||||
const v = obj.getString(key)
|
||||
return v != null ? String(v) : null
|
||||
}
|
||||
} catch (e) {}
|
||||
const v = obj[key]
|
||||
return (v != null && v !== '') ? String(v) : null
|
||||
}
|
||||
|
||||
let _accessToken : string | null = null;
|
||||
let _refreshToken : string | null = null;
|
||||
let _expiresAt : number | null = null;
|
||||
@@ -130,84 +148,62 @@ export class AkReq {
|
||||
static async request(options : AkReqOptions, skipRefresh ?: boolean) : Promise<AkReqResponse<any>> {
|
||||
// 自动刷新 token
|
||||
if (skipRefresh != true) {
|
||||
let apikey : string | null = null;
|
||||
const headersObj = options.headers;
|
||||
if (headersObj != null && typeof headersObj.getString === 'function') {
|
||||
apikey = headersObj.getString('apikey');
|
||||
}
|
||||
// safeStr 兼容 plain object 和 UTSJSONObject
|
||||
const apikey = safeStr(options.headers as any, 'apikey');
|
||||
await this.refreshTokenIfNeeded(apikey);
|
||||
}
|
||||
|
||||
// 构建新的 headers 对象,确保所有字段都被正确传递
|
||||
const newHeaders = new UTSJSONObject()
|
||||
|
||||
// 首先复制原始 headers
|
||||
if (options.headers != null) {
|
||||
const originalHeaders = options.headers
|
||||
if (typeof originalHeaders.getString === 'function') {
|
||||
// 复制 apikey
|
||||
const apikeyStr = originalHeaders.getString('apikey')
|
||||
if (apikeyStr != null) {
|
||||
newHeaders.set('apikey', apikeyStr)
|
||||
}
|
||||
// 复制 Content-Type
|
||||
const contentType = originalHeaders.getString('Content-Type')
|
||||
if (contentType != null) {
|
||||
newHeaders.set('Content-Type', contentType)
|
||||
}
|
||||
// 复制 Prefer
|
||||
const prefer = originalHeaders.getString('Prefer')
|
||||
if (prefer != null) {
|
||||
newHeaders.set('Prefer', prefer)
|
||||
}
|
||||
// 复制 Authorization(如果存在)
|
||||
const auth = originalHeaders.getString('Authorization')
|
||||
if (auth != null) {
|
||||
newHeaders.set('Authorization', auth)
|
||||
}
|
||||
// 构建请求 headers:用 safeStr 兼容 plain object / UTSJSONObject
|
||||
// aksupa.uts 创建的 headers 是“{ key: val } as UTSJSONObject” ,只是类型标注,运行时是 plain object
|
||||
const origHdr = options.headers as any
|
||||
const origApikey = safeStr(origHdr, 'apikey')
|
||||
const origCT = safeStr(origHdr, 'Content-Type') ?? safeStr(origHdr, 'content-type')
|
||||
const origPrefer = safeStr(origHdr, 'Prefer')
|
||||
const origAuth = safeStr(origHdr, 'Authorization') ?? safeStr(origHdr, 'authorization')
|
||||
const origRange = safeStr(origHdr, 'Range')
|
||||
const origRangeUnit = safeStr(origHdr, 'Range-Unit')
|
||||
|
||||
// apikey:优先用传入的,再 fallback 到全局 SUPA_KEY
|
||||
const finalApikey = origApikey ?? (SUPA_KEY !== '' ? SUPA_KEY : null)
|
||||
|
||||
// Authorization:Supabase Kong 要求所有请求都带 Authorization: Bearer <jwt>
|
||||
// 匿名态:Bearer <anon_key>;登录态:Bearer <access_token>
|
||||
const token = this.getToken()
|
||||
let finalAuth: string | null = origAuth
|
||||
let authMode: string = 'none'
|
||||
if (finalAuth == null) {
|
||||
if (token != null && token !== '') {
|
||||
// 登录态:用用户 access_token
|
||||
finalAuth = `Bearer ${token}`
|
||||
authMode = 'user-token'
|
||||
} else if (finalApikey != null && finalApikey !== '') {
|
||||
// 匿名态:用 anon key 作为 Bearer(Supabase Kong 必须)
|
||||
finalAuth = `Bearer ${finalApikey}`
|
||||
authMode = 'anon-key'
|
||||
}
|
||||
} else {
|
||||
authMode = 'pre-set'
|
||||
}
|
||||
|
||||
// 补齐 apikey (如果 headers 中没有,则直接使用 SUPA_KEY 补全)
|
||||
if (newHeaders.getString('apikey') == null) {
|
||||
if (SUPA_KEY != null && SUPA_KEY != "") {
|
||||
newHeaders.set('apikey', SUPA_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加/更新 Authorization
|
||||
const token = this.getToken();
|
||||
|
||||
// 检查原始 headers 中是否存在 Authorization
|
||||
let existAuth: string | null = null
|
||||
if (options.headers != null) {
|
||||
const originalHeaders = options.headers
|
||||
if (typeof originalHeaders.getString === 'function') {
|
||||
existAuth = originalHeaders.getString('Authorization')
|
||||
if (existAuth == null) {
|
||||
existAuth = originalHeaders.getString('authorization')
|
||||
}
|
||||
}
|
||||
}
|
||||
// 组装最终 header(plain object,所有平台均可正确传递)
|
||||
const currentHeaders: Record<string, string> = {}
|
||||
if (finalApikey != null && finalApikey !== '') currentHeaders['apikey'] = finalApikey
|
||||
if (finalAuth != null && finalAuth !== '') currentHeaders['Authorization'] = finalAuth
|
||||
currentHeaders['Content-Type'] = origCT ?? options.contentType ?? 'application/json'
|
||||
currentHeaders['Accept'] = 'application/json'
|
||||
if (origPrefer != null) currentHeaders['Prefer'] = origPrefer
|
||||
if (origRange != null) currentHeaders['Range'] = origRange
|
||||
if (origRangeUnit != null) currentHeaders['Range-Unit'] = origRangeUnit
|
||||
|
||||
if ((token != null && token != "") && (existAuth == null)) {
|
||||
newHeaders.set('Authorization', `Bearer ${token}`)
|
||||
}
|
||||
|
||||
// 确保 Content-Type 存在
|
||||
if (newHeaders.getString('Content-Type') == null) {
|
||||
const contentType = options.contentType ?? 'application/json'
|
||||
if (contentType != null && contentType != "") {
|
||||
newHeaders.set('Content-Type', contentType)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加 Accept
|
||||
newHeaders.set('Accept', 'application/json')
|
||||
|
||||
console.log('[AkReq.request] headers:', JSON.stringify(newHeaders))
|
||||
|
||||
const currentHeaders = newHeaders
|
||||
// 请求发出前诊断日志(脱敏:前6位+后4位)
|
||||
const dbgKey = finalApikey != null && finalApikey.length > 14
|
||||
? finalApikey.substring(0, 6) + '...' + finalApikey.substring(finalApikey.length - 4)
|
||||
: (finalApikey != null ? '(short)' : '(MISSING!)')
|
||||
const dbgAuth = finalAuth != null && finalAuth.length > 15
|
||||
? finalAuth.substring(0, 13) + '...' + finalAuth.substring(finalAuth.length - 4)
|
||||
: (finalAuth != null ? finalAuth : '(MISSING!)')
|
||||
console.log('[ak-req]', (options.method ?? 'GET'), options.url)
|
||||
console.log('[ak-req] apikey:', dbgKey, '| Authorization:', dbgAuth, '| auth-mode:', authMode, '| prefer:', origPrefer ?? '(none)')
|
||||
|
||||
const timeout = options.timeout ?? 10000;
|
||||
const maxRetry = Math.max(0, options.retryCount ?? 0);
|
||||
@@ -234,15 +230,41 @@ export class AkReq {
|
||||
}
|
||||
|
||||
// 兼容 res.data 可能为 string 或 UTSJSONObject 或 UTSArray
|
||||
// 先读取响应 content-type,判断是否应该 JSON.parse
|
||||
// res.header 在 MP 环境下是普通 JS 对象(无 getString),用括号访问
|
||||
let respContentType = ''
|
||||
try {
|
||||
const hdr = res.header as any
|
||||
if (hdr != null) {
|
||||
respContentType = hdr['content-type'] ?? hdr['Content-Type'] ?? ''
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
let data : UTSJSONObject | Array<UTSJSONObject> | null;
|
||||
if (typeof res.data == 'string') {
|
||||
const strData = res.data as string;
|
||||
if (strData.length > 0 && /[^\s]/.test(strData)) {
|
||||
try {
|
||||
data = JSON.parse(strData) as UTSJSONObject;
|
||||
} catch (e) {
|
||||
// 非 JSON 响应(例如纯文本/空响应/数字等),保持原始字符串,避免 JSON.parse 崩溃
|
||||
data = new UTSJSONObject({ raw: strData });
|
||||
const looksLikeHtml = strData.trimStart().startsWith('<')
|
||||
if (looksLikeHtml || (respContentType.indexOf('text/html') >= 0)) {
|
||||
// 明确是 HTML,直接跳过 JSON.parse
|
||||
const preview = strData.substring(0, 250)
|
||||
console.error('[ak-req] non-json response (HTML)')
|
||||
console.error('[ak-req] status:', res.statusCode)
|
||||
console.error('[ak-req] content-type:', respContentType)
|
||||
console.error('[ak-req] raw preview:', preview)
|
||||
data = new UTSJSONObject({ raw: strData })
|
||||
} else {
|
||||
try {
|
||||
data = JSON.parse(strData) as UTSJSONObject;
|
||||
} catch (e) {
|
||||
// 非 JSON 响应(纯文本/数字等)
|
||||
const preview = strData.substring(0, 250)
|
||||
console.error('[ak-req] json parse failed')
|
||||
console.error('[ak-req] status:', res.statusCode)
|
||||
console.error('[ak-req] content-type:', respContentType)
|
||||
console.error('[ak-req] raw preview:', preview)
|
||||
data = new UTSJSONObject({ raw: strData });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
data = null;
|
||||
@@ -250,12 +272,14 @@ export class AkReq {
|
||||
} else if (Array.isArray(res.data)) {
|
||||
data = res.data as UTSJSONObject[];
|
||||
} else {
|
||||
const objData = res.data as UTSJSONObject | null;
|
||||
data = objData;
|
||||
// MP 下 res.data 可能是 plain JS object(平台已预解析 JSON),用 safeStr 兼容
|
||||
const objData = res.data as any;
|
||||
data = objData as UTSJSONObject;
|
||||
if (objData != null) {
|
||||
const accessToken = objData.getString('access_token');
|
||||
const refreshTokenNew = objData.getString('refresh_token');
|
||||
const expiresAt = objData.getNumber('expires_at');
|
||||
const accessToken = safeStr(objData, 'access_token')
|
||||
const refreshTokenNew = safeStr(objData, 'refresh_token')
|
||||
const expiresAtRaw = objData['expires_at']
|
||||
const expiresAt = expiresAtRaw != null ? Number(expiresAtRaw) : null
|
||||
if (accessToken !== null && refreshTokenNew !== null && expiresAt !== null) {
|
||||
AkReq.setToken(accessToken, refreshTokenNew, expiresAt);
|
||||
}
|
||||
@@ -298,26 +322,47 @@ export class AkReq {
|
||||
attempt++;
|
||||
}
|
||||
const finalRes = lastRes!!;
|
||||
// 全局处理 401 未授权:在非 refresh 场景下,清理 token。
|
||||
// 测试模式下不强制跳登录页,避免影响任意跳转调试。
|
||||
// 全局处理 401 未授权
|
||||
if ((finalRes.status === 401) && (skipRefresh !== true)) {
|
||||
uni.$emit('AUTH_SESSION_EXPIRED', { reason: '401' });
|
||||
// 401 诊断日志:区分"前端 header 问题"vs"服务端 key/实例问题"
|
||||
const sentApikey = (currentHeaders as any)['apikey']
|
||||
const sentAuth = (currentHeaders as any)['Authorization']
|
||||
const dbgSentKey = sentApikey != null && sentApikey.length > 14
|
||||
? sentApikey.substring(0, 6) + '...' + sentApikey.substring(sentApikey.length - 4)
|
||||
: (sentApikey != null ? '(short)' : '(MISSING!)')
|
||||
const dbgSentAuth = sentAuth != null && sentAuth.length > 15
|
||||
? sentAuth.substring(0, 13) + '...' + sentAuth.substring(sentAuth.length - 4)
|
||||
: (sentAuth != null ? '(short)' : '(MISSING!)')
|
||||
console.error('[ak-req] ★ 401 Unauthorized')
|
||||
console.error('[ak-req] url:', options.url)
|
||||
console.error('[ak-req] auth-mode: ' + authMode)
|
||||
console.error('[ak-req] 发送 apikey:', dbgSentKey)
|
||||
console.error('[ak-req] 发送 Authorization:', dbgSentAuth)
|
||||
console.error('[ak-req] response body:', JSON.stringify(finalRes.data))
|
||||
// 前端 header 检查
|
||||
if (sentApikey == null || sentApikey === '') {
|
||||
console.error('[ak-req] ✗ 前端问题:apikey 未发送,检查 SUPA_KEY 是否已配置')
|
||||
} else if (sentAuth == null || sentAuth === '') {
|
||||
console.error('[ak-req] ✗ 前端问题:Authorization 未发送(本次修复后不应再出现此情况)')
|
||||
} else {
|
||||
console.error('[ak-req] ✓ 前端 header 已正确发送,401 来自服务端')
|
||||
console.error('[ak-req] 请运维核查以下服务端配置:')
|
||||
console.error('[ak-req] 1. SUPA_KEY 是否属于 9126 这个实例(不同实例 key 不通用)')
|
||||
console.error('[ak-req] 2. 9126 和 9127 是否为同一套 docker-compose / 同一实例')
|
||||
console.error('[ak-req] 3. Kong 是否已 reload 到最新 consumer/key 配置')
|
||||
console.error('[ak-req] 4. Supabase 实例的 ANON_KEY 环境变量与 SUPA_KEY 是否一致')
|
||||
console.error('[ak-req] 5. categories/ml_brands/ml_member_levels 是否在该实例 public schema 下')
|
||||
console.error('[ak-req] 6. RLS policy 是否允许 anon role SELECT')
|
||||
}
|
||||
|
||||
uni.$emit('AUTH_SESSION_EXPIRED', { reason: '401' });
|
||||
try {
|
||||
this.clearToken();
|
||||
uni.showToast({ title: '未授权或登录已过期,请重新登录', icon: 'none' });
|
||||
// IS_TEST_MODE=true 时不 showToast 和跳转,避免干扰调试
|
||||
if (IS_TEST_MODE !== true) {
|
||||
uni.showToast({ title: '未授权或登录已过期,请重新登录', icon: 'none' });
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
// 动态读取配置,避免 ak-req 模块与业务工程强耦合
|
||||
// const cfg = require('@/ak/config.uts') as any
|
||||
// const isTest = cfg != null ? (cfg.IS_TEST_MODE === true) : false
|
||||
const isTest = IS_TEST_MODE
|
||||
// if (!isTest) {
|
||||
// uni.reLaunch({ url: '/pages/user/login' });
|
||||
// }
|
||||
} catch (e) {
|
||||
// try { uni.reLaunch({ url: '/pages/user/login' }); } catch (e2) {}
|
||||
}
|
||||
}
|
||||
return finalRes;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@ export { supa }
|
||||
import type { OrderOptions } from '@/components/supadb/aksupa.uts'
|
||||
|
||||
const OLD_URL = '192.168.1.61:18000'
|
||||
const NEW_URL = '119.146.131.237:9126'
|
||||
// const NEW_URL = '119.146.131.237:9126'
|
||||
|
||||
// 医疗项目 Supabase 实例地址(与 ak/config.uts 中的 SUPA_URL 保持一致,去掉 http:// 前缀)
|
||||
const NEW_URL = '119.146.131.237:9127'
|
||||
|
||||
function fixImageUrl(url: string | null): string {
|
||||
if (url == null) return ''
|
||||
|
||||
Reference in New Issue
Block a user