迁移版本
This commit is contained in:
@@ -27,4 +27,9 @@ export const toCartApi = async (listId, productId) => {
|
||||
product_id: productId
|
||||
})
|
||||
}
|
||||
|
||||
export const submitHighIntentApi = async (list_id, intent_remark = '') => {
|
||||
return await post(`${prefix}submit-high-intent`, { list_id, intent_remark })
|
||||
}
|
||||
export const savePreferSkuApi = async (params) => {
|
||||
return await post(`${prefix}save-prefer-sku`, params)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
import {get} from '../request';
|
||||
import {get, post} from '../request';
|
||||
|
||||
const prefix = 'product/'
|
||||
|
||||
@@ -12,3 +12,6 @@ export const getCategoryListByPidApi = async (pid = 0) => {
|
||||
export const getProductDetailApi = async (id = 0, p_user_id = 0) => {
|
||||
return await get(`${prefix}detail`, {id, p_user_id})
|
||||
}
|
||||
export const productViewApi = async (id) => {
|
||||
return await post(`${prefix}view`, { id })
|
||||
}
|
||||
|
||||
@@ -15,3 +15,6 @@ export const updateNickNameApi = async (nick_name) => {
|
||||
nick_name
|
||||
})
|
||||
}
|
||||
export const updateProfileApi = async (nick_name, avatar) => {
|
||||
return await post(`${prefix}update-profile`, { nick_name, avatar })
|
||||
}
|
||||
|
||||
@@ -63,6 +63,12 @@
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/cart/quote",
|
||||
"style": {
|
||||
"navigationBarTitleText": "查看报价"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/search/search",
|
||||
"style": {
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
<view class="top-left">
|
||||
<u-icon name="calendar" color="#999" size="14"></u-icon>
|
||||
<text class="date-text">{{ item.created_at }}</text>
|
||||
<view class="list-status-badges">
|
||||
<text v-if="item.is_high_intent" class="list-badge intent">已申请</text>
|
||||
<text v-if="item.quote_status === 2" class="list-badge quoted">已报价</text>
|
||||
<text v-else-if="item.quote_status === 1" class="list-badge quoting">报价中</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 显性删除按钮:更直观,防误触 -->
|
||||
<view class="delete-btn" @tap.stop="handleDelete(index, item.id)">
|
||||
@@ -319,6 +324,35 @@ view, scroll-view, text, image {
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.list-status-badges {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
.list-badge {
|
||||
font-size: 18rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 4rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.list-badge.intent {
|
||||
color: #B4854D;
|
||||
background: rgba(180, 133, 77, 0.1);
|
||||
}
|
||||
|
||||
.list-badge.quoted {
|
||||
color: #fff;
|
||||
background: #B4854D;
|
||||
}
|
||||
|
||||
.list-badge.quoting {
|
||||
color: #B4854D;
|
||||
border: 1rpx solid rgba(180, 133, 77, 0.4);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
padding: 10rpx;
|
||||
margin-right: -10rpx; /* 增加点击热区修正视觉位置 */
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部 Header: 极简大气 -->
|
||||
<view class="detail-header">
|
||||
<view class="header-content">
|
||||
<view class="title-group">
|
||||
@@ -8,42 +7,40 @@
|
||||
<text class="page-subtitle">高端臻选系列</text>
|
||||
</view>
|
||||
<view class="header-badges">
|
||||
<view class="status-badge-row" v-if="is_high_intent || quote_status === 2">
|
||||
<text v-if="is_high_intent" class="status-badge intent">已申请</text>
|
||||
<text v-if="quote_status === 2" class="status-badge quoted">已报价</text>
|
||||
</view>
|
||||
<text class="count-badge">共计 {{ info.items ? info.items.length : 0 }} 件</text>
|
||||
<text class="id-badge">清单号: {{ listId }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<scroll-view class="list-container" scroll-y>
|
||||
<view
|
||||
v-for="(item, index) in info.items"
|
||||
:key="index"
|
||||
:key="item.id || index"
|
||||
class="product-card animate-up"
|
||||
:style="{ animationDelay: index * 0.05 + 's' }"
|
||||
>
|
||||
<!-- 1. 卡片顶部工具栏:此处替代了 u-swipe-action -->
|
||||
<!-- 彻底移除了滑动组件,改为显性的删除按钮 -->
|
||||
<view class="card-top-bar" @tap="navigateToDetail(item.product.id)">
|
||||
<view class="top-left">
|
||||
<text class="index-num">NO.{{ index + 1 < 10 ? '0' + (index + 1) : index + 1 }}</text>
|
||||
<text class="divider-v">|</text>
|
||||
<text class="status-text">已选配置</text>
|
||||
<text class="status-text" :class="{ 'status-text--active': item.has_prefer_sku }">{{ specStatusText(item) }}</text>
|
||||
</view>
|
||||
|
||||
<view class="top-right">
|
||||
<!-- 删除按钮 -->
|
||||
<view class="action-btn delete" @tap.stop="handleDelete(index, item.id)">
|
||||
<u-icon name="trash" color="#999" size="18"></u-icon>
|
||||
</view>
|
||||
<!-- 详情跳转图标 -->
|
||||
<view class="action-btn arrow">
|
||||
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 2. 产品主体内容:左图右文,极简排版 -->
|
||||
<view class="card-body" @tap="navigateToDetail(item.product.id)">
|
||||
<view class="image-wrapper">
|
||||
<image
|
||||
@@ -57,10 +54,10 @@
|
||||
<view class="p-tags">
|
||||
<text class="luxury-tag">甄选家具</text>
|
||||
</view>
|
||||
<text v-if="item.selected_sku" class="selected-sku-hint">{{ item.selected_sku }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 3. 规格清单:轻奢菜单风格 -->
|
||||
<view class="specs-panel">
|
||||
<view class="specs-inner">
|
||||
<view
|
||||
@@ -73,11 +70,19 @@
|
||||
</view>
|
||||
|
||||
<view class="spec-detail-row">
|
||||
<!-- 移除 nowrap 防止长文本撑开 -->
|
||||
<text class="dim-text" v-if="spec.dimension">{{ spec.dimension }}</text>
|
||||
<view class="dashed-line"></view>
|
||||
<view class="price-group">
|
||||
<view v-for="(pItem, pIndex) in spec.routine" :key="pIndex" class="price-unit">
|
||||
<view
|
||||
v-for="(pItem, pIndex) in spec.routine"
|
||||
:key="pIndex"
|
||||
class="price-unit"
|
||||
:class="{
|
||||
'price-unit--selected': canSelectSku && isSpecSelected(item, spec, pIndex),
|
||||
'price-unit--disabled': !canSelectSku
|
||||
}"
|
||||
@tap.stop="canSelectSku && handlePreferSku(item, spec, pIndex)"
|
||||
>
|
||||
<text class="currency">¥</text>{{ pItem }}
|
||||
</view>
|
||||
</view>
|
||||
@@ -91,12 +96,90 @@
|
||||
<view class="safe-bottom"></view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="action-bar-wrapper safe-area-bottom">
|
||||
<view class="action-bar">
|
||||
<button
|
||||
v-if="quote_visible"
|
||||
class="bar-btn primary"
|
||||
@tap="goToQuote"
|
||||
>查看报价</button>
|
||||
<button
|
||||
v-if="!is_high_intent"
|
||||
class="bar-btn outline"
|
||||
@tap="openApplyPopup"
|
||||
>申请报价</button>
|
||||
<view v-else-if="!quote_visible" class="intent-status-bar">
|
||||
<text>已申请报价</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 申请报价弹层 -->
|
||||
<u-popup :show="applyPopupShow" mode="bottom" round="16" @close="closeApplyPopup">
|
||||
<view class="apply-popup">
|
||||
<view class="apply-popup-title">申请报价</view>
|
||||
<view class="apply-field">
|
||||
<text class="apply-label">备注(选填)</text>
|
||||
<textarea
|
||||
class="apply-remark"
|
||||
v-model="intentRemark"
|
||||
maxlength="500"
|
||||
placeholder="可填写特殊需求、交期等"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="quote_apply_require_sku" class="apply-sku-section">
|
||||
<text class="apply-label">请确认规格</text>
|
||||
<scroll-view scroll-y class="apply-sku-list">
|
||||
<view
|
||||
v-for="(item, index) in info.items"
|
||||
:key="item.id || index"
|
||||
class="apply-sku-row"
|
||||
@tap="openSkuPicker(item)"
|
||||
>
|
||||
<image class="apply-sku-cover" :src="item.product.cover" mode="aspectFill" />
|
||||
<view class="apply-sku-info">
|
||||
<text class="apply-sku-title">{{ item.product.title }}</text>
|
||||
<text
|
||||
class="apply-sku-value"
|
||||
:class="{ 'apply-sku-value--empty': !item.selected_sku }"
|
||||
>{{ item.selected_sku || '未选' }}</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" color="#ccc" size="14"></u-icon>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<button class="apply-confirm-btn" @tap="confirmApply">确认申请</button>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<!-- 快速选 SKU -->
|
||||
<u-popup :show="skuPickerShow" mode="bottom" round="16" @close="closeSkuPicker">
|
||||
<view class="sku-picker">
|
||||
<view class="sku-picker-title">{{ skuPickerTitle }}</view>
|
||||
<scroll-view scroll-y class="sku-picker-list">
|
||||
<view
|
||||
v-for="(opt, idx) in skuPickerOptions"
|
||||
:key="idx"
|
||||
class="sku-picker-item"
|
||||
:class="{ 'sku-picker-item--active': isPickerOptionSelected(opt) }"
|
||||
@tap="pickSkuOption(opt)"
|
||||
>
|
||||
<text class="sku-picker-label">{{ opt.displayLabel }}</text>
|
||||
<text class="sku-picker-price">{{ opt.priceText }}</text>
|
||||
</view>
|
||||
<view v-if="!skuPickerOptions.length" class="sku-picker-empty">暂无可选规格</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<u-toast ref="uToast"></u-toast>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {deleteCartItemApi, getCartItemApi} from "@/api/page/cart";
|
||||
import {deleteCartItemApi, getCartItemApi, submitHighIntentApi, savePreferSkuApi} from "@/api/page/cart";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
@@ -104,20 +187,152 @@ export default {
|
||||
listId: '',
|
||||
listName: '清单详情',
|
||||
info: { items: [] },
|
||||
// swipeOptions 已彻底移除
|
||||
is_high_intent: false,
|
||||
quote_status: 0,
|
||||
quote_visible: false,
|
||||
quote_final_total: null,
|
||||
quote_apply_require_sku: false,
|
||||
applyPopupShow: false,
|
||||
intentRemark: '',
|
||||
skuPickerShow: false,
|
||||
skuPickerItem: null,
|
||||
skuPickerOptions: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canSelectSku() {
|
||||
return this.quote_apply_require_sku && !this.quote_visible;
|
||||
},
|
||||
skuPickerTitle() {
|
||||
const t = this.skuPickerItem && this.skuPickerItem.product
|
||||
? this.skuPickerItem.product.title
|
||||
: '';
|
||||
return t ? `选择规格 - ${t}` : '选择规格';
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.listId = options.id
|
||||
this.getInfo();
|
||||
},
|
||||
methods: {
|
||||
specStatusText(item) {
|
||||
if (this.quote_visible && item.has_prefer_sku) {
|
||||
return '报价规格';
|
||||
}
|
||||
if (item.has_prefer_sku || item.selected_sku) {
|
||||
return '已预选';
|
||||
}
|
||||
return this.canSelectSku ? '点击预选规格' : '规格价目';
|
||||
},
|
||||
isSpecSelected(item, spec, pIndex) {
|
||||
const sheetId = Number(spec.id);
|
||||
if (item.price_sheet_id === sheetId && item.routine_index === pIndex) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
handlePreferSku(item, spec, pIndex) {
|
||||
if (!this.canSelectSku) {
|
||||
return;
|
||||
}
|
||||
if (this.isSpecSelected(item, spec, pIndex)) {
|
||||
return;
|
||||
}
|
||||
this.savePreferSku(item, spec.id, pIndex);
|
||||
},
|
||||
savePreferSku(item, priceSheetId, routineIndex) {
|
||||
uni.showLoading({ title: '保存中...' });
|
||||
return savePreferSkuApi({
|
||||
list_id: Number(this.listId),
|
||||
list_item_id: item.id,
|
||||
price_sheet_id: priceSheetId,
|
||||
routine_index: routineIndex,
|
||||
}).then((res) => {
|
||||
const data = res || {};
|
||||
item.price_sheet_id = data.price_sheet_id ?? priceSheetId;
|
||||
item.routine_index = data.routine_index ?? routineIndex;
|
||||
item.selected_sku = data.selected_sku ?? item.selected_sku;
|
||||
item.has_prefer_sku = true;
|
||||
uni.hideLoading();
|
||||
uni.showToast({ title: '已预选规格', icon: 'success' });
|
||||
return data;
|
||||
}).catch(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
parseRoutineLabel(raw) {
|
||||
const s = String(raw || '').trim();
|
||||
if (!s) return '常规';
|
||||
const m = s.match(/^(.*?)[::](.+)$/);
|
||||
if (m) {
|
||||
return (m[1] || '').trim() || '常规';
|
||||
}
|
||||
return '常规';
|
||||
},
|
||||
buildSkuOptions(item) {
|
||||
const sheets = (item && item.product && item.product.price_sheet) || [];
|
||||
const options = [];
|
||||
sheets.forEach((spec) => {
|
||||
const parts = [spec.specification, spec.dimension].filter(Boolean);
|
||||
const sheetTitle = parts.join(' / ') || '规格';
|
||||
const routines = Array.isArray(spec.routine) ? spec.routine : [];
|
||||
if (!routines.length) {
|
||||
options.push({
|
||||
price_sheet_id: spec.id,
|
||||
routine_index: 0,
|
||||
displayLabel: sheetTitle,
|
||||
priceText: '',
|
||||
sheetTitle,
|
||||
label: '常规',
|
||||
});
|
||||
return;
|
||||
}
|
||||
routines.forEach((raw, pIndex) => {
|
||||
const label = this.parseRoutineLabel(raw);
|
||||
const displayLabel = label && label !== '常规'
|
||||
? `${sheetTitle} / ${label}`
|
||||
: sheetTitle;
|
||||
options.push({
|
||||
price_sheet_id: spec.id,
|
||||
routine_index: pIndex,
|
||||
displayLabel,
|
||||
priceText: String(raw || ''),
|
||||
sheetTitle,
|
||||
label,
|
||||
});
|
||||
});
|
||||
});
|
||||
return options;
|
||||
},
|
||||
openSkuPicker(item) {
|
||||
this.skuPickerItem = item;
|
||||
this.skuPickerOptions = this.buildSkuOptions(item);
|
||||
this.skuPickerShow = true;
|
||||
},
|
||||
closeSkuPicker() {
|
||||
this.skuPickerShow = false;
|
||||
this.skuPickerItem = null;
|
||||
this.skuPickerOptions = [];
|
||||
},
|
||||
isPickerOptionSelected(opt) {
|
||||
const item = this.skuPickerItem;
|
||||
if (!item) return false;
|
||||
return Number(item.price_sheet_id) === Number(opt.price_sheet_id)
|
||||
&& Number(item.routine_index) === Number(opt.routine_index);
|
||||
},
|
||||
pickSkuOption(opt) {
|
||||
const item = this.skuPickerItem;
|
||||
if (!item) return;
|
||||
this.savePreferSku(item, opt.price_sheet_id, opt.routine_index).then(() => {
|
||||
this.closeSkuPicker();
|
||||
});
|
||||
},
|
||||
handleDelete(index, id) {
|
||||
uni.showModal({
|
||||
title: '移除确认',
|
||||
content: '将从您的甄选清单中移除此商品',
|
||||
confirmText: '移除',
|
||||
confirmColor: '#B4854D', // 金色确认按钮
|
||||
confirmColor: '#B4854D',
|
||||
cancelColor: '#999999',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
@@ -133,12 +348,50 @@ export default {
|
||||
})
|
||||
},
|
||||
getInfo() {
|
||||
uni.showLoading({ title: '加载中...' }) // 中文Loading
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
getCartItemApi(this.listId).then((res) => {
|
||||
this.info = res || { items: [] };
|
||||
const data = res || { items: [] };
|
||||
this.info = data;
|
||||
this.listName = data.name || '清单详情';
|
||||
this.is_high_intent = !!data.is_high_intent;
|
||||
this.quote_status = data.quote_status || 0;
|
||||
this.quote_visible = !!data.quote_visible;
|
||||
this.quote_final_total = data.quote_final_total;
|
||||
this.quote_apply_require_sku = Number(data.quote_apply_require_sku || 0) === 1;
|
||||
this.intentRemark = data.intent_remark || '';
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
goToQuote() {
|
||||
uni.navigateTo({
|
||||
url: `/pages/cart/quote?id=${this.listId}`,
|
||||
});
|
||||
},
|
||||
openApplyPopup() {
|
||||
this.applyPopupShow = true;
|
||||
},
|
||||
closeApplyPopup() {
|
||||
this.applyPopupShow = false;
|
||||
},
|
||||
confirmApply() {
|
||||
if (this.quote_apply_require_sku) {
|
||||
const items = this.info.items || [];
|
||||
const missing = items.some((it) => !it.selected_sku);
|
||||
if (missing) {
|
||||
uni.showToast({ title: '请先选择全部规格', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
uni.showLoading({ title: '提交中...' });
|
||||
submitHighIntentApi(this.listId, this.intentRemark || '').then(() => {
|
||||
uni.hideLoading();
|
||||
this.closeApplyPopup();
|
||||
uni.showToast({ title: '已申请报价', icon: 'success' });
|
||||
this.getInfo();
|
||||
}).catch(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
navigateToDetail(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/detail?id=${id}`,
|
||||
@@ -149,19 +402,17 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
/* 核心修复:强制使用 border-box 盒模型,防止 padding 撑开宽度 */
|
||||
view, scroll-view, text, image {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 全局容器:高级灰背景 */
|
||||
.container {
|
||||
min-height: 100vh;
|
||||
width: 100%; /* 确保容器宽度 */
|
||||
width: 100%;
|
||||
background-color: #F7F8FA;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden; /* 防止水平溢出 */
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
@@ -170,7 +421,6 @@ view, scroll-view, text, image {
|
||||
}
|
||||
.animate-up { animation: slideUp 0.5s cubic-bezier(0.2, 0.8, 0.2, 1) backwards; }
|
||||
|
||||
/* 1. Header 区域:极简,大量留白 */
|
||||
.detail-header {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
@@ -192,16 +442,16 @@ view, scroll-view, text, image {
|
||||
|
||||
.page-title {
|
||||
font-size: 48rpx;
|
||||
font-weight: 300; /* 细字体显得更高级 */
|
||||
font-weight: 300;
|
||||
color: #1A1A1A;
|
||||
letter-spacing: 2rpx;
|
||||
margin-bottom: 8rpx;
|
||||
font-family: serif; /* 尝试使用衬线风格(取决于系统支持) */
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 18rpx;
|
||||
color: #B4854D; /* 金色副标题 */
|
||||
color: #B4854D;
|
||||
letter-spacing: 6rpx;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
@@ -214,6 +464,30 @@ view, scroll-view, text, image {
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.status-badge-row {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
font-size: 18rpx;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.status-badge.intent {
|
||||
color: #B4854D;
|
||||
background: rgba(180, 133, 77, 0.12);
|
||||
}
|
||||
|
||||
.status-badge.quoted {
|
||||
color: #fff;
|
||||
background: #B4854D;
|
||||
}
|
||||
|
||||
.count-badge, .id-badge {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
@@ -221,26 +495,23 @@ view, scroll-view, text, image {
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
/* 2. 列表容器 */
|
||||
.list-container {
|
||||
flex: 1;
|
||||
width: 100%; /* 明确宽度 */
|
||||
width: 100%;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
/* 3. 商品卡片:核心设计 */
|
||||
.product-card {
|
||||
width: 100%; /* 确保卡片不超出父容器 */
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
margin-bottom: 30rpx;
|
||||
border-radius: 12rpx; /* 不需要太大的圆角 */
|
||||
box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.04); /* 大而柔和的阴影 */
|
||||
border-radius: 12rpx;
|
||||
box-shadow: 0 10rpx 40rpx rgba(0,0,0,0.04);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1rpx solid rgba(0,0,0,0.02); /* 极细的边框 */
|
||||
border: 1rpx solid rgba(0,0,0,0.02);
|
||||
}
|
||||
|
||||
/* -- 卡片顶部工具栏 -- */
|
||||
.card-top-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -253,6 +524,8 @@ view, scroll-view, text, image {
|
||||
.top-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.index-num {
|
||||
@@ -275,10 +548,16 @@ view, scroll-view, text, image {
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.status-text--active {
|
||||
color: #B4854D;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.top-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 30rpx; /* 按钮之间拉开距离 */
|
||||
gap: 30rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@@ -289,11 +568,10 @@ view, scroll-view, text, image {
|
||||
height: 44rpx;
|
||||
}
|
||||
|
||||
/* -- 卡片主体 (图片+标题) -- */
|
||||
.card-body {
|
||||
padding: 30rpx;
|
||||
display: flex;
|
||||
align-items: center; /* 垂直居中 */
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
@@ -304,7 +582,7 @@ view, scroll-view, text, image {
|
||||
overflow: hidden;
|
||||
background: #F9F9F9;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.05);
|
||||
flex-shrink: 0; /* 防止图片被压缩 */
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.product-thumb {
|
||||
@@ -317,7 +595,7 @@ view, scroll-view, text, image {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden; /* 防止文字过长溢出 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.p-title {
|
||||
@@ -326,7 +604,6 @@ view, scroll-view, text, image {
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 16rpx;
|
||||
/* 增加截断,防止超长标题撑开 */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -339,20 +616,26 @@ view, scroll-view, text, image {
|
||||
.luxury-tag {
|
||||
font-size: 18rpx;
|
||||
color: #B4854D;
|
||||
background: rgba(180, 133, 77, 0.08); /* 极淡的金色背景 */
|
||||
background: rgba(180, 133, 77, 0.08);
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 4rpx;
|
||||
letter-spacing: 1rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* -- 规格清单面板 (菜单式) -- */
|
||||
.selected-sku-hint {
|
||||
margin-top: 12rpx;
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.specs-panel {
|
||||
padding: 0 30rpx 40rpx; /* 底部留白 */
|
||||
padding: 0 30rpx 40rpx;
|
||||
}
|
||||
|
||||
.specs-inner {
|
||||
background: #FAFAFA; /* 浅灰底 */
|
||||
background: #FAFAFA;
|
||||
border: 1rpx solid #EFEFEF;
|
||||
border-radius: 8rpx;
|
||||
padding: 24rpx;
|
||||
@@ -386,34 +669,47 @@ view, scroll-view, text, image {
|
||||
.dim-text {
|
||||
font-size: 22rpx;
|
||||
color: #888;
|
||||
/* 移除 nowrap,允许换行,防止长尺寸描述撑开容器 */
|
||||
/* white-space: nowrap; */
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
|
||||
/* 虚线连接符 */
|
||||
.dashed-line {
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
border-bottom: 2rpx dashed #E0E0E0;
|
||||
margin: 0 16rpx;
|
||||
position: relative;
|
||||
top: -6rpx; /* 微调对齐 */
|
||||
min-width: 40rpx; /* 保证至少有一点虚线 */
|
||||
top: -6rpx;
|
||||
min-width: 40rpx;
|
||||
}
|
||||
|
||||
.price-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
flex-shrink: 0; /* 防止价格被挤压 */
|
||||
flex-shrink: 0;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.price-unit {
|
||||
font-family: 'Times New Roman', serif; /* 衬线体价格,显贵 */
|
||||
font-size: 30rpx;
|
||||
font-family: 'Times New Roman', serif;
|
||||
font-size: 28rpx;
|
||||
color: #B4854D;
|
||||
font-weight: bold;
|
||||
padding: 12rpx 20rpx;
|
||||
border-radius: 8rpx;
|
||||
border: 1rpx solid transparent;
|
||||
background: #fff;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.price-unit--selected {
|
||||
border-color: #B4854D;
|
||||
background: rgba(180, 133, 77, 0.12);
|
||||
box-shadow: 0 4rpx 12rpx rgba(180, 133, 77, 0.15);
|
||||
}
|
||||
|
||||
.price-unit--disabled {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.currency {
|
||||
@@ -423,6 +719,223 @@ view, scroll-view, text, image {
|
||||
}
|
||||
|
||||
.safe-bottom {
|
||||
height: 60rpx;
|
||||
height: 200rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
.action-bar-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
|
||||
padding: 20rpx 30rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 20rpx;
|
||||
width: 100%;
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
.bar-btn {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
font-size: 30rpx;
|
||||
padding: 0 24rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.bar-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.bar-btn.primary {
|
||||
background: #B4854D;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bar-btn.outline {
|
||||
background: #fff;
|
||||
color: #B4854D;
|
||||
border: 1rpx solid #B4854D;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.intent-status-bar {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
background: #F3F1EC;
|
||||
border-radius: 12rpx;
|
||||
border: 1rpx solid #E8E4DC;
|
||||
}
|
||||
|
||||
.intent-status-bar text {
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.apply-popup {
|
||||
padding: 36rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.apply-popup-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
text-align: center;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.apply-field {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.apply-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.apply-remark {
|
||||
width: 100%;
|
||||
min-height: 140rpx;
|
||||
padding: 20rpx;
|
||||
background: #F7F8FA;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.apply-sku-section {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.apply-sku-list {
|
||||
max-height: 420rpx;
|
||||
}
|
||||
|
||||
.apply-sku-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
}
|
||||
|
||||
.apply-sku-cover {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #F5F5F5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.apply-sku-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.apply-sku-title {
|
||||
font-size: 28rpx;
|
||||
color: #222;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.apply-sku-value {
|
||||
font-size: 24rpx;
|
||||
color: #B4854D;
|
||||
}
|
||||
|
||||
.apply-sku-value--empty {
|
||||
color: #E07040;
|
||||
}
|
||||
|
||||
.apply-confirm-btn {
|
||||
margin: 0;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: #B4854D;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.apply-confirm-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.sku-picker {
|
||||
padding: 36rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
max-height: 70vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sku-picker-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
text-align: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.sku-picker-list {
|
||||
max-height: 560rpx;
|
||||
}
|
||||
|
||||
.sku-picker-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 28rpx 20rpx;
|
||||
border-bottom: 1rpx solid #F0F0F0;
|
||||
}
|
||||
|
||||
.sku-picker-item--active {
|
||||
background: rgba(180, 133, 77, 0.08);
|
||||
}
|
||||
|
||||
.sku-picker-label {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.sku-picker-price {
|
||||
font-size: 26rpx;
|
||||
color: #B4854D;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sku-picker-empty {
|
||||
padding: 60rpx 0;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
||||
463
pages/cart/quote.vue
Normal file
463
pages/cart/quote.vue
Normal file
@@ -0,0 +1,463 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<scroll-view class="page-scroll" scroll-y>
|
||||
<view class="quote-sheet">
|
||||
<view class="sheet-header">
|
||||
<view class="brand-block">
|
||||
<view class="brand-mark" />
|
||||
<view class="brand-text">
|
||||
<text class="brand-name">产品报价</text>
|
||||
<text class="brand-sub">PRODUCT QUOTATION</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-right">
|
||||
<text class="sheet-title">报价单</text>
|
||||
<text class="sheet-list-name">清单:{{ listName }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="meta-grid">
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">清单名</text>
|
||||
<text class="meta-value">{{ listName }}</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">商品数</text>
|
||||
<text class="meta-value">{{ quoteItems.length }} 件</text>
|
||||
</view>
|
||||
<view class="meta-item">
|
||||
<text class="meta-label">明细合计</text>
|
||||
<text class="meta-value">¥{{ formatMoney(quote_items_total) }}</text>
|
||||
</view>
|
||||
<view class="meta-item" v-if="showPackagePrefer">
|
||||
<text class="meta-label">套餐优惠总价</text>
|
||||
<text class="meta-value accent">¥{{ formatMoney(quote_preferential_total) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="items-section">
|
||||
<view
|
||||
v-for="(item, index) in quoteItems"
|
||||
:key="item.id || index"
|
||||
class="quote-row"
|
||||
>
|
||||
<text class="row-index">{{ index + 1 }}</text>
|
||||
<image
|
||||
class="row-thumb"
|
||||
:src="itemCover(item)"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="row-body">
|
||||
<text class="row-title">{{ itemTitle(item) }}</text>
|
||||
<text v-if="itemIdentifier(item)" class="row-id">{{ itemIdentifier(item) }}</text>
|
||||
<view v-if="item.selected_sku" class="sku-block">
|
||||
<text class="sku-tag">预选规格</text>
|
||||
<text class="sku-text">{{ item.selected_sku }}</text>
|
||||
</view>
|
||||
<view class="row-prices">
|
||||
<view class="price-line" v-if="item.unit_price != null && item.unit_price !== ''">
|
||||
<text class="pl-label">单价</text>
|
||||
<text class="pl-val">¥{{ formatMoney(item.unit_price) }}</text>
|
||||
</view>
|
||||
<view class="price-line" v-if="item.preferential_price != null && item.preferential_price !== ''">
|
||||
<text class="pl-label">优惠价</text>
|
||||
<text class="pl-val accent">¥{{ formatMoney(item.preferential_price) }}</text>
|
||||
</view>
|
||||
<view class="price-line final">
|
||||
<text class="pl-label">成交价</text>
|
||||
<text class="pl-val final-val">¥{{ formatMoney(item.final_price) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="quoteItems.length === 0" class="empty-hint">
|
||||
<text>暂无报价明细</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="total-section">
|
||||
<text class="total-left">已选规格 {{ preferSkuCount }} / {{ quoteItems.length }} 件计入合计</text>
|
||||
<view class="total-right">
|
||||
<text class="total-label">总计金额</text>
|
||||
<text class="total-amount">¥{{ quoteFinalTotalDisplay }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="sheet-footer">
|
||||
<text class="footer-note">说明:本报价按清单当前已选规格生成;未选规格商品不计入合计。价格如有变动以最终确认为准。</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCartItemApi } from '@/api/page/cart';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
listId: '',
|
||||
listName: '-',
|
||||
quote_final_total: null,
|
||||
quote_items_total: null,
|
||||
quote_preferential_total: null,
|
||||
info: { items: [] },
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
quoteItems() {
|
||||
const items = this.info.items || [];
|
||||
return items.filter((item) => item.has_quote);
|
||||
},
|
||||
preferSkuCount() {
|
||||
return this.quoteItems.filter((item) => item.selected_sku).length;
|
||||
},
|
||||
quoteFinalTotalDisplay() {
|
||||
if (this.quote_final_total == null || this.quote_final_total === '') {
|
||||
return '--';
|
||||
}
|
||||
return this.formatMoney(this.quote_final_total);
|
||||
},
|
||||
showPackagePrefer() {
|
||||
return (
|
||||
this.quote_preferential_total != null &&
|
||||
this.quote_preferential_total !== '' &&
|
||||
Number(this.quote_preferential_total) !== Number(this.quote_items_total)
|
||||
);
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.listId = options.id;
|
||||
this.loadQuote();
|
||||
},
|
||||
methods: {
|
||||
formatMoney(n) {
|
||||
if (n == null || n === '') {
|
||||
return '--';
|
||||
}
|
||||
const num = Number(n) || 0;
|
||||
return num.toLocaleString('zh-CN', { maximumFractionDigits: 2, minimumFractionDigits: 0 });
|
||||
},
|
||||
itemCover(item) {
|
||||
return item.cover || (item.product && item.product.cover) || '';
|
||||
},
|
||||
itemTitle(item) {
|
||||
return item.title || (item.product && item.product.title) || '-';
|
||||
},
|
||||
itemIdentifier(item) {
|
||||
return item.identifier || (item.product && item.product.identifier) || '';
|
||||
},
|
||||
loadQuote() {
|
||||
uni.showLoading({ title: '加载中...' });
|
||||
getCartItemApi(this.listId).then((res) => {
|
||||
const data = res || { items: [] };
|
||||
this.info = data;
|
||||
this.listName = data.name || '-';
|
||||
this.quote_final_total = data.quote_final_total;
|
||||
this.quote_items_total = data.quote_items_total;
|
||||
this.quote_preferential_total = data.quote_preferential_total;
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
view, scroll-view, text, image {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f3f1ec;
|
||||
}
|
||||
|
||||
.page-scroll {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx 24rpx 48rpx;
|
||||
}
|
||||
|
||||
.quote-sheet {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 12rpx 40rpx rgba(31, 111, 91, 0.08);
|
||||
border: 1rpx solid rgba(31, 111, 91, 0.12);
|
||||
}
|
||||
|
||||
.sheet-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 36rpx 32rpx 28rpx;
|
||||
border-bottom: 4rpx solid #1f6f5b;
|
||||
background: #f7f5f1;
|
||||
}
|
||||
|
||||
.brand-block {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
width: 12rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 6rpx;
|
||||
background: linear-gradient(180deg, #1f6f5b 0%, #c4a574 100%);
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.brand-name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.brand-sub {
|
||||
font-size: 18rpx;
|
||||
color: #1f6f5b;
|
||||
letter-spacing: 4rpx;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
|
||||
.sheet-title {
|
||||
display: block;
|
||||
font-size: 52rpx;
|
||||
font-weight: 400;
|
||||
color: #1a1a1a;
|
||||
font-family: serif;
|
||||
letter-spacing: 8rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.sheet-list-name {
|
||||
font-size: 24rpx;
|
||||
color: #6d655c;
|
||||
}
|
||||
|
||||
.meta-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 24rpx 32rpx;
|
||||
gap: 16rpx 24rpx;
|
||||
background: #faf9f7;
|
||||
border-bottom: 1rpx solid #ebe6df;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
width: calc(50% - 12rpx);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.meta-label {
|
||||
font-size: 22rpx;
|
||||
color: #9a9288;
|
||||
}
|
||||
|
||||
.meta-value {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.meta-value.accent {
|
||||
color: #1f6f5b;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.items-section {
|
||||
padding: 8rpx 0;
|
||||
}
|
||||
|
||||
.quote-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0ebe4;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.quote-row:nth-child(even) {
|
||||
background: #fcfbfa;
|
||||
}
|
||||
|
||||
.row-index {
|
||||
width: 40rpx;
|
||||
font-size: 24rpx;
|
||||
color: #1f6f5b;
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.row-thumb {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #f3f1ec;
|
||||
flex-shrink: 0;
|
||||
border: 1rpx solid #ebe6df;
|
||||
}
|
||||
|
||||
.row-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.row-title {
|
||||
font-size: 30rpx;
|
||||
color: #222;
|
||||
font-weight: 500;
|
||||
line-height: 1.4;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.row-id {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.sku-block {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 16rpx;
|
||||
padding: 12rpx 16rpx;
|
||||
background: rgba(31, 111, 91, 0.08);
|
||||
border-radius: 8rpx;
|
||||
border-left: 4rpx solid #1f6f5b;
|
||||
}
|
||||
|
||||
.sku-tag {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
background: #1f6f5b;
|
||||
padding: 4rpx 10rpx;
|
||||
border-radius: 4rpx;
|
||||
flex-shrink: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sku-text {
|
||||
font-size: 24rpx;
|
||||
color: #444;
|
||||
line-height: 1.5;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.row-prices {
|
||||
border-top: 1rpx dashed #e8e4dc;
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.price-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6rpx 0;
|
||||
}
|
||||
|
||||
.price-line.final {
|
||||
margin-top: 8rpx;
|
||||
padding-top: 12rpx;
|
||||
border-top: 1rpx solid #f0ebe4;
|
||||
}
|
||||
|
||||
.pl-label {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.pl-val {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
font-family: 'Times New Roman', serif;
|
||||
}
|
||||
|
||||
.pl-val.accent {
|
||||
color: #B4854D;
|
||||
}
|
||||
|
||||
.pl-val.final-val {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #1f6f5b;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
text-align: center;
|
||||
padding: 80rpx 0;
|
||||
font-size: 28rpx;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.total-section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
padding: 28rpx 32rpx;
|
||||
background: #f7f5f1;
|
||||
border-top: 2rpx solid #1f6f5b;
|
||||
}
|
||||
|
||||
.total-left {
|
||||
font-size: 24rpx;
|
||||
color: #6d655c;
|
||||
flex: 1;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
.total-right {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.total-label {
|
||||
font-size: 26rpx;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.total-amount {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #1f6f5b;
|
||||
font-family: 'Times New Roman', serif;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.sheet-footer {
|
||||
padding: 24rpx 32rpx 36rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.footer-note {
|
||||
font-size: 22rpx;
|
||||
color: #7a7268;
|
||||
line-height: 1.7;
|
||||
}
|
||||
</style>
|
||||
@@ -67,6 +67,7 @@
|
||||
</view>
|
||||
<!-- 更精致的文字区域 -->
|
||||
<view class="category-info">
|
||||
<text class="category-name-en">{{ item.name_en || '-' }}</text>
|
||||
<text class="category-name">{{ item.name }}</text>
|
||||
<view class="category-indicator"></view>
|
||||
</view>
|
||||
@@ -316,6 +317,18 @@ export default {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.category-name-en {
|
||||
font-size: 20rpx;
|
||||
color: #B4854D;
|
||||
letter-spacing: 1rpx;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
margin-bottom: 4rpx;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* 卡片文字下方的点缀,增加设计感 */
|
||||
.category-indicator {
|
||||
width: 24rpx;
|
||||
|
||||
@@ -99,6 +99,36 @@
|
||||
</view>
|
||||
</up-popup>
|
||||
|
||||
<!-- 完善资料弹窗 -->
|
||||
<u-modal
|
||||
:show="profileModal"
|
||||
title="完善资料"
|
||||
:showCancelButton="true"
|
||||
cancelText="稍后"
|
||||
confirmText="确认"
|
||||
confirmColor="#B4854D"
|
||||
@confirm="confirmProfile"
|
||||
@cancel="profileModal = false"
|
||||
>
|
||||
<view class="profile-modal-content">
|
||||
<button class="avatar-picker" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
|
||||
<image
|
||||
v-if="profileForm.avatar"
|
||||
:src="profileForm.avatar"
|
||||
mode="aspectFill"
|
||||
class="avatar-preview"
|
||||
/>
|
||||
<text v-else class="avatar-placeholder">选择头像</text>
|
||||
</button>
|
||||
<input
|
||||
type="nickname"
|
||||
class="nickname-input"
|
||||
v-model="profileForm.nick_name"
|
||||
placeholder="请输入昵称"
|
||||
/>
|
||||
</view>
|
||||
</u-modal>
|
||||
|
||||
<!-- 底部垫高 -->
|
||||
<view class="safe-padding-bottom"></view>
|
||||
|
||||
@@ -119,9 +149,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProductDetailApi } from "@/api/page/product";
|
||||
import { getProductDetailApi, productViewApi } from "@/api/page/product";
|
||||
import { getCartListApi, toCartApi } from "@/api/page/cart";
|
||||
import {getCache} from "@/utils/cache";
|
||||
import { updateProfileApi } from "@/api/page/user";
|
||||
import { getCache, setCache } from "@/utils/cache";
|
||||
import { urlSafeBase64Encode } from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
@@ -130,6 +161,11 @@ export default {
|
||||
id: 0,
|
||||
p_user_id: 0,
|
||||
listModal: false,
|
||||
profileModal: false,
|
||||
profileForm: {
|
||||
nick_name: '',
|
||||
avatar: ''
|
||||
},
|
||||
listOption: [],
|
||||
product: {
|
||||
title: '',
|
||||
@@ -187,6 +223,39 @@ export default {
|
||||
cover: res.cover || res.carousel[0] || ''
|
||||
};
|
||||
this.initAlias();
|
||||
productViewApi(this.id).catch(() => {});
|
||||
this.checkProfile();
|
||||
});
|
||||
},
|
||||
checkProfile() {
|
||||
const userInfo = getCache('user_info') || {};
|
||||
const nick = userInfo.nick_name || '';
|
||||
const needProfile = !userInfo.avatar || !nick || nick.indexOf('微信用户') === 0;
|
||||
if (needProfile) {
|
||||
this.profileForm = {
|
||||
nick_name: nick,
|
||||
avatar: userInfo.avatar || ''
|
||||
};
|
||||
this.profileModal = true;
|
||||
}
|
||||
},
|
||||
onChooseAvatar(e) {
|
||||
this.profileForm.avatar = e.detail.avatarUrl;
|
||||
},
|
||||
confirmProfile() {
|
||||
if (!this.profileForm.nick_name || !this.profileForm.avatar) {
|
||||
uni.showToast({ title: '请完善头像和昵称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
updateProfileApi(this.profileForm.nick_name, this.profileForm.avatar).then(() => {
|
||||
const userInfo = getCache('user_info') || {};
|
||||
setCache('user_info', {
|
||||
...userInfo,
|
||||
nick_name: this.profileForm.nick_name,
|
||||
avatar: this.profileForm.avatar
|
||||
});
|
||||
this.profileModal = false;
|
||||
uni.showToast({ title: '资料已更新', icon: 'success' });
|
||||
});
|
||||
},
|
||||
initAlias() {
|
||||
@@ -468,4 +537,48 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.profile-modal-content {
|
||||
padding: 20rpx 10rpx 10rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.avatar-picker {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #F7F8FA;
|
||||
border: 2rpx dashed #B4854D;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.avatar-picker::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.avatar-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
font-size: 24rpx;
|
||||
color: #B4854D;
|
||||
}
|
||||
|
||||
.nickname-input {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
padding: 16rpx 0;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
</style>
|
||||
@@ -37,7 +37,10 @@
|
||||
:class="['tab-item', { active: currentTab === index }]"
|
||||
@tap="switchTab(index, tab.id)"
|
||||
>
|
||||
<text class="tab-text">{{ tab.name }}</text>
|
||||
<view class="tab-text-wrap">
|
||||
<text class="tab-text-en">{{ tab.name_en || '-' }}</text>
|
||||
<text class="tab-text">{{ tab.name }}</text>
|
||||
</view>
|
||||
<view class="active-dot" v-if="currentTab === index"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
@@ -205,6 +208,7 @@ export default {
|
||||
res.unshift({
|
||||
id: 0,
|
||||
name: "全部",
|
||||
name_en: "All",
|
||||
})
|
||||
this.tabs = res
|
||||
})
|
||||
@@ -360,16 +364,38 @@ export default {
|
||||
.category-tabs {
|
||||
white-space: nowrap;
|
||||
padding: 0 10rpx;
|
||||
height: 90rpx;
|
||||
height: 110rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 0 30rpx;
|
||||
height: 90rpx;
|
||||
line-height: 90rpx;
|
||||
padding: 0 28rpx;
|
||||
height: 110rpx;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.tab-text-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding-bottom: 12rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tab-text-en {
|
||||
font-size: 18rpx;
|
||||
color: #999;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 4rpx;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text-en {
|
||||
color: #B4854D;
|
||||
}
|
||||
|
||||
.tab-text {
|
||||
@@ -377,6 +403,7 @@ export default {
|
||||
color: #666;
|
||||
font-weight: 400;
|
||||
transition: all 0.3s;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tab-item.active .tab-text {
|
||||
@@ -387,7 +414,7 @@ export default {
|
||||
|
||||
.active-dot {
|
||||
position: absolute;
|
||||
bottom: 16rpx;
|
||||
bottom: 10rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 8rpx;
|
||||
|
||||
Reference in New Issue
Block a user