feat: 重构首页与全局UI,新增服务选择抽屉与统一导航组件
新增统一导航栏组件YjyfNavBar,替换各页面原生导航 新增服务选择抽屉组件GoodsPickerDrawer,实现分类选品流程 新增tabbar图标矢量资源,优化底部导航样式 新增后端运力统计接口与多商品下单逻辑 修复页面跳转逻辑,统一使用switchTab/redirectTo 优化订单创建与评价页的UI组件化,提升一致性
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>
|
||||
391
yjyf-app/components/YjyfNavBar.vue
普通文件
@@ -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">
|
||||
|
||||
473
yjyf-app/pages/category/category.vue
普通文件
@@ -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';
|
||||
|
||||