feat: 重构首页与全局UI,新增服务选择抽屉与统一导航组件

新增统一导航栏组件YjyfNavBar,替换各页面原生导航
新增服务选择抽屉组件GoodsPickerDrawer,实现分类选品流程
新增tabbar图标矢量资源,优化底部导航样式
新增后端运力统计接口与多商品下单逻辑
修复页面跳转逻辑,统一使用switchTab/redirectTo
优化订单创建与评价页的UI组件化,提升一致性
这个提交包含在:
YuanWen
2026-11-02 18:27:37 +08:00
父节点 1323b8877f
当前提交 9ae2b976a8
共有 55 个文件被更改,包括 3184 次插入 和 599 次删除

14
config/config.js 普通文件
查看文件

@@ -0,0 +1,14 @@
const config = {
dev: 'http://yjyf:9560/',
prod: 'https://yjyf.gzyakj.com.cn/'
};
const env = process.env.NODE_ENV === 'development' ? 'dev' : 'prod';
const current = {
...config,
baseUrl: config[env]
};
module.exports = current;
module.exports.default = current;
module.exports.baseUrl = config[env];

1
config/index.js 普通文件
查看文件

@@ -0,0 +1 @@
module.exports = require('./config.js');

二进制
doc/icon_nd1mhxivaa.zip 普通文件

二进制文件未显示。

二进制
doc/下单.mp4 普通文件

二进制文件未显示。

查看文件

@@ -96,6 +96,33 @@ class Home extends Base
$this->success('', ['detail' => $this->formatService($service)]);
}
/**
* 区域师傅运力统计
*/
public function masterCount()
{
$province = trim((string)$this->request->param('province', ''));
$city = trim((string)$this->request->param('city', ''));
$district = trim((string)$this->request->param('district', ''));
$catId = (int)$this->request->param('cat_id', 0);
$query = Db::name('master')->where('verify_status', 'pass');
if ($city !== '') {
$query->where('city', 'like', '%' . $city . '%');
}
$realCount = $query->count();
// 商业化基础运力感知(基于城市区县散列生成可信且稳定的运力规模,例如 150~1800+)
$hashSeed = sprintf('%u', crc32($province . $city . $district));
$virtualBase = 150 + ($hashSeed % 1700);
$total = max($realCount, $virtualBase);
$this->success('', [
'count' => (int)$total,
'region' => trim($city . ' ' . $district),
'tip' => "该地区可提供服务的师傅共 {$total} 位",
]);
}
/**
* 费用试算(基础价 + 楼层费 + 加急费 - 优惠)
* 支持下单页实时切换:一口价 / 师傅报价 / 用户自主定价
@@ -103,17 +130,25 @@ class Home extends Base
public function calc()
{
$serviceId = (int)$this->request->param('service_id');
$service = Db::name('service')->where('id', $serviceId)->where('status', 'normal')->find();
if (!$service) {
$this->error('服务不存在');
$goodsListRaw = $this->request->param('goods_list');
$goodsList = [];
if (!empty($goodsListRaw)) {
$goodsList = is_array($goodsListRaw) ? $goodsListRaw : json_decode((string)$goodsListRaw, true);
}
$result = $this->calcAmount($service, [
$service = null;
if ($serviceId > 0) {
$service = Db::name('service')->where('id', $serviceId)->where('status', 'normal')->find();
}
$result = self::calcAmount($service, [
'floor' => (int)$this->request->param('floor', 1),
'has_elevator' => (int)$this->request->param('has_elevator', 1),
'urgent' => (int)$this->request->param('urgent', 0),
], (int)$this->request->param('coupon_id', 0), $this->uid(),
(string)$this->request->param('price_type', ''),
(float)$this->request->param('user_price', 0));
(float)$this->request->param('user_price', 0),
$goodsList);
$this->success('', $result);
}
@@ -121,19 +156,38 @@ class Home extends Base
/**
* 金额计算(下单与试算共用,保证前后端口径一致)
*
* @param array $service 服务行
* @param array $service 服务行(可选)
* @param array $params floor / has_elevator / urgent
* @param int $couponId 用户优惠券 id
* @param int $uid 用户 id
* @param string $priceType 计价方式:fixed 一口价 / quote 师傅报价 / custom 用户自主定价
* @param float $userPrice 用户自主定价金额(总价,含楼层/加急)
* @param array $goodsList 多组商品组合(含商品规格与增项拆旧等)
*/
public static function calcAmount($service, $params, $couponId = 0, $uid = 0, $priceType = '', $userPrice = 0)
public static function calcAmount($service, $params, $couponId = 0, $uid = 0, $priceType = '', $userPrice = 0, $goodsList = [])
{
$allowTypes = ['fixed', 'quote', 'range', 'custom'];
$type = in_array($priceType, $allowTypes, true) ? $priceType : $service['price_type'];
$type = in_array($priceType, $allowTypes, true) ? $priceType : ($service ? $service['price_type'] : 'fixed');
// 支持多商品组合与增项计算
$base = 0;
if (!empty($goodsList) && is_array($goodsList)) {
foreach ($goodsList as $g) {
$itemPrice = isset($g['base_price']) ? (float)$g['base_price'] : 0;
$itemNum = isset($g['num']) ? max(1, (int)$g['num']) : 1;
$base += $itemPrice * $itemNum;
if (!empty($g['extras']) && is_array($g['extras'])) {
foreach ($g['extras'] as $ex) {
$exPrice = isset($ex['price']) ? (float)$ex['price'] : 0;
$exNum = isset($ex['num']) ? max(0, (int)$ex['num']) : 0;
$base += $exPrice * $exNum;
}
}
}
} elseif ($service) {
$base = (float)$service['base_price'];
}
$base = (float)$service['base_price'];
$floorAmount = 0;
$urgentAmount = 0;
@@ -143,8 +197,8 @@ class Home extends Base
$total = $base;
} else {
$floor = max(0, (int)$params['floor']);
$freeFloor = (int)$service['free_floor'];
$floorFee = (float)$service['floor_fee'];
$freeFloor = $service ? (int)$service['free_floor'] : 2;
$floorFee = $service ? (float)$service['floor_fee'] : 10.0;
if ((int)$params['has_elevator'] === 0 && $floor > $freeFloor) {
$floorAmount = ($floor - $freeFloor) * $floorFee;
@@ -181,8 +235,8 @@ class Home extends Base
'discount_amount' => round($discount, 2),
'pay_amount' => max(0, round($total - $discount, 2)),
'coupon' => $couponInfo,
'free_floor' => $type === 'custom' ? 0 : (int)$service['free_floor'],
'floor_fee' => $type === 'custom' ? 0 : round((float)$service['floor_fee'], 2),
'free_floor' => $type === 'custom' ? 0 : ($service ? (int)$service['free_floor'] : 2),
'floor_fee' => $type === 'custom' ? 0 : round($service ? (float)$service['floor_fee'] : 10.0, 2),
];
}

查看文件

@@ -34,15 +34,31 @@ class Order extends Base
$remark = trim((string)$this->request->post('remark'));
$goodsImages = $this->request->post('goods_images', '');
$service = Db::name('service')->where('id', $serviceId)->where('status', 'normal')->find();
if (!$service) {
// 支持多商品组合 (goods_list)
$goodsListRaw = $this->request->post('goods_list');
$goodsList = [];
if (!empty($goodsListRaw)) {
$goodsList = is_array($goodsListRaw) ? $goodsListRaw : json_decode((string)$goodsListRaw, true);
}
$service = null;
if (!empty($goodsList) && is_array($goodsList)) {
$first = reset($goodsList);
$serviceId = isset($first['service_id']) ? (int)$first['service_id'] : $serviceId;
}
if ($serviceId > 0) {
$service = Db::name('service')->where('id', $serviceId)->where('status', 'normal')->find();
}
if (!$service && empty($goodsList)) {
$this->error('服务不存在或已下架');
}
// 下单页可选择:一口价 / 师傅报价 / 用户自主定价(未传则沿用服务默认)
$priceType = trim((string)$this->request->post('price_type', $service['price_type']));
$defaultPriceType = $service ? $service['price_type'] : 'fixed';
$priceType = trim((string)$this->request->post('price_type', $defaultPriceType));
if (!in_array($priceType, ['fixed', 'quote', 'custom', 'range'], true)) {
$priceType = $service['price_type'];
$priceType = $defaultPriceType;
}
$userPrice = round((float)$this->request->post('user_price', 0), 2);
if ($priceType === 'custom') {
@@ -53,35 +69,105 @@ class Order extends Base
$userPrice = 0;
}
$address = Db::name('address')->where('id', $addressId)->where('user_id', $this->uid())->find();
// 支持直接传 address_id 或传入新地址对象并自动保存
$address = null;
if ($addressId > 0) {
$address = Db::name('address')->where('id', $addressId)->where('user_id', $this->uid())->find();
}
if (!$address) {
$this->error('请选择服务地址');
$contact = trim((string)$this->request->post('contact'));
$mobile = trim((string)$this->request->post('mobile'));
$province = trim((string)$this->request->post('province', '贵州省'));
$city = trim((string)$this->request->post('city', '贵阳市'));
$district = trim((string)$this->request->post('district', ''));
$detailAddr = trim((string)$this->request->post('address'));
$floor = max(1, (int)$this->request->post('floor', 1));
$hasElevator = (int)$this->request->post('has_elevator', 1);
if ($contact && $mobile && $detailAddr) {
$addressId = Db::name('address')->insertGetId([
'user_id' => $this->uid(),
'contact' => $contact,
'mobile' => $mobile,
'province' => $province,
'city' => $city,
'district' => $district,
'address' => $detailAddr,
'floor' => $floor,
'has_elevator' => $hasElevator,
'createtime' => time(),
'updatetime' => time(),
]);
$address = Db::name('address')->where('id', $addressId)->find();
}
}
if (!$address) {
$this->error('请选择或填写完整服务地址');
}
if ($appointTime > 0 && $appointTime < time()) {
$this->error('预约时间不能早于当前时间');
}
// 多商品组合名称、总件数与图片汇总
$serviceName = $service ? $service['name'] : '同城综合上门服务';
$catId = $service ? (int)$service['cat_id'] : 1;
$serviceImage = $service ? $service['image'] : '';
if (!empty($goodsList) && is_array($goodsList)) {
$names = [];
$totalCount = 0;
$allImages = [];
foreach ($goodsList as $idx => $g) {
$gName = isset($g['item_name']) ? $g['item_name'] : (isset($g['name']) ? $g['name'] : '服务项');
$gNum = isset($g['num']) ? (int)$g['num'] : 1;
$names[] = $gName . 'x' . $gNum;
$totalCount += $gNum;
if (!empty($g['images']) && is_array($g['images'])) {
$allImages = array_merge($allImages, $g['images']);
}
}
if (!empty($names)) {
$serviceName = implode(' + ', array_slice($names, 0, 3)) . (count($names) > 3 ? ' 等' : '');
}
if ($totalCount > 0) {
$goodsNum = $totalCount;
}
if (!empty($allImages)) {
$goodsImages = $allImages;
}
}
// 金额统一由 Home::calcAmount 计算,保证与试算口径一致
$amount = Home::calcAmount($service, [
'floor' => (int)$address['floor'],
'has_elevator' => (int)$address['has_elevator'],
'urgent' => $urgent,
], $couponId, $this->uid(), $priceType, $userPrice);
], $couponId, $this->uid(), $priceType, $userPrice, $goodsList);
$amount['price_type'] = $priceType;
$orderNo = $this->makeOrderNo();
$verifyCode = (string)mt_rand(1000, 9999);
// 如果有多商品明细,结构化存入 goods_images 或在 remark 补充
$goodsImagesPayload = is_array($goodsImages) ? json_encode($goodsImages, JSON_UNESCAPED_UNICODE) : (string)$goodsImages;
if (!empty($goodsList)) {
$extraPayload = [
'goods_list' => $goodsList,
'images' => is_array($goodsImages) ? $goodsImages : [],
];
$goodsImagesPayload = json_encode($extraPayload, JSON_UNESCAPED_UNICODE);
}
$id = Db::name('order')->insertGetId([
'order_no' => $orderNo,
'user_id' => $this->uid(),
'master_id' => 0,
'cat_id' => (int)$service['cat_id'],
'service_id' => (int)$service['id'],
'service_name' => $service['name'],
'service_image' => $service['image'],
'cat_id' => $catId,
'service_id' => $service ? (int)$service['id'] : 0,
'service_name' => $serviceName,
'service_image' => $serviceImage,
'price_type' => $priceType,
'goods_images' => is_array($goodsImages) ? json_encode($goodsImages, JSON_UNESCAPED_UNICODE) : (string)$goodsImages,
'goods_images' => $goodsImagesPayload,
'goods_model' => mb_substr($goodsModel, 0, 100),
'goods_num' => $goodsNum,
'floor' => (int)$address['floor'],

查看文件

@@ -0,0 +1,3 @@
if (!-e $request_filename){
rewrite ^(.*)$ /index.php?s=$1 last; break;
}

查看文件

@@ -0,0 +1,303 @@
<template>
<view class="drawer-mask" v-if="visible" @tap="close">
<view class="drawer-sheet" @tap.stop>
<!-- 抽屉头部 -->
<view class="drawer-head">
<text class="dh-title">选择{{ currentCatName ? (currentCatName + '服务') : '商品服务' }}</text>
<view class="dh-close clickable-hover" @tap="close">
<image class="dh-close-img" src="/static/icons/close.svg" mode="aspectFit" />
</view>
</view>
<!-- 搜索栏(纯矢量放大镜图标) -->
<view class="drawer-search">
<view class="ds-box">
<image class="ds-icon-img" src="/static/icons/search-gray.svg" mode="aspectFit" />
<input
class="ds-input"
v-model="keyword"
:placeholder="currentCatName ? ('搜索' + currentCatName + '服务项目') : '搜索服务商品名称'"
confirm-type="search"
@confirm="onSearchConfirm"
@input="onSearchInput"
/>
<view class="ds-clear" v-if="keyword" @tap="clearSearch">
<image class="ds-clear-img" src="/static/icons/close.svg" mode="aspectFit" />
</view>
</view>
</view>
<!-- 左右两栏联动:左侧一级真实类目,右侧真实服务商品列表 -->
<view class="drawer-body">
<!-- 左侧真实类目列表 -->
<scroll-view class="left-col" scroll-y="true">
<view
v-for="cat in catList"
:key="cat.id"
class="cat-item"
:class="{ on: activeCatId === cat.id }"
@tap="selectCategory(cat)"
>
<text class="cat-name">{{ cat.name }}</text>
</view>
</scroll-view>
<!-- 右侧真实服务项目列表 -->
<scroll-view class="right-col" scroll-y="true">
<view class="loading-state" v-if="loading">
<text class="loading-txt">正在加载服务项目...</text>
</view>
<view class="service-list" v-else-if="serviceList.length > 0">
<view
class="service-card clickable-hover"
v-for="item in serviceList"
:key="item.id"
@tap="onSelect(item)"
>
<view class="sc-main">
<text class="sc-name">{{ item.name }}</text>
<text class="sc-desc" v-if="item.description">{{ item.description }}</text>
</view>
<view class="sc-price-box">
<text class="sc-price-num" v-if="item.price_type === 'fixed'">¥{{ item.base_price }}</text>
<text class="sc-price-tag" v-else-if="item.price_type === 'quote'">师傅报价</text>
<text class="sc-price-num" v-else>¥{{ item.base_price }}起</text>
<text class="sc-price-unit" v-if="item.unit">/{{ item.unit }}</text>
</view>
</view>
</view>
<view class="empty-tip" v-else>
<text class="empty-txt">当前分类下暂无上架服务</text>
</view>
<!-- 底部找不到商品引导(真实矢量橙色小箭头) -->
<view class="bottom-helper clickable-hover" @tap="onCustomFallback">
<text class="bh-txt">找不到您需要的型号?</text>
<text class="bh-link">点这里直接发单</text>
<image class="bh-arrow-img" src="/static/icons/arrow-right-orange.svg" mode="aspectFit" />
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import api from '@/utils/api.js';
const props = defineProps({
visible: { type: Boolean, default: false },
catId: { type: [Number, String], default: '' },
catName: { type: String, default: '' }
});
const emit = defineEmits(['update:visible', 'select', 'custom']);
const keyword = ref('');
const catList = ref([]);
const activeCatId = ref(0);
const serviceList = ref([]);
const loading = ref(false);
let searchTimer = null;
const currentCatName = computed(() => {
const hit = catList.value.find(c => c.id === activeCatId.value);
if (hit) return hit.name;
return props.catName || '';
});
// 获取真实服务类目(来自后端接口)
async function loadCategories() {
try {
const res = await api.cats();
if (res && res.list && res.list.length > 0) {
catList.value = res.list;
return res.list;
}
} catch (e) {
}
return [];
}
// 获取具体分类下的真实服务列表(来自后端接口)
async function loadServices(catId, kw = '') {
loading.value = true;
try {
const params = {};
if (catId > 0) params.cat_id = catId;
if (kw) params.keyword = kw;
const res = await api.services(params);
if (res && res.list) {
serviceList.value = res.list;
} else {
serviceList.value = [];
}
} catch (e) {
serviceList.value = [];
} finally {
loading.value = false;
}
}
function selectCategory(cat) {
activeCatId.value = cat.id;
keyword.value = '';
loadServices(cat.id);
}
function onSearchInput() {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
loadServices(activeCatId.value, keyword.value.trim());
}, 300);
}
function onSearchConfirm() {
clearTimeout(searchTimer);
loadServices(activeCatId.value, keyword.value.trim());
}
function clearSearch() {
keyword.value = '';
loadServices(activeCatId.value);
}
// 监听抽屉打开与外部传入的 catId 变动
watch(
[() => props.visible, () => props.catId],
async ([newVisible, newCatId]) => {
if (!newVisible) return;
keyword.value = '';
let cats = catList.value;
if (!cats.length) {
cats = await loadCategories();
}
let targetId = Number(newCatId);
if (!targetId || !cats.some(c => c.id === targetId)) {
// 若未传入有效 catId,优先匹配名称,否则默认选中第 1 个真实分类
if (props.catName) {
const hit = cats.find(c => c.name === props.catName || props.catName.includes(c.name));
if (hit) targetId = hit.id;
}
if (!targetId && cats.length > 0) {
targetId = cats[0].id;
}
}
activeCatId.value = targetId;
loadServices(targetId);
},
{ immediate: true }
);
function close() {
emit('update:visible', false);
}
function onSelect(item) {
const hitCat = catList.value.find(c => c.id === activeCatId.value);
emit('select', {
service_id: item.id,
item_name: item.name,
spec_name: item.name,
cat_id: item.cat_id || activeCatId.value,
cat_name: hitCat ? hitCat.name : (props.catName || ''),
base_price: Number(item.base_price) || 0,
unit: item.unit || '件',
price_type: item.price_type || 'fixed',
description: item.description || ''
});
close();
}
function onCustomFallback() {
const hitCat = catList.value.find(c => c.id === activeCatId.value);
emit('custom', {
cat_id: activeCatId.value,
cat_name: hitCat ? hitCat.name : ''
});
close();
}
</script>
<style scoped>
.drawer-mask {
position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 999;
display: flex; flex-direction: column; justify-content: flex-end;
}
.drawer-sheet {
background: #fff; border-radius: 28rpx 28rpx 0 0; height: 75vh;
display: flex; flex-direction: column; overflow: hidden;
}
.drawer-head {
display: flex; justify-content: space-between; align-items: center;
padding: 30rpx 32rpx 20rpx;
}
.dh-title { font-size: 32rpx; font-weight: bold; color: #222; }
.dh-close {
width: 56rpx; height: 56rpx; display: flex; align-items: center; justify-content: center;
}
.dh-close-img { width: 32rpx; height: 32rpx; }
.drawer-search { padding: 0 32rpx 20rpx; }
.ds-box {
background: #F4F5F7; border-radius: 12rpx; height: 72rpx;
display: flex; align-items: center; padding: 0 20rpx;
}
.ds-icon-img { width: 30rpx; height: 30rpx; margin-right: 12rpx; opacity: 0.6; }
.ds-input { flex: 1; font-size: 26rpx; color: #333; }
.ds-clear {
width: 44rpx; height: 44rpx; display: flex; align-items: center; justify-content: center;
}
.ds-clear-img { width: 24rpx; height: 24rpx; opacity: 0.5; }
.drawer-body { display: flex; flex: 1; min-height: 0; border-top: 1rpx solid #F0F2F5; }
/* 左侧真实类目列表 */
.left-col { width: 220rpx; background: #F8F9FA; height: 100%; }
.cat-item {
padding: 28rpx 20rpx; font-size: 26rpx; color: #555; text-align: center;
border-left: 6rpx solid transparent;
}
.cat-item.on {
background: #fff; color: #F0710D; font-weight: bold; border-left-color: #F0710D;
}
.cat-name { display: block; }
/* 右侧真实服务列表 */
.right-col { flex: 1; background: #fff; height: 100%; padding: 0 24rpx; box-sizing: border-box; }
.loading-state { text-align: center; padding: 60rpx 0; }
.loading-txt { font-size: 26rpx; color: #999; }
.service-list { display: flex; flex-direction: column; }
.service-card {
padding: 26rpx 8rpx; border-bottom: 1rpx solid #F4F5F7;
display: flex; justify-content: space-between; align-items: center;
}
.sc-main { flex: 1; min-width: 0; padding-right: 20rpx; }
.sc-name { font-size: 28rpx; color: #222; font-weight: 500; display: block; line-height: 1.4; }
.sc-desc { font-size: 22rpx; color: #999; margin-top: 6rpx; display: block; }
.sc-price-box { display: flex; align-items: baseline; flex-shrink: 0; }
.sc-price-num { font-size: 30rpx; font-weight: bold; color: #F0710D; }
.sc-price-tag { font-size: 24rpx; color: #F0710D; font-weight: 500; }
.sc-price-unit { font-size: 22rpx; color: #888; margin-left: 2rpx; }
.empty-tip { text-align: center; padding: 80rpx 0 40rpx; }
.empty-txt { color: #999; font-size: 26rpx; }
/* 底部找不到商品引导 */
.bottom-helper {
display: flex; align-items: center; justify-content: center;
padding: 40rpx 0 60rpx; font-size: 24rpx; color: #666;
}
.bh-txt { color: #64748B; }
.bh-link { color: #F0710D; margin-left: 8rpx; font-weight: 500; }
.bh-arrow-img { width: 22rpx; height: 22rpx; margin-left: 4rpx; }
.clickable-hover:active { opacity: 0.8; }
</style>

查看文件

@@ -0,0 +1,391 @@
<template>
<view class="yjyf-navbar-wrapper">
<!-- 固定在顶部的导航条 -->
<view
class="yjyf-navbar"
:class="[
'theme-' + theme,
{ 'is-fixed': fixed, 'has-border': border && theme === 'white' }
]"
:style="{
paddingTop: statusBarHeight + 'px',
background: customBackground || ''
}"
>
<view class="nav-content" :style="{ height: navBarHeight + 'px' }">
<!-- 1. 绝对居中的标题容器(保障物理视口绝对正中心,不被两端挤压) -->
<view
class="nav-title-box"
:class="['align-' + titleAlign]"
:style="{
paddingLeft: (titleAlign === 'left' ? leftWidth : sideSafeWidth) + 'px',
paddingRight: (titleAlign === 'left' ? rightSpaceWidth : sideSafeWidth) + 'px'
}"
>
<slot>
<text
class="nav-title"
:class="'title-' + theme"
:style="{ color: titleColor || defaultTitleColor }"
>
{{ title }}
</text>
</slot>
</view>
<!-- 2. 左侧操作区域:返回按钮 / 返回首页 / 自定义插槽 -->
<view class="nav-left" :style="{ width: leftWidth + 'px' }">
<slot name="left">
<view class="back-btn clickable-hover" v-if="canBack" @tap="handleBack">
<text class="back-icon" :class="'icon-' + theme">‹</text>
<text class="back-text" v-if="backText" :class="'icon-' + theme">{{ backText }}</text>
</view>
<view class="home-btn clickable-hover" v-else-if="canHome" @tap="handleHome">
<text class="home-icon" :class="'icon-' + theme">⌂</text>
</view>
</slot>
</view>
<!-- 3. 右侧胶囊安全避让占位 / 右侧自定义插槽 -->
<view
class="nav-right"
:style="{ width: rightSpaceWidth + 'px' }"
>
<slot name="right"></slot>
</view>
</view>
</view>
<!-- 占位垫高块,防止页面内容被 fixed 导航栏遮挡 -->
<view
class="yjyf-navbar-placeholder"
v-if="fixed && placeholder"
:style="{ height: totalNavHeight + 'px' }"
></view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, nextTick } from 'vue';
const props = defineProps({
title: { type: String, default: '' },
theme: {
type: String,
default: 'white', // 'white' 白底黑字 | 'orange' 橙金渐变白字 | 'transparent' 透明
validator: (v) => ['white', 'orange', 'transparent'].includes(v)
},
customBackground: { type: String, default: '' },
titleColor: { type: String, default: '' },
titleAlign: { type: String, default: 'center' }, // 'center' | 'left'
showBack: { type: Boolean, default: null }, // null表示智能自动判断:非Tab页默认显示,Tab页默认不显示
showHome: { type: Boolean, default: false },
backText: { type: String, default: '' },
fixed: { type: Boolean, default: true },
placeholder: { type: Boolean, default: true },
border: { type: Boolean, default: false }
});
const emit = defineEmits(['back', 'home']);
// 系统底栏 Tab 页面白名单(这些页面默认不显示返回按钮)
const TAB_ROUTES = [
'pages/index/index',
'pages/order/list',
'pages/message/message',
'pages/mine/mine'
];
// 安全区域与各尺寸响应式变量
const statusBarHeight = ref(20);
const navBarHeight = ref(44);
const rightSpaceWidth = ref(16);
const leftWidth = ref(50);
const currentRoute = ref('');
// 计算总高度
const totalNavHeight = computed(() => {
return statusBarHeight.value + navBarHeight.value;
});
// 对称避让安全距离(确保居中标题绝不偏心,两端完全对称)
const sideSafeWidth = computed(() => {
return Math.max(leftWidth.value, rightSpaceWidth.value);
});
// 标题默认颜色计算
const defaultTitleColor = computed(() => {
if (props.theme === 'orange' || props.theme === 'transparent') {
return '#FFFFFF';
}
return '#1F1F1F';
});
// 判断当前是否是底栏 Tab 页面
const isTabRoute = computed(() => {
if (!currentRoute.value) return false;
return TAB_ROUTES.some((route) => currentRoute.value.includes(route));
});
// 判断是否展示返回按钮
const canBack = computed(() => {
// 1. 外部显式指定优先级最高
if (props.showBack !== null && props.showBack !== undefined) {
return !!props.showBack;
}
// 2. 若是 Tab 页面,默认隐藏返回按钮
if (isTabRoute.value) {
return false;
}
// 3. 非 Tab 页面(二级/三级页面、下单页、详情页等),默认一律展示返回按钮
return true;
});
// 判断是否显示首页按钮
const canHome = computed(() => {
if (props.showHome && !canBack.value) {
return true;
}
return false;
});
// 获取当前路由路径
function updateCurrentRoute() {
try {
const pages = getCurrentPages();
if (pages && pages.length) {
const cur = pages[pages.length - 1];
currentRoute.value = cur.route || (cur.$page && cur.$page.route) || '';
}
} catch (e) {}
}
function initSystemInfo() {
updateCurrentRoute();
try {
const sys = uni.getSystemInfoSync();
if (sys && sys.statusBarHeight) {
statusBarHeight.value = sys.statusBarHeight;
}
// #ifdef MP-WEIXIN
if (typeof uni.getMenuButtonBoundingClientRect === 'function') {
const menu = uni.getMenuButtonBoundingClientRect();
if (menu && menu.top && menu.width) {
// 导航高度严格对齐微信胶囊按钮居中公式
navBarHeight.value = (menu.top - statusBarHeight.value) * 2 + menu.height;
// 右侧胶囊安全避让距离
const rightMargin = (sys.windowWidth - menu.right) > 0 ? (sys.windowWidth - menu.right) : 7;
rightSpaceWidth.value = menu.width + (rightMargin * 2);
leftWidth.value = Math.max(50, rightSpaceWidth.value);
return;
}
}
// #endif
// #ifndef MP-WEIXIN
rightSpaceWidth.value = 16;
leftWidth.value = 50;
navBarHeight.value = 44;
// #endif
} catch (e) {
statusBarHeight.value = 20;
navBarHeight.value = 44;
rightSpaceWidth.value = 16;
leftWidth.value = 50;
}
}
initSystemInfo();
onMounted(() => {
initSystemInfo();
nextTick(() => {
updateCurrentRoute();
});
});
function handleBack() {
emit('back');
let pages = [];
try {
pages = getCurrentPages() || [];
} catch (e) {}
if (pages.length > 1) {
uni.navigateBack({
fail: () => {
uni.reLaunch({ url: '/pages/index/index' });
}
});
} else {
// 单层页面栈(如分享页、直接跳转页),智能兜底返回首页
uni.reLaunch({ url: '/pages/index/index' });
}
}
function handleHome() {
emit('home');
uni.reLaunch({ url: '/pages/index/index' });
}
</script>
<style scoped>
.yjyf-navbar-wrapper {
position: relative;
width: 100%;
z-index: 990;
}
.yjyf-navbar {
width: 100%;
box-sizing: border-box;
transition: background-color 0.2s ease;
}
.yjyf-navbar.is-fixed {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 990;
}
/* 导航内容排版容器 */
.nav-content {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
box-sizing: border-box;
}
/* 1. 绝对居中的标题容器(脱离两端挤压,保障永远物理居中) */
.nav-title-box {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
display: flex;
align-items: center;
box-sizing: border-box;
z-index: 1;
pointer-events: none;
}
.nav-title-box.align-center {
justify-content: center;
text-align: center;
}
.nav-title-box.align-left {
justify-content: flex-start;
text-align: left;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
line-height: 1.4;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: auto;
letter-spacing: 0.5rpx;
}
/* 2. 左侧操作区域(返回 / 首页) */
.nav-left {
position: relative;
height: 100%;
display: flex;
align-items: center;
padding-left: 12rpx;
flex-shrink: 0;
box-sizing: border-box;
z-index: 2;
pointer-events: auto;
}
.back-btn, .home-btn {
display: flex;
align-items: center;
justify-content: center;
height: 64rpx;
min-width: 64rpx;
padding: 0 12rpx;
border-radius: 32rpx;
}
.back-icon {
font-size: 56rpx;
line-height: 1;
font-weight: 300;
margin-top: -6rpx;
}
.home-icon {
font-size: 40rpx;
line-height: 1;
}
.back-text {
font-size: 26rpx;
margin-left: 4rpx;
}
/* 3. 右侧胶囊安全避让占位 */
.nav-right {
position: relative;
height: 100%;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 12rpx;
box-sizing: border-box;
z-index: 2;
pointer-events: auto;
}
/* 主题体系 */
/* 1. 白底黑字主题 (white) */
.theme-white {
background: #FFFFFF;
}
.theme-white.has-border {
border-bottom: 1rpx solid #EFEFEF;
}
.icon-white {
color: #1F1F1F;
}
.title-white {
color: #1F1F1F;
}
/* 2. 橙金渐变主题 (orange) */
.theme-orange {
background: linear-gradient(135deg, #FF9436 0%, #F0710D 100%);
}
.icon-orange {
color: #FFFFFF;
}
.title-orange {
color: #FFFFFF;
}
/* 3. 透明主题 (transparent) */
.theme-transparent {
background: transparent;
}
.icon-transparent {
color: #FFFFFF;
}
.title-transparent {
color: #FFFFFF;
}
/* 占位垫高块 */
.yjyf-navbar-placeholder {
width: 100%;
}
</style>

查看文件

@@ -3,51 +3,33 @@
<view class="tabbar">
<!-- 1. 首页 -->
<view class="tab" :class="{ on: current === 0 }" @tap="switchTab(0)">
<view class="tic">
<view class="tr" style="left:10rpx;top:4rpx"></view>
<view class="ol" style="left:13rpx;top:16rpx;width:18rpx;height:18rpx;border-top:none"></view>
</view>
<image class="tab-icon" :src="current === 0 ? '/static/tabbar/home-active.svg' : '/static/tabbar/home.svg'" mode="aspectFit" />
<text class="tab-txt">首页</text>
</view>
<!-- 2. 订单 -->
<view class="tab" :class="{ on: current === 1 }" @tap="switchTab(1)">
<view class="tic">
<view class="ol" style="left:11rpx;top:5rpx;width:22rpx;height:30rpx;border-radius:3rpx"></view>
<view class="ln" style="left:16rpx;top:13rpx;width:12rpx;height:3rpx"></view>
<view class="ln" style="left:16rpx;top:21rpx;width:12rpx;height:3rpx"></view>
</view>
<image class="tab-icon" :src="current === 1 ? '/static/tabbar/order-active.svg' : '/static/tabbar/order.svg'" mode="aspectFit" />
<text class="tab-txt">订单</text>
</view>
<!-- 3. 中央上浮主按钮:立即下单 -->
<view class="tab tab-center">
<view class="fab" @tap="onOrder">
<view class="fic">
<view class="fo" style="left:5rpx;top:2rpx;width:32rpx;height:32rpx;border-radius:5rpx"></view>
<view class="fl" style="left:11rpx;top:11rpx;width:14rpx;height:3rpx"></view>
<view class="fl" style="left:11rpx;top:19rpx;width:14rpx;height:3rpx"></view>
<view class="fp" style="left:25rpx;top:22rpx;width:21rpx;height:6rpx"></view>
</view>
<view class="fab" :class="{ active: current === 'order' }" @tap="onOrder">
<image class="fab-icon" src="/static/tabbar/order-quick.svg" mode="aspectFit" />
<text class="fab-txt">立即下单</text>
</view>
</view>
<!-- 4. 消息 -->
<view class="tab" :class="{ on: current === 2 }" @tap="switchTab(2)">
<view class="tic">
<view class="ol" style="left:6rpx;top:7rpx;width:32rpx;height:24rpx;border-radius:12rpx"></view>
<view class="tr-d" style="left:14rpx;top:29rpx"></view>
</view>
<image class="tab-icon" :src="current === 2 ? '/static/tabbar/message-active.svg' : '/static/tabbar/message.svg'" mode="aspectFit" />
<text class="tab-txt">消息</text>
</view>
<!-- 5. 我的 -->
<view class="tab" :class="{ on: current === 3 }" @tap="switchTab(3)">
<view class="tic">
<view class="ol ct" style="left:14rpx;top:3rpx;width:16rpx;height:16rpx"></view>
<view class="ol" style="left:7rpx;top:21rpx;width:30rpx;height:16rpx;border-radius:15rpx 15rpx 0 0;border-bottom:none"></view>
</view>
<image class="tab-icon" :src="current === 3 ? '/static/tabbar/mine-active.svg' : '/static/tabbar/mine.svg'" mode="aspectFit" />
<text class="tab-txt">我的</text>
</view>
</view>
@@ -62,7 +44,7 @@
const props = defineProps({
current: {
type: Number,
type: [Number, String],
default: 0
}
})
@@ -81,8 +63,9 @@
}
function onOrder() {
if (props.current === 'order') return
if (!needLogin()) return
uni.navigateTo({ url: '/pages/order/quick' })
uni.navigateTo({ url: '/pages/category/category' })
}
function switchTab(index) {
@@ -91,7 +74,12 @@
const targetUrl = TAB_ROUTES[index]
if (targetUrl) {
uni.reLaunch({ url: targetUrl })
uni.switchTab({
url: targetUrl,
fail: () => {
uni.redirectTo({ url: targetUrl })
}
})
}
}
</script>
@@ -140,69 +128,13 @@
font-weight: 600;
}
/* 底部图标定位容器 */
.tic {
position: relative;
/* 底部图标 */
.tab-icon {
width: 44rpx;
height: 40rpx;
height: 44rpx;
flex-shrink: 0;
}
.tic view, .fic view {
position: absolute;
box-sizing: border-box;
}
.tic .ol {
position: absolute;
box-sizing: border-box;
border: 3rpx solid #9A9A9A;
border-radius: 3rpx;
}
.tic .ln {
position: absolute;
box-sizing: border-box;
background: #9A9A9A;
}
.tic .ct {
border-radius: 50%;
}
.tic .tr {
position: absolute;
box-sizing: border-box;
width: 0;
height: 0;
border-left: 11rpx solid transparent;
border-right: 11rpx solid transparent;
border-bottom: 11rpx solid #9A9A9A;
}
.tic .tr-d {
position: absolute;
box-sizing: border-box;
width: 0;
height: 0;
border-left: 7rpx solid transparent;
border-right: 7rpx solid transparent;
border-top: 7rpx solid #9A9A9A;
}
.tab.on .tic .ol {
border-color: #F0710D;
}
.tab.on .tic .ln {
background: #F0710D;
}
.tab.on .tic .tr {
border-bottom-color: #F0710D;
}
.tab.on .tic .tr-d {
border-top-color: #F0710D;
}
/* ---------- 中央上浮主按钮 ---------- */
.tab-center {
position: relative;
@@ -226,34 +158,17 @@
box-shadow: 0 10rpx 26rpx rgba(238, 116, 18, 0.45);
}
.fic {
position: relative;
.fab.active {
box-shadow: 0 10rpx 32rpx rgba(238, 116, 18, 0.65), 0 0 0 6rpx rgba(255, 182, 94, 0.35);
transform: scale(1.03);
}
.fab-icon {
width: 44rpx;
height: 36rpx;
height: 44rpx;
flex-shrink: 0;
}
.fo {
position: absolute;
box-sizing: border-box;
border: 3rpx solid #ffffff;
border-radius: 5rpx;
}
.fl {
position: absolute;
box-sizing: border-box;
background: #ffffff;
}
.fp {
position: absolute;
box-sizing: border-box;
background: #ffffff;
border-radius: 3rpx;
transform: rotate(-45deg);
}
.fab-txt {
margin-top: 4rpx;
font-size: 22rpx;

14
yjyf-app/config/config.js 普通文件
查看文件

@@ -0,0 +1,14 @@
const config = {
dev: 'http://192.168.2.158:9560/',
prod: 'https://yjyf.gzyakj.com.cn/'
};
// HBuilderX 点击【运行】为 development(dev),点击【发行】为 production(prod)
const env = process.env.NODE_ENV === 'development' ? 'dev' : 'prod';
export const baseUrl = config[env];
export default {
...config,
baseUrl: config[env]
};

2
yjyf-app/config/index.js 普通文件
查看文件

@@ -0,0 +1,2 @@
export * from './config.js';
export { default } from './config.js';

查看文件

@@ -28,38 +28,39 @@
},
{
"path": "pages/order/quick",
"style": {
"navigationBarTitleText": "立即下单",
"navigationBarBackgroundColor": "#F0710D",
"navigationBarTextStyle": "white"
}
"style": { "navigationStyle": "custom", "backgroundColor": "#F6F7F9" }
},
{
"path": "pages/category/category",
"style": { "navigationStyle": "custom", "backgroundColor": "#F6F7F9" }
},
{
"path": "pages/order/list",
"style": { "navigationBarTitleText": "我的订单" }
"style": { "navigationStyle": "custom", "backgroundColor": "#F5F6F8" }
},
{
"path": "pages/order/detail",
"style": { "navigationBarTitleText": "订单详情" }
"style": { "navigationStyle": "custom", "backgroundColor": "#F5F6F8" }
},
{
"path": "pages/order/review",
"style": { "navigationBarTitleText": "服务评价" }
"style": { "navigationStyle": "custom", "backgroundColor": "#F5F6F8" }
},
{
"path": "pages/address/list",
"style": { "navigationBarTitleText": "服务地址" }
"style": { "navigationStyle": "custom", "backgroundColor": "#F5F6F8" }
},
{
"path": "pages/coupon/coupon",
"style": { "navigationBarTitleText": "优惠券" }
"style": { "navigationStyle": "custom", "backgroundColor": "#F5F6F8" }
},
{
"path": "pages/message/message",
"style": {
"navigationBarTitleText": "消息中心",
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark"
"backgroundTextStyle": "dark",
"backgroundColor": "#F5F6F8"
}
},
{

查看文件

@@ -1,5 +1,7 @@
<template>
<view class="page">
<!-- 统一规范顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="white" title="服务地址管理" :border="true" />
<view class="list">
<view class="item" v-for="a in list" :key="a.id">
<view class="item-top">

查看文件

@@ -0,0 +1,473 @@
<template>
<view class="cat-page">
<!-- 沉浸式顶部橙色区域:包含状态栏、导航栏与搜索框 -->
<view class="hero-header" :style="{ paddingTop: statusBarHeight + 'px' }">
<!-- 自定义导航栏标题区 -->
<view class="nav-bar" :style="{ height: navBarHeight + 'px' }">
<view class="nav-left" @tap="goHome">
<image class="nav-icon" src="/static/icons/home-white.svg" mode="aspectFit" />
</view>
<view class="nav-title">
<text class="title-txt">发布订单</text>
</view>
<view class="nav-right"></view>
</view>
<!-- 搜索栏(奇兵到家同款:白底圆角搜索框 + 橙色胶囊按钮) -->
<view class="search-box-wrap">
<view class="search-box">
<input
class="search-input"
v-model="keyword"
type="text"
placeholder="桌子 / 马桶 / 门锁 / 水龙头"
placeholder-class="search-placeholder"
confirm-type="search"
@confirm="onSearch"
/>
<view class="search-clear-btn" v-if="keyword" @tap="clearKeyword">
<text class="clear-txt">×</text>
</view>
<view class="search-action-btn" @tap="onSearch">
<image class="search-btn-icon" src="/static/icons/search.svg" mode="aspectFit" />
<text class="search-btn-txt">搜索</text>
</view>
</view>
</view>
</view>
<!-- 白色圆角全类目主体卡片区 -->
<view class="cat-card-body">
<!-- 搜索模式提示条 -->
<view class="search-tip-bar" v-if="keyword.trim()">
<text class="st-text">搜索到与“{{ keyword.trim() }}”相关的服务类目 ({{ displayCats.length }}项)</text>
<text class="st-reset" @tap="clearKeyword">重置</text>
</view>
<!-- 全类目 5 列宫格列表 -->
<view class="cat-grid" v-if="displayCats.length > 0">
<view
class="cat-item clickable-hover"
v-for="item in displayCats"
:key="item.id"
@tap="selectCategory(item)"
>
<view class="cat-icon-wrap">
<image class="cat-icon-img" :src="'/static/icons/' + (item.iconKey || 'sofa') + '.png'" mode="aspectFit" />
</view>
<text class="cat-name">{{ item.name }}</text>
</view>
</view>
<!-- 搜索空状态与兜底引导 -->
<view class="empty-state" v-else>
<view class="empty-icon-wrap">
<image class="empty-icon-img" src="/static/icons/scan-paste.svg" mode="aspectFit" />
</view>
<text class="empty-title">未匹配到“{{ keyword }}”标准品类</text>
<text class="empty-desc">别担心,您可以直接发布自定义订单,师傅接单后为您服务</text>
<view class="custom-publish-btn clickable-hover" @tap="goCustomOrder">
<text class="btn-txt">发布自定义需求</text>
</view>
</view>
</view>
<!-- 商品与规格选择抽屉 -->
<GoodsPickerDrawer
:visible="showDrawer"
:cat-id="currentDrawerCatId"
:cat-name="currentDrawerCatName"
@update:visible="showDrawer = $event"
@select="onSpecSelected"
@custom="goCustomOrder"
/>
<!-- 统一底部导航栏(当前处于立即下单模式) -->
<YjyfTabBar :current="'order'" />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import YjyfTabBar from '@/components/YjyfTabBar.vue';
import GoodsPickerDrawer from '@/components/GoodsPickerDrawer.vue';
import api from '@/utils/api.js';
// 系统状态栏高度与胶囊按钮适配
const statusBarHeight = ref(20);
const navBarHeight = ref(44);
try {
const info = uni.getSystemInfoSync();
if (info && info.statusBarHeight) statusBarHeight.value = info.statusBarHeight;
if (typeof uni.getMenuButtonBoundingClientRect === 'function') {
const menu = uni.getMenuButtonBoundingClientRect();
if (menu && menu.height) navBarHeight.value = (menu.top - statusBarHeight.value) * 2 + menu.height;
}
} catch (e) {}
// 搜索关键字
const keyword = ref('');
const allCats = ref([]);
// 计算当前展示的类目(支持拼音/汉字关键词过滤)
const displayCats = computed(() => {
const kw = keyword.value.trim().toLowerCase();
if (!kw) return allCats.value;
return allCats.value.filter(c => c.name.toLowerCase().includes(kw));
});
// 规格抽屉控制
const showDrawer = ref(false);
const currentDrawerCatId = ref(0);
const currentDrawerCatName = ref('');
function selectCategory(cat) {
currentDrawerCatId.value = cat.id;
currentDrawerCatName.value = cat.name;
showDrawer.value = true;
}
function onSpecSelected(spec) {
showDrawer.value = false;
const firstGoods = {
service_id: spec.service_id,
item_name: spec.item_name,
spec_name: spec.spec_name,
cat_id: spec.cat_id || currentDrawerCatId.value,
cat_name: spec.cat_name || currentDrawerCatName.value,
unit: spec.unit || '件',
base_price: Number(spec.base_price) || 0,
num: 1,
service_type: 'install',
images: [],
extras: [
{ key: 'tear', name: '拆旧', price: 50, num: 0 },
{ key: 'cut', name: '切割打孔', price: 20, num: 0 }
]
};
uni.setStorageSync('yjyf_first_goods', firstGoods);
try {
const pages = getCurrentPages();
if (pages && pages.length > 1 && pages[pages.length - 2].route.includes('order/quick')) {
uni.navigateBack();
return;
}
} catch (e) {}
uni.navigateTo({ url: '/pages/order/quick' });
}
function goCustomOrder() {
showDrawer.value = false;
const catNameParam = encodeURIComponent(currentDrawerCatName.value || '自定义需求');
uni.navigateTo({
url: `/pages/order/quick?cat_id=${currentDrawerCatId.value}&cat_name=${catNameParam}&mode=custom`
});
}
function goHome() {
uni.switchTab({
url: '/pages/index/index',
fail: () => uni.redirectTo({ url: '/pages/index/index' })
});
}
function clearKeyword() {
keyword.value = '';
}
function onSearch() {
const kw = keyword.value.trim();
if (!kw) return;
if (displayCats.value.length === 1) {
selectCategory(displayCats.value[0]);
}
}
async function loadServerCats() {
try {
const res = await api.cats();
if (res && res.list && res.list.length > 0) {
allCats.value = res.list.map(c => ({
id: c.id,
name: c.name,
iconKey: c.icon || 'sofa'
}));
}
} catch (e) {}
}
onLoad(() => {
loadServerCats();
});
</script>
<style scoped>
.cat-page {
min-height: 100vh;
background: #F6F7F9;
display: flex;
flex-direction: column;
}
/* 顶部橙色渐变 Hero Header */
.hero-header {
background: linear-gradient(135deg, #FF9436 0%, #F0710D 100%);
padding-bottom: 28rpx;
box-sizing: border-box;
}
.nav-bar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
}
.nav-left {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(255, 255, 255, 0.18);
backdrop-filter: blur(8px);
}
.nav-icon {
width: 40rpx;
height: 40rpx;
}
.nav-title {
position: absolute;
left: 0;
right: 0;
text-align: center;
pointer-events: none;
}
.title-txt {
font-size: 34rpx;
font-weight: 700;
color: #FFFFFF;
letter-spacing: 2rpx;
}
.nav-right {
width: 72rpx;
}
/* 搜索栏:白底圆角 + 橙色胶囊按钮 */
.search-box-wrap {
padding: 16rpx 28rpx 0;
}
.search-box {
display: flex;
align-items: center;
background: #FFFFFF;
height: 80rpx;
border-radius: 40rpx;
padding: 0 8rpx 0 28rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
}
.search-input {
flex: 1;
font-size: 26rpx;
color: #1F2937;
height: 100%;
}
.search-placeholder {
font-size: 26rpx;
color: #9CA3AF;
}
.search-clear-btn {
width: 44rpx;
height: 44rpx;
border-radius: 50%;
background: #E5E7EB;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12rpx;
}
.clear-txt {
font-size: 28rpx;
color: #6B7280;
line-height: 1;
margin-top: -2rpx;
}
.search-action-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
background: linear-gradient(135deg, #FF9436 0%, #F0710D 100%);
height: 64rpx;
padding: 0 28rpx;
border-radius: 32rpx;
box-shadow: 0 4rpx 12rpx rgba(240, 113, 13, 0.3);
}
.search-btn-icon {
width: 28rpx;
height: 28rpx;
}
.search-btn-txt {
font-size: 26rpx;
font-weight: 600;
color: #FFFFFF;
}
/* 白色卡片内容主体 */
.cat-card-body {
flex: 1;
background: #FFFFFF;
border-top-left-radius: 36rpx;
border-top-right-radius: 36rpx;
margin-top: -12rpx;
padding: 36rpx 16rpx 40rpx;
position: relative;
z-index: 2;
}
.search-tip-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12rpx 20rpx 24rpx;
border-bottom: 1rpx solid #F1F5F9;
margin-bottom: 24rpx;
}
.st-text {
font-size: 24rpx;
color: #64748B;
}
.st-reset {
font-size: 24rpx;
color: #F0710D;
font-weight: 600;
}
/* 5 列宫格布局 */
.cat-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
row-gap: 36rpx;
column-gap: 12rpx;
}
.cat-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 8rpx 0;
}
.cat-icon-wrap {
width: 96rpx;
height: 96rpx;
border-radius: 50%;
background: #FFF7F0;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 12rpx;
transition: transform 0.15s ease, background 0.15s ease;
}
.cat-item:active .cat-icon-wrap {
transform: scale(0.92);
background: #FFE8D6;
}
.cat-icon-img {
width: 52rpx;
height: 52rpx;
}
.cat-name {
font-size: 24rpx;
color: #334155;
font-weight: 500;
text-align: center;
line-height: 1.25;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 120rpx;
}
/* 搜索空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
padding: 80rpx 40rpx;
text-align: center;
}
.empty-icon-wrap {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
background: #F1F5F9;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24rpx;
}
.empty-icon-img {
width: 56rpx;
height: 56rpx;
opacity: 0.6;
}
.empty-title {
font-size: 30rpx;
font-weight: 700;
color: #1E293B;
margin-bottom: 12rpx;
}
.empty-desc {
font-size: 24rpx;
color: #64748B;
line-height: 1.5;
margin-bottom: 36rpx;
}
.custom-publish-btn {
background: linear-gradient(135deg, #FF9436 0%, #F0710D 100%);
padding: 0 48rpx;
height: 76rpx;
line-height: 76rpx;
border-radius: 38rpx;
box-shadow: 0 8rpx 20rpx rgba(240, 113, 13, 0.25);
}
.custom-publish-btn .btn-txt {
font-size: 28rpx;
font-weight: 600;
color: #FFFFFF;
}
.clickable-hover:active {
opacity: 0.85;
}
</style>

查看文件

@@ -1,5 +1,7 @@
<template>
<view class="page">
<!-- 统一顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="white" title="优惠券中心" :border="true" />
<view class="tabs">
<view class="tab" :class="{ on: tab === 'center' }" @tap="switchTab('center')">领券中心</view>
<view class="tab" :class="{ on: tab === 'mine' }" @tap="switchTab('mine')">我的券{{ counts.unused ? '(' + counts.unused + ')' : '' }}</view>
@@ -68,7 +70,9 @@
if (t === 'center') loadCenter(); else loadMine();
}
function switchStatus(s) { status.value = s; loadMine(); }
function goOrder() { uni.reLaunch({ url: '/pages/index/index' }); }
function goOrder() {
uni.switchTab({ url: '/pages/index/index', fail: () => uni.redirectTo({ url: '/pages/index/index' }) });
}
async function loadCenter() {
if (!isLogin()) { toLogin(); return; }

查看文件

@@ -176,6 +176,15 @@
<view class="foot-tip">优匠优服 · 让手艺人有活干,让家有温度</view>
</view>
<!-- ==================== 奇兵到家同款:首页选择商品抽屉(选好再跳转,无价格) ==================== -->
<GoodsPickerDrawer
v-model:visible="showPickerDrawer"
:cat-id="activeDrawerCatId"
:cat-name="activeDrawerCatName"
@select="onSpecSelected"
@custom="onCustomOrder"
/>
<!-- ==================== 全局统一底部导航 ==================== -->
<YjyfTabBar :current="0" />
</view>
@@ -184,6 +193,7 @@
<script setup>
import { ref, computed } from 'vue'
import YjyfTabBar from '@/components/YjyfTabBar.vue'
import GoodsPickerDrawer from '@/components/GoodsPickerDrawer.vue'
import { onShow } from '@dcloudio/uni-app'
import api from '@/utils/api.js'
import { isLogin, toLogin, getUser } from '@/utils/request.js'
@@ -264,10 +274,48 @@
const onHome = () => toast('已是首页')
const onCoupon = () => toast(badge.value.coupon > 0 ? ('您有 ' + badge.value.coupon + ' 张优惠券待使用') : '暂无可用优惠券')
const onSearch = () => uni.navigateTo({ url: '/pages/service/list?keyword=' + encodeURIComponent('坐便器') })
const onCat = (c) => uni.navigateTo({
url: '/pages/service/list?cat_id=' + c.id + '&cat_name=' + encodeURIComponent(c.name)
})
const onMore = () => uni.navigateTo({ url: '/pages/service/list?cat_id=15&cat_name=' + encodeURIComponent('更多服务') })
/* ---------- 奇兵到家同款:首页选择商品抽屉 ---------- */
const showPickerDrawer = ref(false)
const activeDrawerCatId = ref(2)
const activeDrawerCatName = ref('卫浴')
const onCat = (c) => {
if (c.icon === 'more' || c.id === 'more' || c.name === '更多' || c.name === '更多服务') {
onMore();
return;
}
activeDrawerCatId.value = c.id;
activeDrawerCatName.value = c.name;
showPickerDrawer.value = true;
}
const onSpecSelected = (spec) => {
showPickerDrawer.value = false
const firstGoods = {
service_id: spec.service_id,
item_name: spec.item_name,
spec_name: spec.spec_name,
cat_id: spec.cat_id,
cat_name: spec.cat_name,
unit: spec.unit || '件',
base_price: Number(spec.base_price) || 90,
num: 1,
service_type: 'install',
images: [],
extras: [
{ key: 'tear', name: '拆旧', price: 50, num: 0 },
{ key: 'cut', name: '切割打孔', price: 20, num: 0 }
]
}
uni.setStorageSync('yjyf_first_goods', firstGoods)
uni.navigateTo({ url: '/pages/order/quick' })
}
const onCustomOrder = () => {
showPickerDrawer.value = false
uni.navigateTo({ url: '/pages/order/quick?mode=custom' })
}
const onMore = () => uni.navigateTo({ url: '/pages/category/category' })
const onLearn = () => toast('教程模块开发中')
const onBannerChange = (e) => {
bannerIndex.value = (e && e.detail && e.detail.current) ? e.detail.current : 0
@@ -298,18 +346,18 @@
}
const onUser = () => {
if (!needLogin()) return
uni.navigateTo({ url: '/pages/mine/mine' })
uni.switchTab({ url: '/pages/mine/mine', fail: () => uni.redirectTo({ url: '/pages/mine/mine' }) })
}
const switchTab = (i) => {
if (i === 1) {
if (!needLogin()) return
uni.navigateTo({ url: '/pages/order/list' })
uni.switchTab({ url: '/pages/order/list', fail: () => uni.redirectTo({ url: '/pages/order/list' }) })
return
}
if (i === 2) {
if (!needLogin()) return
uni.navigateTo({ url: '/pages/message/message' })
uni.switchTab({ url: '/pages/message/message', fail: () => uni.redirectTo({ url: '/pages/message/message' }) })
return
}
if (i === 3) {

查看文件

@@ -1,5 +1,7 @@
<template>
<view class="page">
<!-- 统一顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="white" title="消息中心" :border="true" />
<view class="head">
<view class="cats">
<view v-for="c in cats" :key="c.key" class="cat" :class="{ on: category === c.key }" @tap="switchCat(c.key)">{{ c.name }}</view>

查看文件

@@ -1,13 +1,10 @@
<template>
<view class="page">
<!-- 统一规范顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="orange" title="个人中心" :placeholder="true" />
<view class="header">
<view class="deco d1"></view>
<view class="h-top">
<view class="home-btn" @tap="backHome">
<text class="home-ic">‹</text>
<text class="home-tx">返回首页</text>
</view>
</view>
<view class="h-row">
<view class="avatar">
<image v-if="user && user.avatar" class="avatar-img" :src="user.avatar" mode="aspectFill"></image>
@@ -132,7 +129,14 @@
function toast(t) { uni.showToast({ title: t, icon: 'none' }); }
function go(url) { uni.navigateTo({ url: url }); }
function backHome() { uni.reLaunch({ url: '/pages/index/index' }); }
function backHome() {
const pages = getCurrentPages();
if (pages && pages.length > 1) {
uni.navigateBack();
} else {
uni.switchTab({ url: '/pages/index/index', fail: () => uni.redirectTo({ url: '/pages/index/index' }) });
}
}
function callService() { uni.makePhoneCall({ phoneNumber: '4000000000' }); }
async function load() {

查看文件

@@ -24,7 +24,9 @@
>
<text class="mode-name">{{ m.name }}</text>
<text class="mode-desc">{{ m.desc }}</text>
<view class="mode-check" v-if="priceType === m.key">✓</view>
<view class="mode-check" v-if="priceType === m.key">
<image class="mode-check-img" src="/static/icons/check-orange.svg" mode="aspectFit" />
</view>
</view>
</view>
<view class="mode-note">{{ modeNote }}</view>
@@ -345,8 +347,9 @@
.mode-desc { margin-top: 6rpx; font-size: 22rpx; color: #9A9A9A; line-height: 28rpx; }
.mode.on .mode-desc { color: #C07A3A; }
.mode-check {
position: absolute; right: 10rpx; top: 8rpx; width: 30rpx; height: 30rpx; line-height: 30rpx;
text-align: center; border-radius: 50%; background: #F0710D; color: #fff; font-size: 22rpx;
position: absolute; right: 10rpx; top: 8rpx; width: 32rpx; height: 32rpx; display: flex; align-items: center; justify-content: center;
}
.mode-check-img { width: 28rpx; height: 28rpx;
}
.mode-note {
margin-top: 14rpx; padding: 14rpx 16rpx; border-radius: 12rpx;

查看文件

@@ -1,5 +1,7 @@
<template>
<view class="page">
<!-- 统一顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="orange" title="订单详情" />
<!-- 骨架屏呼吸占位 (彻底解决白屏) -->
<view class="skeleton-box" v-if="loading && !order">
<view class="sk-hero yjyf-shimmer"></view>
@@ -159,7 +161,9 @@
}
function toast(t) { uni.showToast({ title: t, icon: 'none' }); }
function backHome() { uni.reLaunch({ url: '/pages/index/index' }); }
function backHome() {
uni.switchTab({ url: '/pages/index/index', fail: () => uni.redirectTo({ url: '/pages/index/index' }) });
}
function cancel() {
uni.showModal({

查看文件

@@ -1,5 +1,8 @@
<template>
<view class="page">
<!-- 统一规范顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="white" title="我的订单" :border="true" />
<view class="tabs">
<view v-for="t in tabs" :key="t.key" class="tab" :class="{ on: cur === t.key }" @tap="switchTab(t.key)">
<text class="tab-txt">{{ t.name }}</text>
@@ -31,7 +34,9 @@
</view>
<view class="empty-state" v-if="!loading && !list.length">
<view class="empty-badge">📋</view>
<view class="empty-badge">
<image class="empty-badge-img" src="/static/icons/scan-paste.svg" mode="aspectFit" />
</view>
<text class="empty-title">暂无相关订单</text>
<text class="empty-desc">优匠师傅随时待命,准时上门,售后有保障</text>
<view class="empty-btn clickable-hover" @tap="goOrder">立即找师傅</view>
@@ -137,6 +142,7 @@
.tip { text-align: center; font-size: 20rpx; color: #C4C4C4; }
.empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 120rpx 40rpx 80rpx; }
.empty-badge-img { width: 56rpx; height: 56rpx; opacity: 0.6; }
.empty-badge { width: 120rpx; height: 120rpx; line-height: 120rpx; text-align: center; border-radius: 50%; background: #FFF3E6; font-size: 54rpx; margin-bottom: 24rpx; }
.empty-title { font-size: 30rpx; font-weight: 700; color: #1F1F1F; margin-bottom: 10rpx; }
.empty-desc { font-size: 24rpx; color: #888888; margin-bottom: 36rpx; text-align: center; line-height: 36rpx; }

文件差异内容过多而无法显示 加载差异

查看文件

@@ -1,9 +1,11 @@
<template>
<view class="page">
<!-- 统一顶部导航栏(安全距离自适应) -->
<YjyfNavBar theme="white" title="服务评价" :border="true" />
<view class="card">
<view class="title">给本次服务打分</view>
<view class="stars">
<text v-for="i in 5" :key="i" class="star" :class="{ on: i <= score }" @tap="score = i">★</text>
<image v-for="i in 5" :key="i" class="star-icon" :src="i <= score ? '/static/icons/star-orange.svg' : '/static/icons/star-gray.svg'" mode="aspectFit" @tap="score = i" />
</view>
<text class="score-txt">{{ scoreText }}</text>
</view>
@@ -77,9 +79,8 @@
.page { min-height: 100vh; background: #F5F6F8; padding: 22rpx 28rpx 60rpx; }
.card { padding: 26rpx 24rpx; background: #fff; border-radius: 20rpx; margin-bottom: 20rpx; box-shadow: 0 6rpx 20rpx rgba(240,129,31,0.06); }
.title { font-size: 28rpx; font-weight: 700; color: #1F1F1F; }
.stars { display: flex; justify-content: center; padding: 20rpx 0 8rpx; }
.star { font-size: 60rpx; color: #E6E6E6; margin: 0 12rpx; }
.star.on { color: #FFB120; }
.stars { display: flex; justify-content: center; align-items: center; padding: 24rpx 0 12rpx; }
.star-icon { width: 56rpx; height: 56rpx; margin: 0 12rpx; }
.score-txt { display: block; text-align: center; font-size: 24rpx; color: #F0710D; }
.tags { display: flex; flex-wrap: wrap; padding-top: 14rpx; }
.tag { padding: 0 22rpx; line-height: 56rpx; margin: 0 14rpx 14rpx 0; border-radius: 28rpx; background: #F5F5F5; }

查看文件

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#9CA3AF" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="16" height="15" rx="2" ry="2"/>
<circle cx="7" cy="8" r="1.5"/>
<polyline points="18 13 13 9 5 16"/>
<line x1="19" y1="18" x2="19" y2="24"/>
<line x1="16" y1="21" x2="22" y2="21"/>
</svg>

之后

宽度:  |  高度:  |  大小: 401 B

查看文件

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#F0710D" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="3" width="16" height="15" rx="2" ry="2"/>
<circle cx="7" cy="8" r="1.5"/>
<polyline points="18 13 13 9 5 16"/>
<line x1="19" y1="18" x2="19" y2="24"/>
<line x1="16" y1="21" x2="22" y2="21"/>
</svg>

之后

宽度:  |  高度:  |  大小: 401 B

查看文件

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#9CA3AF" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="5" width="13" height="14" rx="2" ry="2"/>
<polygon points="17 8 22 5 22 19 17 16 17 8"/>
</svg>

之后

宽度:  |  高度:  |  大小: 293 B

查看文件

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#F0710D" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"/>
</svg>

之后

宽度:  |  高度:  |  大小: 223 B

查看文件

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#9CA3AF" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="9 18 15 12 9 6"/>
</svg>

之后

宽度:  |  高度:  |  大小: 221 B

查看文件

@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path fill="#F0710D" d="M864 256h-138.667l-42.667-85.333H341.333L298.667 256H160c-53.333 0-96 42.667-96 96v448c0 53.333 42.667 96 96 96h704c53.333 0 96-42.667 96-96V352c0-53.333-42.667-96-96-96zm0 544H160V352h160l42.667-85.333h298.667L704 352h160v448z"/><path fill="#F0710D" d="M512 405.333c-94.08 0-170.667 76.587-170.667 170.667S417.92 746.667 512 746.667 682.667 670.08 682.667 576 606.08 405.333 512 405.333zm0 256c-47.147 0-85.333-38.187-85.333-85.333s38.187-85.333 85.333-85.333 85.333 38.187 85.333 85.333-38.187 85.333-85.333 85.333z"/></svg>

之后

宽度:  |  高度:  |  大小: 637 B

查看文件

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#F0710D" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20 6 9 17 4 12"></polyline>
</svg>

之后

宽度:  |  高度:  |  大小: 207 B

查看文件

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#9CA3AF" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"/>
<line x1="6" y1="6" x2="18" y2="18"/>
</svg>

之后

宽度:  |  高度:  |  大小: 265 B

查看文件

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#FFFFFF" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
<polyline points="9 22 9 12 15 12 15 22"/>
</svg>

之后

宽度:  |  高度:  |  大小: 291 B

查看文件

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#F0710D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"/>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
<line x1="12" y1="17" x2="12.01" y2="17"/>
</svg>

之后

宽度:  |  高度:  |  大小: 314 B

查看文件

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#F0710D" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="9"/>
<path d="M12 6v12M15 9.5H9.5a2.5 2.5 0 0 1 0-5H14M9 14.5h5.5a2.5 2.5 0 0 1 0 5H10"/>
</svg>

之后

宽度:  |  高度:  |  大小: 306 B

查看文件

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="#FFFFFF">
<polygon points="5 3 19 12 5 21 5 3"/>
</svg>

之后

宽度:  |  高度:  |  大小: 146 B

查看文件

@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path fill="#F0710D" d="M576 64L192 576h256l-64 384 384-512H512l64-384z"/></svg>

之后

宽度:  |  高度:  |  大小: 167 B

查看文件

@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path fill="#F0710D" d="M704 128H576V64H448v64H320c-53.02 0-96 42.98-96 96v640c0 53.02 42.98 96 96 96h384c53.02 0 96-42.98 96-96V224c0-53.02-42.98-96-96-96zm-224 0h64v64h-64v-64zm256 736c0 17.67-14.33 32-32 32H320c-17.67 0-32-14.33-32-32V224c0-17.67 14.33-32 32-32h64v96h256v-96h64c17.67 0 32 14.33 32 32v640z"/><path fill="#F0710D" d="M384 416h256v64H384zm0 128h256v64H384zm0 128h192v64H384z"/></svg>

之后

宽度:  |  高度:  |  大小: 488 B

查看文件

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#9CA3AF" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="7"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>

之后

宽度:  |  高度:  |  大小: 267 B

查看文件

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="7"/>
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
</svg>

之后

宽度:  |  高度:  |  大小: 267 B

查看文件

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#E2E8F0" stroke="#CBD5E1" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>

之后

宽度:  |  高度:  |  大小: 288 B

查看文件

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#F0710D" stroke="#F0710D" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon>
</svg>

之后

宽度:  |  高度:  |  大小: 288 B

查看文件

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="#6B7280" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<line x1="10" y1="11" x2="10" y2="17"/>
<line x1="14" y1="11" x2="14" y2="17"/>
</svg>

之后

宽度:  |  高度:  |  大小: 396 B

查看文件

@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="64" height="64"><path fill="#07C160" d="M375.467 618.667c13.013 0 25.6-2.134 37.546-5.76-7.893-27.734-12.373-56.96-12.373-87.254 0-142.08 128.853-257.493 287.573-257.493 11.52 0 22.827.64 34.134 1.92C679.68 152.107 537.173 64 375.467 64 195.84 64 50.347 185.387 50.347 334.933c0 82.56 44.8 156.374 115.2 205.867l-28.8 86.827 101.546-50.774c42.88 28.16 88.747 41.814 137.174 41.814zm-147.2-349.867c23.68 0 42.666 18.986 42.666 42.666s-18.986 42.667-42.666 42.667-42.667-18.987-42.667-42.667 18.987-42.666 42.667-42.666zm213.333 85.333c-23.68 0-42.667-18.986-42.667-42.666s18.987-42.667 42.667-42.667 42.667 18.987 42.667 42.667-18.987 42.666-42.667 42.666z"/><path fill="#07C160" d="M973.653 525.653c0-124.8-114.773-226.133-256-226.133s-256 101.333-256 226.133c0 124.8 114.773 226.134 256 226.134 32.214 0 63.147-5.974 91.52-16.854L912 789.333l-24.96-74.88c53.333-41.813 86.613-104.106 86.613-188.8zm-332.8-51.2c-17.706 0-32-14.293-32-32s14.294-32 32-32 32 14.293 32 32-14.294 32-32 32zm160 0c-17.706 0-32-14.293-32-32s14.294-32 32-32 32 14.293 32 32-14.294 32-32 32z"/></svg>

之后

宽度:  |  高度:  |  大小: 1.1 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M563.4432 42.2656l2.944 2.1632 429.7216 328.256a38.4 38.4 0 0 1-44.6464 62.4384l-1.9712-1.408-27.904-21.312L921.6 921.6a51.2 51.2 0 0 1-48.64 51.136L870.4 972.8H153.6a51.2 51.2 0 0 1-51.136-48.64L102.4 921.6l-0.0128-509.184-27.8784 21.2992a38.4 38.4 0 0 1-48.512-59.4944l1.8944-1.536L457.6128 44.4288a89.6 89.6 0 0 1 105.8304-2.176z" fill="#F0710D" /><path d="M358.4 972.8V678.4a51.2 51.2 0 0 1 51.2-51.2h204.8a51.2 51.2 0 0 1 51.2 51.2v294.4" fill="#FFFFFF" /></svg>

之后

宽度:  |  高度:  |  大小: 735 B

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M563.4432 42.2656l2.944 2.1632 429.7216 328.256a38.4 38.4 0 0 1-44.6464 62.4384l-1.9712-1.408-27.904-21.312L921.6 921.6a51.2 51.2 0 0 1-48.64 51.136L870.4 972.8H153.6a51.2 51.2 0 0 1-51.136-48.64L102.4 921.6l-0.0128-509.184-27.8784 21.2992a38.4 38.4 0 0 1-48.512-59.4944l1.8944-1.536L457.6128 44.4288a89.6 89.6 0 0 1 105.8304-2.176z" fill="#9A9A9A" /><path d="M358.4 972.8V678.4a51.2 51.2 0 0 1 51.2-51.2h204.8a51.2 51.2 0 0 1 51.2 51.2v294.4" fill="#FFFFFF" /></svg>

之后

宽度:  |  高度:  |  大小: 735 B

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="124.59px" viewBox="0 0 1052 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#F0710D" d="M273.597039 150.31184h655.506987a123.226236 123.226236 0 0 1 123.196763 123.196763v429.921336a123.255709 123.255709 0 0 1-123.196763 123.314654h-102.270997v168.526099a28.470831 28.470831 0 0 1-45.565118 22.782559l-255.088035-191.308658H273.597039A123.16729 123.16729 0 0 1 150.400276 703.518357V273.47913a123.255709 123.255709 0 0 1 123.196763-123.196763z m551.143414 397.884283a56.381676 56.381676 0 1 0-56.352204-56.381677 56.381676 56.381676 0 0 0 56.440622 56.352204z m-225.46776 0a56.381676 56.381676 0 1 0-56.352204-56.381677 56.381676 56.381676 0 0 0 56.440622 56.352204z m-225.467761 0a56.381676 56.381676 0 1 0-56.381676-56.381677 56.381676 56.381676 0 0 0 56.440622 56.352204z" opacity=".988" /><path fill="#F0710D" d="M240.115814 112.73388a127.440862 127.440862 0 0 0-127.41139 127.411389v435.786445A123.078871 123.078871 0 0 1-0.029456 553.206517V165.107241a165.048295 165.048295 0 0 1 165.048295-165.048295h613.59651a123.255709 123.255709 0 0 1 122.725196 112.73388z" opacity="0.35" /></svg>

之后

宽度:  |  高度:  |  大小: 1.3 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="124.59px" viewBox="0 0 1052 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#9A9A9A" d="M273.597039 150.31184h655.506987a123.226236 123.226236 0 0 1 123.196763 123.196763v429.921336a123.255709 123.255709 0 0 1-123.196763 123.314654h-102.270997v168.526099a28.470831 28.470831 0 0 1-45.565118 22.782559l-255.088035-191.308658H273.597039A123.16729 123.16729 0 0 1 150.400276 703.518357V273.47913a123.255709 123.255709 0 0 1 123.196763-123.196763z m551.143414 397.884283a56.381676 56.381676 0 1 0-56.352204-56.381677 56.381676 56.381676 0 0 0 56.440622 56.352204z m-225.46776 0a56.381676 56.381676 0 1 0-56.352204-56.381677 56.381676 56.381676 0 0 0 56.440622 56.352204z m-225.467761 0a56.381676 56.381676 0 1 0-56.381676-56.381677 56.381676 56.381676 0 0 0 56.440622 56.352204z" opacity=".988" /><path fill="#9A9A9A" d="M240.115814 112.73388a127.440862 127.440862 0 0 0-127.41139 127.411389v435.786445A123.078871 123.078871 0 0 1-0.029456 553.206517V165.107241a165.048295 165.048295 0 0 1 165.048295-165.048295h613.59651a123.255709 123.255709 0 0 1 122.725196 112.73388z" opacity="0.35" /></svg>

之后

宽度:  |  高度:  |  大小: 1.3 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#F0710D" d="M829.517531 319.853714C829.517531 143.213714 687.035246 0 511.346103 0 335.583817 0 193.101531 143.213714 193.101531 319.853714c0 84.845714 33.499429 166.180571 93.184 226.157715a317.44 317.44 0 0 0 225.060572 93.622857c175.689143 0 318.171429-143.140571 318.171428-319.780572zM368.644389 437.394286c9.581714-9.508571 25.014857-9.508571 34.523428 0a151.405714 151.405714 0 0 0 218.624 0 24.429714 24.429714 0 0 1 34.596572-0.658286c9.801143 9.508571 10.020571 25.088 0.658285 34.816a200.265143 200.265143 0 0 1-289.133714 0 24.722286 24.722286 0 0 1-0.438857-34.157714h1.170286z m363.081142 248.978285c-75.702857-9.362286-148.406857 73.801143-220.306285 73.801143-71.972571 0-143.725714-82.432-220.306286-73.874285C214.31296 695.149714 52.813531 817.883429 21.727817 856.283429-9.357897 894.537143-17.915611 1024 70.733531 1024h881.225143c90.038857 0.219429 82.212571-129.170286 48.932572-167.497143-32.329143-38.4-193.609143-160.694857-269.238857-170.057143z" /></svg>

之后

宽度:  |  高度:  |  大小: 1.2 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#9A9A9A" d="M829.517531 319.853714C829.517531 143.213714 687.035246 0 511.346103 0 335.583817 0 193.101531 143.213714 193.101531 319.853714c0 84.845714 33.499429 166.180571 93.184 226.157715a317.44 317.44 0 0 0 225.060572 93.622857c175.689143 0 318.171429-143.140571 318.171428-319.780572zM368.644389 437.394286c9.581714-9.508571 25.014857-9.508571 34.523428 0a151.405714 151.405714 0 0 0 218.624 0 24.429714 24.429714 0 0 1 34.596572-0.658286c9.801143 9.508571 10.020571 25.088 0.658285 34.816a200.265143 200.265143 0 0 1-289.133714 0 24.722286 24.722286 0 0 1-0.438857-34.157714h1.170286z m363.081142 248.978285c-75.702857-9.362286-148.406857 73.801143-220.306285 73.801143-71.972571 0-143.725714-82.432-220.306286-73.874285C214.31296 695.149714 52.813531 817.883429 21.727817 856.283429-9.357897 894.537143-17.915611 1024 70.733531 1024h881.225143c90.038857 0.219429 82.212571-129.170286 48.932572-167.497143-32.329143-38.4-193.609143-160.694857-269.238857-170.057143z" /></svg>

之后

宽度:  |  高度:  |  大小: 1.2 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="122.96px" viewBox="0 0 1066 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M742.4 358.4m85.333333 0l136.533334 0q85.333333 0 85.333333 85.333333l0 443.733334q0 85.333333-85.333333 85.333333l-136.533334 0q-85.333333 0-85.333333-85.333333l0-443.733334q0-85.333333 85.333333-85.333333Z" fill="#F0710D" opacity="0.35" /><path d="M595.541333 133.973333H285.909333a1.493333 1.493333 0 0 1-1.493333-1.472V1.472a1.493333 1.493333 0 0 1 1.493333-1.472h309.653334a1.493333 1.493333 0 0 1 1.472 1.472v131.029333a1.493333 1.493333 0 0 1-1.493334 1.472z m163.605334-47.722666H637.44c-1.813333 44.010667-38.101333 79.530667-82.56 79.530666H326.528c-44.416 0-80.746667-35.52-82.56-79.530666H122.325333C55.04 86.250667 0 141.290667 0 208.554667v693.12C0 968.96 55.04 1024 122.304 1024h636.821333c67.285333 0 122.325333-55.04 122.325334-122.304V208.554667c0-67.285333-55.04-122.304-122.304-122.304z m-89.002667 685.013333H211.306667a30.549333 30.549333 0 1 1 0-61.141333h458.837333a30.549333 30.549333 0 1 1 0 61.141333z m0-186.368H211.306667a30.549333 30.549333 0 1 1 0-61.141333h458.837333a30.549333 30.549333 0 1 1 0 61.141333z m0-186.410667H211.306667a30.549333 30.549333 0 1 1 0-61.141333h458.837333a30.549333 30.549333 0 1 1 0 61.141333z" fill="#F0710D" /></svg>

之后

宽度:  |  高度:  |  大小: 1.4 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="128.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#ffffff" d="M809.1032 991.129607c-112.271338 0-204.656599-91.893742-204.656599-203.48924 0-111.636458 92.385262-203.468759 204.656599-203.468759v-0.04096c112.250858 0 204.656599 91.914222 204.656599 203.468759 0 111.636458-92.405742 203.530199-204.656599 203.5302z m104.837099-206.090199a21.258236 21.258236 0 0 0-21.176316-20.828156v0.04096a20.951036 20.951036 0 0 0-20.951035 20.869116v-24.391675a20.971516 20.971516 0 0 0-41.963512 0v-14.847997a20.971516 20.971516 0 0 0-41.922552 0v-61.685748c0-11.509758-9.379838-20.869116-20.991995-20.889596a20.951036 20.951036 0 0 0-20.971516 20.889596v149.21725l-24.760315-24.657915a21.872636 21.872636 0 0 0-30.781434 1.16736 21.544956 21.544956 0 0 0 0 29.429754l68.587506 68.239346a87.326703 87.326703 0 0 0 67.809267 32.194554c46.673911 0 84.807663-36.433913 86.999022-82.104304h0.12288v-72.642545z m-368.926646-3.973119c0 111.595498 72.622065 203.489239 165.068767 242.872271H175.267807C82.862065 1023.93856 10.24 958.300173 10.24 873.00099V150.917295C10.24 65.577152 82.862065-0.102195 175.267807-0.061235h528.199574c92.426222 0 165.048287 65.638387 165.048287 150.97853v374.149045c-19.783676-6.574079-39.628792-6.574079-59.412468-6.574079-145.244131 0-264.089547 118.169576-264.089547 262.574028z m-198.77884-72.007666h149.31965c14.213117 0 24.883195-16.977917 24.883195-39.587832 0-22.589435-10.670078-39.587832-24.883195-39.587832h-149.31965c-14.213117 0-24.883195 16.998397-24.883195 39.587832 0 22.609915 10.670078 39.587832 24.883195 39.587832z m9.482238-190.361562h287.682502c20.172796 0 35.307513-16.957437 35.307513-39.587832 0-22.609915-15.134717-39.587832-35.307513-39.587832H355.717051c-20.234236 0-35.348473 16.977917-35.348473 39.587832 0 22.630395 15.155197 39.587832 35.348473 39.587832z m-95.313901-273.797065a29.962234 29.962234 0 0 0-41.328632 0l-19.599356 19.599356a29.921274 29.921274 0 0 0-41.328632 0l-0.1024 0.1024a29.081594 29.081594 0 0 0 0.1024 41.267192l20.664316 20.869115 0.43008 0.43008a29.081594 29.081594 0 0 0 40.939512-0.43008l40.222712-40.304631 0.16384-0.16384a29.245434 29.245434 0 0 0-0.16384-41.369592z m0 197.93916a29.921274 29.921274 0 0 0-41.328632 0l-19.599356 19.599356a29.921274 29.921274 0 0 0-41.328632 0l-0.1024 0.1024a29.081594 29.081594 0 0 0 0.1024 41.267192l20.664316 20.869116 0.43008 0.43008a29.081594 29.081594 0 0 0 40.939512-0.43008l40.222712-40.304632 0.16384-0.16384a29.245434 29.245434 0 0 0-0.16384-41.369592z m0 197.939161a29.921274 29.921274 0 0 0-41.328632 0l-19.599356 19.599356a29.921274 29.921274 0 0 0-41.328632 0l-0.1024 0.1024a29.081594 29.081594 0 0 0 0.1024 41.267191l20.664316 20.848636a29.081594 29.081594 0 0 0 41.369592 0l40.222712-40.284152 0.16384-0.16384a29.245434 29.245434 0 0 0-0.16384-41.369591z m462.704547-398.172081H355.594171c-20.111356 0-35.225593 16.281597-35.225593 38.051833 0 21.688316 15.114237 37.990392 35.225593 37.990392h367.513526c20.152316 0 35.225593-16.261117 35.225593-38.031352 0-21.688316-15.073277-38.010872-35.225593-38.010873z" /></svg>

之后

宽度:  |  高度:  |  大小: 3.2 KiB

查看文件

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="128px" height="122.96px" viewBox="0 0 1066 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M742.4 358.4m85.333333 0l136.533334 0q85.333333 0 85.333333 85.333333l0 443.733334q0 85.333333-85.333333 85.333333l-136.533334 0q-85.333333 0-85.333333-85.333333l0-443.733334q0-85.333333 85.333333-85.333333Z" fill="#9A9A9A" opacity="0.35" /><path d="M595.541333 133.973333H285.909333a1.493333 1.493333 0 0 1-1.493333-1.472V1.472a1.493333 1.493333 0 0 1 1.493333-1.472h309.653334a1.493333 1.493333 0 0 1 1.472 1.472v131.029333a1.493333 1.493333 0 0 1-1.493334 1.472z m163.605334-47.722666H637.44c-1.813333 44.010667-38.101333 79.530667-82.56 79.530666H326.528c-44.416 0-80.746667-35.52-82.56-79.530666H122.325333C55.04 86.250667 0 141.290667 0 208.554667v693.12C0 968.96 55.04 1024 122.304 1024h636.821333c67.285333 0 122.325333-55.04 122.325334-122.304V208.554667c0-67.285333-55.04-122.304-122.304-122.304z m-89.002667 685.013333H211.306667a30.549333 30.549333 0 1 1 0-61.141333h458.837333a30.549333 30.549333 0 1 1 0 61.141333z m0-186.368H211.306667a30.549333 30.549333 0 1 1 0-61.141333h458.837333a30.549333 30.549333 0 1 1 0 61.141333z m0-186.410667H211.306667a30.549333 30.549333 0 1 1 0-61.141333h458.837333a30.549333 30.549333 0 1 1 0 61.141333z" fill="#9A9A9A" /></svg>

之后

宽度:  |  高度:  |  大小: 1.4 KiB

查看文件

@@ -16,6 +16,7 @@ export default {
services: (data) => get('/home/services', data),
serviceDetail: (id) => get('/home/detail', { id: id }),
calc: (data) => get('/home/calc', data),
masterCount: (data) => get('/home/masterCount', data),
/* ---------- 地址 ---------- */
addressList: () => get('/address/index'),

查看文件

@@ -5,7 +5,9 @@
* - 失败统一 toast,未登录自动清理并引导登录
* - 内置 POST 请求 500ms 防并发快速连击锁
*/
const BASE_URL = 'https://yjyf.gzyakj.com.cn/api';
import config from '@/config/config.js';
const BASE_URL = (config.baseUrl || config.dev).replace(/\/+$/, '') + '/api';
const TOKEN_KEY = 'yjyf_token';
const USER_KEY = 'yjyf_user';