This commit is contained in:
2025-09-17 14:40:16 +08:00
parent 46c5682960
commit a3bbc0098a
94 changed files with 3549 additions and 105 deletions

View File

@@ -3,10 +3,14 @@
const envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';
const storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';
const fallbackBaseUrl = 'http://localhost:8080';
const fallbackBaseUrl = 'http://192.168.31.193:8080';
export const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\/$/, '');
// 多地址候选(按优先级顺序,自动去重与去尾斜杠)
const candidateBases = [envBaseUrl, storageBaseUrl, fallbackBaseUrl, 'http://127.0.0.1:8080', 'http://localhost:8080'];
export const API_BASE_URL_CANDIDATES = Array.from(new Set(candidateBases.filter(Boolean))).map(u => String(u).replace(/\/$/, ''));
const envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';
const storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';
export const SHOP_ID = Number(envShopId || storageShopId || 1);
@@ -18,7 +22,7 @@ export const SHOP_ID = Number(envShopId || storageShopId || 1);
// - 生产默认关闭false开发可通过本地存储或环境变量开启
const envEnableDefaultUser = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER)) || '';
const storageEnableDefaultUser = typeof uni !== 'undefined' ? (uni.getStorageSync('ENABLE_DEFAULT_USER') || '') : '';
export const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || 'false').toLowerCase() === 'true';
export const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || 'true').toLowerCase() === 'true';
const envDefaultUserId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID)) || '';
const storageDefaultUserId = typeof uni !== 'undefined' ? (uni.getStorageSync('DEFAULT_USER_ID') || '') : '';

View File

@@ -16,3 +16,4 @@ export const EXPENSE_CATEGORIES = [
]

View File

@@ -1,4 +1,4 @@
import { API_BASE_URL, SHOP_ID, ENABLE_DEFAULT_USER, DEFAULT_USER_ID } from './config.js'
import { API_BASE_URL, API_BASE_URL_CANDIDATES, SHOP_ID, ENABLE_DEFAULT_USER, DEFAULT_USER_ID } from './config.js'
function buildUrl(path) {
if (!path) return API_BASE_URL
@@ -6,22 +6,26 @@ function buildUrl(path) {
return API_BASE_URL + (path.startsWith('/') ? path : '/' + path)
}
function requestWithFallback(options, candidates, idx, resolve, reject) {
const base = candidates[idx] || API_BASE_URL
const url = options.url.replace(/^https?:\/\/[^/]+/, base)
uni.request({ ...options, url, success: (res) => {
const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data)
if (idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject)
reject(new Error('HTTP ' + statusCode))
}, fail: (err) => {
if (idx + 1 < candidates.length) return requestWithFallback(options, candidates, idx + 1, resolve, reject)
reject(err)
} })
}
export function get(path, params = {}) {
return new Promise((resolve, reject) => {
const headers = { 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID
uni.request({
url: buildUrl(path),
method: 'GET',
data: params,
header: headers,
success: (res) => {
const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data)
reject(new Error('HTTP ' + statusCode))
},
fail: (err) => reject(err)
})
const options = { url: buildUrl(path), method: 'GET', data: params, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
@@ -30,19 +34,63 @@ export function post(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { 'Content-Type': 'application/json', 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID
uni.request({
url: buildUrl(path),
method: 'POST',
data: body,
header: headers,
success: (res) => {
const { statusCode, data } = res
if (statusCode >= 200 && statusCode < 300) return resolve(data)
reject(new Error('HTTP ' + statusCode))
},
fail: (err) => reject(err)
})
const options = { url: buildUrl(path), method: 'POST', data: body, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
export function put(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { 'Content-Type': 'application/json', 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID
const options = { url: buildUrl(path), method: 'PUT', data: body, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
export function del(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { 'Content-Type': 'application/json', 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) headers['X-User-Id'] = DEFAULT_USER_ID
const options = { url: buildUrl(path), method: 'DELETE', data: body, header: headers }
requestWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}
function uploadWithFallback(options, candidates, idx, resolve, reject) {
const base = candidates[idx] || API_BASE_URL
const url = options.url.replace(/^https?:\/\/[^/]+/, base)
const uploadOptions = { ...options, url }
uni.uploadFile({
...uploadOptions,
success: (res) => {
const statusCode = res.statusCode || 0
if (statusCode >= 200 && statusCode < 300) {
try {
const data = typeof res.data === 'string' ? JSON.parse(res.data) : res.data
return resolve(data)
} catch (e) {
return resolve(res.data)
}
}
if (idx + 1 < candidates.length) return uploadWithFallback(options, candidates, idx + 1, resolve, reject)
reject(new Error('HTTP ' + statusCode))
},
fail: (err) => {
if (idx + 1 < candidates.length) return uploadWithFallback(options, candidates, idx + 1, resolve, reject)
reject(err)
}
})
}
// 文件上传封装:自动注入租户/用户头并进行多地址回退
export function upload(path, filePath, formData = {}, name = 'file') {
return new Promise((resolve, reject) => {
const header = { 'X-Shop-Id': SHOP_ID }
if (ENABLE_DEFAULT_USER && DEFAULT_USER_ID) header['X-User-Id'] = DEFAULT_USER_ID
const options = { url: buildUrl(path), filePath, name, formData, header }
uploadWithFallback(options, API_BASE_URL_CANDIDATES, 0, resolve, reject)
})
}

View File

@@ -0,0 +1,164 @@
<template>
<view class="uploader">
<view class="grid" :style="{height: areaHeight+'rpx'}">
<movable-area class="area" :style="{height: areaHeight+'rpx'}">
<movable-view
v-for="(img, index) in innerList"
:key="img.uid"
class="cell"
:style="cellStyle(index)"
:direction="'all'"
:damping="40"
:friction="2"
:x="img.x"
:y="img.y"
@change="onMoving(index, $event)"
@touchend="onMoveEnd(index)"
>
<image :src="img.url" mode="aspectFill" class="thumb" @click="preview(index)" />
<view class="remove" @click.stop="remove(index)">×</view>
</movable-view>
<view v-if="innerList.length < max" class="adder" @click="choose">
<text></text>
</view>
</movable-area>
</view>
</view>
</template>
<script>
import { upload } from '../common/http.js'
const ITEM_SIZE = 210 // rpx
const GAP = 18 // rpx
const COLS = 3
function px(rpx) {
// 以 750 设计稿计算;此函数仅用于内部位置计算,不写入样式
return rpx
}
export default {
name: 'ImageUploader',
props: {
modelValue: { type: Array, default: () => [] },
max: { type: Number, default: 9 },
uploadPath: { type: String, default: '/api/attachments' },
uploadFieldName: { type: String, default: 'file' },
formData: { type: Object, default: () => ({ ownerType: 'product' }) }
},
data() {
return {
innerList: []
}
},
computed: {
areaHeight() {
const rows = Math.ceil((this.innerList.length + 1) / COLS) || 1
return rows * ITEM_SIZE + (rows - 1) * GAP
}
},
watch: {
modelValue: {
immediate: true,
handler(list) {
const mapped = (list || []).map((u, i) => ({
uid: String(i) + '_' + (u.id || u.url || Math.random().toString(36).slice(2)),
url: typeof u === 'string' ? u : (u.url || ''),
x: this.posOf(i).x,
y: this.posOf(i).y
}))
this.innerList = mapped
}
}
},
methods: {
posOf(index) {
const row = Math.floor(index / COLS)
const col = index % COLS
return { x: px(col * (ITEM_SIZE + GAP)), y: px(row * (ITEM_SIZE + GAP)) }
},
cellStyle(index) {
return {
width: ITEM_SIZE + 'rpx',
height: ITEM_SIZE + 'rpx'
}
},
preview(index) {
uni.previewImage({ urls: this.innerList.map(i => i.url), current: index })
},
remove(index) {
this.innerList.splice(index, 1)
this.reflow()
this.emit()
},
choose() {
const remain = this.max - this.innerList.length
if (remain <= 0) return
uni.chooseImage({ count: remain, success: async (res) => {
for (const path of res.tempFilePaths) {
await this.doUpload(path)
}
}})
},
async doUpload(filePath) {
try {
const resp = await upload(this.uploadPath, filePath, this.formData, this.uploadFieldName)
const url = resp?.url || resp?.data?.url || resp?.path || ''
if (!url) throw new Error('上传响应无 url')
this.innerList.push({ uid: Math.random().toString(36).slice(2), url, ...this.posOf(this.innerList.length) })
this.reflow()
this.emit()
} catch (e) {
uni.showToast({ title: '上传失败', icon: 'none' })
}
},
onMoving(index, e) {
// 实时更新移动中元素的位置
const { x, y } = e.detail
this.innerList[index].x = x
this.innerList[index].y = y
},
onMoveEnd(index) {
// 根据落点推算新的索引
const mv = this.innerList[index]
const col = Math.round(mv.x / (ITEM_SIZE + GAP))
const row = Math.round(mv.y / (ITEM_SIZE + GAP))
let newIndex = row * COLS + col
newIndex = Math.max(0, Math.min(newIndex, this.innerList.length - 1))
if (newIndex !== index) {
const moved = this.innerList.splice(index, 1)[0]
this.innerList.splice(newIndex, 0, moved)
}
this.reflow()
this.emit()
},
reflow() {
this.innerList.forEach((it, i) => {
const p = this.posOf(i)
it.x = p.x
it.y = p.y
})
},
emit() {
this.$emit('update:modelValue', this.innerList.map(i => i.url))
this.$emit('change', this.innerList.map(i => i.url))
}
}
}
</script>
<style>
.uploader { padding: 12rpx; background: #fff; }
.grid { position: relative; }
.area { width: 100%; position: relative; }
.cell { position: absolute; border-radius: 12rpx; overflow: hidden; box-shadow: 0 0 1rpx rgba(0,0,0,0.08); }
.thumb { width: 100%; height: 100%; }
.remove { position: absolute; right: 6rpx; top: 6rpx; background: rgba(0,0,0,0.45); color: #fff; width: 40rpx; height: 40rpx; text-align: center; line-height: 40rpx; border-radius: 20rpx; font-size: 28rpx; }
.adder { width: 210rpx; height: 210rpx; border: 2rpx dashed #ccc; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; color: #999; position: absolute; left: 0; top: 0; }
</style>

View File

@@ -18,6 +18,36 @@
"navigationBarTitleText": "选择商品"
}
},
{
"path": "pages/product/list",
"style": {
"navigationBarTitleText": "货品列表"
}
},
{
"path": "pages/product/form",
"style": {
"navigationBarTitleText": "编辑货品"
}
},
{
"path": "pages/product/categories",
"style": {
"navigationBarTitleText": "类别管理"
}
},
{
"path": "pages/product/units",
"style": {
"navigationBarTitleText": "单位管理"
}
},
{
"path": "pages/product/settings",
"style": {
"navigationBarTitleText": "货品设置"
}
},
{
"path": "pages/customer/select",
"style": {

View File

@@ -43,3 +43,4 @@
</style>

View File

@@ -48,3 +48,4 @@
</style>

View File

@@ -69,7 +69,7 @@
<view class="tab" :class="{ active: activeTab==='home' }" @click="activeTab='home'">
<text>首页</text>
</view>
<view class="tab" :class="{ active: activeTab==='product' }" @click="activeTab='product'">
<view class="tab" :class="{ active: activeTab==='product' }" @click="goProduct">
<text>货品</text>
</view>
<view class="tab primary" @click="onCreateOrder">
@@ -99,6 +99,7 @@
loadingNotices: false,
noticeError: '',
features: [
{ key: 'product', title: '货品', img: '/static/icons/product.png', emoji: '📦' },
{ key: 'customer', title: '客户', img: '/static/icons/customer.png', emoji: '👥' },
{ key: 'sale', title: '销售', img: '/static/icons/sale.png', emoji: '💰' },
{ key: 'account', title: '账户', img: '/static/icons/account.png', emoji: '💳' },
@@ -118,12 +119,14 @@
methods: {
async fetchMetrics() {
try {
const d = await get('/api/metrics/overview')
const d = await get('/api/dashboard/overview')
const toNum = v => (typeof v === 'number' ? v : Number(v || 0))
this.kpi = {
todaySales: (d && d.todaySales) || '0.00',
monthSales: (d && d.monthSales) || '0.00',
monthProfit: (d && d.monthProfit) || '0.00',
stockCount: (d && d.stockCount) || '0'
...this.kpi,
todaySales: toNum(d && d.todaySalesAmount).toFixed(2),
monthSales: toNum(d && d.monthSalesAmount).toFixed(2),
monthProfit: toNum(d && d.monthGrossProfit).toFixed(2),
stockCount: String((d && d.stockTotalQuantity) != null ? d.stockTotalQuantity : 0)
}
} catch (e) {
// 忽略错误,保留默认值
@@ -145,8 +148,16 @@
}
},
onFeatureTap(item) {
if (item.key === 'product') {
uni.navigateTo({ url: '/pages/product/list' })
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' })
},

View File

@@ -0,0 +1,67 @@
<template>
<view class="page">
<view class="toolbar">
<input v-model.trim="name" placeholder="新类别名称" />
<button size="mini" @click="create">新增</button>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="c in list" :key="c.id">
<input v-model.trim="c.name" />
<view class="ops">
<button size="mini" @click="update(c)">保存</button>
<button size="mini" type="warn" @click="remove(c)">删除</button>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get, post, put, del } from '../../common/http.js'
export default {
data() {
return { name: '', list: [] }
},
onLoad() { this.reload() },
methods: {
async reload() {
try {
const res = await get('/api/product-categories')
this.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
async create() {
if (!this.name) return
await post('/api/product-categories', { name: this.name })
this.name = ''
this.reload()
},
async update(c) {
await put('/api/product-categories/' + c.id, { name: c.name })
uni.showToast({ title: '已保存', icon: 'success' })
},
async remove(c) {
uni.showModal({ content: '确定删除该类别?', success: async (r) => {
if (!r.confirm) return
await del('/api/product-categories/' + c.id)
this.reload()
}})
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }
.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.list { flex:1; }
.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }
.ops { display:flex; gap: 10rpx; }
</style>

View File

@@ -0,0 +1,213 @@
<template>
<scroll-view scroll-y class="page">
<view class="card">
<view class="row">
<text class="label">商品名称</text>
<input v-model.trim="form.name" placeholder="必填" />
</view>
<view class="row">
<text class="label">条形码</text>
<input v-model.trim="form.barcode" placeholder="可扫码或输入" />
<!-- #ifdef APP-PLUS -->
<button size="mini" @click="scan">扫码</button>
<!-- #endif -->
</view>
<view class="row">
<text class="label">品牌/型号/规格/产地</text>
</view>
<view class="row">
<input v-model.trim="form.brand" placeholder="品牌" />
</view>
<view class="row">
<input v-model.trim="form.model" placeholder="型号" />
</view>
<view class="row">
<input v-model.trim="form.spec" placeholder="规格" />
</view>
<view class="row">
<input v-model.trim="form.origin" placeholder="产地" />
</view>
<view class="row">
<picker mode="selector" :range="unitNames" @change="onPickUnit">
<view class="picker">主单位{{ unitLabel }}</view>
</picker>
<picker mode="selector" :range="categoryNames" @change="onPickCategory">
<view class="picker">类别{{ categoryLabel }}</view>
</picker>
</view>
</view>
<view class="card">
<view class="row">
<text class="label">库存与安全库存</text>
</view>
<view class="row">
<input type="number" v-model.number="form.stock" placeholder="当前库存" />
<input type="number" v-model.number="form.safeMin" placeholder="安全库存下限" />
<input type="number" v-model.number="form.safeMax" placeholder="安全库存上限" />
</view>
</view>
<view class="card">
<view class="row">
<text class="label">价格进价/零售/批发/大单</text>
</view>
<view class="row prices">
<input type="number" v-model.number="form.purchasePrice" placeholder="进货价" />
<input type="number" v-model.number="form.retailPrice" placeholder="零售价" />
<input type="number" v-model.number="form.wholesalePrice" placeholder="批发价" />
<input type="number" v-model.number="form.bigClientPrice" placeholder="大单价" />
</view>
</view>
<view class="card">
<text class="label">图片</text>
<ImageUploader v-model="form.images" :formData="{ ownerType: 'product' }" />
</view>
<view class="card">
<text class="label">备注</text>
<textarea v-model.trim="form.remark" placeholder="可选" auto-height />
</view>
<view class="fixed">
<button type="default" @click="save(false)">保存</button>
<button type="primary" @click="save(true)">保存并继续</button>
</view>
</scroll-view>
</template>
<script>
import ImageUploader from '../../components/ImageUploader.vue'
import { get, post, put } from '../../common/http.js'
export default {
components: { ImageUploader },
data() {
return {
id: '',
form: {
name: '', barcode: '', brand: '', model: '', spec: '', origin: '',
categoryId: '', unitId: '',
stock: null, safeMin: null, safeMax: null,
purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null,
images: [], remark: ''
},
units: [],
categories: []
}
},
onLoad(query) {
this.id = query?.id || ''
this.bootstrap()
},
computed: {
unitNames() { return this.units.map(u => u.name) },
categoryNames() { return this.categories.map(c => c.name) },
unitLabel() {
const u = this.units.find(x => String(x.id) === String(this.form.unitId))
return u ? u.name : '选择单位'
},
categoryLabel() {
const c = this.categories.find(x => String(x.id) === String(this.form.categoryId))
return c ? c.name : '选择类别'
}
},
methods: {
async bootstrap() {
await Promise.all([this.fetchUnits(), this.fetchCategories()])
if (this.id) this.loadDetail()
},
async fetchUnits() {
try {
const res = await get('/api/product-units')
this.units = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
async fetchCategories() {
try {
const res = await get('/api/product-categories')
this.categories = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
onPickUnit(e) {
const idx = Number(e.detail.value); const u = this.units[idx]
this.form.unitId = u ? u.id : ''
},
onPickCategory(e) {
const idx = Number(e.detail.value); const c = this.categories[idx]
this.form.categoryId = c ? c.id : ''
},
scan() {
uni.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result
}})
},
async loadDetail() {
try {
const data = await get('/api/products/' + this.id)
Object.assign(this.form, {
name: data.name,
barcode: data.barcode, brand: data.brand, model: data.model, spec: data.spec, origin: data.origin,
categoryId: data.categoryId, unitId: data.unitId,
stock: data.stock,
safeMin: data.safeMin, safeMax: data.safeMax,
purchasePrice: data.purchasePrice, retailPrice: data.retailPrice,
wholesalePrice: data.wholesalePrice, bigClientPrice: data.bigClientPrice,
images: (data.images || []).map(i => i.url || i)
})
} catch (_) {}
},
validate() {
if (!this.form.name) { uni.showToast({ title: '请填写名称', icon: 'none' }); return false }
if (this.form.safeMin != null && this.form.safeMax != null && Number(this.form.safeMin) > Number(this.form.safeMax)) {
uni.showToast({ title: '安全库存区间不合法', icon: 'none' }); return false
}
return true
},
buildPayload() {
const f = this.form
return {
name: f.name, barcode: f.barcode, brand: f.brand, model: f.model, spec: f.spec, origin: f.origin,
categoryId: f.categoryId || null, unitId: f.unitId,
safeMin: f.safeMin, safeMax: f.safeMax,
prices: {
purchasePrice: f.purchasePrice, retailPrice: f.retailPrice, wholesalePrice: f.wholesalePrice, bigClientPrice: f.bigClientPrice
},
stock: f.stock,
images: f.images,
remark: f.remark
}
},
async save(goOn) {
if (!this.validate()) return
const payload = this.buildPayload()
try {
if (this.id) await put('/api/products/' + this.id, payload)
else await post('/api/products', payload)
uni.showToast({ title: '保存成功', icon: 'success' })
if (goOn && !this.id) {
this.form = { name: '', barcode: '', brand: '', model: '', spec: '', origin: '', categoryId: '', unitId: '', stock: null, safeMin: null, safeMax: null, purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null, images: [], remark: '' }
} else {
setTimeout(() => uni.navigateBack(), 400)
}
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
}
}
</script>
<style>
.page { background:#f6f6f6; height: 100vh; }
.card { background:#fff; margin: 16rpx; padding: 16rpx; border-radius: 12rpx; }
.row { display:flex; gap: 12rpx; align-items: center; margin-bottom: 12rpx; }
.label { width: 180rpx; color:#666; }
.row input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666; margin-left: 8rpx; }
.prices input { width: 30%; }
.fixed { position: fixed; left: 0; right: 0; bottom: 0; background:#fff; padding: 12rpx 16rpx; display:flex; gap: 16rpx; }
</style>

View File

@@ -0,0 +1,133 @@
<template>
<view class="page">
<view class="tabs">
<view class="tab" :class="{active: tab==='all'}" @click="switchTab('all')">全部</view>
<view class="tab" :class="{active: tab==='category'}" @click="switchTab('category')">按类别</view>
</view>
<view class="search">
<input v-model.trim="query.kw" placeholder="输入名称/条码/规格查询" @confirm="reload" />
<picker mode="selector" :range="categoryNames" v-if="tab==='category'" @change="onPickCategory">
<view class="picker">{{ categoryLabel }}</view>
</picker>
<button size="mini" @click="reload">查询</button>
</view>
<scroll-view scroll-y class="list" @scrolltolower="loadMore">
<block v-if="items.length">
<view class="item" v-for="it in items" :key="it.id" @click="openForm(it.id)">
<image v-if="it.cover" :src="it.cover" class="thumb" mode="aspectFill" />
<view class="content">
<view class="name">{{ it.name }}</view>
<view class="meta">{{ it.brand || '-' }} {{ it.model || '' }} {{ it.spec || '' }}</view>
<view class="meta">库存{{ it.stock ?? 0 }}
<text class="price">零售价¥{{ (it.retailPrice ?? it.price ?? 0).toFixed(2) }}</text>
</view>
</view>
</view>
</block>
<view v-else class="empty">
<text>暂无数据点击右上角新增</text>
</view>
</scroll-view>
<view class="fab" @click="openForm()"></view>
</view>
</template>
<script>
import { get } from '../../common/http.js'
export default {
data() {
return {
items: [],
query: { kw: '', page: 1, size: 20, categoryId: '' },
finished: false,
loading: false,
tab: 'all',
categories: []
}
},
onLoad() {
this.fetchCategories()
this.reload()
},
computed: {
categoryNames() { return this.categories.map(c => c.name) },
categoryLabel() {
const c = this.categories.find(x => String(x.id) === String(this.query.categoryId))
return c ? '类别:' + c.name : '选择类别'
}
},
methods: {
switchTab(t) {
this.tab = t
this.query.categoryId = ''
this.reload()
},
onPickCategory(e) {
const idx = Number(e.detail.value)
const c = this.categories[idx]
this.query.categoryId = c ? c.id : ''
this.reload()
},
async fetchCategories() {
try {
const res = await get('/api/product-categories', {})
this.categories = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
reload() {
this.items = []
this.query.page = 1
this.finished = false
this.loadMore()
},
async loadMore() {
if (this.loading || this.finished) return
this.loading = true
try {
const params = { kw: this.query.kw, page: this.query.page, size: this.query.size }
if (this.tab === 'category' && this.query.categoryId) params.categoryId = this.query.categoryId
const res = await get('/api/products', params)
const list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
this.items = this.items.concat(list)
if (list.length < this.query.size) this.finished = true
this.query.page += 1
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
this.loading = false
}
},
openForm(id) {
const url = '/pages/product/form' + (id ? ('?id=' + id) : '')
uni.navigateTo({ url })
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.tabs { display:flex; background:#fff; }
.tab { flex:1; text-align:center; padding: 20rpx 0; color:#666; }
.tab.active { color:#18b566; font-weight: 600; }
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; align-items: center; }
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666; }
.list { flex:1; }
.item { display:flex; padding: 20rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.thumb { width: 120rpx; height: 120rpx; border-radius: 12rpx; margin-right: 16rpx; background:#fafafa; }
.content { flex:1; }
.name { color:#333; margin-bottom: 6rpx; font-weight: 600; }
.meta { color:#888; font-size: 24rpx; }
.price { margin-left: 20rpx; color:#f60; }
.empty { height: 60vh; display:flex; align-items:center; justify-content:center; color:#999; }
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:#18b566; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15); }
</style>

View File

@@ -47,3 +47,4 @@
</style>

View File

@@ -0,0 +1,45 @@
<template>
<view class="page">
<view class="item">
<text>隐藏零库存商品</text>
<switch :checked="settings.hideZeroStock" @change="(e)=>update('hideZeroStock', e.detail.value)" />
</view>
<view class="item">
<text>隐藏进货价</text>
<switch :checked="settings.hidePurchasePrice" @change="(e)=>update('hidePurchasePrice', e.detail.value)" />
</view>
</view>
</template>
<script>
import { get, put } from '../../common/http.js'
export default {
data() {
return { settings: { hideZeroStock: false, hidePurchasePrice: false } }
},
onLoad() { this.load() },
methods: {
async load() {
try {
const res = await get('/api/product-settings')
this.settings = { hideZeroStock: !!res?.hideZeroStock, hidePurchasePrice: !!res?.hidePurchasePrice }
} catch (_) {}
},
async update(key, val) {
const next = { ...this.settings, [key]: val }
this.settings = next
try { await put('/api/product-settings', next) } catch (_) {}
}
}
}
</script>
<style>
.page { background:#fff; }
.item { display:flex; justify-content: space-between; align-items:center; padding: 20rpx; border-bottom: 1rpx solid #f1f1f1; }
</style>

View File

@@ -0,0 +1,67 @@
<template>
<view class="page">
<view class="toolbar">
<input v-model.trim="name" placeholder="新单位名称" />
<button size="mini" @click="create">新增</button>
</view>
<scroll-view scroll-y class="list">
<view class="item" v-for="u in list" :key="u.id">
<input v-model.trim="u.name" />
<view class="ops">
<button size="mini" @click="update(u)">保存</button>
<button size="mini" type="warn" @click="remove(u)">删除</button>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { get, post, put, del } from '../../common/http.js'
export default {
data() {
return { name: '', list: [] }
},
onLoad() { this.reload() },
methods: {
async reload() {
try {
const res = await get('/api/product-units')
this.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])
} catch (_) {}
},
async create() {
if (!this.name) return
await post('/api/product-units', { name: this.name })
this.name = ''
this.reload()
},
async update(u) {
await put('/api/product-units/' + u.id, { name: u.name })
uni.showToast({ title: '已保存', icon: 'success' })
},
async remove(u) {
uni.showModal({ content: '确定删除该单位?', success: async (r) => {
if (!r.confirm) return
await del('/api/product-units/' + u.id)
this.reload()
}})
}
}
}
</script>
<style>
.page { display:flex; flex-direction: column; height: 100vh; }
.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }
.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }
.list { flex:1; }
.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }
.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }
.ops { display:flex; gap: 10rpx; }
</style>

View File

@@ -48,3 +48,4 @@
</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"],"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;;;"}
{"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"],"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;;;"}

View File

@@ -1 +1 @@
{"version":3,"file":"config.js","sources":["common/config.js"],"sourcesContent":["// 统一配置:禁止在业务代码中硬编码\n// 优先级:环境变量(Vite/HBuilderX 构建注入) > 本地存储 > 默认值\n\nconst envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';\nconst storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';\nconst fallbackBaseUrl = 'http://localhost:8080';\n\nexport const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\\/$/, '');\n\nconst envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';\nconst storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';\nexport const SHOP_ID = Number(envShopId || storageShopId || 1);\n\n\n// 默认用户(可移除):\n// - 用途:开发/演示环境自动将用户固定为“张老板”id=2\n// - 开关优先级:环境变量 > 本地存储 > 默认值\n// - 生产默认关闭false开发可通过本地存储或环境变量开启\nconst envEnableDefaultUser = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER)) || '';\nconst storageEnableDefaultUser = typeof uni !== 'undefined' ? (uni.getStorageSync('ENABLE_DEFAULT_USER') || '') : '';\nexport const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || 'false').toLowerCase() === 'true';\n\nconst envDefaultUserId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID)) || '';\nconst storageDefaultUserId = typeof uni !== 'undefined' ? (uni.getStorageSync('DEFAULT_USER_ID') || '') : '';\nexport const DEFAULT_USER_ID = Number(envDefaultUserId || storageDefaultUserId || 2);\n\n\n"],"names":["uni"],"mappings":";;AAGA,MAAM,aAAc,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,yBAAyB,QAAQ,IAAI,iBAAkB;AACzI,MAAM,iBAAiB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,cAAc,KAAK,KAAM;AACjG,MAAM,kBAAkB;AAEZ,MAAC,gBAAgB,cAAc,kBAAkB,iBAAiB,QAAQ,OAAO,EAAE;AAE/F,MAAM,YAAa,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,YAAa;AAC9H,MAAM,gBAAgB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,SAAS,KAAK,KAAM;AAC/E,MAAC,UAAU,OAAO,aAAa,iBAAiB,CAAC;AAO7D,MAAM,uBAAwB,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,gCAAgC,QAAQ,IAAI,wBAAyB;AACjK,MAAM,2BAA2B,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,qBAAqB,KAAK,KAAM;AACtG,MAAC,sBAAsB,OAAO,wBAAwB,4BAA4B,OAAO,EAAE,YAAW,MAAO;AAEzH,MAAM,mBAAoB,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,IAAI,oBAAqB;AACrJ,MAAM,uBAAuB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,iBAAiB,KAAK,KAAM;AAC9F,MAAC,kBAAkB,OAAO,oBAAoB,wBAAwB,CAAC;;;;;"}
{"version":3,"file":"config.js","sources":["common/config.js"],"sourcesContent":["// 统一配置:禁止在业务代码中硬编码\n// 优先级:环境变量(Vite/HBuilderX 构建注入) > 本地存储 > 默认值\n\nconst envBaseUrl = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL)) || '';\nconst storageBaseUrl = typeof uni !== 'undefined' ? (uni.getStorageSync('API_BASE_URL') || '') : '';\nconst fallbackBaseUrl = 'http://192.168.31.193:8080';\n\nexport const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\\/$/, '');\n\n// 多地址候选(按优先级顺序,自动去重与去尾斜杠)\nconst candidateBases = [envBaseUrl, storageBaseUrl, fallbackBaseUrl, 'http://127.0.0.1:8080', 'http://localhost:8080'];\nexport const API_BASE_URL_CANDIDATES = Array.from(new Set(candidateBases.filter(Boolean))).map(u => String(u).replace(/\\/$/, ''));\n\nconst envShopId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID)) || '';\nconst storageShopId = typeof uni !== 'undefined' ? (uni.getStorageSync('SHOP_ID') || '') : '';\nexport const SHOP_ID = Number(envShopId || storageShopId || 1);\n\n\n// 默认用户(可移除):\n// - 用途:开发/演示环境自动将用户固定为“张老板”id=2\n// - 开关优先级:环境变量 > 本地存储 > 默认值\n// - 生产默认关闭false开发可通过本地存储或环境变量开启\nconst envEnableDefaultUser = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER)) || '';\nconst storageEnableDefaultUser = typeof uni !== 'undefined' ? (uni.getStorageSync('ENABLE_DEFAULT_USER') || '') : '';\nexport const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || 'true').toLowerCase() === 'true';\n\nconst envDefaultUserId = (typeof process !== 'undefined' && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID)) || '';\nconst storageDefaultUserId = typeof uni !== 'undefined' ? (uni.getStorageSync('DEFAULT_USER_ID') || '') : '';\nexport const DEFAULT_USER_ID = Number(envDefaultUserId || storageDefaultUserId || 2);\n\n\n"],"names":["uni"],"mappings":";;AAGA,MAAM,aAAc,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,yBAAyB,QAAQ,IAAI,iBAAkB;AACzI,MAAM,iBAAiB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,cAAc,KAAK,KAAM;AACjG,MAAM,kBAAkB;AAEZ,MAAC,gBAAgB,cAAc,kBAAkB,iBAAiB,QAAQ,OAAO,EAAE;AAG/F,MAAM,iBAAiB,CAAC,YAAY,gBAAgB,iBAAiB,yBAAyB,uBAAuB;AACzG,MAAC,0BAA0B,MAAM,KAAK,IAAI,IAAI,eAAe,OAAO,OAAO,CAAC,CAAC,EAAE,IAAI,OAAK,OAAO,CAAC,EAAE,QAAQ,OAAO,EAAE,CAAC;AAEhI,MAAM,YAAa,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI,YAAa;AAC9H,MAAM,gBAAgB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,SAAS,KAAK,KAAM;AAC/E,MAAC,UAAU,OAAO,aAAa,iBAAiB,CAAC;AAO7D,MAAM,uBAAwB,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,gCAAgC,QAAQ,IAAI,wBAAyB;AACjK,MAAM,2BAA2B,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,qBAAqB,KAAK,KAAM;AACtG,MAAC,sBAAsB,OAAO,wBAAwB,4BAA4B,MAAM,EAAE,YAAW,MAAO;AAExH,MAAM,mBAAoB,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,IAAI,4BAA4B,QAAQ,IAAI,oBAAqB;AACrJ,MAAM,uBAAuB,OAAOA,cAAG,UAAK,cAAeA,cAAAA,MAAI,eAAe,iBAAiB,KAAK,KAAM;AAC9F,MAAC,kBAAkB,OAAO,oBAAoB,wBAAwB,CAAC;;;;;;"}

View 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"],"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\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;;;"}

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","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</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;"}

View File

@@ -1 +1 @@
{"version":3,"file":"select.js","sources":["pages/customer/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY3VzdG9tZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索客户名称/电话\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"c in customers\" :key=\"c.id\" @click=\"select(c)\">\r\n\t\t\t\t<view class=\"name\">{{ c.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ c.mobile || '—' }}</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\texport default {\r\n\t\tdata() { return { kw: '', customers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/customers', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.customers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(c) {\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.order.customerId = c.id\r\n\t\t\t\t\topener.$vm.customerName = c.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\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.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\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","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/customer/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,WAAW,CAAA;EAAM;AAAA,EAC3C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,kBAAkB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AAC1E,aAAK,YAAY,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC5E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,MAAM,aAAa,EAAE;AAChC,eAAO,IAAI,eAAe,EAAE;AAAA,MAC7B;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}
{"version":3,"file":"select.js","sources":["pages/customer/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvY3VzdG9tZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索客户名称/电话\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"c in customers\" :key=\"c.id\" @click=\"select(c)\">\r\n\t\t\t\t<view class=\"name\">{{ c.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ c.mobile || '—' }}</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\texport default {\r\n\t\tdata() { return { kw: '', customers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/customers', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.customers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(c) {\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.order.customerId = c.id\r\n\t\t\t\t\topener.$vm.customerName = c.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\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.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\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/customer/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,WAAW,CAAA;EAAM;AAAA,EAC3C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,kBAAkB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AAC1E,aAAK,YAAY,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC5E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,MAAM,aAAa,EAAE;AAChC,eAAO,IAAI,eAAe,EAAE;AAAA,MAC7B;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"file":"categories.js","sources":["pages/product/categories.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9jYXRlZ29yaWVzLnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"toolbar\">\r\n\t\t\t<input v-model.trim=\"name\" placeholder=\"新类别名称\" />\r\n\t\t\t<button size=\"mini\" @click=\"create\">新增</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"c in list\" :key=\"c.id\">\r\n\t\t\t\t<input v-model.trim=\"c.name\" />\r\n\t\t\t\t<view class=\"ops\">\r\n\t\t\t\t\t<button size=\"mini\" @click=\"update(c)\">保存</button>\r\n\t\t\t\t\t<button size=\"mini\" type=\"warn\" @click=\"remove(c)\">删除</button>\r\n\t\t\t\t</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\nimport { get, post, put, del } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { name: '', list: [] }\r\n\t},\r\n\tonLoad() { this.reload() },\r\n\tmethods: {\r\n\t\tasync reload() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-categories')\r\n\t\t\t\tthis.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync create() {\r\n\t\t\tif (!this.name) return\r\n\t\t\tawait post('/api/product-categories', { name: this.name })\r\n\t\t\tthis.name = ''\r\n\t\t\tthis.reload()\r\n\t\t},\r\n\t\tasync update(c) {\r\n\t\t\tawait put('/api/product-categories/' + c.id, { name: c.name })\r\n\t\t\tuni.showToast({ title: '已保存', icon: 'success' })\r\n\t\t},\r\n\t\tasync remove(c) {\r\n\t\t\tuni.showModal({ content: '确定删除该类别?', success: async (r) => {\r\n\t\t\t\tif (!r.confirm) return\r\n\t\t\t\tawait del('/api/product-categories/' + c.id)\r\n\t\t\t\tthis.reload()\r\n\t\t\t}})\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { display:flex; flex-direction: column; height: 100vh; }\r\n.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n.list { flex:1; }\r\n.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }\r\n.ops { display:flex; gap: 10rpx; }\r\n</style>\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/categories.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","post","put","uni","del"],"mappings":";;;AAqBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,MAAM,IAAI,MAAM,CAAA,EAAG;AAAA,EAC5B;AAAA,EACD,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,yBAAyB;AAC/C,aAAK,OAAO,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eACtE,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,SAAS;AACd,UAAI,CAAC,KAAK;AAAM;AAChB,YAAMC,YAAAA,KAAK,2BAA2B,EAAE,MAAM,KAAK,KAAG,CAAG;AACzD,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACZ;AAAA,IACD,MAAM,OAAO,GAAG;AACf,YAAMC,YAAG,IAAC,6BAA6B,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM;AAC7DC,oBAAG,MAAC,UAAU,EAAE,OAAO,OAAO,MAAM,WAAW;AAAA,IAC/C;AAAA,IACD,MAAM,OAAO,GAAG;AACfA,oBAAG,MAAC,UAAU,EAAE,SAAS,YAAY,SAAS,OAAO,MAAM;AAC1D,YAAI,CAAC,EAAE;AAAS;AAChB,cAAMC,gBAAI,6BAA6B,EAAE,EAAE;AAC3C,aAAK,OAAO;AAAA,MACb,EAAC,CAAC;AAAA,IACH;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;AClDA,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

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"version":3,"file":"select.js","sources":["pages/product/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9zZWxlY3QudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索商品名称/编码\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"p in products\" :key=\"p.id\" @click=\"select(p)\">\r\n\t\t\t\t<view class=\"name\">{{ p.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ p.code }} · 库存:{{ p.stock || 0 }}</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\texport default {\r\n\t\tdata() { return { kw: '', products: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/products', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.products = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(p) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm && opener.$vm.items) {\r\n\t\t\t\t\topener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) })\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\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.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\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","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,UAAU,CAAA;EAAM;AAAA,EAC1C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,iBAAiB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AACzE,aAAK,WAAW,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC3E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,OAAO,OAAO,IAAI,OAAO;AAC7C,eAAO,IAAI,MAAM,KAAK,EAAE,WAAW,EAAE,IAAI,aAAa,EAAE,MAAM,UAAU,GAAG,WAAW,OAAO,EAAE,SAAS,CAAC,GAAG;AAAA,MAC7G;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;AClCD,GAAG,WAAW,eAAe;"}
{"version":3,"file":"select.js","sources":["pages/product/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9zZWxlY3QudnVl"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索商品名称/编码\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"p in products\" :key=\"p.id\" @click=\"select(p)\">\r\n\t\t\t\t<view class=\"name\">{{ p.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ p.code }} · 库存:{{ p.stock || 0 }}</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\texport default {\r\n\t\tdata() { return { kw: '', products: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/products', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.products = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(p) {\r\n\t\t\t\tconst opener = getCurrentPages()[getCurrentPages().length-2]\r\n\t\t\t\tif (opener && opener.$vm && opener.$vm.items) {\r\n\t\t\t\t\topener.$vm.items.push({ productId: p.id, productName: p.name, quantity: 1, unitPrice: Number(p.price || 0) })\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\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.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\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/product/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,UAAU,CAAA;EAAM;AAAA,EAC1C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,iBAAiB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AACzE,aAAK,WAAW,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC3E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,OAAO,OAAO,IAAI,OAAO;AAC7C,eAAO,IAAI,MAAM,KAAK,EAAE,WAAW,EAAE,IAAI,aAAa,EAAE,MAAM,UAAU,GAAG,WAAW,OAAO,EAAE,SAAS,CAAC,GAAG;AAAA,MAC7G;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;AClCD,GAAG,WAAW,eAAe;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"settings.js","sources":["pages/product/settings.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC9zZXR0aW5ncy52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"item\">\r\n\t\t\t<text>隐藏零库存商品</text>\r\n\t\t\t<switch :checked=\"settings.hideZeroStock\" @change=\"(e)=>update('hideZeroStock', e.detail.value)\" />\r\n\t\t</view>\r\n\t\t<view class=\"item\">\r\n\t\t\t<text>隐藏进货价</text>\r\n\t\t\t<switch :checked=\"settings.hidePurchasePrice\" @change=\"(e)=>update('hidePurchasePrice', e.detail.value)\" />\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport { get, put } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { settings: { hideZeroStock: false, hidePurchasePrice: false } }\r\n\t},\r\n\tonLoad() { this.load() },\r\n\tmethods: {\r\n\t\tasync load() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-settings')\r\n\t\t\t\tthis.settings = { hideZeroStock: !!res?.hideZeroStock, hidePurchasePrice: !!res?.hidePurchasePrice }\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync update(key, val) {\r\n\t\t\tconst next = { ...this.settings, [key]: val }\r\n\t\t\tthis.settings = next\r\n\t\t\ttry { await put('/api/product-settings', next) } catch (_) {}\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { background:#fff; }\r\n.item { display:flex; justify-content: space-between; align-items:center; padding: 20rpx; border-bottom: 1rpx solid #f1f1f1; }\r\n</style>\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/settings.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","put"],"mappings":";;;AAgBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,UAAU,EAAE,eAAe,OAAO,mBAAmB,QAAQ;AAAA,EACtE;AAAA,EACD,SAAS;AAAE,SAAK;EAAQ;AAAA,EACxB,SAAS;AAAA,IACR,MAAM,OAAO;AACZ,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,uBAAuB;AAC7C,aAAK,WAAW,EAAE,eAAe,CAAC,EAAC,2BAAK,gBAAe,mBAAmB,CAAC,EAAC,2BAAK,mBAAkB;AAAA,eAC3F,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,OAAO,KAAK,KAAK;AACtB,YAAM,OAAO,EAAE,GAAG,KAAK,UAAU,CAAC,GAAG,GAAG,IAAI;AAC5C,WAAK,WAAW;AAChB,UAAI;AAAE,cAAMC,YAAAA,IAAI,yBAAyB,IAAI;AAAA,MAAI,SAAO,GAAG;AAAA,MAAC;AAAA,IAC7D;AAAA,EACD;AACD;;;;;;;;;;ACjCA,GAAG,WAAW,eAAe;"}

View File

@@ -0,0 +1 @@
{"version":3,"file":"units.js","sources":["pages/product/units.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvcHJvZHVjdC91bml0cy52dWU"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"toolbar\">\r\n\t\t\t<input v-model.trim=\"name\" placeholder=\"新单位名称\" />\r\n\t\t\t<button size=\"mini\" @click=\"create\">新增</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"u in list\" :key=\"u.id\">\r\n\t\t\t\t<input v-model.trim=\"u.name\" />\r\n\t\t\t\t<view class=\"ops\">\r\n\t\t\t\t\t<button size=\"mini\" @click=\"update(u)\">保存</button>\r\n\t\t\t\t\t<button size=\"mini\" type=\"warn\" @click=\"remove(u)\">删除</button>\r\n\t\t\t\t</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\nimport { get, post, put, del } from '../../common/http.js'\r\n\r\nexport default {\r\n\tdata() {\r\n\t\treturn { name: '', list: [] }\r\n\t},\r\n\tonLoad() { this.reload() },\r\n\tmethods: {\r\n\t\tasync reload() {\r\n\t\t\ttry {\r\n\t\t\t\tconst res = await get('/api/product-units')\r\n\t\t\t\tthis.list = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t} catch (_) {}\r\n\t\t},\r\n\t\tasync create() {\r\n\t\t\tif (!this.name) return\r\n\t\t\tawait post('/api/product-units', { name: this.name })\r\n\t\t\tthis.name = ''\r\n\t\t\tthis.reload()\r\n\t\t},\r\n\t\tasync update(u) {\r\n\t\t\tawait put('/api/product-units/' + u.id, { name: u.name })\r\n\t\t\tuni.showToast({ title: '已保存', icon: 'success' })\r\n\t\t},\r\n\t\tasync remove(u) {\r\n\t\t\tuni.showModal({ content: '确定删除该单位?', success: async (r) => {\r\n\t\t\t\tif (!r.confirm) return\r\n\t\t\t\tawait del('/api/product-units/' + u.id)\r\n\t\t\t\tthis.reload()\r\n\t\t\t}})\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style>\r\n.page { display:flex; flex-direction: column; height: 100vh; }\r\n.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\r\n.list { flex:1; }\r\n.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1; }\r\n.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx; }\r\n.ops { display:flex; gap: 10rpx; }\r\n</style>\r\n\r\n\r\n\r\n\r\n","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/product/units.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","post","put","uni","del"],"mappings":";;;AAqBA,MAAK,YAAU;AAAA,EACd,OAAO;AACN,WAAO,EAAE,MAAM,IAAI,MAAM,CAAA,EAAG;AAAA,EAC5B;AAAA,EACD,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,YAAG,IAAC,oBAAoB;AAC1C,aAAK,OAAO,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eACtE,GAAG;AAAA,MAAC;AAAA,IACb;AAAA,IACD,MAAM,SAAS;AACd,UAAI,CAAC,KAAK;AAAM;AAChB,YAAMC,YAAAA,KAAK,sBAAsB,EAAE,MAAM,KAAK,KAAG,CAAG;AACpD,WAAK,OAAO;AACZ,WAAK,OAAO;AAAA,IACZ;AAAA,IACD,MAAM,OAAO,GAAG;AACf,YAAMC,YAAG,IAAC,wBAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM;AACxDC,oBAAG,MAAC,UAAU,EAAE,OAAO,OAAO,MAAM,WAAW;AAAA,IAC/C;AAAA,IACD,MAAM,OAAO,GAAG;AACfA,oBAAG,MAAC,UAAU,EAAE,SAAS,YAAY,SAAS,OAAO,MAAM;AAC1D,YAAI,CAAC,EAAE;AAAS;AAChB,cAAMC,gBAAI,wBAAwB,EAAE,EAAE;AACtC,aAAK,OAAO;AAAA,MACb,EAAC,CAAC;AAAA,IACH;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;;;;;AClDA,GAAG,WAAW,eAAe;"}

View File

@@ -1 +1 @@
{"version":3,"file":"select.js","sources":["pages/supplier/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvc3VwcGxpZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索供应商名称/电话\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"s in suppliers\" :key=\"s.id\" @click=\"select(s)\">\r\n\t\t\t\t<view class=\"name\">{{ s.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ s.mobile || '—' }}</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\texport default {\r\n\t\tdata() { return { kw: '', suppliers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/suppliers', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.suppliers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(s) {\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.order.supplierId = s.id\r\n\t\t\t\t\topener.$vm.supplierName = s.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\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.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\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","import MiniProgramPage from 'C:/Users/21826/Desktop/Wj/PartsInquiry/frontend/pages/supplier/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,WAAW,CAAA;EAAM;AAAA,EAC3C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,kBAAkB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AAC1E,aAAK,YAAY,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC5E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,MAAM,aAAa,EAAE;AAChC,eAAO,IAAI,eAAe,EAAE;AAAA,MAC7B;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}
{"version":3,"file":"select.js","sources":["pages/supplier/select.vue","../../../../Downloads/HBuilderX.4.76.2025082103/HBuilderX/plugins/uniapp-cli-vite/uniPage:/cGFnZXMvc3VwcGxpZXIvc2VsZWN0LnZ1ZQ"],"sourcesContent":["<template>\r\n\t<view class=\"page\">\r\n\t\t<view class=\"search\">\r\n\t\t\t<input v-model=\"kw\" placeholder=\"搜索供应商名称/电话\" @confirm=\"search\" />\r\n\t\t\t<button size=\"mini\" @click=\"search\">搜索</button>\r\n\t\t</view>\r\n\t\t<scroll-view scroll-y class=\"list\">\r\n\t\t\t<view class=\"item\" v-for=\"s in suppliers\" :key=\"s.id\" @click=\"select(s)\">\r\n\t\t\t\t<view class=\"name\">{{ s.name }}</view>\r\n\t\t\t\t<view class=\"meta\">{{ s.mobile || '—' }}</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\texport default {\r\n\t\tdata() { return { kw: '', suppliers: [] } },\r\n\t\tonLoad() { this.search() },\r\n\t\tmethods: {\r\n\t\t\tasync search() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tconst res = await get('/api/suppliers', { kw: this.kw, page: 1, size: 50 })\r\n\t\t\t\t\tthis.suppliers = Array.isArray(res?.list) ? res.list : (Array.isArray(res) ? res : [])\r\n\t\t\t\t} catch(e) { uni.showToast({ title: '加载失败', icon: 'none' }) }\r\n\t\t\t},\r\n\t\t\tselect(s) {\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.order.supplierId = s.id\r\n\t\t\t\t\topener.$vm.supplierName = s.name\r\n\t\t\t\t}\r\n\t\t\t\tuni.navigateBack()\r\n\t\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.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; }\r\n\t.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx; }\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/supplier/select.vue'\nwx.createPage(MiniProgramPage)"],"names":["get","uni"],"mappings":";;;AAiBC,MAAK,YAAU;AAAA,EACd,OAAO;AAAE,WAAO,EAAE,IAAI,IAAI,WAAW,CAAA;EAAM;AAAA,EAC3C,SAAS;AAAE,SAAK;EAAU;AAAA,EAC1B,SAAS;AAAA,IACR,MAAM,SAAS;AACd,UAAI;AACH,cAAM,MAAM,MAAMA,gBAAI,kBAAkB,EAAE,IAAI,KAAK,IAAI,MAAM,GAAG,MAAM,GAAC,CAAG;AAC1E,aAAK,YAAY,MAAM,QAAQ,2BAAK,IAAI,IAAI,IAAI,OAAQ,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAA;AAAA,eAC5E,GAAG;AAAEC,sBAAAA,MAAI,UAAU,EAAE,OAAO,QAAQ,MAAM,OAAO,CAAC;AAAA,MAAE;AAAA,IAC5D;AAAA,IACD,OAAO,GAAG;AACT,YAAM,SAAS,gBAAiB,EAAC,gBAAe,EAAG,SAAO,CAAC;AAC3D,UAAI,UAAU,OAAO,KAAK;AACzB,eAAO,IAAI,MAAM,aAAa,EAAE;AAChC,eAAO,IAAI,eAAe,EAAE;AAAA,MAC7B;AACAA,oBAAAA,MAAI,aAAa;AAAA,IAClB;AAAA,EACD;AACD;;;;;;;;;;;;;;;;;;ACnCD,GAAG,WAAW,eAAe;"}

View File

@@ -5,6 +5,11 @@ if (!Math) {
"./pages/index/index.js";
"./pages/order/create.js";
"./pages/product/select.js";
"./pages/product/list.js";
"./pages/product/form.js";
"./pages/product/categories.js";
"./pages/product/units.js";
"./pages/product/settings.js";
"./pages/customer/select.js";
"./pages/supplier/select.js";
"./pages/account/select.js";

View File

@@ -3,6 +3,11 @@
"pages/index/index",
"pages/order/create",
"pages/product/select",
"pages/product/list",
"pages/product/form",
"pages/product/categories",
"pages/product/units",
"pages/product/settings",
"pages/customer/select",
"pages/supplier/select",
"pages/account/select"

View File

@@ -2,18 +2,21 @@
const common_vendor = require("./vendor.js");
const envBaseUrl = typeof process !== "undefined" && process.env && (process.env.VITE_APP_API_BASE_URL || process.env.API_BASE_URL) || "";
const storageBaseUrl = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("API_BASE_URL") || "" : "";
const fallbackBaseUrl = "http://localhost:8080";
const fallbackBaseUrl = "http://192.168.31.193:8080";
const API_BASE_URL = (envBaseUrl || storageBaseUrl || fallbackBaseUrl).replace(/\/$/, "");
const candidateBases = [envBaseUrl, storageBaseUrl, fallbackBaseUrl, "http://127.0.0.1:8080", "http://localhost:8080"];
const API_BASE_URL_CANDIDATES = Array.from(new Set(candidateBases.filter(Boolean))).map((u) => String(u).replace(/\/$/, ""));
const envShopId = typeof process !== "undefined" && process.env && (process.env.VITE_APP_SHOP_ID || process.env.SHOP_ID) || "";
const storageShopId = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("SHOP_ID") || "" : "";
const SHOP_ID = Number(envShopId || storageShopId || 1);
const envEnableDefaultUser = typeof process !== "undefined" && process.env && (process.env.VITE_APP_ENABLE_DEFAULT_USER || process.env.ENABLE_DEFAULT_USER) || "";
const storageEnableDefaultUser = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("ENABLE_DEFAULT_USER") || "" : "";
const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || "false").toLowerCase() === "true";
const ENABLE_DEFAULT_USER = String(envEnableDefaultUser || storageEnableDefaultUser || "true").toLowerCase() === "true";
const envDefaultUserId = typeof process !== "undefined" && process.env && (process.env.VITE_APP_DEFAULT_USER_ID || process.env.DEFAULT_USER_ID) || "";
const storageDefaultUserId = typeof common_vendor.index !== "undefined" ? common_vendor.index.getStorageSync("DEFAULT_USER_ID") || "" : "";
const DEFAULT_USER_ID = Number(envDefaultUserId || storageDefaultUserId || 2);
exports.API_BASE_URL = API_BASE_URL;
exports.API_BASE_URL_CANDIDATES = API_BASE_URL_CANDIDATES;
exports.DEFAULT_USER_ID = DEFAULT_USER_ID;
exports.ENABLE_DEFAULT_USER = ENABLE_DEFAULT_USER;
exports.SHOP_ID = SHOP_ID;

View File

@@ -8,24 +8,29 @@ function buildUrl(path) {
return path;
return common_config.API_BASE_URL + (path.startsWith("/") ? path : "/" + path);
}
function requestWithFallback(options, candidates, idx, resolve, reject) {
const base = candidates[idx] || common_config.API_BASE_URL;
const url = options.url.replace(/^https?:\/\/[^/]+/, base);
common_vendor.index.request({ ...options, url, success: (res) => {
const { statusCode, data } = res;
if (statusCode >= 200 && statusCode < 300)
return resolve(data);
if (idx + 1 < candidates.length)
return requestWithFallback(options, candidates, idx + 1, resolve, reject);
reject(new Error("HTTP " + statusCode));
}, fail: (err) => {
if (idx + 1 < candidates.length)
return requestWithFallback(options, candidates, idx + 1, resolve, reject);
reject(err);
} });
}
function get(path, params = {}) {
return new Promise((resolve, reject) => {
const headers = { "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
headers["X-User-Id"] = common_config.DEFAULT_USER_ID;
common_vendor.index.request({
url: buildUrl(path),
method: "GET",
data: params,
header: headers,
success: (res) => {
const { statusCode, data } = res;
if (statusCode >= 200 && statusCode < 300)
return resolve(data);
reject(new Error("HTTP " + statusCode));
},
fail: (err) => reject(err)
});
const options = { url: buildUrl(path), method: "GET", data: params, header: headers };
requestWithFallback(options, common_config.API_BASE_URL_CANDIDATES, 0, resolve, reject);
});
}
function post(path, body = {}) {
@@ -33,21 +38,67 @@ function post(path, body = {}) {
const headers = { "Content-Type": "application/json", "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
headers["X-User-Id"] = common_config.DEFAULT_USER_ID;
common_vendor.index.request({
url: buildUrl(path),
method: "POST",
data: body,
header: headers,
success: (res) => {
const { statusCode, data } = res;
if (statusCode >= 200 && statusCode < 300)
return resolve(data);
reject(new Error("HTTP " + statusCode));
},
fail: (err) => reject(err)
});
const options = { url: buildUrl(path), method: "POST", data: body, header: headers };
requestWithFallback(options, common_config.API_BASE_URL_CANDIDATES, 0, resolve, reject);
});
}
function put(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { "Content-Type": "application/json", "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
headers["X-User-Id"] = common_config.DEFAULT_USER_ID;
const options = { url: buildUrl(path), method: "PUT", data: body, header: headers };
requestWithFallback(options, common_config.API_BASE_URL_CANDIDATES, 0, resolve, reject);
});
}
function del(path, body = {}) {
return new Promise((resolve, reject) => {
const headers = { "Content-Type": "application/json", "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
headers["X-User-Id"] = common_config.DEFAULT_USER_ID;
const options = { url: buildUrl(path), method: "DELETE", data: body, header: headers };
requestWithFallback(options, common_config.API_BASE_URL_CANDIDATES, 0, resolve, reject);
});
}
function uploadWithFallback(options, candidates, idx, resolve, reject) {
const base = candidates[idx] || common_config.API_BASE_URL;
const url = options.url.replace(/^https?:\/\/[^/]+/, base);
const uploadOptions = { ...options, url };
common_vendor.index.uploadFile({
...uploadOptions,
success: (res) => {
const statusCode = res.statusCode || 0;
if (statusCode >= 200 && statusCode < 300) {
try {
const data = typeof res.data === "string" ? JSON.parse(res.data) : res.data;
return resolve(data);
} catch (e) {
return resolve(res.data);
}
}
if (idx + 1 < candidates.length)
return uploadWithFallback(options, candidates, idx + 1, resolve, reject);
reject(new Error("HTTP " + statusCode));
},
fail: (err) => {
if (idx + 1 < candidates.length)
return uploadWithFallback(options, candidates, idx + 1, resolve, reject);
reject(err);
}
});
}
function upload(path, filePath, formData = {}, name = "file") {
return new Promise((resolve, reject) => {
const header = { "X-Shop-Id": common_config.SHOP_ID };
if (common_config.ENABLE_DEFAULT_USER && common_config.DEFAULT_USER_ID)
header["X-User-Id"] = common_config.DEFAULT_USER_ID;
const options = { url: buildUrl(path), filePath, name, formData, header };
uploadWithFallback(options, common_config.API_BASE_URL_CANDIDATES, 0, resolve, reject);
});
}
exports.del = del;
exports.get = get;
exports.post = post;
exports.put = put;
exports.upload = upload;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/http.js.map

View File

@@ -68,8 +68,8 @@ const capitalize = cacheStringFunction((str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
});
const toHandlerKey = cacheStringFunction((str) => {
const s = str ? `on${capitalize(str)}` : ``;
return s;
const s2 = str ? `on${capitalize(str)}` : ``;
return s2;
});
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
const invokeArrayFns$1 = (fns, arg) => {
@@ -92,6 +92,36 @@ const toNumber = (val) => {
const n = isString(val) ? Number(val) : NaN;
return isNaN(n) ? val : n;
};
function normalizeStyle(value) {
if (isArray(value)) {
const res = {};
for (let i = 0; i < value.length; i++) {
const item = value[i];
const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item);
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key];
}
}
}
return res;
} else if (isString(value) || isObject(value)) {
return value;
}
}
const listDelimiterRE = /;(?![^(]*\))/g;
const propertyDelimiterRE = /:([^]+)/;
const styleCommentRE = /\/\*[^]*?\*\//g;
function parseStringStyle(cssText) {
const ret = {};
cssText.replace(styleCommentRE, "").split(listDelimiterRE).forEach((item) => {
if (item) {
const tmp = item.split(propertyDelimiterRE);
tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());
}
});
return ret;
}
const toDisplayString = (val) => {
return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? JSON.stringify(val, replacer, 2) : String(val);
};
@@ -1287,6 +1317,9 @@ function isReadonly(value) {
function isShallow(value) {
return !!(value && value["__v_isShallow"]);
}
function isProxy(value) {
return isReactive(value) || isReadonly(value);
}
function toRaw(observed) {
const raw = observed && observed["__v_raw"];
return raw ? toRaw(raw) : observed;
@@ -2078,6 +2111,47 @@ function setCurrentRenderingInstance(instance) {
instance && instance.type.__scopeId || null;
return prev;
}
const COMPONENTS = "components";
function resolveComponent(name, maybeSelfReference) {
return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name;
}
function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) {
const instance = currentRenderingInstance || currentInstance;
if (instance) {
const Component2 = instance.type;
if (type === COMPONENTS) {
const selfName = getComponentName(
Component2,
false
);
if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) {
return Component2;
}
}
const res = (
// local registration
// check instance[type] first which is resolved for options API
resolve(instance[type] || Component2[type], name) || // global registration
resolve(instance.appContext[type], name)
);
if (!res && maybeSelfReference) {
return Component2;
}
if (warnMissing && !res) {
const extra = type === COMPONENTS ? `
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``;
warn$1(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`);
}
return res;
} else {
warn$1(
`resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().`
);
}
}
function resolve(registry, name) {
return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]);
}
const INITIAL_WATCHER_VALUE = {};
function watch(source, cb, options) {
if (!isFunction(cb)) {
@@ -3692,6 +3766,12 @@ const Static = Symbol.for("v-stc");
function isVNode(value) {
return value ? value.__v_isVNode === true : false;
}
const InternalObjectKey = `__vInternal`;
function guardReactiveProps(props) {
if (!props)
return null;
return isProxy(props) || InternalObjectKey in props ? extend({}, props) : props;
}
const emptyAppContext = createAppContext();
let uid = 0;
function createComponentInstance(vnode, parent, suspense) {
@@ -4932,6 +5012,11 @@ function initApp(app) {
}
}
const propsCaches = /* @__PURE__ */ Object.create(null);
function renderProps(props) {
const { uid: uid2, __counter } = getCurrentInstance();
const propsId = (propsCaches[uid2] || (propsCaches[uid2] = [])).push(guardReactiveProps(props)) - 1;
return uid2 + "," + propsId + "," + __counter;
}
function pruneComponentPropsCache(uid2) {
delete propsCaches[uid2];
}
@@ -4972,6 +5057,22 @@ function getCreateApp() {
return my[method];
}
}
function stringifyStyle(value) {
if (isString(value)) {
return value;
}
return stringify(normalizeStyle(value));
}
function stringify(styles) {
let ret = "";
if (!styles || isString(styles)) {
return ret;
}
for (const key in styles) {
ret += `${key.startsWith(`--`) ? key : hyphenate(key)}:${styles[key]};`;
}
return ret;
}
function vOn(value, key) {
const instance = getCurrentInstance();
const ctx = instance.ctx;
@@ -5121,8 +5222,10 @@ function withModelModifiers(fn, { number, trim }, isComponent = false) {
}
const o = (value, key) => vOn(value, key);
const f = (source, renderItem) => vFor(source, renderItem);
const s = (value) => stringifyStyle(value);
const e = (target, ...sources) => extend(target, ...sources);
const t = (val) => toDisplayString(val);
const p = (props) => renderProps(props);
const m = (fn, modifiers, isComponent = false) => withModelModifiers(fn, modifiers, isComponent);
function createApp$1(rootComponent, rootProps = null) {
rootComponent && (rootComponent.mpType = "app");
@@ -5445,8 +5548,8 @@ function promisify$1(name, fn) {
if (hasCallback(args)) {
return wrapperReturnValue(name, invokeApi(name, fn, extend({}, args), rest));
}
return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
invokeApi(name, fn, extend({}, args, { success: resolve, fail: reject }), rest);
return wrapperReturnValue(name, handlePromise(new Promise((resolve2, reject) => {
invokeApi(name, fn, extend({}, args, { success: resolve2, fail: reject }), rest);
})));
};
}
@@ -5767,7 +5870,7 @@ function invokeGetPushCidCallbacks(cid2, errMsg) {
getPushCidCallbacks.length = 0;
}
const API_GET_PUSH_CLIENT_ID = "getPushClientId";
const getPushClientId = defineAsyncApi(API_GET_PUSH_CLIENT_ID, (_, { resolve, reject }) => {
const getPushClientId = defineAsyncApi(API_GET_PUSH_CLIENT_ID, (_, { resolve: resolve2, reject }) => {
Promise.resolve().then(() => {
if (typeof enabled === "undefined") {
enabled = false;
@@ -5776,7 +5879,7 @@ const getPushClientId = defineAsyncApi(API_GET_PUSH_CLIENT_ID, (_, { resolve, re
}
getPushCidCallbacks.push((cid2, errMsg) => {
if (cid2) {
resolve({ cid: cid2 });
resolve2({ cid: cid2 });
} else {
reject(errMsg);
}
@@ -5845,9 +5948,9 @@ function promisify(name, api) {
if (isFunction(options.success) || isFunction(options.fail) || isFunction(options.complete)) {
return wrapperReturnValue(name, invokeApi(name, api, extend({}, options), rest));
}
return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
return wrapperReturnValue(name, handlePromise(new Promise((resolve2, reject) => {
invokeApi(name, api, extend({}, options, {
success: resolve,
success: resolve2,
fail: reject
}), rest);
})));
@@ -6454,13 +6557,13 @@ function initRuntimeSocket(hosts, port, id) {
}
const SOCKET_TIMEOUT = 500;
function tryConnectSocket(host2, port, id) {
return new Promise((resolve, reject) => {
return new Promise((resolve2, reject) => {
const socket = index.connectSocket({
url: `ws://${host2}:${port}/${id}`,
multiple: true,
// 支付宝小程序 是否开启多实例
fail() {
resolve(null);
resolve2(null);
}
});
const timer = setTimeout(() => {
@@ -6468,19 +6571,19 @@ function tryConnectSocket(host2, port, id) {
code: 1006,
reason: "connect timeout"
});
resolve(null);
resolve2(null);
}, SOCKET_TIMEOUT);
socket.onOpen((e2) => {
clearTimeout(timer);
resolve(socket);
resolve2(socket);
});
socket.onClose((e2) => {
clearTimeout(timer);
resolve(null);
resolve2(null);
});
socket.onError((e2) => {
clearTimeout(timer);
resolve(null);
resolve2(null);
});
});
}
@@ -7898,5 +8001,8 @@ exports.f = f;
exports.index = index;
exports.m = m;
exports.o = o;
exports.p = p;
exports.resolveComponent = resolveComponent;
exports.s = s;
exports.t = t;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/vendor.js.map

View File

@@ -0,0 +1,144 @@
"use strict";
const common_vendor = require("../common/vendor.js");
const common_http = require("../common/http.js");
const ITEM_SIZE = 210;
const GAP = 18;
const COLS = 3;
function px(rpx) {
return rpx;
}
const _sfc_main = {
name: "ImageUploader",
props: {
modelValue: { type: Array, default: () => [] },
max: { type: Number, default: 9 },
uploadPath: { type: String, default: "/api/attachments" },
uploadFieldName: { type: String, default: "file" },
formData: { type: Object, default: () => ({ ownerType: "product" }) }
},
data() {
return {
innerList: []
};
},
computed: {
areaHeight() {
const rows = Math.ceil((this.innerList.length + 1) / COLS) || 1;
return rows * ITEM_SIZE + (rows - 1) * GAP;
}
},
watch: {
modelValue: {
immediate: true,
handler(list) {
const mapped = (list || []).map((u, i) => ({
uid: String(i) + "_" + (u.id || u.url || Math.random().toString(36).slice(2)),
url: typeof u === "string" ? u : u.url || "",
x: this.posOf(i).x,
y: this.posOf(i).y
}));
this.innerList = mapped;
}
}
},
methods: {
posOf(index) {
const row = Math.floor(index / COLS);
const col = index % COLS;
return { x: px(col * (ITEM_SIZE + GAP)), y: px(row * (ITEM_SIZE + GAP)) };
},
cellStyle(index) {
return {
width: ITEM_SIZE + "rpx",
height: ITEM_SIZE + "rpx"
};
},
preview(index) {
common_vendor.index.previewImage({ urls: this.innerList.map((i) => i.url), current: index });
},
remove(index) {
this.innerList.splice(index, 1);
this.reflow();
this.emit();
},
choose() {
const remain = this.max - this.innerList.length;
if (remain <= 0)
return;
common_vendor.index.chooseImage({ count: remain, success: async (res) => {
for (const path of res.tempFilePaths) {
await this.doUpload(path);
}
} });
},
async doUpload(filePath) {
var _a;
try {
const resp = await common_http.upload(this.uploadPath, filePath, this.formData, this.uploadFieldName);
const url = (resp == null ? void 0 : resp.url) || ((_a = resp == null ? void 0 : resp.data) == null ? void 0 : _a.url) || (resp == null ? void 0 : resp.path) || "";
if (!url)
throw new Error("上传响应无 url");
this.innerList.push({ uid: Math.random().toString(36).slice(2), url, ...this.posOf(this.innerList.length) });
this.reflow();
this.emit();
} catch (e) {
common_vendor.index.showToast({ title: "上传失败", icon: "none" });
}
},
onMoving(index, e) {
const { x, y } = e.detail;
this.innerList[index].x = x;
this.innerList[index].y = y;
},
onMoveEnd(index) {
const mv = this.innerList[index];
const col = Math.round(mv.x / (ITEM_SIZE + GAP));
const row = Math.round(mv.y / (ITEM_SIZE + GAP));
let newIndex = row * COLS + col;
newIndex = Math.max(0, Math.min(newIndex, this.innerList.length - 1));
if (newIndex !== index) {
const moved = this.innerList.splice(index, 1)[0];
this.innerList.splice(newIndex, 0, moved);
}
this.reflow();
this.emit();
},
reflow() {
this.innerList.forEach((it, i) => {
const p = this.posOf(i);
it.x = p.x;
it.y = p.y;
});
},
emit() {
this.$emit("update:modelValue", this.innerList.map((i) => i.url));
this.$emit("change", this.innerList.map((i) => i.url));
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: common_vendor.f($data.innerList, (img, index, i0) => {
return {
a: img.url,
b: common_vendor.o(($event) => $options.preview(index), img.uid),
c: common_vendor.o(($event) => $options.remove(index), img.uid),
d: img.uid,
e: common_vendor.s($options.cellStyle(index)),
f: img.x,
g: img.y,
h: common_vendor.o(($event) => $options.onMoving(index, $event), img.uid),
i: common_vendor.o(($event) => $options.onMoveEnd(index), img.uid)
};
}),
b: $data.innerList.length < $props.max
}, $data.innerList.length < $props.max ? {
c: common_vendor.o((...args) => $options.choose && $options.choose(...args))
} : {}, {
d: $options.areaHeight + "rpx",
e: $options.areaHeight + "rpx"
});
}
const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createComponent(Component);
//# sourceMappingURL=../../.sourcemap/mp-weixin/components/ImageUploader.js.map

View File

@@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="uploader"><view class="grid" style="{{'height:' + e}}"><movable-area class="area" style="{{'height:' + d}}"><movable-view wx:for="{{a}}" wx:for-item="img" wx:key="d" class="cell" style="{{img.e}}" direction="{{'all'}}" damping="{{40}}" friction="{{2}}" x="{{img.f}}" y="{{img.g}}" bindchange="{{img.h}}" bindtouchend="{{img.i}}"><image src="{{img.a}}" mode="aspectFill" class="thumb" bindtap="{{img.b}}"/><view class="remove" catchtap="{{img.c}}">×</view></movable-view><view wx:if="{{b}}" class="adder" bindtap="{{c}}"><text></text></view></movable-area></view></view>

View File

@@ -0,0 +1,15 @@
.uploader { padding: 12rpx; background: #fff;
}
.grid { position: relative;
}
.area { width: 100%; position: relative;
}
.cell { position: absolute; border-radius: 12rpx; overflow: hidden; box-shadow: 0 0 1rpx rgba(0,0,0,0.08);
}
.thumb { width: 100%; height: 100%;
}
.remove { position: absolute; right: 6rpx; top: 6rpx; background: rgba(0,0,0,0.45); color: #fff; width: 40rpx; height: 40rpx; text-align: center; line-height: 40rpx; border-radius: 20rpx; font-size: 28rpx;
}
.adder { width: 210rpx; height: 210rpx; border: 2rpx dashed #ccc; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; color: #999; position: absolute; left: 0; top: 0;
}

View File

@@ -11,6 +11,7 @@ const _sfc_main = {
loadingNotices: false,
noticeError: "",
features: [
{ key: "product", title: "货品", img: "/static/icons/product.png", emoji: "📦" },
{ key: "customer", title: "客户", img: "/static/icons/customer.png", emoji: "👥" },
{ key: "sale", title: "销售", img: "/static/icons/sale.png", emoji: "💰" },
{ key: "account", title: "账户", img: "/static/icons/account.png", emoji: "💳" },
@@ -30,12 +31,14 @@ const _sfc_main = {
methods: {
async fetchMetrics() {
try {
const d = await common_http.get("/api/metrics/overview");
const d = await common_http.get("/api/dashboard/overview");
const toNum = (v) => typeof v === "number" ? v : Number(v || 0);
this.kpi = {
todaySales: d && d.todaySales || "0.00",
monthSales: d && d.monthSales || "0.00",
monthProfit: d && d.monthProfit || "0.00",
stockCount: d && d.stockCount || "0"
...this.kpi,
todaySales: toNum(d && d.todaySalesAmount).toFixed(2),
monthSales: toNum(d && d.monthSalesAmount).toFixed(2),
monthProfit: toNum(d && d.monthGrossProfit).toFixed(2),
stockCount: String((d && d.stockTotalQuantity) != null ? d.stockTotalQuantity : 0)
};
} catch (e) {
}
@@ -56,8 +59,16 @@ const _sfc_main = {
}
},
onFeatureTap(item) {
if (item.key === "product") {
common_vendor.index.navigateTo({ url: "/pages/product/list" });
return;
}
common_vendor.index.showToast({ title: item.title + "(开发中)", icon: "none" });
},
goProduct() {
this.activeTab = "product";
common_vendor.index.navigateTo({ url: "/pages/product/list" });
},
onCreateOrder() {
common_vendor.index.navigateTo({ url: "/pages/order/create" });
},
@@ -116,7 +127,7 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
l: $data.activeTab === "home" ? 1 : "",
m: common_vendor.o(($event) => $data.activeTab = "home"),
n: $data.activeTab === "product" ? 1 : "",
o: common_vendor.o(($event) => $data.activeTab = "product"),
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(($event) => $data.activeTab = "detail"),

View File

@@ -0,0 +1,62 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { name: "", list: [] };
},
onLoad() {
this.reload();
},
methods: {
async reload() {
try {
const res = await common_http.get("/api/product-categories");
this.list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
async create() {
if (!this.name)
return;
await common_http.post("/api/product-categories", { name: this.name });
this.name = "";
this.reload();
},
async update(c) {
await common_http.put("/api/product-categories/" + c.id, { name: c.name });
common_vendor.index.showToast({ title: "已保存", icon: "success" });
},
async remove(c) {
common_vendor.index.showModal({ content: "确定删除该类别?", success: async (r) => {
if (!r.confirm)
return;
await common_http.del("/api/product-categories/" + c.id);
this.reload();
} });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.name,
b: common_vendor.o(common_vendor.m(($event) => $data.name = $event.detail.value, {
trim: true
})),
c: common_vendor.o((...args) => $options.create && $options.create(...args)),
d: common_vendor.f($data.list, (c, k0, i0) => {
return {
a: c.name,
b: common_vendor.o(common_vendor.m(($event) => c.name = $event.detail.value, {
trim: true
}), c.id),
c: common_vendor.o(($event) => $options.update(c), c.id),
d: common_vendor.o(($event) => $options.remove(c), c.id),
e: c.id
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/categories.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "类别管理",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="toolbar"><input placeholder="新类别名称" value="{{a}}" bindinput="{{b}}"/><button size="mini" bindtap="{{c}}">新增</button></view><scroll-view scroll-y class="list"><view wx:for="{{d}}" wx:for-item="c" wx:key="e" class="item"><input value="{{c.a}}" bindinput="{{c.b}}"/><view class="ops"><button size="mini" bindtap="{{c.c}}">保存</button><button size="mini" type="warn" bindtap="{{c.d}}">删除</button></view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx;
}
.ops { display:flex; gap: 10rpx;
}

View File

@@ -0,0 +1,251 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
function parseSafeStock(text) {
const m = String(text || "").match(/\s*(\d+(?:\.\d+)?)\s*(?:-|~|—|到|,)?\s*(\d+(?:\.\d+)?)?\s*/);
if (!m)
return { min: null, max: null };
return { min: m[1] ? Number(m[1]) : null, max: m[2] ? Number(m[2]) : null };
}
const _sfc_main = {
data() {
return {
id: null,
units: [],
categories: [],
form: {
name: "",
categoryId: null,
unitId: null,
barcode: "",
brand: "",
model: "",
spec: "",
purchasePrice: 0,
wholesalePrice: 0,
bigClientPrice: 0,
retailPrice: 0,
stock: 0,
safeMin: null,
safeMax: null,
images: []
}
};
},
onLoad(query) {
this.id = query && query.id ? Number(query.id) : null;
this.bootstrap();
},
computed: {
categoryLabel() {
const c = this.categories.find((x) => x.id === this.form.categoryId);
return c ? c.name : "未选择";
},
unitLabel() {
const u = this.units.find((x) => x.id === this.form.unitId);
return u ? u.name : "未选择";
},
safeStockText: {
get() {
const a = this.form.safeMin != null ? String(this.form.safeMin) : "";
const b = this.form.safeMax != null ? String(this.form.safeMax) : "";
if (!a && !b)
return "";
if (a && b)
return a + "-" + b;
return a || b;
},
set(v) {
const { min, max } = parseSafeStock(v);
this.form.safeMin = min;
this.form.safeMax = max;
}
}
},
methods: {
async bootstrap() {
try {
const [units, cats] = await Promise.all([
common_http.get("/api/product/units"),
common_http.get("/api/product/categories")
]);
this.units = Array.isArray(units == null ? void 0 : units.list) ? units.list : Array.isArray(units) ? units : [];
this.categories = Array.isArray(cats == null ? void 0 : cats.list) ? cats.list : Array.isArray(cats) ? cats : [];
} catch (_) {
}
if (this.id) {
try {
const d = await common_http.get("/api/product/detail", { id: this.id });
Object.assign(this.form, {
name: d.name || "",
categoryId: d.categoryId || null,
unitId: d.unitId || null,
barcode: d.barcode || "",
brand: d.brand || "",
model: d.model || "",
spec: d.spec || "",
purchasePrice: Number(d.purchasePrice || 0),
wholesalePrice: Number(d.wholesalePrice || 0),
bigClientPrice: Number(d.bigClientPrice || 0),
retailPrice: Number(d.retailPrice || 0),
stock: Number(d.stock || 0),
safeMin: d.safeMin ?? null,
safeMax: d.safeMax ?? null,
images: Array.isArray(d.images) ? d.images : []
});
} catch (_) {
}
}
},
chooseCategory() {
if (!this.categories.length)
return;
common_vendor.index.showActionSheet({ itemList: this.categories.map((c) => c.name), success: ({ tapIndex }) => {
this.form.categoryId = this.categories[tapIndex].id;
} });
},
chooseUnit() {
if (!this.units.length)
return;
common_vendor.index.showActionSheet({ itemList: this.units.map((u) => u.name), success: ({ tapIndex }) => {
this.form.unitId = this.units[tapIndex].id;
} });
},
scanBarcode() {
common_vendor.index.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result;
}, fail: () => {
common_vendor.index.showToast({ title: "扫码失败,请手动录入", icon: "none" });
} });
},
async chooseImages() {
common_vendor.index.chooseImage({ count: 6, sizeType: ["compressed"], success: async (res) => {
try {
const uploaded = [];
for (const path of res.tempFilePaths) {
const ret = await common_http.upload("/api/upload", path, { biz: "product" });
uploaded.push(ret.url || ret.path || path);
}
this.form.images = (this.form.images || []).concat(uploaded).slice(0, 9);
} catch (e) {
common_vendor.index.showToast({ title: "上传失败", icon: "none" });
}
} });
},
moveLeft(i) {
if (i <= 0)
return;
const a = this.form.images;
[a[i - 1], a[i]] = [a[i], a[i - 1]];
},
moveRight(i) {
const a = this.form.images;
if (i >= a.length - 1)
return;
[a[i + 1], a[i]] = [a[i], a[i + 1]];
},
removeImg(i) {
this.form.images.splice(i, 1);
},
async submit() {
if (!this.form.name) {
common_vendor.index.showToast({ title: "请填写名称", icon: "none" });
return;
}
const payload = {
name: this.form.name,
categoryId: this.form.categoryId,
unitId: this.form.unitId,
barcode: this.form.barcode,
brand: this.form.brand,
model: this.form.model,
spec: this.form.spec,
purchasePrice: Number(this.form.purchasePrice || 0),
wholesalePrice: Number(this.form.wholesalePrice || 0),
bigClientPrice: Number(this.form.bigClientPrice || 0),
retailPrice: Number(this.form.retailPrice || 0),
stock: Number(this.form.stock || 0),
safeMin: this.form.safeMin,
safeMax: this.form.safeMax,
images: this.form.images
};
try {
if (this.id)
await common_http.put("/api/product", { id: this.id, ...payload });
else
await common_http.post("/api/product", payload);
common_vendor.index.showToast({ title: "已保存", icon: "success" });
setTimeout(() => {
common_vendor.index.navigateBack();
}, 600);
} catch (e) {
common_vendor.index.showToast({ title: e && e.message || "保存失败", icon: "none" });
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
trim: true
})),
c: common_vendor.t($options.categoryLabel),
d: common_vendor.o((...args) => $options.chooseCategory && $options.chooseCategory(...args)),
e: common_vendor.t($options.unitLabel),
f: common_vendor.o((...args) => $options.chooseUnit && $options.chooseUnit(...args)),
g: $data.form.barcode,
h: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
trim: true
})),
i: common_vendor.o((...args) => $options.scanBarcode && $options.scanBarcode(...args)),
j: $data.form.brand,
k: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
trim: true
})),
l: $data.form.model,
m: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
trim: true
})),
n: $data.form.spec,
o: common_vendor.o(common_vendor.m(($event) => $data.form.spec = $event.detail.value, {
trim: true
})),
p: $data.form.purchasePrice,
q: common_vendor.o(common_vendor.m(($event) => $data.form.purchasePrice = $event.detail.value, {
number: true
})),
r: $data.form.wholesalePrice,
s: common_vendor.o(common_vendor.m(($event) => $data.form.wholesalePrice = $event.detail.value, {
number: true
})),
t: $data.form.bigClientPrice,
v: common_vendor.o(common_vendor.m(($event) => $data.form.bigClientPrice = $event.detail.value, {
number: true
})),
w: $data.form.retailPrice,
x: common_vendor.o(common_vendor.m(($event) => $data.form.retailPrice = $event.detail.value, {
number: true
})),
y: $data.form.stock,
z: common_vendor.o(common_vendor.m(($event) => $data.form.stock = $event.detail.value, {
number: true
})),
A: $options.safeStockText,
B: common_vendor.o(($event) => $options.safeStockText = $event.detail.value),
C: common_vendor.o((...args) => $options.chooseImages && $options.chooseImages(...args)),
D: common_vendor.f($data.form.images, (url, idx, i0) => {
return {
a: url,
b: common_vendor.o(($event) => $options.moveLeft(idx), idx),
c: common_vendor.o(($event) => $options.moveRight(idx), idx),
d: common_vendor.o(($event) => $options.removeImg(idx), idx),
e: idx
};
}),
E: common_vendor.o((...args) => $options.submit && $options.submit(...args))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/edit.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="value" placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="field" bindtap="{{d}}"><text class="label">类别</text><text class="value">{{c}}</text></view><view class="field" bindtap="{{f}}"><text class="label">主单位</text><text class="value">{{e}}</text></view><view class="field"><text class="label">条形码</text><input class="value" placeholder="手动录入或扫码" value="{{g}}" bindinput="{{h}}"/><button size="mini" class="scan" bindtap="{{i}}">扫码</button></view><view class="field"><text class="label">品牌</text><input class="value" placeholder="可选" value="{{j}}" bindinput="{{k}}"/></view><view class="field"><text class="label">型号</text><input class="value" placeholder="可选" value="{{l}}" bindinput="{{m}}"/></view><view class="field"><text class="label">规格</text><input class="value" placeholder="可选" value="{{n}}" bindinput="{{o}}"/></view><view class="grid2"><view class="field"><text class="label">进货价</text><input class="value" type="digit" value="{{p}}" bindinput="{{q}}"/></view><view class="field"><text class="label">批发价</text><input class="value" type="digit" value="{{r}}" bindinput="{{s}}"/></view><view class="field"><text class="label">大单报价</text><input class="value" type="digit" value="{{t}}" bindinput="{{v}}"/></view><view class="field"><text class="label">零售价</text><input class="value" type="digit" value="{{w}}" bindinput="{{x}}"/></view></view><view class="grid2"><view class="field"><text class="label">当前库存</text><input class="value" type="digit" value="{{y}}" bindinput="{{z}}"/></view><view class="field"><text class="label">安全库存</text><input class="value" type="text" placeholder="如 10-100" value="{{A}}" bindinput="{{B}}"/></view></view><view class="photos"><view class="photos-header"><text>商品图片</text><button size="mini" bindtap="{{C}}">选择图片</button></view><view class="photo-list"><view wx:for="{{D}}" wx:for-item="url" wx:key="e" class="photo"><image src="{{url.a}}" mode="aspectFill"/><view class="photo-actions"><text class="btn" bindtap="{{url.b}}">←</text><text class="btn" bindtap="{{url.c}}">→</text><text class="btn danger" bindtap="{{url.d}}">删</text></view></view></view></view></view><view class="bottom"><button class="primary" bindtap="{{E}}">保存</button></view></view>

View File

@@ -0,0 +1,35 @@
.page{ padding-bottom: 140rpx;
}
.form{ background:#fff;
}
.field{ display:flex; align-items:center; gap: 12rpx; padding: 22rpx 20rpx; border-bottom: 1rpx solid #f2f2f2;
}
.label{ color:#666; width: 160rpx;
}
.value{ flex:1; color:#333;
}
.scan{ flex: 0 0 auto;
}
.grid2{ display:grid; grid-template-columns: 1fr 1fr;
}
.photos{ padding: 16rpx 20rpx;
}
.photos-header{ display:flex; justify-content: space-between; align-items:center; margin-bottom: 12rpx;
}
.photo-list{ display:grid; grid-template-columns: repeat(3, 1fr); gap: 12rpx;
}
.photo{ position: relative;
}
.photo image{ width: 100%; height: 200rpx; border-radius: 12rpx; background:#f6f6f6; display:block;
}
.photo-actions{ position:absolute; right:6rpx; bottom:6rpx; display:flex; gap: 6rpx;
}
.btn{ font-size: 22rpx; padding: 4rpx 8rpx; background: rgba(0,0,0,0.45); color:#fff; border-radius: 8rpx;
}
.btn.danger{ background: rgba(221,82,77,0.85);
}
.bottom{ position: fixed; left:0; right:0; bottom:0; background:#fff; padding: 16rpx 24rpx calc(env(safe-area-inset-bottom) + 16rpx); box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.06);
}
.primary{ width: 100%; background: linear-gradient(135deg, #FFE69A 0%, #F4CF62 45%, #D7A72E 100%); color:#493c1b; border-radius: 999rpx; padding: 20rpx 0; font-weight:800;
}

View File

@@ -0,0 +1,249 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const ImageUploader = () => "../../components/ImageUploader.js";
const _sfc_main = {
components: { ImageUploader },
data() {
return {
id: "",
form: {
name: "",
barcode: "",
brand: "",
model: "",
spec: "",
origin: "",
categoryId: "",
unitId: "",
stock: null,
safeMin: null,
safeMax: null,
purchasePrice: null,
retailPrice: null,
wholesalePrice: null,
bigClientPrice: null,
images: [],
remark: ""
},
units: [],
categories: []
};
},
onLoad(query) {
this.id = (query == null ? void 0 : query.id) || "";
this.bootstrap();
},
computed: {
unitNames() {
return this.units.map((u) => u.name);
},
categoryNames() {
return this.categories.map((c) => c.name);
},
unitLabel() {
const u = this.units.find((x) => String(x.id) === String(this.form.unitId));
return u ? u.name : "选择单位";
},
categoryLabel() {
const c = this.categories.find((x) => String(x.id) === String(this.form.categoryId));
return c ? c.name : "选择类别";
}
},
methods: {
async bootstrap() {
await Promise.all([this.fetchUnits(), this.fetchCategories()]);
if (this.id)
this.loadDetail();
},
async fetchUnits() {
try {
const res = await common_http.get("/api/product-units");
this.units = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
async fetchCategories() {
try {
const res = await common_http.get("/api/product-categories");
this.categories = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
onPickUnit(e) {
const idx = Number(e.detail.value);
const u = this.units[idx];
this.form.unitId = u ? u.id : "";
},
onPickCategory(e) {
const idx = Number(e.detail.value);
const c = this.categories[idx];
this.form.categoryId = c ? c.id : "";
},
scan() {
common_vendor.index.scanCode({ onlyFromCamera: false, success: (res) => {
this.form.barcode = res.result;
} });
},
async loadDetail() {
try {
const data = await common_http.get("/api/products/" + this.id);
Object.assign(this.form, {
name: data.name,
barcode: data.barcode,
brand: data.brand,
model: data.model,
spec: data.spec,
origin: data.origin,
categoryId: data.categoryId,
unitId: data.unitId,
stock: data.stock,
safeMin: data.safeMin,
safeMax: data.safeMax,
purchasePrice: data.purchasePrice,
retailPrice: data.retailPrice,
wholesalePrice: data.wholesalePrice,
bigClientPrice: data.bigClientPrice,
images: (data.images || []).map((i) => i.url || i)
});
} catch (_) {
}
},
validate() {
if (!this.form.name) {
common_vendor.index.showToast({ title: "请填写名称", icon: "none" });
return false;
}
if (this.form.safeMin != null && this.form.safeMax != null && Number(this.form.safeMin) > Number(this.form.safeMax)) {
common_vendor.index.showToast({ title: "安全库存区间不合法", icon: "none" });
return false;
}
return true;
},
buildPayload() {
const f = this.form;
return {
name: f.name,
barcode: f.barcode,
brand: f.brand,
model: f.model,
spec: f.spec,
origin: f.origin,
categoryId: f.categoryId || null,
unitId: f.unitId,
safeMin: f.safeMin,
safeMax: f.safeMax,
prices: {
purchasePrice: f.purchasePrice,
retailPrice: f.retailPrice,
wholesalePrice: f.wholesalePrice,
bigClientPrice: f.bigClientPrice
},
stock: f.stock,
images: f.images,
remark: f.remark
};
},
async save(goOn) {
if (!this.validate())
return;
const payload = this.buildPayload();
try {
if (this.id)
await common_http.put("/api/products/" + this.id, payload);
else
await common_http.post("/api/products", payload);
common_vendor.index.showToast({ title: "保存成功", icon: "success" });
if (goOn && !this.id) {
this.form = { name: "", barcode: "", brand: "", model: "", spec: "", origin: "", categoryId: "", unitId: "", stock: null, safeMin: null, safeMax: null, purchasePrice: null, retailPrice: null, wholesalePrice: null, bigClientPrice: null, images: [], remark: "" };
} else {
setTimeout(() => common_vendor.index.navigateBack(), 400);
}
} catch (e) {
common_vendor.index.showToast({ title: "保存失败", icon: "none" });
}
}
}
};
if (!Array) {
const _component_ImageUploader = common_vendor.resolveComponent("ImageUploader");
_component_ImageUploader();
}
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.form.name,
b: common_vendor.o(common_vendor.m(($event) => $data.form.name = $event.detail.value, {
trim: true
})),
c: $data.form.barcode,
d: common_vendor.o(common_vendor.m(($event) => $data.form.barcode = $event.detail.value, {
trim: true
})),
e: $data.form.brand,
f: common_vendor.o(common_vendor.m(($event) => $data.form.brand = $event.detail.value, {
trim: true
})),
g: $data.form.model,
h: common_vendor.o(common_vendor.m(($event) => $data.form.model = $event.detail.value, {
trim: true
})),
i: $data.form.spec,
j: common_vendor.o(common_vendor.m(($event) => $data.form.spec = $event.detail.value, {
trim: true
})),
k: $data.form.origin,
l: common_vendor.o(common_vendor.m(($event) => $data.form.origin = $event.detail.value, {
trim: true
})),
m: common_vendor.t($options.unitLabel),
n: $options.unitNames,
o: common_vendor.o((...args) => $options.onPickUnit && $options.onPickUnit(...args)),
p: common_vendor.t($options.categoryLabel),
q: $options.categoryNames,
r: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args)),
s: $data.form.stock,
t: common_vendor.o(common_vendor.m(($event) => $data.form.stock = $event.detail.value, {
number: true
})),
v: $data.form.safeMin,
w: common_vendor.o(common_vendor.m(($event) => $data.form.safeMin = $event.detail.value, {
number: true
})),
x: $data.form.safeMax,
y: common_vendor.o(common_vendor.m(($event) => $data.form.safeMax = $event.detail.value, {
number: true
})),
z: $data.form.purchasePrice,
A: common_vendor.o(common_vendor.m(($event) => $data.form.purchasePrice = $event.detail.value, {
number: true
})),
B: $data.form.retailPrice,
C: common_vendor.o(common_vendor.m(($event) => $data.form.retailPrice = $event.detail.value, {
number: true
})),
D: $data.form.wholesalePrice,
E: common_vendor.o(common_vendor.m(($event) => $data.form.wholesalePrice = $event.detail.value, {
number: true
})),
F: $data.form.bigClientPrice,
G: common_vendor.o(common_vendor.m(($event) => $data.form.bigClientPrice = $event.detail.value, {
number: true
})),
H: common_vendor.o(($event) => $data.form.images = $event),
I: common_vendor.p({
formData: {
ownerType: "product"
},
modelValue: $data.form.images
}),
J: $data.form.remark,
K: common_vendor.o(common_vendor.m(($event) => $data.form.remark = $event.detail.value, {
trim: true
})),
L: common_vendor.o(($event) => $options.save(false)),
M: common_vendor.o(($event) => $options.save(true))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/form.js.map

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "编辑货品",
"usingComponents": {
"image-uploader": "../../components/ImageUploader"
}
}

View File

@@ -0,0 +1 @@
<scroll-view scroll-y class="page"><view class="card"><view class="row"><text class="label">商品名称</text><input placeholder="必填" value="{{a}}" bindinput="{{b}}"/></view><view class="row"><text class="label">条形码</text><input placeholder="可扫码或输入" value="{{c}}" bindinput="{{d}}"/></view><view class="row"><text class="label">品牌/型号/规格/产地</text></view><view class="row"><input placeholder="品牌" value="{{e}}" bindinput="{{f}}"/></view><view class="row"><input placeholder="型号" value="{{g}}" bindinput="{{h}}"/></view><view class="row"><input placeholder="规格" value="{{i}}" bindinput="{{j}}"/></view><view class="row"><input placeholder="产地" value="{{k}}" bindinput="{{l}}"/></view><view class="row"><picker mode="selector" range="{{n}}" bindchange="{{o}}"><view class="picker">主单位:{{m}}</view></picker><picker mode="selector" range="{{q}}" bindchange="{{r}}"><view class="picker">类别:{{p}}</view></picker></view></view><view class="card"><view class="row"><text class="label">库存与安全库存</text></view><view class="row"><input type="number" placeholder="当前库存" value="{{s}}" bindinput="{{t}}"/><input type="number" placeholder="安全库存下限" value="{{v}}" bindinput="{{w}}"/><input type="number" placeholder="安全库存上限" value="{{x}}" bindinput="{{y}}"/></view></view><view class="card"><view class="row"><text class="label">价格(进价/零售/批发/大单)</text></view><view class="row prices"><input type="number" placeholder="进货价" value="{{z}}" bindinput="{{A}}"/><input type="number" placeholder="零售价" value="{{B}}" bindinput="{{C}}"/><input type="number" placeholder="批发价" value="{{D}}" bindinput="{{E}}"/><input type="number" placeholder="大单价" value="{{F}}" bindinput="{{G}}"/></view></view><view class="card"><text class="label">图片</text><image-uploader wx:if="{{I}}" u-i="4a3f460a-0" bind:__l="__l" bindupdateModelValue="{{H}}" u-p="{{I}}"/></view><view class="card"><text class="label">备注</text><block wx:if="{{r0}}"><textarea placeholder="可选" auto-height value="{{J}}" bindinput="{{K}}"/></block></view><view class="fixed"><button type="default" bindtap="{{L}}">保存</button><button type="primary" bindtap="{{M}}">保存并继续</button></view></scroll-view>

View File

@@ -0,0 +1,17 @@
.page { background:#f6f6f6; height: 100vh;
}
.card { background:#fff; margin: 16rpx; padding: 16rpx; border-radius: 12rpx;
}
.row { display:flex; gap: 12rpx; align-items: center; margin-bottom: 12rpx;
}
.label { width: 180rpx; color:#666;
}
.row input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx;
}
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666; margin-left: 8rpx;
}
.prices input { width: 30%;
}
.fixed { position: fixed; left: 0; right: 0; bottom: 0; background:#fff; padding: 12rpx 16rpx; display:flex; gap: 16rpx;
}

View File

@@ -0,0 +1,122 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return {
items: [],
query: { kw: "", page: 1, size: 20, categoryId: "" },
finished: false,
loading: false,
tab: "all",
categories: []
};
},
onLoad() {
this.fetchCategories();
this.reload();
},
computed: {
categoryNames() {
return this.categories.map((c) => c.name);
},
categoryLabel() {
const c = this.categories.find((x) => String(x.id) === String(this.query.categoryId));
return c ? "类别:" + c.name : "选择类别";
}
},
methods: {
switchTab(t) {
this.tab = t;
this.query.categoryId = "";
this.reload();
},
onPickCategory(e) {
const idx = Number(e.detail.value);
const c = this.categories[idx];
this.query.categoryId = c ? c.id : "";
this.reload();
},
async fetchCategories() {
try {
const res = await common_http.get("/api/product-categories", {});
this.categories = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
reload() {
this.items = [];
this.query.page = 1;
this.finished = false;
this.loadMore();
},
async loadMore() {
if (this.loading || this.finished)
return;
this.loading = true;
try {
const params = { kw: this.query.kw, page: this.query.page, size: this.query.size };
if (this.tab === "category" && this.query.categoryId)
params.categoryId = this.query.categoryId;
const res = await common_http.get("/api/products", params);
const list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
this.items = this.items.concat(list);
if (list.length < this.query.size)
this.finished = true;
this.query.page += 1;
} catch (e) {
common_vendor.index.showToast({ title: "加载失败", icon: "none" });
} finally {
this.loading = false;
}
},
openForm(id) {
const url = "/pages/product/form" + (id ? "?id=" + id : "");
common_vendor.index.navigateTo({ url });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return common_vendor.e({
a: $data.tab === "all" ? 1 : "",
b: common_vendor.o(($event) => $options.switchTab("all")),
c: $data.tab === "category" ? 1 : "",
d: common_vendor.o(($event) => $options.switchTab("category")),
e: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
f: $data.query.kw,
g: common_vendor.o(common_vendor.m(($event) => $data.query.kw = $event.detail.value, {
trim: true
})),
h: $data.tab === "category"
}, $data.tab === "category" ? {
i: common_vendor.t($options.categoryLabel),
j: $options.categoryNames,
k: common_vendor.o((...args) => $options.onPickCategory && $options.onPickCategory(...args))
} : {}, {
l: common_vendor.o((...args) => $options.reload && $options.reload(...args)),
m: $data.items.length
}, $data.items.length ? {
n: common_vendor.f($data.items, (it, k0, i0) => {
return common_vendor.e({
a: it.cover
}, it.cover ? {
b: it.cover
} : {}, {
c: common_vendor.t(it.name),
d: common_vendor.t(it.brand || "-"),
e: common_vendor.t(it.model || ""),
f: common_vendor.t(it.spec || ""),
g: common_vendor.t(it.stock ?? 0),
h: common_vendor.t((it.retailPrice ?? it.price ?? 0).toFixed(2)),
i: it.id,
j: common_vendor.o(($event) => $options.openForm(it.id), it.id)
});
})
} : {}, {
o: common_vendor.o((...args) => $options.loadMore && $options.loadMore(...args)),
p: common_vendor.o(($event) => $options.openForm())
});
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/list.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "货品列表",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="tabs"><view class="{{['tab', a && 'active']}}" bindtap="{{b}}">全部</view><view class="{{['tab', c && 'active']}}" bindtap="{{d}}">按类别</view></view><view class="search"><input placeholder="输入名称/条码/规格查询" bindconfirm="{{e}}" value="{{f}}" bindinput="{{g}}"/><picker wx:if="{{h}}" mode="selector" range="{{j}}" bindchange="{{k}}"><view class="picker">{{i}}</view></picker><button size="mini" bindtap="{{l}}">查询</button></view><scroll-view scroll-y class="list" bindscrolltolower="{{o}}"><block wx:if="{{m}}"><view wx:for="{{n}}" wx:for-item="it" wx:key="i" class="item" bindtap="{{it.j}}"><image wx:if="{{it.a}}" src="{{it.b}}" class="thumb" mode="aspectFill"/><view class="content"><view class="name">{{it.c}}</view><view class="meta">{{it.d}} {{it.e}} {{it.f}}</view><view class="meta">库存:{{it.g}} <text class="price">零售价:¥{{it.h}}</text></view></view></view></block><view wx:else class="empty"><text>暂无数据,点击右上角“+”新增</text></view></scroll-view><view class="fab" bindtap="{{p}}"></view></view>

View File

@@ -0,0 +1,33 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.tabs { display:flex; background:#fff;
}
.tab { flex:1; text-align:center; padding: 20rpx 0; color:#666;
}
.tab.active { color:#18b566; font-weight: 600;
}
.search { display:flex; gap: 12rpx; padding: 16rpx; background:#fff; align-items: center;
}
.search input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.picker { padding: 8rpx 12rpx; background:#f0f0f0; border-radius: 10rpx; color:#666;
}
.list { flex:1;
}
.item { display:flex; padding: 20rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.thumb { width: 120rpx; height: 120rpx; border-radius: 12rpx; margin-right: 16rpx; background:#fafafa;
}
.content { flex:1;
}
.name { color:#333; margin-bottom: 6rpx; font-weight: 600;
}
.meta { color:#888; font-size: 24rpx;
}
.price { margin-left: 20rpx; color:#f60;
}
.empty { height: 60vh; display:flex; align-items:center; justify-content:center; color:#999;
}
.fab { position: fixed; right: 30rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; background:#18b566; color:#fff; border-radius: 50rpx; text-align:center; line-height: 100rpx; font-size: 48rpx; box-shadow: 0 8rpx 20rpx rgba(0,0,0,0.15);
}

View File

@@ -0,0 +1,39 @@
"use strict";
const common_http = require("../../common/http.js");
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
data() {
return { settings: { hideZeroStock: false, hidePurchasePrice: false } };
},
onLoad() {
this.load();
},
methods: {
async load() {
try {
const res = await common_http.get("/api/product-settings");
this.settings = { hideZeroStock: !!(res == null ? void 0 : res.hideZeroStock), hidePurchasePrice: !!(res == null ? void 0 : res.hidePurchasePrice) };
} catch (_) {
}
},
async update(key, val) {
const next = { ...this.settings, [key]: val };
this.settings = next;
try {
await common_http.put("/api/product-settings", next);
} catch (_) {
}
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.settings.hideZeroStock,
b: common_vendor.o((e) => $options.update("hideZeroStock", e.detail.value)),
c: $data.settings.hidePurchasePrice,
d: common_vendor.o((e) => $options.update("hidePurchasePrice", e.detail.value))
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/settings.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "货品设置",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="item"><text>隐藏零库存商品</text><switch checked="{{a}}" bindchange="{{b}}"/></view><view class="item"><text>隐藏进货价</text><switch checked="{{c}}" bindchange="{{d}}"/></view></view>

View File

@@ -0,0 +1,5 @@
.page { background:#fff;
}
.item { display:flex; justify-content: space-between; align-items:center; padding: 20rpx; border-bottom: 1rpx solid #f1f1f1;
}

View File

@@ -0,0 +1,62 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const common_http = require("../../common/http.js");
const _sfc_main = {
data() {
return { name: "", list: [] };
},
onLoad() {
this.reload();
},
methods: {
async reload() {
try {
const res = await common_http.get("/api/product-units");
this.list = Array.isArray(res == null ? void 0 : res.list) ? res.list : Array.isArray(res) ? res : [];
} catch (_) {
}
},
async create() {
if (!this.name)
return;
await common_http.post("/api/product-units", { name: this.name });
this.name = "";
this.reload();
},
async update(u) {
await common_http.put("/api/product-units/" + u.id, { name: u.name });
common_vendor.index.showToast({ title: "已保存", icon: "success" });
},
async remove(u) {
common_vendor.index.showModal({ content: "确定删除该单位?", success: async (r) => {
if (!r.confirm)
return;
await common_http.del("/api/product-units/" + u.id);
this.reload();
} });
}
}
};
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return {
a: $data.name,
b: common_vendor.o(common_vendor.m(($event) => $data.name = $event.detail.value, {
trim: true
})),
c: common_vendor.o((...args) => $options.create && $options.create(...args)),
d: common_vendor.f($data.list, (u, k0, i0) => {
return {
a: u.name,
b: common_vendor.o(common_vendor.m(($event) => u.name = $event.detail.value, {
trim: true
}), u.id),
c: common_vendor.o(($event) => $options.update(u), u.id),
d: common_vendor.o(($event) => $options.remove(u), u.id),
e: u.id
};
})
};
}
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/product/units.js.map

View File

@@ -0,0 +1,4 @@
{
"navigationBarTitleText": "单位管理",
"usingComponents": {}
}

View File

@@ -0,0 +1 @@
<view class="page"><view class="toolbar"><input placeholder="新单位名称" value="{{a}}" bindinput="{{b}}"/><button size="mini" bindtap="{{c}}">新增</button></view><scroll-view scroll-y class="list"><view wx:for="{{d}}" wx:for-item="u" wx:key="e" class="item"><input value="{{u.a}}" bindinput="{{u.b}}"/><view class="ops"><button size="mini" bindtap="{{u.c}}">保存</button><button size="mini" type="warn" bindtap="{{u.d}}">删除</button></view></view></scroll-view></view>

View File

@@ -0,0 +1,15 @@
.page { display:flex; flex-direction: column; height: 100vh;
}
.toolbar { display:flex; gap: 12rpx; padding: 16rpx; background:#fff;
}
.toolbar input { flex:1; background:#f6f6f6; border-radius: 12rpx; padding: 12rpx;
}
.list { flex:1;
}
.item { display:flex; gap: 12rpx; align-items:center; padding: 16rpx; background:#fff; border-bottom: 1rpx solid #f1f1f1;
}
.item input { flex:1; background:#f7f7f7; border-radius: 10rpx; padding: 12rpx;
}
.ops { display:flex; gap: 10rpx;
}

View File

@@ -0,0 +1,5 @@
{
"setting": {
"urlCheck": false
}
}