Initial commit: frontend code
This commit is contained in:
37
App.vue
Normal file
37
App.vue
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<view id="app">
|
||||||
|
<slot></slot>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
onLaunch: function() {
|
||||||
|
console.log('App Launch')
|
||||||
|
// 检查登录状态
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
if (!token) {
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/login/index'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onShow: function() {
|
||||||
|
console.log('App Show')
|
||||||
|
},
|
||||||
|
onHide: function() {
|
||||||
|
console.log('App Hide')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* 全局样式 */
|
||||||
|
page {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
api/auth.js
Normal file
55
api/auth.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import api from './index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证相关API
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 发送验证码
|
||||||
|
*/
|
||||||
|
sendCode(phone) {
|
||||||
|
return api.request('/auth/send-code', 'POST', { phone })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手机号验证码登录
|
||||||
|
*/
|
||||||
|
phoneLogin(phone, code) {
|
||||||
|
return api.request('/auth/phone-login', 'POST', { phone, code })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信登录
|
||||||
|
*/
|
||||||
|
wechatLogin(code) {
|
||||||
|
return api.request('/auth/wechat', 'POST', { code })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝登录
|
||||||
|
*/
|
||||||
|
alipayLogin(code) {
|
||||||
|
return api.request('/auth/alipay', 'POST', { code })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新Token
|
||||||
|
*/
|
||||||
|
refreshToken(refreshToken) {
|
||||||
|
return api.request('/auth/refresh', 'POST', { refreshToken })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户信息
|
||||||
|
*/
|
||||||
|
getCurrentUser() {
|
||||||
|
return api.request('/auth/me', 'GET')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
*/
|
||||||
|
logout() {
|
||||||
|
return api.request('/auth/logout', 'POST')
|
||||||
|
}
|
||||||
|
}
|
||||||
41
api/index.js
Normal file
41
api/index.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// API基础配置
|
||||||
|
const BASE_URL = 'http://localhost:8080/api/v1'
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
const request = (url, method, data = {}) => {
|
||||||
|
const token = uni.getStorageSync('token')
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.request({
|
||||||
|
url: BASE_URL + url,
|
||||||
|
method: method,
|
||||||
|
data: data,
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': token ? `Bearer ${token}` : ''
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
if (res.data.code === 0) {
|
||||||
|
resolve(res.data.data)
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: res.data.message || '请求失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject(res.data)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
uni.showToast({
|
||||||
|
title: '网络请求失败',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
request
|
||||||
|
}
|
||||||
62
api/order.js
Normal file
62
api/order.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import api from './index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单相关API
|
||||||
|
* 核心业务:
|
||||||
|
* - 订单创建:计算原价(total_amount)、优惠金额(discount_amount)、实付金额(actual_amount)
|
||||||
|
* - 订单原价 = 商品标价 × 数量之和
|
||||||
|
* - 实付金额 = 原价 - 优惠金额
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 创建订单
|
||||||
|
* 请求参数示例:
|
||||||
|
* {
|
||||||
|
* customer_id: "CUS001",
|
||||||
|
* items: [
|
||||||
|
* { product_id: "PROD001", quantity: 10, price: 50.00 }
|
||||||
|
* ],
|
||||||
|
* discount_rate: 90,
|
||||||
|
* remark: "客户要求送货上门",
|
||||||
|
* payment_method: "wechat"
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
createOrder(data) {
|
||||||
|
return api.request('/orders', 'POST', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单列表
|
||||||
|
*/
|
||||||
|
getOrders(params) {
|
||||||
|
return api.request('/orders', 'GET', params)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取订单详情(含明细)
|
||||||
|
*/
|
||||||
|
getOrderDetail(id) {
|
||||||
|
return api.request(`/orders/${id}`, 'GET')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消订单
|
||||||
|
*/
|
||||||
|
cancelOrder(id) {
|
||||||
|
return api.request(`/orders/${id}/cancel`, 'PUT')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款
|
||||||
|
*/
|
||||||
|
refundOrder(id) {
|
||||||
|
return api.request(`/orders/${id}/refund`, 'PUT')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单统计
|
||||||
|
*/
|
||||||
|
getStatistics(params) {
|
||||||
|
return api.request('/orders/statistics', 'GET', params)
|
||||||
|
}
|
||||||
|
}
|
||||||
76
api/product.js
Normal file
76
api/product.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import api from './index'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 商品相关API
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 获取分类列表
|
||||||
|
*/
|
||||||
|
getCategories() {
|
||||||
|
return api.request('/products/categories', 'GET')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增分类
|
||||||
|
*/
|
||||||
|
createCategory(data) {
|
||||||
|
return api.request('/products/categories', 'POST', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改分类
|
||||||
|
*/
|
||||||
|
updateCategory(id, data) {
|
||||||
|
return api.request(`/products/categories/${id}`, 'PUT', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除分类
|
||||||
|
*/
|
||||||
|
deleteCategory(id) {
|
||||||
|
return api.request(`/products/categories/${id}`, 'DELETE')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取商品列表
|
||||||
|
*/
|
||||||
|
getProducts(params) {
|
||||||
|
return api.request('/products', 'GET', params)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取商品详情
|
||||||
|
*/
|
||||||
|
getProduct(id) {
|
||||||
|
return api.request(`/products/${id}`, 'GET')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增商品
|
||||||
|
*/
|
||||||
|
createProduct(data) {
|
||||||
|
return api.request('/products', 'POST', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改商品
|
||||||
|
*/
|
||||||
|
updateProduct(id, data) {
|
||||||
|
return api.request(`/products/${id}`, 'PUT', data)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除商品
|
||||||
|
*/
|
||||||
|
deleteProduct(id) {
|
||||||
|
return api.request(`/products/${id}`, 'DELETE')
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取库存预警商品
|
||||||
|
*/
|
||||||
|
getStockAlerts() {
|
||||||
|
return api.request('/products/alerts', 'GET')
|
||||||
|
}
|
||||||
|
}
|
||||||
11
main.js
Normal file
11
main.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
App.mpType = 'page'
|
||||||
|
|
||||||
|
const app = new Vue({
|
||||||
|
...App
|
||||||
|
})
|
||||||
|
app.$mount()
|
||||||
31
manifest.json
Normal file
31
manifest.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "建材销售管家",
|
||||||
|
"appid": "__UNI__BUILDING",
|
||||||
|
"description": "建材销售管家移动端应用",
|
||||||
|
"versionName": "1.0.0",
|
||||||
|
"versionCode": "100",
|
||||||
|
"transformPx": false,
|
||||||
|
"app-plus": {
|
||||||
|
"usingComponents": true,
|
||||||
|
"splashscreen": {
|
||||||
|
"alwaysShowBeforeRender": true,
|
||||||
|
"waiting": true,
|
||||||
|
"autoclose": true,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"modules": {}
|
||||||
|
},
|
||||||
|
"h5": {
|
||||||
|
"router": {
|
||||||
|
"mode": "hash",
|
||||||
|
"base": "/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mp-weixin": {
|
||||||
|
"appid": "wx1234567890",
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": false
|
||||||
|
},
|
||||||
|
"usingComponents": true
|
||||||
|
}
|
||||||
|
}
|
||||||
24
package.json
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "building-frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev:h5": "uni",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dcloudio/uni-app": "^2.0.0",
|
||||||
|
"@dcloudio/uni-h5": "^2.0.0",
|
||||||
|
"@dcloudio/uni-mp-weixin": "^2.0.0",
|
||||||
|
"@dcloudio/uni-mp-alipay": "^2.0.0",
|
||||||
|
"vue": "^3.2.45"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dcloudio/types": "^3.0.16",
|
||||||
|
"@dcloudio/uni-cli-shared": "^2.0.0",
|
||||||
|
"@dcloudio/vite-plugin-uni": "^2.0.0",
|
||||||
|
"vite": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
60
pages.json
Normal file
60
pages.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/index/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "首页"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/login/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "登录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/product/list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "商品列表"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/order/create",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "创建订单"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/order/list",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "订单列表"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "建材销售管家",
|
||||||
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
|
"backgroundColor": "#F8F8F8"
|
||||||
|
},
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#7A7E83",
|
||||||
|
"selectedColor": "#3cc51f",
|
||||||
|
"borderStyle": "black",
|
||||||
|
"backgroundColor": "#ffffff",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/index/index",
|
||||||
|
"text": "首页"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/product/list",
|
||||||
|
"text": "商品"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/order/list",
|
||||||
|
"text": "订单"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
219
pages/index/index.vue
Normal file
219
pages/index/index.vue
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- 用户信息 -->
|
||||||
|
<view class="user-info">
|
||||||
|
<view class="avatar">
|
||||||
|
<text class="username">{{ userInfo.username || '用户' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="role-tag">{{ userInfo.role || '销售员' }}</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 功能菜单 -->
|
||||||
|
<view class="menu-grid">
|
||||||
|
<view class="menu-item" @click="goTo('/pages/product/list')">
|
||||||
|
<text class="menu-icon">📦</text>
|
||||||
|
<text class="menu-text">商品管理</text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-item" @click="goTo('/pages/order/create')">
|
||||||
|
<text class="menu-icon">📝</text>
|
||||||
|
<text class="menu-text">创建订单</text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-item" @click="goTo('/pages/order/list')">
|
||||||
|
<text class="menu-icon">📋</text>
|
||||||
|
<text class="menu-text">订单列表</text>
|
||||||
|
</view>
|
||||||
|
<view class="menu-item" @click="goStock()">
|
||||||
|
<text class="menu-icon">🏭</text>
|
||||||
|
<text class="menu-text">库存管理</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 快捷操作 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">今日概览</view>
|
||||||
|
<view class="stats-grid">
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value">{{ stats.orderCount || 0 }}</text>
|
||||||
|
<text class="stat-label">今日订单</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value">¥{{ stats.actualAmount || 0 }}</text>
|
||||||
|
<text class="stat-label">今日销售额</text>
|
||||||
|
</view>
|
||||||
|
<view class="stat-item">
|
||||||
|
<text class="stat-value">{{ stats.stockAlerts || 0 }}</text>
|
||||||
|
<text class="stat-label">库存预警</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 退出登录 -->
|
||||||
|
<button class="logout-btn" @click="logout">退出登录</button>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import authApi from '@/api/auth'
|
||||||
|
import orderApi from '@/api/order'
|
||||||
|
import productApi from '@/api/product'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
userInfo: {},
|
||||||
|
stats: {
|
||||||
|
orderCount: 0,
|
||||||
|
actualAmount: 0,
|
||||||
|
stockAlerts: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.loadUserInfo()
|
||||||
|
this.loadStats()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadUserInfo() {
|
||||||
|
try {
|
||||||
|
const userInfo = await authApi.getCurrentUser()
|
||||||
|
this.userInfo = userInfo
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadStats() {
|
||||||
|
try {
|
||||||
|
// 获取今日订单统计
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
const stats = await orderApi.getStatistics({ startDate: today })
|
||||||
|
this.stats.orderCount = stats.orderCount || 0
|
||||||
|
this.stats.actualAmount = stats.actualAmount || 0
|
||||||
|
|
||||||
|
// 获取库存预警
|
||||||
|
const alerts = await productApi.getStockAlerts()
|
||||||
|
this.stats.stockAlerts = alerts ? alerts.length : 0
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goTo(url) {
|
||||||
|
uni.navigateTo({ url })
|
||||||
|
},
|
||||||
|
goStock() {
|
||||||
|
uni.showToast({ title: '库存管理功能开发中', icon: 'none' })
|
||||||
|
},
|
||||||
|
async logout() {
|
||||||
|
try {
|
||||||
|
await authApi.logout()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
uni.removeStorageSync('token')
|
||||||
|
uni.reLaunch({ url: '/pages/login/index' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
padding: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 120rpx;
|
||||||
|
background: #3cc51f;
|
||||||
|
border-radius: 60rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-tag {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
padding: 8rpx 16rpx;
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
width: 25%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
font-size: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-text {
|
||||||
|
margin-top: 10rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 30rpx;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout-btn {
|
||||||
|
background: #fff;
|
||||||
|
color: #ff4d4f;
|
||||||
|
border: none;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
293
pages/login/index.vue
Normal file
293
pages/login/index.vue
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
<template>
|
||||||
|
<view class="login-page">
|
||||||
|
<view class="logo">
|
||||||
|
<text class="logo-text">🏠</text>
|
||||||
|
<text class="app-name">建材销售管家</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 手机号登录 -->
|
||||||
|
<view class="login-form">
|
||||||
|
<view class="form-item">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
v-model="phone"
|
||||||
|
placeholder="请输入手机号"
|
||||||
|
maxlength="11"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type="number"
|
||||||
|
v-model="code"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
maxlength="6"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
class="code-btn"
|
||||||
|
:disabled="countdown > 0"
|
||||||
|
@click="sendCode"
|
||||||
|
>
|
||||||
|
{{ countdown > 0 ? `${countdown}s` : '获取验证码' }}
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<button class="login-btn" @click="phoneLogin">登录</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 其他登录方式 -->
|
||||||
|
<view class="other-login">
|
||||||
|
<view class="divider">
|
||||||
|
<view class="line"></view>
|
||||||
|
<text class="divider-text">其他登录方式</text>
|
||||||
|
<view class="line"></view>
|
||||||
|
</view>
|
||||||
|
<view class="login-methods">
|
||||||
|
<view class="method-item" @click="wechatLogin">
|
||||||
|
<text class="method-icon">💬</text>
|
||||||
|
<text class="method-text">微信登录</text>
|
||||||
|
</view>
|
||||||
|
<view class="method-item" @click="alipayLogin">
|
||||||
|
<text class="method-icon">💰</text>
|
||||||
|
<text class="method-text">支付宝登录</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import authApi from '@/api/auth'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
phone: '',
|
||||||
|
code: '',
|
||||||
|
countdown: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 发送验证码
|
||||||
|
async sendCode() {
|
||||||
|
if (!this.phone || this.phone.length !== 11) {
|
||||||
|
uni.showToast({ title: '请输入正确的手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await authApi.sendCode(this.phone)
|
||||||
|
uni.showToast({ title: '验证码已发送', icon: 'success' })
|
||||||
|
|
||||||
|
// 开始倒计时
|
||||||
|
this.countdown = 60
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
this.countdown--
|
||||||
|
if (this.countdown <= 0) {
|
||||||
|
clearInterval(timer)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 手机号登录
|
||||||
|
async phoneLogin() {
|
||||||
|
if (!this.phone || this.phone.length !== 11) {
|
||||||
|
uni.showToast({ title: '请输入手机号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.code || this.code.length !== 6) {
|
||||||
|
uni.showToast({ title: '请输入验证码', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await authApi.phoneLogin(this.phone, this.code)
|
||||||
|
uni.setStorageSync('token', data.token)
|
||||||
|
uni.setStorageSync('refreshToken', data.refreshToken)
|
||||||
|
uni.setStorageSync('userId', data.userId)
|
||||||
|
|
||||||
|
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
}, 1000)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 微信登录
|
||||||
|
async wechatLogin() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
uni.getProvider({
|
||||||
|
service: 'oauth',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.provider.includes('weixin')) {
|
||||||
|
uni.login({
|
||||||
|
provider: 'weixin',
|
||||||
|
success: async (loginRes) => {
|
||||||
|
try {
|
||||||
|
const data = await authApi.wechatLogin(loginRes.code)
|
||||||
|
uni.setStorageSync('token', data.token)
|
||||||
|
uni.setStorageSync('refreshToken', data.refreshToken)
|
||||||
|
uni.setStorageSync('userId', data.userId)
|
||||||
|
|
||||||
|
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
}, 1000)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-WEIXIN
|
||||||
|
uni.showToast({ title: '请在微信小程序中使用', icon: 'none' })
|
||||||
|
// #endif
|
||||||
|
},
|
||||||
|
|
||||||
|
// 支付宝登录
|
||||||
|
alipayLogin() {
|
||||||
|
// #ifdef MP-ALIPAY
|
||||||
|
my.getAuthCode({
|
||||||
|
scopes: 'auth_base',
|
||||||
|
success: async (res) => {
|
||||||
|
try {
|
||||||
|
const data = await authApi.alipayLogin(res.authCode)
|
||||||
|
uni.setStorageSync('token', data.token)
|
||||||
|
uni.setStorageSync('refreshToken', data.refreshToken)
|
||||||
|
uni.setStorageSync('userId', data.userId)
|
||||||
|
|
||||||
|
uni.showToast({ title: '登录成功', icon: 'success' })
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.reLaunch({ url: '/pages/index/index' })
|
||||||
|
}, 1000)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// #endif
|
||||||
|
// #ifndef MP-ALIPAY
|
||||||
|
uni.showToast({ title: '请在支付宝小程序中使用', icon: 'none' })
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.login-page {
|
||||||
|
padding: 100rpx 60rpx;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 80rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-text {
|
||||||
|
font-size: 120rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-btn {
|
||||||
|
width: 200rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
background: #3cc51f;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code-btn[disabled] {
|
||||||
|
background: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
background: #3cc51f;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 32rpx;
|
||||||
|
border: none;
|
||||||
|
margin-top: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.other-login {
|
||||||
|
margin-top: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
flex: 1;
|
||||||
|
height: 1rpx;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider-text {
|
||||||
|
padding: 0 20rpx;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-methods {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 40rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-icon {
|
||||||
|
font-size: 60rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
537
pages/order/create.vue
Normal file
537
pages/order/create.vue
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- 客户信息 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">客户信息</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">客户</text>
|
||||||
|
<picker
|
||||||
|
mode="selector"
|
||||||
|
:range="customers"
|
||||||
|
range-key="name"
|
||||||
|
@change="selectCustomer"
|
||||||
|
>
|
||||||
|
<view class="picker-value">
|
||||||
|
{{ selectedCustomer ? selectedCustomer.name : '请选择客户' }}
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品选择 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-header">
|
||||||
|
<text class="section-title">商品明细</text>
|
||||||
|
<text class="add-btn" @click="addProduct">+ 添加商品</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-for="(item, index) in orderItems" :key="index" class="order-item">
|
||||||
|
<view class="item-info">
|
||||||
|
<text class="item-name">{{ item.productName }}</text>
|
||||||
|
<text class="item-spec">{{ item.spec || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="item-edit">
|
||||||
|
<view class="quantity-edit">
|
||||||
|
<text class="qty-label">数量</text>
|
||||||
|
<input
|
||||||
|
class="qty-input"
|
||||||
|
type="number"
|
||||||
|
v-model="item.quantity"
|
||||||
|
@change="calcAmount"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="price-edit">
|
||||||
|
<text class="qty-label">单价</text>
|
||||||
|
<input
|
||||||
|
class="price-input"
|
||||||
|
type="digit"
|
||||||
|
v-model="item.price"
|
||||||
|
@change="calcAmount"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<text class="item-subtotal">¥{{ (item.price * item.quantity).toFixed(2) }}</text>
|
||||||
|
<text class="delete-btn" @click="removeItem(index)">×</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="orderItems.length === 0" class="empty-tip">
|
||||||
|
请添加商品
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 优惠设置 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">优惠设置</view>
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">折扣率</text>
|
||||||
|
<input
|
||||||
|
class="discount-input"
|
||||||
|
type="digit"
|
||||||
|
v-model="discountRate"
|
||||||
|
@change="calcAmount"
|
||||||
|
/>
|
||||||
|
<text class="unit">%</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单金额 -->
|
||||||
|
<view class="section amount-section">
|
||||||
|
<view class="amount-row">
|
||||||
|
<text class="amount-label">原价合计</text>
|
||||||
|
<text class="amount-value">¥{{ totalAmount.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="amount-row">
|
||||||
|
<text class="amount-label">优惠金额</text>
|
||||||
|
<text class="amount-value discount">-¥{{ discountAmount.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="amount-row actual">
|
||||||
|
<text class="amount-label">实付金额</text>
|
||||||
|
<text class="amount-value">¥{{ actualAmount.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 支付方式 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">支付方式</view>
|
||||||
|
<view class="payment-methods">
|
||||||
|
<view
|
||||||
|
class="method-item"
|
||||||
|
:class="{ active: paymentMethod === 'cash' }"
|
||||||
|
@click="paymentMethod = 'cash'"
|
||||||
|
>
|
||||||
|
💵 现金
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="method-item"
|
||||||
|
:class="{ active: paymentMethod === 'wechat' }"
|
||||||
|
@click="paymentMethod = 'wechat'"
|
||||||
|
>
|
||||||
|
💬 微信
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="method-item"
|
||||||
|
:class="{ active: paymentMethod === 'alipay' }"
|
||||||
|
@click="paymentMethod = 'alipay'"
|
||||||
|
>
|
||||||
|
💰 支付宝
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 备注 -->
|
||||||
|
<view class="section">
|
||||||
|
<view class="section-title">备注</view>
|
||||||
|
<textarea
|
||||||
|
class="remark-input"
|
||||||
|
v-model="remark"
|
||||||
|
placeholder="请输入备注信息"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 提交按钮 -->
|
||||||
|
<view class="submit-bar">
|
||||||
|
<view class="submit-info">
|
||||||
|
<text>实付: </text>
|
||||||
|
<text class="submit-amount">¥{{ actualAmount.toFixed(2) }}</text>
|
||||||
|
</view>
|
||||||
|
<button class="submit-btn" @click="createOrder">创建订单</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 商品选择弹窗 -->
|
||||||
|
<uni-popup ref="productPopup" type="bottom">
|
||||||
|
<view class="product-popup">
|
||||||
|
<view class="popup-header">
|
||||||
|
<text>选择商品</text>
|
||||||
|
<text @click="$refs.productPopup.close()">关闭</text>
|
||||||
|
</view>
|
||||||
|
<view class="popup-search">
|
||||||
|
<input v-model="searchKeyword" placeholder="搜索商品" />
|
||||||
|
</view>
|
||||||
|
<scroll-view class="popup-list" scroll-y>
|
||||||
|
<view
|
||||||
|
v-for="p in productList"
|
||||||
|
:key="p.productId"
|
||||||
|
class="popup-item"
|
||||||
|
@click="selectProduct(p)"
|
||||||
|
>
|
||||||
|
<text>{{ p.name }}</text>
|
||||||
|
<text>¥{{ p.price }}</text>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</uni-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import orderApi from '@/api/order'
|
||||||
|
import productApi from '@/api/product'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// 客户相关
|
||||||
|
customers: [],
|
||||||
|
selectedCustomer: null,
|
||||||
|
|
||||||
|
// 商品相关
|
||||||
|
orderItems: [],
|
||||||
|
productList: [],
|
||||||
|
searchKeyword: '',
|
||||||
|
|
||||||
|
// 金额相关
|
||||||
|
discountRate: 100, // 折扣率
|
||||||
|
totalAmount: 0, // 原价
|
||||||
|
discountAmount: 0, // 优惠金额
|
||||||
|
actualAmount: 0, // 实付金额
|
||||||
|
|
||||||
|
// 其他
|
||||||
|
paymentMethod: 'cash',
|
||||||
|
remark: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.loadProducts()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadProducts() {
|
||||||
|
try {
|
||||||
|
const res = await productApi.getProducts({ page: 1, pageSize: 100 })
|
||||||
|
this.productList = res.records || []
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectCustomer(e) {
|
||||||
|
this.selectedCustomer = this.customers[e.detail.value]
|
||||||
|
},
|
||||||
|
addProduct() {
|
||||||
|
this.$refs.productPopup.open()
|
||||||
|
},
|
||||||
|
selectProduct(product) {
|
||||||
|
// 检查是否已添加
|
||||||
|
const exists = this.orderItems.find(item => item.productId === product.productId)
|
||||||
|
if (exists) {
|
||||||
|
uni.showToast({ title: '商品已添加', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orderItems.push({
|
||||||
|
productId: product.productId,
|
||||||
|
productName: product.name,
|
||||||
|
spec: product.spec,
|
||||||
|
unit: product.unit,
|
||||||
|
price: product.price,
|
||||||
|
quantity: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
this.calcAmount()
|
||||||
|
this.$refs.productPopup.close()
|
||||||
|
},
|
||||||
|
removeItem(index) {
|
||||||
|
this.orderItems.splice(index, 1)
|
||||||
|
this.calcAmount()
|
||||||
|
},
|
||||||
|
// 核心金额计算逻辑
|
||||||
|
calcAmount() {
|
||||||
|
// 1. 计算原价 = Σ(单价 × 数量)
|
||||||
|
this.totalAmount = this.orderItems.reduce((sum, item) => {
|
||||||
|
return sum + (item.price * item.quantity)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
// 2. 计算优惠金额 = 原价 × (100 - 折扣率) / 100
|
||||||
|
this.discountAmount = this.totalAmount * (100 - this.discountRate) / 100
|
||||||
|
|
||||||
|
// 3. 计算实付金额 = 原价 - 优惠金额
|
||||||
|
this.actualAmount = this.totalAmount - this.discountAmount
|
||||||
|
},
|
||||||
|
async createOrder() {
|
||||||
|
if (this.orderItems.length === 0) {
|
||||||
|
uni.showToast({ title: '请添加商品', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
customerId: this.selectedCustomer ? this.selectedCustomer.customerId : null,
|
||||||
|
items: this.orderItems.map(item => ({
|
||||||
|
productId: item.productId,
|
||||||
|
quantity: item.quantity,
|
||||||
|
price: item.price
|
||||||
|
})),
|
||||||
|
discountRate: parseFloat(this.discountRate),
|
||||||
|
remark: this.remark,
|
||||||
|
paymentMethod: this.paymentMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const order = await orderApi.createOrder(data)
|
||||||
|
|
||||||
|
uni.showToast({ title: '订单创建成功', icon: 'success' })
|
||||||
|
|
||||||
|
// 跳转到订单详情或列表
|
||||||
|
setTimeout(() => {
|
||||||
|
uni.navigateTo({
|
||||||
|
url: `/pages/order/list`
|
||||||
|
})
|
||||||
|
}, 1500)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
padding: 20rpx;
|
||||||
|
padding-bottom: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
color: #3cc51f;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
width: 120rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.picker-value {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discount-input {
|
||||||
|
width: 120rpx;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-item {
|
||||||
|
padding: 20rpx;
|
||||||
|
background: #f9f9f9;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-info {
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-spec {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-edit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quantity-edit, .price-edit {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-label, .qty-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qty-input, .price-input {
|
||||||
|
width: 100rpx;
|
||||||
|
height: 56rpx;
|
||||||
|
background: #fff;
|
||||||
|
border: 1rpx solid #ddd;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-subtotal {
|
||||||
|
flex: 1;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-tip {
|
||||||
|
padding: 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-section {
|
||||||
|
background: #fff7e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-label {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value.discount {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row.actual {
|
||||||
|
border-top: 1rpx dashed #ddd;
|
||||||
|
padding-top: 20rpx;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row.actual .amount-value {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-methods {
|
||||||
|
display: flex;
|
||||||
|
gap: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item {
|
||||||
|
flex: 1;
|
||||||
|
padding: 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.method-item.active {
|
||||||
|
background: #e6f7ff;
|
||||||
|
color: #1890ff;
|
||||||
|
border: 1rpx solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remark-input {
|
||||||
|
width: 100%;
|
||||||
|
height: 120rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-info {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-amount {
|
||||||
|
font-size: 36rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 80rpx;
|
||||||
|
line-height: 80rpx;
|
||||||
|
background: #3cc51f;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-popup {
|
||||||
|
background: #fff;
|
||||||
|
height: 60vh;
|
||||||
|
border-radius: 24rpx 24rpx 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-bottom: 1rpx solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-search {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-search input {
|
||||||
|
height: 60rpx;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-list {
|
||||||
|
height: calc(60vh - 140rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
372
pages/order/list.vue
Normal file
372
pages/order/list.vue
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- 筛选栏 -->
|
||||||
|
<view class="filter-bar">
|
||||||
|
<view
|
||||||
|
class="filter-item"
|
||||||
|
:class="{ active: status === null }"
|
||||||
|
@click="filterStatus(null)"
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="filter-item"
|
||||||
|
:class="{ active: status === 1 }"
|
||||||
|
@click="filterStatus(1)"
|
||||||
|
>
|
||||||
|
已完成
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="filter-item"
|
||||||
|
:class="{ active: status === 2 }"
|
||||||
|
@click="filterStatus(2)"
|
||||||
|
>
|
||||||
|
已取消
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 订单列表 -->
|
||||||
|
<view class="order-list">
|
||||||
|
<view
|
||||||
|
v-for="order in orders"
|
||||||
|
:key="order.orderId"
|
||||||
|
class="order-card"
|
||||||
|
@click="viewDetail(order)"
|
||||||
|
>
|
||||||
|
<view class="order-header">
|
||||||
|
<text class="order-no">{{ order.orderNo }}</text>
|
||||||
|
<text class="order-status" :class="getStatusClass(order.status)">
|
||||||
|
{{ getStatusText(order.status) }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-customer">
|
||||||
|
<text class="customer-name">{{ order.customerName || '散客' }}</text>
|
||||||
|
<text class="customer-phone">{{ order.customerPhone || '-' }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-items">
|
||||||
|
<text class="items-label">商品明细</text>
|
||||||
|
<text class="items-count">{{ getItemCount(order.orderId) }}种商品</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-amount">
|
||||||
|
<view class="amount-row">
|
||||||
|
<text class="amount-label">原价</text>
|
||||||
|
<text class="amount-value">¥{{ order.totalAmount }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="amount-row">
|
||||||
|
<text class="amount-label">优惠</text>
|
||||||
|
<text class="amount-value discount">-¥{{ order.discountAmount }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="amount-row actual">
|
||||||
|
<text class="amount-label">实付</text>
|
||||||
|
<text class="amount-value">¥{{ order.actualAmount }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="order-footer">
|
||||||
|
<text class="order-time">{{ formatTime(order.createdAt) }}</text>
|
||||||
|
<text class="operator">{{ order.operatorName }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view v-if="orders.length === 0" class="empty">
|
||||||
|
<text>暂无订单</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载更多 -->
|
||||||
|
<view v-if="orders.length > 0" class="load-more">
|
||||||
|
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import orderApi from '@/api/order'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
status: null,
|
||||||
|
orders: [],
|
||||||
|
orderItemsMap: {},
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
hasMore: true,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.loadOrders()
|
||||||
|
},
|
||||||
|
onReachBottom() {
|
||||||
|
if (this.hasMore && !this.loading) {
|
||||||
|
this.page++
|
||||||
|
this.loadOrders()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPullDownRefresh() {
|
||||||
|
this.page = 1
|
||||||
|
this.loadOrders().then(() => {
|
||||||
|
uni.stopPullDownRefresh()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadOrders() {
|
||||||
|
if (this.loading) return
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await orderApi.getOrders({
|
||||||
|
status: this.status,
|
||||||
|
page: this.page,
|
||||||
|
pageSize: this.pageSize
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = res.records || []
|
||||||
|
|
||||||
|
// 加载每个订单的明细
|
||||||
|
for (const order of list) {
|
||||||
|
try {
|
||||||
|
const detail = await orderApi.getOrderDetail(order.orderId)
|
||||||
|
this.$set(this.orderItemsMap, order.orderId, detail.items || [])
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.page === 1) {
|
||||||
|
this.orders = list
|
||||||
|
} else {
|
||||||
|
this.orders = [...this.orders, ...list]
|
||||||
|
}
|
||||||
|
this.hasMore = list.length >= this.pageSize
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
filterStatus(status) {
|
||||||
|
this.status = status
|
||||||
|
this.page = 1
|
||||||
|
this.loadOrders()
|
||||||
|
},
|
||||||
|
viewDetail(order) {
|
||||||
|
// 跳转到订单详情页或显示详情弹窗
|
||||||
|
uni.showModal({
|
||||||
|
title: '订单详情',
|
||||||
|
content: `订单号: ${order.orderNo}\n原价: ¥${order.totalAmount}\n优惠: ¥${order.discountAmount}\n实付: ¥${order.actualAmount}`,
|
||||||
|
showCancel: false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getItemCount(orderId) {
|
||||||
|
const items = this.orderItemsMap[orderId]
|
||||||
|
return items ? items.length : 0
|
||||||
|
},
|
||||||
|
getStatusClass(status) {
|
||||||
|
const map = {
|
||||||
|
1: 'status-success',
|
||||||
|
2: 'status-cancel',
|
||||||
|
3: 'status-refunding',
|
||||||
|
4: 'status-refunded'
|
||||||
|
}
|
||||||
|
return map[status] || ''
|
||||||
|
},
|
||||||
|
getStatusText(status) {
|
||||||
|
const map = {
|
||||||
|
1: '已完成',
|
||||||
|
2: '已取消',
|
||||||
|
3: '退款中',
|
||||||
|
4: '已退款'
|
||||||
|
}
|
||||||
|
return map[status] || '未知'
|
||||||
|
},
|
||||||
|
formatTime(time) {
|
||||||
|
if (!time) return '-'
|
||||||
|
return time.substring(0, 16).replace('T', ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-bar {
|
||||||
|
display: flex;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 16rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 12rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-item.active {
|
||||||
|
background: #3cc51f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-no {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-status {
|
||||||
|
font-size: 24rpx;
|
||||||
|
padding: 4rpx 12rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background: #f6ffed;
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-cancel {
|
||||||
|
background: #fff1f0;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-refunding {
|
||||||
|
background: #fff7e6;
|
||||||
|
color: #fa8c16;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-refunded {
|
||||||
|
background: #f9f0ff;
|
||||||
|
color: #722ed1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-customer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.customer-phone {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-items {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
border-top: 1rpx solid #f5f5f5;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-count {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-amount {
|
||||||
|
padding: 16rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 8rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-value.discount {
|
||||||
|
color: #52c41a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row.actual {
|
||||||
|
border-top: 1rpx dashed #ddd;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row.actual .amount-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row.actual .amount-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-top: 16rpx;
|
||||||
|
border-top: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.order-time {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operator {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
padding: 100rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
padding: 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
274
pages/product/list.vue
Normal file
274
pages/product/list.vue
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<template>
|
||||||
|
<view class="page">
|
||||||
|
<!-- 搜索栏 -->
|
||||||
|
<view class="search-bar">
|
||||||
|
<input
|
||||||
|
class="search-input"
|
||||||
|
v-model="keyword"
|
||||||
|
placeholder="搜索商品名称"
|
||||||
|
@confirm="search"
|
||||||
|
/>
|
||||||
|
<button class="search-btn" @click="search">搜索</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 分类筛选 -->
|
||||||
|
<scroll-view class="category-scroll" scroll-x>
|
||||||
|
<view
|
||||||
|
class="category-item"
|
||||||
|
:class="{ active: !categoryId }"
|
||||||
|
@click="selectCategory('')"
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
v-for="cat in categories"
|
||||||
|
:key="cat.categoryId"
|
||||||
|
class="category-item"
|
||||||
|
:class="{ active: categoryId === cat.categoryId }"
|
||||||
|
@click="selectCategory(cat.categoryId)"
|
||||||
|
>
|
||||||
|
{{ cat.name }}
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 商品列表 -->
|
||||||
|
<view class="product-list">
|
||||||
|
<view
|
||||||
|
v-for="item in products"
|
||||||
|
:key="item.productId"
|
||||||
|
class="product-item"
|
||||||
|
@click="viewDetail(item)"
|
||||||
|
>
|
||||||
|
<view class="product-info">
|
||||||
|
<text class="product-name">{{ item.name }}</text>
|
||||||
|
<text class="product-spec">{{ item.spec || '-' }}</text>
|
||||||
|
<view class="product-price">
|
||||||
|
<text class="price">¥{{ item.price }}</text>
|
||||||
|
<text class="unit">/{{ item.unit }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="product-stock">
|
||||||
|
<text class="stock-label">库存</text>
|
||||||
|
<text class="stock-value">{{ getStock(item.productId) }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<view v-if="products.length === 0" class="empty">
|
||||||
|
<text>暂无商品</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 加载更多 -->
|
||||||
|
<view v-if="products.length > 0" class="load-more">
|
||||||
|
<text>{{ loading ? '加载中...' : (hasMore ? '上拉加载更多' : '没有更多了') }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import productApi from '@/api/product'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
keyword: '',
|
||||||
|
categoryId: '',
|
||||||
|
categories: [],
|
||||||
|
products: [],
|
||||||
|
stocks: {},
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20,
|
||||||
|
hasMore: true,
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLoad() {
|
||||||
|
this.loadCategories()
|
||||||
|
this.loadProducts()
|
||||||
|
},
|
||||||
|
onReachBottom() {
|
||||||
|
if (this.hasMore && !this.loading) {
|
||||||
|
this.page++
|
||||||
|
this.loadProducts()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadCategories() {
|
||||||
|
try {
|
||||||
|
const categories = await productApi.getCategories()
|
||||||
|
this.categories = categories || []
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadProducts() {
|
||||||
|
if (this.loading) return
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await productApi.getProducts({
|
||||||
|
categoryId: this.categoryId,
|
||||||
|
keyword: this.keyword,
|
||||||
|
page: this.page,
|
||||||
|
pageSize: this.pageSize
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = res.records || []
|
||||||
|
if (this.page === 1) {
|
||||||
|
this.products = list
|
||||||
|
} else {
|
||||||
|
this.products = [...this.products, ...list]
|
||||||
|
}
|
||||||
|
this.hasMore = list.length >= this.pageSize
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectCategory(id) {
|
||||||
|
this.categoryId = id
|
||||||
|
this.page = 1
|
||||||
|
this.loadProducts()
|
||||||
|
},
|
||||||
|
search() {
|
||||||
|
this.page = 1
|
||||||
|
this.loadProducts()
|
||||||
|
},
|
||||||
|
viewDetail(item) {
|
||||||
|
uni.showToast({ title: '商品详情开发中', icon: 'none' })
|
||||||
|
},
|
||||||
|
getStock(productId) {
|
||||||
|
return this.stocks[productId] || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.page {
|
||||||
|
padding: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-bar {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
height: 70rpx;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
width: 120rpx;
|
||||||
|
height: 70rpx;
|
||||||
|
line-height: 70rpx;
|
||||||
|
background: #3cc51f;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
margin-left: 20rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-scroll {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12rpx 24rpx;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item.active {
|
||||||
|
background: #3cc51f;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-list {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 24rpx;
|
||||||
|
border-bottom: 1rpx solid #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-price {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
color: #ff4d4f;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit {
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-stock {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-label {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stock-value {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
padding: 100rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.load-more {
|
||||||
|
padding: 20rpx;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user