This commit is contained in:
2025-09-20 12:05:53 +08:00
parent bff3d0414d
commit 9b107d665a
73 changed files with 2903 additions and 140 deletions

View File

@@ -84,11 +84,47 @@
"navigationBarTitleText": "选择账户"
}
},
{
"path": "pages/account/ledger",
"style": {
"navigationBarTitleText": "账户流水"
}
},
{
"path": "pages/account/form",
"style": {
"navigationBarTitleText": "新增/编辑账户"
}
},
{
"path": "pages/detail/index",
"style": {
"navigationBarTitleText": "明细"
}
},
{
"path": "pages/my/index",
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/my/about",
"style": {
"navigationBarTitleText": "关于与协议"
}
},
{
"path": "pages/report/index",
"style": {
"navigationBarTitleText": "报表"
}
},
{
"path": "pages/report/entry",
"style": {
"navigationBarTitleText": "报表"
}
}
],
"globalStyle": {

View File

@@ -0,0 +1,74 @@
<template>
<view class="page">
<view class="form">
<view class="field"><text class="label">账户名称</text><input class="input" v-model="form.name" placeholder="必填"/></view>
<view class="field" @click="showType=true">
<text class="label">账户类型</text>
<text class="value">{{ typeLabel(form.type) }}</text>
</view>
<view v-if="form.type==='bank'" class="field"><text class="label">银行名称</text><input class="input" v-model="form.bankName" placeholder="选填"/></view>
<view v-if="form.type==='bank'" class="field"><text class="label">银行账号</text><input class="input" v-model="form.bankAccount" placeholder="选填"/></view>
<view class="field"><text class="label">当前余额</text><input class="input" type="number" v-model="form.openingBalance" placeholder="0.00"/></view>
</view>
<view class="actions">
<button class="primary" @click="save">保存</button>
</view>
<uni-popup ref="popup" type="bottom" v-model="showType">
<view class="sheet">
<view class="sheet-item" v-for="t in types" :key="t.key" @click="form.type=t.key;showType=false">{{ t.name }}</view>
<view class="sheet-cancel" @click="showType=false">取消</view>
</view>
</uni-popup>
</view>
</template>
<script>
import { post, put, get } from '../../common/http.js'
export default {
data(){
return {
id: null,
form: { name: '', type: 'cash', bankName: '', bankAccount: '', openingBalance: '' },
showType: false,
types: [
{ key: 'cash', name: '现金' },
{ key: 'bank', name: '银行存款' },
{ key: 'wechat', name: '微信' },
{ key: 'alipay', name: '支付宝' },
{ key: 'other', name: '其他' }
]
}
},
onLoad(q){ this.id = q && q.id ? Number(q.id) : null; if (this.id) this.load(); },
methods: {
typeLabel(t){ const m = {cash:'现金', bank:'银行存款', wechat:'微信', alipay:'支付宝', other:'其他'}; return m[t]||t },
async load(){ try { const list = await get('/api/accounts'); const a = (Array.isArray(list)?list:(list?.list||[])).find(x=>x.id==this.id); if (a) { this.form={ name:a.name, type:a.type, bankName:a.bank_name||a.bankName||'', bankAccount:a.bank_account||a.bankAccount||'', openingBalance:'' } } } catch(e){} },
async save(){
if (!this.form.name) { uni.showToast({ title: '请输入名称', icon: 'none' }); return }
try {
const body = { ...this.form, openingBalance: Number(this.form.openingBalance||0) }
if (this.id) await put(`/api/accounts/${this.id}`, body)
else await post('/api/accounts', { ...body, status: 1 })
uni.showToast({ title: '已保存', icon: 'success' })
setTimeout(()=>uni.navigateBack(), 300)
} catch(e) { uni.showToast({ title: '保存失败', icon: 'none' }) }
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.form { background:#fff; }
.field { display:flex; align-items:center; justify-content: space-between; padding: 18rpx 20rpx; border-bottom:1rpx solid #f3f3f3; }
.label { color:#666; }
.input { flex:1; text-align: right; color:#333; }
.value { color:#333; }
.actions { margin-top: 20rpx; padding: 0 20rpx; }
.primary { width: 100%; background: #3c9cff; color:#fff; border-radius: 8rpx; padding: 22rpx 0; }
.sheet { background:#fff; }
.sheet-item { padding: 26rpx; text-align:center; border-bottom:1rpx solid #f2f2f2; }
.sheet-cancel { padding: 26rpx; text-align:center; color:#666; }
</style>

View File

@@ -0,0 +1,87 @@
<template>
<view class="page">
<view class="filters">
<picker mode="date" :value="startDate" @change="e=>{startDate=e.detail.value;load()}">
<view class="field"><text class="label">开始</text><text class="value">{{ startDate || '—' }}</text></view>
</picker>
<picker mode="date" :value="endDate" @change="e=>{endDate=e.detail.value;load()}">
<view class="field"><text class="label">结束</text><text class="value">{{ endDate || '—' }}</text></view>
</picker>
</view>
<view class="summary">
<view class="sum-item"><text class="k">收入</text><text class="v">{{ fmt(income) }}</text></view>
<view class="sum-item"><text class="k">支出</text><text class="v">{{ fmt(expense) }}</text></view>
<view class="sum-item"><text class="k">期初</text><text class="v">{{ fmt(opening) }}</text></view>
<view class="sum-item"><text class="k">期末</text><text class="v">{{ fmt(ending) }}</text></view>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="it in list" :key="it.id">
<view class="row">
<text class="title">{{ it.src==='other' ? (it.category || '其他') : (it.remark || '收付款') }}</text>
<text class="amount" :class="{ in: it.direction==='in', out: it.direction==='out' }">{{ it.direction==='in' ? '+' : '-' }}{{ fmt(it.amount) }}</text>
</view>
<view class="meta">{{ formatDate(it.tx_time || it.txTime) }} · {{ it.remark || '-' }}</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() {
return { accountId: null, startDate: '', endDate: '', list: [], opening: 0, income: 0, expense: 0, ending: 0 }
},
onLoad(query) {
this.accountId = Number(query && query.id)
this.quickInit()
this.load()
},
methods: {
quickInit() {
// 默认本月
const now = new Date()
const y = now.getFullYear(), m = now.getMonth()+1
this.startDate = `${y}-${String(m).padStart(2,'0')}-01`
const lastDay = new Date(y, m, 0).getDate()
this.endDate = `${y}-${String(m).padStart(2,'0')}-${String(lastDay).padStart(2,'0')}`
},
async load(page=1, size=50) {
try {
const res = await get(`/api/accounts/${this.accountId}/ledger`, { startDate: this.startDate, endDate: this.endDate, page, size })
this.list = (res && res.list) || []
this.opening = Number(res && res.opening || 0)
this.income = Number(res && res.income || 0)
this.expense = Number(res && res.expense || 0)
this.ending = Number(res && res.ending || 0)
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
},
fmt(v) { return (typeof v === 'number' ? v : Number(v||0)).toFixed(2) },
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())} ${pad(d.getHours())}:${pad(d.getMinutes())}` } catch(e){ return s } }
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.filters { display:flex; gap: 16rpx; padding: 16rpx; background:#fff; }
.field { display:flex; justify-content: space-between; align-items:center; padding: 16rpx; border:1rpx solid #eee; border-radius: 12rpx; min-width: 300rpx; }
.label { color:#666; }
.value { color:#333; }
.summary { display:grid; grid-template-columns: repeat(4,1fr); gap: 12rpx; padding: 12rpx 16rpx; background:#fff; border-top:1rpx solid #f1f1f1; border-bottom:1rpx solid #f1f1f1; }
.sum-item { padding: 12rpx; text-align:center; }
.k { display:block; color:#888; font-size: 24rpx; }
.v { display:block; margin-top:6rpx; font-weight:700; color:#333; }
.list { flex:1; }
.item { padding: 18rpx 16rpx; border-bottom:1rpx solid #f4f4f4; background:#fff; }
.row { display:flex; align-items:center; justify-content: space-between; margin-bottom: 6rpx; }
.title { color:#333; }
.amount { font-weight:700; }
.amount.in { color:#2a9d8f; }
.amount.out { color:#d35b5b; }
.meta { color:#999; font-size: 24rpx; }
</style>

View File

@@ -6,6 +6,7 @@
<view class="meta">{{ typeLabel(a.type) }} · 余额{{ a.balance?.toFixed ? a.balance.toFixed(2) : a.balance }}</view>
</view>
</scroll-view>
<view class="fab" @click="create"></view>
</view>
</template>
@@ -13,8 +14,9 @@
import { get } from '../../common/http.js'
const TYPE_MAP = { cash: '现金', bank: '银行', alipay: '支付宝', wechat: '微信', other: '其他' }
export default {
data() { return { accounts: [] } },
async onLoad() {
data() { return { accounts: [], mode: 'view' } },
async onLoad(q) {
this.mode = (q && q.mode) || 'view'
try {
const res = await get('/api/accounts')
this.accounts = Array.isArray(res) ? res : (res?.list || [])
@@ -22,13 +24,18 @@
},
methods: {
select(a) {
const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm) {
opener.$vm.selectedAccountId = a.id
opener.$vm.selectedAccountName = a.name
if (this.mode === 'pick') {
const opener = getCurrentPages()[getCurrentPages().length-2]
if (opener && opener.$vm) {
opener.$vm.selectedAccountId = a.id
opener.$vm.selectedAccountName = a.name
}
uni.navigateBack()
} else {
uni.navigateTo({ url: `/pages/account/ledger?id=${a.id}` })
}
uni.navigateBack()
},
create() { uni.navigateTo({ url: '/pages/account/form' }) },
typeLabel(t) { return TYPE_MAP[t] || t }
}
}
@@ -40,6 +47,7 @@
.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.name { color:#333; margin-bottom: 6rpx; }
.meta { color:#888; font-size: 24rpx; }
.fab { position: fixed; right: 32rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; border-radius: 50%; background:#3c9cff; color:#fff; display:flex; align-items:center; justify-content:center; font-size: 52rpx; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.18); }
</style>

View File

@@ -78,10 +78,10 @@
<view class="tab" :class="{ active: activeTab==='detail' }" @click="goDetail">
<text>明细</text>
</view>
<view class="tab" :class="{ active: activeTab==='report' }" @click="activeTab='report'">
<view class="tab" :class="{ active: activeTab==='report' }" @click="goReport">
<text>报表</text>
</view>
<view class="tab" :class="{ active: activeTab==='me' }" @click="activeTab='me'">
<view class="tab" :class="{ active: activeTab==='me' }" @click="goMe">
<text>我的</text>
</view>
</view>
@@ -156,6 +156,11 @@
uni.navigateTo({ url: '/pages/customer/select' })
return
}
if (item.key === 'account') {
// 进入账户模块(先使用账户选择页,已对接后端 /api/accounts
uni.navigateTo({ url: '/pages/account/select' })
return
}
if (item.key === 'supplier') {
uni.navigateTo({ url: '/pages/supplier/select' })
return
@@ -174,6 +179,14 @@
try { console.log('[index] goDetail → /pages/detail/index') } catch(e){}
uni.navigateTo({ url: '/pages/detail/index' })
},
goReport() {
this.activeTab = 'report'
uni.navigateTo({ url: '/pages/report/entry' })
},
goMe() {
this.activeTab = 'me'
uni.navigateTo({ url: '/pages/my/index' })
},
onNoticeTap(n) {
uni.showModal({
title: '广告',

View File

@@ -0,0 +1,59 @@
<template>
<view class="about">
<view class="hero">
<image class="logo" src="/static/logo.png" mode="aspectFit" />
<text class="title">五金配件管家</text>
<text class="subtitle">专注小微门店的极简进销存</text>
</view>
<view class="card">
<view class="row">
<text class="label">版本</text>
<text class="value">1.0.0</text>
</view>
<view class="row">
<text class="label">隐私协议</text>
<text class="link" @click="openPolicy">查看</text>
</view>
<view class="row">
<text class="label">用户协议</text>
<text class="link" @click="openTerms">查看</text>
</view>
<view class="row">
<text class="label">个人信息安全投诉</text>
<text class="link" @click="openComplaint">提交</text>
</view>
</view>
</view>
</template>
<script>
export default {
methods: {
openPolicy() {
uni.showModal({ title: '隐私协议', content: '隐私协议(静态占位)', showCancel: false })
},
openTerms() {
uni.showModal({ title: '用户协议', content: '用户协议(静态占位)', showCancel: false })
},
openComplaint() {
uni.showToast({ title: '暂未开通', icon: 'none' })
}
}
}
</script>
<style>
.about { padding: 24rpx; }
.hero { padding: 32rpx 24rpx; display: flex; flex-direction: column; align-items: center; gap: 10rpx; }
.logo { width: 160rpx; height: 160rpx; border-radius: 32rpx; }
.title { margin-top: 8rpx; font-size: 36rpx; font-weight: 800; color: #333; }
.subtitle { font-size: 26rpx; color: #888; }
.card { margin-top: 18rpx; background: #fff; border-radius: 16rpx; overflow: hidden; }
.row { display: flex; align-items: center; padding: 24rpx; border-top: 1rpx solid #f2f2f2; }
.label { color: #666; }
.value { margin-left: auto; color: #333; }
.link { margin-left: auto; color: #1aad19; }
</style>

152
frontend/pages/my/index.vue Normal file
View File

@@ -0,0 +1,152 @@
<template>
<view class="me">
<view class="card user">
<image class="avatar" :src="avatarUrl" mode="aspectFill" @error="onAvatarError" />
<view class="meta">
<text class="name">{{ shopName }}</text>
<text class="phone">{{ mobileDisplay }}</text>
<text class="role">老板</text>
</view>
</view>
<view class="group">
<view class="group-title">会员与订单</view>
<view class="cell" @click="goVip">
<text>VIP会员</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goMyOrders">
<text>我的订单</text>
<text class="arrow"></text>
</view>
</view>
<view class="group">
<view class="group-title">基础管理</view>
<view class="cell" @click="goSupplier">
<text>供应商管理</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goCustomer">
<text>客户管理</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goCustomerQuote">
<text>客户报价</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goShop">
<text>店铺管理</text>
<text class="arrow"></text>
</view>
</view>
<view class="group">
<view class="group-title">设置中心</view>
<view class="cell" @click="editProfile">
<text>账号与安全</text>
<text class="desc">修改头像姓名密码</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goProductSettings">
<text>商品设置</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goSystemParams">
<text>系统参数</text>
<text class="desc">低价提示默认收款单行折扣等</text>
<text class="arrow"></text>
</view>
<view class="cell" @click="goAbout">
<text>关于与协议</text>
<text class="arrow"></text>
</view>
<view class="cell danger" @click="logout">
<text>退出登录</text>
</view>
</view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() {
return {
avatarUrl: '/static/logo.png',
shopName: '我的店铺',
mobile: ''
}
},
onLoad() {
this.fetchProfile()
},
computed: {
mobileDisplay() {
const m = String(this.mobile || '')
return m.length === 11 ? m.slice(0,3) + '****' + m.slice(7) : (m || '未绑定手机号')
}
},
methods: {
async fetchProfile() {
// 后端暂无专门店铺/用户信息接口,先使用概览接口作为在线性检测与占位数据来源
try { await get('/api/dashboard/overview') } catch(e) {}
// 读取本地可能保存的店铺名与头像
try {
const storeName = uni.getStorageSync('SHOP_NAME') || ''
const avatar = uni.getStorageSync('USER_AVATAR') || ''
const phone = uni.getStorageSync('USER_MOBILE') || ''
if (storeName) this.shopName = storeName
if (avatar) this.avatarUrl = avatar
this.mobile = phone
} catch(e) {}
},
onAvatarError() {
this.avatarUrl = '/static/logo.png'
},
goVip() { uni.showToast({ title: 'VIP会员开发中', icon: 'none' }) },
goMyOrders() { uni.navigateTo({ url: '/pages/detail/index' }) },
goSupplier() { uni.navigateTo({ url: '/pages/supplier/select' }) },
goCustomer() { uni.navigateTo({ url: '/pages/customer/select' }) },
goCustomerQuote() { uni.showToast({ title: '客户报价(开发中)', icon: 'none' }) },
goShop() { uni.showToast({ title: '店铺管理(开发中)', icon: 'none' }) },
editProfile() { uni.showToast({ title: '账号与安全(开发中)', icon: 'none' }) },
goProductSettings() { uni.navigateTo({ url: '/pages/product/settings' }) },
goSystemParams() { uni.showToast({ title: '系统参数(开发中)', icon: 'none' }) },
goAbout() { uni.navigateTo({ url: '/pages/my/about' }) },
logout() {
try {
uni.removeStorageSync('TOKEN')
uni.removeStorageSync('USER_AVATAR')
uni.removeStorageSync('USER_NAME')
uni.removeStorageSync('USER_MOBILE')
uni.removeStorageSync('SHOP_NAME')
uni.showToast({ title: '已退出', icon: 'none' })
setTimeout(() => { uni.reLaunch({ url: '/pages/index/index' }) }, 300)
} catch(e) {
uni.reLaunch({ url: '/pages/index/index' })
}
}
}
}
</script>
<style>
.me { padding: 24rpx; }
.card.user { display: flex; gap: 18rpx; padding: 22rpx; background: #fff; border-radius: 16rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); align-items: center; }
.avatar { width: 120rpx; height: 120rpx; border-radius: 60rpx; background: #f5f5f5; }
.meta { display: flex; flex-direction: column; gap: 6rpx; }
.name { font-size: 34rpx; font-weight: 700; color: #333; }
.phone { font-size: 26rpx; color: #888; }
.role { font-size: 22rpx; color: #999; }
.group { margin-top: 24rpx; background: #fff; border-radius: 16rpx; overflow: hidden; }
.group-title { padding: 18rpx 22rpx; font-size: 26rpx; color: #999; background: #fafafa; }
.cell { display: flex; align-items: center; padding: 26rpx 22rpx; border-top: 1rpx solid #f0f0f0; color: #333; }
.cell .desc { margin-left: auto; margin-right: 8rpx; font-size: 22rpx; color: #999; }
.cell .arrow { margin-left: auto; color: #bbb; }
.cell.danger { color: #dd524d; justify-content: center; font-weight: 700; }
</style>

View File

@@ -89,6 +89,11 @@
<!-- 其它收入/支出 表单 -->
<view v-else>
<!-- 往来单位类型切换 -->
<view class="subtabs">
<button class="subbtn" :class="{ active: counterpartyType==='customer' }" @click="setCounterparty('customer')">客户</button>
<button class="subbtn" :class="{ active: counterpartyType==='supplier' }" @click="setCounterparty('supplier')">供应商</button>
</view>
<view class="chips">
<view v-for="c in (biz==='income' ? incomeCategories : expenseCategories)" :key="c.key" class="chip" :class="{ active: activeCategory===c.key }" @click="activeCategory=c.key">{{ c.label }}</view>
</view>
@@ -139,8 +144,8 @@
</template>
<script>
import { get, post } from '../../common/http.js'
import { INCOME_CATEGORIES, EXPENSE_CATEGORIES } from '../../common/constants.js'
import { get, post } from '../../common/http.js'
import { INCOME_CATEGORIES, EXPENSE_CATEGORIES } from '../../common/constants.js'
function todayString() {
const d = new Date()
@@ -151,7 +156,7 @@
export default {
data() {
return {
return {
biz: 'sale',
saleType: 'out',
purchaseType: 'in',
@@ -168,6 +173,7 @@
supplierName: '',
items: [],
activeCategory: 'sale_income',
counterpartyType: 'customer',
trxAmount: 0,
selectedAccountId: null,
selectedAccountName: '',
@@ -185,16 +191,19 @@
},
customerLabel() { return this.customerName || '零售客户' },
supplierLabel() { return this.supplierName || '零散供应商' },
incomeCategories() { return INCOME_CATEGORIES },
expenseCategories() { return EXPENSE_CATEGORIES },
incomeCategories() { return this._incomeCategories || INCOME_CATEGORIES },
expenseCategories() { return this._expenseCategories || EXPENSE_CATEGORIES },
accountLabel() { return this.selectedAccountName || '现金' },
counterpartyLabel() { return this.customerName || this.supplierName || '—' },
counterpartyLabel() { return this.counterpartyType==='customer' ? (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)
}
},
onLoad() {
this.fetchCategories()
},
onShow() {
if (this.biz === 'sale') {
if (this.order.customerId && this.order.customerId !== this._lastCustomerId) {
@@ -207,6 +216,20 @@
}
},
methods: {
async fetchCategories() {
try {
const res = await get('/api/finance/categories')
if (res && Array.isArray(res.incomeCategories)) this._incomeCategories = res.incomeCategories
if (res && Array.isArray(res.expenseCategories)) this._expenseCategories = res.expenseCategories
this.ensureActiveCategory()
} catch (_) { this.ensureActiveCategory() }
},
ensureActiveCategory() {
const list = this.biz==='income' ? (this.incomeCategories||[]) : (this.expenseCategories||[])
if (!list.length) return
const exists = list.some(it => it && it.key === this.activeCategory)
if (!exists) this.activeCategory = list[0].key
},
async loadCustomerLevel(customerId) {
try {
const d = await get(`/api/customers/${customerId}`)
@@ -235,7 +258,7 @@
this.recalc()
},
onPriceInput(it) { if (it) { it._autoPrice = false; this.recalc() } },
switchBiz(type) { this.biz = type },
switchBiz(type) { this.biz = type; this.ensureActiveCategory() },
onDateChange(e) { this.order.orderTime = e.detail.value },
chooseCustomer() {
uni.navigateTo({ url: '/pages/customer/select' })
@@ -244,12 +267,13 @@
chooseProduct() {
uni.navigateTo({ url: '/pages/product/select' })
},
chooseAccount() { uni.navigateTo({ url: '/pages/account/select' }) },
chooseAccount() { uni.navigateTo({ url: '/pages/account/select?mode=pick' }) },
chooseCounterparty() {
if (this.biz==='income' || this.biz==='expense') {
uni.navigateTo({ url: '/pages/customer/select' })
}
if (!(this.biz==='income' || this.biz==='expense')) return
if (this.counterpartyType==='customer') { uni.navigateTo({ url: '/pages/customer/select' }) }
else { uni.navigateTo({ url: '/pages/supplier/select' }) }
},
setCounterparty(t) { this.counterpartyType = t; this.ensureActiveCategory() },
recalc() { this.$forceUpdate() },
recalcPay() { this.$forceUpdate() },
async submit() {
@@ -262,7 +286,7 @@
const invalid = this.items.find(it => !it.productId || Number(it.quantity||0) <= 0)
if (invalid) { uni.showToast({ title: '数量需大于0', icon: 'none' }); return }
}
const payload = isSaleOrPurchase ? (isCollectOrPay ? [
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) }
@@ -276,7 +300,8 @@
}) : {
type: this.biz,
category: this.activeCategory,
counterpartyId: this.order.customerId || null,
counterpartyType: this.counterpartyType,
counterpartyId: this.counterpartyType==='customer' ? (this.order.customerId || null) : (this.order.supplierId || null),
accountId: this.selectedAccountId || null,
amount: Number(this.trxAmount||0),
txTime: this.order.orderTime,
@@ -328,6 +353,10 @@
.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; }
/* 分类chips样式选中后文字变红 */
.chips { display:flex; flex-wrap: wrap; gap: 12rpx; padding: 12rpx 24rpx; }
.chip { padding: 10rpx 20rpx; border-radius: 999rpx; background: #f4f4f4; color:#666; }
.chip.active { color: #e54d42; }
</style>

View File

@@ -0,0 +1,44 @@
<template>
<view class="entry">
<view class="section">
<view class="section-title">资金报表</view>
<view class="grid">
<view class="btn" @click="go('sale','customer')">利润统计</view>
<view class="btn" @click="go('sale','product')">营业员统计</view>
<view class="btn" @click="go('sale','customer')">经营业绩</view>
</view>
</view>
<view class="section">
<view class="section-title">进销存报表</view>
<view class="grid">
<view class="btn" @click="go('sale','customer')">销售统计</view>
<view class="btn" @click="go('purchase','supplier')">进货统计</view>
<view class="btn" @click="go('inventory','qty')">库存统计</view>
<view class="btn" @click="go('arap','ar')">应收对账单</view>
<view class="btn" @click="go('arap','ap')">应付对账单</view>
</view>
</view>
</view>
</template>
<script>
export default {
methods: {
go(mode, dim) {
const q = `mode=${encodeURIComponent(mode)}&dim=${encodeURIComponent(dim||'')}`
uni.navigateTo({ url: `/pages/report/index?${q}` })
}
}
}
</script>
<style>
.entry { padding: 20rpx; }
.section { margin-bottom: 24rpx; }
.section-title { background:#f1f4f8; color:#6a7a8a; padding: 14rpx 16rpx; border-radius: 12rpx; font-weight: 700; }
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18rpx; padding: 18rpx 6rpx 0; }
.btn { text-align: center; padding: 18rpx 8rpx; border: 1rpx solid #e5e9ef; border-radius: 12rpx; color:#333; background: #fff; }
.btn:active { background: #f6f8fa; }
</style>

View File

@@ -0,0 +1,291 @@
<template>
<view class="report">
<view class="modes">
<view class="mode-tab" :class="{active: mode==='sale'}" @click="setMode('sale')">销售统计</view>
<view class="mode-tab" :class="{active: mode==='purchase'}" @click="setMode('purchase')">进货统计</view>
<view class="mode-tab" :class="{active: mode==='inventory'}" @click="setMode('inventory')">库存统计</view>
<view class="mode-tab" :class="{active: mode==='arap'}" @click="setMode('arap')">应收/应付对账</view>
</view>
<view class="toolbar">
<picker mode="date" :value="startDate" @change="onStartChange"><view class="date">{{ startDate }}</view></picker>
<text style="margin: 0 8rpx;"></text>
<picker mode="date" :value="endDate" @change="onEndChange"><view class="date">{{ endDate }}</view></picker>
</view>
<view class="tabs" v-if="mode==='sale'">
<view class="tab" :class="{active: dim==='customer'}" @click="dim='customer'; refresh()">按客户</view>
<view class="tab" :class="{active: dim==='product'}" @click="dim='product'; refresh()">按货品</view>
</view>
<view class="tabs" v-else-if="mode==='purchase'">
<view class="tab" :class="{active: dim==='supplier'}" @click="dim='supplier'; refresh()">按供应商</view>
<view class="tab" :class="{active: dim==='product'}" @click="dim='product'; refresh()">按货品</view>
</view>
<view class="tabs" v-else-if="mode==='inventory'">
<view class="tab" :class="{active: dim==='qty'}" @click="dim='qty'; refresh()">按数量</view>
<view class="tab" :class="{active: dim==='amount'}" @click="dim='amount'; refresh()">按金额</view>
</view>
<view class="tabs" v-else-if="mode==='arap'">
<view class="tab" :class="{active: dim==='ar'}" @click="dim='ar'; refresh()">应收对账</view>
<view class="tab" :class="{active: dim==='ap'}" @click="dim='ap'; refresh()">应付对账</view>
</view>
<view class="summary">
<view class="item"><text class="label">销售额</text><text class="value"> {{ fmt(total.sales) }}</text></view>
<view class="item"><text class="label">成本</text><text class="value"> {{ fmt(total.cost) }}</text></view>
<view class="item"><text class="label">利润</text><text class="value"> {{ fmt(total.profit) }}</text></view>
<view class="item"><text class="label">利润率</text><text class="value">{{ profitRate }}</text></view>
</view>
<view v-for="(row, idx) in rows" :key="idx" class="card">
<view class="row-head">
<image v-if="row.avatar" class="thumb" :src="row.avatar" />
<view class="title">{{ row.name }}</view>
</view>
<view class="row-body">
<text>销售额 {{ fmt(row.sales) }}</text>
<text style="margin-left: 18rpx;">成本 {{ fmt(row.cost) }}</text>
<text style="margin-left: 18rpx;">利润 {{ fmt(row.profit) }}</text>
</view>
</view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
function formatDate(d) {
const y = d.getFullYear()
const m = String(d.getMonth()+1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}-${m}-${day}`
}
export default {
data() {
const now = new Date()
const start = new Date(now.getFullYear(), now.getMonth(), 1)
return {
startDate: formatDate(start),
endDate: formatDate(now),
mode: 'sale',
dim: 'customer',
rows: [],
total: { sales: 0, cost: 0, profit: 0 }
}
},
onLoad(query) {
try {
const m = query && query.mode
const d = query && query.dim
if (m) this.mode = m
if (d) this.dim = d
} catch(e){}
this.refresh()
},
computed: {
profitRate() {
const { sales, profit } = this.total
if (!sales) return '0.00%'
return ((profit / sales) * 100).toFixed(2) + '%'
}
},
methods: {
fmt(n) { return Number(n || 0).toFixed(2) },
setMode(m) {
this.mode = m
this.dim = m === 'sale' ? 'customer' : m === 'purchase' ? 'supplier' : m === 'inventory' ? 'qty' : 'ar'
this.refresh()
},
onStartChange(e) { this.startDate = e.detail.value; this.refresh() },
onEndChange(e) { this.endDate = e.detail.value; this.refresh() },
async refresh() {
if (this.mode === 'sale') {
if (this.dim === 'customer') return this.loadByCustomer()
if (this.dim === 'product') return this.loadByProduct()
}
if (this.mode === 'purchase') {
if (this.dim === 'supplier') return this.loadPurchaseBySupplier()
if (this.dim === 'product') return this.loadPurchaseByProduct()
}
if (this.mode === 'inventory') {
if (this.dim === 'qty') return this.loadInventoryByQty()
if (this.dim === 'amount') return this.loadInventoryByAmount()
}
if (this.mode === 'arap') {
if (this.dim === 'ar') return this.loadAR()
if (this.dim === 'ap') return this.loadAP()
}
},
async loadByCustomer() {
// 数据来源:/api/orders?biz=sale&type=out 与 /api/products/{id} 获取成本(近似),或由订单明细返回单价与估算成本
// 当前后端列表返回字段包含 amount、customerName缺少明细成本采用二段法
// 1) 列表聚合销售额2) 如存在 productId 与单位进价可获取成本;暂以 0 成本占位,保留接口演进点。
try {
const listResp = await get('/api/orders', { biz: 'sale', type: 'out', startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 })
const list = (listResp && (listResp.list || listResp)) || []
const map = new Map()
let totalSales = 0
for (const it of list) {
const name = it.customerName || '未知客户'
const amount = Number(it.amount || 0)
totalSales += amount
if (!map.has(name)) map.set(name, { name, sales: 0, cost: 0, profit: 0 })
const row = map.get(name)
row.sales += amount
}
const rows = Array.from(map.values()).map(r => ({ ...r, profit: r.sales - r.cost }))
const total = { sales: totalSales, cost: 0, profit: totalSales }
this.rows = rows
this.total = total
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
},
async loadByProduct() {
try {
const listResp = await get('/api/orders', { biz: 'sale', type: 'out', startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 })
const list = (listResp && (listResp.list || listResp)) || []
// 订单详情接口包含明细,逐单补拉详情聚合(规模较小时可接受;后端如提供汇总接口可替换)
const agg = new Map()
for (const it of list) {
try {
const d = await get(`/api/orders/${it.id}`)
const items = d && d.items || []
for (const m of items) {
const key = String(m.productId || m.name)
if (!agg.has(key)) agg.set(key, { name: m.name || ('#'+key), sales: 0, cost: 0, profit: 0 })
const row = agg.get(key)
const sales = Number(m.amount || 0)
// 近似成本:缺后端返回进价,暂以 0待后端扩展返回 purchasePrice
row.sales += sales
}
} catch(_) {}
}
const rows = Array.from(agg.values()).map(r => ({ ...r, profit: r.sales - r.cost }))
const totalSales = rows.reduce((s, r) => s + r.sales, 0)
this.rows = rows
this.total = { sales: totalSales, cost: 0, profit: totalSales }
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
}
},
async loadPurchaseBySupplier() {
try {
const listResp = await get('/api/purchase-orders', { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 })
const list = (listResp && (listResp.list || listResp)) || []
const map = new Map(); let total = 0
for (const it of list) {
const name = it.supplierName || '未知供应商'
const amount = Number(it.amount || 0)
total += amount
if (!map.has(name)) map.set(name, { name, sales: 0, cost: 0, profit: 0 })
const row = map.get(name)
// 在进货统计语境里sales 用来展示“进货额”cost/profit 保持 0
row.sales += amount
}
this.rows = Array.from(map.values())
this.total = { sales: total, cost: 0, profit: 0 }
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
async loadPurchaseByProduct() {
try {
const listResp = await get('/api/purchase-orders', { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 })
const list = (listResp && (listResp.list || listResp)) || []
const agg = new Map()
for (const it of list) {
try {
const d = await get(`/api/purchase-orders/${it.id}`)
for (const m of (d?.items || [])) {
const key = String(m.productId || m.name)
if (!agg.has(key)) agg.set(key, { name: m.name || ('#'+key), sales: 0, cost: 0, profit: 0 })
const row = agg.get(key)
row.sales += Number(m.amount || 0) // 这里的 sales 表示“进货额”
}
} catch(_){}
}
const rows = Array.from(agg.values())
const total = rows.reduce((s, r)=> s + r.sales, 0)
this.rows = rows; this.total = { sales: total, cost: 0, profit: 0 }
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
async loadInventoryByQty() {
try {
const resp = await get('/api/inventories/logs', { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 })
const list = (resp && (resp.list || resp)) || []
const map = new Map(); let totalQty = 0
for (const it of list) {
const key = it.productId || '未知'
if (!map.has(key)) map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 })
const row = map.get(key)
const q = Number(it.qtyDelta || 0)
row.sales += q
totalQty += q
}
this.rows = Array.from(map.values())
this.total = { sales: totalQty, cost: 0, profit: 0 }
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
async loadInventoryByAmount() {
try {
const resp = await get('/api/inventories/logs', { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 })
const list = (resp && (resp.list || resp)) || []
const map = new Map(); let totalAmt = 0
for (const it of list) {
const key = it.productId || '未知'
if (!map.has(key)) map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 })
const row = map.get(key)
const a = Number(it.amount || it.amountDelta || 0)
row.sales += a
totalAmt += a
}
this.rows = Array.from(map.values())
this.total = { sales: totalAmt, cost: 0, profit: 0 }
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
async loadAR() {
// 读取客户列表含 receivable 字段,作为对账口径(期末=期初+增加-收回-抹零);后端如提供期间变动接口再替换
try {
const res = await get('/api/customers', { page: 1, size: 100, debtOnly: false })
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
const rows = list.map(c => ({ name: c.name, sales: Number(c.receivable || 0), cost: 0, profit: 0 }))
const total = rows.reduce((s, r)=> s + r.sales, 0)
this.rows = rows; this.total = { sales: total, cost: 0, profit: 0 }
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
},
async loadAP() {
// 供应商暂未返回应付字段先展示总览为0并提示后端扩展遵循“不开假数据”
try {
const res = await get('/api/suppliers', { page: 1, size: 100 })
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
const rows = list.map(s => ({ name: s.name, sales: Number(s.apPayable || 0), cost: 0, profit: 0 }))
const total = rows.reduce((s, r)=> s + r.sales, 0)
this.rows = rows; this.total = { sales: total, cost: 0, profit: 0 }
} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }
}
}
}
</script>
<style>
.report { padding: 20rpx; }
.modes { display: flex; gap: 12rpx; margin-bottom: 14rpx; }
.mode-tab { flex: 1; text-align: center; padding: 16rpx 0; border-radius: 999rpx; background: #f4f4f4; color: #666; border: 1rpx solid #e9e9e9; }
.mode-tab.active { background: #1aad19; color: #fff; border-color: #1aad19; font-weight: 700; }
.toolbar { display: flex; align-items: center; gap: 8rpx; background: #fff; padding: 14rpx 16rpx; border-radius: 12rpx; }
.date { padding: 10rpx 16rpx; border: 1rpx solid #eee; border-radius: 8rpx; }
.tabs { display: flex; gap: 16rpx; margin-top: 14rpx; }
.tab { padding: 12rpx 18rpx; border-radius: 999rpx; background: #f4f4f4; color: #666; }
.tab.active { background: #1aad19; color: #fff; }
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-top: 14rpx; }
.summary .item { background: #fff; border-radius: 12rpx; padding: 16rpx; }
.summary .label { font-size: 22rpx; color: #888; }
.summary .value { display: block; margin-top: 8rpx; font-weight: 700; color: #333; }
.card { margin-top: 16rpx; background: #fff; border-radius: 12rpx; padding: 16rpx; }
.row-head { display: flex; align-items: center; gap: 12rpx; }
.thumb { width: 72rpx; height: 72rpx; border-radius: 8rpx; background: #f2f2f2; }
.title { font-size: 28rpx; font-weight: 700; }
.row-body { margin-top: 10rpx; color: #666; }
</style>

View File

@@ -1 +1 @@
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\r\n","import App from './App'\r\n\r\n// #ifndef VUE3\r\nimport Vue from 'vue'\r\nimport './uni.promisify.adaptor'\r\nVue.config.productionTip = false\r\nApp.mpType = 'app'\r\nconst app = new Vue({\r\n ...App\r\n})\r\napp.$mount()\r\n// #endif\r\n\r\n// #ifdef VUE3\r\nimport { createSSRApp } from 'vue'\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n return {\r\n app\r\n }\r\n}\r\n// #endif\r\n\r\n// 规范化 WebSocket 关闭码(仅微信小程序)\r\n// #ifdef MP-WEIXIN\r\nif (typeof uni !== 'undefined' && typeof uni.connectSocket === 'function') {\r\n const _connectSocket = uni.connectSocket\r\n uni.connectSocket = function(options) {\r\n const task = _connectSocket.call(this, options)\r\n if (task && typeof task.close === 'function') {\r\n const _close = task.close\r\n task.close = function(params = {}) {\r\n if (params && typeof params === 'object') {\r\n const codeNum = Number(params.code)\r\n const isValid = codeNum === 1000 || (codeNum >= 3000 && codeNum <= 4999)\r\n if (!isValid) {\r\n params.code = 1000\r\n if (!params.reason) params.reason = 'normalized from invalid close code'\r\n }\r\n }\r\n return _close.call(this, params)\r\n }\r\n }\r\n return task\r\n }\r\n}\r\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;;;;;;;;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;AAKA,IAAI,OAAOF,cAAG,UAAK,eAAe,OAAOA,cAAAA,MAAI,kBAAkB,YAAY;AACzE,QAAM,iBAAiBA,cAAAA,MAAI;AAC3BA,sBAAI,gBAAgB,SAAS,SAAS;AACpC,UAAM,OAAO,eAAe,KAAK,MAAM,OAAO;AAC9C,QAAI,QAAQ,OAAO,KAAK,UAAU,YAAY;AAC5C,YAAM,SAAS,KAAK;AACpB,WAAK,QAAQ,SAAS,SAAS,IAAI;AACjC,YAAI,UAAU,OAAO,WAAW,UAAU;AACxC,gBAAM,UAAU,OAAO,OAAO,IAAI;AAClC,gBAAM,UAAU,YAAY,OAAS,WAAW,OAAQ,WAAW;AACnE,cAAI,CAAC,SAAS;AACZ,mBAAO,OAAO;AACd,gBAAI,CAAC,OAAO;AAAQ,qBAAO,SAAS;AAAA,UACrC;AAAA,QACF;AACD,eAAO,OAAO,KAAK,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AACD,WAAO;AAAA,EACR;AACH;;;"}
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script>\r\n\texport default {\r\n\t\tonLaunch: function() {\r\n\t\t\tconsole.log('App Launch')\r\n\t\t},\r\n\t\tonShow: function() {\r\n\t\t\tconsole.log('App Show')\r\n\t\t},\r\n\t\tonHide: function() {\r\n\t\t\tconsole.log('App Hide')\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t/*每个页面公共css */\r\n</style>\r\n","import App from './App'\r\n\r\n// #ifndef VUE3\r\nimport Vue from 'vue'\r\nimport './uni.promisify.adaptor'\r\nVue.config.productionTip = false\r\nApp.mpType = 'app'\r\nconst app = new Vue({\r\n ...App\r\n})\r\napp.$mount()\r\n// #endif\r\n\r\n// #ifdef VUE3\r\nimport { createSSRApp } from 'vue'\r\nexport function createApp() {\r\n const app = createSSRApp(App)\r\n return {\r\n app\r\n }\r\n}\r\n// #endif\r\n\r\n// 规范化 WebSocket 关闭码(仅微信小程序)\r\n// #ifdef MP-WEIXIN\r\nif (typeof uni !== 'undefined' && typeof uni.connectSocket === 'function') {\r\n const _connectSocket = uni.connectSocket\r\n uni.connectSocket = function(options) {\r\n const task = _connectSocket.call(this, options)\r\n if (task && typeof task.close === 'function') {\r\n const _close = task.close\r\n task.close = function(params = {}) {\r\n if (params && typeof params === 'object') {\r\n const codeNum = Number(params.code)\r\n const isValid = codeNum === 1000 || (codeNum >= 3000 && codeNum <= 4999)\r\n if (!isValid) {\r\n params.code = 1000\r\n if (!params.reason) params.reason = 'normalized from invalid close code'\r\n }\r\n }\r\n return _close.call(this, params)\r\n }\r\n }\r\n return task\r\n }\r\n}\r\n// #endif"],"names":["uni","createSSRApp","App"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AACC,MAAK,YAAU;AAAA,EACd,UAAU,WAAW;AACpBA,kBAAAA,MAAA,MAAA,OAAA,gBAAY,YAAY;AAAA,EACxB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,gBAAA,UAAU;AAAA,EACtB;AAAA,EACD,QAAQ,WAAW;AAClBA,kBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,EACvB;AACD;ACIM,SAAS,YAAY;AAC1B,QAAM,MAAMC,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;AAKA,IAAI,OAAOF,cAAG,UAAK,eAAe,OAAOA,cAAAA,MAAI,kBAAkB,YAAY;AACzE,QAAM,iBAAiBA,cAAAA,MAAI;AAC3BA,sBAAI,gBAAgB,SAAS,SAAS;AACpC,UAAM,OAAO,eAAe,KAAK,MAAM,OAAO;AAC9C,QAAI,QAAQ,OAAO,KAAK,UAAU,YAAY;AAC5C,YAAM,SAAS,KAAK;AACpB,WAAK,QAAQ,SAAS,SAAS,IAAI;AACjC,YAAI,UAAU,OAAO,WAAW,UAAU;AACxC,gBAAM,UAAU,OAAO,OAAO,IAAI;AAClC,gBAAM,UAAU,YAAY,OAAS,WAAW,OAAQ,WAAW;AACnE,cAAI,CAAC,SAAS;AACZ,mBAAO,OAAO;AACd,gBAAI,CAAC,OAAO;AAAQ,qBAAO,SAAS;AAAA,UACrC;AAAA,QACF;AACD,eAAO,OAAO,KAAK,MAAM,MAAM;AAAA,MAChC;AAAA,IACF;AACD,WAAO;AAAA,EACR;AACH;;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"select.js","sources":["pages/account/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvYWNjb3VudC9zZWxlY3QudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"a in accounts\" :key=\"a.id\" @click=\"select(a)\">\r\n\t\t\t\t<view class=\"name\">{{ a.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ typeLabel(a.type) }} · 余额:{{ a.balance?.toFixed ? a.balance.toFixed(2) : a.balance }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\tconst TYPE_MAP = { cash: '现金', bank: '银行', alipay: '支付宝', wechat: '微信', other: '其他' }\r\n\texport default {\r\n\t\tdata() { return { accounts: [] } },\r\n\t\tasync onLoad() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/accounts')\r\n\t\t\t\tthis.accounts = Array.isArray(res) ? res : (res?.list || [])\r\n\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tselect(a) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm) {\r\n\t\t\t\t\topener.$vm.selectedAccountId = a.id\r\n\t\t\t\t\topener.$vm.selectedAccountName = a.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\t\t},\r\n\t\t\ttypeLabel(t) { return TYPE_MAP[t] || t }\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n</style>\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/account/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAaC,MAAM,WAAW,EAAE,MAAM,MAAM,MAAM,MAAM,QAAQ,OAAO,QAAQ,MAAM,OAAO,KAAK;AACpF,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,UAAU,CAAG,EAAA;AAAA,EAAG;AAAA,EAClC,MAAM,SAAS;AACd,QAAI;AACH,YAAM,MAAM,MAAMA,YAAG,IAAC,eAAe;AACrC,WAAK,WAAW,MAAM,QAAQ,GAAG,IAAI,OAAO,2BAAK,SAAQ;aAClD,GAAG;AAAEC,oBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,IAAE;AAAA,EAC5D;AAAA,EACD,SAAS;AAAA,IACR,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,oBAAoB,EAAE;AACjC,eAAO,IAAI,sBAAsB,EAAE;AAAA,MACpC;AACAA,oBAAAA,MAAI,aAAa;AAAA,IACjB;AAAA,IACD,UAAU,GAAG;AAAE,aAAO,SAAS,CAAC,KAAK;AAAA,IAAE;AAAA,EACxC;AACD;;;;;;;;;;;;;;;;AChCD,GAAG,WAAW,eAAe;"}
{"version":3,"file":"select.js","sources":["pages/account/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvYWNjb3VudC9zZWxlY3QudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"a in accounts\" :key=\"a.id\" @click=\"select(a)\">\r\n\t\t\t\t<view class=\"name\">{{ a.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ typeLabel(a.type) }} · 余额:{{ a.balance?.toFixed ? a.balance.toFixed(2) : a.balance }}</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t\t<view class=\"fab\" @click=\"create\"></view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { get } from '../../common/http.js'\r\n\tconst TYPE_MAP = { cash: '现金', bank: '银行', alipay: '支付宝', wechat: '微信', other: '其他' }\r\n\texport default {\r\n\t\tdata() { return { accounts: [], mode: 'view' } },\r\n\t\tasync onLoad(q) {\r\n\t\t\tthis.mode = (q && q.mode) || 'view'\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/accounts')\r\n\t\t\t\tthis.accounts = Array.isArray(res) ? res : (res?.list || [])\r\n\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tselect(a) {\r\n\t\t\t\tif (this.mode === 'pick') {\r\n\t\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\t\tif (opener && opener.$vm) {\r\n\t\t\t\t\t\topener.$vm.selectedAccountId = a.id\r\n\t\t\t\t\t\topener.$vm.selectedAccountName = a.name\r\n\t\t\t\t\t}\r\n\t\t\t\t\tuni.navigateBack()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tuni.navigateTo({ url: `/pages/account/ledger?id=${a.id}` })\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tcreate() { uni.navigateTo({ url: '/pages/account/form' }) },\r\n\t\t\ttypeLabel(t) { return TYPE_MAP[t] || t }\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style>\r\n\t.page { display:flex; flex-direction: column; height: 100vh; }\r\n\t.list { flex:1; }\r\n\t.item { padding: 20rpx 24rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n\t.name { color:#333; margin-bottom: 6rpx; }\r\n\t.meta { color:#888; font-size: 24rpx; }\r\n\t.fab { position: fixed; right: 32rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; border-radius: 50%; background:#3c9cff; color:#fff; display:flex; align-items:center; justify-content:center; font-size: 52rpx; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.18); }\r\n</style>\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/account/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAcC,MAAM,WAAW,EAAE,MAAM,MAAM,MAAM,MAAM,QAAQ,OAAO,QAAQ,MAAM,OAAO,KAAK;AACpF,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,UAAU,CAAA,GAAI,MAAM;EAAU;AAAA,EAChD,MAAM,OAAO,GAAG;AACf,SAAK,OAAQ,KAAK,EAAE,QAAS;AAC7B,QAAI;AACH,YAAM,MAAM,MAAMA,YAAG,IAAC,eAAe;AACrC,WAAK,WAAW,MAAM,QAAQ,GAAG,IAAI,OAAO,2BAAK,SAAQ;aAClD,GAAG;AAAEC,oBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,IAAE;AAAA,EAC5D;AAAA,EACD,SAAS;AAAA,IACR,OAAO,GAAG;AACT,UAAI,KAAK,SAAS,QAAQ;AACzB,cAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,YAAI,UAAU,OAAO,KAAK;AACzB,iBAAO,IAAI,oBAAoB,EAAE;AACjC,iBAAO,IAAI,sBAAsB,EAAE;AAAA,QACpC;AACAA,sBAAAA,MAAI,aAAa;AAAA,aACX;AACNA,4BAAI,WAAW,EAAE,KAAK,4BAA4B,EAAE,EAAE,IAAI;AAAA,MAC3D;AAAA,IACA;AAAA,IACD,SAAS;AAAEA,oBAAAA,MAAI,WAAW,EAAE,KAAK,sBAAoB,CAAG;AAAA,IAAG;AAAA,IAC3D,UAAU,GAAG;AAAE,aAAO,SAAS,CAAC,KAAK;AAAA,IAAE;AAAA,EACxC;AACD;;;;;;;;;;;;;;;;;ACvCD,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"about.js","sources":["pages/my/about.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvbXkvYWJvdXQudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"about\">\r\n\t\t<view class=\"hero\">\r\n\t\t\t<image class=\"logo\" src=\"/static/logo.png\" mode=\"aspectFit\" />\r\n\t\t\t<text class=\"title\">五金配件管家</text>\r\n\t\t\t<text class=\"subtitle\">专注小微门店的极简进销存</text>\r\n\t\t</view>\r\n\r\n\t\t<view class=\"card\">\r\n\t\t\t<view class=\"row\">\r\n\t\t\t\t<text class=\"label\">版本</text>\r\n\t\t\t\t<text class=\"value\">1.0.0</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"row\">\r\n\t\t\t\t<text class=\"label\">隐私协议</text>\r\n\t\t\t\t<text class=\"link\" @click=\"openPolicy\">查看</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"row\">\r\n\t\t\t\t<text class=\"label\">用户协议</text>\r\n\t\t\t\t<text class=\"link\" @click=\"openTerms\">查看</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"row\">\r\n\t\t\t\t<text class=\"label\">个人信息安全投诉</text>\r\n\t\t\t\t<text class=\"link\" @click=\"openComplaint\">提交</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nexport default {\r\n\tmethods: {\r\n\t\topenPolicy() {\r\n\t\t\tuni.showModal({ title: '隐私协议', content: '隐私协议(静态占位)', showCancel: false })\r\n\t\t},\r\n\t\topenTerms() {\r\n\t\t\tuni.showModal({ title: '用户协议', content: '用户协议(静态占位)', showCancel: false })\r\n\t\t},\r\n\t\topenComplaint() {\r\n\t\t\tuni.showToast({ title: '暂未开通', icon: 'none' })\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.about { padding: 24rpx; }\r\n.hero { padding: 32rpx 24rpx; display: flex; flex-direction: column; align-items: center; gap: 10rpx; }\r\n.logo { width: 160rpx; height: 160rpx; border-radius: 32rpx; }\r\n.title { margin-top: 8rpx; font-size: 36rpx; font-weight: 800; color: #333; }\r\n.subtitle { font-size: 26rpx; color: #888; }\r\n.card { margin-top: 18rpx; background: #fff; border-radius: 16rpx; overflow: hidden; }\r\n.row { display: flex; align-items: center; padding: 24rpx; border-top: 1rpx solid #f2f2f2; }\r\n.label { color: #666; }\r\n.value { margin-left: auto; color: #333; }\r\n.link { margin-left: auto; color: #1aad19; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/my/about.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni"],"mappings":";;;AA8BA,MAAK,YAAU;AAAA,EACd,SAAS;AAAA,IACR,aAAa;AACZA,0BAAI,UAAU,EAAE,OAAO,QAAQ,SAAS,cAAc,YAAY,OAAO;AAAA,IACzE;AAAA,IACD,YAAY;AACXA,0BAAI,UAAU,EAAE,OAAO,QAAQ,SAAS,cAAc,YAAY,OAAO;AAAA,IACzE;AAAA,IACD,gBAAgB;AACfA,oBAAG,MAAC,UAAU,EAAE,OAAO,QAAQ,MAAM,QAAQ;AAAA,IAC9C;AAAA,EACD;AACD;;;;;;;;;;ACzCA,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"entry.js","sources":["pages/report/entry.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcmVwb3J0L2VudHJ5LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"entry\">\r\n\t\t<view class=\"section\">\r\n\t\t\t<view class=\"section-title\">资金报表</view>\r\n\t\t\t<view class=\"grid\">\r\n\t\t\t\t<view class=\"btn\" @click=\"go('sale','customer')\">利润统计</view>\r\n\t\t\t\t<view class=\"btn\" @click=\"go('sale','product')\">营业员统计</view>\r\n\t\t\t\t<view class=\"btn\" @click=\"go('sale','customer')\">经营业绩</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<view class=\"section\">\r\n\t\t\t<view class=\"section-title\">进销存报表</view>\r\n\t\t\t<view class=\"grid\">\r\n\t\t\t\t<view class=\"btn\" @click=\"go('sale','customer')\">销售统计</view>\r\n\t\t\t\t<view class=\"btn\" @click=\"go('purchase','supplier')\">进货统计</view>\r\n\t\t\t\t<view class=\"btn\" @click=\"go('inventory','qty')\">库存统计</view>\r\n\t\t\t\t<view class=\"btn\" @click=\"go('arap','ar')\">应收对账单</view>\r\n\t\t\t\t<view class=\"btn\" @click=\"go('arap','ap')\">应付对账单</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nexport default {\r\n\tmethods: {\r\n\t\tgo(mode, dim) {\r\n\t\t\tconst q = `mode=${encodeURIComponent(mode)}&dim=${encodeURIComponent(dim||'')}`\r\n\t\t\tuni.navigateTo({ url: `/pages/report/index?${q}` })\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.entry { padding: 20rpx; }\r\n.section { margin-bottom: 24rpx; }\r\n.section-title { background:#f1f4f8; color:#6a7a8a; padding: 14rpx 16rpx; border-radius: 12rpx; font-weight: 700; }\r\n.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18rpx; padding: 18rpx 6rpx 0; }\r\n.btn { text-align: center; padding: 18rpx 8rpx; border: 1rpx solid #e5e9ef; border-radius: 12rpx; color:#333; background: #fff; }\r\n.btn:active { background: #f6f8fa; }\r\n</style>\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/report/entry.vue'\nwx.createPage(MiniProgramPage)"],"names":["uni"],"mappings":";;AAwBA,MAAK,YAAU;AAAA,EACd,SAAS;AAAA,IACR,GAAG,MAAM,KAAK;AACb,YAAM,IAAI,QAAQ,mBAAmB,IAAI,CAAC,QAAQ,mBAAmB,OAAK,EAAE,CAAC;AAC7EA,oBAAG,MAAC,WAAW,EAAE,KAAK,uBAAuB,CAAC,IAAI;AAAA,IACnD;AAAA,EACD;AACD;;;;;;;;;;;;;;AC9BA,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,13 @@ if (!Math) {
"./pages/supplier/select.js";
"./pages/supplier/form.js";
"./pages/account/select.js";
"./pages/account/ledger.js";
"./pages/account/form.js";
"./pages/detail/index.js";
"./pages/my/index.js";
"./pages/my/about.js";
"./pages/report/index.js";
"./pages/report/entry.js";
}
const _sfc_main = {
onLaunch: function() {

View File

@@ -14,7 +14,13 @@
"pages/supplier/select",
"pages/supplier/form",
"pages/account/select",
"pages/detail/index"
"pages/account/ledger",
"pages/account/form",
"pages/detail/index",
"pages/my/index",
"pages/my/about",
"pages/report/index",
"pages/report/entry"
],
"window": {
"navigationBarTextStyle": "black",

View File

@@ -5219,6 +5219,10 @@ function vFor(source, renderItem) {
}
return ret;
}
function setRef(ref2, id, opts = {}) {
const { $templateRefs } = getCurrentInstance();
$templateRefs.push({ i: id, r: ref2, k: opts.k, f: opts.f });
}
function withModelModifiers(fn, { number, trim }, isComponent = false) {
if (isComponent) {
return (...args) => {
@@ -5247,6 +5251,7 @@ const e = (target, ...sources) => extend(target, ...sources);
const n = (value) => normalizeClass(value);
const t = (val) => toDisplayString(val);
const p = (props) => renderProps(props);
const sr = (ref2, id, opts) => setRef(ref2, id, opts);
const m = (fn, modifiers, isComponent = false) => withModelModifiers(fn, modifiers, isComponent);
function createApp$1(rootComponent, rootProps = null) {
rootComponent && (rootComponent.mpType = "app");
@@ -7069,7 +7074,7 @@ function isConsoleWritable() {
function initRuntimeSocketService() {
const hosts = "198.18.0.1,192.168.31.192,127.0.0.1";
const port = "8090";
const id = "mp-weixin_eSBEHk";
const id = "mp-weixin_HpGDB1";
const lazy = typeof swan !== "undefined";
let restoreError = lazy ? () => {
} : initOnError();
@@ -8026,5 +8031,6 @@ exports.o = o;
exports.p = p;
exports.resolveComponent = resolveComponent;
exports.s = s;
exports.sr = sr;
exports.t = t;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/vendor.js.map

View File

@@ -0,0 +1,102 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
id: null,
form: { name: "", type: "cash", bankName: "", bankAccount: "", openingBalance: "" },
showType: false,
types: [
{ key: "cash", name: "现金" },
{ key: "bank", name: "银行存款" },
{ key: "wechat", name: "微信" },
{ key: "alipay", name: "支付宝" },
{ key: "other", name: "其他" }
]
};
},
onLoad(q) {
this.id = q && q.id ? Number(q.id) : null;
if (this.id)
this.load();
},
methods: {
typeLabel(t) {
const m = { cash: "现金", bank: "银行存款", wechat: "微信", alipay: "支付宝", other: "其他" };
return m[t] || t;
},
async load() {
try {
const list = await common_http.get("/api/accounts");
const a = (Array.isArray(list) ? list : (list == null ? void 0 : list.list) || []).find((x) => x.id == this.id);
if (a) {
this.form = { name: a.name, type: a.type, bankName: a.bank_name || a.bankName || "", bankAccount: a.bank_account || a.bankAccount || "", openingBalance: "" };
}
} catch (e) {
}
},
async save() {
if (!this.form.name) {
common_vendor.index.showToast({ title: "请输入名称", icon: "none" });
return;
}
try {
const body = { ...this.form, openingBalance: Number(this.form.openingBalance || 0) };
if (this.id)
await common_http.put(`/api/accounts/${this.id}`, body);
else
await common_http.post("/api/accounts", { ...body, status: 1 });
common_vendor.index.showToast({ title: "已保存", icon: "success" });
setTimeout(() => common_vendor.index.navigateBack(), 300);
} catch (e) {
common_vendor.index.showToast({ title: "保存失败", icon: "none" });
}
}
}
};
if (!Array) {
const _component_uni_popup = common_vendor.resolveComponent("uni-popup");
_component_uni_popup();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.form.name,
b: common_vendor.o(($event) => $data.form.name = $event.detail.value),
c: common_vendor.t($options.typeLabel($data.form.type)),
d: common_vendor.o(($event) => $data.showType = true),
e: $data.form.type === "bank"
}, $data.form.type === "bank" ? {
f: $data.form.bankName,
g: common_vendor.o(($event) => $data.form.bankName = $event.detail.value)
} : {}, {
h: $data.form.type === "bank"
}, $data.form.type === "bank" ? {
i: $data.form.bankAccount,
j: common_vendor.o(($event) => $data.form.bankAccount = $event.detail.value)
} : {}, {
k: $data.form.openingBalance,
l: common_vendor.o(($event) => $data.form.openingBalance = $event.detail.value),
m: common_vendor.o((...args) => $options.save && $options.save(...args)),
n: common_vendor.f($data.types, (t, k0, i0) => {
return {
a: common_vendor.t(t.name),
b: t.key,
c: common_vendor.o(($event) => {
$data.form.type = t.key;
$data.showType = false;
}, t.key)
};
}),
o: common_vendor.o(($event) => $data.showType = false),
p: common_vendor.sr("popup", "4430e2e8-0"),
q: common_vendor.o(($event) => $data.showType = $event),
r: common_vendor.p({
type: "bottom",
modelValue: $data.showType
})
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/account/form.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "新增/编辑账户",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="form"><view class="field"><text class="label">账户名称</text><input class="input" placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="field" bindtap="{{d}}"><text class="label">账户类型</text><text class="value">{{c}}</text></view><view wx:if="{{e}}" class="field"><text class="label">银行名称</text><input class="input" placeholder="选填" value="{{f}}" bindinput="{{g}}"/></view><view wx:if="{{h}}" class="field"><text class="label">银行账号</text><input class="input" placeholder="选填" value="{{i}}" bindinput="{{j}}"/></view><view class="field"><text class="label">当前余额</text><input class="input" type="number" placeholder="0.00" value="{{k}}" bindinput="{{l}}"/></view></view><view class="actions"><button class="primary" bindtap="{{m}}">保存</button></view><uni-popup wx:if="{{r}}" class="r" u-s="{{['d']}}" u-r="popup" u-i="4430e2e8-0" bind:__l="__l" bindupdateModelValue="{{q}}" u-p="{{r}}"><view class="sheet"><view wx:for="{{n}}" wx:for-item="t" wx:key="b" class="sheet-item" bindtap="{{t.c}}">{{t.a}}</view><view class="sheet-cancel" bindtap="{{o}}">取消</view></view></uni-popup></view>

View File

@@ -0,0 +1,23 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.form { background:#fff;
}
.field { display:flex; align-items:center; justify-content: space-between; padding: 18rpx 20rpx; border-bottom:1rpx solid #f3f3f3;
}
.label { color:#666;
}
.input { flex:1; text-align: right; color:#333;
}
.value { color:#333;
}
.actions { margin-top: 20rpx; padding: 0 20rpx;
}
.primary { width: 100%; background: #3c9cff; color:#fff; border-radius: 8rpx; padding: 22rpx 0;
}
.sheet { background:#fff;
}
.sheet-item { padding: 26rpx; text-align:center; border-bottom:1rpx solid #f2f2f2;
}
.sheet-cancel { padding: 26rpx; text-align:center; color:#666;
}

View File

@@ -0,0 +1,83 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { accountId: null, startDate: "", endDate: "", list: [], opening: 0, income: 0, expense: 0, ending: 0 };
},
onLoad(query) {
this.accountId = Number(query && query.id);
this.quickInit();
this.load();
},
methods: {
quickInit() {
const now = /* @__PURE__ */ new Date();
const y = now.getFullYear(), m = now.getMonth() + 1;
this.startDate = `${y}-${String(m).padStart(2, "0")}-01`;
const lastDay = new Date(y, m, 0).getDate();
this.endDate = `${y}-${String(m).padStart(2, "0")}-${String(lastDay).padStart(2, "0")}`;
},
async load(page = 1, size = 50) {
try {
const res = await common_http.get(`/api/accounts/${this.accountId}/ledger`, { startDate: this.startDate, endDate: this.endDate, page, size });
this.list = res && res.list || [];
this.opening = Number(res && res.opening || 0);
this.income = Number(res && res.income || 0);
this.expense = Number(res && res.expense || 0);
this.ending = Number(res && res.ending || 0);
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
fmt(v) {
return (typeof v === "number" ? v : Number(v || 0)).toFixed(2);
},
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())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
} catch (e) {
return s;
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.t($data.startDate || "—"),
b: $data.startDate,
c: common_vendor.o((e) => {
$data.startDate = e.detail.value;
$options.load();
}),
d: common_vendor.t($data.endDate || "—"),
e: $data.endDate,
f: common_vendor.o((e) => {
$data.endDate = e.detail.value;
$options.load();
}),
g: common_vendor.t($options.fmt($data.income)),
h: common_vendor.t($options.fmt($data.expense)),
i: common_vendor.t($options.fmt($data.opening)),
j: common_vendor.t($options.fmt($data.ending)),
k: common_vendor.f($data.list, (it, k0, i0) => {
return {
a: common_vendor.t(it.src === "other" ? it.category || "其他" : it.remark || "收付款"),
b: common_vendor.t(it.direction === "in" ? "+" : "-"),
c: common_vendor.t($options.fmt(it.amount)),
d: it.direction === "in" ? 1 : "",
e: it.direction === "out" ? 1 : "",
f: common_vendor.t($options.formatDate(it.tx_time || it.txTime)),
g: common_vendor.t(it.remark || "-"),
h: it.id
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/account/ledger.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "账户流水",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="filters"><picker mode="date" value="{{b}}" bindchange="{{c}}"><view class="field"><text class="label">开始</text><text class="value">{{a}}</text></view></picker><picker mode="date" value="{{e}}" bindchange="{{f}}"><view class="field"><text class="label">结束</text><text class="value">{{d}}</text></view></picker></view><view class="summary"><view class="sum-item"><text class="k">收入</text><text class="v">{{g}}</text></view><view class="sum-item"><text class="k">支出</text><text class="v">{{h}}</text></view><view class="sum-item"><text class="k">期初</text><text class="v">{{i}}</text></view><view class="sum-item"><text class="k">期末</text><text class="v">{{j}}</text></view></view><scroll-view scroll-y class="list"><view wx:for="{{k}}" wx:for-item="it" wx:key="h" class="item"><view class="row"><text class="title">{{it.a}}</text><text class="{{['amount', it.d && 'in', it.e && 'out']}}">{{it.b}}{{it.c}}</text></view><view class="meta">{{it.f}} · {{it.g}}</view></view></scroll-view></view>

View File

@@ -0,0 +1,35 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.filters { display:flex; gap: 16rpx; padding: 16rpx; background:#fff;
}
.field { display:flex; justify-content: space-between; align-items:center; padding: 16rpx; border:1rpx solid #eee; border-radius: 12rpx; min-width: 300rpx;
}
.label { color:#666;
}
.value { color:#333;
}
.summary { display:grid; grid-template-columns: repeat(4,1fr); gap: 12rpx; padding: 12rpx 16rpx; background:#fff; border-top:1rpx solid #f1f1f1; border-bottom:1rpx solid #f1f1f1;
}
.sum-item { padding: 12rpx; text-align:center;
}
.k { display:block; color:#888; font-size: 24rpx;
}
.v { display:block; margin-top:6rpx; font-weight:700; color:#333;
}
.list { flex:1;
}
.item { padding: 18rpx 16rpx; border-bottom:1rpx solid #f4f4f4; background:#fff;
}
.row { display:flex; align-items:center; justify-content: space-between; margin-bottom: 6rpx;
}
.title { color:#333;
}
.amount { font-weight:700;
}
.amount.in { color:#2a9d8f;
}
.amount.out { color:#d35b5b;
}
.meta { color:#999; font-size: 24rpx;
}

View File

@@ -4,9 +4,10 @@ const common_http = require("../../common/http.js");
const TYPE_MAP = { cash: "现金", bank: "银行", alipay: "支付宝", wechat: "微信", other: "其他" };
const _sfc_main = {
data() {
return { accounts: [] };
return { accounts: [], mode: "view" };
},
async onLoad() {
async onLoad(q) {
this.mode = q && q.mode || "view";
try {
const res = await common_http.get("/api/accounts");
this.accounts = Array.isArray(res) ? res : (res == null ? void 0 : res.list) || [];
@@ -16,12 +17,19 @@ const _sfc_main = {
},
methods: {
select(a) {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) {
opener.$vm.selectedAccountId = a.id;
opener.$vm.selectedAccountName = a.name;
if (this.mode === "pick") {
const opener = getCurrentPages()[getCurrentPages().length - 2];
if (opener && opener.$vm) {
opener.$vm.selectedAccountId = a.id;
opener.$vm.selectedAccountName = a.name;
}
common_vendor.index.navigateBack();
} else {
common_vendor.index.navigateTo({ url: `/pages/account/ledger?id=${a.id}` });
}
common_vendor.index.navigateBack();
},
create() {
common_vendor.index.navigateTo({ url: "/pages/account/form" });
},
typeLabel(t) {
return TYPE_MAP[t] || t;
@@ -39,7 +47,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
d: a.id,
e: common_vendor.o(($event) => $options.select(a), a.id)
};
})
}),
b: common_vendor.o((...args) => $options.create && $options.create(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="page"><scroll-view scroll-y class="list"><view wx:for="{{a}}" wx:for-item="a" wx:key="d" class="item" bindtap="{{a.e}}"><view class="name">{{a.a}}</view><view class="meta">{{a.b}} · 余额:{{a.c}}</view></view></scroll-view></view>
<view class="page"><scroll-view scroll-y class="list"><view wx:for="{{a}}" wx:for-item="a" wx:key="d" class="item" bindtap="{{a.e}}"><view class="name">{{a.a}}</view><view class="meta">{{a.b}} · 余额:{{a.c}}</view></view></scroll-view><view class="fab" bindtap="{{b}}"></view></view>

View File

@@ -9,3 +9,5 @@
}
.meta { color:#888; font-size: 24rpx;
}
.fab { position: fixed; right: 32rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; border-radius: 50%; background:#3c9cff; color:#fff; display:flex; align-items:center; justify-content:center; font-size: 52rpx; box-shadow: 0 10rpx 20rpx rgba(0,0,0,0.18);
}

View File

@@ -67,6 +67,10 @@ const _sfc_main = {
common_vendor.index.navigateTo({ url: "/pages/customer/select" });
return;
}
if (item.key === "account") {
common_vendor.index.navigateTo({ url: "/pages/account/select" });
return;
}
if (item.key === "supplier") {
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
return;
@@ -83,11 +87,19 @@ const _sfc_main = {
goDetail() {
this.activeTab = "detail";
try {
common_vendor.index.__f__("log", "at pages/index/index.vue:174", "[index] goDetail → /pages/detail/index");
common_vendor.index.__f__("log", "at pages/index/index.vue:179", "[index] goDetail → /pages/detail/index");
} catch (e) {
}
common_vendor.index.navigateTo({ url: "/pages/detail/index" });
},
goReport() {
this.activeTab = "report";
common_vendor.index.navigateTo({ url: "/pages/report/entry" });
},
goMe() {
this.activeTab = "me";
common_vendor.index.navigateTo({ url: "/pages/my/index" });
},
onNoticeTap(n) {
common_vendor.index.showModal({
title: "广告",
@@ -148,9 +160,9 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
q: $data.activeTab === "detail" ? 1 : "",
r: common_vendor.o((...args) => $options.goDetail && $options.goDetail(...args)),
s: $data.activeTab === "report" ? 1 : "",
t: common_vendor.o(($event) => $data.activeTab = "report"),
t: common_vendor.o((...args) => $options.goReport && $options.goReport(...args)),
v: $data.activeTab === "me" ? 1 : "",
w: common_vendor.o(($event) => $data.activeTab = "me")
w: common_vendor.o((...args) => $options.goMe && $options.goMe(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -0,0 +1,27 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_assets = require("../../common/assets.js");
const _sfc_main = {
methods: {
openPolicy() {
common_vendor.index.showModal({ title: "隐私协议", content: "隐私协议(静态占位)", showCancel: false });
},
openTerms() {
common_vendor.index.showModal({ title: "用户协议", content: "用户协议(静态占位)", showCancel: false });
},
openComplaint() {
common_vendor.index.showToast({ title: "暂未开通", icon: "none" });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_assets._imports_0$1,
b: common_vendor.o((...args) => $options.openPolicy && $options.openPolicy(...args)),
c: common_vendor.o((...args) => $options.openTerms && $options.openTerms(...args)),
d: common_vendor.o((...args) => $options.openComplaint && $options.openComplaint(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/my/about.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "关于与协议",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="about"><view class="hero"><image class="logo" src="{{a}}" mode="aspectFit"/><text class="title">五金配件管家</text><text class="subtitle">专注小微门店的极简进销存</text></view><view class="card"><view class="row"><text class="label">版本</text><text class="value">1.0.0</text></view><view class="row"><text class="label">隐私协议</text><text class="link" bindtap="{{b}}">查看</text></view><view class="row"><text class="label">用户协议</text><text class="link" bindtap="{{c}}">查看</text></view><view class="row"><text class="label">个人信息安全投诉</text><text class="link" bindtap="{{d}}">提交</text></view></view></view>

View File

@@ -0,0 +1,21 @@
.about { padding: 24rpx;
}
.hero { padding: 32rpx 24rpx; display: flex; flex-direction: column; align-items: center; gap: 10rpx;
}
.logo { width: 160rpx; height: 160rpx; border-radius: 32rpx;
}
.title { margin-top: 8rpx; font-size: 36rpx; font-weight: 800; color: #333;
}
.subtitle { font-size: 26rpx; color: #888;
}
.card { margin-top: 18rpx; background: #fff; border-radius: 16rpx; overflow: hidden;
}
.row { display: flex; align-items: center; padding: 24rpx; border-top: 1rpx solid #f2f2f2;
}
.label { color: #666;
}
.value { margin-left: auto; color: #333;
}
.link { margin-left: auto; color: #1aad19;
}

View File

@@ -0,0 +1,110 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
avatarUrl: "/static/logo.png",
shopName: "我的店铺",
mobile: ""
};
},
onLoad() {
this.fetchProfile();
},
computed: {
mobileDisplay() {
const m = String(this.mobile || "");
return m.length === 11 ? m.slice(0, 3) + "****" + m.slice(7) : m || "未绑定手机号";
}
},
methods: {
async fetchProfile() {
try {
await common_http.get("/api/dashboard/overview");
} catch (e) {
}
try {
const storeName = common_vendor.index.getStorageSync("SHOP_NAME") || "";
const avatar = common_vendor.index.getStorageSync("USER_AVATAR") || "";
const phone = common_vendor.index.getStorageSync("USER_MOBILE") || "";
if (storeName)
this.shopName = storeName;
if (avatar)
this.avatarUrl = avatar;
this.mobile = phone;
} catch (e) {
}
},
onAvatarError() {
this.avatarUrl = "/static/logo.png";
},
goVip() {
common_vendor.index.showToast({ title: "VIP会员开发中", icon: "none" });
},
goMyOrders() {
common_vendor.index.navigateTo({ url: "/pages/detail/index" });
},
goSupplier() {
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
},
goCustomer() {
common_vendor.index.navigateTo({ url: "/pages/customer/select" });
},
goCustomerQuote() {
common_vendor.index.showToast({ title: "客户报价(开发中)", icon: "none" });
},
goShop() {
common_vendor.index.showToast({ title: "店铺管理(开发中)", icon: "none" });
},
editProfile() {
common_vendor.index.showToast({ title: "账号与安全(开发中)", icon: "none" });
},
goProductSettings() {
common_vendor.index.navigateTo({ url: "/pages/product/settings" });
},
goSystemParams() {
common_vendor.index.showToast({ title: "系统参数(开发中)", icon: "none" });
},
goAbout() {
common_vendor.index.navigateTo({ url: "/pages/my/about" });
},
logout() {
try {
common_vendor.index.removeStorageSync("TOKEN");
common_vendor.index.removeStorageSync("USER_AVATAR");
common_vendor.index.removeStorageSync("USER_NAME");
common_vendor.index.removeStorageSync("USER_MOBILE");
common_vendor.index.removeStorageSync("SHOP_NAME");
common_vendor.index.showToast({ title: "已退出", icon: "none" });
setTimeout(() => {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}, 300);
} catch (e) {
common_vendor.index.reLaunch({ url: "/pages/index/index" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.avatarUrl,
b: common_vendor.o((...args) => $options.onAvatarError && $options.onAvatarError(...args)),
c: common_vendor.t($data.shopName),
d: common_vendor.t($options.mobileDisplay),
e: common_vendor.o((...args) => $options.goVip && $options.goVip(...args)),
f: common_vendor.o((...args) => $options.goMyOrders && $options.goMyOrders(...args)),
g: common_vendor.o((...args) => $options.goSupplier && $options.goSupplier(...args)),
h: common_vendor.o((...args) => $options.goCustomer && $options.goCustomer(...args)),
i: common_vendor.o((...args) => $options.goCustomerQuote && $options.goCustomerQuote(...args)),
j: common_vendor.o((...args) => $options.goShop && $options.goShop(...args)),
k: common_vendor.o((...args) => $options.editProfile && $options.editProfile(...args)),
l: common_vendor.o((...args) => $options.goProductSettings && $options.goProductSettings(...args)),
m: common_vendor.o((...args) => $options.goSystemParams && $options.goSystemParams(...args)),
n: common_vendor.o((...args) => $options.goAbout && $options.goAbout(...args)),
o: common_vendor.o((...args) => $options.logout && $options.logout(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/my/index.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "我的",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="me"><view class="card user"><image class="avatar" src="{{a}}" mode="aspectFill" binderror="{{b}}"/><view class="meta"><text class="name">{{c}}</text><text class="phone">{{d}}</text><text class="role">老板</text></view></view><view class="group"><view class="group-title">会员与订单</view><view class="cell" bindtap="{{e}}"><text>VIP会员</text><text class="arrow"></text></view><view class="cell" bindtap="{{f}}"><text>我的订单</text><text class="arrow"></text></view></view><view class="group"><view class="group-title">基础管理</view><view class="cell" bindtap="{{g}}"><text>供应商管理</text><text class="arrow"></text></view><view class="cell" bindtap="{{h}}"><text>客户管理</text><text class="arrow"></text></view><view class="cell" bindtap="{{i}}"><text>客户报价</text><text class="arrow"></text></view><view class="cell" bindtap="{{j}}"><text>店铺管理</text><text class="arrow"></text></view></view><view class="group"><view class="group-title">设置中心</view><view class="cell" bindtap="{{k}}"><text>账号与安全</text><text class="desc">修改头像、姓名、密码</text><text class="arrow"></text></view><view class="cell" bindtap="{{l}}"><text>商品设置</text><text class="arrow"></text></view><view class="cell" bindtap="{{m}}"><text>系统参数</text><text class="desc">低价提示、默认收款、单行折扣等</text><text class="arrow"></text></view><view class="cell" bindtap="{{n}}"><text>关于与协议</text><text class="arrow"></text></view><view class="cell danger" bindtap="{{o}}"><text>退出登录</text></view></view></view>

View File

@@ -0,0 +1,27 @@
.me { padding: 24rpx;
}
.card.user { display: flex; gap: 18rpx; padding: 22rpx; background: #fff; border-radius: 16rpx; box-shadow: 0 6rpx 16rpx rgba(0,0,0,0.06); align-items: center;
}
.avatar { width: 120rpx; height: 120rpx; border-radius: 60rpx; background: #f5f5f5;
}
.meta { display: flex; flex-direction: column; gap: 6rpx;
}
.name { font-size: 34rpx; font-weight: 700; color: #333;
}
.phone { font-size: 26rpx; color: #888;
}
.role { font-size: 22rpx; color: #999;
}
.group { margin-top: 24rpx; background: #fff; border-radius: 16rpx; overflow: hidden;
}
.group-title { padding: 18rpx 22rpx; font-size: 26rpx; color: #999; background: #fafafa;
}
.cell { display: flex; align-items: center; padding: 26rpx 22rpx; border-top: 1rpx solid #f0f0f0; color: #333;
}
.cell .desc { margin-left: auto; margin-right: 8rpx; font-size: 22rpx; color: #999;
}
.cell .arrow { margin-left: auto; color: #bbb;
}
.cell.danger { color: #dd524d; justify-content: center; font-weight: 700;
}

View File

@@ -28,6 +28,7 @@ const _sfc_main = {
supplierName: "",
items: [],
activeCategory: "sale_income",
counterpartyType: "customer",
trxAmount: 0,
selectedAccountId: null,
selectedAccountName: "",
@@ -50,16 +51,16 @@ const _sfc_main = {
return this.supplierName || "零散供应商";
},
incomeCategories() {
return common_constants.INCOME_CATEGORIES;
return this._incomeCategories || common_constants.INCOME_CATEGORIES;
},
expenseCategories() {
return common_constants.EXPENSE_CATEGORIES;
return this._expenseCategories || common_constants.EXPENSE_CATEGORIES;
},
accountLabel() {
return this.selectedAccountName || "现金";
},
counterpartyLabel() {
return this.customerName || this.supplierName || "—";
return this.counterpartyType === "customer" ? this.customerName || "—" : this.supplierName || "—";
},
// 收款/付款合计
payTotal() {
@@ -67,6 +68,9 @@ const _sfc_main = {
return Number(p.cash || 0) + Number(p.bank || 0) + Number(p.wechat || 0);
}
},
onLoad() {
this.fetchCategories();
},
onShow() {
if (this.biz === "sale") {
if (this.order.customerId && this.order.customerId !== this._lastCustomerId) {
@@ -85,6 +89,26 @@ const _sfc_main = {
}
},
methods: {
async fetchCategories() {
try {
const res = await common_http.get("/api/finance/categories");
if (res && Array.isArray(res.incomeCategories))
this._incomeCategories = res.incomeCategories;
if (res && Array.isArray(res.expenseCategories))
this._expenseCategories = res.expenseCategories;
this.ensureActiveCategory();
} catch (_) {
this.ensureActiveCategory();
}
},
ensureActiveCategory() {
const list = this.biz === "income" ? this.incomeCategories || [] : this.expenseCategories || [];
if (!list.length)
return;
const exists = list.some((it) => it && it.key === this.activeCategory);
if (!exists)
this.activeCategory = list[0].key;
},
async loadCustomerLevel(customerId) {
try {
const d = await common_http.get(`/api/customers/${customerId}`);
@@ -133,6 +157,7 @@ const _sfc_main = {
},
switchBiz(type) {
this.biz = type;
this.ensureActiveCategory();
},
onDateChange(e) {
this.order.orderTime = e.detail.value;
@@ -147,13 +172,21 @@ const _sfc_main = {
common_vendor.index.navigateTo({ url: "/pages/product/select" });
},
chooseAccount() {
common_vendor.index.navigateTo({ url: "/pages/account/select" });
common_vendor.index.navigateTo({ url: "/pages/account/select?mode=pick" });
},
chooseCounterparty() {
if (this.biz === "income" || this.biz === "expense") {
if (!(this.biz === "income" || this.biz === "expense"))
return;
if (this.counterpartyType === "customer") {
common_vendor.index.navigateTo({ url: "/pages/customer/select" });
} else {
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
}
},
setCounterparty(t) {
this.counterpartyType = t;
this.ensureActiveCategory();
},
recalc() {
this.$forceUpdate();
},
@@ -189,7 +222,8 @@ const _sfc_main = {
} : {
type: this.biz,
category: this.activeCategory,
counterpartyId: this.order.customerId || null,
counterpartyType: this.counterpartyType,
counterpartyId: this.counterpartyType === "customer" ? this.order.customerId || null : this.order.supplierId || null,
accountId: this.selectedAccountId || null,
amount: Number(this.trxAmount || 0),
txTime: this.order.orderTime,
@@ -288,7 +322,11 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
ac: common_vendor.t($options.totalAmount.toFixed(2)),
ad: common_vendor.o((...args) => $options.chooseProduct && $options.chooseProduct(...args))
} : {
ae: common_vendor.f($data.biz === "income" ? $options.incomeCategories : $options.expenseCategories, (c, k0, i0) => {
ae: $data.counterpartyType === "customer" ? 1 : "",
af: common_vendor.o(($event) => $options.setCounterparty("customer")),
ag: $data.counterpartyType === "supplier" ? 1 : "",
ah: common_vendor.o(($event) => $options.setCounterparty("supplier")),
ai: common_vendor.f($data.biz === "income" ? $options.incomeCategories : $options.expenseCategories, (c, k0, i0) => {
return {
a: common_vendor.t(c.label),
b: c.key,
@@ -296,23 +334,23 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
d: common_vendor.o(($event) => $data.activeCategory = c.key, c.key)
};
}),
af: common_vendor.t($options.counterpartyLabel),
ag: common_vendor.o((...args) => $options.chooseCounterparty && $options.chooseCounterparty(...args)),
ah: common_vendor.t($options.accountLabel),
ai: common_vendor.o((...args) => $options.chooseAccount && $options.chooseAccount(...args)),
aj: $data.trxAmount,
ak: common_vendor.o(common_vendor.m(($event) => $data.trxAmount = $event.detail.value, {
aj: common_vendor.t($options.counterpartyLabel),
ak: common_vendor.o((...args) => $options.chooseCounterparty && $options.chooseCounterparty(...args)),
al: common_vendor.t($options.accountLabel),
am: common_vendor.o((...args) => $options.chooseAccount && $options.chooseAccount(...args)),
an: $data.trxAmount,
ao: common_vendor.o(common_vendor.m(($event) => $data.trxAmount = $event.detail.value, {
number: true
})),
al: $data.order.remark,
am: common_vendor.o(($event) => $data.order.remark = $event.detail.value)
ap: $data.order.remark,
aq: common_vendor.o(($event) => $data.order.remark = $event.detail.value)
}, {
aa: $data.biz === "sale" || $data.biz === "purchase",
an: !$data.items.length
ar: !$data.items.length
}, !$data.items.length ? {
ao: common_assets._imports_0$1
as: common_assets._imports_0$1
} : {
ap: common_vendor.f($data.items, (it, idx, i0) => {
at: common_vendor.f($data.items, (it, idx, i0) => {
return {
a: common_vendor.t(it.productName),
b: common_vendor.o([common_vendor.m(($event) => it.quantity = $event.detail.value, {
@@ -328,8 +366,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
};
})
}, {
aq: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
ar: common_vendor.o((...args) => $options.submit && $options.submit(...args))
av: common_vendor.o((...args) => $options.saveAndReset && $options.saveAndReset(...args)),
aw: common_vendor.o((...args) => $options.submit && $options.submit(...args))
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

View File

@@ -1 +1 @@
<view class="order"><view class="tabs"><text class="{{[a && 'active']}}" bindtap="{{b}}">销售</text><text class="{{[c && 'active']}}" bindtap="{{d}}">进货</text><text class="{{[e && 'active']}}" bindtap="{{f}}">其他收入</text><text class="{{[g && 'active']}}" bindtap="{{h}}">其他支出</text></view><view wx:if="{{i}}" class="subtabs"><button class="{{['subbtn', j && 'active']}}" bindtap="{{k}}">出货</button><button class="{{['subbtn', l && 'active']}}" bindtap="{{m}}">退货</button><button class="{{['subbtn', n && 'active']}}" bindtap="{{o}}">收款</button></view><view wx:elif="{{p}}" class="subtabs"><button class="{{['subbtn', q && 'active']}}" bindtap="{{r}}">进货</button><button class="{{['subbtn', s && 'active']}}" bindtap="{{t}}">退货</button><button class="{{['subbtn', v && 'active']}}" bindtap="{{w}}">付款</button></view><picker mode="date" value="{{y}}" bindchange="{{z}}"><view class="field"><text class="label">时间</text><text class="value">{{x}}</text></view></picker><view wx:if="{{A}}" class="field" bindtap="{{C}}"><text class="label">客户</text><text class="value">{{B}}</text></view><view wx:elif="{{D}}" class="field" bindtap="{{F}}"><text class="label">供应商</text><text class="value">{{E}}</text></view><view wx:if="{{G}}"><view wx:if="{{H}}" class="field" bindtap="{{J}}"><text class="label">客户</text><text class="value">{{I}}</text></view><view wx:else class="field" bindtap="{{L}}"><text class="label">供应商</text><text class="value">{{K}}</text></view><view class="field pay-row"><text class="label">现金</text><input class="pay-input" type="digit" placeholder="0.00" bindinput="{{M}}" value="{{N}}"/></view><view class="field pay-row"><text class="label">银行存款</text><input class="pay-input" type="digit" placeholder="0.00" bindinput="{{O}}" value="{{P}}"/></view><view class="field pay-row"><text class="label">微信</text><input class="pay-input" type="digit" placeholder="0.00" bindinput="{{Q}}" value="{{R}}"/></view><view class="collapse-trigger" bindtap="{{T}}">{{S}}</view><view class="textarea"><view class="amount-badge">总金额:{{U}}</view><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多输入200个字" value="{{V}}" bindinput="{{W}}"></textarea></block><view class="date-mini"><picker mode="date" value="{{Y}}" bindchange="{{Z}}"><text>{{X}}</text></picker></view></view></view><view wx:elif="{{aa}}"><view class="summary"><text>选中货品({{ab}}</text><text>合计金额:¥ {{ac}}</text></view><view class="add" bindtap="{{ad}}">+</view></view><view wx:else><view class="chips"><view wx:for="{{ae}}" wx:for-item="c" wx:key="b" class="{{['chip', c.c && 'active']}}" bindtap="{{c.d}}">{{c.a}}</view></view><view class="field" bindtap="{{ag}}"><text class="label">往来单位</text><text class="value">{{af}}</text></view><view class="field" bindtap="{{ai}}"><text class="label">结算账户</text><text class="value">{{ah}}</text></view><view class="field"><text class="label">金额</text><input class="value" type="digit" placeholder="0.00" value="{{aj}}" bindinput="{{ak}}"/></view><view class="textarea"><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多输入200个字" value="{{al}}" bindinput="{{am}}"></textarea></block></view></view><view wx:if="{{an}}" class="empty"><image src="{{ao}}" mode="widthFix" class="empty-img"></image><text class="empty-text">购物车里空空如也</text><text class="empty-sub">扫描或点击 “+” 选择商品吧</text></view><view wx:else class="list"><view wx:for="{{ap}}" wx:for-item="it" wx:key="g" class="row"><view class="col name">{{it.a}}</view><view class="col qty"><input type="number" bindinput="{{it.b}}" value="{{it.c}}"/></view><view class="col price"><input type="number" bindinput="{{it.d}}" value="{{it.e}}"/></view><view class="col amount">¥ {{it.f}}</view></view></view><view class="bottom"><button class="ghost" bindtap="{{aq}}">再记一笔</button><button class="primary" bindtap="{{ar}}">保存</button></view></view>
<view class="order"><view class="tabs"><text class="{{[a && 'active']}}" bindtap="{{b}}">销售</text><text class="{{[c && 'active']}}" bindtap="{{d}}">进货</text><text class="{{[e && 'active']}}" bindtap="{{f}}">其他收入</text><text class="{{[g && 'active']}}" bindtap="{{h}}">其他支出</text></view><view wx:if="{{i}}" class="subtabs"><button class="{{['subbtn', j && 'active']}}" bindtap="{{k}}">出货</button><button class="{{['subbtn', l && 'active']}}" bindtap="{{m}}">退货</button><button class="{{['subbtn', n && 'active']}}" bindtap="{{o}}">收款</button></view><view wx:elif="{{p}}" class="subtabs"><button class="{{['subbtn', q && 'active']}}" bindtap="{{r}}">进货</button><button class="{{['subbtn', s && 'active']}}" bindtap="{{t}}">退货</button><button class="{{['subbtn', v && 'active']}}" bindtap="{{w}}">付款</button></view><picker mode="date" value="{{y}}" bindchange="{{z}}"><view class="field"><text class="label">时间</text><text class="value">{{x}}</text></view></picker><view wx:if="{{A}}" class="field" bindtap="{{C}}"><text class="label">客户</text><text class="value">{{B}}</text></view><view wx:elif="{{D}}" class="field" bindtap="{{F}}"><text class="label">供应商</text><text class="value">{{E}}</text></view><view wx:if="{{G}}"><view wx:if="{{H}}" class="field" bindtap="{{J}}"><text class="label">客户</text><text class="value">{{I}}</text></view><view wx:else class="field" bindtap="{{L}}"><text class="label">供应商</text><text class="value">{{K}}</text></view><view class="field pay-row"><text class="label">现金</text><input class="pay-input" type="digit" placeholder="0.00" bindinput="{{M}}" value="{{N}}"/></view><view class="field pay-row"><text class="label">银行存款</text><input class="pay-input" type="digit" placeholder="0.00" bindinput="{{O}}" value="{{P}}"/></view><view class="field pay-row"><text class="label">微信</text><input class="pay-input" type="digit" placeholder="0.00" bindinput="{{Q}}" value="{{R}}"/></view><view class="collapse-trigger" bindtap="{{T}}">{{S}}</view><view class="textarea"><view class="amount-badge">总金额:{{U}}</view><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多输入200个字" value="{{V}}" bindinput="{{W}}"></textarea></block><view class="date-mini"><picker mode="date" value="{{Y}}" bindchange="{{Z}}"><text>{{X}}</text></picker></view></view></view><view wx:elif="{{aa}}"><view class="summary"><text>选中货品({{ab}}</text><text>合计金额:¥ {{ac}}</text></view><view class="add" bindtap="{{ad}}">+</view></view><view wx:else><view class="subtabs"><button class="{{['subbtn', ae && 'active']}}" bindtap="{{af}}">客户</button><button class="{{['subbtn', ag && 'active']}}" bindtap="{{ah}}">供应商</button></view><view class="chips"><view wx:for="{{ai}}" wx:for-item="c" wx:key="b" class="{{['chip', c.c && 'active']}}" bindtap="{{c.d}}">{{c.a}}</view></view><view class="field" bindtap="{{ak}}"><text class="label">往来单位</text><text class="value">{{aj}}</text></view><view class="field" bindtap="{{am}}"><text class="label">结算账户</text><text class="value">{{al}}</text></view><view class="field"><text class="label">金额</text><input class="value" type="digit" placeholder="0.00" value="{{an}}" bindinput="{{ao}}"/></view><view class="textarea"><block wx:if="{{r0}}"><textarea maxlength="200" placeholder="备注最多输入200个字" value="{{ap}}" bindinput="{{aq}}"></textarea></block></view></view><view wx:if="{{ar}}" class="empty"><image src="{{as}}" mode="widthFix" class="empty-img"></image><text class="empty-text">购物车里空空如也</text><text class="empty-sub">扫描或点击 “+” 选择商品吧</text></view><view wx:else class="list"><view wx:for="{{at}}" wx:for-item="it" wx:key="g" class="row"><view class="col name">{{it.a}}</view><view class="col qty"><input type="number" bindinput="{{it.b}}" value="{{it.c}}"/></view><view class="col price"><input type="number" bindinput="{{it.d}}" value="{{it.e}}"/></view><view class="col amount">¥ {{it.f}}</view></view></view><view class="bottom"><button class="ghost" bindtap="{{av}}">再记一笔</button><button class="primary" bindtap="{{aw}}">保存</button></view></view>

View File

@@ -49,4 +49,11 @@
.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;
}
/* 分类chips样式选中后文字变红 */
.chips { display:flex; flex-wrap: wrap; gap: 12rpx; padding: 12rpx 24rpx;
}
.chip { padding: 10rpx 20rpx; border-radius: 999rpx; background: #f4f4f4; color:#666;
}
.chip.active { color: #e54d42;
}

View File

@@ -0,0 +1,25 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
methods: {
go(mode, dim) {
const q = `mode=${encodeURIComponent(mode)}&dim=${encodeURIComponent(dim || "")}`;
common_vendor.index.navigateTo({ url: `/pages/report/index?${q}` });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: common_vendor.o(($event) => $options.go("sale", "customer")),
b: common_vendor.o(($event) => $options.go("sale", "product")),
c: common_vendor.o(($event) => $options.go("sale", "customer")),
d: common_vendor.o(($event) => $options.go("sale", "customer")),
e: common_vendor.o(($event) => $options.go("purchase", "supplier")),
f: common_vendor.o(($event) => $options.go("inventory", "qty")),
g: common_vendor.o(($event) => $options.go("arap", "ar")),
h: common_vendor.o(($event) => $options.go("arap", "ap"))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/report/entry.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "报表",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="entry"><view class="section"><view class="section-title">资金报表</view><view class="grid"><view class="btn" bindtap="{{a}}">利润统计</view><view class="btn" bindtap="{{b}}">营业员统计</view><view class="btn" bindtap="{{c}}">经营业绩</view></view></view><view class="section"><view class="section-title">进销存报表</view><view class="grid"><view class="btn" bindtap="{{d}}">销售统计</view><view class="btn" bindtap="{{e}}">进货统计</view><view class="btn" bindtap="{{f}}">库存统计</view><view class="btn" bindtap="{{g}}">应收对账单</view><view class="btn" bindtap="{{h}}">应付对账单</view></view></view></view>

View File

@@ -0,0 +1,13 @@
.entry { padding: 20rpx;
}
.section { margin-bottom: 24rpx;
}
.section-title { background:#f1f4f8; color:#6a7a8a; padding: 14rpx 16rpx; border-radius: 12rpx; font-weight: 700;
}
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18rpx; padding: 18rpx 6rpx 0;
}
.btn { text-align: center; padding: 18rpx 8rpx; border: 1rpx solid #e5e9ef; border-radius: 12rpx; color:#333; background: #fff;
}
.btn:active { background: #f6f8fa;
}

View File

@@ -0,0 +1,338 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
function formatDate(d) {
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${y}-${m}-${day}`;
}
const _sfc_main = {
data() {
const now = /* @__PURE__ */ new Date();
const start = new Date(now.getFullYear(), now.getMonth(), 1);
return {
startDate: formatDate(start),
endDate: formatDate(now),
mode: "sale",
dim: "customer",
rows: [],
total: { sales: 0, cost: 0, profit: 0 }
};
},
onLoad(query) {
try {
const m = query && query.mode;
const d = query && query.dim;
if (m)
this.mode = m;
if (d)
this.dim = d;
} catch (e) {
}
this.refresh();
},
computed: {
profitRate() {
const { sales, profit } = this.total;
if (!sales)
return "0.00%";
return (profit / sales * 100).toFixed(2) + "%";
}
},
methods: {
fmt(n) {
return Number(n || 0).toFixed(2);
},
setMode(m) {
this.mode = m;
this.dim = m === "sale" ? "customer" : m === "purchase" ? "supplier" : m === "inventory" ? "qty" : "ar";
this.refresh();
},
onStartChange(e) {
this.startDate = e.detail.value;
this.refresh();
},
onEndChange(e) {
this.endDate = e.detail.value;
this.refresh();
},
async refresh() {
if (this.mode === "sale") {
if (this.dim === "customer")
return this.loadByCustomer();
if (this.dim === "product")
return this.loadByProduct();
}
if (this.mode === "purchase") {
if (this.dim === "supplier")
return this.loadPurchaseBySupplier();
if (this.dim === "product")
return this.loadPurchaseByProduct();
}
if (this.mode === "inventory") {
if (this.dim === "qty")
return this.loadInventoryByQty();
if (this.dim === "amount")
return this.loadInventoryByAmount();
}
if (this.mode === "arap") {
if (this.dim === "ar")
return this.loadAR();
if (this.dim === "ap")
return this.loadAP();
}
},
async loadByCustomer() {
try {
const listResp = await common_http.get("/api/orders", { biz: "sale", type: "out", startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const map = /* @__PURE__ */ new Map();
let totalSales = 0;
for (const it of list) {
const name = it.customerName || "未知客户";
const amount = Number(it.amount || 0);
totalSales += amount;
if (!map.has(name))
map.set(name, { name, sales: 0, cost: 0, profit: 0 });
const row = map.get(name);
row.sales += amount;
}
const rows = Array.from(map.values()).map((r) => ({ ...r, profit: r.sales - r.cost }));
const total = { sales: totalSales, cost: 0, profit: totalSales };
this.rows = rows;
this.total = total;
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadByProduct() {
try {
const listResp = await common_http.get("/api/orders", { biz: "sale", type: "out", startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const agg = /* @__PURE__ */ new Map();
for (const it of list) {
try {
const d = await common_http.get(`/api/orders/${it.id}`);
const items = d && d.items || [];
for (const m of items) {
const key = String(m.productId || m.name);
if (!agg.has(key))
agg.set(key, { name: m.name || "#" + key, sales: 0, cost: 0, profit: 0 });
const row = agg.get(key);
const sales = Number(m.amount || 0);
row.sales += sales;
}
} catch (_) {
}
}
const rows = Array.from(agg.values()).map((r) => ({ ...r, profit: r.sales - r.cost }));
const totalSales = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: totalSales, cost: 0, profit: totalSales };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadPurchaseBySupplier() {
try {
const listResp = await common_http.get("/api/purchase-orders", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const map = /* @__PURE__ */ new Map();
let total = 0;
for (const it of list) {
const name = it.supplierName || "未知供应商";
const amount = Number(it.amount || 0);
total += amount;
if (!map.has(name))
map.set(name, { name, sales: 0, cost: 0, profit: 0 });
const row = map.get(name);
row.sales += amount;
}
this.rows = Array.from(map.values());
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadPurchaseByProduct() {
try {
const listResp = await common_http.get("/api/purchase-orders", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = listResp && (listResp.list || listResp) || [];
const agg = /* @__PURE__ */ new Map();
for (const it of list) {
try {
const d = await common_http.get(`/api/purchase-orders/${it.id}`);
for (const m of (d == null ? void 0 : d.items) || []) {
const key = String(m.productId || m.name);
if (!agg.has(key))
agg.set(key, { name: m.name || "#" + key, sales: 0, cost: 0, profit: 0 });
const row = agg.get(key);
row.sales += Number(m.amount || 0);
}
} catch (_) {
}
}
const rows = Array.from(agg.values());
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadInventoryByQty() {
try {
const resp = await common_http.get("/api/inventories/logs", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = resp && (resp.list || resp) || [];
const map = /* @__PURE__ */ new Map();
let totalQty = 0;
for (const it of list) {
const key = it.productId || "未知";
if (!map.has(key))
map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 });
const row = map.get(key);
const q = Number(it.qtyDelta || 0);
row.sales += q;
totalQty += q;
}
this.rows = Array.from(map.values());
this.total = { sales: totalQty, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadInventoryByAmount() {
try {
const resp = await common_http.get("/api/inventories/logs", { startDate: this.startDate, endDate: this.endDate, page: 1, size: 200 });
const list = resp && (resp.list || resp) || [];
const map = /* @__PURE__ */ new Map();
let totalAmt = 0;
for (const it of list) {
const key = it.productId || "未知";
if (!map.has(key))
map.set(key, { name: String(key), sales: 0, cost: 0, profit: 0 });
const row = map.get(key);
const a = Number(it.amount || it.amountDelta || 0);
row.sales += a;
totalAmt += a;
}
this.rows = Array.from(map.values());
this.total = { sales: totalAmt, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadAR() {
try {
const res = await common_http.get("/api/customers", { page: 1, size: 100, debtOnly: false });
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const rows = list.map((c) => ({ name: c.name, sales: Number(c.receivable || 0), cost: 0, profit: 0 }));
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
},
async loadAP() {
try {
const res = await common_http.get("/api/suppliers", { page: 1, size: 100 });
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
const rows = list.map((s) => ({ name: s.name, sales: Number(s.apPayable || 0), cost: 0, profit: 0 }));
const total = rows.reduce((s, r) => s + r.sales, 0);
this.rows = rows;
this.total = { sales: total, cost: 0, profit: 0 };
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.mode === "sale" ? 1 : "",
b: common_vendor.o(($event) => $options.setMode("sale")),
c: $data.mode === "purchase" ? 1 : "",
d: common_vendor.o(($event) => $options.setMode("purchase")),
e: $data.mode === "inventory" ? 1 : "",
f: common_vendor.o(($event) => $options.setMode("inventory")),
g: $data.mode === "arap" ? 1 : "",
h: common_vendor.o(($event) => $options.setMode("arap")),
i: common_vendor.t($data.startDate),
j: $data.startDate,
k: common_vendor.o((...args) => $options.onStartChange && $options.onStartChange(...args)),
l: common_vendor.t($data.endDate),
m: $data.endDate,
n: common_vendor.o((...args) => $options.onEndChange && $options.onEndChange(...args)),
o: $data.mode === "sale"
}, $data.mode === "sale" ? {
p: $data.dim === "customer" ? 1 : "",
q: common_vendor.o(($event) => {
$data.dim = "customer";
$options.refresh();
}),
r: $data.dim === "product" ? 1 : "",
s: common_vendor.o(($event) => {
$data.dim = "product";
$options.refresh();
})
} : $data.mode === "purchase" ? {
v: $data.dim === "supplier" ? 1 : "",
w: common_vendor.o(($event) => {
$data.dim = "supplier";
$options.refresh();
}),
x: $data.dim === "product" ? 1 : "",
y: common_vendor.o(($event) => {
$data.dim = "product";
$options.refresh();
})
} : $data.mode === "inventory" ? {
A: $data.dim === "qty" ? 1 : "",
B: common_vendor.o(($event) => {
$data.dim = "qty";
$options.refresh();
}),
C: $data.dim === "amount" ? 1 : "",
D: common_vendor.o(($event) => {
$data.dim = "amount";
$options.refresh();
})
} : $data.mode === "arap" ? {
F: $data.dim === "ar" ? 1 : "",
G: common_vendor.o(($event) => {
$data.dim = "ar";
$options.refresh();
}),
H: $data.dim === "ap" ? 1 : "",
I: common_vendor.o(($event) => {
$data.dim = "ap";
$options.refresh();
})
} : {}, {
t: $data.mode === "purchase",
z: $data.mode === "inventory",
E: $data.mode === "arap",
J: common_vendor.t($options.fmt($data.total.sales)),
K: common_vendor.t($options.fmt($data.total.cost)),
L: common_vendor.t($options.fmt($data.total.profit)),
M: common_vendor.t($options.profitRate),
N: common_vendor.f($data.rows, (row, idx, i0) => {
return common_vendor.e({
a: row.avatar
}, row.avatar ? {
b: row.avatar
} : {}, {
c: common_vendor.t(row.name),
d: common_vendor.t($options.fmt(row.sales)),
e: common_vendor.t($options.fmt(row.cost)),
f: common_vendor.t($options.fmt(row.profit)),
g: idx
});
})
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/report/index.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "报表",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="report"><view class="modes"><view class="{{['mode-tab', a && 'active']}}" bindtap="{{b}}">销售统计</view><view class="{{['mode-tab', c && 'active']}}" bindtap="{{d}}">进货统计</view><view class="{{['mode-tab', e && 'active']}}" bindtap="{{f}}">库存统计</view><view class="{{['mode-tab', g && 'active']}}" bindtap="{{h}}">应收/应付对账</view></view><view class="toolbar"><picker mode="date" value="{{j}}" bindchange="{{k}}"><view class="date">{{i}}</view></picker><text style="margin:0 8rpx">—</text><picker mode="date" value="{{m}}" bindchange="{{n}}"><view class="date">{{l}}</view></picker></view><view wx:if="{{o}}" class="tabs"><view class="{{['tab', p && 'active']}}" bindtap="{{q}}">按客户</view><view class="{{['tab', r && 'active']}}" bindtap="{{s}}">按货品</view></view><view wx:elif="{{t}}" class="tabs"><view class="{{['tab', v && 'active']}}" bindtap="{{w}}">按供应商</view><view class="{{['tab', x && 'active']}}" bindtap="{{y}}">按货品</view></view><view wx:elif="{{z}}" class="tabs"><view class="{{['tab', A && 'active']}}" bindtap="{{B}}">按数量</view><view class="{{['tab', C && 'active']}}" bindtap="{{D}}">按金额</view></view><view wx:elif="{{E}}" class="tabs"><view class="{{['tab', F && 'active']}}" bindtap="{{G}}">应收对账</view><view class="{{['tab', H && 'active']}}" bindtap="{{I}}">应付对账</view></view><view class="summary"><view class="item"><text class="label">销售额</text><text class="value">¥ {{J}}</text></view><view class="item"><text class="label">成本</text><text class="value">¥ {{K}}</text></view><view class="item"><text class="label">利润</text><text class="value">¥ {{L}}</text></view><view class="item"><text class="label">利润率</text><text class="value">{{M}}</text></view></view><view wx:for="{{N}}" wx:for-item="row" wx:key="g" class="card"><view class="row-head"><image wx:if="{{row.a}}" class="thumb" src="{{row.b}}"/><view class="title">{{row.c}}</view></view><view class="row-body"><text>销售额:¥ {{row.d}}</text><text style="margin-left:18rpx">成本:¥ {{row.e}}</text><text style="margin-left:18rpx">利润:¥ {{row.f}}</text></view></view></view>

View File

@@ -0,0 +1,37 @@
.report { padding: 20rpx;
}
.modes { display: flex; gap: 12rpx; margin-bottom: 14rpx;
}
.mode-tab { flex: 1; text-align: center; padding: 16rpx 0; border-radius: 999rpx; background: #f4f4f4; color: #666; border: 1rpx solid #e9e9e9;
}
.mode-tab.active { background: #1aad19; color: #fff; border-color: #1aad19; font-weight: 700;
}
.toolbar { display: flex; align-items: center; gap: 8rpx; background: #fff; padding: 14rpx 16rpx; border-radius: 12rpx;
}
.date { padding: 10rpx 16rpx; border: 1rpx solid #eee; border-radius: 8rpx;
}
.tabs { display: flex; gap: 16rpx; margin-top: 14rpx;
}
.tab { padding: 12rpx 18rpx; border-radius: 999rpx; background: #f4f4f4; color: #666;
}
.tab.active { background: #1aad19; color: #fff;
}
.summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8rpx; margin-top: 14rpx;
}
.summary .item { background: #fff; border-radius: 12rpx; padding: 16rpx;
}
.summary .label { font-size: 22rpx; color: #888;
}
.summary .value { display: block; margin-top: 8rpx; font-weight: 700; color: #333;
}
.card { margin-top: 16rpx; background: #fff; border-radius: 12rpx; padding: 16rpx;
}
.row-head { display: flex; align-items: center; gap: 12rpx;
}
.thumb { width: 72rpx; height: 72rpx; border-radius: 8rpx; background: #f2f2f2;
}
.title { font-size: 28rpx; font-weight: 700;
}
.row-body { margin-top: 10rpx; color: #666;
}