feat:优化

This commit is contained in:
dzc committed 2026-09-29 20:00:47 +08:00
1 parent 585e5e2f62
commit 16ec75360e
4 files changed
+401 -95

No files matched your search

+4 -4
View File
@@ -25,16 +25,16 @@ VITE_OAUTH2_APP_CLIENT='mini:mini'
# VITE_ZZD_CORP_ID=
# --------------------------- 开发环境 api地址 ----------------------------------
VITE_APP_DEV_SERVICE = https://www.pjzhhq.cn/api # 域名线上
# VITE_APP_DEV_SERVICE = https://www.pjzhhq.cn/api # 域名线上
#VITE_APP_DEV_SERVICE = http://59.202.170.152:81/api # 信创线上
# VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
VITE_APP_DEV_SERVICE = https://dzc-test-1.fy.takin.cc # 邓-本地
# VITE_APP_DEV_SERVICE = http://127.0.0.1:9999 # 邓-本地
# VITE_APP_DEV_SERVICE = http://123.157.250.162:8089/api # 测试线上
# --------------------------- 开发环境 图片访问基础地址 ---------------------------
VITE_IMAGE_DEV_PATH = https://www.pjzhhq.cn/api # 域名线上
# VITE_IMAGE_DEV_PATH = https://www.pjzhhq.cn/api # 域名线上
#VITE_IMAGE_DEV_PATH = http://59.202.170.152:81/api # 信创线上
# VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc # 邓-本地
VITE_IMAGE_DEV_PATH = https://dzc-test-1.fy.takin.cc # 邓-本地
# VITE_IMAGE_DEV_PATH = http://127.0.0.1:9999
# VITE_IMAGE_DEV_PATH = http://123.157.250.162:8089/api # 测试线上
+8 -1
View File
@@ -20,4 +20,11 @@ dist
*.sln
*.sw?
.env
.env
# AI 辅助工具生成文件(ZCode 等)
.zcode/
.zcodeignore
.mimosa/
.v2c/
.video_agent/
+164 -60
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref, reactive } from "vue"
import { ref, reactive, computed } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import { merchantApi } from "@@/api"
import { hooks, router } from "@@/index"
@@ -12,6 +12,8 @@ const state = reactive<any>({
type: "2",
parentId: "",
},
detail: {},
parent: null,
})
const pageHook = hooks.usePage(pageRef, merchantApi.childrenPage, null, "data.records")
@@ -19,6 +21,24 @@ const query = (page: number, pagesize: number) => {
pageHook.startPage(page, pagesize, false, state.pa)
}
const swiperPics = computed(() => String(state.detail?.swiperPic || "").split(",").filter(Boolean))
// parentId 为空/-1 视为主店,否则是分店
const isBranch = computed(() => {
const pid = state.detail?.parentId
return pid != null && pid !== "" && pid !== -1
})
const jobTimeText = (v: any) => {
if (!v?.jobTime || v.jobTime == ",,") return ""
const labels = ["早", "中", "晚"]
return v.jobTime
.split(",")
.map((s: string, i: number) => (s ? `${labels[i]} ${s}` : ""))
.filter(Boolean)
.join(" · ")
}
onLoad((options: any) => {
const app = getApp()
app._initTabbar()
@@ -49,6 +69,27 @@ onShow(async () => {
const getDetail = async () => {
const { data } = await merchantApi.detail({ id: state.pa.parentId })
state.detail = data
await loadParent(data?.parentId)
}
// 若当前店铺是分店,加载父店信息展示
const loadParent = async (pid: any) => {
state.parent = null
if (!pid || pid == -1) return
try {
const { data } = await merchantApi.detail({ id: pid })
state.parent = data || null
} catch {
state.parent = null
}
}
const toParentBrand = async () => {
if (!state.parent) return
const isLogin = await hooks.useCheckLogin()
if (isLogin) {
router.jump("/pages/merchant/brandDetail", { parentId: state.parent.id })
}
}
const toDetail = async (v: any) => {
@@ -60,67 +101,130 @@ const toDetail = async (v: any) => {
</script>
<template>
<view class="h-full overflow-hidden">
<swiper
class="w-100vw h-30vh"
circular
:indicator-dots="true"
indicator-active-color="#0779FD"
indicator-color="#ffffff"
:autoplay="true"
>
<swiper-item v-for="(v, i) in state.detail?.swiperPic?.split(',')" :key="i">
<image class="w-full h-full" :src="filePath(v)" mode="aspectFill" />
</swiper-item>
</swiper>
<crud-layout bg="/static/page/blue-bg.png">
<z-paging ref="pageRef" height="100%" :fixed="false" v-model="pageHook.list" :default-page-size="10"
safe-area-inset-bottom :loading-more-enabled="true" :show-scrollbar="false" @query="query"
:auto-clean-list-when-reload="false" :auto-scroll-to-top-when-reload="false"
:hide-empty-view="!!state.parent" empty-view-text="暂无分店">
<view class="h-30rpx" />
<crud-layout>
<z-paging
ref="pageRef"
height="70%"
:fixed="false"
v-model="pageHook.list"
:default-page-size="10"
safe-area-inset-bottom
:loading-more-enabled="true"
:show-scrollbar="false"
@query="query"
:auto-clean-list-when-reload="false"
:auto-scroll-to-top-when-reload="false"
@scroll="pageHook.scroll"
>
<view class="h-20rpx" />
<view class="flex-row card p-20rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="toDetail(v)">
<image class="w-180rpx h-180rpx rounded-12rpx my-auto" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="sub-text-34">{{ v.name }}</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view class="flex-1">
<view v-if="v.phone" class="flex items-center">
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ v.phone }}</view>
</view>
</view>
<tm-tag class="max-w-160rpx" size="xs">
{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}
</tm-tag>
</view>
<view class="second-text-24">
<view v-if="v.jobTime != ',,'" class="second-text-28">营业时间:</view>
<view>{{ v.jobTime?.split(",")[0] ? "早:" + v.jobTime?.split(",")[0] : "" }}</view>
<view>{{ v.jobTime?.split(",")[1] ? "中:" + v.jobTime?.split(",")[1] : "" }}</view>
<view>{{ v.jobTime?.split(",")[2] ? "晚:" + v.jobTime?.split(",")[2] : "" }}</view>
</view>
<view v-if="v.addressDes" class="second-text-28">
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
<text class="ml-1">地址:</text>
<text class="m-l-10rpx">{{ v.addressDes }}</text>
</view>
<view v-if="swiperPics.length" class="card h-320rpx overflow-hidden m-b-20rpx">
<swiper class="w-full h-full" circular :indicator-dots="true" indicator-active-color="#0779FD"
indicator-color="rgba(255,255,255,0.8)" :autoplay="true" interval="4000">
<swiper-item v-for="(p, i) in swiperPics" :key="i">
<image class="w-full h-full" :src="filePath(p)" mode="aspectFill" />
</swiper-item>
</swiper>
</view>
<view class="card p-24rpx flex">
<image class="w-160rpx h-160rpx rounded-16rpx bg-#F2F6FA my-auto flex-none" :src="filePath(state.detail.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx min-w-0">
<view class="flex items-center">
<text class="sub-text-30 font-bold flex-1 min-w-0 show-line">{{ state.detail.name }}</text>
<view class="brand-pill m-l-12rpx flex-none">{{ isBranch ? "分店" : "品牌店" }}</view>
</view>
<view v-if="state.detail.discountsPrice" class="promo-bar m-t-12rpx">
<tm-icon name="coupon-line" color="#f63a36" :size="24" />
<text class="m-l-8rpx flex-1 min-w-0 show-line">{{ state.detail.discountsPrice }}</text>
</view>
<view v-if="state.detail.phone || state.detail.deptName" class="m-t-12rpx flex items-center justify-between">
<view v-if="state.detail.phone" class="flex items-center flex-1 min-w-0">
<tm-icon name="phone-line" color="#A6AEB8" :size="26" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ state.detail.phone }}</text>
</view>
<tm-tag v-if="state.detail.deptName" class="flex-none" size="xs">
{{ state.detail.deptName.length > 5 ? state.detail.deptName.substring(0, 5) + "..." : state.detail.deptName }}
</tm-tag>
</view>
<view v-if="jobTimeText(state.detail)" class="m-t-8rpx flex items-center">
<tm-icon name="calendar-schedule-line" color="#A6AEB8" :size="26" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ jobTimeText(state.detail) }}</text>
</view>
<view v-if="state.detail.addressDes" class="m-t-8rpx flex items-center">
<tm-icon name="map-pin-2-line" color="#A6AEB8" :size="26" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ state.detail.addressDes }}</text>
</view>
</view>
</z-paging>
</crud-layout>
</view>
</view>
<view v-if="state.parent" class="card p-20rpx m-t-20rpx flex items-center" hover-class="card-hover"
@tap="toParentBrand">
<image class="w-88rpx h-88rpx rounded-12rpx bg-#F2F6FA flex-none" :src="filePath(state.parent.logoPic)"
mode="aspectFill" />
<view class="flex-1 min-w-0 m-l-16rpx">
<view class="second-text-22">所属品牌</view>
<view class="sub-text-28 font-medium m-t-2rpx show-line">{{ state.parent.name }}</view>
</view>
<tm-icon name="arrow-right-s-line" color="#C2C9D2" :size="28" />
</view>
<view v-if="!state.parent" class="flex items-center m-t-28rpx m-b-16rpx px-4rpx">
<view class="section-bar" />
<text class="sub-text-28 font-bold m-l-12rpx">分店列表</text>
<text v-if="Number(state.detail.mchCount) > 0" class="second-text-24 m-l-8rpx">({{ state.detail.mchCount }}家)</text>
</view>
<view v-for="(v, i) in pageHook.list" :key="v.id ?? i" class="card p-20rpx m-b-20rpx flex"
hover-class="card-hover" @tap="toDetail(v)">
<image class="w-140rpx h-140rpx rounded-12rpx bg-#F2F6FA my-auto flex-none" :src="filePath(v.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-16rpx min-w-0">
<view class="flex items-center">
<text class="sub-text-28 font-bold flex-1 min-w-0 show-line">{{ v.name }}</text>
<tm-icon name="arrow-right-s-line" color="#C2C9D2" :size="28" />
</view>
<view v-if="v.discountsPrice" class="promo-bar m-t-10rpx">
<tm-icon name="coupon-line" color="#f63a36" :size="22" />
<text class="m-l-8rpx flex-1 min-w-0 show-line">{{ v.discountsPrice }}</text>
</view>
<view v-if="v.phone" class="m-t-8rpx flex items-center">
<tm-icon name="phone-line" color="#A6AEB8" :size="24" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ v.phone }}</text>
</view>
<view v-if="v.addressDes" class="m-t-6rpx flex items-center">
<tm-icon name="map-pin-2-line" color="#A6AEB8" :size="24" />
<text class="second-text-22 m-l-8rpx flex-1 min-w-0 show-line">{{ v.addressDes }}</text>
</view>
</view>
</view>
</z-paging>
</crud-layout>
</template>
<style lang="scss" scoped>
.card-hover {
opacity: 0.86;
}
.brand-pill {
padding: 4rpx 14rpx;
border-radius: 999rpx;
background: rgba(7, 121, 253, 0.1);
color: #0779fd;
font-size: 20rpx;
font-weight: 500;
line-height: 1.5;
}
.promo-bar {
display: flex;
align-items: center;
padding: 6rpx 12rpx;
border-radius: 8rpx;
background: #fff1f0;
color: #f63a36;
font-size: 24rpx;
font-weight: 500;
line-height: 1.4;
}
.section-bar {
width: 8rpx;
height: 28rpx;
border-radius: 8rpx;
background: #0779fd;
}
</style>
+225 -30
View File
@@ -60,6 +60,55 @@ const toDetail = async (v: any) => {
uni.showToast({ title: "合作商家类型未知", icon: "none" })
}
}
const isBrand = (v: any) => v.supType == "10" || v.supType == "50"
const hasBranch = (v: any) => isBrand(v) && Number(v.mchCount) > 0
const jobTimeText = (v: any) => {
if (!v.jobTime || v.jobTime == ",,") return ""
const labels = ["早", "中", "晚"]
return v.jobTime
.split(",")
.map((s: string, i: number) => (s ? `${labels[i]} ${s}` : ""))
.filter(Boolean)
.join(" · ")
}
// 后端对过大 size 的分页查询会返回空列表,这里用小分页循环拉全量
const loadBranches = async (v: any) => {
v._loading = true
try {
const size = 10
const all: any[] = []
for (let current = 1; current <= 50; current++) {
const res: any = await merchantApi.childrenPage({ current, size, type: "2", parentId: v.id })
if (res.code != 0) throw new Error(res.msg || "分店加载失败")
const records = res.data?.records || []
all.push(...records)
if (records.length < size || all.length >= Number(res.data?.total || 0)) break
}
v._children = all
// 空结果不缓存,避免一次异常返回空后本次会话内再也刷不出来
v._childrenLoaded = all.length > 0
} catch (error: any) {
v._expanded = false
uni.showToast({ title: error?.message || "分店加载失败", icon: "none" })
} finally {
v._loading = false
}
}
const toggleBranch = async (v: any) => {
v._expanded = !v._expanded
if (v._expanded && !v._childrenLoaded && !v._loading) await loadBranches(v)
}
const toBranchDetail = async (b: any) => {
const isLogin = await hooks.useCheckLogin()
if (isLogin) {
router.jump("/pages/merchant/detail", { id: b.id })
}
}
</script>
<template>
@@ -71,11 +120,11 @@ const toDetail = async (v: any) => {
<view class="h-30rpx" />
<view class="flex w-full">
<tm-input class="flex-1" v-model="state.pa.name" type="text" height="72" minHeight="72"
fontSize="28" :showClear="true" :focus="false" placeholder="请输入商家名称"
style="border: 4rpx solid #0779fd; border-radius: 14rpx" :focus-highlight="false"
color="#ECF4F9">
fontSize="28" :showClear="true" :focus="false" placeholder="搜索商家名称"
:focus-highlight="false" color="#ffffff"
style="border-radius: 36rpx; box-shadow: 0 6rpx 20rpx rgba(7, 121, 253, 0.1)">
<template v-slot:rightIcon>
<tm-button class="mr-1 px-3 py-26rpx text-30rpx!" size="xs" @click="search">搜 索</tm-button>
<tm-button class="mr-1 px-4 py-24rpx text-28rpx!" size="xs" @click="search">搜索</tm-button>
</template>
</tm-input>
</view>
@@ -84,39 +133,185 @@ const toDetail = async (v: any) => {
</view>
</template>
<view class="flex-row card p-20rpx m-b-20rpx" v-for="(v, i) in pageHook.list" :key="i" @tap="toDetail(v)">
<image class="w-180rpx h-180rpx rounded-12rpx my-auto" :src="filePath(v.logoPic)" mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx">
<view class="sub-text-34">
<text class="mr-2">{{ v.name }}</text>
<!-- <tm-tag v-if="v.supType == '10' && Number(v.mchCount) > 0" size="xs" skin="thin">共{{ v.mchCount }}家</tm-tag> -->
</view>
<view class="m-t-10rpx">
<view class="flex justify-between items-center">
<view class="flex-1">
<view v-if="v.phone" class="flex items-center">
<image class="w-24rpx h-24rpx" src="@/static/page/phone.svg" />
<view class="second-text-28 ml-1">{{ v.phone }}</view>
</view>
<view class="card m-b-20rpx overflow-hidden" hover-class="card-hover" v-for="(v, i) in pageHook.list"
:key="v.id ?? i" @tap="toDetail(v)">
<view class="flex p-24rpx">
<image class="w-160rpx h-160rpx rounded-16rpx bg-#F2F6FA my-auto flex-none" :src="filePath(v.logoPic)"
mode="aspectFill" />
<view class="flex-1 flex-col m-l-20rpx min-w-0">
<view class="flex items-center">
<text class="sub-text-30 font-bold flex-1 min-w-0 show-line">{{ v.name }}</text>
<view v-if="isBrand(v)" class="brand-pill m-l-12rpx flex-none">品牌店</view>
</view>
<view v-if="v.discountsPrice" class="promo-bar m-t-12rpx">
<tm-icon name="coupon-line" color="#f63a36" :size="24" />
<text class="m-l-8rpx flex-1 min-w-0 show-line">{{ v.discountsPrice }}</text>
</view>
<view v-if="jobTimeText(v)" class="m-t-12rpx flex items-center">
<tm-icon name="calendar-schedule-line" color="#A6AEB8" :size="26" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ jobTimeText(v) }}</text>
</view>
<view v-if="v.phone || v.deptName" class="m-t-8rpx flex items-center justify-between">
<view v-if="v.phone" class="flex items-center flex-1 min-w-0">
<tm-icon name="phone-line" color="#A6AEB8" :size="26" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ v.phone }}</text>
</view>
<tm-tag class="max-w-160rpx" size="xs">{{ v.deptName.length > 5 ? v.deptName.substring(0, 5)
+ "..." :
v.deptName }}</tm-tag>
<tm-tag v-if="v.deptName" class="flex-none" size="xs">
{{ v.deptName.length > 5 ? v.deptName.substring(0, 5) + "..." : v.deptName }}
</tm-tag>
</view>
<view class="second-text-24">
<view v-if="v.jobTime != ',,'" class="second-text-28">营业时间:</view>
<view>{{ v.jobTime?.split(",")[0] ? "早:" + v.jobTime?.split(",")[0] : "" }}</view>
<view>{{ v.jobTime?.split(",")[1] ? "中:" + v.jobTime?.split(",")[1] : "" }}</view>
<view>{{ v.jobTime?.split(",")[2] ? "晚:" + v.jobTime?.split(",")[2] : "" }}</view>
<view v-if="v.addressDes" class="m-t-8rpx flex items-center">
<tm-icon name="map-pin-2-line" color="#A6AEB8" :size="26" />
<text class="second-text-24 m-l-8rpx flex-1 min-w-0 show-line">{{ v.addressDes }}</text>
</view>
<view v-if="v.addressDes" class="second-text-28">
<image class="w-24rpx h-24rpx" src="@/static/page/address.svg" />
<text class="ml-1">地址:</text>
<text class="m-l-10rpx">{{ v.addressDes }}</text>
</view>
</view>
<view v-if="hasBranch(v)">
<view class="branch-toggle" hover-class="branch-toggle-hover" @tap.stop="toggleBranch(v)">
<view class="flex items-center">
<tm-icon name="store-2-line" color="#0779FD" :size="26" />
<text class="branch-toggle-text">分店 · {{ v.mchCount }}家</text>
</view>
<view class="flex items-center">
<text class="branch-toggle-hint">{{ v._expanded ? "收起" : "展开" }}</text>
<tm-icon name="arrow-down-s-line" color="#0779FD" :size="26"
:class="['chevron', v._expanded ? 'chevron-open' : '']" />
</view>
</view>
<view v-if="v._expanded" class="branch-panel">
<view v-if="v._loading" class="branch-tip">分店加载中...</view>
<template v-else>
<view v-for="(b, bi) in v._children || []" :key="b.id ?? bi" class="branch-item"
hover-class="branch-item-hover" @tap.stop="toBranchDetail(b)">
<image v-if="b.logoPic" class="w-56rpx h-56rpx rounded-10rpx bg-#F2F6FA flex-none"
:src="filePath(b.logoPic)" mode="aspectFill" />
<view v-else class="branch-logo-fallback flex-row-center">
<tm-icon name="store-2-line" color="#9BBDF6" :size="28" />
</view>
<view class="flex-1 min-w-0 m-l-14rpx">
<text class="sub-text-26 show-line">{{ b.name }}</text>
<view v-if="b.addressDes || b.phone" class="m-t-2rpx">
<text class="second-text-22 show-line">{{ b.addressDes || b.phone }}</text>
</view>
</view>
<tm-icon name="arrow-right-s-line" color="#C2C9D2" :size="28" />
</view>
<view v-if="!(v._children || []).length" class="branch-tip">暂无分店信息</view>
</template>
</view>
</view>
</view>
</z-paging>
</crud-layout>
</template>
<style lang="scss" scoped>
.card-hover {
opacity: 0.86;
}
.brand-pill {
padding: 4rpx 14rpx;
border-radius: 999rpx;
background: rgba(7, 121, 253, 0.1);
color: #0779fd;
font-size: 20rpx;
font-weight: 500;
line-height: 1.5;
}
.promo-bar {
display: flex;
align-items: center;
padding: 6rpx 12rpx;
border-radius: 8rpx;
background: #fff1f0;
color: #f63a36;
font-size: 24rpx;
font-weight: 500;
line-height: 1.4;
}
.branch-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 24rpx;
border-top: 1rpx solid #f0f3f7;
}
.branch-toggle-hover {
background: #f5f9ff;
}
.branch-toggle-text {
margin-left: 8rpx;
font-size: 24rpx;
font-weight: 500;
color: #0779fd;
}
.branch-toggle-hint {
font-size: 22rpx;
color: #8fa6c4;
margin-right: 4rpx;
}
.chevron {
transition: transform 0.25s ease;
}
.chevron-open {
transform: rotate(180deg);
}
.branch-panel {
background: #f8fbff;
animation: branch-in 0.22s ease both;
}
@keyframes branch-in {
from {
opacity: 0;
transform: translateY(-10rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.branch-item {
display: flex;
align-items: center;
padding: 16rpx 24rpx;
}
.branch-item + .branch-item {
border-top: 1rpx solid #eef3fa;
}
.branch-item-hover {
background: #eff5fd;
}
.branch-logo-fallback {
width: 56rpx;
height: 56rpx;
border-radius: 10rpx;
background: #eaf2fd;
flex-shrink: 0;
}
.branch-tip {
padding: 24rpx;
text-align: center;
font-size: 24rpx;
color: #9aa5b1;
}
</style>