9.20/3
This commit is contained in:
160
doc/admin_requirements.md
Normal file
160
doc/admin_requirements.md
Normal file
@@ -0,0 +1,160 @@
|
||||
### 管理端需求规格说明书(PartsInquiry)
|
||||
|
||||
#### 1. 目标与范围
|
||||
- 目标:为“配件查询/进销存”系统提供 PC Web 管理端,支持主数据配置、日常业务监管与数据分析,保障多租户(按店铺)数据隔离与安全可控。
|
||||
- 范围:围绕商品、库存、订单(销售/进货/退货)、往来(客户/供应商)、账户与收支、报表统计、配件众包审核、公告、系统参数、用户与店铺管理等模块展开。
|
||||
|
||||
#### 2. 角色与权限(分阶段)
|
||||
- MVP:基于现有 `users.role` 与 `is_owner` 字段实现“店长(owner)/员工(staff)”两级权限开关;菜单与接口做最小化授权控制(店长全量、员工受限)。
|
||||
- 方案A(MVP落地):后端以常量维护权限点 → 通过 `users.role` 做菜单/接口守卫;无需改库,风险低、上线快。
|
||||
- 方案B(标准RBAC,需改库):新增 `roles/permissions/user_roles/role_permissions` 等表,菜单-权限点分离;支持细粒度到按钮/字段;成本高、可作为后续版本演进。
|
||||
|
||||
#### 3. 多租户与数据隔离
|
||||
- 以店铺 `shops` 为租户单位,所有业务表包含 `shop_id`;接口通过请求头 `X-Shop-Id` 或 `X-User-Id` 解析绑定店铺。
|
||||
- 管理端所有查询/导出均默认限定到当前 `shop_id`,严禁越权访问。
|
||||
|
||||
#### 4. 信息架构(菜单与模块)
|
||||
1) 首页总览(Dashboard)
|
||||
- 今日销售额、本月销售额、本月毛利、库存总量等关键指标卡片
|
||||
- 公告栏(置顶/定时生效)
|
||||
- 快捷入口(新建商品、销售开单、客户/供应商管理、其他收支)
|
||||
|
||||
2) 商品中心
|
||||
- 商品管理:新增/编辑/停用、图片管理、别名管理、多列销售价格(零售/批发/大单报价)、安全库存上下限
|
||||
- 商品类别:树形维护、排序
|
||||
- 计量单位:主数据维护
|
||||
- 商品设置:开关“隐藏零库存”“隐藏进货价”等
|
||||
- 批量导入/导出:CSV/Excel 模板下载、校验、结果反馈
|
||||
|
||||
3) 库存与盘点
|
||||
- 库存台账:按商品查看现存量、安全库存预警、批量调价/调库存
|
||||
- 库存流水:支持按时间/商品/原因筛选,定位库存变动来源
|
||||
- 盘点(两种实现路径):
|
||||
- 方案A(MVP):使用 `inventory_movements` 记录调整(reason=adjust),形成“快速盘点”单据视图
|
||||
- 方案B(标准):新增 `stock_counts/stock_count_items` 建模正式盘点单(含草稿/提交/审核/生效流程)
|
||||
|
||||
4) 交易中心
|
||||
- 销售:出货单、退货单、收款;列表/详情/导出
|
||||
- 进货:入库单、退货单、付款;列表/详情/导出
|
||||
- 收付款流水:多维筛选与汇总
|
||||
|
||||
5) 往来管理
|
||||
- 客户:档案维护(含价格列偏好)、“只看欠款”筛选、期初应收
|
||||
- 供应商:档案维护、“只看欠款”(应付)筛选、期初应付
|
||||
|
||||
6) 账户与财务
|
||||
- 账户:现金/银行/微信/支付宝等基础账户维护、余额、启停
|
||||
- 账户流水(Ledger):期间收支、期初/本期/期末,按关键词检索
|
||||
- 其他收入/支出:分类维护(经营、办公等),按往来单位与账户记账
|
||||
|
||||
7) 报表中心
|
||||
- 资金报表:利润、营业员统计、经营业绩
|
||||
- 进销存报表:销售统计、进货统计、库存统计、应收/应付对账单
|
||||
- 导出:按筛选条件导出 CSV/Excel
|
||||
|
||||
8) 配件查询与众包审核
|
||||
- 数据查询:多条件组合、模糊匹配、分页、导出
|
||||
- 提交审核:用户提交新配件(型号必填)、图片上传、管理员审核(通过/驳回/编辑)
|
||||
- 图片管理:多图、排序、预览、去重
|
||||
|
||||
9) 公告管理
|
||||
- 列表/新建/发布/下线、置顶、定时生效
|
||||
|
||||
10) 系统设置
|
||||
- 店铺信息、系统参数(业务逻辑开关)、默认价格列、附件占位图路径配置等
|
||||
|
||||
11) 用户与店铺
|
||||
- 用户:员工管理、角色(MVP为枚举)、状态启停
|
||||
- 店铺:基本信息、状态启停
|
||||
|
||||
#### 5. 数据模型对齐(不改库前提)
|
||||
- 商品域:`products/product_prices/inventories/product_images/product_aliases/product_categories/product_units/global_skus`
|
||||
- 交易域:`sales_orders/sales_order_items/sales_return_orders/...`、`purchase_orders/purchase_order_items/purchase_return_orders/...`
|
||||
- 库存域:`inventory_movements`
|
||||
- 往来域:`customers/suppliers`
|
||||
- 资金域:`accounts/payments/other_transactions/finance_categories`
|
||||
- 系统与运营:`system_parameters/notices/users/shops/user_identities/wechat_sessions`
|
||||
|
||||
说明:管理端各模块的列表、详情、导出、筛选字段需与上述表结构保持一致;统计口径(如毛利)以现有订单金额与进价近似规则为准。
|
||||
|
||||
#### 6. 功能与数据口径要点
|
||||
- 销售/进货单保存即生效(approved),联动库存;退货单同理
|
||||
- 收/付款必须绑定订单(sale/purchase),不可游离单据
|
||||
- 客户默认价格列按 `customers.price_level`,支持在下单时临时改价
|
||||
- 安全库存预警依据 `products.safe_min/safe_max` 与当前库存 `inventories.quantity`
|
||||
- 图片当前为占位图策略,管理端需支持占位图路径的配置项
|
||||
|
||||
#### 7. 关键缺口与多方案设计
|
||||
1) 权限体系
|
||||
- A(MVP):沿用 `users.role`(owner/staff/finance...)+ 后端常量权限映射,菜单/接口守卫
|
||||
- B(标准RBAC):新增角色/权限/关联表,支持细粒度到按钮/字段(需改库与前后端联动)
|
||||
|
||||
2) 盘点业务
|
||||
- A(MVP):以 `inventory_movements` reason=adjust 记录快速盘点;提供导入与差异对账视图
|
||||
- B(标准):`stock_counts/stock_count_items` 正式票据流转(草稿/提交/审核/生效),支持多盘点范围
|
||||
|
||||
3) 配件提交审核
|
||||
- A(轻量):在 `products` 增加状态/来源字段区分“待审核/发布”(需改库)
|
||||
- B(清晰):新增 `part_submissions` 表管理提交、审核、版本留痕(需改库);发布后写入 `products`
|
||||
|
||||
4) 附件与图片
|
||||
- A(现状/MVP):维持占位图上传接口,集中配置占位图片路径与 URL
|
||||
- B(完善):引入通用 `attachments` 表与文件存储(本地/对象存储),商品图片与业务附件统一管理(需改库与上传服务)
|
||||
|
||||
5) 报表性能
|
||||
- A(MVP):在线聚合 + 必要索引保障(结合 `idx_*` 与 FULLTEXT)
|
||||
- B(增强):引入日/月度快照表或物化视图,提升大数据量下统计性能(需改库与离线作业)
|
||||
|
||||
#### 8. MVP 范围(建议首版)
|
||||
- 首页总览:指标卡、公告、快捷入口
|
||||
- 商品中心:商品/类别/单位/设置,图片占位图;导出(导入次版)
|
||||
- 往来管理:客户/供应商,支持“只看欠款”筛选
|
||||
- 交易中心:销售/进货单列表与详情、收付款创建与列表
|
||||
- 账户与财务:账户管理、账户流水、其他收支与分类
|
||||
- 库存:库存台账与库存流水(快速调整)
|
||||
- 公告管理:发布/下线/置顶/定时
|
||||
- 系统设置:系统参数读取/保存、附件占位图路径配置
|
||||
- 配件审核:审核列表 + 审核通过/驳回(先走轻量方案A或作为次版)
|
||||
|
||||
不含:标准RBAC、正式盘点票据、通用附件库、复杂经营报表、审计日志、批量导入(除模板下载)
|
||||
|
||||
#### 9. 验收标准(MVP)
|
||||
- 指标一致:首页指标与 `/api/dashboard/overview` 返回一致
|
||||
- 列表正确:商品/订单/客户/供应商/账户/收支/库存流水支持分页、筛选、导出
|
||||
- 金额口径:订单金额与收/付款、其他收支汇总与接口返回一致
|
||||
- 安全:所有查询写入均限定 `shop_id`;非店长用户受限菜单与接口
|
||||
- 可靠:核心列表在 10 万级数据下查询 ≤ 2s(有索引)
|
||||
- 可配置:系统参数与占位图路径可读写并即时生效(或重启后生效有说明)
|
||||
|
||||
#### 10. 接口对接与规范
|
||||
- 所有接口在 `/doc/openapi.yaml` 维护;每个 path 在 summary/description 标注实现状态(❌/✅)
|
||||
- 统一响应风格与字段命名(驼峰),分页返回 `{ list, page, size, total }` 或兼容数组
|
||||
- 认证与多租户:通过 `X-Shop-Id`/`X-User-Id` 解析店铺并进行权限校验
|
||||
- 导出:提供 `text/csv` 或 Excel(application/vnd.ms-excel)响应,支持 UTF-8 BOM
|
||||
|
||||
#### 11. 配置项(环境变量/系统参数)
|
||||
- 附件占位图:`ATTACHMENTS_PLACEHOLDER_IMAGE`、`ATTACHMENTS_PLACEHOLDER_URL`
|
||||
- 默认店铺/用户(仅开发环境):`ENABLE_DEFAULT_USER`、`DEFAULT_USER_ID`
|
||||
- 商品设置(系统参数 `product.settings`):`hideZeroStock`、`hidePurchasePrice`
|
||||
- 报表与导出:最大导出行数上限、异步导出开关
|
||||
|
||||
#### 12. 非功能性要求
|
||||
- 安全:多租户隔离、权限校验、输入校验与审计日志(次版)
|
||||
- 性能:核心列表与统计具备必要索引;长耗时任务支持异步导出
|
||||
- 可观测:请求链路日志、接口耗时、错误聚合;慢查询监控
|
||||
- 兼容:PC Web 适配 1366×768 及以上分辨率
|
||||
|
||||
#### 13. UI/UX 原则
|
||||
- 结构克制、信息分层清晰;强调列表可读性与筛选效率
|
||||
- 一键导出、批量操作具备操作确认与结果反馈
|
||||
- 重要字段固定列/高亮(金额、数量、状态、时间)
|
||||
|
||||
#### 14. 后续演进建议
|
||||
- 引入标准 RBAC 与审计日志
|
||||
- 正式盘点票据与盘亏盘盈处理
|
||||
- 通用附件中心与对象存储接入
|
||||
- 复杂报表物化与计划任务
|
||||
|
||||
(本文档仅描述当前功能与落地方案,不包含历史变更说明。)
|
||||
|
||||
|
||||
@@ -15,5 +15,21 @@ export const EXPENSE_CATEGORIES = [
|
||||
{ key: 'other_expense', label: '其它支出' }
|
||||
]
|
||||
|
||||
// 路由常量(集中管理页面路径,避免在业务中硬编码)
|
||||
export const ROUTES = {
|
||||
home: '/pages/index/index',
|
||||
productList: '/pages/product/list',
|
||||
productForm: '/pages/product/form',
|
||||
productSelect: '/pages/product/select',
|
||||
productSettings: '/pages/product/settings',
|
||||
orderCreate: '/pages/order/create',
|
||||
detail: '/pages/detail/index',
|
||||
my: '/pages/my/index',
|
||||
myAbout: '/pages/my/about',
|
||||
report: '/pages/report/index',
|
||||
customerSelect: '/pages/customer/select',
|
||||
supplierSelect: '/pages/supplier/select',
|
||||
accountSelect: '/pages/account/select'
|
||||
}
|
||||
|
||||
|
||||
|
||||
0
frontend/common/navigation.js
Normal file
0
frontend/common/navigation.js
Normal file
0
frontend/components/layout/BottomNav.vue
Normal file
0
frontend/components/layout/BottomNav.vue
Normal file
0
frontend/components/tabs/TabDetail.vue
Normal file
0
frontend/components/tabs/TabDetail.vue
Normal file
0
frontend/components/tabs/TabHome.vue
Normal file
0
frontend/components/tabs/TabHome.vue
Normal file
0
frontend/components/tabs/TabMy.vue
Normal file
0
frontend/components/tabs/TabMy.vue
Normal file
0
frontend/components/tabs/TabOrder.vue
Normal file
0
frontend/components/tabs/TabOrder.vue
Normal file
0
frontend/components/tabs/TabProduct.vue
Normal file
0
frontend/components/tabs/TabProduct.vue
Normal file
@@ -127,5 +127,18 @@
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#8a7535",
|
||||
"selectedColor": "#B4880F",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{ "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/logo.png", "selectedIconPath": "static/logo.png" },
|
||||
{ "pagePath": "pages/product/list", "text": "货品", "iconPath": "static/logo.png", "selectedIconPath": "static/logo.png" },
|
||||
{ "pagePath": "pages/order/create", "text": "开单", "iconPath": "static/logo.png", "selectedIconPath": "static/logo.png" },
|
||||
{ "pagePath": "pages/detail/index", "text": "明细", "iconPath": "static/logo.png", "selectedIconPath": "static/logo.png" },
|
||||
{ "pagePath": "pages/my/index", "text": "我的", "iconPath": "static/logo.png", "selectedIconPath": "static/logo.png" }
|
||||
]
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ export default {
|
||||
} finally { this.loading = false }
|
||||
},
|
||||
formatDate(s) { if (!s) return ''; try { const d = new Date(s); const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}` } catch (_) { return String(s).slice(0,10) } },
|
||||
onCreate() { if (this.biz === 'sale') { uni.navigateTo({ url: '/pages/order/create' }); return } uni.showToast({ title: '该类型创建页待实现', icon: 'none' }) },
|
||||
onCreate() { if (this.biz === 'sale') { uni.switchTab({ url: '/pages/order/create' }); return } uni.showToast({ title: '该类型创建页待实现', icon: 'none' }) },
|
||||
openDetail(it) { uni.showToast({ title: '详情开发中', icon: 'none' }) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,32 +64,13 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部操作条 -->
|
||||
<view class="bottom-bar">
|
||||
<view class="tab" :class="{ active: activeTab==='home' }" @click="activeTab='home'">
|
||||
<text>首页</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: activeTab==='product' }" @click="goProduct">
|
||||
<text>货品</text>
|
||||
</view>
|
||||
<view class="tab primary" @click="onCreateOrder">
|
||||
<text>开单</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: activeTab==='detail' }" @click="goDetail">
|
||||
<text>明细</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: activeTab==='report' }" @click="goReport">
|
||||
<text>报表</text>
|
||||
</view>
|
||||
<view class="tab" :class="{ active: activeTab==='me' }" @click="goMe">
|
||||
<text>我的</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部操作条改为原生 tabBar,移除自定义栏 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get } from '../../common/http.js'
|
||||
import { ROUTES } from '../../common/constants.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -149,12 +130,13 @@
|
||||
},
|
||||
onFeatureTap(item) {
|
||||
if (item.key === 'product') {
|
||||
uni.navigateTo({ url: '/pages/product/list' })
|
||||
uni.switchTab({ url: '/pages/product/list' })
|
||||
return
|
||||
}
|
||||
if (item.key === 'sale') {
|
||||
// 进入开单页并预选“销售-出货”
|
||||
uni.navigateTo({ url: '/pages/order/create?biz=sale&type=out' })
|
||||
try { uni.setStorageSync('ORDER_DEFAULT_PARAMS', { biz: 'sale', type: 'out' }) } catch(e) {}
|
||||
uni.switchTab({ url: '/pages/order/create' })
|
||||
return
|
||||
}
|
||||
if (item.key === 'customer') {
|
||||
@@ -172,41 +154,30 @@
|
||||
}
|
||||
if (item.key === 'purchase') {
|
||||
// 进入开单页并预选“进货-进货”
|
||||
uni.navigateTo({ url: '/pages/order/create?biz=purchase&type=in' })
|
||||
try { uni.setStorageSync('ORDER_DEFAULT_PARAMS', { biz: 'purchase', type: 'in' }) } catch(e) {}
|
||||
uni.switchTab({ url: '/pages/order/create' })
|
||||
return
|
||||
}
|
||||
if (item.key === 'report') {
|
||||
// 直接进入报表页
|
||||
uni.navigateTo({ url: '/pages/report/index' })
|
||||
// 报表非 tab 页,使用 navigateTo 进入
|
||||
uni.navigateTo({ url: ROUTES.report })
|
||||
return
|
||||
}
|
||||
if (item.key === 'otherPay') {
|
||||
// 进入开单页并预选“其他支出”
|
||||
uni.navigateTo({ url: '/pages/order/create?biz=expense' })
|
||||
try { uni.setStorageSync('ORDER_DEFAULT_PARAMS', { biz: 'expense' }) } catch(e) {}
|
||||
uni.switchTab({ url: '/pages/order/create' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: item.title + '(开发中)', icon: 'none' })
|
||||
},
|
||||
goProduct() {
|
||||
this.activeTab = 'product'
|
||||
uni.navigateTo({ url: '/pages/product/list' })
|
||||
},
|
||||
onCreateOrder() {
|
||||
uni.navigateTo({ url: '/pages/order/create' })
|
||||
},
|
||||
goProduct() { uni.switchTab({ url: '/pages/product/list' }) },
|
||||
onCreateOrder() { uni.switchTab({ url: '/pages/order/create' }) },
|
||||
goDetail() {
|
||||
this.activeTab = 'detail'
|
||||
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/index' })
|
||||
},
|
||||
goMe() {
|
||||
this.activeTab = 'me'
|
||||
uni.navigateTo({ url: '/pages/my/index' })
|
||||
uni.switchTab({ url: '/pages/detail/index' })
|
||||
},
|
||||
goMe() { uni.switchTab({ url: '/pages/my/index' }) },
|
||||
onNoticeTap(n) {
|
||||
uni.showModal({
|
||||
title: '广告',
|
||||
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
this.avatarUrl = '/static/logo.png'
|
||||
},
|
||||
goVip() { uni.showToast({ title: 'VIP会员(开发中)', icon: 'none' }) },
|
||||
goMyOrders() { uni.navigateTo({ url: '/pages/detail/index' }) },
|
||||
goMyOrders() { uni.switchTab({ url: '/pages/detail/index' }) },
|
||||
goSupplier() { uni.navigateTo({ url: '/pages/supplier/select' }) },
|
||||
goCustomer() { uni.navigateTo({ url: '/pages/customer/select' }) },
|
||||
goCustomerQuote() { uni.showToast({ title: '客户报价(开发中)', icon: 'none' }) },
|
||||
|
||||
@@ -203,8 +203,11 @@
|
||||
},
|
||||
onLoad(query) {
|
||||
try {
|
||||
const biz = query && query.biz
|
||||
const type = query && query.type
|
||||
// 1) 优先读取本地预设参数(来自首页九宫格)
|
||||
const preset = uni.getStorageSync('ORDER_DEFAULT_PARAMS') || {}
|
||||
// 2) 再读取路由 query 作为兜底(兼容历史调用)
|
||||
const biz = (query && query.biz) || preset.biz
|
||||
const type = (query && query.type) || preset.type
|
||||
if (biz === 'sale' || biz === 'purchase' || biz === 'income' || biz === 'expense') {
|
||||
this.biz = biz
|
||||
}
|
||||
@@ -214,6 +217,8 @@
|
||||
if (this.biz === 'purchase' && (type === 'in' || type === 'return' || type === 'pay')) {
|
||||
this.purchaseType = type
|
||||
}
|
||||
// 一次性参数,读取后清空
|
||||
try { uni.removeStorageSync('ORDER_DEFAULT_PARAMS') } catch(_) {}
|
||||
} catch(e) {}
|
||||
this.fetchCategories()
|
||||
},
|
||||
|
||||
0
frontend/pages/shell/index.vue
Normal file
0
frontend/pages/shell/index.vue
Normal file
@@ -1 +1 @@
|
||||
{"version":3,"file":"constants.js","sources":["common/constants.js"],"sourcesContent":["// 统一常量配置:其他收入/支出分类,禁止在业务中硬编码\r\nexport const INCOME_CATEGORIES = [\r\n\t{ key: 'sale_income', label: '销售收入' },\r\n\t{ key: 'operation_income', label: '经营所得' },\r\n\t{ key: 'interest_income', label: '利息收入' },\r\n\t{ key: 'investment_income', label: '投资收入' },\r\n\t{ key: 'other_income', label: '其它收入' }\r\n]\r\n\r\nexport const EXPENSE_CATEGORIES = [\r\n\t{ key: 'operation_expense', label: '经营支出' },\r\n\t{ key: 'office_supplies', label: '办公用品' },\r\n\t{ key: 'rent', label: '房租' },\r\n\t{ key: 'interest_expense', label: '利息支出' },\r\n\t{ key: 'other_expense', label: '其它支出' }\r\n]\r\n\r\n\r\n\r\n"],"names":[],"mappings":";AACY,MAAC,oBAAoB;AAAA,EAChC,EAAE,KAAK,eAAe,OAAO,OAAQ;AAAA,EACrC,EAAE,KAAK,oBAAoB,OAAO,OAAQ;AAAA,EAC1C,EAAE,KAAK,mBAAmB,OAAO,OAAQ;AAAA,EACzC,EAAE,KAAK,qBAAqB,OAAO,OAAQ;AAAA,EAC3C,EAAE,KAAK,gBAAgB,OAAO,OAAQ;AACvC;AAEY,MAAC,qBAAqB;AAAA,EACjC,EAAE,KAAK,qBAAqB,OAAO,OAAQ;AAAA,EAC3C,EAAE,KAAK,mBAAmB,OAAO,OAAQ;AAAA,EACzC,EAAE,KAAK,QAAQ,OAAO,KAAM;AAAA,EAC5B,EAAE,KAAK,oBAAoB,OAAO,OAAQ;AAAA,EAC1C,EAAE,KAAK,iBAAiB,OAAO,OAAQ;AACxC;;;"}
|
||||
{"version":3,"file":"constants.js","sources":["common/constants.js"],"sourcesContent":["// 统一常量配置:其他收入/支出分类,禁止在业务中硬编码\r\nexport const INCOME_CATEGORIES = [\r\n\t{ key: 'sale_income', label: '销售收入' },\r\n\t{ key: 'operation_income', label: '经营所得' },\r\n\t{ key: 'interest_income', label: '利息收入' },\r\n\t{ key: 'investment_income', label: '投资收入' },\r\n\t{ key: 'other_income', label: '其它收入' }\r\n]\r\n\r\nexport const EXPENSE_CATEGORIES = [\r\n\t{ key: 'operation_expense', label: '经营支出' },\r\n\t{ key: 'office_supplies', label: '办公用品' },\r\n\t{ key: 'rent', label: '房租' },\r\n\t{ key: 'interest_expense', label: '利息支出' },\r\n\t{ key: 'other_expense', label: '其它支出' }\r\n]\r\n\r\n// 路由常量(集中管理页面路径,避免在业务中硬编码)\r\nexport const ROUTES = {\r\n\thome: '/pages/index/index',\r\n\tproductList: '/pages/product/list',\r\n\tproductForm: '/pages/product/form',\r\n\tproductSelect: '/pages/product/select',\r\n\tproductSettings: '/pages/product/settings',\r\n\torderCreate: '/pages/order/create',\r\n\tdetail: '/pages/detail/index',\r\n\tmy: '/pages/my/index',\r\n\tmyAbout: '/pages/my/about',\r\n\treport: '/pages/report/index',\r\n\tcustomerSelect: '/pages/customer/select',\r\n\tsupplierSelect: '/pages/supplier/select',\r\n\taccountSelect: '/pages/account/select'\r\n}\r\n\r\n\r\n"],"names":[],"mappings":";AACY,MAAC,oBAAoB;AAAA,EAChC,EAAE,KAAK,eAAe,OAAO,OAAQ;AAAA,EACrC,EAAE,KAAK,oBAAoB,OAAO,OAAQ;AAAA,EAC1C,EAAE,KAAK,mBAAmB,OAAO,OAAQ;AAAA,EACzC,EAAE,KAAK,qBAAqB,OAAO,OAAQ;AAAA,EAC3C,EAAE,KAAK,gBAAgB,OAAO,OAAQ;AACvC;AAEY,MAAC,qBAAqB;AAAA,EACjC,EAAE,KAAK,qBAAqB,OAAO,OAAQ;AAAA,EAC3C,EAAE,KAAK,mBAAmB,OAAO,OAAQ;AAAA,EACzC,EAAE,KAAK,QAAQ,OAAO,KAAM;AAAA,EAC5B,EAAE,KAAK,oBAAoB,OAAO,OAAQ;AAAA,EAC1C,EAAE,KAAK,iBAAiB,OAAO,OAAQ;AACxC;AAGY,MAAC,SAAS;AAAA,EACrB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,eAAe;AAChB;;;;"}
|
||||
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
File diff suppressed because one or more lines are too long
38
frontend/unpackage/dist/dev/mp-weixin/app.json
vendored
38
frontend/unpackage/dist/dev/mp-weixin/app.json
vendored
@@ -27,5 +27,43 @@
|
||||
"navigationBarBackgroundColor": "#F8F8F8",
|
||||
"backgroundColor": "#F8F8F8"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#8a7535",
|
||||
"selectedColor": "#B4880F",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderStyle": "black",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "首页",
|
||||
"iconPath": "static/logo.png",
|
||||
"selectedIconPath": "static/logo.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/product/list",
|
||||
"text": "货品",
|
||||
"iconPath": "static/logo.png",
|
||||
"selectedIconPath": "static/logo.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/order/create",
|
||||
"text": "开单",
|
||||
"iconPath": "static/logo.png",
|
||||
"selectedIconPath": "static/logo.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/detail/index",
|
||||
"text": "明细",
|
||||
"iconPath": "static/logo.png",
|
||||
"selectedIconPath": "static/logo.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/my/index",
|
||||
"text": "我的",
|
||||
"iconPath": "static/logo.png",
|
||||
"selectedIconPath": "static/logo.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -13,6 +13,22 @@ const EXPENSE_CATEGORIES = [
|
||||
{ key: "interest_expense", label: "利息支出" },
|
||||
{ key: "other_expense", label: "其它支出" }
|
||||
];
|
||||
const ROUTES = {
|
||||
home: "/pages/index/index",
|
||||
productList: "/pages/product/list",
|
||||
productForm: "/pages/product/form",
|
||||
productSelect: "/pages/product/select",
|
||||
productSettings: "/pages/product/settings",
|
||||
orderCreate: "/pages/order/create",
|
||||
detail: "/pages/detail/index",
|
||||
my: "/pages/my/index",
|
||||
myAbout: "/pages/my/about",
|
||||
report: "/pages/report/index",
|
||||
customerSelect: "/pages/customer/select",
|
||||
supplierSelect: "/pages/supplier/select",
|
||||
accountSelect: "/pages/account/select"
|
||||
};
|
||||
exports.EXPENSE_CATEGORIES = EXPENSE_CATEGORIES;
|
||||
exports.INCOME_CATEGORIES = INCOME_CATEGORIES;
|
||||
exports.ROUTES = ROUTES;
|
||||
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/constants.js.map
|
||||
|
||||
@@ -125,7 +125,7 @@ const _sfc_main = {
|
||||
},
|
||||
onCreate() {
|
||||
if (this.biz === "sale") {
|
||||
common_vendor.index.navigateTo({ url: "/pages/order/create" });
|
||||
common_vendor.index.switchTab({ url: "/pages/order/create" });
|
||||
return;
|
||||
}
|
||||
common_vendor.index.showToast({ title: "该类型创建页待实现", icon: "none" });
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use strict";
|
||||
const common_vendor = require("../../common/vendor.js");
|
||||
const common_http = require("../../common/http.js");
|
||||
const common_constants = require("../../common/constants.js");
|
||||
const common_assets = require("../../common/assets.js");
|
||||
const _sfc_main = {
|
||||
data() {
|
||||
@@ -60,11 +61,15 @@ const _sfc_main = {
|
||||
},
|
||||
onFeatureTap(item) {
|
||||
if (item.key === "product") {
|
||||
common_vendor.index.navigateTo({ url: "/pages/product/list" });
|
||||
common_vendor.index.switchTab({ url: "/pages/product/list" });
|
||||
return;
|
||||
}
|
||||
if (item.key === "sale") {
|
||||
common_vendor.index.navigateTo({ url: "/pages/order/create?biz=sale&type=out" });
|
||||
try {
|
||||
common_vendor.index.setStorageSync("ORDER_DEFAULT_PARAMS", { biz: "sale", type: "out" });
|
||||
} catch (e) {
|
||||
}
|
||||
common_vendor.index.switchTab({ url: "/pages/order/create" });
|
||||
return;
|
||||
}
|
||||
if (item.key === "customer") {
|
||||
@@ -80,41 +85,42 @@ const _sfc_main = {
|
||||
return;
|
||||
}
|
||||
if (item.key === "purchase") {
|
||||
common_vendor.index.navigateTo({ url: "/pages/order/create?biz=purchase&type=in" });
|
||||
try {
|
||||
common_vendor.index.setStorageSync("ORDER_DEFAULT_PARAMS", { biz: "purchase", type: "in" });
|
||||
} catch (e) {
|
||||
}
|
||||
common_vendor.index.switchTab({ url: "/pages/order/create" });
|
||||
return;
|
||||
}
|
||||
if (item.key === "report") {
|
||||
common_vendor.index.navigateTo({ url: "/pages/report/index" });
|
||||
common_vendor.index.navigateTo({ url: common_constants.ROUTES.report });
|
||||
return;
|
||||
}
|
||||
if (item.key === "otherPay") {
|
||||
common_vendor.index.navigateTo({ url: "/pages/order/create?biz=expense" });
|
||||
try {
|
||||
common_vendor.index.setStorageSync("ORDER_DEFAULT_PARAMS", { biz: "expense" });
|
||||
} catch (e) {
|
||||
}
|
||||
common_vendor.index.switchTab({ url: "/pages/order/create" });
|
||||
return;
|
||||
}
|
||||
common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" });
|
||||
},
|
||||
goProduct() {
|
||||
this.activeTab = "product";
|
||||
common_vendor.index.navigateTo({ url: "/pages/product/list" });
|
||||
common_vendor.index.switchTab({ url: "/pages/product/list" });
|
||||
},
|
||||
onCreateOrder() {
|
||||
common_vendor.index.navigateTo({ url: "/pages/order/create" });
|
||||
common_vendor.index.switchTab({ url: "/pages/order/create" });
|
||||
},
|
||||
goDetail() {
|
||||
this.activeTab = "detail";
|
||||
try {
|
||||
common_vendor.index.__f__("log", "at pages/index/index.vue:199", "[index] goDetail → /pages/detail/index");
|
||||
common_vendor.index.__f__("log", "at pages/index/index.vue:177", "[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/index" });
|
||||
common_vendor.index.switchTab({ url: "/pages/detail/index" });
|
||||
},
|
||||
goMe() {
|
||||
this.activeTab = "me";
|
||||
common_vendor.index.navigateTo({ url: "/pages/my/index" });
|
||||
common_vendor.index.switchTab({ url: "/pages/my/index" });
|
||||
},
|
||||
onNoticeTap(n) {
|
||||
common_vendor.index.showModal({
|
||||
@@ -167,18 +173,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
||||
g: item.key,
|
||||
h: common_vendor.o(($event) => $options.onFeatureTap(item), item.key)
|
||||
});
|
||||
}),
|
||||
l: $data.activeTab === "home" ? 1 : "",
|
||||
m: common_vendor.o(($event) => $data.activeTab = "home"),
|
||||
n: $data.activeTab === "product" ? 1 : "",
|
||||
o: common_vendor.o((...args) => $options.goProduct && $options.goProduct(...args)),
|
||||
p: common_vendor.o((...args) => $options.onCreateOrder && $options.onCreateOrder(...args)),
|
||||
q: $data.activeTab === "detail" ? 1 : "",
|
||||
r: common_vendor.o((...args) => $options.goDetail && $options.goDetail(...args)),
|
||||
s: $data.activeTab === "report" ? 1 : "",
|
||||
t: common_vendor.o((...args) => $options.goReport && $options.goReport(...args)),
|
||||
v: $data.activeTab === "me" ? 1 : "",
|
||||
w: common_vendor.o((...args) => $options.goMe && $options.goMe(...args))
|
||||
})
|
||||
});
|
||||
}
|
||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
|
||||
|
||||
@@ -1 +1 @@
|
||||
<view class="home"><image class="home-bg" src="{{a}}" mode="aspectFill"></image><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi"><view class="kpi-item"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{b}}</text></view><view class="kpi-item"><text class="kpi-label">本月销售额</text><text class="kpi-value">{{c}}</text></view><view class="kpi-item"><text class="kpi-label">本月利润</text><text class="kpi-value">{{d}}</text></view><view class="kpi-item"><text class="kpi-label">库存商品数量</text><text class="kpi-value">{{e}}</text></view></view></view><view class="notice"><view class="notice-left">广告</view><view wx:if="{{f}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{g}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{h}}</view><view wx:elif="{{i}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{j}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="grid"><view wx:for="{{k}}" wx:for-item="item" wx:key="g" class="grid-item" bindtap="{{item.h}}"><view class="icon icon-squircle"><image wx:if="{{item.a}}" src="{{item.b}}" class="icon-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="icon-emoji">{{item.e}}</text><view wx:else class="icon-placeholder"></view></view><text class="grid-chip">{{item.f}}</text></view></view></view><view class="bottom-bar"><view class="{{['tab', l && 'active']}}" bindtap="{{m}}"><text>首页</text></view><view class="{{['tab', n && 'active']}}" bindtap="{{o}}"><text>货品</text></view><view class="tab primary" bindtap="{{p}}"><text>开单</text></view><view class="{{['tab', q && 'active']}}" bindtap="{{r}}"><text>明细</text></view><view class="{{['tab', s && 'active']}}" bindtap="{{t}}"><text>报表</text></view><view class="{{['tab', v && 'active']}}" bindtap="{{w}}"><text>我的</text></view></view></view>
|
||||
<view class="home"><image class="home-bg" src="{{a}}" mode="aspectFill"></image><view class="hero"><view class="hero-top"><text class="brand">五金配件管家</text><view class="cta"><text class="cta-text">咨询</text></view></view><view class="kpi"><view class="kpi-item"><text class="kpi-label">今日销售额</text><text class="kpi-value">{{b}}</text></view><view class="kpi-item"><text class="kpi-label">本月销售额</text><text class="kpi-value">{{c}}</text></view><view class="kpi-item"><text class="kpi-label">本月利润</text><text class="kpi-value">{{d}}</text></view><view class="kpi-item"><text class="kpi-label">库存商品数量</text><text class="kpi-value">{{e}}</text></view></view></view><view class="notice"><view class="notice-left">广告</view><view wx:if="{{f}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">加载中...</view><view wx:elif="{{g}}" class="notice-swiper" style="display:flex;align-items:center;color:#dd524d">{{h}}</view><view wx:elif="{{i}}" class="notice-swiper" style="display:flex;align-items:center;color:#6b5a2a">暂无公告</view><swiper wx:else class="notice-swiper" circular autoplay interval="4000" duration="400" vertical><swiper-item wx:for="{{j}}" wx:for-item="n" wx:key="e"><view class="notice-item" bindtap="{{n.d}}"><text class="notice-text">{{n.a}}</text><text wx:if="{{n.b}}" class="notice-tag">{{n.c}}</text></view></swiper-item></swiper></view><view class="section-title"><text class="section-text">常用功能</text></view><view class="grid-wrap"><view class="grid"><view wx:for="{{k}}" wx:for-item="item" wx:key="g" class="grid-item" bindtap="{{item.h}}"><view class="icon icon-squircle"><image wx:if="{{item.a}}" src="{{item.b}}" class="icon-img" mode="aspectFit" binderror="{{item.c}}"></image><text wx:elif="{{item.d}}" class="icon-emoji">{{item.e}}</text><view wx:else class="icon-placeholder"></view></view><text class="grid-chip">{{item.f}}</text></view></view></view></view>
|
||||
@@ -43,7 +43,7 @@ const _sfc_main = {
|
||||
common_vendor.index.showToast({ title: "VIP会员(开发中)", icon: "none" });
|
||||
},
|
||||
goMyOrders() {
|
||||
common_vendor.index.navigateTo({ url: "/pages/detail/index" });
|
||||
common_vendor.index.switchTab({ url: "/pages/detail/index" });
|
||||
},
|
||||
goSupplier() {
|
||||
common_vendor.index.navigateTo({ url: "/pages/supplier/select" });
|
||||
|
||||
@@ -70,8 +70,9 @@ const _sfc_main = {
|
||||
},
|
||||
onLoad(query) {
|
||||
try {
|
||||
const biz = query && query.biz;
|
||||
const type = query && query.type;
|
||||
const preset = common_vendor.index.getStorageSync("ORDER_DEFAULT_PARAMS") || {};
|
||||
const biz = query && query.biz || preset.biz;
|
||||
const type = query && query.type || preset.type;
|
||||
if (biz === "sale" || biz === "purchase" || biz === "income" || biz === "expense") {
|
||||
this.biz = biz;
|
||||
}
|
||||
@@ -81,6 +82,10 @@ const _sfc_main = {
|
||||
if (this.biz === "purchase" && (type === "in" || type === "return" || type === "pay")) {
|
||||
this.purchaseType = type;
|
||||
}
|
||||
try {
|
||||
common_vendor.index.removeStorageSync("ORDER_DEFAULT_PARAMS");
|
||||
} catch (_) {
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
this.fetchCategories();
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
"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
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"navigationBarTitleText": "报表",
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<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>
|
||||
@@ -1,13 +0,0 @@
|
||||
|
||||
.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;
|
||||
}
|
||||
4
frontend/unpackage/dist/dev/mp-weixin/pages/shell/index.json
vendored
Normal file
4
frontend/unpackage/dist/dev/mp-weixin/pages/shell/index.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"navigationBarTitleText": "五金配件管家",
|
||||
"usingComponents": {}
|
||||
}
|
||||
1
frontend/unpackage/dist/dev/mp-weixin/pages/shell/index.wxml
vendored
Normal file
1
frontend/unpackage/dist/dev/mp-weixin/pages/shell/index.wxml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<view class="shell"><keep-alive><component is="{{a}}" switch-tab="{{b}}"/></keep-alive><bottom-nav wx:if="{{d}}" bindchange="{{c}}" u-i="26537006-0" bind:__l="__l" u-p="{{d}}"/></view>
|
||||
5
frontend/unpackage/dist/dev/mp-weixin/project.private.config.json
vendored
Normal file
5
frontend/unpackage/dist/dev/mp-weixin/project.private.config.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"setting": {
|
||||
"urlCheck": false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user