9.18王德鹏/1
This commit is contained in:
162
frontend/pages/detail/index.vue
Normal file
162
frontend/pages/detail/index.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<!-- 顶部时间维度筛选 -->
|
||||
<view class="seg">
|
||||
<view :class="['seg-item', range==='custom' && 'active']" @click="switchRange('custom')">自定义</view>
|
||||
<view :class="['seg-item', range==='week' && 'active']" @click="switchRange('week')">本周</view>
|
||||
<view :class="['seg-item', range==='today' && 'active']" @click="switchRange('today')">今日</view>
|
||||
<view :class="['seg-item', range==='month' && 'active']" @click="switchRange('month')">本月</view>
|
||||
<view :class="['seg-item', range==='year' && 'active']" @click="switchRange('year')">本年</view>
|
||||
</view>
|
||||
|
||||
<!-- 业务类型侧边切换:销售/进货/收款/资金/盘点 -->
|
||||
<view class="content">
|
||||
<view class="biz-tabs">
|
||||
<view v-for="b in bizList" :key="b.key" :class="['biz', biz===b.key && 'active']" @click="switchBiz(b.key)">{{ b.name }}</view>
|
||||
</view>
|
||||
|
||||
<view class="panel">
|
||||
<!-- 搜索框与期间显示、总额 -->
|
||||
<view class="toolbar">
|
||||
<view class="search">
|
||||
<input class="search-input" v-model.trim="query.kw" :placeholder="placeholder" @confirm="reload" />
|
||||
</view>
|
||||
<view class="period">{{ periodLabel }}</view>
|
||||
<button size="mini" @click="reload">查询</button>
|
||||
</view>
|
||||
|
||||
<view class="total">合计:¥{{ totalAmount.toFixed(2) }}</view>
|
||||
|
||||
<!-- 列表 -->
|
||||
<scroll-view scroll-y class="list" @scrolltolower="loadMore">
|
||||
<block v-if="items.length">
|
||||
<view class="item" v-for="it in items" :key="it.id" @click="openDetail(it)">
|
||||
<view class="item-left">
|
||||
<view class="date">{{ formatDate(it.orderTime || it.txTime || it.createdAt) }}</view>
|
||||
<view class="name">{{ it.customerName || it.supplierName || it.accountName || it.remark || '-' }}</view>
|
||||
<view class="no">{{ it.orderNo || it.code || it.id }}</view>
|
||||
</view>
|
||||
<view class="amount">¥ {{ (it.amount || 0).toFixed(2) }}</view>
|
||||
<view class="arrow">›</view>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else class="empty">暂无数据</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右下角新增按钮:根据业务类型跳转对应开单页或创建页 -->
|
||||
<view class="fab" @click="onCreate">+</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '../../common/http.js'
|
||||
|
||||
const API_OF = {
|
||||
sale: '/api/orders',
|
||||
purchase: '/api/purchase-orders',
|
||||
collect: '/api/payments',
|
||||
fund: '/api/other-transactions',
|
||||
stock: '/api/inventories/logs'
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
biz: 'sale',
|
||||
bizList: [
|
||||
{ key: 'sale', name: '出货' },
|
||||
{ key: 'purchase', name: '进货' },
|
||||
{ key: 'collect', name: '收款' },
|
||||
{ key: 'fund', name: '资金' },
|
||||
{ key: 'stock', name: '盘点' }
|
||||
],
|
||||
range: 'month',
|
||||
query: { kw: '' },
|
||||
items: [],
|
||||
page: 1,
|
||||
size: 20,
|
||||
finished: false,
|
||||
loading: false,
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
placeholder() { return '单据号/客户名称/品名规格/备注' },
|
||||
periodLabel() { return this.startDate && this.endDate ? `${this.startDate}~${this.endDate}` : '' },
|
||||
totalAmount() { return this.items.reduce((s, it) => s + Number(it.amount || 0), 0) }
|
||||
},
|
||||
onLoad() {
|
||||
try { console.log('[detail] onLoad route = pages/detail/index') } catch(e){}
|
||||
this.computeRange()
|
||||
this.reload()
|
||||
},
|
||||
methods: {
|
||||
switchBiz(k) { if (this.biz === k) return; this.biz = k; this.reload() },
|
||||
switchRange(r) { this.range = r; this.computeRange(); this.reload() },
|
||||
computeRange() {
|
||||
const now = new Date()
|
||||
const pad = n => String(n).padStart(2, '0')
|
||||
const fmt = d => `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`
|
||||
let start = now, end = now
|
||||
if (this.range === 'today') { start = end = now }
|
||||
else if (this.range === 'week') { const day = now.getDay() || 7; start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - day + 1); end = now }
|
||||
else if (this.range === 'month') { start = new Date(now.getFullYear(), now.getMonth(), 1); end = new Date(now.getFullYear(), now.getMonth() + 1, 0) }
|
||||
else if (this.range === 'year') { start = new Date(now.getFullYear(), 0, 1); end = new Date(now.getFullYear(), 11, 31) }
|
||||
else { start = new Date(now.getFullYear(), now.getMonth(), 1); end = new Date(now.getFullYear(), now.getMonth() + 1, 0) }
|
||||
this.startDate = fmt(start); this.endDate = fmt(end)
|
||||
},
|
||||
reload() { this.items = []; this.page = 1; this.finished = false; this.loadMore() },
|
||||
async loadMore() {
|
||||
if (this.loading || this.finished) return
|
||||
this.loading = true
|
||||
try {
|
||||
const path = API_OF[this.biz] || '/api/orders'
|
||||
const params = { kw: this.query.kw, page: this.page, size: this.size, startDate: this.startDate, endDate: this.endDate, biz: this.biz }
|
||||
if (this.biz === 'sale') params.type = 'out'
|
||||
const res = await get(path, params)
|
||||
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
|
||||
this.items = this.items.concat(list)
|
||||
if (list.length < this.size) this.finished = true
|
||||
this.page += 1
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '加载失败', icon: 'none' })
|
||||
} finally { this.loading = false }
|
||||
},
|
||||
formatDate(s) { if (!s) return ''; try { const d = new Date(s); const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}` } catch (_) { return String(s).slice(0,10) } },
|
||||
onCreate() { if (this.biz === 'sale') { uni.navigateTo({ url: '/pages/order/create' }); return } uni.showToast({ title: '该类型创建页待实现', icon: 'none' }) },
|
||||
openDetail(it) { uni.showToast({ title: '详情开发中', icon: 'none' }) }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.page { display:flex; flex-direction: column; height: 100vh; }
|
||||
.seg { display:flex; background:#fff; }
|
||||
.seg-item { flex:1; padding: 22rpx 0; text-align:center; color:#666; }
|
||||
.seg-item.active { color:#18b566; font-weight: 600; }
|
||||
|
||||
.content { display:flex; flex:1; min-height: 0; }
|
||||
.biz-tabs { width: 120rpx; background:#eef6ff; display:flex; flex-direction: column; }
|
||||
.biz { flex:0 0 120rpx; display:flex; align-items:center; justify-content:center; color:#4aa3d6; }
|
||||
.biz.active { background:#3ac1c9; color:#fff; border-radius: 0 16rpx 16rpx 0; }
|
||||
|
||||
.panel { flex:1; display:flex; flex-direction: column; background:#fff; margin: 16rpx; border-radius: 16rpx; padding: 12rpx; }
|
||||
.toolbar { display:flex; align-items: center; gap: 12rpx; padding: 8rpx 6rpx; }
|
||||
.search { flex:1; }
|
||||
.search-input { width:100%; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
|
||||
.period { color:#999; font-size: 24rpx; padding: 0 6rpx; }
|
||||
.total { color:#18b566; font-weight: 700; padding: 6rpx 6rpx 12rpx; }
|
||||
.list { flex:1; }
|
||||
.item { display:flex; align-items:center; padding: 20rpx 10rpx; border-bottom: 1rpx solid #f1f1f1; }
|
||||
.item-left { flex:1; }
|
||||
.date { color:#999; font-size: 24rpx; }
|
||||
.name { color:#333; margin: 4rpx 0; font-weight: 600; }
|
||||
.no { color:#bbb; font-size: 22rpx; }
|
||||
.amount { color:#333; font-weight: 700; }
|
||||
.arrow { color:#ccc; font-size: 40rpx; margin-left: 8rpx; }
|
||||
.empty { height: 50vh; display:flex; align-items:center; justify-content:center; color:#999; }
|
||||
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:#18b566; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15); }
|
||||
</style>
|
||||
@@ -75,7 +75,7 @@
|
||||
<view class="tab primary" @click="onCreateOrder">
|
||||
<text>开单</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: activeTab==='detail' }" @click="activeTab='detail'">
|
||||
<view class="tab" :class="{ active: activeTab==='detail' }" @click="goDetail">
|
||||
<text>明细</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: activeTab==='report' }" @click="activeTab='report'">
|
||||
@@ -161,6 +161,11 @@
|
||||
onCreateOrder() {
|
||||
uni.navigateTo({ url: '/pages/order/create' })
|
||||
},
|
||||
goDetail() {
|
||||
this.activeTab = 'detail'
|
||||
try { console.log('[index] goDetail → /pages/detail/index') } catch(e){}
|
||||
uni.navigateTo({ url: '/pages/detail/index' })
|
||||
},
|
||||
onNoticeTap(n) {
|
||||
uni.showModal({
|
||||
title: '广告',
|
||||
@@ -325,6 +330,7 @@
|
||||
background: rgba(255,255,255,0.85);
|
||||
box-shadow: 0 -6rpx 18rpx rgba(0,0,0,0.08);
|
||||
backdrop-filter: blur(10rpx);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.tab { flex: 1; text-align: center; color: #8a7535; font-size: 26rpx; }
|
||||
|
||||
@@ -36,8 +36,48 @@
|
||||
<text class="value">{{ supplierLabel }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 已选商品与合计(销售/进货) -->
|
||||
<view v-if="biz==='sale' || biz==='purchase'">
|
||||
<!-- 销售/进货:收款/付款 专用页面 -->
|
||||
<view v-if="(biz==='sale' && saleType==='collect') || (biz==='purchase' && purchaseType==='pay')">
|
||||
<!-- 客户 / 供应商 -->
|
||||
<view class="field" v-if="biz==='sale'" @click="chooseCustomer">
|
||||
<text class="label">客户</text>
|
||||
<text class="value">{{ customerLabel }}</text>
|
||||
</view>
|
||||
<view class="field" v-else @click="chooseSupplier">
|
||||
<text class="label">供应商</text>
|
||||
<text class="value">{{ supplierLabel }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 三种收付款方式 -->
|
||||
<view class="field pay-row">
|
||||
<text class="label">现金</text>
|
||||
<input class="pay-input" type="digit" v-model.number="payments.cash" placeholder="0.00" @input="recalcPay()"/>
|
||||
</view>
|
||||
<view class="field pay-row">
|
||||
<text class="label">银行存款</text>
|
||||
<input class="pay-input" type="digit" v-model.number="payments.bank" placeholder="0.00" @input="recalcPay()"/>
|
||||
</view>
|
||||
<view class="field pay-row">
|
||||
<text class="label">微信</text>
|
||||
<input class="pay-input" type="digit" v-model.number="payments.wechat" placeholder="0.00" @input="recalcPay()"/>
|
||||
</view>
|
||||
|
||||
<view class="collapse-trigger" @click="showMore = !showMore">{{ showMore ? '收起' : '' }}</view>
|
||||
|
||||
<!-- 备注与日期 -->
|
||||
<view class="textarea">
|
||||
<view class="amount-badge">总金额:{{ payTotal.toFixed(2) }}</view>
|
||||
<textarea v-model="order.remark" maxlength="200" placeholder="备注(最多输入200个字)"></textarea>
|
||||
<view class="date-mini">
|
||||
<picker mode="date" :value="order.orderTime" @change="onDateChange">
|
||||
<text>{{ order.orderTime }}</text>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 已选商品与合计(销售/进货 出入库) -->
|
||||
<view v-else-if="biz==='sale' || biz==='purchase'">
|
||||
<view class="summary">
|
||||
<text>选中货品({{ totalQuantity }})</text>
|
||||
<text>合计金额:¥ {{ totalAmount.toFixed(2) }}</text>
|
||||
@@ -127,7 +167,10 @@
|
||||
activeCategory: 'sale_income',
|
||||
trxAmount: 0,
|
||||
selectedAccountId: null,
|
||||
selectedAccountName: ''
|
||||
selectedAccountName: '',
|
||||
// 收款/付款输入
|
||||
payments: { cash: 0, bank: 0, wechat: 0 },
|
||||
showMore: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -141,11 +184,16 @@
|
||||
supplierLabel() { return this.supplierName || '零散供应商' },
|
||||
incomeCategories() { return INCOME_CATEGORIES },
|
||||
expenseCategories() { return EXPENSE_CATEGORIES },
|
||||
accountLabel() { return this.selectedAccountName || '现金' },
|
||||
counterpartyLabel() { return this.customerName || this.supplierName || '—' }
|
||||
accountLabel() { return this.selectedAccountName || '现金' },
|
||||
counterpartyLabel() { return this.customerName || this.supplierName || '—' },
|
||||
// 收款/付款合计
|
||||
payTotal() {
|
||||
const p = this.payments || { cash:0, bank:0, wechat:0 }
|
||||
return Number(p.cash||0) + Number(p.bank||0) + Number(p.wechat||0)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
switchBiz(type) { this.biz = type },
|
||||
switchBiz(type) { this.biz = type },
|
||||
onDateChange(e) { this.order.orderTime = e.detail.value },
|
||||
chooseCustomer() {
|
||||
uni.navigateTo({ url: '/pages/customer/select' })
|
||||
@@ -160,17 +208,24 @@
|
||||
uni.navigateTo({ url: '/pages/customer/select' })
|
||||
}
|
||||
},
|
||||
recalc() { this.$forceUpdate() },
|
||||
recalc() { this.$forceUpdate() },
|
||||
recalcPay() { this.$forceUpdate() },
|
||||
async submit() {
|
||||
const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase')
|
||||
const payload = isSaleOrPurchase ? {
|
||||
type: this.biz === 'sale' ? (this.saleType) : ('purchase.' + this.purchaseType),
|
||||
orderTime: this.order.orderTime,
|
||||
customerId: this.order.customerId,
|
||||
supplierId: this.order.supplierId,
|
||||
items: this.items.map(it => ({ productId: it.productId, quantity: Number(it.quantity||0), unitPrice: Number(it.unitPrice||0) })),
|
||||
amount: this.totalAmount
|
||||
} : {
|
||||
const isSaleOrPurchase = (this.biz==='sale' || this.biz==='purchase')
|
||||
const isCollectOrPay = (this.biz==='sale' && this.saleType==='collect') || (this.biz==='purchase' && this.purchaseType==='pay')
|
||||
const saleTypeValue = this.biz==='sale' ? ('sale.' + this.saleType) : ('purchase.' + this.purchaseType)
|
||||
const payload = isSaleOrPurchase ? (isCollectOrPay ? [
|
||||
{ method: 'cash', amount: Number(this.payments.cash||0) },
|
||||
{ method: 'bank', amount: Number(this.payments.bank||0) },
|
||||
{ method: 'wechat', amount: Number(this.payments.wechat||0) }
|
||||
].filter(p => p.amount>0) : {
|
||||
type: saleTypeValue,
|
||||
orderTime: this.order.orderTime,
|
||||
customerId: this.order.customerId,
|
||||
supplierId: this.order.supplierId,
|
||||
items: this.items.map(it => ({ productId: it.productId, quantity: Number(it.quantity||0), unitPrice: Number(it.unitPrice||0) })),
|
||||
amount: this.totalAmount
|
||||
}) : {
|
||||
type: this.biz,
|
||||
category: this.activeCategory,
|
||||
counterpartyId: this.order.customerId || null,
|
||||
@@ -179,8 +234,8 @@
|
||||
txTime: this.order.orderTime,
|
||||
remark: this.order.remark
|
||||
}
|
||||
try {
|
||||
const url = isSaleOrPurchase ? '/api/orders' : '/api/other-transactions'
|
||||
try {
|
||||
const url = isSaleOrPurchase ? (isCollectOrPay ? (`/api/payments/${this.biz}`) : '/api/orders') : '/api/other-transactions'
|
||||
await post(url, payload)
|
||||
uni.showToast({ title: '已保存', icon: 'success' })
|
||||
setTimeout(() => { uni.navigateBack() }, 600)
|
||||
@@ -188,10 +243,11 @@
|
||||
uni.showToast({ title: e && e.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
saveAndReset() {
|
||||
saveAndReset() {
|
||||
this.items = []
|
||||
this.trxAmount = 0
|
||||
this.order.remark = ''
|
||||
this.payments = { cash: 0, bank: 0, wechat: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +275,11 @@
|
||||
.col.amount { text-align:right; padding-right: 12rpx; color:#333; }
|
||||
.bottom { position: fixed; left:0; right:0; bottom:0; background:#fff; padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 16rpx); box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.06); }
|
||||
.primary { width: 100%; background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%); color:#493c1b; border-radius: 999rpx; padding: 20rpx 0; font-weight:800; }
|
||||
/* 收款/付款页样式 */
|
||||
.pay-row .pay-input { text-align: right; color:#333; }
|
||||
.textarea { position: relative; padding: 16rpx 24rpx; background:#fff; border-top: 1rpx solid #eee; }
|
||||
.amount-badge { position: absolute; right: 24rpx; top: -36rpx; background: #d1f0ff; color:#107e9b; padding: 8rpx 16rpx; border-radius: 12rpx; font-size: 24rpx; }
|
||||
.date-mini { position: absolute; right: 24rpx; bottom: 20rpx; color:#666; font-size: 24rpx; }
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
@@ -65,3 +65,6 @@ export default {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ export default {
|
||||
this.fetchCategories()
|
||||
this.reload()
|
||||
},
|
||||
onShow() {
|
||||
// 从创建/编辑页返回时,确保刷新最新列表
|
||||
this.reload()
|
||||
},
|
||||
computed: {
|
||||
categoryNames() { return this.categories.map(c => c.name) },
|
||||
categoryLabel() {
|
||||
@@ -131,3 +135,6 @@ export default {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<scroll-view scroll-y class="list">
|
||||
<view class="item" v-for="p in products" :key="p.id" @click="select(p)">
|
||||
<view class="name">{{ p.name }}</view>
|
||||
<view class="meta">{{ p.code }} · 库存:{{ p.stock || 0 }}</view>
|
||||
<view class="meta">{{ (p.brand||'') + ' ' + (p.model||'') + ' ' + (p.spec||'') }} · 库存:{{ p.stock ?? 0 }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -43,3 +43,6 @@ export default {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -65,3 +65,6 @@ export default {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user