feat: 清单分享编码与加购选规格
详情展示/复制唯一编码,分享链接按编码只读打开;加购须先选规格;列表可复制编码。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,8 +5,16 @@ const prefix = 'list/'
|
||||
export const getCartListApi = async () => {
|
||||
return await get(`${prefix}list`)
|
||||
}
|
||||
export const getCartItemApi = async (id) => {
|
||||
return await get(`${prefix}detail`, {id})
|
||||
export const getCartItemApi = async (params) => {
|
||||
const query = typeof params === 'object' && params !== null
|
||||
? params
|
||||
: { id: params }
|
||||
return await get(`${prefix}detail`, query)
|
||||
}
|
||||
export const ensureShareCodeApi = async (listId) => {
|
||||
return await post(`${prefix}ensure-share-code`, {
|
||||
list_id: listId,
|
||||
})
|
||||
}
|
||||
export const createCartApi = async (params) => {
|
||||
return await post(`${prefix}create`, params)
|
||||
@@ -21,10 +29,12 @@ export const deleteCartItemApi = async (id) => {
|
||||
ids: [id]
|
||||
})
|
||||
}
|
||||
export const toCartApi = async (listId, productId) => {
|
||||
export const toCartApi = async (listId, productId, priceSheetId, routineIndex) => {
|
||||
return await post(`${prefix}to-cart`, {
|
||||
list_id: listId,
|
||||
product_id: productId
|
||||
product_id: productId,
|
||||
price_sheet_id: priceSheetId,
|
||||
routine_index: routineIndex,
|
||||
})
|
||||
}
|
||||
export const submitHighIntentApi = async (list_id, intent_remark = '') => {
|
||||
|
||||
@@ -45,6 +45,14 @@
|
||||
<view class="card-body">
|
||||
<view class="body-left">
|
||||
<text class="item-name">{{ item.name }}</text>
|
||||
<view
|
||||
v-if="item.share_code"
|
||||
class="share-code-row"
|
||||
@tap.stop="copyShareCode(item.share_code)"
|
||||
>
|
||||
<text class="share-code-text">编码 {{ item.share_code }}</text>
|
||||
<text class="share-code-copy">复制</text>
|
||||
</view>
|
||||
<view class="item-remark" v-if="item.remark">
|
||||
<text class="remark-line"></text>
|
||||
<text class="remark-text">{{ item.remark }}</text>
|
||||
@@ -154,6 +162,15 @@ export default {
|
||||
navigateBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
copyShareCode(code) {
|
||||
if (!code) return;
|
||||
uni.setClipboardData({
|
||||
data: String(code),
|
||||
success: () => {
|
||||
uni.showToast({ title: '编码已复制', icon: 'success' });
|
||||
},
|
||||
});
|
||||
},
|
||||
showCreateModal() {
|
||||
this.showModal = true
|
||||
},
|
||||
@@ -375,11 +392,34 @@ view, scroll-view, text, image {
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
color: #222;
|
||||
margin-bottom: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
display: block;
|
||||
font-family: serif;
|
||||
}
|
||||
|
||||
.share-code-row {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
padding: 4rpx 0;
|
||||
}
|
||||
|
||||
.share-code-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.share-code-copy {
|
||||
font-size: 20rpx;
|
||||
color: #B4854D;
|
||||
padding: 2rpx 12rpx;
|
||||
border: 1rpx solid #B4854D;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.item-remark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,18 +1,40 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 悬浮分享:无编码先生成;有编码再走微信分享 -->
|
||||
<button
|
||||
v-if="isOwner && shareCode"
|
||||
class="share-fixed-btn"
|
||||
open-type="share"
|
||||
hover-class="none"
|
||||
>
|
||||
<u-icon name="share-fill" color="#333" size="20"></u-icon>
|
||||
</button>
|
||||
<button
|
||||
v-else-if="isOwner"
|
||||
class="share-fixed-btn"
|
||||
hover-class="none"
|
||||
:loading="sharePreparing"
|
||||
@tap="prepareShare"
|
||||
>
|
||||
<u-icon name="share-fill" color="#333" size="20"></u-icon>
|
||||
</button>
|
||||
|
||||
<view class="detail-header">
|
||||
<view class="header-content">
|
||||
<view class="title-group">
|
||||
<text class="page-title">{{ listName }}</text>
|
||||
<text class="page-subtitle">高端臻选系列</text>
|
||||
<text class="page-subtitle">{{ isOwner ? '高端臻选系列' : '好友分享的清单' }}</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="isOwner && 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 class="code-badge-row" @tap="copyShareCode">
|
||||
<text class="id-badge">编码: {{ shareCode || '加载中…' }}</text>
|
||||
<text v-if="shareCode" class="copy-badge">复制</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -32,7 +54,11 @@
|
||||
</view>
|
||||
|
||||
<view class="top-right">
|
||||
<view class="action-btn delete" @tap.stop="handleDelete(index, item.id)">
|
||||
<view
|
||||
v-if="isOwner"
|
||||
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">
|
||||
@@ -104,13 +130,27 @@
|
||||
@tap="goToQuote"
|
||||
>查看报价</button>
|
||||
<button
|
||||
v-if="!is_high_intent"
|
||||
v-if="isOwner && !is_high_intent"
|
||||
class="bar-btn outline"
|
||||
@tap="openApplyPopup"
|
||||
>申请报价</button>
|
||||
<view v-else-if="!quote_visible" class="intent-status-bar">
|
||||
<view v-else-if="isOwner && !quote_visible" class="intent-status-bar">
|
||||
<text>已申请报价</text>
|
||||
</view>
|
||||
<view v-else-if="!isOwner && !quote_visible" class="intent-status-bar">
|
||||
<text>仅浏览</text>
|
||||
</view>
|
||||
<button
|
||||
v-if="isOwner && shareCode"
|
||||
class="bar-btn outline share-bar-btn"
|
||||
open-type="share"
|
||||
>分享清单</button>
|
||||
<button
|
||||
v-else-if="isOwner"
|
||||
class="bar-btn outline share-bar-btn"
|
||||
:loading="sharePreparing"
|
||||
@tap="prepareShare"
|
||||
>分享清单</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -128,7 +168,7 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="quote_apply_require_sku" class="apply-sku-section">
|
||||
<view v-if="!quote_visible" class="apply-sku-section">
|
||||
<text class="apply-label">请确认规格</text>
|
||||
<scroll-view scroll-y class="apply-sku-list">
|
||||
<view
|
||||
@@ -179,29 +219,38 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {deleteCartItemApi, getCartItemApi, submitHighIntentApi, savePreferSkuApi} from "@/api/page/cart";
|
||||
import {
|
||||
deleteCartItemApi,
|
||||
getCartItemApi,
|
||||
submitHighIntentApi,
|
||||
savePreferSkuApi,
|
||||
ensureShareCodeApi,
|
||||
} from "@/api/page/cart";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
listId: '',
|
||||
shareCodeQuery: '',
|
||||
listName: '清单详情',
|
||||
shareCode: '',
|
||||
isOwner: true,
|
||||
info: { items: [] },
|
||||
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: [],
|
||||
sharePreparing: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
canSelectSku() {
|
||||
return this.quote_apply_require_sku && !this.quote_visible;
|
||||
return this.isOwner && !this.quote_visible;
|
||||
},
|
||||
skuPickerTitle() {
|
||||
const t = this.skuPickerItem && this.skuPickerItem.product
|
||||
@@ -211,10 +260,49 @@ export default {
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.listId = options.id
|
||||
this.listId = options.id || '';
|
||||
this.shareCodeQuery = (options.code || options.share_code || '').toUpperCase();
|
||||
this.getInfo();
|
||||
},
|
||||
onShareAppMessage() {
|
||||
const code = this.shareCode || '';
|
||||
const name = this.listName || '清单';
|
||||
const path = code
|
||||
? `/pages/cart/detail?code=${code}`
|
||||
: `/pages/cart/detail?id=${this.listId}`;
|
||||
return {
|
||||
title: code ? `${name}(编码 ${code})` : name,
|
||||
path,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async prepareShare() {
|
||||
if (!this.isOwner || this.sharePreparing) return;
|
||||
if (this.shareCode) return;
|
||||
this.sharePreparing = true;
|
||||
try {
|
||||
const res = await ensureShareCodeApi(this.listId);
|
||||
this.shareCode = (res && res.share_code) || '';
|
||||
if (this.shareCode) {
|
||||
uni.showToast({ title: '编码已生成,请再点分享', icon: 'none' });
|
||||
} else {
|
||||
uni.showToast({ title: '生成编码失败', icon: 'none' });
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '生成编码失败', icon: 'none' });
|
||||
} finally {
|
||||
this.sharePreparing = false;
|
||||
}
|
||||
},
|
||||
copyShareCode() {
|
||||
if (!this.shareCode) return;
|
||||
uni.setClipboardData({
|
||||
data: this.shareCode,
|
||||
success: () => {
|
||||
uni.showToast({ title: '编码已复制', icon: 'success' });
|
||||
},
|
||||
});
|
||||
},
|
||||
specStatusText(item) {
|
||||
if (this.quote_visible && item.has_prefer_sku) {
|
||||
return '报价规格';
|
||||
@@ -305,6 +393,9 @@ export default {
|
||||
return options;
|
||||
},
|
||||
openSkuPicker(item) {
|
||||
if (!this.canSelectSku) {
|
||||
return;
|
||||
}
|
||||
this.skuPickerItem = item;
|
||||
this.skuPickerOptions = this.buildSkuOptions(item);
|
||||
this.skuPickerShow = true;
|
||||
@@ -349,17 +440,24 @@ export default {
|
||||
},
|
||||
getInfo() {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
getCartItemApi(this.listId).then((res) => {
|
||||
const params = this.shareCodeQuery
|
||||
? { share_code: this.shareCodeQuery }
|
||||
: { id: this.listId }
|
||||
getCartItemApi(params).then((res) => {
|
||||
const data = res || { items: [] };
|
||||
this.info = data;
|
||||
this.listId = data.id || this.listId;
|
||||
this.listName = data.name || '清单详情';
|
||||
this.shareCode = data.share_code || '';
|
||||
this.isOwner = data.is_owner !== false;
|
||||
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()
|
||||
}).catch(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
goToQuote() {
|
||||
@@ -374,13 +472,11 @@ export default {
|
||||
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;
|
||||
}
|
||||
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(() => {
|
||||
@@ -495,6 +591,20 @@ view, scroll-view, text, image {
|
||||
letter-spacing: 1rpx;
|
||||
}
|
||||
|
||||
.code-badge-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.copy-badge {
|
||||
font-size: 20rpx;
|
||||
color: #B4854D;
|
||||
padding: 2rpx 12rpx;
|
||||
border: 1rpx solid #B4854D;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
@@ -722,6 +832,34 @@ view, scroll-view, text, image {
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
.share-fixed-btn {
|
||||
position: fixed;
|
||||
top: 40rpx;
|
||||
right: 30rpx;
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
backdrop-filter: blur(8px);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
z-index: 999;
|
||||
border: none;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.share-fixed-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.share-bar-btn {
|
||||
flex: 0.85;
|
||||
}
|
||||
|
||||
.action-bar-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
||||
@@ -66,19 +66,36 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加清单弹窗 -->
|
||||
<!-- 添加清单弹窗:先选规格,再选清单 -->
|
||||
<up-popup
|
||||
:show="listModal"
|
||||
title="添加到清单"
|
||||
mode="bottom"
|
||||
:round="24"
|
||||
@close="listModal = false"
|
||||
@close="closeListModal"
|
||||
:safeAreaInsetTop="true"
|
||||
:closeable="true"
|
||||
bgColor="#F7F8FA"
|
||||
>
|
||||
<view class="popup-content">
|
||||
<scroll-view scroll-y style="max-height: 600rpx;">
|
||||
<view class="sku-pick-section">
|
||||
<text class="sku-pick-label">请选择规格</text>
|
||||
<scroll-view scroll-y class="sku-pick-scroll">
|
||||
<view
|
||||
v-for="(opt, idx) in addSkuOptions"
|
||||
:key="idx"
|
||||
class="sku-pick-item"
|
||||
:class="{ 'sku-pick-item--active': isAddSkuSelected(opt) }"
|
||||
@tap="selectAddSku(opt)"
|
||||
>
|
||||
<text class="sku-pick-name">{{ opt.displayLabel }}</text>
|
||||
<text class="sku-pick-price" v-if="opt.priceText">{{ opt.priceText }}</text>
|
||||
</view>
|
||||
<view v-if="!addSkuOptions.length" class="sku-pick-empty">暂无可选规格</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<text class="sku-pick-label">选择清单</text>
|
||||
<scroll-view scroll-y style="max-height: 360rpx;">
|
||||
<view class="list-options">
|
||||
<view
|
||||
v-for="(item, index) in listOption"
|
||||
@@ -167,6 +184,9 @@ export default {
|
||||
avatar: ''
|
||||
},
|
||||
listOption: [],
|
||||
addSkuOptions: [],
|
||||
selectedPriceSheetId: 0,
|
||||
selectedRoutineIndex: 0,
|
||||
product: {
|
||||
title: '',
|
||||
cover: '',
|
||||
@@ -204,12 +224,71 @@ export default {
|
||||
uni.navigateBack();
|
||||
},
|
||||
showModal() {
|
||||
this.selectedPriceSheetId = 0;
|
||||
this.selectedRoutineIndex = 0;
|
||||
this.addSkuOptions = this.buildAddSkuOptions();
|
||||
this.getListOption();
|
||||
this.listModal = true;
|
||||
},
|
||||
closeListModal() {
|
||||
this.listModal = false;
|
||||
},
|
||||
parseRoutineLabel(raw) {
|
||||
const s = String(raw || '').trim();
|
||||
if (!s) return '常规';
|
||||
const m = s.match(/^(.*?)[::](.+)$/);
|
||||
if (m) {
|
||||
return (m[1] || '').trim() || '常规';
|
||||
}
|
||||
return '常规';
|
||||
},
|
||||
buildAddSkuOptions() {
|
||||
const sheets = (this.product && this.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: '',
|
||||
});
|
||||
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 || ''),
|
||||
});
|
||||
});
|
||||
});
|
||||
return options;
|
||||
},
|
||||
selectAddSku(opt) {
|
||||
this.selectedPriceSheetId = Number(opt.price_sheet_id);
|
||||
this.selectedRoutineIndex = Number(opt.routine_index);
|
||||
},
|
||||
isAddSkuSelected(opt) {
|
||||
return Number(this.selectedPriceSheetId) === Number(opt.price_sheet_id)
|
||||
&& Number(this.selectedRoutineIndex) === Number(opt.routine_index)
|
||||
&& Number(this.selectedPriceSheetId) > 0;
|
||||
},
|
||||
addToCart(id) {
|
||||
toCartApi(id, this.id).then(() => {
|
||||
this.listModal = false;
|
||||
if (!this.selectedPriceSheetId) {
|
||||
uni.showToast({ title: '请先选择规格', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
toCartApi(id, this.id, this.selectedPriceSheetId, this.selectedRoutineIndex).then(() => {
|
||||
this.closeListModal();
|
||||
uni.showToast({
|
||||
title: '已添加到清单',
|
||||
icon: 'success'
|
||||
@@ -500,6 +579,59 @@ export default {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.sku-pick-section {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.sku-pick-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.sku-pick-scroll {
|
||||
max-height: 280rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.sku-pick-item {
|
||||
background: #fff;
|
||||
padding: 20rpx 24rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 12rpx;
|
||||
border: 2rpx solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.sku-pick-item--active {
|
||||
border-color: #B4854D;
|
||||
background: rgba(180, 133, 77, 0.08);
|
||||
}
|
||||
|
||||
.sku-pick-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sku-pick-price {
|
||||
font-size: 24rpx;
|
||||
color: #B4854D;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sku-pick-empty {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.list-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user