完成店铺创建
This commit is contained in:
860
pages/mall/admin/shop/manage.uvue
Normal file
860
pages/mall/admin/shop/manage.uvue
Normal file
@@ -0,0 +1,860 @@
|
||||
<!-- 店铺管理页:查看 / 编辑店铺信息 -->
|
||||
<template>
|
||||
<view class="shop-manage-page">
|
||||
|
||||
<!-- 加载中 -->
|
||||
<view v-if="pageLoading" class="loading-wrap">
|
||||
<text class="loading-txt">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 无店铺:引导空态 -->
|
||||
<template v-else-if="!hasShop">
|
||||
<view class="empty-wrap">
|
||||
<text class="empty-icon">🏪</text>
|
||||
<text class="empty-title">您还没有创建店铺</text>
|
||||
<text class="empty-desc">注册店铺后即可发布商品、管理订单</text>
|
||||
<button class="btn-create" @click="goToCreate">立即创建店铺</button>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 有店铺:查看 / 编辑 -->
|
||||
<template v-else>
|
||||
<!-- 标题行 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">店铺管理</text>
|
||||
<view class="header-actions">
|
||||
<button v-if="!isEditing" class="btn-edit" @click="enterEdit">编辑信息</button>
|
||||
<view v-else class="edit-btns">
|
||||
<button class="btn-cancel" @click="cancelEdit">取消</button>
|
||||
<button class="btn-save" :class="{ disabled: saving }" :disabled="saving" @click="saveShop">
|
||||
{{ saving ? '保存中...' : '保存' }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 店铺基础信息卡 -->
|
||||
<view class="form-card">
|
||||
<view class="card-title">基础信息</view>
|
||||
|
||||
<!-- Logo -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">店铺 Logo</view>
|
||||
<view v-if="!isEditing" class="view-mode">
|
||||
<image
|
||||
v-if="form.shop_logo"
|
||||
:src="form.shop_logo"
|
||||
class="preview-logo"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<text v-else class="empty-val">未设置</text>
|
||||
</view>
|
||||
<view v-else>
|
||||
<view class="upload-area" @click="pickLogoImage">
|
||||
<image v-if="form.shop_logo" :src="form.shop_logo" class="preview-logo upload-preview" mode="aspectFill" />
|
||||
<view v-else class="upload-placeholder">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-txt">上传 Logo</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 店铺名称 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label"><text class="required" v-if="isEditing">*</text><text>店铺名称</text></view>
|
||||
<text v-if="!isEditing" class="view-val">{{ form.shop_name || '-' }}</text>
|
||||
<input v-else class="form-input" v-model="form.shop_name" placeholder="请输入店铺名称" maxlength="50" />
|
||||
</view>
|
||||
|
||||
<!-- 店铺简介 -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">店铺简介</view>
|
||||
<text v-if="!isEditing" class="view-val multiline">{{ form.description || '-' }}</text>
|
||||
<textarea v-else class="form-textarea" v-model="form.description" placeholder="请输入店铺简介" maxlength="200"></textarea>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式卡 -->
|
||||
<view class="form-card">
|
||||
<view class="card-title">联系方式</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">联系人</view>
|
||||
<text v-if="!isEditing" class="view-val">{{ form.contact_name || '-' }}</text>
|
||||
<input v-else class="form-input" v-model="form.contact_name" placeholder="请输入联系人" maxlength="50" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">联系电话</view>
|
||||
<text v-if="!isEditing" class="view-val">{{ form.contact_phone || '-' }}</text>
|
||||
<input v-else class="form-input" v-model="form.contact_phone" placeholder="请输入联系电话" maxlength="20" type="number" />
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">联系邮箱</view>
|
||||
<text v-if="!isEditing" class="view-val">{{ form.contact_email || '-' }}</text>
|
||||
<input v-else class="form-input" v-model="form.contact_email" placeholder="选填" maxlength="100" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资质信息卡 -->
|
||||
<view class="form-card">
|
||||
<view class="card-title">资质信息</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">营业执照号</view>
|
||||
<text v-if="!isEditing" class="view-val">{{ form.business_license || '-' }}</text>
|
||||
<input v-else class="form-input" v-model="form.business_license" placeholder="营业执照注册号" maxlength="50" />
|
||||
</view>
|
||||
|
||||
<!-- 扩展资质(存在 verification_data 里) -->
|
||||
<view class="form-item">
|
||||
<view class="form-label">店铺类型</view>
|
||||
<text v-if="!isEditing" class="view-val">{{ getShopTypeLabel(extData.shop_type) }}</text>
|
||||
<picker v-else :range="shopTypeLabels" :value="shopTypeIndex" @change="onShopTypeChange">
|
||||
<view class="picker-trigger">
|
||||
<text>{{ getShopTypeLabel(extData.shop_type) }}</text>
|
||||
<text class="picker-arrow">▼</text>
|
||||
</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">身份证正面</view>
|
||||
<view v-if="!isEditing">
|
||||
<image v-if="extData.id_card_front" :src="extData.id_card_front" class="cert-img" mode="aspectFill" />
|
||||
<text v-else class="empty-val">未上传</text>
|
||||
</view>
|
||||
<view v-else class="upload-area-wide" @click="pickIdCardFront">
|
||||
<image v-if="extData.id_card_front" :src="extData.id_card_front" class="cert-img" mode="aspectFill" />
|
||||
<view v-else class="upload-placeholder">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-txt">上传身份证正面</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">身份证反面</view>
|
||||
<view v-if="!isEditing">
|
||||
<image v-if="extData.id_card_back" :src="extData.id_card_back" class="cert-img" mode="aspectFill" />
|
||||
<text v-else class="empty-val">未上传</text>
|
||||
</view>
|
||||
<view v-else class="upload-area-wide" @click="pickIdCardBack">
|
||||
<image v-if="extData.id_card_back" :src="extData.id_card_back" class="cert-img" mode="aspectFill" />
|
||||
<view v-else class="upload-placeholder">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-txt">上传身份证反面</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">营业执照图片</view>
|
||||
<view v-if="!isEditing">
|
||||
<image v-if="extData.license_image" :src="extData.license_image" class="cert-img" mode="aspectFill" />
|
||||
<text v-else class="empty-val">未上传</text>
|
||||
</view>
|
||||
<view v-else class="upload-area-wide" @click="pickLicenseImage">
|
||||
<image v-if="extData.license_image" :src="extData.license_image" class="cert-img" mode="aspectFill" />
|
||||
<view v-else class="upload-placeholder">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-txt">上传营业执照</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="form-item">
|
||||
<view class="form-label">品牌授权书</view>
|
||||
<view v-if="!isEditing">
|
||||
<image v-if="extData.brand_authorization" :src="extData.brand_authorization" class="cert-img" mode="aspectFill" />
|
||||
<text v-else class="empty-val">未上传</text>
|
||||
</view>
|
||||
<view v-else class="upload-area-wide" @click="pickBrandAuth">
|
||||
<image v-if="extData.brand_authorization" :src="extData.brand_authorization" class="cert-img" mode="aspectFill" />
|
||||
<view v-else class="upload-placeholder">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-txt">上传品牌授权书</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 店铺状态信息(只读) -->
|
||||
<view class="form-card info-card">
|
||||
<view class="card-title">店铺状态</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">店铺ID</text>
|
||||
<text class="info-val">{{ shopId }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">商家ID</text>
|
||||
<text class="info-val">{{ merchantId }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">当前状态</text>
|
||||
<text class="info-val" :class="form.status === 1 ? 'status-normal' : 'status-off'">
|
||||
{{ form.status === 1 ? '正常营业' : form.status === 2 ? '暂停营业' : '已关闭' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">商品数量</text>
|
||||
<text class="info-val">{{ form.product_count }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">创建时间</text>
|
||||
<text class="info-val">{{ formatDate(form.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="uts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import { SUPA_URL } from '@/ak/config.uts'
|
||||
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
|
||||
|
||||
// ===== 页面状态 =====
|
||||
const pageLoading = ref(true)
|
||||
const hasShop = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
// ===== 从 ml_shops 回读的字段 =====
|
||||
const shopId = ref('')
|
||||
const merchantId = ref('')
|
||||
|
||||
// ===== 店铺表单(绑定到对应的 ml_shops 字段)=====
|
||||
const form = ref({
|
||||
shop_name: '',
|
||||
shop_logo: '',
|
||||
description: '',
|
||||
contact_name: '',
|
||||
contact_phone: '',
|
||||
contact_email: '',
|
||||
business_license: '',
|
||||
status: 1,
|
||||
product_count: 0,
|
||||
created_at: ''
|
||||
})
|
||||
|
||||
// 编辑前的快照(用于取消时恢复)
|
||||
const formSnapshot = ref({ ...form.value })
|
||||
|
||||
// ===== 扩展资质(存储在 ml_user_profiles.verification_data)=====
|
||||
const extData = ref({
|
||||
shop_type: 'personal',
|
||||
id_card_front: '',
|
||||
id_card_back: '',
|
||||
license_image: '',
|
||||
brand_authorization: ''
|
||||
})
|
||||
const extSnapshot = ref({ ...extData.value })
|
||||
|
||||
// ===== 店铺类型配置 =====
|
||||
const shopTypeOptions = [
|
||||
{ value: 'personal', label: '个人店' },
|
||||
{ value: 'individual', label: '个体工商户' },
|
||||
{ value: 'enterprise', label: '普通企业店' },
|
||||
{ value: 'flagship', label: '旗舰店' },
|
||||
{ value: 'exclusive', label: '专卖店' },
|
||||
{ value: 'specialty', label: '专营店' },
|
||||
]
|
||||
const shopTypeLabels = shopTypeOptions.map(o => o.label)
|
||||
const shopTypeIndex = ref(0)
|
||||
|
||||
function getShopTypeLabel(val: string): string {
|
||||
const found = shopTypeOptions.find(o => o.value === val)
|
||||
return found ? found.label : '个人店'
|
||||
}
|
||||
|
||||
function onShopTypeChange(e: any) {
|
||||
const idx = e.detail.value as number
|
||||
shopTypeIndex.value = idx
|
||||
extData.value.shop_type = shopTypeOptions[idx].value
|
||||
}
|
||||
|
||||
// ===== 加载当前店铺 =====
|
||||
onMounted(async () => {
|
||||
await loadShop()
|
||||
})
|
||||
|
||||
async function loadShop() {
|
||||
pageLoading.value = true
|
||||
try {
|
||||
const userId = supa.getSession().user?.getString('id')
|
||||
if (!userId) {
|
||||
pageLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 根据 merchant_id 查询 ml_shops(merchant_id = ak_users.id = auth user id)
|
||||
const shopRes = await supa.from('ml_shops')
|
||||
.select('*')
|
||||
.eq('merchant_id', userId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (shopRes.error != null || !shopRes.data) {
|
||||
hasShop.value = false
|
||||
pageLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 解析返回数据
|
||||
const rawData = shopRes.data
|
||||
let shopRow: UTSJSONObject | null = null
|
||||
|
||||
if (Array.isArray(rawData)) {
|
||||
if ((rawData as Array<any>).length === 0) {
|
||||
hasShop.value = false
|
||||
pageLoading.value = false
|
||||
return
|
||||
}
|
||||
shopRow = (rawData as Array<UTSJSONObject>)[0]
|
||||
} else {
|
||||
shopRow = rawData as UTSJSONObject
|
||||
}
|
||||
|
||||
if (!shopRow) {
|
||||
hasShop.value = false
|
||||
pageLoading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
hasShop.value = true
|
||||
shopId.value = shopRow.getString('id') ?? ''
|
||||
merchantId.value = shopRow.getString('merchant_id') ?? userId
|
||||
|
||||
form.value = {
|
||||
shop_name: shopRow.getString('shop_name') ?? '',
|
||||
shop_logo: shopRow.getString('shop_logo') ?? '',
|
||||
description: shopRow.getString('description') ?? '',
|
||||
contact_name: shopRow.getString('contact_name') ?? '',
|
||||
contact_phone: shopRow.getString('contact_phone') ?? '',
|
||||
contact_email: shopRow.getString('contact_email') ?? '',
|
||||
business_license: shopRow.getString('business_license') ?? '',
|
||||
status: shopRow.getNumber('status') ?? 1,
|
||||
product_count: shopRow.getNumber('product_count') ?? 0,
|
||||
created_at: shopRow.getString('created_at') ?? ''
|
||||
}
|
||||
formSnapshot.value = { ...form.value }
|
||||
|
||||
// 加载扩展资质
|
||||
await loadExtData(userId)
|
||||
} catch (e: any) {
|
||||
console.error('[ShopManage] 加载店铺失败:', e)
|
||||
hasShop.value = false
|
||||
} finally {
|
||||
pageLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadExtData(userId: string) {
|
||||
try {
|
||||
const profileRes = await supa.from('ml_user_profiles')
|
||||
.select('verification_data')
|
||||
.eq('user_id', userId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (profileRes.error != null || !profileRes.data) return
|
||||
|
||||
const rawData = profileRes.data
|
||||
let profileRow: UTSJSONObject | null = null
|
||||
if (Array.isArray(rawData)) {
|
||||
if ((rawData as Array<any>).length > 0) {
|
||||
profileRow = (rawData as Array<UTSJSONObject>)[0]
|
||||
}
|
||||
} else {
|
||||
profileRow = rawData as UTSJSONObject
|
||||
}
|
||||
|
||||
if (!profileRow) return
|
||||
|
||||
const vd = profileRow.getJSON('verification_data')
|
||||
if (vd != null) {
|
||||
extData.value = {
|
||||
shop_type: vd.getString('shop_type') ?? 'personal',
|
||||
id_card_front: vd.getString('id_card_front') ?? '',
|
||||
id_card_back: vd.getString('id_card_back') ?? '',
|
||||
license_image: vd.getString('license_image') ?? '',
|
||||
brand_authorization: vd.getString('brand_authorization') ?? ''
|
||||
}
|
||||
// 同步 picker index
|
||||
const idx = shopTypeOptions.findIndex(o => o.value === extData.value.shop_type)
|
||||
shopTypeIndex.value = idx >= 0 ? idx : 0
|
||||
}
|
||||
extSnapshot.value = { ...extData.value }
|
||||
} catch (e: any) {
|
||||
console.warn('[ShopManage] 加载扩展资质失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 编辑模式 =====
|
||||
function enterEdit() {
|
||||
formSnapshot.value = { ...form.value }
|
||||
extSnapshot.value = { ...extData.value }
|
||||
isEditing.value = true
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
form.value = { ...formSnapshot.value }
|
||||
extData.value = { ...extSnapshot.value }
|
||||
const idx = shopTypeOptions.findIndex(o => o.value === extData.value.shop_type)
|
||||
shopTypeIndex.value = idx >= 0 ? idx : 0
|
||||
isEditing.value = false
|
||||
}
|
||||
|
||||
// ===== 图片选择 =====
|
||||
function pickLogoImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const paths = res.tempFilePaths as string[]
|
||||
if (paths.length > 0) form.value.shop_logo = paths[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pickIdCardFront() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const paths = res.tempFilePaths as string[]
|
||||
if (paths.length > 0) extData.value.id_card_front = paths[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pickIdCardBack() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const paths = res.tempFilePaths as string[]
|
||||
if (paths.length > 0) extData.value.id_card_back = paths[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pickLicenseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const paths = res.tempFilePaths as string[]
|
||||
if (paths.length > 0) extData.value.license_image = paths[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function pickBrandAuth() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: (res) => {
|
||||
const paths = res.tempFilePaths as string[]
|
||||
if (paths.length > 0) extData.value.brand_authorization = paths[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ===== 上传图片到 Supabase storage =====
|
||||
async function uploadImage(localPath: string, folder: string): Promise<string> {
|
||||
if (!localPath) return ''
|
||||
if (localPath.startsWith('http') && !localPath.startsWith('blob:')) return localPath
|
||||
|
||||
const extMatch = localPath.match(/\.(\w+)$/)
|
||||
const ext = extMatch ? extMatch[1] : 'jpg'
|
||||
const uuid = Math.random().toString(36).substring(2, 10)
|
||||
const remotePath = `shop/${folder}_${Date.now()}_${uuid}.${ext}`
|
||||
|
||||
const res = await supa.storage.from('zhipao').upload(remotePath, localPath, {})
|
||||
if (res.error != null) throw new Error('图片上传失败')
|
||||
|
||||
return `${SUPA_URL}/storage/v1/object/public/zhipao/${remotePath}`
|
||||
}
|
||||
|
||||
// ===== 保存 =====
|
||||
async function saveShop() {
|
||||
if (!form.value.shop_name.trim()) {
|
||||
uni.showToast({ title: '请输入店铺名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const userId = supa.getSession().user?.getString('id')
|
||||
if (!userId) {
|
||||
uni.showToast({ title: '未获取到登录信息', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
saving.value = true
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
try {
|
||||
// 1. 上传有变更的图片(本地路径需上传,已有 URL 直接复用)
|
||||
const [logoUrl, idFrontUrl, idBackUrl, licenseUrl, brandUrl] = await Promise.all([
|
||||
form.value.shop_logo ? uploadImage(form.value.shop_logo, 'logo') : Promise.resolve(''),
|
||||
extData.value.id_card_front ? uploadImage(extData.value.id_card_front, 'id_front') : Promise.resolve(''),
|
||||
extData.value.id_card_back ? uploadImage(extData.value.id_card_back, 'id_back') : Promise.resolve(''),
|
||||
extData.value.license_image ? uploadImage(extData.value.license_image, 'license') : Promise.resolve(''),
|
||||
extData.value.brand_authorization ? uploadImage(extData.value.brand_authorization, 'brand_auth') : Promise.resolve(''),
|
||||
])
|
||||
|
||||
// 更新本地表单中的 URL(去掉临时路径)
|
||||
if (logoUrl) form.value.shop_logo = logoUrl
|
||||
if (idFrontUrl) extData.value.id_card_front = idFrontUrl
|
||||
if (idBackUrl) extData.value.id_card_back = idBackUrl
|
||||
if (licenseUrl) extData.value.license_image = licenseUrl
|
||||
if (brandUrl) extData.value.brand_authorization = brandUrl
|
||||
|
||||
// 2. 更新 ml_shops
|
||||
const shopUpdatePayload = {
|
||||
shop_name: form.value.shop_name.trim(),
|
||||
shop_logo: form.value.shop_logo,
|
||||
description: form.value.description,
|
||||
contact_name: form.value.contact_name,
|
||||
contact_phone: form.value.contact_phone,
|
||||
contact_email: form.value.contact_email,
|
||||
business_license: form.value.business_license
|
||||
} as UTSJSONObject
|
||||
|
||||
const updateRes = await supa.from('ml_shops')
|
||||
.update(shopUpdatePayload)
|
||||
.eq('merchant_id', userId)
|
||||
.execute()
|
||||
|
||||
if (updateRes.error != null) {
|
||||
console.error('[ShopManage] 更新 ml_shops 失败:', updateRes.error)
|
||||
const errMsg = updateRes.error?.getString ? updateRes.error.getString('message') ?? '保存失败' : '保存失败'
|
||||
uni.showToast({ title: '保存失败:' + errMsg, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 验证更新结果
|
||||
if (!updateRes.data) {
|
||||
console.error('[ShopManage] 更新 ml_shops 未返回数据')
|
||||
} else {
|
||||
console.log('[ShopManage] 更新成功,返回数据:', updateRes.data)
|
||||
}
|
||||
|
||||
// 3. 更新 ml_user_profiles.verification_data
|
||||
const extPayloadData = {
|
||||
shop_type: extData.value.shop_type,
|
||||
id_card_front: extData.value.id_card_front,
|
||||
id_card_back: extData.value.id_card_back,
|
||||
license_image: extData.value.license_image,
|
||||
brand_authorization: extData.value.brand_authorization,
|
||||
updated_at: new Date().toISOString()
|
||||
} as UTSJSONObject
|
||||
|
||||
// 先查询是否已有对应记录
|
||||
const profCheckRes = await supa.from('ml_user_profiles')
|
||||
.select('id')
|
||||
.eq('user_id', userId)
|
||||
.single()
|
||||
.execute()
|
||||
|
||||
if (profCheckRes.data != null && !profCheckRes.error) {
|
||||
const profRes = await supa.from('ml_user_profiles')
|
||||
.update({ verification_data: extPayloadData } as UTSJSONObject)
|
||||
.eq('user_id', userId)
|
||||
.execute()
|
||||
if (profRes.error != null) {
|
||||
console.warn('[ShopManage] 更新扩展资质失败(不影响主信息):', profRes.error)
|
||||
}
|
||||
} else {
|
||||
const profRes = await supa.from('ml_user_profiles')
|
||||
.insert({ user_id: userId, verification_data: extPayloadData } as UTSJSONObject)
|
||||
.execute()
|
||||
if (profRes.error != null) {
|
||||
console.warn('[ShopManage] 写入扩展资质失败(不影响主信息):', profRes.error)
|
||||
}
|
||||
}
|
||||
|
||||
isEditing.value = false
|
||||
formSnapshot.value = { ...form.value }
|
||||
extSnapshot.value = { ...extData.value }
|
||||
|
||||
// 4. 重新拉取最新数据回显
|
||||
await loadShop()
|
||||
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
} catch (e: any) {
|
||||
console.error('[ShopManage] 保存异常:', e)
|
||||
uni.showToast({ title: e?.message ?? '保存失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
saving.value = false
|
||||
uni.hideLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 导航 =====
|
||||
function goToCreate() {
|
||||
openRoute('shop_create')
|
||||
}
|
||||
|
||||
// ===== 工具 =====
|
||||
function formatDate(dateStr: string): string {
|
||||
if (!dateStr) return '-'
|
||||
try {
|
||||
const d = new Date(dateStr)
|
||||
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
||||
} catch (_) {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.shop-manage-page {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* 加载 */
|
||||
.loading-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
}
|
||||
.loading-txt { font-size: 14px; color: #999; }
|
||||
|
||||
/* 空态 */
|
||||
.empty-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80px 40px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.empty-icon { font-size: 56px; margin-bottom: 16px; }
|
||||
.empty-title { font-size: 18px; font-weight: 600; color: #333; margin-bottom: 8px; }
|
||||
.empty-desc { font-size: 13px; color: #999; margin-bottom: 28px; }
|
||||
.btn-create {
|
||||
padding: 0 32px;
|
||||
height: 40px;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 页面头部 */
|
||||
.page-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.page-title {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn-edit {
|
||||
padding: 0 20px;
|
||||
height: 36px;
|
||||
background: #1890ff;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.edit-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
}
|
||||
.btn-cancel {
|
||||
padding: 0 16px;
|
||||
height: 36px;
|
||||
background: #fff;
|
||||
color: #606266;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.btn-save {
|
||||
padding: 0 20px;
|
||||
height: 36px;
|
||||
background: #52c41a;
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&.disabled { background: #b7eb8f; cursor: not-allowed; }
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.required { color: #ff4d4f; margin-right: 4px; }
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
margin-bottom: 16px;
|
||||
&:last-child { margin-bottom: 0; }
|
||||
}
|
||||
.form-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
.form-input {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.form-textarea {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
box-sizing: border-box;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
/* 只读模式展示 */
|
||||
.view-val {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.view-val.multiline { white-space: pre-wrap; }
|
||||
.empty-val { font-size: 13px; color: #aaa; }
|
||||
|
||||
/* Logo 预览 */
|
||||
.preview-logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.upload-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 上传区域 */
|
||||
.upload-area {
|
||||
width: 100px;
|
||||
height: 80px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
&:hover { border-color: #1890ff; }
|
||||
}
|
||||
.upload-area-wide {
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
&:hover { border-color: #1890ff; }
|
||||
}
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.upload-icon { font-size: 20px; color: #c0c4cc; }
|
||||
.upload-txt { font-size: 11px; color: #aaa; }
|
||||
|
||||
/* 证件图 */
|
||||
.cert-img {
|
||||
width: 200px;
|
||||
height: 120px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.view-mode { display: flex; flex-direction: row; align-items: center; }
|
||||
|
||||
/* Picker */
|
||||
.picker-trigger {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 160px;
|
||||
height: 36px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.picker-arrow { font-size: 10px; color: #c0c4cc; }
|
||||
|
||||
/* 状态信息 */
|
||||
.info-card { }
|
||||
.info-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #f9f9f9;
|
||||
&:last-child { border-bottom: none; }
|
||||
}
|
||||
.info-label { width: 100px; font-size: 13px; color: #999; }
|
||||
.info-val { font-size: 13px; color: #333; }
|
||||
.status-normal { color: #52c41a; font-weight: 500; }
|
||||
.status-off { color: #ff4d4f; font-weight: 500; }
|
||||
</style>
|
||||
Reference in New Issue
Block a user