861 lines
26 KiB
Plaintext
861 lines
26 KiB
Plaintext
<!-- 店铺管理页:查看 / 编辑店铺信息 -->
|
||
<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>
|