文件
yjyf/yjyf-app/pages/service/detail.vue
YuanWen 7ab7429dee feat: 新增统一底部导航与全局样式系统,优化交互体验
新增全局 YjyfTabBar 组件,替换各页面自定义导航,实现一致的 tab 切换逻辑与图标样式
新增全局设计令牌与 SCSS 变量系统,统一色彩、间距、字阶、圆角与投影规范
新增 POST 请求防并发锁,避免重复提交
重构多个页面,添加骨架屏、空状态引导、点击反馈类,修复硬编码样式与假数据
删除废弃的后台入口文件与 nginx 配置,清理冗余代码
2026-11-02 14:00:19 +08:00

119 行
4.8 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="page">
<!-- 骨架屏呼吸占位 (彻底解决白屏) -->
<view class="skeleton-box" v-if="loading && !detail">
<view class="sk-hero yjyf-shimmer"></view>
<view class="sk-card yjyf-shimmer"></view>
<view class="sk-card yjyf-shimmer"></view>
</view>
<view class="main-body" v-else-if="detail">
<view class="hero">
<view class="hero-thumb"><text class="hero-txt">{{ detail.name.slice(0, 2) }}</text></view>
<view class="hero-info">
<text class="name">{{ detail.name }}</text>
<view class="tags">
<text class="tag">{{ detail.price_type_text }}</text>
<text class="tag">{{ detail.cat_name }}</text>
</view>
<text class="price">{{ detail.price_text }}</text>
</view>
</view>
<view class="card">
<view class="card-title">费用说明</view>
<view class="li"><text class="k">基础价</text><text class="v">¥{{ detail.base_price }} / {{ detail.unit }}</text></view>
<view class="li"><text class="k">楼层费</text><text class="v">{{ detail.floor_fee }} 元/层({{ detail.free_floor }} 层内免)</text></view>
<view class="li"><text class="k">加急费</text><text class="v">基础价 20%</text></view>
<view class="li"><text class="k">无电梯</text><text class="v">超免楼层按楼层费计</text></view>
</view>
<view class="card">
<view class="card-title">服务保障</view>
<view class="li"><text class="k">平台认证</text><text class="v">实名 + 技能审核</text></view>
<view class="li"><text class="k">验收付款</text><text class="v">客户验收后平台结算</text></view>
<view class="li"><text class="k">现场取证</text><text class="v">施工拍照留痕</text></view>
<text class="desc" v-if="detail.description">{{ detail.description }}</text>
</view>
<view class="bar">
<view class="bar-left">
<text class="bar-price">{{ detail.price_text }}</text>
<text class="bar-tip">无隐藏费用</text>
</view>
<view class="bar-btn clickable-hover" @tap="goCreate">立即下单</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import api from '@/utils/api.js';
const detail = ref(null);
const loading = ref(true);
let serviceId = 0;
function goCreate() {
uni.navigateTo({ url: '/pages/order/create?service_id=' + serviceId });
}
onLoad(async (options) => {
serviceId = Number(options.id || 0);
const res = await api.serviceDetail(serviceId);
detail.value = res.detail;
});
</script>
<style scoped>
.page { min-height: 100vh; background: #F5F6F8; padding-bottom: 160rpx; }
.hero {
display: flex; align-items: center; padding: 34rpx 28rpx;
background: linear-gradient(160deg, #FFB85C 0%, #F98D25 60%, #ED6F0E 100%);
}
.hero-thumb {
width: 130rpx; height: 130rpx; border-radius: 22rpx; flex-shrink: 0;
background: rgba(255,255,255,0.92); display: flex; align-items: center; justify-content: center;
}
.hero-txt { font-size: 36rpx; font-weight: 700; color: #B85E2A; }
.hero-info { flex: 1; min-width: 0; margin-left: 22rpx; display: flex; flex-direction: column; }
.name { font-size: 38rpx; font-weight: 700; color: #fff; letter-spacing: 1rpx; }
.tags { display: flex; margin-top: 12rpx; }
.tag {
padding: 0 14rpx; line-height: 34rpx; margin-right: 10rpx; border-radius: 17rpx;
background: rgba(255,255,255,0.22); color: #fff; font-size: 20rpx;
}
.price { margin-top: 14rpx; font-size: 34rpx; font-weight: 700; color: #FFF3DF; }
.card {
margin: 22rpx 28rpx 0; padding: 26rpx 24rpx; background: #fff;
border-radius: 20rpx; box-shadow: 0 6rpx 20rpx rgba(240,129,31,0.06);
}
.card-title { font-size: 30rpx; font-weight: 700; color: #1F1F1F; margin-bottom: 14rpx; }
.li { display: flex; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #F4F4F4; }
.k { font-size: 26rpx; color: #666; }
.v { font-size: 26rpx; color: #333; }
.desc { display: block; margin-top: 16rpx; font-size: 24rpx; color: #9A9A9A; line-height: 36rpx; }
.bar {
position: fixed; left: 0; right: 0; bottom: 0; height: 120rpx;
padding: 0 28rpx; padding-bottom: env(safe-area-inset-bottom);
display: flex; align-items: center; justify-content: space-between;
background: #fff; box-shadow: 0 -6rpx 24rpx rgba(0,0,0,0.07);
}
.bar-left { display: flex; flex-direction: column; }
.bar-price { font-size: 34rpx; font-weight: 700; color: #F0710D; }
.bar-tip { font-size: 20rpx; color: #B5B5B5; }
.bar-btn {
height: 84rpx; padding: 0 56rpx; display: flex; align-items: center;
border-radius: 42rpx; color: #fff; font-size: 30rpx; font-weight: 600;
background: linear-gradient(135deg, #FFA93F 0%, #F0710D 100%);
box-shadow: 0 8rpx 20rpx rgba(240,113,13,0.3);
}
.skeleton-box { padding: 30rpx 28rpx; }
.sk-hero { height: 180rpx; border-radius: 20rpx; margin-bottom: 24rpx; }
.sk-card { height: 260rpx; border-radius: 20rpx; margin-bottom: 24rpx; }
</style>