Compare commits
No commits in common. "main" and "master" have entirely different histories.
|
|
@ -0,0 +1,31 @@
|
|||
######################################################################
|
||||
# Build Tools
|
||||
|
||||
/unpackage/*
|
||||
/node_modules/*
|
||||
|
||||
######################################################################
|
||||
# Development Tools
|
||||
|
||||
/.idea/*
|
||||
/.vscode/*
|
||||
/.hbuilderx/*
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
|
||||
# 微信小程序CI上传密钥(敏感文件,禁止提交)
|
||||
private.key
|
||||
|
||||
# 可选:如果有测试环境的密钥文件,也一起忽略
|
||||
private-test.key
|
||||
|
||||
# 其他小程序开发常见忽略项(建议一并添加,避免冗余文件提交)
|
||||
node_modules/
|
||||
dist/
|
||||
unpackage/
|
||||
.DS_Store
|
||||
*.log
|
||||
.vscode/
|
||||
.project
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
## 修改浅色模式样式,增强模块区分度
|
||||
|
||||
### 修改内容:
|
||||
1. **调整CSS变量**(在 `:root` 中)
|
||||
- 背景色:`#f5f7fa` → `#e8ecf1`(增强对比)
|
||||
- 卡片阴影:`rgba(0, 0, 0, 0.05)` → `rgba(0, 0, 0, 0.12)`(更明显)
|
||||
- 边框色:`#e0e0e0` → `#d1d5db`(更清晰的边框)
|
||||
- 分割线:`darkgray` → `#d1d5db`
|
||||
|
||||
2. **增强卡片样式**
|
||||
- 添加微妙的边框效果
|
||||
- 增强阴影深度
|
||||
- 优化卡片与背景的对比
|
||||
|
||||
3. **保持深色模式不变**
|
||||
- 深色模式已经很清晰,无需修改
|
||||
|
||||
### 预期效果:
|
||||
- 模块之间区分更明显
|
||||
- 视觉层次更清晰
|
||||
- 保持简洁风格的同时提升可读性
|
||||
64
App.vue
|
|
@ -1,33 +1,33 @@
|
|||
<script>
|
||||
import config from './config'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
this.initApp()
|
||||
},
|
||||
methods: {
|
||||
// 初始化应用
|
||||
initApp() {
|
||||
// 初始化应用配置
|
||||
this.initConfig()
|
||||
// 检查用户登录状态
|
||||
//#ifdef H5
|
||||
this.checkLogin()
|
||||
//#endif
|
||||
},
|
||||
initConfig() {
|
||||
this.globalData.config = config
|
||||
},
|
||||
checkLogin() {
|
||||
if (!getToken()) {
|
||||
this.$tab.reLaunch('/pages/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/static/scss/index.scss'
|
||||
<script>
|
||||
import config from './config'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
this.initApp()
|
||||
},
|
||||
methods: {
|
||||
// 初始化应用
|
||||
initApp() {
|
||||
// 初始化应用配置
|
||||
this.initConfig()
|
||||
// 检查用户登录状态
|
||||
//#ifdef H5
|
||||
this.checkLogin()
|
||||
//#endif
|
||||
},
|
||||
initConfig() {
|
||||
this.globalData.config = config
|
||||
},
|
||||
checkLogin() {
|
||||
if (!getToken()) {
|
||||
this.$tab.reLaunch('/pages/login')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/static/scss/index.scss'
|
||||
</style>
|
||||
|
|
|
|||
42
LICENSE
|
|
@ -1,21 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 若依
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 若依
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<p align="center">
|
||||
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-43e3941654fa3054c9684bf53d1b1d356a1.png">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v1.2.0</h1>
|
||||
<h4 align="center">基于UniApp开发的轻量级移动端框架</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/y_project/RuoYi-App/stargazers"><img src="https://gitee.com/y_project/RuoYi-App/badge/star.svg?theme=dark"></a>
|
||||
<a href="https://gitee.com/y_project/RuoYi-App"><img src="https://img.shields.io/badge/RuoYi-v1.2.0-brightgreen.svg"></a>
|
||||
<a href="https://gitee.com/y_project/RuoYi-App/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
|
||||
</p>
|
||||
|
||||
## 平台简介
|
||||
|
||||
RuoYi App 移动解决方案,采用uniapp框架,一份代码多终端适配,同时支持APP、小程序、H5!实现了与[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)、[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud)完美对接的移动解决方案!目前已经实现登录、我的、工作台、编辑资料、头像修改、密码修改、常见问题、关于我们等基础功能。
|
||||
|
||||
* 配套后端代码仓库地址[RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue) 或 [RuoYi-Cloud](https://github.com/yangzongzhuan/RuoYi-Cloud) 版本。
|
||||
* 应用框架基于[uniapp](https://uniapp.dcloud.net.cn/),支持小程序、H5、Android和IOS。
|
||||
* 前端组件采用[uni-ui](https://github.com/dcloudio/uni-ui),全端兼容的高性能UI框架。
|
||||
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)
|
||||
|
||||
|
||||
## 技术文档
|
||||
|
||||
- 官网网站:[http://ruoyi.vip](http://ruoyi.vip)
|
||||
- 文档地址:[http://doc.ruoyi.vip](http://doc.ruoyi.vip)
|
||||
- H5页体验:[http://h5.ruoyi.vip](http://h5.ruoyi.vip)
|
||||
- QQ交流群: ①133713780(满)、②146013835(满)、③189091635
|
||||
- 小程序体验
|
||||
|
||||
<img src="https://oscimg.oschina.net/oscnet/up-26c76dc90b92acdbd9ac8cd5252f07c8ad9.jpg" alt="小程序演示"/>
|
||||
|
||||
|
||||
## 演示图
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-21f6f842fdc94540469b4eb43fdadbaf7f8.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-a6f23cf9a371a30165e135eff6d9ae89a9d.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-ff5f62016bf6624c1ff27eee57499dccd44.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-b9a582fdb26ec69d407fabd044d2c8494df.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-96427ee08fca29d77934cfc8d1b1a637cef.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-5fdadc582d24cccd7727030d397b63185a3.png"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-0a36797b6bcc50c36d40c3c782665b89efc.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-d77995cc00687cedd00d5ac7d68a07ea276.png"/></td>
|
||||
<td><img src="https://oscimg.oschina.net/oscnet/up-fa8f5ab20becf59b4b38c1b92a9989e7109.png"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 登录方法
|
||||
export function login(username, password, code, uuid) {
|
||||
const data = {
|
||||
username,
|
||||
password,
|
||||
code,
|
||||
uuid
|
||||
}
|
||||
return request({
|
||||
'url': '/login',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
'method': 'post',
|
||||
'data': data
|
||||
})
|
||||
}
|
||||
|
||||
// 注册方法
|
||||
export function register(data) {
|
||||
return request({
|
||||
url: '/register',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户详细信息
|
||||
export function getInfo() {
|
||||
return request({
|
||||
'url': '/getInfo',
|
||||
'method': 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 退出方法
|
||||
export function logout() {
|
||||
return request({
|
||||
'url': '/logout',
|
||||
'method': 'post'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
export function getCodeImg() {
|
||||
return request({
|
||||
'url': '/captchaImage',
|
||||
headers: {
|
||||
isToken: false
|
||||
},
|
||||
method: 'get',
|
||||
timeout: 20000
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,355 @@
|
|||
import { getToken } from '@/utils/auth'
|
||||
import { toast } from '@/utils/common'
|
||||
|
||||
// 参数转URL查询字符串
|
||||
function tansParams(params) {
|
||||
let result = ''
|
||||
for (const propName of Object.keys(params)) {
|
||||
const value = params[propName]
|
||||
var part = encodeURIComponent(propName) + "="
|
||||
if (value !== null && value !== "" && typeof (value) !== "undefined") {
|
||||
if (typeof value === 'object') {
|
||||
for (const key of Object.keys(value)) {
|
||||
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
|
||||
let params = propName + '[' + key + ']'
|
||||
var subPart = encodeURIComponent(params) + "="
|
||||
result += subPart + encodeURIComponent(value[key]) + "&"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result += part + encodeURIComponent(value) + "&"
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 专门用于8081端口的请求
|
||||
const request8081 = config => {
|
||||
config.header = config.header || {}
|
||||
|
||||
// 8081端口可能使用不同的认证方式
|
||||
const token = getToken()
|
||||
|
||||
console.log('=== 8081端口请求详情 ===')
|
||||
console.log('Token值:', token)
|
||||
console.log('Token存在:', !!token)
|
||||
|
||||
if (token) {
|
||||
// 尝试多种认证方式
|
||||
// 方式1:Bearer Token(标准JWT)
|
||||
config.header['Authorization'] = 'Bearer ' + token
|
||||
|
||||
// 方式2:直接使用token(不带Bearer)
|
||||
// config.header['Authorization'] = token
|
||||
|
||||
// 方式3:自定义认证头
|
||||
// config.header['X-Auth-Token'] = token
|
||||
|
||||
// 方式4:Cookie方式
|
||||
// config.header['Cookie'] = 'token=' + token
|
||||
}
|
||||
|
||||
// 处理GET请求参数
|
||||
let requestUrl = config.baseUrl + config.url
|
||||
if (config.params) {
|
||||
let url = requestUrl + '?' + tansParams(config.params)
|
||||
requestUrl = url.slice(0, -1)
|
||||
}
|
||||
|
||||
console.log('使用的认证方式:', 'Bearer Token')
|
||||
console.log('请求头:', config.header)
|
||||
console.log('请求URL:', requestUrl)
|
||||
console.log('请求参数:', config.params)
|
||||
console.log('请求方法:', config.method)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// 为PUT请求设置Content-Type
|
||||
if (config.method && config.method.toLowerCase() === 'put') {
|
||||
config.header = {
|
||||
...config.header,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
uni.request({
|
||||
method: config.method || 'get',
|
||||
timeout: config.timeout || 10000,
|
||||
url: requestUrl,
|
||||
data: config.data,
|
||||
header: config.header,
|
||||
dataType: 'json'
|
||||
}).then(response => {
|
||||
let [error, res] = response
|
||||
|
||||
if (error) {
|
||||
toast('后端接口连接异常')
|
||||
reject('后端接口连接异常')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('=== 8081端口响应详情 ===')
|
||||
// console.log('响应状态码:', res.statusCode)
|
||||
console.log('响应头:', res.header)
|
||||
console.log('响应数据:', res.data)
|
||||
console.log('响应数据类型:', typeof res.data)
|
||||
|
||||
const code = res.data.code || 200
|
||||
const msg = res.data.msg || '请求失败'
|
||||
|
||||
// console.log('业务状态码:', code)
|
||||
// console.log('业务消息:', msg)
|
||||
|
||||
if (code === 401) {
|
||||
toast('认证失败,请重新登录')
|
||||
reject('401')
|
||||
} else if (code === 403) {
|
||||
// console.log('=== 403错误详细信息 ===')
|
||||
// console.log('完整响应:', JSON.stringify(res, null, 2))
|
||||
toast('没有权限访问该资源')
|
||||
reject('403')
|
||||
} else if (code === 500) {
|
||||
toast(msg)
|
||||
reject('500')
|
||||
} else if (code !== 200) {
|
||||
toast(msg)
|
||||
reject(code)
|
||||
}
|
||||
|
||||
resolve(res.data)
|
||||
}).catch(error => {
|
||||
console.error('8081端口请求异常:', error)
|
||||
toast('网络请求失败')
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取商品列表
|
||||
export function getProductList(params) {
|
||||
const token = getToken()
|
||||
// console.log('=== 商品列表请求诊断 ===')
|
||||
// console.log('1. Token值:', token)
|
||||
// console.log('2. Token长度:', token ? token.length : 0)
|
||||
// console.log('3. Token前缀:', token ? token.substring(0, 20) + '...' : '无')
|
||||
// console.log('4. 请求URL:', 'http://193.112.94.36:8081/mall/product/list')
|
||||
// console.log('5. 认证方式:', 'Bearer Token (与8080共享)')
|
||||
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/product/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 删除商品
|
||||
export function deleteProduct(id) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/product/delete/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增商品
|
||||
export function addProduct(data) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/product/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 新增商品(带文件上传)
|
||||
export function addProductWithFile(filePath, formData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: 'http://193.112.94.36:8081/mall/product/add',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: formData,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getToken()
|
||||
},
|
||||
success: (uploadRes) => {
|
||||
const res = JSON.parse(uploadRes.data);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 获取商品详情
|
||||
export function getProductDetail(id) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/product/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改商品(form-data格式,支持文件上传)
|
||||
export function updateProductWithFile(filePath, formData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: 'http://193.112.94.36:8081/mall/product/update',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: formData,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getToken()
|
||||
},
|
||||
success: (uploadRes) => {
|
||||
const res = JSON.parse(uploadRes.data);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 修改商品
|
||||
export function updateProduct(data) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/product/update',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 商品数据导入(文件上传)
|
||||
export function importProductData(filePath, formData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
url: 'http://193.112.94.36:8081/mall/product/importData',
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
formData: formData,
|
||||
header: {
|
||||
'Authorization': 'Bearer ' + getToken()
|
||||
},
|
||||
success: (uploadRes) => {
|
||||
const res = JSON.parse(uploadRes.data);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (error) => {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 获取导入记录
|
||||
export function getImportRecord() {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/product/importRecord',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// 批量删除商品
|
||||
export function batchDeleteProduct(ids) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/product/batchDelete',
|
||||
method: 'delete',
|
||||
data: { ids: ids }
|
||||
})
|
||||
}
|
||||
|
||||
// 添加品牌
|
||||
export function addBrand(data) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/brand/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取品牌列表
|
||||
export function getBrandList(params) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/brand/list',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取品牌树状结构
|
||||
export function getBrandTree(storeId, brandName) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/brand/getTree/${storeId}`,
|
||||
method: 'get',
|
||||
params: { brandName }
|
||||
})
|
||||
}
|
||||
|
||||
// 删除品牌
|
||||
export function deleteBrand(brandId) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/brand/delete/${brandId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 修改品牌名称
|
||||
export function updateBrand(data) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/brand/update',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 新增分类
|
||||
export function addClassification(data) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/classification/add',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取分类树状结构
|
||||
export function getClassificationTree(storeId, classificationName) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/classification/getTree/${storeId}`,
|
||||
method: 'get',
|
||||
params: { classificationName }
|
||||
})
|
||||
}
|
||||
|
||||
// 修改分类名称
|
||||
export function updateClassification(data) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: '/mall/classification/update',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除分类
|
||||
export function deleteClassification(classificationId) {
|
||||
return request8081({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/classification/delete/${classificationId}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 根据用户ID查询门店列表
|
||||
export function getStoreList(userId) {
|
||||
return request({
|
||||
baseUrl: 'http://193.112.94.36:8081',
|
||||
url: `/mall/store/getUserStore/${userId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询字典数据列表
|
||||
export function listData(query) {
|
||||
return request({
|
||||
url: '/system/dict/data/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询字典数据详细
|
||||
export function getData(dictCode) {
|
||||
return request({
|
||||
url: '/system/dict/data/' + dictCode,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 根据字典类型查询字典数据信息
|
||||
export function getDicts(dictType) {
|
||||
return request({
|
||||
url: '/system/dict/data/type/' + dictType,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增字典数据
|
||||
export function addData(data) {
|
||||
return request({
|
||||
url: '/system/dict/data',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改字典数据
|
||||
export function updateData(data) {
|
||||
return request({
|
||||
url: '/system/dict/data',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除字典数据
|
||||
export function delData(dictCode) {
|
||||
return request({
|
||||
url: '/system/dict/data/' + dictCode,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 测试接口
|
||||
export function getOen() {
|
||||
return request({
|
||||
url: '/system/config/test',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 查询字典类型列表
|
||||
export function listType(query) {
|
||||
return request({
|
||||
url: '/system/dict/type/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询字典类型详细
|
||||
export function getType(dictId) {
|
||||
return request({
|
||||
url: '/system/dict/type/' + dictId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增字典类型
|
||||
export function addType(data) {
|
||||
return request({
|
||||
url: '/system/dict/type',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改字典类型
|
||||
export function updateType(data) {
|
||||
return request({
|
||||
url: '/system/dict/type',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除字典类型
|
||||
export function delType(dictId) {
|
||||
return request({
|
||||
url: '/system/dict/type/' + dictId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 刷新字典缓存
|
||||
export function refreshCache() {
|
||||
return request({
|
||||
url: '/system/dict/type/refreshCache',
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取字典选择框列表
|
||||
export function optionselect() {
|
||||
return request({
|
||||
url: '/system/dict/type/optionselect',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import config from '@/config'
|
||||
import storage from '@/utils/storage'
|
||||
import constant from '@/utils/constant'
|
||||
import { isHttp, isEmpty } from "@/utils/validate"
|
||||
import { login, logout, getInfo } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import defAva from '@/static/images/profile.jpg'
|
||||
|
||||
const baseUrl = config.baseUrl
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: getToken(),
|
||||
id: storage.get(constant.id),
|
||||
name: storage.get(constant.name),
|
||||
avatar: storage.get(constant.avatar),
|
||||
roles: storage.get(constant.roles),
|
||||
permissions: storage.get(constant.permissions)
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_ID: (state, id) => {
|
||||
state.id = id
|
||||
storage.set(constant.id, id)
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
storage.set(constant.name, name)
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
storage.set(constant.avatar, avatar)
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
storage.set(constant.roles, roles)
|
||||
},
|
||||
SET_PERMISSIONS: (state, permissions) => {
|
||||
state.permissions = permissions
|
||||
storage.set(constant.permissions, permissions)
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
Login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(res => {
|
||||
const user = res.user
|
||||
let avatar = (isEmpty(user) || isEmpty(user.avatar)) ? "" : user.avatar
|
||||
if (!isHttp(avatar)) {
|
||||
avatar = (isEmpty(avatar)) ? defAva : baseUrl + avatar
|
||||
}
|
||||
const userid = (isEmpty(user) || isEmpty(user.userId)) ? "" : user.userId
|
||||
const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user.userName
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
commit('SET_ROLES', res.roles)
|
||||
commit('SET_PERMISSIONS', res.permissions)
|
||||
} else {
|
||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||||
}
|
||||
commit('SET_ID', userid)
|
||||
commit('SET_NAME', username)
|
||||
commit('SET_AVATAR', avatar)
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 退出系统
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
removeToken()
|
||||
storage.clean()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default user
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
const ci = require('miniprogram-ci');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// 配置项(必须改!)
|
||||
const config = {
|
||||
appid: 'wx9393bde462d9805a', // 替换成你的正式小程序AppID
|
||||
// 替换成你的密钥文件路径(比如 private.wx9393bde462d9805a.key)
|
||||
privateKeyPath: path.resolve(__dirname, './private.wx9393bde462d9805a.key'),
|
||||
projectPath: path.resolve(__dirname, './unpackage/dist/dev/mp-weixin'),
|
||||
version: '1.0.0',
|
||||
desc: '自动化上传测试',
|
||||
// ci-script.js 中的 setting 配置
|
||||
setting: {
|
||||
es6: true,
|
||||
es7: true,
|
||||
minify: true, // 基础压缩
|
||||
minifyJS: true, // 压缩JS(移除注释、空格、变量名混淆)
|
||||
minifyWXSS: true, // 压缩样式(合并重复样式、移除空格)
|
||||
minifyXML: true, // 压缩配置文件(app.json/page.json)
|
||||
autoPrefixWXSS: true,
|
||||
codeProtect: false, // 关闭代码保护(保护会增加体积)
|
||||
ignoreUnusedFiles: true, // 自动忽略未引用的文件
|
||||
disableUseStrict: true, // 禁用"use strict"减少JS体积
|
||||
disableShowSourceMap: true // 关闭sourcemap(调试文件占体积)
|
||||
},
|
||||
previewQrOutputPath: path.resolve(__dirname, './preview-qr.png'),
|
||||
onProgressUpdate: (res) => {
|
||||
console.log(`进度:${res.progress}%,状态:${res.status}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 上传代码方法
|
||||
async function uploadCode() {
|
||||
try {
|
||||
if (!fs.existsSync(config.privateKeyPath)) {
|
||||
throw new Error(`密钥文件不存在:${config.privateKeyPath}`);
|
||||
}
|
||||
const project = new ci.Project({
|
||||
appid: config.appid,
|
||||
type: 'miniProgram',
|
||||
projectPath: config.projectPath,
|
||||
privateKeyPath: config.privateKeyPath,
|
||||
ignores: ['node_modules/**/*'],
|
||||
});
|
||||
const uploadResult = await ci.upload({
|
||||
project,
|
||||
version: config.version,
|
||||
desc: config.desc,
|
||||
setting: config.setting,
|
||||
onProgressUpdate: config.onProgressUpdate,
|
||||
});
|
||||
console.log('✅ 代码上传成功:', uploadResult);
|
||||
} catch (error) {
|
||||
console.error('❌ 上传失败:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行上传
|
||||
uploadCode();
|
||||
|
|
@ -0,0 +1,269 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="back-btn" @click="goBack">
|
||||
<text class="back-icon">←</text>
|
||||
</view>
|
||||
<view class="title">选择品牌</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<input type="text" placeholder="搜索品牌名称" class="search-input" v-model="searchKeyword" />
|
||||
</view>
|
||||
|
||||
<!-- 品牌树状列表 -->
|
||||
<view class="brand-tree">
|
||||
<!-- 一级品牌项 -->
|
||||
<view v-for="(brand, index) in filteredBrandList" :key="index" class="level1-item">
|
||||
<view class="level1-header">
|
||||
<text class="expand-icon" @click="toggleExpand(index)">{{ brand.expanded ? '▼' : '▶' }}</text>
|
||||
<text class="level1-name" @click="toggleExpand(index)">{{ brand.name }}</text>
|
||||
<view class="action-buttons">
|
||||
<!-- 选择一级品牌按钮 -->
|
||||
<text class="select-btn" @click.stop="selectBrand(brand.name, brand.id, null)">选择</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 二级子品牌容器 -->
|
||||
<view v-if="brand.expanded && brand.children && brand.children.length > 0" class="level2-container">
|
||||
<!-- 二级品牌项 -->
|
||||
<view v-for="(subBrand, subIndex) in brand.children" :key="subIndex" class="level2-item">
|
||||
<text class="level2-name">{{ subBrand.name }}</text>
|
||||
<text class="select-btn" @click.stop="selectBrand(brand.name, brand.id, subBrand.name, subBrand.id)">选择</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部添加按钮 -->
|
||||
<view class="add-brand-btn" @click="goToAddBrand">
|
||||
<text class="add-btn-icon">+</text>
|
||||
<text>添加品牌</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBrandTree } from '@/api/product'
|
||||
import { getStoreId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
brandList: [],
|
||||
searchKeyword: '',
|
||||
storeId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 过滤品牌列表
|
||||
filteredBrandList() {
|
||||
if (!this.searchKeyword.trim()) {
|
||||
return this.brandList
|
||||
}
|
||||
const keyword = this.searchKeyword.toLowerCase().trim()
|
||||
return this.brandList.filter(brand => {
|
||||
// 检查一级品牌是否匹配
|
||||
const matchesLevel1 = brand.name.toLowerCase().includes(keyword)
|
||||
// 检查二级品牌是否有匹配项
|
||||
const matchesLevel2 = brand.children && brand.children.some(subBrand => subBrand.name.toLowerCase().includes(keyword))
|
||||
return matchesLevel1 || matchesLevel2
|
||||
})
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 获取门店ID
|
||||
this.storeId = getStoreId()
|
||||
// 加载品牌列表
|
||||
this.loadBrandList()
|
||||
},
|
||||
methods: {
|
||||
// 加载品牌列表
|
||||
async loadBrandList() {
|
||||
try {
|
||||
const res = await getBrandTree()
|
||||
if (res.code === 200) {
|
||||
// 为每个一级品牌添加expanded属性,默认为true
|
||||
this.brandList = res.data.map(brand => ({
|
||||
...brand,
|
||||
expanded: true,
|
||||
children: brand.children || []
|
||||
}))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取品牌列表失败:', error)
|
||||
}
|
||||
},
|
||||
// 切换品牌展开/收起
|
||||
toggleExpand(index) {
|
||||
this.brandList[index].expanded = !this.brandList[index].expanded
|
||||
},
|
||||
// 选择品牌
|
||||
selectBrand(brandName, brandId, subBrandName, subBrandId) {
|
||||
// 构造返回数据
|
||||
const selectedBrand = {
|
||||
brandName: subBrandName || brandName,
|
||||
brandId: subBrandId || brandId,
|
||||
parentBrandName: subBrandName ? brandName : null,
|
||||
parentBrandId: subBrandName ? brandId : null
|
||||
}
|
||||
// 返回结果给上一页
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
success: () => {
|
||||
// 触发品牌选择事件
|
||||
uni.$emit('brandSelected', selectedBrand)
|
||||
}
|
||||
})
|
||||
},
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
},
|
||||
// 跳转到添加品牌页面
|
||||
goToAddBrand() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/addBrand/addBrand'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
position: relative;
|
||||
height: 88rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-box {
|
||||
background-color: #fff;
|
||||
padding: 16rpx 30rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 6rpx;
|
||||
padding: 18rpx;
|
||||
font-size: 28rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 品牌树 */
|
||||
.brand-tree {
|
||||
flex: 1;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.level1-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.level1-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
font-size: 24rpx;
|
||||
margin-right: 15rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.level1-name {
|
||||
font-size: 28rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.select-btn {
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
font-size: 24rpx;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 6rpx;
|
||||
}
|
||||
|
||||
/* 二级子品牌容器 */
|
||||
.level2-container {
|
||||
display: flex;
|
||||
padding: 24rpx 30rpx;
|
||||
gap: 24rpx;
|
||||
flex-wrap: wrap;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.level2-item {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.level2-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 底部添加按钮 */
|
||||
.add-brand-btn {
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
margin: 0 30rpx 30rpx 30rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.add-btn-icon {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
<template>
|
||||
<view class="scan-view-container">
|
||||
<!-- 扫码控件容器 -->
|
||||
<view class="scan-box" id="scan-box"></view>
|
||||
|
||||
<!-- 扫码状态 -->
|
||||
<view class="scan-status" v-if="isScanning">
|
||||
<view class="scan-line"></view>
|
||||
<text class="scan-text">正在扫描...</text>
|
||||
</view>
|
||||
|
||||
<!-- 扫码控制按钮 -->
|
||||
<view class="scan-controls" v-if="isScanning">
|
||||
<view class="control-btn" @click="toggleFlash">
|
||||
<uni-icons type="flash" size="22" color="#fff"></uni-icons>
|
||||
<text class="control-text">{{ flashOn ? '关闭手电' : '开启手电' }}</text>
|
||||
</view>
|
||||
<view class="control-btn" @click="pauseScan">
|
||||
<uni-icons type="pause" size="22" color="#fff"></uni-icons>
|
||||
<text class="control-text">{{ scanPaused ? '继续扫码' : '暂停扫码' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ScanView',
|
||||
props: {
|
||||
autoStart: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isScanning: false,
|
||||
scanPaused: false,
|
||||
flashOn: false,
|
||||
barcodeInstance: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
if (this.autoStart) {
|
||||
this.initScan();
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.closeScan();
|
||||
},
|
||||
methods: {
|
||||
initScan() {
|
||||
if (typeof plus === 'undefined') {
|
||||
console.log('当前环境不支持plus扫码');
|
||||
this.$emit('error', '扫码功能需要在App中使用');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('开始初始化扫码控件');
|
||||
this.isScanning = true;
|
||||
|
||||
const barcode = plus.barcode.create('barcode', [plus.barcode.CODE_128, plus.barcode.EAN_13], {
|
||||
top: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
position: 'absolute'
|
||||
});
|
||||
|
||||
console.log('扫码控件创建成功');
|
||||
|
||||
barcode.onmarked = (type, result) => {
|
||||
console.log('识别成功:', result);
|
||||
this.isScanning = false;
|
||||
this.$emit('success', result);
|
||||
};
|
||||
|
||||
barcode.onerror = (error) => {
|
||||
console.error('扫码错误:', error);
|
||||
this.isScanning = false;
|
||||
this.$emit('error', error || '扫码失败');
|
||||
};
|
||||
|
||||
const scanBox = document.getElementById('scan-box');
|
||||
if (scanBox) {
|
||||
scanBox.appendChild(barcode);
|
||||
}
|
||||
|
||||
barcode.start();
|
||||
console.log('扫码已启动');
|
||||
this.barcodeInstance = barcode;
|
||||
},
|
||||
|
||||
closeScan() {
|
||||
if (this.barcodeInstance) {
|
||||
this.barcodeInstance.close();
|
||||
this.barcodeInstance = null;
|
||||
this.isScanning = false;
|
||||
console.log('扫码控件已关闭');
|
||||
}
|
||||
},
|
||||
|
||||
pauseScan() {
|
||||
this.scanPaused = !this.scanPaused;
|
||||
if (this.barcodeInstance) {
|
||||
this.scanPaused ? this.barcodeInstance.pause() : this.barcodeInstance.resume();
|
||||
}
|
||||
this.$emit('pause', this.scanPaused);
|
||||
},
|
||||
|
||||
resumeScan() {
|
||||
if (this.barcodeInstance && this.scanPaused) {
|
||||
this.barcodeInstance.resume();
|
||||
this.scanPaused = false;
|
||||
this.isScanning = true;
|
||||
}
|
||||
},
|
||||
|
||||
toggleFlash() {
|
||||
this.flashOn = !this.flashOn;
|
||||
if (this.barcodeInstance) {
|
||||
this.barcodeInstance.setFlash(this.flashOn);
|
||||
}
|
||||
this.$emit('flash', this.flashOn);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.scan-view-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.scan-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.scan-status {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.scan-line {
|
||||
width: 80%;
|
||||
height: 2px;
|
||||
background-color: #e60012;
|
||||
animation: scanMove 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes scanMove {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(250px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-text {
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.scan-controls {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.control-text {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
<template>
|
||||
<view class="uni-section">
|
||||
<view class="uni-section-header" @click="onClick">
|
||||
<view class="uni-section-header__decoration" v-if="type" :class="type" />
|
||||
<slot v-else name="decoration"></slot>
|
||||
|
||||
<view class="uni-section-header__content">
|
||||
<text :style="{'font-size':titleFontSize,'color':titleColor}" class="uni-section__content-title" :class="{'distraction':!subTitle}">{{ title }}</text>
|
||||
<text v-if="subTitle" :style="{'font-size':subTitleFontSize,'color':subTitleColor}" class="uni-section-header__content-sub">{{ subTitle }}</text>
|
||||
</view>
|
||||
|
||||
<view class="uni-section-header__slot-right">
|
||||
<slot name="right"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="uni-section-content" :style="{padding: _padding}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
/**
|
||||
* Section 标题栏
|
||||
* @description 标题栏
|
||||
* @property {String} type = [line|circle|square] 标题装饰类型
|
||||
* @value line 竖线
|
||||
* @value circle 圆形
|
||||
* @value square 正方形
|
||||
* @property {String} title 主标题
|
||||
* @property {String} titleFontSize 主标题字体大小
|
||||
* @property {String} titleColor 主标题字体颜色
|
||||
* @property {String} subTitle 副标题
|
||||
* @property {String} subTitleFontSize 副标题字体大小
|
||||
* @property {String} subTitleColor 副标题字体颜色
|
||||
* @property {String} padding 默认插槽 padding
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniSection',
|
||||
emits:['click'],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ''
|
||||
},
|
||||
titleFontSize: {
|
||||
type: String,
|
||||
default: '14px'
|
||||
},
|
||||
titleColor:{
|
||||
type: String,
|
||||
default: '#333'
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
subTitleFontSize: {
|
||||
type: String,
|
||||
default: '12px'
|
||||
},
|
||||
subTitleColor: {
|
||||
type: String,
|
||||
default: '#999'
|
||||
},
|
||||
padding: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
_padding(){
|
||||
if(typeof this.padding === 'string'){
|
||||
return this.padding
|
||||
}
|
||||
|
||||
return this.padding?'10px':''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
title(newVal) {
|
||||
if (uni.report && newVal !== '') {
|
||||
uni.report('title', newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" >
|
||||
$uni-primary: #2979ff !default;
|
||||
|
||||
.uni-section {
|
||||
background-color: #fff;
|
||||
.uni-section-header {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 12px 10px;
|
||||
font-weight: normal;
|
||||
|
||||
&__decoration{
|
||||
margin-right: 6px;
|
||||
background-color: $uni-primary;
|
||||
&.line {
|
||||
width: 4px;
|
||||
height: 12px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
&.circle {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top-right-radius: 50px;
|
||||
border-top-left-radius: 50px;
|
||||
border-bottom-left-radius: 50px;
|
||||
border-bottom-right-radius: 50px;
|
||||
}
|
||||
|
||||
&.square {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
color: #333;
|
||||
|
||||
.distraction {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
&-sub {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
&__slot-right{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-section-content{
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
12
config.js
|
|
@ -1,8 +1,11 @@
|
|||
// 应用全局配置
|
||||
module.exports = {
|
||||
module.exports = {
|
||||
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
|
||||
|
||||
baseUrl:'http://193.112.94.36:8080',
|
||||
|
||||
// baseUrl:'http://yunzs2.haich.cc',
|
||||
// baseUrl:'https://yunzs.haich.cc',
|
||||
baseUrl:'http://193.112.94.36:8080',
|
||||
// baseUrl = 'https://api.ruoyi.com'
|
||||
// prodApi: 'https://vue.ruoyi.vip/prod-api',
|
||||
// baseUrl: 'http://localhost:8080',
|
||||
// 应用信息
|
||||
|
|
@ -12,7 +15,8 @@ module.exports = {
|
|||
// 应用版本
|
||||
version: "1.2.0",
|
||||
// 应用logo
|
||||
logo: "/static/logo.png",
|
||||
logo: "http://193.112.94.36:8099/static/images/logo.png",
|
||||
// logo: "http://yunzs.haich.cc/static/images/logo.png",
|
||||
// 官方网站
|
||||
site_url: "http://ruoyi.vip",
|
||||
// 政策协议
|
||||
|
|
|
|||
38
main.js
|
|
@ -1,20 +1,20 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import store from './store' // store
|
||||
import plugins from './plugins' // plugins
|
||||
import './permission' // permission
|
||||
import { getDicts } from "@/api/system/dict/data"
|
||||
|
||||
Vue.use(plugins)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$store = store
|
||||
Vue.prototype.getDicts = getDicts
|
||||
|
||||
App.mpType = 'app'
|
||||
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
|
||||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import store from './store' // store
|
||||
import plugins from './plugins' // plugins
|
||||
import './permission' // permission
|
||||
import { getDicts } from "@/api/system/dict/data"
|
||||
|
||||
Vue.use(plugins)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.prototype.$store = store
|
||||
Vue.prototype.getDicts = getDicts
|
||||
|
||||
App.mpType = 'app'
|
||||
|
||||
const app = new Vue({
|
||||
...App
|
||||
})
|
||||
|
||||
app.$mount()
|
||||
|
|
|
|||
|
|
@ -21,21 +21,19 @@
|
|||
"distribute" : {
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<!-- 扫码核心必要权限 -->",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"",
|
||||
"<!-- 网络相关(仅当扫码后需要联网查商品时保留) -->",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"",
|
||||
"<!-- 基础必要权限(如无特殊需求可保留) -->",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
|
||||
]
|
||||
},
|
||||
"ios" : {
|
||||
|
|
@ -50,7 +48,7 @@
|
|||
},
|
||||
"quickapp" : {},
|
||||
"mp-weixin" : {
|
||||
"appid" : "wxccd7e2a0911b3397",
|
||||
"appid" : "wx29a83645aa2b4ebd",
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"es6" : false,
|
||||
|
|
@ -60,11 +58,17 @@
|
|||
"optimization" : {
|
||||
"subPackages" : true
|
||||
},
|
||||
"usingComponents" : true
|
||||
"usingComponents" : true,
|
||||
"networkTimeout" : {
|
||||
"request" : 10000,
|
||||
"connectSocket" : 10000,
|
||||
"uploadFile" : 10000,
|
||||
"downloadFile" : 10000
|
||||
}
|
||||
},
|
||||
"vueVersion" : "2",
|
||||
"h5" : {
|
||||
"template" : "static/index.html",
|
||||
"template" : "http://193.112.94.36:8099/static/images/index.html",
|
||||
"devServer" : {
|
||||
"port" : 9090,
|
||||
"https" : false
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"miniprogram-ci": "^2.1.26"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
{
|
||||
"pages": [{
|
||||
"path": "pages/login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/register",
|
||||
"style": {
|
||||
"navigationBarTitleText": "注册"
|
||||
}
|
||||
}, {
|
||||
"path": "pages/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "若依移动端框架",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}, {"path": "pages/work/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "工作台"
|
||||
}
|
||||
}, {"path": "pages/mine/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
}
|
||||
}, {"path": "pages/product/product",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/mine",
|
||||
"pages": [
|
||||
{"path": "avatar/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改头像"
|
||||
}
|
||||
},
|
||||
{"path": "info/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "个人信息"
|
||||
}
|
||||
},
|
||||
{"path": "info/edit",
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑资料"
|
||||
}
|
||||
},
|
||||
{"path": "pwd/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "修改密码"
|
||||
}
|
||||
},
|
||||
{"path": "setting/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "应用设置"
|
||||
}
|
||||
},
|
||||
{"path": "help/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "常见问题"
|
||||
}
|
||||
},
|
||||
{"path": "about/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "关于我们"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/common",
|
||||
"pages": [
|
||||
{"path": "webview/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "浏览网页"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "textview/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "浏览文本"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/product",
|
||||
"pages": [
|
||||
{"path": "../addProduct/addProduct",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "../batchDeleteProduct/batchDeleteProduct",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "../edit/edit",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/brand",
|
||||
"pages": [
|
||||
{
|
||||
"path": "../addBrand/addBrand",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "../BrandSelector/BrandSelector",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择品牌"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/other",
|
||||
"pages": [
|
||||
|
||||
{"path": "../user/user",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{"path": "../settings/settings",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{"path": "../asset/asset",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{"path": "../enter/enter",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{"path": "../storeSelect/storeSelect",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择门店"
|
||||
}
|
||||
},
|
||||
{"path": "../import/import",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "../userStores/userStores",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "../category/category",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "../back/back",
|
||||
"style": {
|
||||
"navigationBarTitleText": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
"color": "#000000",
|
||||
"selectedColor": "#000000",
|
||||
"borderStyle": "white",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [{
|
||||
"pagePath": "pages/index",
|
||||
"iconPath": "/static/images/tabbar/Frame 87.png",
|
||||
"selectedIconPath": "/static/images/tabbar/Frame 86.png",
|
||||
"text": "我的店"
|
||||
}, {
|
||||
"pagePath": "pages/work/index",
|
||||
"iconPath": "/static/images/tabbar/Union.png",
|
||||
"selectedIconPath": "/static/images/tabbar/Union-1.png",
|
||||
"text": "热销榜"
|
||||
}, {
|
||||
"pagePath": "pages/mine/index",
|
||||
"iconPath": "/static/images/tabbar/Vector-1.png",
|
||||
"selectedIconPath": "/static/images/tabbar/Vector.png",
|
||||
"text": "消息"
|
||||
}
|
||||
]
|
||||
},
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "RuoYi",
|
||||
"navigationBarBackgroundColor": "#FFFFFF"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
<template>
|
||||
<view v-if="visible" class="brand-selector-overlay" @click="handleClose">
|
||||
<view class="brand-selector-container" @click.stop>
|
||||
<!-- 头部 -->
|
||||
<view class="selector-header">
|
||||
<view class="header-left" @click="handleClose">
|
||||
<text class="close-icon">×</text>
|
||||
</view>
|
||||
<view class="header-title">选择品牌</view>
|
||||
<view class="header-right" @click="handleConfirm">
|
||||
<text class="confirm-btn">完成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框(增加层级隔离+样式重置) -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<uni-icons class="search-icon" type="search" size="20" color="#CCCCCC"></uni-icons>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索品牌名称"
|
||||
placeholder-class="input-placeholder"
|
||||
v-model="searchKeyword"
|
||||
class="input"
|
||||
@input="handleSearchInput"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 品牌列表 -->
|
||||
<view class="brand-list">
|
||||
<view
|
||||
class="brand-item"
|
||||
:class="{active: selectedBrand === item.name}"
|
||||
v-for="item in filteredBrandList"
|
||||
:key="item.name"
|
||||
@click="selectBrand(item.name)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 品牌管理入口 -->
|
||||
<view class="brand-management" @click="handleToBrandManagement">
|
||||
<text class="management-text">品牌管理</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'BrandSelector',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
defaultValue: { type: String, default: '默认品牌' },
|
||||
brandList: { type: Array, default: () => [{ name: '默认品牌' }] }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedBrand: this.defaultValue,
|
||||
searchKeyword: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
defaultValue(newVal) {
|
||||
this.selectedBrand = newVal
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
filteredBrandList() {
|
||||
if (!this.searchKeyword) return this.brandList
|
||||
return this.brandList.filter(item =>
|
||||
item.name.toLowerCase().includes(this.searchKeyword.toLowerCase())
|
||||
)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectBrand(brandName) {
|
||||
this.selectedBrand = brandName
|
||||
},
|
||||
handleConfirm() {
|
||||
this.$emit('confirm', this.selectedBrand)
|
||||
this.handleClose()
|
||||
},
|
||||
handleClose() {
|
||||
this.$emit('close')
|
||||
},
|
||||
handleToBrandManagement() {
|
||||
this.$emit('toBrandManagement')
|
||||
},
|
||||
handleSearchInput(e) {
|
||||
this.searchKeyword = e.detail.value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 基础遮罩层
|
||||
.brand-selector-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
// 容器
|
||||
.brand-selector-container {
|
||||
width: 100%;
|
||||
background: #FFFFFF;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
// 头部
|
||||
.selector-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
|
||||
.close-icon { font-size: 36rpx; color: #666666; font-weight: 400; }
|
||||
.header-title { font-size: 32rpx; font-weight: 500; color: #333333; }
|
||||
.confirm-btn { font-size: 30rpx; color: #E62429; font-weight: 400; }
|
||||
}
|
||||
|
||||
// 搜索框:终极修复版(解决覆盖+还原视觉)
|
||||
.search-bar {
|
||||
padding: 20rpx 32rpx;
|
||||
background: #FFFFFF;
|
||||
position: relative;
|
||||
z-index: 1001; // 确保搜索框在最上层
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #FFFFFF !important;
|
||||
border: 1rpx solid #E8E8E8;
|
||||
border-radius: 8rpx;
|
||||
height: 76rpx;
|
||||
padding: 0 24rpx;
|
||||
position: relative;
|
||||
z-index: 1002; // 确保输入框容器层级最高
|
||||
|
||||
.search-icon {
|
||||
margin-right: 16rpx;
|
||||
color: #CCCCCC !important;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1; // 图标层级低于输入框文字
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333333 !important; // 强制文字颜色,防止被全局样式覆盖
|
||||
border: none !important;
|
||||
outline: none !important;
|
||||
background: transparent !important;
|
||||
padding: 0; // 彻底移除内边距,避免行高冲突
|
||||
line-height: 76rpx; // 行高与容器高度一致,确保垂直居中
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
z-index: 2; // 确保文字在最上层,不被任何元素遮挡
|
||||
}
|
||||
|
||||
.input-placeholder {
|
||||
font-size: 28rpx;
|
||||
color: #CCCCCC !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 品牌列表
|
||||
.brand-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.brand-item {
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1rpx solid #F5F5F5;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
position: relative;
|
||||
font-weight: 400;
|
||||
|
||||
&.active {
|
||||
color: #E62429;
|
||||
&::after {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
right: 32rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 品牌管理入口
|
||||
.brand-management {
|
||||
padding: 28rpx 32rpx;
|
||||
text-align: center;
|
||||
color: #1677FF;
|
||||
font-size: 28rpx;
|
||||
border-top: 1rpx solid #F5F5F5;
|
||||
font-weight: 400;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,977 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="back-btn" @click="goBack">
|
||||
<!-- <text class="back-icon">←</text> -->
|
||||
</view>
|
||||
<view class="title">品牌管理</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
placeholder="搜索品牌名称"
|
||||
class="search-input"
|
||||
@input="onSearchInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 品牌树状列表 -->
|
||||
<view class="brand-tree">
|
||||
<!-- 一级品牌项 -->
|
||||
<view v-for="(brand, index) in brandList" :key="index" class="level1-item">
|
||||
<view class="level1-header">
|
||||
<text class="expand-icon" @click="toggleExpand(index)">{{ brand.expanded ? '▼' : '▶' }}</text>
|
||||
<text class="level1-name" @click="toggleExpand(index)">{{ brand.name }}</text>
|
||||
<view class="menu-container" style="position: relative;">
|
||||
<view class="menu-btn" @click.stop="toggleMenuOptions(index)">
|
||||
<text class="menu-icon">···</text>
|
||||
</view>
|
||||
<!-- 菜单选项列表 -->
|
||||
<view v-if="showMenuOptions && selectedBrandIndex === index" class="menu-options">
|
||||
<!-- 重命名选项 -->
|
||||
<view class="menu-option" @click.stop="showRenameDialog(index)">
|
||||
<text class="rename-icon">✏️</text>
|
||||
<text>重命名</text>
|
||||
</view>
|
||||
<!-- 删除品牌选项 -->
|
||||
<view class="menu-option delete-option" @click.stop="showDeleteBrandConfirm(index)">
|
||||
<text class="trash-icon">🗑️</text>
|
||||
<text>删除品牌</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 二级子品牌列表(展开时显示) -->
|
||||
<view v-if="brand.expanded" class="level2-container">
|
||||
<view
|
||||
v-for="(subBrand, subIndex) in brand.children"
|
||||
:key="subIndex"
|
||||
class="level2-item"
|
||||
draggable="true"
|
||||
@dragstart="dragStart(index, subIndex)"
|
||||
@dragover="dragOver"
|
||||
@drop="drop(index, subIndex)"
|
||||
>
|
||||
<text class="level2-name">{{ subBrand.name }}</text>
|
||||
<view class="edit-btn" @click.stop="openEditDialog(index, subIndex)">
|
||||
<text class="edit-icon">✎</text>
|
||||
<text class="edit-text">编辑</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加子品牌按钮 -->
|
||||
<view class="add-sub-brand" @click="addSubBrand(index)">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">添加子品牌</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部添加一级品牌按钮 -->
|
||||
<view class="add-brand-btn" @click="addBrand">
|
||||
<text class="add-btn-icon">+</text>
|
||||
<text class="add-btn-text">添加品牌</text>
|
||||
</view>
|
||||
|
||||
<!-- 修改品牌名称弹窗 -->
|
||||
<view v-if="editDialogVisible" class="dialog-overlay">
|
||||
<view class="dialog">
|
||||
<view class="dialog-title">修改品牌名称</view>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="editBrandName"
|
||||
type="text"
|
||||
class="dialog-input"
|
||||
placeholder="请输入品牌名称"
|
||||
:focus="inputFocus"
|
||||
@blur="inputFocus = false"
|
||||
@focus="onInputFocus"
|
||||
/>
|
||||
<text v-if="editBrandName" class="clear-icon" @click="clearInput">×</text>
|
||||
</view>
|
||||
|
||||
<!-- 带垃圾桶图标的删除按钮 -->
|
||||
<view class="delete-btn" @click="showDeleteConfirm">
|
||||
<text class="trash-icon">🗑️</text>
|
||||
<text>删除品牌</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-buttons">
|
||||
<view class="cancel-btn" @click="closeEditDialog">取消</view>
|
||||
<view class="confirm-btn" @click="confirmEdit">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加品牌弹窗 -->
|
||||
<view v-if="addBrandDialogVisible" class="dialog-overlay">
|
||||
<view class="dialog">
|
||||
<view class="dialog-title">{{ isAddingSubBrand ? '添加子品牌' : '添加品牌' }}</view>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<view class="input-wrapper">
|
||||
<input
|
||||
v-model="addBrandName"
|
||||
type="text"
|
||||
class="dialog-input"
|
||||
placeholder="请输入品牌名称"
|
||||
:focus="inputFocus"
|
||||
@blur="inputFocus = false"
|
||||
@focus="onInputFocus"
|
||||
/>
|
||||
<text v-if="addBrandName" class="clear-icon" @click="clearAddInput">×</text>
|
||||
</view>
|
||||
|
||||
<view class="dialog-buttons">
|
||||
<view class="cancel-btn" @click="closeAddBrandDialog">取消</view>
|
||||
<view class="confirm-btn" @click="confirmAddBrand">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 删除确认弹窗(在修改弹窗之上) -->
|
||||
<view v-if="deleteConfirmVisible" class="delete-confirm-overlay">
|
||||
<view class="delete-confirm-dialog">
|
||||
<view class="delete-dialog-title">确认删除</view>
|
||||
<view class="delete-dialog-content">确定要删除这个品牌吗?</view>
|
||||
<view class="delete-dialog-buttons">
|
||||
<view class="delete-cancel-btn" @click="hideDeleteConfirm">取消</view>
|
||||
<view class="delete-confirm-btn" @click="deleteBrand">删除</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 导入品牌相关API
|
||||
import { getBrandList, addBrand, getBrandTree, deleteBrand, updateBrand } from '@/api/product'
|
||||
import { getStoreId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
brandList: [],
|
||||
storeId: null,
|
||||
editDialogVisible: false,
|
||||
deleteConfirmVisible: false,
|
||||
editParentIndex: null,
|
||||
editSubIndex: null,
|
||||
editBrandName: '',
|
||||
inputFocus: false,
|
||||
dragSource: {
|
||||
parentIndex: null,
|
||||
subIndex: null
|
||||
},
|
||||
// 添加品牌弹窗相关
|
||||
addBrandDialogVisible: false,
|
||||
addBrandName: '',
|
||||
isAddingSubBrand: false,
|
||||
currentParentIndex: null,
|
||||
// 删除品牌相关
|
||||
showMenuOptions: false,
|
||||
selectedBrandIndex: null,
|
||||
isDeletingLevel1Brand: false,
|
||||
// 重命名弹窗相关
|
||||
renameDialogVisible: false,
|
||||
renameBrandName: '',
|
||||
renameBrandIndex: null,
|
||||
// 编辑二级品牌相关
|
||||
editSubBrandId: null,
|
||||
// 搜索关键字
|
||||
searchKeyword: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 获取storeId
|
||||
this.storeId = getStoreId()
|
||||
// 加载品牌列表,初始时不传入搜索关键字
|
||||
this.loadBrandList()
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
// 加载品牌列表
|
||||
async loadBrandList(brandName = '') {
|
||||
try {
|
||||
// 使用新的getBrandTree接口,传入storeId作为URL参数和brandName作为查询参数
|
||||
const res = await getBrandTree(this.storeId, brandName)
|
||||
if (res.code === 200 && res.data) {
|
||||
// 格式化并设置品牌列表
|
||||
this.brandList = this.formatBrandData(res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载品牌列表失败:', error)
|
||||
uni.showToast({ title: '加载品牌列表失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
// 格式化品牌数据为树状结构
|
||||
formatBrandData(brandData) {
|
||||
// 假设新接口返回的数据结构已经是树状的,包含id、brandName、children等字段
|
||||
// 但需要根据实际返回格式进行调整
|
||||
return brandData.map(brand => ({
|
||||
name: brand.brandName,
|
||||
id: brand.id,
|
||||
expanded: true,
|
||||
children: brand.children ? brand.children.map(subBrand => ({
|
||||
name: subBrand.brandName,
|
||||
id: subBrand.id
|
||||
})) : []
|
||||
}))
|
||||
},
|
||||
// 搜索输入处理
|
||||
onSearchInput() {
|
||||
const keyword = this.searchKeyword.trim()
|
||||
|
||||
// 调用API进行搜索,传入搜索关键字
|
||||
this.loadBrandList(keyword)
|
||||
},
|
||||
// 展开/折叠一级品牌
|
||||
toggleExpand(index) {
|
||||
this.brandList[index].expanded = !this.brandList[index].expanded
|
||||
},
|
||||
// 添加一级品牌
|
||||
addBrand() {
|
||||
// 打开添加品牌弹窗
|
||||
this.isAddingSubBrand = false
|
||||
this.currentParentIndex = null
|
||||
this.addBrandName = ''
|
||||
this.addBrandDialogVisible = true
|
||||
|
||||
// 聚焦输入框
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.inputFocus = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
// 添加子品牌
|
||||
addSubBrand(parentIndex) {
|
||||
// 打开添加子品牌弹窗
|
||||
this.isAddingSubBrand = true
|
||||
this.currentParentIndex = parentIndex
|
||||
this.addBrandName = ''
|
||||
this.addBrandDialogVisible = true
|
||||
|
||||
// 聚焦输入框
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.inputFocus = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
// 确认添加或修改品牌
|
||||
async confirmAddBrand() {
|
||||
if (!this.addBrandName.trim()) {
|
||||
uni.showToast({ title: '请输入品牌名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 构造请求参数
|
||||
const params = {
|
||||
brandName: this.addBrandName.trim(),
|
||||
storeId: this.storeId
|
||||
}
|
||||
|
||||
// 如果是添加子品牌,添加parentId参数
|
||||
if (this.isAddingSubBrand) {
|
||||
params.parentId = this.brandList[this.currentParentIndex].id || 0
|
||||
} else {
|
||||
// 添加一级品牌时,parentId为0
|
||||
params.parentId = 0
|
||||
}
|
||||
|
||||
// 调用添加品牌接口
|
||||
const res = await addBrand(params)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 关闭弹窗
|
||||
this.closeAddBrandDialog()
|
||||
// 重新加载品牌列表
|
||||
await this.loadBrandList()
|
||||
|
||||
uni.showToast({
|
||||
title: '添加成功',
|
||||
icon: 'success'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '操作失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加品牌失败:', error)
|
||||
uni.showToast({ title: '添加失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
// 关闭添加品牌弹窗
|
||||
closeAddBrandDialog() {
|
||||
this.addBrandDialogVisible = false
|
||||
this.addBrandName = ''
|
||||
this.inputFocus = false
|
||||
this.editSubBrandId = null
|
||||
},
|
||||
// 清除添加品牌输入框
|
||||
clearAddInput() {
|
||||
this.addBrandName = ''
|
||||
// 聚焦输入框
|
||||
this.$nextTick(() => {
|
||||
this.inputFocus = true
|
||||
})
|
||||
},
|
||||
// 打开编辑弹窗
|
||||
openEditDialog(parentIndex, subIndex) {
|
||||
this.editParentIndex = parentIndex
|
||||
this.editSubIndex = subIndex
|
||||
|
||||
// 判断是一级品牌还是二级品牌
|
||||
if (subIndex === undefined) {
|
||||
// 一级品牌
|
||||
this.isDeletingLevel1Brand = true
|
||||
this.editBrandName = this.brandList[parentIndex].name
|
||||
} else {
|
||||
// 二级品牌
|
||||
this.isDeletingLevel1Brand = false
|
||||
this.editBrandName = this.brandList[parentIndex].children[subIndex].name
|
||||
}
|
||||
|
||||
this.editDialogVisible = true
|
||||
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.inputFocus = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
// 输入框获取焦点
|
||||
onInputFocus() {
|
||||
this.inputFocus = true
|
||||
},
|
||||
// 关闭编辑弹窗
|
||||
closeEditDialog() {
|
||||
this.editDialogVisible = false
|
||||
this.editParentIndex = null
|
||||
this.editSubIndex = null
|
||||
this.editBrandName = ''
|
||||
this.inputFocus = false
|
||||
},
|
||||
// 清除输入框内容
|
||||
clearInput() {
|
||||
this.editBrandName = ''
|
||||
setTimeout(() => {
|
||||
this.inputFocus = true
|
||||
}, 50)
|
||||
},
|
||||
// 确认修改
|
||||
async confirmEdit() {
|
||||
if (this.editBrandName.trim() === '') {
|
||||
uni.showToast({ title: '请输入品牌名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 构造请求参数
|
||||
let brandId;
|
||||
|
||||
// 判断是修改一级品牌还是二级品牌
|
||||
if (this.isDeletingLevel1Brand) {
|
||||
// 修改一级品牌,使用一级品牌的id
|
||||
brandId = this.brandList[this.editParentIndex].id
|
||||
} else {
|
||||
// 修改二级品牌,使用二级品牌的id
|
||||
brandId = this.brandList[this.editParentIndex].children[this.editSubIndex].id
|
||||
}
|
||||
|
||||
const params = {
|
||||
brandName: this.editBrandName.trim(),
|
||||
id: brandId,
|
||||
storeId: this.storeId
|
||||
}
|
||||
|
||||
// 调用修改品牌名称API
|
||||
const res = await updateBrand(params)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 更新本地数据
|
||||
if (this.isDeletingLevel1Brand) {
|
||||
// 修改一级品牌名称
|
||||
this.brandList[this.editParentIndex].name = this.editBrandName.trim()
|
||||
} else {
|
||||
// 修改二级品牌名称
|
||||
this.brandList[this.editParentIndex].children[this.editSubIndex].name = this.editBrandName.trim()
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
this.closeEditDialog()
|
||||
|
||||
uni.showToast({ title: '修改成功', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '修改失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改品牌名称失败:', error)
|
||||
uni.showToast({ title: '修改失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
// 显示删除确认弹窗
|
||||
showDeleteConfirm() {
|
||||
this.deleteConfirmVisible = true
|
||||
},
|
||||
// 隐藏删除确认弹窗
|
||||
hideDeleteConfirm() {
|
||||
this.deleteConfirmVisible = false
|
||||
},
|
||||
// 删除品牌
|
||||
async deleteBrand() {
|
||||
try {
|
||||
let brandId;
|
||||
|
||||
// 判断是删除一级品牌还是二级品牌
|
||||
if (this.isDeletingLevel1Brand) {
|
||||
// 删除一级品牌,使用一级品牌的id
|
||||
brandId = this.brandList[this.editParentIndex].id
|
||||
} else {
|
||||
// 删除二级品牌,使用二级品牌的id
|
||||
brandId = this.brandList[this.editParentIndex].children[this.editSubIndex].id
|
||||
}
|
||||
|
||||
// 调用删除品牌API
|
||||
const res = await deleteBrand(brandId)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 隐藏弹窗
|
||||
this.hideDeleteConfirm()
|
||||
this.closeEditDialog()
|
||||
|
||||
// 重新加载品牌列表
|
||||
await this.loadBrandList()
|
||||
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '删除失败', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除品牌失败:', error)
|
||||
uni.showToast({ title: '删除失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
// 切换菜单选项的显示/隐藏
|
||||
toggleMenuOptions(index) {
|
||||
if (this.showMenuOptions && this.selectedBrandIndex === index) {
|
||||
// 如果点击的是当前显示选项的品牌,关闭选项
|
||||
this.showMenuOptions = false
|
||||
this.selectedBrandIndex = null
|
||||
} else {
|
||||
// 否则显示当前品牌的菜单选项
|
||||
this.showMenuOptions = true
|
||||
this.selectedBrandIndex = index
|
||||
}
|
||||
},
|
||||
// 显示重命名弹窗(一级品牌)
|
||||
showRenameDialog(index) {
|
||||
// 关闭菜单选项
|
||||
this.showMenuOptions = false
|
||||
this.selectedBrandIndex = null
|
||||
|
||||
// 调用打开编辑弹窗方法,只传parentIndex,不传subIndex,表示修改一级品牌
|
||||
this.openEditDialog(index)
|
||||
},
|
||||
// 显示删除品牌确认弹窗
|
||||
showDeleteBrandConfirm(index) {
|
||||
// 关闭菜单选项
|
||||
this.showMenuOptions = false
|
||||
this.selectedBrandIndex = null
|
||||
|
||||
// 设置要删除的品牌索引
|
||||
this.editParentIndex = index
|
||||
this.editSubIndex = 0
|
||||
this.isDeletingLevel1Brand = true
|
||||
|
||||
// 显示删除确认弹窗
|
||||
this.showDeleteConfirm()
|
||||
},
|
||||
// 拖拽开始
|
||||
dragStart(parentIndex, subIndex) {
|
||||
this.dragSource = { parentIndex, subIndex }
|
||||
},
|
||||
// 拖拽经过
|
||||
dragOver(e) {
|
||||
e.preventDefault()
|
||||
},
|
||||
// 拖拽放下
|
||||
drop(targetParentIndex, targetSubIndex) {
|
||||
const { parentIndex, subIndex } = this.dragSource
|
||||
if (parentIndex === targetParentIndex) {
|
||||
const draggedItem = this.brandList[parentIndex].children.splice(subIndex, 1)[0]
|
||||
this.brandList[targetParentIndex].children.splice(targetSubIndex, 0, draggedItem)
|
||||
}
|
||||
this.dragSource = { parentIndex: null, subIndex: null }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
position: relative;
|
||||
height: 88rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
font-size: 28rpx;
|
||||
letter-spacing: 6rpx;
|
||||
}
|
||||
|
||||
/* 菜单容器 */
|
||||
.menu-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 菜单选项列表 */
|
||||
.menu-options {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background-color: #fff;
|
||||
border: 1rpx solid #f0f0f0;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
margin-top: 10rpx;
|
||||
min-width: 200rpx;
|
||||
}
|
||||
|
||||
/* 菜单项 */
|
||||
.menu-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
font-size: 28rpx;
|
||||
padding: 20rpx 30rpx;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
/* 菜单项 hover 效果 */
|
||||
.menu-option:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 重命名选项 */
|
||||
.menu-option:not(.delete-option) {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 重命名图标 */
|
||||
.rename-icon {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 删除品牌选项 */
|
||||
.menu-option.delete-option {
|
||||
color: #e62318;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 垃圾桶图标 */
|
||||
.menu-option .trash-icon {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-box {
|
||||
background-color: #fff;
|
||||
padding: 16rpx 30rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 6rpx;
|
||||
padding: 18rpx;
|
||||
font-size: 28rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
height: 72rpx;
|
||||
}
|
||||
|
||||
/* 品牌树 */
|
||||
.brand-tree {
|
||||
flex: 1;
|
||||
padding: 20rpx 30rpx;
|
||||
}
|
||||
|
||||
.level1-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.level1-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
font-size: 24rpx;
|
||||
margin-right: 15rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.level1-name {
|
||||
font-size: 28rpx;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
letter-spacing: 4rpx;
|
||||
}
|
||||
|
||||
/* 二级子品牌容器 */
|
||||
.level2-container {
|
||||
display: flex;
|
||||
padding: 24rpx 30rpx;
|
||||
gap: 24rpx;
|
||||
flex-wrap: wrap;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.level2-item {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8rpx;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.level2-name {
|
||||
font-size: 36rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.edit-icon {
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.add-sub-brand {
|
||||
border: 2rpx dashed #1677ff;
|
||||
border-radius: 8rpx;
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 36rpx;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 底部添加按钮 */
|
||||
.add-brand-btn {
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx;
|
||||
font-size: 30rpx;
|
||||
margin: 0 30rpx 30rpx 30rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.add-btn-icon {
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
/* ================ 弹窗样式 ================ */
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: #fff;
|
||||
border-radius: 12rpx;
|
||||
width: 85%;
|
||||
max-width: 650rpx;
|
||||
padding: 40rpx 50rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 输入框样式 */
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: 30rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog-input {
|
||||
border: 1rpx solid #e5e5e5;
|
||||
border-radius: 8rpx;
|
||||
padding: 22rpx 60rpx 22rpx 22rpx;
|
||||
font-size: 30rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
background-color: #fff;
|
||||
pointer-events: auto;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
caret-color: #e62318;
|
||||
height: 88rpx;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.dialog-input:focus {
|
||||
border-color: #e62318;
|
||||
box-shadow: 0 0 0 2rpx rgba(230, 35, 24, 0.1);
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
text-align: center;
|
||||
line-height: 30rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f5f5f5;
|
||||
z-index: 1002;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.clear-icon:active {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
/* 删除按钮 */
|
||||
.delete-btn {
|
||||
color: #e62318;
|
||||
font-size: 28rpx;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
padding: 16rpx;
|
||||
border-radius: 8rpx;
|
||||
/* background-color: #fff5f5; */
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-btn:active {
|
||||
background-color: #ffeaea;
|
||||
}
|
||||
|
||||
.trash-icon {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
/* 弹窗按钮 */
|
||||
.dialog-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel-btn:active {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
flex: 1;
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.confirm-btn:active {
|
||||
background-color: #d11f15;
|
||||
}
|
||||
|
||||
/* ================ 删除确认弹窗(在修改弹窗之上) ================ */
|
||||
.delete-confirm-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000; /* 比修改弹窗更高的层级 */
|
||||
}
|
||||
|
||||
.delete-confirm-dialog {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
width: 70%;
|
||||
max-width: 560rpx;
|
||||
padding: 40rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.delete-dialog-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.delete-dialog-content {
|
||||
font-size: 30rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
margin-bottom: 40rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.delete-dialog-buttons {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.delete-cancel-btn {
|
||||
flex: 1;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-cancel-btn:active {
|
||||
background-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.delete-confirm-btn {
|
||||
flex: 1;
|
||||
background-color: #e62318;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 30rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.delete-confirm-btn:active {
|
||||
background-color: #d11f15;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,466 @@
|
|||
<template>
|
||||
<view class="add-product-page">
|
||||
<!-- 头部 -->
|
||||
<view class="page-header">
|
||||
<text class="header-title">添加商品</text>
|
||||
</view>
|
||||
|
||||
<!-- 表单区域 -->
|
||||
<view class="form-container">
|
||||
<!-- 商品名称 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">商品名称</text>
|
||||
<input
|
||||
class="item-input"
|
||||
type="text"
|
||||
placeholder="农夫山泉东方树叶乌龙茶500ml"
|
||||
v-model="productInfo.name"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 条码/条码扫码 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">条码</text>
|
||||
<view class="barcode-section">
|
||||
<input
|
||||
class="item-input barcode-input"
|
||||
type="text"
|
||||
placeholder="6921168558032"
|
||||
v-model="productInfo.barcode"
|
||||
/>
|
||||
<view class="scan-btn" @tap="handleScan">
|
||||
<text class="scan-text">扫码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品图 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">商品图</text>
|
||||
<view class="image-upload-section">
|
||||
<view class="upload-placeholder" @tap="chooseImage">
|
||||
<text class="upload-icon">+</text>
|
||||
<text class="upload-text">点击上传商品图片</text>
|
||||
</view>
|
||||
<image
|
||||
v-if="productInfo.image"
|
||||
:src="productInfo.image"
|
||||
class="product-image"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 售价 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">售价</text>
|
||||
<view class="price-section">
|
||||
<input
|
||||
class="item-input price-input"
|
||||
type="digit"
|
||||
placeholder="5.0"
|
||||
v-model="productInfo.price"
|
||||
/>
|
||||
<text class="price-unit">元</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 推荐价格 -->
|
||||
<view class="recommend-price-section">
|
||||
<text class="recommend-title">推荐价格(点击快速设置售价)</text>
|
||||
<view class="recommend-list">
|
||||
<view class="recommend-item" @tap="setPrice(5.0)">
|
||||
<text class="recommend-percent">82%商家卖</text>
|
||||
<text class="recommend-price">5.0元</text>
|
||||
</view>
|
||||
<view class="recommend-item" @tap="setPrice(5.5)">
|
||||
<text class="recommend-percent">9%商家卖</text>
|
||||
<text class="recommend-price">5.5元</text>
|
||||
</view>
|
||||
<view class="recommend-item" @tap="setPrice(4.5)">
|
||||
<text class="recommend-percent">2%商家卖</text>
|
||||
<text class="recommend-price">4.5元</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="divider"></view>
|
||||
|
||||
<!-- 进货数量 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">进货数量</text>
|
||||
<view class="quantity-section">
|
||||
<input
|
||||
class="item-input"
|
||||
type="number"
|
||||
placeholder="请输入进货数量"
|
||||
v-model="productInfo.quantity"
|
||||
/>
|
||||
<text class="quantity-unit">件</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 进货价 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">进货价</text>
|
||||
<view class="cost-section">
|
||||
<input
|
||||
class="item-input"
|
||||
type="digit"
|
||||
placeholder="请输入进货价"
|
||||
v-model="productInfo.cost"
|
||||
/>
|
||||
<text class="cost-unit">元</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- ✅ 保质期管理 - 红色滑动开关 完美匹配截图 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">保质期管理</text>
|
||||
<switch
|
||||
:checked="productInfo.expirationManagement"
|
||||
@change="toggleExpirationManagement"
|
||||
color="#F53F3F"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 保质期天数 -->
|
||||
<view class="form-item" v-if="productInfo.expirationManagement">
|
||||
<text class="item-label">保质期天数</text>
|
||||
<view class="expiration-options">
|
||||
<view
|
||||
class="expiration-item"
|
||||
:class="{ active: productInfo.expirationDays === 365 }"
|
||||
@tap="setExpirationDays(365)"
|
||||
>
|
||||
<text class="expiration-text">1年</text>
|
||||
<text class="expiration-days">36天</text>
|
||||
</view>
|
||||
<view
|
||||
class="expiration-item"
|
||||
:class="{ active: productInfo.expirationDays === 270 }"
|
||||
@tap="setExpirationDays(270)"
|
||||
>
|
||||
<text class="expiration-text">9个月</text>
|
||||
<text class="expiration-days">27天</text>
|
||||
</view>
|
||||
<view
|
||||
class="expiration-item"
|
||||
:class="{ active: productInfo.expirationDays === 180 }"
|
||||
@tap="setExpirationDays(180)"
|
||||
>
|
||||
<text class="expiration-text">6个月</text>
|
||||
<text class="expiration-days">18天</text>
|
||||
</view>
|
||||
<view
|
||||
class="expiration-item"
|
||||
:class="{ active: productInfo.expirationDays === 240 }"
|
||||
@tap="setExpirationDays(240)"
|
||||
>
|
||||
<text class="expiration-text">8个月</text>
|
||||
<text class="expiration-days">24天</text>
|
||||
</view>
|
||||
<view
|
||||
class="expiration-item"
|
||||
:class="{ active: productInfo.expirationDays === 300 }"
|
||||
@tap="setExpirationDays(300)"
|
||||
>
|
||||
<text class="expiration-text">10个月</text>
|
||||
<text class="expiration-days">30天</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 临期提醒天数 -->
|
||||
<view class="form-item" v-if="productInfo.expirationManagement">
|
||||
<text class="item-label">临期提醒天数</text>
|
||||
<view class="warning-days-display">
|
||||
<text class="warning-days-value">{{ warningDaysDisplay }}</text>
|
||||
<text class="warning-days-unit">天</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生产日期 -->
|
||||
<view class="form-item" v-if="productInfo.expirationManagement">
|
||||
<text class="item-label">生产日期</text>
|
||||
<uni-datetime-picker
|
||||
type="date"
|
||||
v-model="productInfo.productionDate"
|
||||
:start="startDate"
|
||||
:end="endDate"
|
||||
placeholder="请选择生产日期"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 货架码 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">货架码</text>
|
||||
<input
|
||||
class="item-input"
|
||||
type="text"
|
||||
placeholder="请输入货架码"
|
||||
v-model="productInfo.shelfCode"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 商品编码 -->
|
||||
<view class="form-item">
|
||||
<text class="item-label">商品编码</text>
|
||||
<input
|
||||
class="item-input"
|
||||
type="text"
|
||||
placeholder="请输入商品编码"
|
||||
v-model="productInfo.productCode"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<view class="save-button-container">
|
||||
<button class="save-button" @tap="handleSave">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { addProductWithFile } from '@/api/product'
|
||||
import { getStoreId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
productInfo: {
|
||||
name: '',
|
||||
barcode: '',
|
||||
image: '',
|
||||
price: '',
|
||||
quantity: '',
|
||||
cost: '',
|
||||
expirationManagement: false,
|
||||
shelfCode: '',
|
||||
productCode: '',
|
||||
expirationDays: '',
|
||||
warningDays: '',
|
||||
productionDate: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
warningDaysDisplay() {
|
||||
if (!this.productInfo.expirationDays) return '';
|
||||
const days = parseInt(this.productInfo.expirationDays);
|
||||
return days.toString().substring(0, 2);
|
||||
},
|
||||
startDate() {
|
||||
return '2020-01-01';
|
||||
},
|
||||
endDate() {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = (now.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = now.getDate().toString().padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
console.log('页面参数:', options);
|
||||
|
||||
if (options.fromData) {
|
||||
try {
|
||||
const fromData = JSON.parse(decodeURIComponent(options.fromData));
|
||||
console.log('接收到的商品数据:', fromData);
|
||||
this.productInfo.name = fromData.productName || '';
|
||||
this.productInfo.barcode = fromData.productBarCode || '';
|
||||
this.productInfo.price = fromData.storePrice || '';
|
||||
this.productInfo.quantity = fromData.stockQuantity || '';
|
||||
this.productInfo.productCode = fromData.productCode || '';
|
||||
this.productInfo.image = fromData.mainImage || '';
|
||||
} catch (error) {
|
||||
console.error('解析商品数据失败:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 扫码功能
|
||||
handleScan() {
|
||||
uni.showToast({ title: '扫码功能(模拟)', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
this.productInfo.barcode = '6921168558032'
|
||||
uni.showToast({ title: '扫码成功', icon: 'success' })
|
||||
}, 500)
|
||||
},
|
||||
|
||||
// 选择图片
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
this.productInfo.image = res.tempFilePaths[0]
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 设置推荐价格
|
||||
setPrice(price) {
|
||||
this.productInfo.price = price.toString()
|
||||
uni.showToast({ title: `已设置为${price}元`, icon: 'success', duration: 1500 })
|
||||
},
|
||||
|
||||
// ✅ 切换保质期管理-适配红色滑动开关 极简写法
|
||||
toggleExpirationManagement(e) {
|
||||
this.productInfo.expirationManagement = e.detail.value
|
||||
},
|
||||
|
||||
// 设置保质期天数
|
||||
setExpirationDays(days) {
|
||||
this.productInfo.expirationDays = days.toString()
|
||||
const warningDays = days.toString().substring(0, 2);
|
||||
this.productInfo.warningDays = warningDays;
|
||||
uni.showToast({ title: `已设置为${days}天`, icon: 'success', duration: 1500 })
|
||||
},
|
||||
|
||||
// 表单验证-完整校验
|
||||
validateForm() {
|
||||
if (!this.productInfo.name.trim()) { uni.showToast({ title: '请输入商品名称', icon: 'none' });return false }
|
||||
if (!this.productInfo.price) { uni.showToast({ title: '请输入售价', icon: 'none' });return false }
|
||||
// 开启保质期管理 必校验三项
|
||||
if (this.productInfo.expirationManagement) {
|
||||
if (!this.productInfo.expirationDays) { uni.showToast({ title: '请选择保质期天数', icon: 'none' });return false }
|
||||
// 临期提醒天数自动设置,不需要手动输入
|
||||
if (!this.productInfo.productionDate) { uni.showToast({ title: '请选择生产日期', icon: 'none' });return false }
|
||||
}
|
||||
return true
|
||||
},
|
||||
|
||||
// 保存商品-完整传参
|
||||
async handleSave() {
|
||||
if (!this.validateForm()) return
|
||||
const storeId = getStoreId();
|
||||
if (!storeId) { uni.showToast({ title: '请先选择门店', icon: 'none' });return }
|
||||
|
||||
uni.showLoading({ title: '保存中...' });
|
||||
try {
|
||||
const formData = {
|
||||
productName: this.productInfo.name,
|
||||
productBarCode: this.productInfo.barcode,
|
||||
storePrice: this.productInfo.price,
|
||||
stockQuantity: this.productInfo.quantity,
|
||||
costPrice: this.productInfo.cost,
|
||||
productCode: this.productInfo.productCode,
|
||||
shelfCode: this.productInfo.shelfCode,
|
||||
expirationManagement: this.productInfo.expirationManagement ? 1 : 0,
|
||||
shelfLife: this.productInfo.expirationDays,
|
||||
productionDate: this.productInfo.productionDate,
|
||||
approaching: this.productInfo.warningDays,
|
||||
storeId: storeId
|
||||
};
|
||||
const res = await addProductWithFile(this.productInfo.image || '', formData);
|
||||
uni.hideLoading();
|
||||
if (res.code === 200) {
|
||||
uni.showToast({ title: '商品添加成功', icon: 'success', duration: 2000 });
|
||||
setTimeout(() => { uni.navigateBack({ delta: 1 }); }, 2000);
|
||||
} else {
|
||||
uni.showToast({ title: res.msg || '商品添加失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading();
|
||||
console.error('添加商品失败:', error);
|
||||
uni.showToast({ title: '网络请求失败', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.add-product-page {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 120rpx;
|
||||
}
|
||||
|
||||
/* 头部 */
|
||||
.page-header {
|
||||
background-color: #ffffff;
|
||||
padding: 40rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.header-title { font-size: 36rpx;font-weight: 600;color: #333333; }
|
||||
|
||||
/* 表单容器 */
|
||||
.form-container { background-color: #ffffff;margin-top: 20rpx; }
|
||||
|
||||
/* 表单项 */
|
||||
.form-item {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.item-label { font-size: 32rpx;color: #333333;font-weight: 500;min-width: 160rpx; }
|
||||
.item-input { flex:1;font-size:32rpx;color:#333;text-align:right;padding:0 20rpx; }
|
||||
.item-input::placeholder { color: #999999; }
|
||||
|
||||
/* 条码区域 */
|
||||
.barcode-section { display:flex;align-items:center;flex:1; }
|
||||
.barcode-input { flex:1;margin-right:20rpx; }
|
||||
.scan-btn { background:#07C160;padding:16rpx 32rpx;border-radius:8rpx; }
|
||||
.scan-text { color:#fff;font-size:28rpx; }
|
||||
|
||||
/* 图片上传区域 */
|
||||
.image-upload-section { flex:1;display:flex;justify-content:flex-end; }
|
||||
.upload-placeholder { width:200rpx;height:200rpx;border:2rpx dashed #ccc;border-radius:8rpx;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#f9f9f9; }
|
||||
.upload-icon { font-size:48rpx;color:#999;margin-bottom:16rpx; }
|
||||
.upload-text { font-size:24rpx;color:#999; }
|
||||
.product-image { width:200rpx;height:200rpx;border-radius:8rpx; }
|
||||
|
||||
/* 价格区域 */
|
||||
.price-section { display:flex;align-items:center;flex:1;justify-content:flex-end; }
|
||||
.price-input { flex:none;width:120rpx;text-align:center; }
|
||||
.price-unit { font-size:32rpx;color:#333;margin-left:10rpx; }
|
||||
|
||||
/* 推荐价格 */
|
||||
.recommend-price-section { padding:32rpx;border-bottom:1rpx solid #f0f0f0; }
|
||||
.recommend-title { font-size:28rpx;color:#666;margin-bottom:24rpx;display:block; }
|
||||
.recommend-list { display:flex;justify-content:space-between; }
|
||||
.recommend-item { display:flex;flex-direction:column;align-items:center;padding:20rpx 24rpx;background:#f9f9f9;border-radius:8rpx;flex:1;margin:0 10rpx; }
|
||||
.recommend-percent { font-size:26rpx;color:#666;margin-bottom:8rpx; }
|
||||
.recommend-price { font-size:30rpx;color:#07C160;font-weight:500; }
|
||||
|
||||
/* 数量/进货价 单位 */
|
||||
.quantity-section,.cost-section { display:flex;align-items:center;flex:1;justify-content:flex-end; }
|
||||
.quantity-unit,.cost-unit { font-size:32rpx;color:#333;margin-left:10rpx; }
|
||||
|
||||
/* ✅ 保质期相关样式 完美匹配 */
|
||||
.expiration-options { display:flex;flex-wrap:wrap;gap:20rpx;flex:1;justify-content:flex-end; }
|
||||
.expiration-item { display:flex;flex-direction:column;align-items:center;padding:16rpx 20rpx;background:#f9f9f9;border-radius:8rpx;min-width:100rpx;border:2rpx solid transparent;transition:all 0.3s; }
|
||||
.expiration-item.active { background:#F53F3F;border-color:#F53F3F; }
|
||||
.expiration-item.active .expiration-text { color:#fff; }
|
||||
.expiration-item.active .expiration-days { color:#fff; }
|
||||
.expiration-text { font-size:28rpx;color:#333; }
|
||||
.expiration-days { font-size:24rpx;color:#999;margin-top:4rpx; }
|
||||
|
||||
/* 临期提醒天数样式 */
|
||||
.warning-days-display { display:flex;align-items:center;flex:1;justify-content:flex-end; }
|
||||
.warning-days-value { font-size:36rpx;color:#F53F3F;font-weight:600;margin-right:8rpx; }
|
||||
.warning-days-unit { font-size:28rpx;color:#666; }
|
||||
|
||||
/* 分隔线 */
|
||||
.divider { height:20rpx;background:#f5f5f5;border-top:1rpx solid #f0f0f0;border-bottom:1rpx solid #f0f0f0; }
|
||||
|
||||
/* 保存按钮 */
|
||||
.save-button-container { position:fixed;bottom:0;left:0;right:0;background:#fff;padding:20rpx 32rpx;border-top:1rpx solid #f0f0f0; }
|
||||
.save-button { background:#F53F3F;color:#ffffff;font-size:34rpx;height:88rpx;line-height:88rpx;border-radius:8rpx; }
|
||||
.save-button::after { border:none; }
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width:750px) {
|
||||
.recommend-list { flex-direction:column; }
|
||||
.recommend-item { margin:10rpx 0; }
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,275 @@
|
|||
<template>
|
||||
<view class="page-wrapper">
|
||||
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="nav-back" @click="goBack">
|
||||
<!-- <text class="back-icon"><</text> -->
|
||||
</view>
|
||||
<view class="nav-title">收入资产</view>
|
||||
<view class="nav-refresh" @click="refresh">
|
||||
<text class="refresh-icon">⟳</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资产卡片 -->
|
||||
<view class="asset-card">
|
||||
<view class="card-header">
|
||||
<text class="card-title">我的资产</text>
|
||||
</view>
|
||||
<view class="asset-amount">0.00</view>
|
||||
<view class="asset-stats">
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">可提现</text>
|
||||
<text class="stat-value">0.00</text>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<text class="stat-label">待结算</text>
|
||||
<text class="stat-value">0.00</text>
|
||||
<text class="stat-tip">次日可提现</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-actions">
|
||||
<button class="action-btn recharge-btn">充值</button>
|
||||
<button class="action-btn withdraw-btn">提现到银行卡</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 资金明细 -->
|
||||
<view class="fund-section">
|
||||
<view class="section-title">资金明细</view>
|
||||
<view class="detail-list">
|
||||
<view class="detail-item active">
|
||||
<text class="detail-text">可提现</text>
|
||||
<text class="detail-amount">¥0.00 ></text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-text">待结算(元)</text>
|
||||
<text class="detail-amount">¥0.00 ></text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-text">已提现(元)</text>
|
||||
<text class="detail-amount">¥0.00 ></text>
|
||||
</view>
|
||||
<view class="detail-item">
|
||||
<text class="detail-text">现金支付(元)</text>
|
||||
<text class="detail-amount">¥0.00 ></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 银行卡区域 -->
|
||||
<view class="bank-section">
|
||||
<view class="bank-item">
|
||||
<text class="bank-text">我的银行卡</text>
|
||||
<text class="bank-status">未绑定</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 常见问题 -->
|
||||
<view class="faq-section">
|
||||
<text class="faq-text">常见问题</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
const refresh = () => {
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局重置与基础样式 */
|
||||
page {
|
||||
background-color: #f5f5f5;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.page-wrapper {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 状态栏 */
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #000;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.status-icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
.icon {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 30rpx;
|
||||
background-color: #f5f5f5;
|
||||
border-bottom: 1rpx solid #e5e5e5;
|
||||
}
|
||||
.nav-back {
|
||||
width: 60rpx;
|
||||
}
|
||||
.back-icon {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.nav-refresh {
|
||||
width: 60rpx;
|
||||
text-align: right;
|
||||
}
|
||||
.refresh-icon {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 资产卡片 */
|
||||
.asset-card {
|
||||
margin: 20rpx 30rpx;
|
||||
padding: 40rpx 30rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
text-align: center;
|
||||
}
|
||||
.card-header {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
.asset-amount {
|
||||
font-size: 72rpx;
|
||||
font-weight: 700;
|
||||
color: #e63946;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.asset-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
.stat-item {
|
||||
text-align: center;
|
||||
}
|
||||
.stat-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
.stat-tip {
|
||||
display: block;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.action-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
border: none;
|
||||
background-color: #e9e9e9;
|
||||
color: #999;
|
||||
}
|
||||
.withdraw-btn {
|
||||
background-color: #1677ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 资金明细 */
|
||||
.fund-section {
|
||||
margin: 0 30rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.section-title {
|
||||
padding: 30rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.detail-list {
|
||||
padding: 0;
|
||||
}
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
.detail-item.active {
|
||||
/* background-color: #f0f8ff; */
|
||||
/* color: #1677ff; */
|
||||
}
|
||||
.detail-amount {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 银行卡区域 */
|
||||
.bank-section {
|
||||
margin: 0 30rpx 30rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.bank-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
.bank-status {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 常见问题 */
|
||||
.faq-section {
|
||||
text-align: center;
|
||||
padding: 20rpx;
|
||||
}
|
||||
.faq-text {
|
||||
font-size: 26rpx;
|
||||
color: #1677ff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<view class="page-container">
|
||||
<!-- 顶部状态栏(模拟原生手机状态栏) -->
|
||||
|
||||
|
||||
<!-- 导航栏 -->
|
||||
<view class="navbar">
|
||||
|
||||
<view class="nav-tabs">
|
||||
<view class="tab-item active">全部事件</view>
|
||||
<view class="tab-item">只看订单</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索与筛选区 -->
|
||||
<view class="search-section">
|
||||
<view class="search-box">
|
||||
<text class="search-icon">🔍</text>
|
||||
<input class="search-input" placeholder="稽查单/事件/订单/商品名/用户" />
|
||||
<text class="split-icon">☰</text>
|
||||
</view>
|
||||
<view class="filter-btn">
|
||||
<text class="filter-icon">🔍</text>
|
||||
<text class="filter-text">筛选</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<view class="content">
|
||||
<view class="empty-card">
|
||||
<view class="empty-icon-wrapper">
|
||||
<view class="empty-icon">📄⚙️</view>
|
||||
</view>
|
||||
<text class="empty-text">暂无数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面基础容器 */
|
||||
.page-container {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 状态栏 */
|
||||
.status-bar {
|
||||
height: 44px;
|
||||
background-color: #e62318;
|
||||
color: white;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-icons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
height: 48px;
|
||||
background-color: #e62318;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-back {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-tabs {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
font-size: 17px;
|
||||
padding: 0 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tab-item.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -8px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* 搜索与筛选区 */
|
||||
.search-section {
|
||||
background-color: white;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.split-icon {
|
||||
font-size: 16px;
|
||||
color: #999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
height: 36px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 15px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 内容区 */
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.empty-card {
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
padding: 60px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.empty-icon-wrapper {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background-color: #fff5f5;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 32px;
|
||||
color: #ffb3b3;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="nav-back" @tap="goBack">
|
||||
<uni-icons type="back" size="18" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">批量删除</view>
|
||||
<view class="nav-more">
|
||||
<uni-icons type="more-filled" size="18" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索商品名称、条码"
|
||||
placeholder-class="placeholder"
|
||||
v-model="searchText"
|
||||
@confirm="onSearch"
|
||||
/>
|
||||
<uni-icons type="scan" size="18" color="#999"></uni-icons>
|
||||
</view>
|
||||
<view class="filter-btn" @tap="showFilter = !showFilter">
|
||||
<uni-icons type="filter" size="18" color="#999"></uni-icons>
|
||||
<text>筛选</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<scroll-view class="goods-list" scroll-y="true">
|
||||
<!-- 空列表提示 -->
|
||||
<view v-if="goodsList.length === 0" class="empty-tip">
|
||||
<text>暂无商品数据</text>
|
||||
</view>
|
||||
<!-- 商品项 -->
|
||||
<view
|
||||
class="goods-item"
|
||||
v-for="item in goodsList"
|
||||
:key="item.id"
|
||||
>
|
||||
<view class="checkbox-wrap" @tap="toggleSelect(item.id)">
|
||||
<view
|
||||
class="custom-checkbox"
|
||||
:class="{ checked: selectedIds.includes(String(item.id)) }"
|
||||
>
|
||||
<text v-if="selectedIds.includes(String(item.id))" class="check-icon">✓</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<image
|
||||
:src="item.mainImage ? 'http://193.112.94.36:8081' + item.mainImage : '/static/default-goods.png'"
|
||||
class="goods-img"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="goods-details">
|
||||
<text class="goods-name">{{ item.productName || '未命名商品' }}</text>
|
||||
<text class="goods-barcode">{{ item.productBarCode || '无条码' }}</text>
|
||||
<text class="goods-price">¥{{ item.storePrice ? Number(item.storePrice).toFixed(2) : '0.00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom-bar" v-if="goodsList.length > 0">
|
||||
<view class="select-all" @tap="toggleSelectAll">
|
||||
<view class="custom-checkbox" :class="{ checked: isAllSelected }">
|
||||
<text v-if="isAllSelected" class="check-icon">✓</text>
|
||||
</view>
|
||||
<text>全选 (已选{{ selectedIds.length }})</text>
|
||||
</view>
|
||||
<button
|
||||
class="delete-btn"
|
||||
:class="{ disabled: selectedIds.length === 0 }"
|
||||
:disabled="selectedIds.length === 0"
|
||||
@tap="onDeleteSelected"
|
||||
>
|
||||
删除商品
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProductList as fetchProductList, batchDeleteProduct } from '@/api/product'
|
||||
import { getToken, getStoreId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchText: '',
|
||||
showFilter: false,
|
||||
storeId: null,
|
||||
goodsList: [],
|
||||
selectedIds: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
allCheckboxValues() {
|
||||
return this.goodsList.map(item => ({
|
||||
text: '',
|
||||
value: String(item.id)
|
||||
}));
|
||||
},
|
||||
isAllSelected() {
|
||||
return this.goodsList.length > 0 && this.selectedIds.length === this.goodsList.length;
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
const storeId = getStoreId();
|
||||
if (storeId) {
|
||||
this.storeId = storeId;
|
||||
this.getProductList();
|
||||
} else {
|
||||
uni.showToast({ title: '请先选择门店', icon: 'none', duration: 1500 });
|
||||
setTimeout(() => uni.navigateBack(), 1500);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
// 加载商品列表
|
||||
async getProductList(searchParams = {}) {
|
||||
try {
|
||||
if (this.storeId) {
|
||||
searchParams.storeId = this.storeId;
|
||||
}
|
||||
const res = await fetchProductList(searchParams);
|
||||
if (res && res.code === 200 && Array.isArray(res.data)) {
|
||||
this.goodsList = res.data;
|
||||
this.selectedIds = [];
|
||||
} else {
|
||||
uni.showToast({ title: res?.msg || '获取商品失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取商品异常:', error);
|
||||
uni.showToast({ title: '网络错误', icon: 'none' });
|
||||
}
|
||||
},
|
||||
// 搜索
|
||||
onSearch() {
|
||||
const keyword = this.searchText.trim();
|
||||
if (!keyword) {
|
||||
this.getProductList();
|
||||
return;
|
||||
}
|
||||
const searchParams = /^\d+$/.test(keyword)
|
||||
? { productBarCode: keyword }
|
||||
: { productName: keyword };
|
||||
this.getProductList(searchParams);
|
||||
},
|
||||
// 单个商品选中/取消
|
||||
toggleSelect(id) {
|
||||
const idStr = String(id);
|
||||
const index = this.selectedIds.indexOf(idStr);
|
||||
if (index > -1) {
|
||||
this.selectedIds.splice(index, 1);
|
||||
} else {
|
||||
this.selectedIds.push(idStr);
|
||||
}
|
||||
},
|
||||
// 全选/取消全选
|
||||
toggleSelectAll() {
|
||||
if (this.isAllSelected) {
|
||||
this.selectedIds = [];
|
||||
} else {
|
||||
this.selectedIds = this.goodsList.map(item => String(item.id));
|
||||
}
|
||||
},
|
||||
// 批量删除
|
||||
async onDeleteSelected() {
|
||||
if (this.selectedIds.length === 0) return;
|
||||
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定删除选中的${this.selectedIds.length}个商品?删除后不可恢复!`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const idsToDelete = this.selectedIds.map(id => Number(id));
|
||||
const result = await batchDeleteProduct(idsToDelete);
|
||||
if (result && result.code === 200) {
|
||||
uni.showToast({ title: '删除成功', icon: 'success' });
|
||||
this.getProductList();
|
||||
} else {
|
||||
uni.showToast({ title: result?.msg || '删除失败', icon: 'none' });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除异常:', error);
|
||||
uni.showToast({ title: '网络错误', icon: 'none' });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局容器 */
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 80rpx;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
background-color: #e63946;
|
||||
color: #fff;
|
||||
}
|
||||
.nav-title { font-size: 18px; font-weight: 600; }
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
.placeholder { color: #999; }
|
||||
.filter-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
.filter-btn text { margin-left: 4px; }
|
||||
|
||||
/* 商品列表 */
|
||||
.goods-list {
|
||||
height: calc(100vh - 200px);
|
||||
padding: 16px;
|
||||
}
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
.goods-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* 复选框容器 */
|
||||
.checkbox-wrap {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
/* 自定义复选框样式 */
|
||||
.custom-checkbox {
|
||||
width: 28rpx;
|
||||
height: 28rpx;
|
||||
border-radius: 4rpx;
|
||||
border: 2rpx solid #ddd;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.custom-checkbox.checked {
|
||||
background-color: #e63946;
|
||||
border-color: #e63946;
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
color: #fff;
|
||||
font-size: 18rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 商品信息 */
|
||||
.goods-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
.goods-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 12px;
|
||||
}
|
||||
.goods-details { flex: 1; }
|
||||
.goods-name {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.goods-barcode {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.goods-price {
|
||||
font-size: 12px;
|
||||
color: #e63946;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 底部操作栏 */
|
||||
.bottom-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
z-index: 99;
|
||||
}
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
.select-all text { margin-left: 6px; }
|
||||
.delete-btn {
|
||||
padding: 10px 24px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
background-color: #e63946;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
.delete-btn.disabled {
|
||||
background-color: #ccc;
|
||||
color: #999;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<view>
|
||||
<uni-card class="view-title" :title="title">
|
||||
<text class="uni-body view-content">{{ content }}</text>
|
||||
</uni-card>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: '',
|
||||
content: ''
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
this.title = options.title
|
||||
this.content = options.content
|
||||
uni.setNavigationBarTitle({
|
||||
title: options.title
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.view-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.view-content {
|
||||
font-size: 26rpx;
|
||||
padding: 12px 5px 0;
|
||||
color: #333;
|
||||
line-height: 24px;
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<view v-if="params.url">
|
||||
<web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
params: {},
|
||||
webviewStyles: {
|
||||
progress: {
|
||||
color: "#FF3333"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
src: {
|
||||
type: [String],
|
||||
default: null
|
||||
}
|
||||
},
|
||||
onLoad(event) {
|
||||
this.params = event
|
||||
if (event.title) {
|
||||
uni.setNavigationBarTitle({
|
||||
title: event.title
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,635 @@
|
|||
<template>
|
||||
<view class="input-list-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="left" size="20" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">录入清单</view>
|
||||
<view class="nav-right" @click="goRecord">
|
||||
<uni-icons type="redo" size="18" color="#fff"></uni-icons>
|
||||
<text class="record-text">记录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 功能操作栏 -->
|
||||
<view class="operation-bar">
|
||||
<view class="search-box">
|
||||
<uni-icons type="search" size="16" color="#999"></uni-icons>
|
||||
<input type="text" placeholder="搜索商品名称、条码" placeholder-class="search-placeholder" />
|
||||
</view>
|
||||
<button class="btn batch-import" @click="goImport">批量导入</button>
|
||||
<button class="btn no-code" @click="goNoCode">无码/生鲜</button>
|
||||
</view>
|
||||
|
||||
<!-- 条码扫描区域 -->
|
||||
<view class="scan-area">
|
||||
<view class="scan-tip">对准商品条码,自动识别</view>
|
||||
|
||||
<!-- App环境扫码 -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="scan-view" id="barcode-view"></view>
|
||||
<view class="scan-controls" v-if="isScanning">
|
||||
<view class="control-item" @click.stop="toggleFlash">
|
||||
<uni-icons type="flash" size="22" color="#fff"></uni-icons>
|
||||
<text class="control-text">{{ flashOn ? '关闭手电' : '开启手电' }}</text>
|
||||
</view>
|
||||
<view class="control-item" @click.stop="pauseScan">
|
||||
<uni-icons type="pause" size="22" color="#fff"></uni-icons>
|
||||
<text class="control-text">{{ scanPaused ? '继续扫码' : '暂停扫码' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- H5环境提示 -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<view class="h5-tip">
|
||||
<text class="tip-text">扫码功能需要在App中使用</text>
|
||||
<button class="scan-btn" @click="startScan">开始扫码</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 已录入商品列表 -->
|
||||
<view class="goods-list" v-if="goodsList.length > 0">
|
||||
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
|
||||
<view class="goods-info">
|
||||
<image :src="item.image || '/static/default-goods.png'" class="goods-img"></image>
|
||||
<view class="goods-detail">
|
||||
<text class="goods-name">{{ item.name }}</text>
|
||||
<text class="goods-barcode">{{ item.barcode }}</text>
|
||||
<text class="goods-price">¥{{ item.price }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-actions">
|
||||
<view class="action-btn remove" @click="removeGoods(index)">移除商品</view>
|
||||
</view>
|
||||
<view class="goods-form">
|
||||
<view class="form-item">
|
||||
<text class="label required">进货数量</text>
|
||||
<input type="number" v-model="item.quantity" class="input" placeholder="请输入数量" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<text class="label">进货价</text>
|
||||
<input type="number" v-model="item.price" class="input" placeholder="请输入价格" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-area" v-else>
|
||||
<text class="empty-title">暂未录入商品</text>
|
||||
<text class="empty-desc">请在上方选择商品录入方式</text>
|
||||
</view>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<view class="submit-area">
|
||||
<button class="submit-btn" @click="submitList" :disabled="goodsList.length === 0">提交清单</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
flashOn: false,
|
||||
scanPaused: false,
|
||||
isScanning: false,
|
||||
barcodeInstance: null,
|
||||
goodsList: [
|
||||
{
|
||||
name: '怡宝饮用纯净水350ml',
|
||||
barcode: '6901285991240',
|
||||
price: 1,
|
||||
quantity: 1,
|
||||
image: 'http://193.112.94.36:8099/static/images/yibao.png'
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef APP-PLUS
|
||||
console.log('页面 mounted 触发');
|
||||
// 关键修复:等待 Webview 完全渲染后再初始化
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.initScan();
|
||||
}, 300); // 延迟 300ms 确保原生层准备就绪
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
onShow() {
|
||||
// #ifdef APP-PLUS
|
||||
// 从后台返回或从设置返回时,重新检查
|
||||
if (!this.barcodeInstance) {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.requestCameraAuth().then(() => {
|
||||
this.initBarcodeScan();
|
||||
}).catch(err => {
|
||||
console.log('权限不足,无法重新初始化');
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
onUnload() {
|
||||
this.destroyScan();
|
||||
},
|
||||
methods: {
|
||||
goBack() {
|
||||
this.destroyScan();
|
||||
uni.navigateBack({ delta: 1 });
|
||||
},
|
||||
goImport() {
|
||||
uni.navigateTo({ url: '/pages/import/import' });
|
||||
},
|
||||
goNoCode() {
|
||||
uni.navigateTo({ url: '/pages/NoCode/NoCode' });
|
||||
},
|
||||
goRecord() {
|
||||
uni.showToast({ title: '记录功能开发中', icon: 'none' });
|
||||
},
|
||||
|
||||
initScan() {
|
||||
this.requestCameraAuth().then(() => {
|
||||
this.initBarcodeScan();
|
||||
}).catch(err => {
|
||||
console.error('权限获取失败:', err);
|
||||
uni.showModal({
|
||||
title: '权限提示',
|
||||
content: '请前往设置开启相机权限',
|
||||
confirmText: '去设置',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.openAppSettings();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
requestCameraAuth() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// ... 这里保持原来的权限申请代码不变 ...
|
||||
// (如果不确定可以保留之前修复的代码)
|
||||
const platform = uni.getSystemInfoSync().platform;
|
||||
if (platform === 'android') {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const Permission = plus.android.importClass('android.Manifest.permission');
|
||||
const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
|
||||
if (main.checkSelfPermission(Permission.CAMERA) === PackageManager.PERMISSION_GRANTED) {
|
||||
resolve();
|
||||
} else {
|
||||
plus.android.requestPermissions(['android.permission.CAMERA'], (res) => {
|
||||
res[0].granted ? resolve() : reject('用户拒绝');
|
||||
}, reject);
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
openAppSettings() {
|
||||
// ... 这里保持原来的跳转设置代码不变 ...
|
||||
try {
|
||||
plus.android.importClass('android.content.Intent');
|
||||
plus.android.importClass('android.provider.Settings');
|
||||
plus.android.importClass('android.net.Uri');
|
||||
const mainActivity = plus.android.runtimeMainActivity();
|
||||
const intent = new android.content.Intent();
|
||||
intent.setAction(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
const uri = android.net.Uri.fromParts('package', mainActivity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
mainActivity.startActivity(intent);
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '跳转失败,请手动开启', icon: 'none' });
|
||||
}
|
||||
},
|
||||
|
||||
initBarcodeScan() {
|
||||
if (!plus || !plus.barcode) return;
|
||||
|
||||
this.destroyScan();
|
||||
this.isScanning = true;
|
||||
|
||||
const sysInfo = uni.getSystemInfoSync();
|
||||
const statusBarHeight = sysInfo.statusBarHeight || 0;
|
||||
const navBarHeight = 44;
|
||||
const totalNavBarHeight = statusBarHeight + navBarHeight;
|
||||
|
||||
// 调试:打印高度计算结果
|
||||
console.log('高度计算:', { statusBarHeight, navBarHeight, totalNavBarHeight });
|
||||
|
||||
// 调整位置:确保扫码框在搜索栏下方
|
||||
const scanTop = totalNavBarHeight + 80;
|
||||
const scanWidth = sysInfo.windowWidth - 30;
|
||||
const scanHeight = 250; // 固定高度测试,避免宽高比计算错误导致高度为0
|
||||
|
||||
// 创建控件
|
||||
const barcode = plus.barcode.create('barcode',
|
||||
[plus.barcode.CODE_128, plus.barcode.EAN_13, plus.barcode.EAN_8],
|
||||
{
|
||||
top: scanTop,
|
||||
left: 15,
|
||||
width: scanWidth,
|
||||
height: scanHeight,
|
||||
scanbarColor: '#e60012',
|
||||
frameColor: '#e60012',
|
||||
background: '#000',
|
||||
zIndex: 9999 // 强制置顶
|
||||
}
|
||||
);
|
||||
|
||||
barcode.onmarked = (type, result) => {
|
||||
if (result) {
|
||||
console.log('扫码结果:', result);
|
||||
this.addGoodsByBarcode(result);
|
||||
}
|
||||
};
|
||||
|
||||
// 关键:获取当前 Webview 并挂载
|
||||
const currentWebview = this.$scope.$getAppWebview();
|
||||
if (currentWebview) {
|
||||
currentWebview.append(barcode);
|
||||
console.log('扫码控件已挂载到 Webview');
|
||||
} else {
|
||||
console.error('获取 Webview 失败,无法挂载');
|
||||
}
|
||||
|
||||
barcode.start();
|
||||
this.barcodeInstance = barcode;
|
||||
},
|
||||
|
||||
destroyScan() {
|
||||
if (this.barcodeInstance) {
|
||||
this.barcodeInstance.close();
|
||||
this.barcodeInstance = null;
|
||||
}
|
||||
this.isScanning = false;
|
||||
},
|
||||
|
||||
toggleFlash() {
|
||||
if (this.barcodeInstance) {
|
||||
this.flashOn = !this.flashOn;
|
||||
this.barcodeInstance.setFlash(this.flashOn);
|
||||
}
|
||||
},
|
||||
|
||||
pauseScan() {
|
||||
if (this.barcodeInstance) {
|
||||
this.scanPaused = !this.scanPaused;
|
||||
this.scanPaused ? this.barcodeInstance.pause() : this.barcodeInstance.resume();
|
||||
}
|
||||
},
|
||||
|
||||
addGoodsByBarcode(barcode) {
|
||||
const mockGoods = {
|
||||
name: '测试商品-' + barcode,
|
||||
barcode: barcode,
|
||||
price: 10,
|
||||
quantity: 1,
|
||||
image: 'http://193.112.94.36:8099/static/images/yibao.png'
|
||||
};
|
||||
this.goodsList.push(mockGoods);
|
||||
uni.showToast({ title: '识别成功', icon: 'success' });
|
||||
},
|
||||
|
||||
removeGoods(index) {
|
||||
this.goodsList.splice(index, 1);
|
||||
},
|
||||
|
||||
submitList() {
|
||||
uni.showToast({ title: '提交成功', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack(), 1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 页面基础样式 */
|
||||
.input-list-page {
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.navbar {
|
||||
width: 100%;
|
||||
height: calc(var(--status-bar-height) + 44px);
|
||||
background-color: #e60012;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.nav-left, .nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.record-text {
|
||||
font-size: 14px;
|
||||
margin-left: 5px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 功能操作栏 */
|
||||
.operation-bar {
|
||||
margin-top: calc(var(--status-bar-height) + 44px);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.search-placeholder {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.btn {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #eee;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 扫码区域(关键修复:增加z-index) */
|
||||
.scan-area {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
background-color: #fff;
|
||||
z-index: 1; /* 确保扫码区域在最上层 */
|
||||
}
|
||||
|
||||
.scan-tip {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scan-view {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: #000;
|
||||
z-index: 2; /* 关键修复:确保扫码容器层级 */
|
||||
}
|
||||
|
||||
.scan-controls {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.control-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.control-text {
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* H5端提示 */
|
||||
.h5-tip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60px 20px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.tip-text {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.scan-btn {
|
||||
padding: 12px 30px;
|
||||
background-color: #e60012;
|
||||
color: #fff;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 商品列表 */
|
||||
.goods-list {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.goods-img {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.goods-detail {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.goods-barcode {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 14px;
|
||||
color: #e60012;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.goods-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.remove {
|
||||
background-color: #fef0f0;
|
||||
color: #ff4d4f;
|
||||
border: 1px solid #ffccc7;
|
||||
}
|
||||
|
||||
/* 商品表单 */
|
||||
.goods-form {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.required::before {
|
||||
content: '*';
|
||||
color: #e60012;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.input {
|
||||
height: 32px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 4px;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-area {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 提交按钮区域 */
|
||||
.submit-area {
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background-color: #e60012;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.submit-btn:disabled {
|
||||
background-color: #ccc;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,493 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<text class="iconfont">←</text>
|
||||
</view>
|
||||
<view class="nav-title">批量导入</view>
|
||||
<view class="nav-right">
|
||||
<text class="iconfont">···</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 选项卡切换 -->
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item" :class="{active: activeTab === 'import'}" @click="activeTab = 'import'">
|
||||
批量导入
|
||||
</view>
|
||||
<view class="tab-item" :class="{active: activeTab === 'record'}" @click="activeTab = 'record'">
|
||||
导入记录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 批量导入内容区 -->
|
||||
<view v-if="activeTab === 'import'" class="content">
|
||||
<!-- 方式1:导入进货单【核心带完整上传功能】 -->
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<view class="method-tag">方式1</view>
|
||||
<view class="method-title">导入进货单</view>
|
||||
</view>
|
||||
<view class="upload-area" @click="pickFile" v-if="!uploadFileName">
|
||||
<view class="upload-icon">
|
||||
<text class="iconfont">↥</text>
|
||||
</view>
|
||||
<view class="upload-text">点击上传文件</view>
|
||||
<view class="upload-desc">(仅支持10M以下,以xls、xlsx或csv结尾的文件类型)</view>
|
||||
</view>
|
||||
<!-- 已选择文件展示 -->
|
||||
<view class="file-selected" v-else>
|
||||
<view class="file-name">{{uploadFileName}}</view>
|
||||
<view class="file-opt">
|
||||
<text class="reupload" @click="pickFile">重新选择</text>
|
||||
<text class="cancel" @click="cancelFile">取消</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 导入按钮 -->
|
||||
<view class="import-btn-box" v-if="uploadFileName">
|
||||
<button class="import-btn" :loading="isUploading" @click="uploadAndImport">开始导入商品</button>
|
||||
</view>
|
||||
<view class="desc-list">
|
||||
<text class="desc-item">1. 可导入:供应商进货单、其他收银系统的商品导出明细</text>
|
||||
<text class="desc-item">2. 支持录入条形码、商品名称、零售价、库存、进货价、商品品牌</text>
|
||||
<text class="desc-item">3. 导入成功后会在原商品基础上增加库存,不会覆盖原库存</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 方式2:通过电脑端录入 -->
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<view class="method-tag">方式2</view>
|
||||
<view class="method-title">通过电脑端录入</view>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<view class="content-text">支持导入更大文件、更多商品数、商品信息</view>
|
||||
<view class="link-area" @click="openLink">
|
||||
<text class="link-text">请在电脑端访问 http://e.weidian.com/main</text>
|
||||
<text class="iconfont">↗</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 方式3:微店商品快速录入 -->
|
||||
<view class="card">
|
||||
<view class="card-header">
|
||||
<view class="method-tag">方式3</view>
|
||||
<view class="method-title">微店商品快速录入</view>
|
||||
</view>
|
||||
<view class="card-content">
|
||||
<view class="content-text">如果您没有电脑,可以试试我们的快捷设置功能</view>
|
||||
<view class="link-area" @click="goQuickSetup">
|
||||
<text class="link-text">3-20分钟快速设置价格 ></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 导入记录内容区【补充列表骨架+空数据】 -->
|
||||
<view v-else class="record-content">
|
||||
<view class="empty-state" v-if="recordList.length == 0">
|
||||
<text class="empty-icon">📋</text>
|
||||
<text class="empty-text">暂无导入记录</text>
|
||||
<text class="empty-subtext">导入商品后,记录将展示在这里</text>
|
||||
</view>
|
||||
<view class="record-list" v-else>
|
||||
<view class="record-item" v-for="(item,index) in recordList" :key="index">
|
||||
<view class="record-name">{{item.fileName}}</view>
|
||||
<view class="record-info">
|
||||
<text class="record-time">{{item.createTime}}</text>
|
||||
<text class="record-status" :class="item.status">{{item.statusText}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 导入商品相关API
|
||||
import { importProductData, getImportRecord } from '@/api/product';
|
||||
// 导入门店ID获取工具
|
||||
import { getStoreId } from '@/utils/auth';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'import', // 默认选中批量导入
|
||||
uploadFilePath: '', // 选中的文件临时路径
|
||||
uploadFileName: '', // 选中的文件名称
|
||||
isUploading: false, // 是否正在上传/导入中
|
||||
recordList: [] // 导入记录列表
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
// 每次进入页面刷新导入记录
|
||||
if(this.activeTab == 'record'){
|
||||
this.getImportRecord();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack({ delta: 1 });
|
||||
},
|
||||
|
||||
// ========== 核心:选择文件并校验 ==========
|
||||
pickFile() {
|
||||
uni.chooseFile({
|
||||
count: 1, // 只允许选择1个文件
|
||||
type: 'file',
|
||||
sizeLimit: 10 * 1024 * 1024, // 限制10M以内,和页面提示一致
|
||||
success: (res) => {
|
||||
const tempFile = res.tempFiles[0];
|
||||
const fileName = tempFile.name;
|
||||
const fileSize = tempFile.size;
|
||||
|
||||
// 1. 文件大小校验
|
||||
if (fileSize > 10 * 1024 * 1024) {
|
||||
return uni.showToast({ title: '文件大小不能超过10M', icon: 'none', duration: 2000 });
|
||||
}
|
||||
|
||||
// 2. 文件格式校验 (严格匹配xls/xlsx/csv)
|
||||
const suffix = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
|
||||
const allowSuffix = ['.xls', '.xlsx', '.csv'];
|
||||
if (!allowSuffix.includes(suffix)) {
|
||||
return uni.showToast({ title: '仅支持xls、xlsx、csv格式文件', icon: 'none', duration: 2000 });
|
||||
}
|
||||
|
||||
// 校验通过,赋值文件信息
|
||||
this.uploadFilePath = tempFile.path;
|
||||
this.uploadFileName = fileName;
|
||||
uni.showToast({ title: '文件选择成功', icon: 'success', duration: 1500 });
|
||||
},
|
||||
fail: () => {
|
||||
uni.showToast({ title: '文件选择失败,请重新选择', icon: 'none' });
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 取消已选择的文件
|
||||
cancelFile() {
|
||||
this.uploadFilePath = '';
|
||||
this.uploadFileName = '';
|
||||
},
|
||||
|
||||
// ========== 核心:文件上传+商品导入 主逻辑 ✅ 已对接你的真实接口 ✅ ==========
|
||||
uploadAndImport() {
|
||||
if (!this.uploadFilePath) return;
|
||||
this.isUploading = true; // 开启加载状态,防止重复点击
|
||||
|
||||
// 准备表单数据
|
||||
const formData = {
|
||||
storeId: getStoreId()
|
||||
};
|
||||
|
||||
// 调用API进行商品导入
|
||||
importProductData(this.uploadFilePath, formData)
|
||||
.then(res => {
|
||||
this.isUploading = false;
|
||||
|
||||
// 业务成功:导入成功 匹配接口 code=200
|
||||
if (res.code === 200) {
|
||||
uni.showToast({
|
||||
title: `导入成功!共导入 ${res.data} 个商品`,
|
||||
icon: 'success',
|
||||
duration: 2500
|
||||
});
|
||||
// 重置文件选择状态
|
||||
this.cancelFile();
|
||||
}
|
||||
// 业务失败:导入失败
|
||||
else {
|
||||
uni.showModal({
|
||||
title: '导入失败',
|
||||
content: res.msg || '文件内容有误,请检查后重新上传',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.isUploading = false;
|
||||
console.error('文件上传失败:', err);
|
||||
uni.showModal({
|
||||
title: '上传异常',
|
||||
content: '网络异常或服务器连接失败,请检查接口服务后重试',
|
||||
showCancel: false,
|
||||
confirmText: '重新上传'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 打开电脑端链接
|
||||
openLink() {
|
||||
uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: '将在浏览器中打开电脑端录入地址',
|
||||
confirmText: '确认打开',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.openURL({
|
||||
url: 'http://e.weidian.com/main',
|
||||
fail: () => uni.showToast({ title: '打开失败,请手动复制链接', icon: 'none' })
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 跳转快速设置页面
|
||||
goQuickSetup() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/quick-setup/quick-setup'
|
||||
});
|
||||
},
|
||||
|
||||
// 获取导入记录
|
||||
getImportRecord() {
|
||||
uni.showLoading({ title: '加载中...', mask: true });
|
||||
|
||||
// 调用API获取导入记录
|
||||
getImportRecord()
|
||||
.then(res => {
|
||||
if(res.code == 200){
|
||||
this.recordList = res.data || [];
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取导入记录失败:', err);
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 全局样式重置 */
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* 导航栏 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #e62318;
|
||||
color: white;
|
||||
padding: 20rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.nav-left, .nav-right {
|
||||
width: 60rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.nav-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 选项卡 */
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
background-color: white;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 30rpx 0;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
border-bottom: 4rpx solid transparent;
|
||||
}
|
||||
.tab-item.active {
|
||||
color: #e62318;
|
||||
border-bottom-color: #e62318;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 内容区 */
|
||||
.content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
/* 卡片通用样式 */
|
||||
.card {
|
||||
background-color: white;
|
||||
border-radius: 16rpx;
|
||||
margin-bottom: 30rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 30rpx;
|
||||
background-color: white;
|
||||
}
|
||||
.method-tag {
|
||||
background-color: #e62318;
|
||||
color: white;
|
||||
font-size: 24rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
.method-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 上传区域 */
|
||||
.upload-area {
|
||||
margin: 0 30rpx 30rpx;
|
||||
border: 2rpx dashed #ccc;
|
||||
border-radius: 12rpx;
|
||||
padding: 60rpx 30rpx;
|
||||
text-align: center;
|
||||
}
|
||||
.upload-icon {
|
||||
font-size: 48rpx;
|
||||
color: #999;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.upload-text {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.upload-desc {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 已选择文件样式 */
|
||||
.file-selected {
|
||||
margin: 0 30rpx 30rpx;
|
||||
padding: 20rpx;
|
||||
background: #f9f9f9;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.file-name {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.file-opt {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
.reupload {
|
||||
font-size: 26rpx;
|
||||
color: #e62318;
|
||||
}
|
||||
.cancel {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 导入按钮样式 */
|
||||
.import-btn-box {
|
||||
padding: 0 30rpx 20rpx;
|
||||
}
|
||||
.import-btn {
|
||||
width: 100%;
|
||||
background: #e62318;
|
||||
color: white;
|
||||
font-size: 30rpx;
|
||||
padding: 20rpx 0;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
/* 描述列表 */
|
||||
.desc-list {
|
||||
padding: 0 30rpx 30rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.desc-item {
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
/* 卡片内容区 */
|
||||
.card-content {
|
||||
padding: 0 30rpx 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.content-text {
|
||||
margin-bottom: 20rpx;
|
||||
display: block;
|
||||
}
|
||||
.link-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #e62318;
|
||||
}
|
||||
.link-text {
|
||||
font-size: 28rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
/* 导入记录样式 */
|
||||
.record-content {
|
||||
padding: 30rpx;
|
||||
}
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 100rpx 0;
|
||||
color: #999;
|
||||
}
|
||||
.empty-icon {
|
||||
font-size: 60rpx;
|
||||
display: block;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: 30rpx;
|
||||
margin-bottom: 10rpx;
|
||||
display: block;
|
||||
}
|
||||
.empty-subtext {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
.record-list {
|
||||
background: white;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
.record-item {
|
||||
padding: 30rpx;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
.record-name {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.record-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
.record-status.success {
|
||||
color: #07c160;
|
||||
}
|
||||
.record-status.fail {
|
||||
color: #e62318;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<view class="normal-login-container">
|
||||
<view class="logo-content align-center justify-center flex">
|
||||
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
|
||||
</image>
|
||||
<text class="title">移动端登录</text>
|
||||
</view>
|
||||
<view class="login-form-content">
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-user icon"></view>
|
||||
<input v-model="loginForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
|
||||
</view>
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-password icon"></view>
|
||||
<input v-model="loginForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
|
||||
</view>
|
||||
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
|
||||
<view class="iconfont icon-code icon"></view>
|
||||
<input v-model="loginForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
|
||||
<view class="login-code">
|
||||
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btn">
|
||||
<button @click="handleLogin" class="login-btn cu-btn block bg-blue lg round">登录</button>
|
||||
</view>
|
||||
<view class="reg text-center" v-if="register">
|
||||
<text class="text-grey1">没有账号?</text>
|
||||
<text @click="handleUserRegister" class="text-blue">立即注册</text>
|
||||
</view>
|
||||
<view class="xieyi text-center">
|
||||
<text class="text-grey1">登录即代表同意</text>
|
||||
<text @click="handleUserAgrement" class="text-blue">《用户协议》</text>
|
||||
<text @click="handlePrivacy" class="text-blue">《隐私协议》</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCodeImg } from '@/api/login'
|
||||
import { getToken, setUserId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
codeUrl: "",
|
||||
captchaEnabled: true,
|
||||
// 用户注册开关
|
||||
register: false,
|
||||
globalConfig: getApp().globalData.config,
|
||||
loginForm: {
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
code: "",
|
||||
uuid: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCode()
|
||||
},
|
||||
onLoad() {
|
||||
//#ifdef H5
|
||||
if (getToken()) {
|
||||
this.$tab.reLaunch('/pages/index')
|
||||
}
|
||||
//#endif
|
||||
},
|
||||
methods: {
|
||||
// 用户注册
|
||||
handleUserRegister() {
|
||||
this.$tab.redirectTo(`/pages/register`)
|
||||
},
|
||||
// 隐私协议
|
||||
handlePrivacy() {
|
||||
let site = this.globalConfig.appInfo.agreements[0]
|
||||
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
|
||||
},
|
||||
// 用户协议
|
||||
handleUserAgrement() {
|
||||
let site = this.globalConfig.appInfo.agreements[1]
|
||||
this.$tab.navigateTo(`/pages/common/webview/index?title=${site.title}&url=${site.url}`)
|
||||
},
|
||||
// 获取图形验证码
|
||||
getCode() {
|
||||
getCodeImg().then(res => {
|
||||
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
if (this.captchaEnabled) {
|
||||
this.codeUrl = 'data:image/gif;base64,' + res.img
|
||||
this.loginForm.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
},
|
||||
// 登录方法
|
||||
async handleLogin() {
|
||||
if (this.loginForm.username === "") {
|
||||
this.$modal.msgError("请输入账号")
|
||||
} else if (this.loginForm.password === "") {
|
||||
this.$modal.msgError("请输入密码")
|
||||
} else if (this.loginForm.code === "" && this.captchaEnabled) {
|
||||
this.$modal.msgError("请输入验证码")
|
||||
} else {
|
||||
this.$modal.loading("登录中,请耐心等待...")
|
||||
this.pwdLogin()
|
||||
}
|
||||
},
|
||||
// 密码登录
|
||||
async pwdLogin() {
|
||||
this.$store.dispatch('Login', this.loginForm).then(() => {
|
||||
this.$modal.closeLoading()
|
||||
this.loginSuccess()
|
||||
}).catch(() => {
|
||||
if (this.captchaEnabled) {
|
||||
this.getCode()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 登录成功后,处理函数
|
||||
loginSuccess(result) {
|
||||
// 设置用户信息(包含用户ID)
|
||||
this.$store.dispatch('GetInfo').then(res => {
|
||||
console.log('用户信息:', res);
|
||||
|
||||
// 获取并存储用户ID
|
||||
if (res.user && res.user.userId) {
|
||||
setUserId(res.user.userId);
|
||||
console.log('用户ID已存储:', res.user.userId);
|
||||
}
|
||||
|
||||
// 跳转到门店选择页面
|
||||
uni.redirectTo({
|
||||
url: '/pages/storeSelect/storeSelect'
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.normal-login-container {
|
||||
width: 100%;
|
||||
|
||||
.logo-content {
|
||||
width: 100%;
|
||||
font-size: 21px;
|
||||
text-align: center;
|
||||
padding-top: 15%;
|
||||
|
||||
image {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form-content {
|
||||
text-align: center;
|
||||
margin: 20px auto;
|
||||
margin-top: 15%;
|
||||
width: 80%;
|
||||
|
||||
.input-item {
|
||||
margin: 20px auto;
|
||||
background-color: #f5f6f7;
|
||||
height: 45px;
|
||||
border-radius: 20px;
|
||||
|
||||
.icon {
|
||||
font-size: 38rpx;
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
margin-top: 40px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.reg {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.xieyi {
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
height: 38px;
|
||||
float: right;
|
||||
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
width: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<view class="about-container">
|
||||
<view class="header-section text-center">
|
||||
<image style="width: 150rpx;height: 150rpx;" src="/static/logo200.png" mode="widthFix">
|
||||
</image>
|
||||
<uni-title type="h2" title="若依移动端"></uni-title>
|
||||
</view>
|
||||
|
||||
<view class="content-section">
|
||||
<view class="menu-list">
|
||||
<view class="list-cell list-cell-arrow">
|
||||
<view class="menu-item-box">
|
||||
<view>版本信息</view>
|
||||
<view class="text-right">v{{version}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow">
|
||||
<view class="menu-item-box">
|
||||
<view>官方邮箱</view>
|
||||
<view class="text-right">ruoyi@xx.com</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow">
|
||||
<view class="menu-item-box">
|
||||
<view>服务热线</view>
|
||||
<view class="text-right">400-999-9999</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow">
|
||||
<view class="menu-item-box">
|
||||
<view>公司网站</view>
|
||||
<view class="text-right">
|
||||
<uni-link :href="url" :text="url" showUnderLine="false"></uni-link>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="copyright">
|
||||
<view>Copyright © 2025 ruoyi.vip All Rights Reserved.</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
url: getApp().globalData.config.appInfo.site_url,
|
||||
version: getApp().globalData.config.appInfo.version
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.copyright {
|
||||
margin-top: 50rpx;
|
||||
text-align: center;
|
||||
line-height: 60rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
display: flex;
|
||||
padding: 30rpx 0 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,619 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<view class="page-body uni-content-info">
|
||||
<view class='cropper-content'>
|
||||
<view v-if="isShowImg" class="uni-corpper" :style="'width:'+cropperInitW+'px;height:'+cropperInitH+'px;background:#000'">
|
||||
<view class="uni-corpper-content" :style="'width:'+cropperW+'px;height:'+cropperH+'px;left:'+cropperL+'px;top:'+cropperT+'px'">
|
||||
<image :src="imageSrc" :style="'width:'+cropperW+'px;height:'+cropperH+'px'"></image>
|
||||
<view class="uni-corpper-crop-box" @touchstart.stop="contentStartMove" @touchmove.stop="contentMoveing" @touchend.stop="contentTouchEnd"
|
||||
:style="'left:'+cutL+'px;top:'+cutT+'px;right:'+cutR+'px;bottom:'+cutB+'px'">
|
||||
<view class="uni-cropper-view-box">
|
||||
<view class="uni-cropper-dashed-h"></view>
|
||||
<view class="uni-cropper-dashed-v"></view>
|
||||
<view class="uni-cropper-line-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-line-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-line-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-line-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-t" data-drag="top" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-tr" data-drag="topTight"></view>
|
||||
<view class="uni-cropper-point point-r" data-drag="right" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-rb" data-drag="rightBottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-b" data-drag="bottom" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-bl" data-drag="bottomLeft"></view>
|
||||
<view class="uni-cropper-point point-l" data-drag="left" @touchstart.stop="dragStart" @touchmove.stop="dragMove"></view>
|
||||
<view class="uni-cropper-point point-lt" data-drag="leftTop"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class='cropper-config'>
|
||||
<button type="primary reverse" @click="getImage" style='margin-top: 30rpx;'> 选择头像 </button>
|
||||
<button type="warn" @click="getImageInfo" style='margin-top: 30rpx;'> 提交 </button>
|
||||
</view>
|
||||
<canvas canvas-id="myCanvas" :style="'position:absolute;border: 1px solid red; width:'+imageW+'px;height:'+imageH+'px;top:-9999px;left:-9999px;'"></canvas>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from './../../../config'
|
||||
import store from "@/store"
|
||||
import { uploadAvatar } from "@/api/system/user"
|
||||
|
||||
const baseUrl = config.baseUrl
|
||||
console.log('Avatar module base URL:', baseUrl)
|
||||
let sysInfo = uni.getSystemInfoSync()
|
||||
let SCREEN_WIDTH = sysInfo.screenWidth
|
||||
let PAGE_X, // 手按下的x位置
|
||||
PAGE_Y, // 手按下y的位置
|
||||
PR = sysInfo.pixelRatio, // dpi
|
||||
T_PAGE_X, // 手移动的时候x的位置
|
||||
T_PAGE_Y, // 手移动的时候Y的位置
|
||||
CUT_L, // 初始化拖拽元素的left值
|
||||
CUT_T, // 初始化拖拽元素的top值
|
||||
CUT_R, // 初始化拖拽元素的
|
||||
CUT_B, // 初始化拖拽元素的
|
||||
CUT_W, // 初始化拖拽元素的宽度
|
||||
CUT_H, // 初始化拖拽元素的高度
|
||||
IMG_RATIO, // 图片比例
|
||||
IMG_REAL_W, // 图片实际的宽度
|
||||
IMG_REAL_H, // 图片实际的高度
|
||||
DRAFG_MOVE_RATIO = 1, //移动时候的比例,
|
||||
INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
|
||||
DRAW_IMAGE_W = sysInfo.screenWidth // 设置生成的图片宽度
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data() {
|
||||
return {
|
||||
imageSrc: store.getters.avatar,
|
||||
isShowImg: false,
|
||||
// 初始化的宽高
|
||||
cropperInitW: SCREEN_WIDTH,
|
||||
cropperInitH: SCREEN_WIDTH,
|
||||
// 动态的宽高
|
||||
cropperW: SCREEN_WIDTH,
|
||||
cropperH: SCREEN_WIDTH,
|
||||
// 动态的left top值
|
||||
cropperL: 0,
|
||||
cropperT: 0,
|
||||
|
||||
transL: 0,
|
||||
transT: 0,
|
||||
|
||||
// 图片缩放值
|
||||
scaleP: 0,
|
||||
imageW: 0,
|
||||
imageH: 0,
|
||||
|
||||
// 裁剪框 宽高
|
||||
cutL: 0,
|
||||
cutT: 0,
|
||||
cutB: SCREEN_WIDTH,
|
||||
cutR: '100%',
|
||||
qualityWidth: DRAW_IMAGE_W,
|
||||
innerAspectRadio: DRAFG_MOVE_RATIO
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady: function () {
|
||||
this.loadImage()
|
||||
},
|
||||
methods: {
|
||||
setData: function (obj) {
|
||||
let that = this
|
||||
Object.keys(obj).forEach(function (key) {
|
||||
that.$set(that.$data, key, obj[key])
|
||||
})
|
||||
},
|
||||
getImage: function () {
|
||||
var _this = this
|
||||
uni.chooseImage({
|
||||
success: function (res) {
|
||||
_this.setData({
|
||||
imageSrc: res.tempFilePaths[0],
|
||||
})
|
||||
_this.loadImage()
|
||||
},
|
||||
})
|
||||
},
|
||||
loadImage: function () {
|
||||
var _this = this
|
||||
|
||||
uni.getImageInfo({
|
||||
src: _this.imageSrc,
|
||||
success: function success(res) {
|
||||
IMG_RATIO = 1 / 1
|
||||
if (IMG_RATIO >= 1) {
|
||||
IMG_REAL_W = SCREEN_WIDTH
|
||||
IMG_REAL_H = SCREEN_WIDTH / IMG_RATIO
|
||||
} else {
|
||||
IMG_REAL_W = SCREEN_WIDTH * IMG_RATIO
|
||||
IMG_REAL_H = SCREEN_WIDTH
|
||||
}
|
||||
let minRange = IMG_REAL_W > IMG_REAL_H ? IMG_REAL_W : IMG_REAL_H
|
||||
INIT_DRAG_POSITION = minRange > INIT_DRAG_POSITION ? INIT_DRAG_POSITION : minRange
|
||||
// 根据图片的宽高显示不同的效果 保证图片可以正常显示
|
||||
if (IMG_RATIO >= 1) {
|
||||
let cutT = Math.ceil((SCREEN_WIDTH / IMG_RATIO - (SCREEN_WIDTH / IMG_RATIO - INIT_DRAG_POSITION)) / 2)
|
||||
let cutB = cutT
|
||||
let cutL = Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH + INIT_DRAG_POSITION) / 2)
|
||||
let cutR = cutL
|
||||
_this.setData({
|
||||
cropperW: SCREEN_WIDTH,
|
||||
cropperH: SCREEN_WIDTH / IMG_RATIO,
|
||||
// 初始化left right
|
||||
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
|
||||
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH / IMG_RATIO) / 2),
|
||||
cutL: cutL,
|
||||
cutT: cutT,
|
||||
cutR: cutR,
|
||||
cutB: cutB,
|
||||
// 图片缩放值
|
||||
imageW: IMG_REAL_W,
|
||||
imageH: IMG_REAL_H,
|
||||
scaleP: IMG_REAL_W / SCREEN_WIDTH,
|
||||
qualityWidth: DRAW_IMAGE_W,
|
||||
innerAspectRadio: IMG_RATIO
|
||||
})
|
||||
} else {
|
||||
let cutL = Math.ceil((SCREEN_WIDTH * IMG_RATIO - (SCREEN_WIDTH * IMG_RATIO)) / 2)
|
||||
let cutR = cutL
|
||||
let cutT = Math.ceil((SCREEN_WIDTH - INIT_DRAG_POSITION) / 2)
|
||||
let cutB = cutT
|
||||
_this.setData({
|
||||
cropperW: SCREEN_WIDTH * IMG_RATIO,
|
||||
cropperH: SCREEN_WIDTH,
|
||||
// 初始化left right
|
||||
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH * IMG_RATIO) / 2),
|
||||
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
|
||||
|
||||
cutL: cutL,
|
||||
cutT: cutT,
|
||||
cutR: cutR,
|
||||
cutB: cutB,
|
||||
// 图片缩放值
|
||||
imageW: IMG_REAL_W,
|
||||
imageH: IMG_REAL_H,
|
||||
scaleP: IMG_REAL_W / SCREEN_WIDTH,
|
||||
qualityWidth: DRAW_IMAGE_W,
|
||||
innerAspectRadio: IMG_RATIO
|
||||
})
|
||||
}
|
||||
_this.setData({
|
||||
isShowImg: true
|
||||
})
|
||||
uni.hideLoading()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 拖动时候触发的touchStart事件
|
||||
contentStartMove(e) {
|
||||
PAGE_X = e.touches[0].pageX
|
||||
PAGE_Y = e.touches[0].pageY
|
||||
},
|
||||
|
||||
// 拖动时候触发的touchMove事件
|
||||
contentMoveing(e) {
|
||||
var _this = this
|
||||
var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
|
||||
var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
|
||||
// 左移
|
||||
if (dragLengthX > 0) {
|
||||
if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL
|
||||
} else {
|
||||
if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR
|
||||
}
|
||||
|
||||
if (dragLengthY > 0) {
|
||||
if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT
|
||||
} else {
|
||||
if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB
|
||||
}
|
||||
this.setData({
|
||||
cutL: this.cutL - dragLengthX,
|
||||
cutT: this.cutT - dragLengthY,
|
||||
cutR: this.cutR + dragLengthX,
|
||||
cutB: this.cutB + dragLengthY
|
||||
})
|
||||
|
||||
PAGE_X = e.touches[0].pageX
|
||||
PAGE_Y = e.touches[0].pageY
|
||||
},
|
||||
|
||||
contentTouchEnd() {
|
||||
|
||||
},
|
||||
|
||||
// 获取图片
|
||||
getImageInfo() {
|
||||
var _this = this
|
||||
uni.showLoading({
|
||||
title: '图片生成中...',
|
||||
})
|
||||
// 将图片写入画布
|
||||
const ctx = uni.createCanvasContext('myCanvas')
|
||||
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H)
|
||||
ctx.draw(true, () => {
|
||||
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
|
||||
var canvasW = ((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) * IMG_REAL_W
|
||||
var canvasH = ((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) * IMG_REAL_H
|
||||
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W
|
||||
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H
|
||||
uni.canvasToTempFilePath({
|
||||
x: canvasL,
|
||||
y: canvasT,
|
||||
width: canvasW,
|
||||
height: canvasH,
|
||||
destWidth: canvasW,
|
||||
destHeight: canvasH,
|
||||
quality: 0.5,
|
||||
canvasId: 'myCanvas',
|
||||
success: function (res) {
|
||||
uni.hideLoading()
|
||||
let data = {name: 'avatarfile', filePath: res.tempFilePath}
|
||||
uploadAvatar(data).then(response => {
|
||||
store.commit('SET_AVATAR', baseUrl + response.imgUrl)
|
||||
uni.showToast({ title: "修改成功", icon: 'success' })
|
||||
uni.navigateBack()
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 设置大小的时候触发的touchStart事件
|
||||
dragStart(e) {
|
||||
T_PAGE_X = e.touches[0].pageX
|
||||
T_PAGE_Y = e.touches[0].pageY
|
||||
CUT_L = this.cutL
|
||||
CUT_R = this.cutR
|
||||
CUT_B = this.cutB
|
||||
CUT_T = this.cutT
|
||||
},
|
||||
|
||||
// 设置大小的时候触发的touchMove事件
|
||||
dragMove(e) {
|
||||
var _this = this
|
||||
var dragType = e.target.dataset.drag
|
||||
switch (dragType) {
|
||||
case 'right':
|
||||
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
|
||||
if (CUT_R + dragLength < 0) dragLength = -CUT_R
|
||||
this.setData({
|
||||
cutR: CUT_R + dragLength
|
||||
})
|
||||
break
|
||||
case 'left':
|
||||
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
|
||||
if (CUT_L - dragLength < 0) dragLength = CUT_L
|
||||
if ((CUT_L - dragLength) > (this.cropperW - this.cutR)) dragLength = CUT_L - (this.cropperW - this.cutR)
|
||||
this.setData({
|
||||
cutL: CUT_L - dragLength
|
||||
})
|
||||
break
|
||||
case 'top':
|
||||
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
|
||||
if (CUT_T - dragLength < 0) dragLength = CUT_T
|
||||
if ((CUT_T - dragLength) > (this.cropperH - this.cutB)) dragLength = CUT_T - (this.cropperH - this.cutB)
|
||||
this.setData({
|
||||
cutT: CUT_T - dragLength
|
||||
})
|
||||
break
|
||||
case 'bottom':
|
||||
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
|
||||
if (CUT_B + dragLength < 0) dragLength = -CUT_B
|
||||
this.setData({
|
||||
cutB: CUT_B + dragLength
|
||||
})
|
||||
break
|
||||
case 'rightBottom':
|
||||
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO
|
||||
var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO
|
||||
|
||||
if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B
|
||||
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R
|
||||
let cutB = CUT_B + dragLengthY
|
||||
let cutR = CUT_R + dragLengthX
|
||||
|
||||
this.setData({
|
||||
cutB: cutB,
|
||||
cutR: cutR
|
||||
})
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cropper-config {
|
||||
padding: 20rpx 40rpx;
|
||||
}
|
||||
|
||||
.cropper-content {
|
||||
min-height: 750rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.uni-corpper {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-touch-callout: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.uni-corpper-content {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-corpper-content image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-width: 0 !important;
|
||||
max-width: none !important;
|
||||
height: 100%;
|
||||
min-height: 0 !important;
|
||||
max-height: none !important;
|
||||
image-orientation: 0deg !important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 移动图片效果 */
|
||||
.uni-cropper-drag-box {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
cursor: move;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 内部的信息 */
|
||||
.uni-corpper-crop-box {
|
||||
position: absolute;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.uni-corpper-crop-box .uni-cropper-view-box {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
outline: 1rpx solid #69f;
|
||||
outline-color: rgba(102, 153, 255, .75)
|
||||
}
|
||||
|
||||
/* 横向虚线 */
|
||||
.uni-cropper-dashed-h {
|
||||
position: absolute;
|
||||
top: 33.33333333%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 33.33333333%;
|
||||
border-top: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||
border-bottom: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 纵向虚线 */
|
||||
.uni-cropper-dashed-v {
|
||||
position: absolute;
|
||||
left: 33.33333333%;
|
||||
top: 0;
|
||||
width: 33.33333333%;
|
||||
height: 100%;
|
||||
border-left: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||
border-right: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* 四个方向的线 为了之后的拖动事件*/
|
||||
.uni-cropper-line-t {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #69f;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 1rpx;
|
||||
opacity: 0.1;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.uni-cropper-line-t::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0rpx;
|
||||
width: 100%;
|
||||
-webkit-transform: translate3d(0, -50%, 0);
|
||||
transform: translate3d(0, -50%, 0);
|
||||
bottom: 0;
|
||||
height: 41rpx;
|
||||
background: transparent;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.uni-cropper-line-r {
|
||||
position: absolute;
|
||||
display: block;
|
||||
background-color: #69f;
|
||||
top: 0;
|
||||
right: 0rpx;
|
||||
width: 1rpx;
|
||||
opacity: 0.1;
|
||||
height: 100%;
|
||||
cursor: e-resize;
|
||||
}
|
||||
|
||||
.uni-cropper-line-r::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 41rpx;
|
||||
-webkit-transform: translate3d(-50%, 0, 0);
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.uni-cropper-line-b {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background-color: #69f;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 1rpx;
|
||||
opacity: 0.1;
|
||||
cursor: s-resize;
|
||||
}
|
||||
|
||||
.uni-cropper-line-b::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0rpx;
|
||||
width: 100%;
|
||||
-webkit-transform: translate3d(0, -50%, 0);
|
||||
transform: translate3d(0, -50%, 0);
|
||||
bottom: 0;
|
||||
height: 41rpx;
|
||||
background: transparent;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.uni-cropper-line-l {
|
||||
position: absolute;
|
||||
display: block;
|
||||
background-color: #69f;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1rpx;
|
||||
opacity: 0.1;
|
||||
height: 100%;
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.uni-cropper-line-l::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
width: 41rpx;
|
||||
-webkit-transform: translate3d(-50%, 0, 0);
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.uni-cropper-point {
|
||||
width: 5rpx;
|
||||
height: 5rpx;
|
||||
background-color: #69f;
|
||||
opacity: .75;
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.point-t {
|
||||
top: -3rpx;
|
||||
left: 50%;
|
||||
margin-left: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-tr {
|
||||
top: -3rpx;
|
||||
left: 100%;
|
||||
margin-left: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-r {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-left: -3rpx;
|
||||
margin-top: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-rb {
|
||||
left: 100%;
|
||||
top: 100%;
|
||||
-webkit-transform: translate3d(-50%, -50%, 0);
|
||||
transform: translate3d(-50%, -50%, 0);
|
||||
cursor: n-resize;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
background-color: #69f;
|
||||
position: absolute;
|
||||
z-index: 1112;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.point-b {
|
||||
left: 50%;
|
||||
top: 100%;
|
||||
margin-left: -3rpx;
|
||||
margin-top: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-bl {
|
||||
left: 0%;
|
||||
top: 100%;
|
||||
margin-left: -3rpx;
|
||||
margin-top: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-l {
|
||||
left: 0%;
|
||||
top: 50%;
|
||||
margin-left: -3rpx;
|
||||
margin-top: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
.point-lt {
|
||||
left: 0%;
|
||||
top: 0%;
|
||||
margin-left: -3rpx;
|
||||
margin-top: -3rpx;
|
||||
cursor: n-resize;
|
||||
}
|
||||
|
||||
/* 裁剪框预览内容 */
|
||||
.uni-cropper-viewer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-cropper-viewer image {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<view class="help-container">
|
||||
<view v-for="(item, findex) in list" :key="findex" :title="item.title" class="list-title">
|
||||
<view class="text-title">
|
||||
<view :class="item.icon"></view>{{ item.title }}
|
||||
</view>
|
||||
<view class="childList">
|
||||
<view v-for="(child, zindex) in item.childList" :key="zindex" class="question" hover-class="hover"
|
||||
@click="handleText(child)">
|
||||
<view class="text-item">{{ child.title }}</view>
|
||||
<view class="line" v-if="zindex !== item.childList.length - 1"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [{
|
||||
icon: 'iconfont icon-github',
|
||||
title: '若依问题',
|
||||
childList: [{
|
||||
title: '若依开源吗?',
|
||||
content: '开源'
|
||||
}, {
|
||||
title: '若依可以商用吗?',
|
||||
content: '可以'
|
||||
}, {
|
||||
title: '若依官网地址多少?',
|
||||
content: 'http://ruoyi.vip'
|
||||
}, {
|
||||
title: '若依文档地址多少?',
|
||||
content: 'http://doc.ruoyi.vip'
|
||||
}]
|
||||
},
|
||||
{
|
||||
icon: 'iconfont icon-help',
|
||||
title: '其他问题',
|
||||
childList: [{
|
||||
title: '如何退出登录?',
|
||||
content: '请点击[我的] - [应用设置] - [退出登录]即可退出登录',
|
||||
}, {
|
||||
title: '如何修改用户头像?',
|
||||
content: '请点击[我的] - [选择头像] - [点击提交]即可更换用户头像',
|
||||
}, {
|
||||
title: '如何修改登录密码?',
|
||||
content: '请点击[我的] - [应用设置] - [修改密码]即可修改登录密码',
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleText(item) {
|
||||
this.$tab.navigateTo(`/pages/common/textview/index?title=${item.title}&content=${item.content}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.help-container {
|
||||
margin-bottom: 100rpx;
|
||||
padding: 30rpx;
|
||||
}
|
||||
|
||||
.list-title {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.childList {
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 0px 10rpx rgba(193, 193, 193, 0.2);
|
||||
border-radius: 16rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 100%;
|
||||
height: 1rpx;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.text-title {
|
||||
color: #303133;
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
margin-left: 10rpx;
|
||||
|
||||
.iconfont {
|
||||
font-size: 16px;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.text-item {
|
||||
font-size: 28rpx;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.question {
|
||||
color: #606266;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
<template>
|
||||
<view class="mine-container" :style="{height: `${windowHeight}px`}">
|
||||
<!--顶部个人信息栏-->
|
||||
<view class="header-section">
|
||||
<view class="flex padding justify-between">
|
||||
<view class="flex align-center">
|
||||
<view v-if="!avatar" class="cu-avatar xl round bg-white">
|
||||
<view class="iconfont icon-people text-gray icon"></view>
|
||||
</view>
|
||||
<image v-if="avatar" @click="handleToAvatar" :src="avatar" class="cu-avatar xl round" mode="widthFix">
|
||||
</image>
|
||||
<view v-if="!name" @click="handleToLogin" class="login-tip">
|
||||
点击登录
|
||||
</view>
|
||||
<view v-if="name" @click="handleToInfo" class="user-info">
|
||||
<view class="u_title">
|
||||
用户名:{{ name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view @click="handleToInfo" class="flex align-center">
|
||||
<text>个人信息</text>
|
||||
<view class="iconfont icon-right"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="content-section">
|
||||
<view class="mine-actions grid col-4 text-center">
|
||||
<view class="action-item" @click="handleJiaoLiuQun">
|
||||
<view class="iconfont icon-friendfill text-pink icon"></view>
|
||||
<text class="text">交流群</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleBuilding">
|
||||
<view class="iconfont icon-service text-blue icon"></view>
|
||||
<text class="text">在线客服</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleBuilding">
|
||||
<view class="iconfont icon-community text-mauve icon"></view>
|
||||
<text class="text">反馈社区</text>
|
||||
</view>
|
||||
<view class="action-item" @click="handleBuilding">
|
||||
<view class="iconfont icon-dianzan text-green icon"></view>
|
||||
<text class="text">点赞我们</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="list-cell list-cell-arrow" @click="handleToEditInfo">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-user menu-icon"></view>
|
||||
<view>编辑资料</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow" @click="handleHelp">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-help menu-icon"></view>
|
||||
<view>常见问题</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow" @click="handleAbout">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-aixin menu-icon"></view>
|
||||
<view>关于我们</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow" @click="handleToSetting">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-setting menu-icon"></view>
|
||||
<view>应用设置</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: this.$store.state.user.name
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
avatar() {
|
||||
return this.$store.state.user.avatar
|
||||
},
|
||||
windowHeight() {
|
||||
return uni.getSystemInfoSync().windowHeight - 50
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleToInfo() {
|
||||
this.$tab.navigateTo('/pages/mine/info/index')
|
||||
},
|
||||
handleToEditInfo() {
|
||||
this.$tab.navigateTo('/pages/mine/info/edit')
|
||||
},
|
||||
handleToSetting() {
|
||||
this.$tab.navigateTo('/pages/mine/setting/index')
|
||||
},
|
||||
handleToLogin() {
|
||||
this.$tab.reLaunch('/pages/login')
|
||||
},
|
||||
handleToAvatar() {
|
||||
this.$tab.navigateTo('/pages/mine/avatar/index')
|
||||
},
|
||||
handleHelp() {
|
||||
this.$tab.navigateTo('/pages/mine/help/index')
|
||||
},
|
||||
handleAbout() {
|
||||
this.$tab.navigateTo('/pages/mine/about/index')
|
||||
},
|
||||
handleJiaoLiuQun() {
|
||||
this.$modal.showToast('QQ群:①133713780(满)、②146013835(满)、③189091635')
|
||||
},
|
||||
handleBuilding() {
|
||||
this.$modal.showToast('模块建设中~')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #f5f6f7;
|
||||
}
|
||||
|
||||
.mine-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
.header-section {
|
||||
padding: 15px 15px 45px 15px;
|
||||
background-color: #3c96f3;
|
||||
color: white;
|
||||
|
||||
.login-tip {
|
||||
font-size: 18px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.cu-avatar {
|
||||
border: 2px solid #eaeaea;
|
||||
|
||||
.icon {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-left: 15px;
|
||||
|
||||
.u_title {
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-section {
|
||||
position: relative;
|
||||
top: -50px;
|
||||
|
||||
.mine-actions {
|
||||
margin: 15px 15px;
|
||||
padding: 20px 0px;
|
||||
border-radius: 8px;
|
||||
background-color: white;
|
||||
|
||||
.action-item {
|
||||
.icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.text {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
margin: 8px 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<view class="example">
|
||||
<uni-forms ref="form" :model="user" labelWidth="80px">
|
||||
<uni-forms-item label="用户昵称" name="nickName">
|
||||
<uni-easyinput v-model="user.nickName" placeholder="请输入昵称" />
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="手机号码" name="phonenumber">
|
||||
<uni-easyinput v-model="user.phonenumber" placeholder="请输入手机号码" />
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="邮箱" name="email">
|
||||
<uni-easyinput v-model="user.email" placeholder="请输入邮箱" />
|
||||
</uni-forms-item>
|
||||
<uni-forms-item label="性别" name="sex" required>
|
||||
<uni-data-checkbox v-model="user.sex" :localdata="sexs" />
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<button type="primary" @click="submit">提交</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUserProfile } from "@/api/system/user"
|
||||
import { updateUserProfile } from "@/api/system/user"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
nickName: "",
|
||||
phonenumber: "",
|
||||
email: "",
|
||||
sex: ""
|
||||
},
|
||||
sexs: [{
|
||||
text: '男',
|
||||
value: "0"
|
||||
}, {
|
||||
text: '女',
|
||||
value: "1"
|
||||
}],
|
||||
rules: {
|
||||
nickName: {
|
||||
rules: [{
|
||||
required: true,
|
||||
errorMessage: '用户昵称不能为空'
|
||||
}]
|
||||
},
|
||||
phonenumber: {
|
||||
rules: [{
|
||||
required: true,
|
||||
errorMessage: '手机号码不能为空'
|
||||
}, {
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
errorMessage: '请输入正确的手机号码'
|
||||
}]
|
||||
},
|
||||
email: {
|
||||
rules: [{
|
||||
required: true,
|
||||
errorMessage: '邮箱地址不能为空'
|
||||
}, {
|
||||
format: 'email',
|
||||
errorMessage: '请输入正确的邮箱地址'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.getUser()
|
||||
},
|
||||
onReady() {
|
||||
this.$refs.form.setRules(this.rules)
|
||||
},
|
||||
methods: {
|
||||
getUser() {
|
||||
getUserProfile().then(response => {
|
||||
this.user = response.data
|
||||
})
|
||||
},
|
||||
submit(ref) {
|
||||
this.$refs.form.validate().then(res => {
|
||||
updateUserProfile(this.user).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.example {
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.segmented-control {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<uni-list>
|
||||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'person-filled'}" title="昵称" :rightText="user.nickName" />
|
||||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'phone-filled'}" title="手机号码" :rightText="user.phonenumber" />
|
||||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'email-filled'}" title="邮箱" :rightText="user.email" />
|
||||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'auth-filled'}" title="岗位" :rightText="postGroup" />
|
||||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'staff-filled'}" title="角色" :rightText="roleGroup" />
|
||||
<uni-list-item showExtraIcon="true" :extraIcon="{type: 'calendar-filled'}" title="创建日期" :rightText="user.createTime" />
|
||||
</uni-list>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUserProfile } from "@/api/system/user"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: {},
|
||||
roleGroup: "",
|
||||
postGroup: ""
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.getUser()
|
||||
},
|
||||
methods: {
|
||||
getUser() {
|
||||
getUserProfile().then(response => {
|
||||
this.user = response.data
|
||||
this.roleGroup = response.roleGroup
|
||||
this.postGroup = response.postGroup
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<template>
|
||||
<view class="pwd-retrieve-container">
|
||||
<uni-forms ref="form" :value="user" labelWidth="80px">
|
||||
<uni-forms-item name="oldPassword" label="旧密码">
|
||||
<uni-easyinput type="password" v-model="user.oldPassword" placeholder="请输入旧密码" />
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="newPassword" label="新密码">
|
||||
<uni-easyinput type="password" v-model="user.newPassword" placeholder="请输入新密码" />
|
||||
</uni-forms-item>
|
||||
<uni-forms-item name="confirmPassword" label="确认密码">
|
||||
<uni-easyinput type="password" v-model="user.confirmPassword" placeholder="请确认新密码" />
|
||||
</uni-forms-item>
|
||||
<button type="primary" @click="submit">提交</button>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { updateUserPwd } from "@/api/system/user"
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
oldPassword: undefined,
|
||||
newPassword: undefined,
|
||||
confirmPassword: undefined
|
||||
},
|
||||
rules: {
|
||||
oldPassword: {
|
||||
rules: [{
|
||||
required: true,
|
||||
errorMessage: '旧密码不能为空'
|
||||
}]
|
||||
},
|
||||
newPassword: {
|
||||
rules: [{
|
||||
required: true,
|
||||
errorMessage: '新密码不能为空',
|
||||
},
|
||||
{
|
||||
minLength: 6,
|
||||
maxLength: 20,
|
||||
errorMessage: '长度在 6 到 20 个字符'
|
||||
}
|
||||
]
|
||||
},
|
||||
confirmPassword: {
|
||||
rules: [{
|
||||
required: true,
|
||||
errorMessage: '确认密码不能为空'
|
||||
}, {
|
||||
validateFunction: (rule, value, data) => data.newPassword === value,
|
||||
errorMessage: '两次输入的密码不一致'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
this.$refs.form.setRules(this.rules)
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.$refs.form.validate().then(res => {
|
||||
updateUserPwd(this.user.oldPassword, this.user.newPassword).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.pwd-retrieve-container {
|
||||
padding-top: 36rpx;
|
||||
padding: 15px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<view class="setting-container" :style="{height: `${windowHeight}px`}">
|
||||
<view class="menu-list">
|
||||
<view class="list-cell list-cell-arrow" @click="handleToPwd">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-password menu-icon"></view>
|
||||
<view>修改密码</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow" @click="handleToUpgrade">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-refresh menu-icon"></view>
|
||||
<view>检查更新</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-cell list-cell-arrow" @click="handleCleanTmp">
|
||||
<view class="menu-item-box">
|
||||
<view class="iconfont icon-clean menu-icon"></view>
|
||||
<view>清理缓存</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="cu-list menu">
|
||||
<view class="cu-item item-box">
|
||||
<view class="content text-center" @click="handleLogout">
|
||||
<text class="text-black">退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
windowHeight: uni.getSystemInfoSync().windowHeight
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleToPwd() {
|
||||
this.$tab.navigateTo('/pages/mine/pwd/index')
|
||||
},
|
||||
handleToUpgrade() {
|
||||
this.$modal.showToast('模块建设中~')
|
||||
},
|
||||
handleCleanTmp() {
|
||||
this.$modal.showToast('模块建设中~')
|
||||
},
|
||||
handleLogout() {
|
||||
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
|
||||
this.$store.dispatch('LogOut').then(() => {}).finally(()=>{
|
||||
this.$tab.reLaunch('/pages/index')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.item-box {
|
||||
background-color: #FFFFFF;
|
||||
margin: 30rpx;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
border-radius: 8rpx;
|
||||
color: #303133;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,854 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<!-- 顶部状态栏/时间 -->
|
||||
<view class="status-bar">
|
||||
<text class="time">9:41</text>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="search-bar">
|
||||
<view class="search-input">
|
||||
<uni-icons type="search" size="18" color="#999"></uni-icons>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="搜索商品条码"
|
||||
placeholder-class="placeholder"
|
||||
v-model="searchText"
|
||||
@confirm="onSearch"
|
||||
/>
|
||||
<uni-icons type="scan" size="18" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 筛选标签 -->
|
||||
<view class="filter-tabs">
|
||||
<scroll-view class="tabs-scroll" scroll-x="true">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'all' }"
|
||||
@tap="switchTab('all')"
|
||||
>
|
||||
全部
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'expiring' }"
|
||||
@tap="switchTab('expiring')"
|
||||
>
|
||||
临期/过期<text class="badge">0</text>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: currentTab === 'outstock' }"
|
||||
@tap="switchTab('outstock')"
|
||||
>
|
||||
缺货<text class="badge">0</text>
|
||||
</view>
|
||||
<view class="tab-item filter-btn" @tap="showFilter = !showFilter">
|
||||
<text>筛选</text>
|
||||
<uni-icons type="arrowdown" size="14" color="#666"></uni-icons>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 微店状态栏 -->
|
||||
<view class="store-status" v-if="currentTab === 'all'">
|
||||
<view class="status-content">
|
||||
<view class="dui">
|
||||
<image class="dui-img" src="http://193.112.94.36:8099/static/images/Frame 52.png"></image>
|
||||
</view>
|
||||
<view class="status-left">
|
||||
<text class="status-text">已开启微店商店库</text>
|
||||
<text class="status-num">微店商品库全226+商品</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="status-right" @tap="goToSettings">
|
||||
<text class="set-text">设置</text>
|
||||
<uni-icons type="forward" size="14" color="#666"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 临期/过期状态栏 -->
|
||||
<view class="store-status" v-else-if="currentTab === 'expiring'">
|
||||
<view class="status-left-content">
|
||||
<view class="dui">
|
||||
<image class="dui-img" src="http://193.112.94.36:8099/static/images/Mask group.png"></image>
|
||||
</view>
|
||||
<view class="status-left">
|
||||
<text class="status-text">快速处理</text>
|
||||
<text class="status-num">临期/过期食品</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="status-right-buttons">
|
||||
<view class="quick-btn pink-btn" @tap="goToPromotion">
|
||||
<text class="btn-text">临期促销</text>
|
||||
<uni-icons type="arrowright" size="12" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="quick-btn pink-btn" @tap="goToExpiredOut">
|
||||
<text class="btn-text">过期出库</text>
|
||||
<uni-icons type="arrowright" size="12" color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 缺货状态栏 -->
|
||||
<view class="store-status" v-else-if="currentTab === 'outstock'">
|
||||
<view class="status-content">
|
||||
<view class="dui">
|
||||
<image class="dui-img" src="http://193.112.94.36:8099/static/images/Mask group (1).png"></image>
|
||||
</view>
|
||||
<view class="status-left">
|
||||
<text class="status-text">库存不足预警设置</text>
|
||||
<text class="status-num">库存低于已设置阈值时,会收到APP推送提醒</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="status-right" @tap="goToAlertSettings">
|
||||
<text class="set-text">设置</text>
|
||||
<uni-icons type="forward" size="14" color="#666"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 商品列表 -->
|
||||
<scroll-view class="goods-list" scroll-y="true">
|
||||
<view class="goods-item" v-for="item in goodsList" :key="item.id">
|
||||
<view class="goods-more-icon" @tap="toggleDeleteMenu(item)">
|
||||
<uni-icons type="more-filled" size="18"></uni-icons>
|
||||
</view>
|
||||
<view class="goods-delete-menu" v-if="item.showDeleteMenu" @tap="showDeleteConfirm(item)">
|
||||
<text class="delete-menu-text">删除商品</text>
|
||||
</view>
|
||||
<view class="goods-top" @tap="goToStockDetail(item)">
|
||||
<view class="goods-tou">
|
||||
<image :src="item.mainImage ? 'http://193.112.94.36:8081' + item.mainImage : 'http://193.112.94.36:8099/static/images/687b6f95b14eff60f4b77147b3726ab2.jpg' "></image>
|
||||
</view>
|
||||
<view class="goods-info">
|
||||
<text class="goods-name">{{ item.productName }}</text>
|
||||
<text class="goods-barcode">{{ item.productBarCode }}</text>
|
||||
<text class="goods-price">¥{{ item.storePrice ? item.storePrice.toFixed(2) : '0.00' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="goods-divider"></view>
|
||||
<view class="goods-stock" >
|
||||
<text class="stock-label">总库存</text>
|
||||
<text class="stock-num">{{ item.stockQuantity }} 件</text>
|
||||
<text class="stock-arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部操作按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<view class="action-btn more-btn" @tap="showMoreActions()">
|
||||
<text>更多</text>
|
||||
</view>
|
||||
<button class="action-button primary" @tap="addNewGoods">
|
||||
添加商品/库存
|
||||
</button>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- 更多操作弹窗 -->
|
||||
<uni-popup ref="popup" type="bottom">
|
||||
<view class="popup-content">
|
||||
<view class="popup-item" @tap="goToInventoryCount">
|
||||
<uni-icons type="scan" size="20" color="#333"></uni-icons>
|
||||
<text>盘点库存</text>
|
||||
</view>
|
||||
<view class="popup-item" @tap="goToOutbound">
|
||||
<uni-icons type="arrowright" size="20" color="#333"></uni-icons>
|
||||
<text>出库</text>
|
||||
</view>
|
||||
<view class="popup-item" @tap="batchDeleteGoods">
|
||||
<uni-icons type="trash" size="20" color="#333"></uni-icons>
|
||||
<text>批量删除商品</text>
|
||||
</view>
|
||||
<view class="popup-item" @tap="goToCategoryManagement">
|
||||
<uni-icons type="apps" size="20" color="#333"></uni-icons>
|
||||
<text>分类管理</text>
|
||||
</view>
|
||||
<view class="popup-item" @tap="goToBrandManagement">
|
||||
<uni-icons type="navigate" size="20" color="#333"></uni-icons>
|
||||
<text>品牌管理</text>
|
||||
</view>
|
||||
<view class="popup-item" @tap="goToBarcodeSettings">
|
||||
<uni-icons type="settings" size="20" color="#333"></uni-icons>
|
||||
<text>条码规则设置</text>
|
||||
</view>
|
||||
<view class="popup-item cancel" @tap="$refs.popup.close()">
|
||||
<text>取消</text>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getProductList as fetchProductList, deleteProduct } from '@/api/product'
|
||||
import { getToken, getStoreId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentTab: 'all',
|
||||
searchText: '',
|
||||
showFilter: false,
|
||||
selectedGoods: null,
|
||||
storeId: null, // 门店ID
|
||||
goodsList: [
|
||||
{
|
||||
id: null,
|
||||
productName: '',
|
||||
productBarCode: '',
|
||||
storePrice: 0.00,
|
||||
stockQuantity: 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取门店ID
|
||||
const storeId = getStoreId();
|
||||
if (storeId) {
|
||||
this.storeId = storeId;
|
||||
console.log('当前门店ID:', storeId);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '请先选择门店',
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取传递过来的tab参数
|
||||
if (options && options.tab) {
|
||||
this.currentTab = options.tab;
|
||||
}
|
||||
|
||||
this.getProductList();
|
||||
},
|
||||
onShow() {
|
||||
this.getProductList();
|
||||
},
|
||||
methods: {
|
||||
async getProductList(searchParams = {}) {
|
||||
try {
|
||||
const token = getToken();
|
||||
console.log('当前Token:', token);
|
||||
console.log('Token是否存在:', !!token);
|
||||
console.log('查询参数:', searchParams);
|
||||
|
||||
// 添加门店ID参数
|
||||
if (this.storeId) {
|
||||
searchParams.storeId = this.storeId;
|
||||
}
|
||||
|
||||
// 根据标签添加不同的查询参数
|
||||
if (this.currentTab === 'outstock') {
|
||||
// 缺货商品
|
||||
searchParams.stockQuantity = 0;
|
||||
} else if (this.currentTab === 'expiring') {
|
||||
// 临期/过期商品
|
||||
// 这里需要根据实际API添加相应的参数
|
||||
// 例如:searchParams.expiring = true;
|
||||
}
|
||||
|
||||
const res = await fetchProductList(searchParams);
|
||||
|
||||
console.log('商品列表接口返回:', res);
|
||||
|
||||
|
||||
if (res && res.code === 200 && res.data) {
|
||||
this.goodsList = res.data.map(item => ({
|
||||
...item,
|
||||
showDeleteMenu: false
|
||||
}));
|
||||
console.log('处理后的商品列表:', this.goodsList);
|
||||
} else {
|
||||
this.goodsList = [];
|
||||
uni.showToast({
|
||||
title: res?.msg || '获取商品列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.goodsList = [];
|
||||
console.error('获取商品列表失败:', error);
|
||||
console.error('错误详情:', JSON.stringify(error));
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
switchTab(tab) {
|
||||
this.currentTab = tab;
|
||||
// 根据标签重新获取商品列表
|
||||
this.getProductList();
|
||||
console.log('切换到标签:', tab);
|
||||
},
|
||||
|
||||
onSearch() {
|
||||
console.log('搜索关键词:', this.searchText);
|
||||
|
||||
if (!this.searchText || this.searchText.trim() === '') {
|
||||
this.getProductList();
|
||||
return;
|
||||
}
|
||||
|
||||
const searchText = this.searchText.trim();
|
||||
let searchParams = {};
|
||||
|
||||
if (/^\d+$/.test(searchText)) {
|
||||
searchParams = { productBarCode: searchText };
|
||||
console.log('按条形码精准查询:', searchParams);
|
||||
} else {
|
||||
searchParams = { productName: searchText };
|
||||
console.log('商品名称模糊查询:', searchParams);
|
||||
}
|
||||
|
||||
this.getProductList(searchParams);
|
||||
},
|
||||
|
||||
goToSettings() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/settings/index'
|
||||
});
|
||||
},
|
||||
|
||||
// 库存不足预警设置
|
||||
goToAlertSettings() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/alertSettings/index'
|
||||
});
|
||||
},
|
||||
|
||||
// 临期促销
|
||||
goToPromotion() {
|
||||
console.log('临期促销');
|
||||
// 这里可以添加临期促销的逻辑
|
||||
},
|
||||
|
||||
// 过期出库
|
||||
goToExpiredOut() {
|
||||
console.log('过期出库');
|
||||
// 这里可以添加过期出库的逻辑
|
||||
},
|
||||
|
||||
showMoreActions() {
|
||||
this.$refs.popup.open();
|
||||
},
|
||||
|
||||
addGoodsStock(goods) {
|
||||
console.log('添加商品库存:', goods);
|
||||
uni.showModal({
|
||||
title: '添加库存',
|
||||
content: `为 ${goods.name} 添加库存`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 执行添加库存操作
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
goToStockDetail(item) {
|
||||
// 跳转到编辑页面,传递商品ID
|
||||
uni.navigateTo({
|
||||
url: `/pages/edit/edit?id=${item.id}`
|
||||
});
|
||||
},
|
||||
|
||||
toggleDeleteMenu(item) {
|
||||
item.showDeleteMenu = !item.showDeleteMenu;
|
||||
},
|
||||
|
||||
showDeleteConfirm(item) {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除商品"${item.productName}"吗?`,
|
||||
success: async (res) => {
|
||||
if (res.confirm) {
|
||||
try {
|
||||
const result = await deleteProduct(item.id);
|
||||
console.log('删除商品结果:', result);
|
||||
|
||||
if (result && result.code === 200) {
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
// 重新加载商品列表
|
||||
this.getProductList();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: result?.msg || '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除商品失败:', error);
|
||||
uni.showToast({
|
||||
title: '删除失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
addNewGoods() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/addProduct/addProduct'
|
||||
// url: '/pages/enter/enter'
|
||||
});
|
||||
},
|
||||
|
||||
batchOperation() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['批量修改价格', '批量修改库存', '批量下架'],
|
||||
success: (res) => {
|
||||
console.log('选择了操作:', res.tapIndex);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
editGoods(goods) {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: `/pages/goods/edit?id=${goods.id}`
|
||||
});
|
||||
},
|
||||
|
||||
adjustStock(goods) {
|
||||
this.$refs.popup.close();
|
||||
uni.showModal({
|
||||
title: '调整库存',
|
||||
content: `调整 ${goods.name} 的库存数量`,
|
||||
editable: true,
|
||||
placeholderText: '请输入调整数量',
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
console.log('调整库存数量:', res.content);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
deleteGoods(goods) {
|
||||
this.$refs.popup.close();
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除商品"${goods.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
console.log('删除商品:', goods);
|
||||
uni.showToast({
|
||||
title: '删除成功',
|
||||
icon: 'success'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
// 盘点库存
|
||||
goToInventoryCount() {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/inventoryCount/inventoryCount'
|
||||
});
|
||||
},
|
||||
// 出库
|
||||
goToOutbound() {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/outbound/outbound'
|
||||
});
|
||||
},
|
||||
// 批量删除商品
|
||||
batchDeleteGoods() {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/batchDeleteProduct/batchDeleteProduct'
|
||||
});
|
||||
},
|
||||
// 分类管理
|
||||
goToCategoryManagement() {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/category/category'
|
||||
});
|
||||
},
|
||||
// 品牌管理
|
||||
goToBrandManagement() {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/addBrand/addBrand'
|
||||
});
|
||||
},
|
||||
// 条码规则设置
|
||||
goToBarcodeSettings() {
|
||||
this.$refs.popup.close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/barcodeSettings/barcodeSettings'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.goods-tou{
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.goods-tou image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
/* 状态栏 */
|
||||
.status-bar {
|
||||
padding: 10px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 搜索栏 */
|
||||
.search-bar {
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.search-input input {
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 筛选标签 */
|
||||
.filter-tabs {
|
||||
background-color: #fff;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.tabs-scroll {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
margin-right: 10px;
|
||||
border-radius: 16px;
|
||||
font-size: 14px;
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
background-color: #e8f4ff;
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-left: 4px;
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 微店状态栏 */
|
||||
.store-status {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-top: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-left-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-right-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.dui {
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.dui-img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.status-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-num {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.status-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 1rpx;
|
||||
}
|
||||
|
||||
.set-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 右侧粉色按钮通用样式 */
|
||||
.quick-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: red;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
color: white;
|
||||
font-size: 22rpx;
|
||||
gap: 4rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
/* 商品列表 */
|
||||
.goods-list {
|
||||
height: calc(100vh - 320px);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.goods-item {
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.goods-more-icon {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.goods-delete-menu {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 12px;
|
||||
background-color: #fff;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||
padding: 12rpx 24rpx;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.delete-menu-text {
|
||||
font-size: 28rpx;
|
||||
color: #ff4444;
|
||||
}
|
||||
|
||||
.goods-top {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.goods-info {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.goods-name {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.goods-barcode {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
font-size: 18px;
|
||||
color: #ff4444;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.goods-divider {
|
||||
height: 1px;
|
||||
border-bottom: 1px dashed #e0e0e0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.goods-stock {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.stock-arrow {
|
||||
font-size: 20px;
|
||||
color: #999;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.stock-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stock-num {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.goods-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.more-btn {
|
||||
background-color: #f5f5f5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #e8f4ff;
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
/* 底部操作按钮 */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex: 1;
|
||||
margin: 0 8px;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-button.primary {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 弹窗样式 */
|
||||
.popup-content {
|
||||
background-color: #fff;
|
||||
border-radius: 16px 16px 0 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.popup-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 0;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.popup-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.popup-item text {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.popup-item.cancel {
|
||||
justify-content: center;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<view class="normal-login-container">
|
||||
<view class="logo-content align-center justify-center flex">
|
||||
<image style="width: 100rpx;height: 100rpx;" :src="globalConfig.appInfo.logo" mode="widthFix">
|
||||
</image>
|
||||
<text class="title">若依移动端注册</text>
|
||||
</view>
|
||||
<view class="login-form-content">
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-user icon"></view>
|
||||
<input v-model="registerForm.username" class="input" type="text" placeholder="请输入账号" maxlength="30" />
|
||||
</view>
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-password icon"></view>
|
||||
<input v-model="registerForm.password" type="password" class="input" placeholder="请输入密码" maxlength="20" />
|
||||
</view>
|
||||
<view class="input-item flex align-center">
|
||||
<view class="iconfont icon-password icon"></view>
|
||||
<input v-model="registerForm.confirmPassword" type="password" class="input" placeholder="请输入重复密码" maxlength="20" />
|
||||
</view>
|
||||
<view class="input-item flex align-center" style="width: 60%;margin: 0px;" v-if="captchaEnabled">
|
||||
<view class="iconfont icon-code icon"></view>
|
||||
<input v-model="registerForm.code" type="number" class="input" placeholder="请输入验证码" maxlength="4" />
|
||||
<view class="login-code">
|
||||
<image :src="codeUrl" @click="getCode" class="login-code-img"></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btn">
|
||||
<button @click="handleRegister()" class="register-btn cu-btn block bg-blue lg round">注册</button>
|
||||
</view>
|
||||
</view>
|
||||
<view class="xieyi text-center">
|
||||
<text @click="handleUserLogin" class="text-blue">使用已有账号登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getCodeImg, register } from '@/api/login'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
codeUrl: "",
|
||||
captchaEnabled: true,
|
||||
globalConfig: getApp().globalData.config,
|
||||
registerForm: {
|
||||
username: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
code: "",
|
||||
uuid: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCode()
|
||||
},
|
||||
methods: {
|
||||
// 用户登录
|
||||
handleUserLogin() {
|
||||
this.$tab.navigateTo(`/pages/login`)
|
||||
},
|
||||
// 获取图形验证码
|
||||
getCode() {
|
||||
getCodeImg().then(res => {
|
||||
this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled
|
||||
if (this.captchaEnabled) {
|
||||
this.codeUrl = 'data:image/gif;base64,' + res.img
|
||||
this.registerForm.uuid = res.uuid
|
||||
}
|
||||
})
|
||||
},
|
||||
// 注册方法
|
||||
async handleRegister() {
|
||||
if (this.registerForm.username === "") {
|
||||
this.$modal.msgError("请输入您的账号")
|
||||
} else if (this.registerForm.password === "") {
|
||||
this.$modal.msgError("请输入您的密码")
|
||||
} else if (this.registerForm.confirmPassword === "") {
|
||||
this.$modal.msgError("请再次输入您的密码")
|
||||
} else if (this.registerForm.password !== this.registerForm.confirmPassword) {
|
||||
this.$modal.msgError("两次输入的密码不一致")
|
||||
} else if (this.registerForm.code === "" && this.captchaEnabled) {
|
||||
this.$modal.msgError("请输入验证码")
|
||||
} else {
|
||||
this.$modal.loading("注册中,请耐心等待...")
|
||||
this.register()
|
||||
}
|
||||
},
|
||||
// 用户注册
|
||||
async register() {
|
||||
register(this.registerForm).then(res => {
|
||||
this.$modal.closeLoading()
|
||||
uni.showModal({
|
||||
title: "系统提示",
|
||||
content: "恭喜你,您的账号 " + this.registerForm.username + " 注册成功!",
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
uni.redirectTo({ url: `/pages/login` });
|
||||
}
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
if (this.captchaEnabled) {
|
||||
this.getCode()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.normal-login-container {
|
||||
width: 100%;
|
||||
|
||||
.logo-content {
|
||||
width: 100%;
|
||||
font-size: 21px;
|
||||
text-align: center;
|
||||
padding-top: 15%;
|
||||
|
||||
image {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form-content {
|
||||
text-align: center;
|
||||
margin: 20px auto;
|
||||
margin-top: 15%;
|
||||
width: 80%;
|
||||
|
||||
.input-item {
|
||||
margin: 20px auto;
|
||||
background-color: #f5f6f7;
|
||||
height: 45px;
|
||||
border-radius: 20px;
|
||||
|
||||
.icon {
|
||||
font-size: 38rpx;
|
||||
margin-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.register-btn {
|
||||
margin-top: 40px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.xieyi {
|
||||
color: #333;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.login-code {
|
||||
height: 38px;
|
||||
float: right;
|
||||
|
||||
.login-code-img {
|
||||
height: 38px;
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
width: 200rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<view class="settings-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">设置</text>
|
||||
</view>
|
||||
|
||||
<!-- 通用模块 -->
|
||||
<view class="settings-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">通用</text>
|
||||
</view>
|
||||
<view class="settings-list">
|
||||
<view class="list-item" @click="navigateTo('个人信息')">
|
||||
<text class="item-text">个人信息</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('店铺信息')">
|
||||
<text class="item-text">店铺信息</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('子账号管理')">
|
||||
<text class="item-text">子账号管理</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 门店模块 -->
|
||||
<view class="settings-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">门店</text>
|
||||
</view>
|
||||
<view class="settings-list">
|
||||
<view class="list-item" @click="navigateTo('开关门设置')">
|
||||
<text class="item-text">开关门设置</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('进店语音设置')">
|
||||
<text class="item-text">进店语音设置</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('推荐设置')">
|
||||
<text class="item-text">推荐设置</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 交易模块 -->
|
||||
<view class="settings-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">交易</text>
|
||||
</view>
|
||||
<view class="settings-list">
|
||||
<view class="list-item" @click="navigateTo('支付方式认证')">
|
||||
<text class="item-text">支付方式认证</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('自动化扣')">
|
||||
<text class="item-text">自动化扣</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('音频级别设置')">
|
||||
<text class="item-text">音频级别设置</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('非值守期间现金收银')">
|
||||
<text class="item-text">非值守期间现金收银</text>
|
||||
<text class="iconfont icon-arrow">›</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="logout-module">
|
||||
<view class="logout-btn" @click="handleLogout">
|
||||
<text class="logout-text">退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 页面数据
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigateTo(pageName) {
|
||||
// 实际项目中,这里会跳转到对应页面
|
||||
uni.showToast({
|
||||
title: `跳转到${pageName}`,
|
||||
icon: 'none',
|
||||
duration: 1000
|
||||
});
|
||||
|
||||
},
|
||||
handleLogout() {
|
||||
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
|
||||
this.$store.dispatch('LogOut').then(() => {}).finally(()=>{
|
||||
this.$tab.reLaunch('/pages/index')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
.page-header {
|
||||
padding: 30rpx 40rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
|
||||
.page-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
.logout-text {
|
||||
font-size: 34rpx;
|
||||
color: red;
|
||||
}
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* ========== 退出登录模块 ========== */
|
||||
.logout-module {
|
||||
margin-top: 40rpx;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
.settings-section {
|
||||
margin-top: 30rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
margin-left: 30rpx;
|
||||
margin-right: 30rpx;
|
||||
|
||||
.section-header {
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-list {
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-size: 36rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式适配 */
|
||||
@media (min-width: 768px) {
|
||||
.settings-container {
|
||||
max-width: 750px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 0 20rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<view class="store-select-container">
|
||||
<!-- 自定义导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<uni-icons type="left" size="20" color="#fff"></uni-icons>
|
||||
</view>
|
||||
<view class="nav-title">选择门店</view>
|
||||
<view class="nav-right"></view>
|
||||
</view>
|
||||
|
||||
<view class="store-list">
|
||||
<view
|
||||
class="store-item"
|
||||
v-for="(item, index) in storeList"
|
||||
:key="index"
|
||||
@click="selectStore(item)"
|
||||
>
|
||||
<view class="store-info">
|
||||
<text class="store-name">{{ item.storeName }}</text>
|
||||
<text class="store-code">编码:{{ item.storeCode }}</text>
|
||||
</view>
|
||||
<uni-icons type="right" size="20" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="empty-state" v-if="storeList.length === 0">
|
||||
<text class="empty-text">暂无门店数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getStoreList } from '@/api/store'
|
||||
import { getUserId, setStoreId, setStoreInfo, getStoreId } from '@/utils/auth'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
userId: null,
|
||||
storeList: []
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
// 获取用户ID
|
||||
const userId = getUserId();
|
||||
if (userId) {
|
||||
this.userId = userId;
|
||||
this.loadStoreList();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '用户ID不存在',
|
||||
icon: 'none'
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadStoreList() {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
|
||||
// 根据用户ID查询门店列表
|
||||
getStoreList(String(this.userId)).then(res => {
|
||||
uni.hideLoading();
|
||||
|
||||
if (res && res.code === 200) {
|
||||
// 适配不同的数据结构
|
||||
if (Array.isArray(res.data)) {
|
||||
this.storeList = res.data;
|
||||
} else if (res.data && res.data.stores) {
|
||||
this.storeList = res.data.stores;
|
||||
}
|
||||
console.log('门店列表:', this.storeList);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res?.msg || '获取门店列表失败',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}).catch(error => {
|
||||
uni.hideLoading();
|
||||
console.error('获取门店列表失败:', error);
|
||||
uni.showToast({
|
||||
title: '网络请求失败',
|
||||
icon: 'none'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
goBack() {
|
||||
// 检查是否已经选择了门店
|
||||
const storeId = getStoreId();
|
||||
if (storeId) {
|
||||
// 已选择门店,不允许返回
|
||||
uni.showToast({
|
||||
title: '请选择门店',
|
||||
icon: 'none'
|
||||
});
|
||||
} else {
|
||||
// 未选择门店,返回登录页面
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
|
||||
selectStore(store) {
|
||||
console.log('选择门店:', store);
|
||||
|
||||
// 存储门店ID和门店信息
|
||||
setStoreId(store.storeId);
|
||||
setStoreInfo(store);
|
||||
|
||||
// 提示选择成功
|
||||
uni.showToast({
|
||||
title: '已选择门店',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 跳转到首页
|
||||
setTimeout(() => {
|
||||
uni.switchTab({
|
||||
url: '/pages/index'
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.store-select-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
padding-top: 44px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background-color: #e60012;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 15px;
|
||||
box-sizing: border-box;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.nav-left, .nav-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.store-list {
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
margin: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.store-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.store-item:active {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.store-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.store-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.store-name {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.store-code {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
<template>
|
||||
<view class="nickname-page">
|
||||
<!-- 顶部导航栏 -->
|
||||
<view class="navbar">
|
||||
<view class="nav-left" @click="goBack">
|
||||
<!-- <text class="nav-icon"><</text> -->
|
||||
</view>
|
||||
<view class="nav-title">昵称</view>
|
||||
<view class="nav-right" @click="saveNickname">
|
||||
<text class="nav-text">完成</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<view class="content">
|
||||
<view class="input-item">
|
||||
<text class="label">名称</text>
|
||||
<input
|
||||
class="input"
|
||||
v-model="nickname"
|
||||
placeholder="请输入昵称"
|
||||
maxlength="20"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
nickname: '' // 绑定输入框的昵称值
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 如果是从其他页面跳转过来,可以接收并回显旧昵称
|
||||
if (options.oldNickname) {
|
||||
this.nickname = options.oldNickname
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
uni.navigateBack()
|
||||
},
|
||||
// 保存昵称
|
||||
saveNickname() {
|
||||
if (!this.nickname.trim()) {
|
||||
uni.showToast({
|
||||
title: '昵称不能为空',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 这里可以调用接口保存昵称到后端
|
||||
uni.showLoading({
|
||||
title: '保存中...'
|
||||
})
|
||||
|
||||
// 模拟接口请求
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 保存成功后返回上一页,并携带新昵称
|
||||
uni.navigateBack({
|
||||
success() {
|
||||
const pages = getCurrentPages()
|
||||
const prevPage = pages[pages.length - 1]
|
||||
if (prevPage) {
|
||||
// 通知上一页更新昵称显示
|
||||
prevPage.$vm.$emit('updateNickname', this.nickname)
|
||||
}
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nickname-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 44px;
|
||||
background-color: #409eff;
|
||||
color: #fff;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.nav-left {
|
||||
width: 40px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
width: 40px;
|
||||
height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 内容区 */
|
||||
.content {
|
||||
margin-top: 10px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.input-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
width: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.input {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,331 @@
|
|||
<template>
|
||||
<view class="container">
|
||||
<!-- 个人信息标题模块 -->
|
||||
<view class="header-module">
|
||||
<text class="header-title">个人信息</text>
|
||||
</view>
|
||||
|
||||
<view class="one-module">
|
||||
<view class="touXiang">
|
||||
<view class="gearOne"><uni-icons type="camera-filled" size="20"></uni-icons></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账户信息模块 -->
|
||||
<view class="info-module">
|
||||
<view class="module-item" @click="goUpdateUserName">
|
||||
<text class="item-label">昵称</text>
|
||||
<text class="item-value">名称</text>
|
||||
</view>
|
||||
<view class="module-item">
|
||||
<text class="item-label">绑定微信号</text>
|
||||
<text class="item-value">名称</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账户安全模块 -->
|
||||
<view class="security-module">
|
||||
<view class="module-title">账户安全</view>
|
||||
<view class="module-item clickable" @tap="handleChangePhone">
|
||||
<text class="item-label">修改手机号</text>
|
||||
<view class="item-right">
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="module-item clickable" @tap="handleChangePassword">
|
||||
<text class="item-label">修改密码</text>
|
||||
<view class="item-right">
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设备管理模块 -->
|
||||
<view class="device-module">
|
||||
<view class="module-title">设备管理</view>
|
||||
<view class="module-item clickable" @tap="handleDeviceManage">
|
||||
<text class="item-label">设备管理</text>
|
||||
<view class="item-right">
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账号管理模块 -->
|
||||
<view class="account-module">
|
||||
<view class="module-title">账号管理</view>
|
||||
<view class="module-item clickable last-item" @tap="handleCancelAccount">
|
||||
<text class="item-label">注销账号</text>
|
||||
<view class="item-right">
|
||||
<text class="arrow">></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 退出登录模块 -->
|
||||
<view class="logout-module">
|
||||
<view class="logout-btn" @click="handleLogout">
|
||||
<text class="logout-text">退出登录</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部提示模块 -->
|
||||
<view class="footer-module">
|
||||
<text class="footer-text">这不是我的账号</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
// 修改手机号
|
||||
handleChangePhone() {
|
||||
uni.showToast({
|
||||
title: '跳转到修改手机号页面',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
// 修改密码
|
||||
handleChangePassword() {
|
||||
uni.showToast({
|
||||
title: '跳转到修改密码页面',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
// 设备管理
|
||||
handleDeviceManage() {
|
||||
uni.showToast({
|
||||
title: '跳转到设备管理页面',
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
goUpdateUserName(){
|
||||
uni.navigateTo({
|
||||
url: '/pages/updateUserName/updateUserName'
|
||||
});
|
||||
},
|
||||
// 注销账号
|
||||
handleCancelAccount() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要注销账号吗?此操作不可恢复。',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '账号注销申请已提交',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 退出登录
|
||||
handleLogout() {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要退出登录吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.showToast({
|
||||
title: '退出登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500,
|
||||
success: () => {
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/login/login' })
|
||||
}, 1500)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
handleLogout() {
|
||||
this.$modal.confirm('确定注销并退出系统吗?').then(() => {
|
||||
this.$store.dispatch('LogOut').then(() => {}).finally(()=>{
|
||||
this.$tab.reLaunch('/pages/index')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.touXiang{
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
background-image: url('http://193.112.94.36:8099/static/images/687b6f95b14eff60f4b77147b3726ab2.jpg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 40rpx auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gearOne{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
/* ========== 状态栏模块 ========== */
|
||||
.status-bar {
|
||||
background-color: #ffffff;
|
||||
padding: 40rpx 0 20rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* ========== 标题模块 ========== */
|
||||
.header-module {
|
||||
background-color: firebrick;
|
||||
padding: 40rpx 40rpx 60rpx;
|
||||
border-bottom: 2rpx solid #f5f5f5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
font-size: 30rpx;
|
||||
/* font-weight: bold; */
|
||||
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
/* ========== 通用模块样式 ========== */
|
||||
.info-module,
|
||||
.security-module,
|
||||
.device-module,
|
||||
.account-module {
|
||||
background-color: #ffffff;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 40rpx;
|
||||
margin: 20rpx;
|
||||
border-radius: 16rpx;
|
||||
|
||||
}
|
||||
|
||||
/* 模块标题 */
|
||||
.module-title {
|
||||
font-size: 30rpx;
|
||||
color: #999999;
|
||||
padding: 30rpx 0 20rpx;
|
||||
}
|
||||
|
||||
/* 模块项 */
|
||||
.module-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 36rpx 0;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
/* 最后一项去掉底部边框 */
|
||||
.last-item {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
/* 点击效果 */
|
||||
.clickable:active {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
/* 标签样式 */
|
||||
.item-label {
|
||||
font-size: 34rpx;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
/* 值样式 */
|
||||
.item-value {
|
||||
font-size: 34rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* 右侧内容 */
|
||||
.item-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 箭头样式 */
|
||||
.arrow {
|
||||
font-size: 36rpx;
|
||||
color: #cccccc;
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
/* ========== 账户信息模块 ========== */
|
||||
.info-module {
|
||||
margin-top: 0;
|
||||
border-top: 2rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
/* ========== 退出登录模块 ========== */
|
||||
.logout-module {
|
||||
margin-top: 40rpx;
|
||||
padding: 0 40rpx;
|
||||
}
|
||||
|
||||
.logout-btn {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logout-btn:active {
|
||||
background-color: rgba(0, 0, 0, 0.02);
|
||||
}
|
||||
|
||||
.logout-text {
|
||||
font-size: 34rpx;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* ========== 底部提示模块 ========== */
|
||||
.footer-module {
|
||||
margin-top: 40rpx;
|
||||
padding: 0 40rpx 60rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
font-size: 20rpx;
|
||||
color: limegreen;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
<template>
|
||||
<view class="user-stores-container">
|
||||
<!-- 页面标题 -->
|
||||
<view class="page-header">
|
||||
<text class="page-title">用户门店关联</text>
|
||||
<view class="header-right">
|
||||
<button class="save-btn" @click="saveUserStores">保存</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 门店列表 -->
|
||||
<view class="stores-list">
|
||||
<view class="store-item" v-for="store in stores" :key="store.storeId">
|
||||
<view class="store-info">
|
||||
<text class="store-name">{{ store.storeName }}</text>
|
||||
<text class="store-code">编码:{{ store.storeCode }}</text>
|
||||
</view>
|
||||
<uni-data-checkbox
|
||||
:value="store.storeId"
|
||||
:checked="isStoreChecked(store.storeId)"
|
||||
@change="onCheckboxChange"
|
||||
:name="'store-' + store.storeId"
|
||||
></uni-data-checkbox>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view class="empty-state" v-if="stores.length === 0">
|
||||
<text class="empty-text">暂无门店数据</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {}, // 用户所关联的门店
|
||||
stores: [], // 所有的门店
|
||||
userId: null,
|
||||
selectedStoreIds: [] // 选中的门店ID数组
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
// 获取用户ID(可以从路由参数或本地存储获取)
|
||||
this.userId = options.userId || 2; // 这里使用默认值2作为示例
|
||||
this.loadUserStores();
|
||||
},
|
||||
methods: {
|
||||
// 加载用户门店数据
|
||||
loadUserStores() {
|
||||
uni.showLoading({
|
||||
title: '加载中...'
|
||||
});
|
||||
|
||||
// 模拟后端请求,实际项目中应替换为真实API调用
|
||||
// 假设API返回的数据格式如下:
|
||||
// {
|
||||
// "code": 200,
|
||||
// "data": {
|
||||
// "allStores": [{storeId: 1, storeName: "门店621", storeCode: "门店6code"}, ...],
|
||||
// "stores": [{storeId: 1, storeName: "门店621", storeCode: "门店6code"}, ...],
|
||||
// "userId": 2
|
||||
// },
|
||||
// "msg": "操作成功"
|
||||
// }
|
||||
|
||||
// 这里使用setTimeout模拟异步请求
|
||||
setTimeout(() => {
|
||||
// 模拟后端返回的数据
|
||||
const mockResponse = {
|
||||
code: 200,
|
||||
data: {
|
||||
allStores: [
|
||||
{storeId: 1, storeName: "门店621", storeCode: "门店6code"},
|
||||
{storeId: 2, storeName: "门店622", storeCode: "门店6code2"},
|
||||
{storeId: 3, storeName: "门店623", storeCode: "门店6code3"}
|
||||
],
|
||||
stores: [
|
||||
{storeId: 1, storeName: "门店621", storeCode: "门店6code"}
|
||||
],
|
||||
userId: 2
|
||||
},
|
||||
msg: "操作成功"
|
||||
};
|
||||
|
||||
// 处理返回数据
|
||||
this.form = mockResponse.data.stores || {};
|
||||
this.stores = mockResponse.data.allStores || [];
|
||||
|
||||
// 初始化选中的门店ID数组
|
||||
this.selectedStoreIds = mockResponse.data.stores.map(store => store.storeId);
|
||||
|
||||
uni.hideLoading();
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
// 检查门店是否被选中
|
||||
isStoreChecked(storeId) {
|
||||
return this.selectedStoreIds.includes(storeId);
|
||||
},
|
||||
|
||||
// 复选框变化事件
|
||||
onCheckboxChange(e) {
|
||||
const storeId = e.value;
|
||||
const isChecked = e.checked;
|
||||
|
||||
if (isChecked) {
|
||||
// 添加到选中数组
|
||||
if (!this.selectedStoreIds.includes(storeId)) {
|
||||
this.selectedStoreIds.push(storeId);
|
||||
}
|
||||
} else {
|
||||
// 从选中数组中移除
|
||||
this.selectedStoreIds = this.selectedStoreIds.filter(id => id !== storeId);
|
||||
}
|
||||
},
|
||||
|
||||
// 保存用户门店关联
|
||||
saveUserStores() {
|
||||
uni.showLoading({
|
||||
title: '保存中...'
|
||||
});
|
||||
|
||||
// 模拟保存请求,实际项目中应替换为真实API调用
|
||||
setTimeout(() => {
|
||||
// 这里可以将selectedStoreIds发送到后端保存
|
||||
console.log('保存的门店ID:', this.selectedStoreIds);
|
||||
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '保存成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
// 返回上一页
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 1500);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-stores-container {
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx 40rpx;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
padding: 12rpx 30rpx;
|
||||
background-color: #007aff;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.stores-list {
|
||||
margin: 20rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.store-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 30rpx 40rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.store-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.store-name {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333333;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.store-code {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 30rpx;
|
||||
color: #999999;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<view class="work-container">
|
||||
<!-- 轮播图 -->
|
||||
<uni-swiper-dot class="uni-swiper-dot-box" :info="data" :current="current" field="content">
|
||||
<swiper class="swiper-box" :current="swiperDotIndex" @change="changeSwiper">
|
||||
<swiper-item v-for="(item, index) in data" :key="index">
|
||||
<view class="swiper-item" @click="clickBannerItem(item)">
|
||||
<image :src="item.image" mode="aspectFill" :draggable="false" />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</uni-swiper-dot>
|
||||
|
||||
<!-- 宫格组件 -->
|
||||
<uni-section title="系统管理" type="line"></uni-section>
|
||||
<view class="grid-body">
|
||||
<uni-grid :column="4" :showBorder="false" @change="changeGrid">
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="person-filled" size="30"></uni-icons>
|
||||
<text class="text">用户管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="staff-filled" size="30"></uni-icons>
|
||||
<text class="text">角色管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="color" size="30"></uni-icons>
|
||||
<text class="text">菜单管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="settings-filled" size="30"></uni-icons>
|
||||
<text class="text">部门管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="heart-filled" size="30"></uni-icons>
|
||||
<text class="text">岗位管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="bars" size="30"></uni-icons>
|
||||
<text class="text">字典管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="gear-filled" size="30"></uni-icons>
|
||||
<text class="text">参数设置</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="chat-filled" size="30"></uni-icons>
|
||||
<text class="text">通知公告</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
<uni-grid-item>
|
||||
<view class="grid-item-box">
|
||||
<uni-icons type="wallet-filled" size="30"></uni-icons>
|
||||
<text class="text">日志管理</text>
|
||||
</view>
|
||||
</uni-grid-item>
|
||||
</uni-grid>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
current: 0,
|
||||
swiperDotIndex: 0,
|
||||
data: [{
|
||||
image: '/static/images/banner/banner01.jpg'
|
||||
},
|
||||
{
|
||||
image: '/static/images/banner/banner02.jpg'
|
||||
},
|
||||
{
|
||||
image: '/static/images/banner/banner03.jpg'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickBannerItem(item) {
|
||||
console.info(item)
|
||||
},
|
||||
changeSwiper(e) {
|
||||
this.current = e.detail.current
|
||||
},
|
||||
changeGrid(e) {
|
||||
this.$modal.showToast('模块建设中~')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
view {
|
||||
font-size: 14px;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.text {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.grid-item-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
.uni-margin-wrap {
|
||||
width: 690rpx;
|
||||
width: 100%;
|
||||
;
|
||||
}
|
||||
|
||||
.swiper {
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.swiper-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
height: 300rpx;
|
||||
line-height: 300rpx;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.uni-swiper-dot-box {
|
||||
width: 400px;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: 0 auto;
|
||||
/* #endif */
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import { getToken } from '@/utils/auth'
|
||||
|
||||
// 登录页面
|
||||
const loginPage = "/pages/login"
|
||||
|
||||
// 页面白名单
|
||||
const whiteList = [
|
||||
'/pages/login', '/pages/register', '/pages/common/webview/index'
|
||||
]
|
||||
|
||||
// 检查地址白名单
|
||||
function checkWhite(url) {
|
||||
const path = url.split('?')[0]
|
||||
return whiteList.indexOf(path) !== -1
|
||||
}
|
||||
|
||||
// 页面跳转验证拦截器
|
||||
let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"]
|
||||
list.forEach(item => {
|
||||
uni.addInterceptor(item, {
|
||||
invoke(to) {
|
||||
if (getToken()) {
|
||||
if (to.url === loginPage) {
|
||||
uni.reLaunch({ url: "/" })
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if (checkWhite(to.url)) {
|
||||
return true
|
||||
}
|
||||
uni.reLaunch({ url: loginPage })
|
||||
return false
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import store from '@/store'
|
||||
|
||||
function authPermission(permission) {
|
||||
const all_permission = "*:*:*"
|
||||
const permissions = store.getters && store.getters.permissions
|
||||
if (permission && permission.length > 0) {
|
||||
return permissions.some(v => {
|
||||
return all_permission === v || v === permission
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function authRole(role) {
|
||||
const super_admin = "admin"
|
||||
const roles = store.getters && store.getters.roles
|
||||
if (role && role.length > 0) {
|
||||
return roles.some(v => {
|
||||
return super_admin === v || v === role
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
// 验证用户是否具备某权限
|
||||
hasPermi(permission) {
|
||||
return authPermission(permission)
|
||||
},
|
||||
// 验证用户是否含有指定权限,只需包含其中一个
|
||||
hasPermiOr(permissions) {
|
||||
return permissions.some(item => {
|
||||
return authPermission(item)
|
||||
})
|
||||
},
|
||||
// 验证用户是否含有指定权限,必须全部拥有
|
||||
hasPermiAnd(permissions) {
|
||||
return permissions.every(item => {
|
||||
return authPermission(item)
|
||||
})
|
||||
},
|
||||
// 验证用户是否具备某角色
|
||||
hasRole(role) {
|
||||
return authRole(role)
|
||||
},
|
||||
// 验证用户是否含有指定角色,只需包含其中一个
|
||||
hasRoleOr(roles) {
|
||||
return roles.some(item => {
|
||||
return authRole(item)
|
||||
})
|
||||
},
|
||||
// 验证用户是否含有指定角色,必须全部拥有
|
||||
hasRoleAnd(roles) {
|
||||
return roles.every(item => {
|
||||
return authRole(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import tab from './tab'
|
||||
import auth from './auth'
|
||||
import modal from './modal'
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
// 页签操作
|
||||
Vue.prototype.$tab = tab
|
||||
// 认证对象
|
||||
Vue.prototype.$auth = auth
|
||||
// 模态框对象
|
||||
Vue.prototype.$modal = modal
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
export default {
|
||||
// 消息提示
|
||||
msg(content) {
|
||||
uni.showToast({
|
||||
title: content,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
// 错误消息
|
||||
msgError(content) {
|
||||
uni.showToast({
|
||||
title: content,
|
||||
icon: 'error'
|
||||
})
|
||||
},
|
||||
// 成功消息
|
||||
msgSuccess(content) {
|
||||
uni.showToast({
|
||||
title: content,
|
||||
icon: 'success'
|
||||
})
|
||||
},
|
||||
// 隐藏消息
|
||||
hideMsg(content) {
|
||||
uni.hideToast()
|
||||
},
|
||||
// 弹出提示
|
||||
alert(content, title) {
|
||||
uni.showModal({
|
||||
title: title || '系统提示',
|
||||
content: content,
|
||||
showCancel: false
|
||||
})
|
||||
},
|
||||
// 确认窗体
|
||||
confirm(content, title) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.showModal({
|
||||
title: title || '系统提示',
|
||||
content: content,
|
||||
cancelText: '取消',
|
||||
confirmText: '确定',
|
||||
success: function(res) {
|
||||
if (res.confirm) {
|
||||
resolve(res.confirm)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 提示信息
|
||||
showToast(option) {
|
||||
if (typeof option === "object") {
|
||||
uni.showToast(option)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: option,
|
||||
icon: "none",
|
||||
duration: 2500
|
||||
})
|
||||
}
|
||||
},
|
||||
// 打开遮罩层
|
||||
loading(content) {
|
||||
uni.showLoading({
|
||||
title: content,
|
||||
icon: 'none'
|
||||
})
|
||||
},
|
||||
// 关闭遮罩层
|
||||
closeLoading() {
|
||||
try {
|
||||
uni.hideLoading()
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
export default {
|
||||
// 关闭所有页面,打开到应用内的某个页面
|
||||
reLaunch(url) {
|
||||
return uni.reLaunch({
|
||||
url: url
|
||||
})
|
||||
},
|
||||
// 跳转到tabBar页面,并关闭其他所有非tabBar页面
|
||||
switchTab(url) {
|
||||
return uni.switchTab({
|
||||
url: url
|
||||
})
|
||||
},
|
||||
// 关闭当前页面,跳转到应用内的某个页面
|
||||
redirectTo(url) {
|
||||
return uni.redirectTo({
|
||||
url: url
|
||||
})
|
||||
},
|
||||
// 保留当前页面,跳转到应用内的某个页面
|
||||
navigateTo(url) {
|
||||
return uni.navigateTo({
|
||||
url: url
|
||||
})
|
||||
},
|
||||
// 关闭当前页面,返回上一页面或多级页面
|
||||
navigateBack() {
|
||||
return uni.navigateBack()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAwaAlslxygdY6X7jWg9l3py4TXn6Kdqnu2oA4cvqvYjekRxZG
|
||||
UNllThCnD8f7nOwJVKHUvvCifC2xb34QXcxQSqLBN5zF1LaenAIEULcu/1p9l3Eq
|
||||
KLaaa7IEaRQz1LY4R6nnAwbHoWFIF2Cvr3k6lHLWxG3Cs2oI/oF1NHPu8oap2Cgp
|
||||
un+m4XX+1L9ylt1iTn3FEcT8MmaTXmqILCsFv2dXYZtZncBvIUP1FRsFzWqSt2Dm
|
||||
jsSD0+OxFiCT4MaUXxe+XLH+eTKy28AhGhIP07DFctR11iyfYEsRjfgRBRuKEHG6
|
||||
t+t9OdUhyB6E/BiFXzzJuuRhe7yS/SGvezQt9QIDAQABAoIBAQCUloZ3QtSo6LKx
|
||||
RJJyak+VXxmEGY2+lJf03BL1wYUX1WVfHCvn3X0NlF/wD2L6wHREm1A9G0NGEnao
|
||||
/dAneyReslmeiNOUcnRzemS/YGRTl6jrr+9PgRot7VXPIa7I3PGBpVPfkbNfF92P
|
||||
+yW3fkvDIgHIigaxUn0GemhsUU+ckv9Ihy6kE+WkH5leqDEBOC2MnLpwZJLWqGh/
|
||||
Bs4RkKaAV9FPeuAbny6nEL6KvSHBT1vUnyw/xCAUsH216TDZAbIpkjnA40vORykX
|
||||
D/NIZECGywCvD2nTS5TCwb7ecVtBodB1GPNdNpqTaNONskbjIzClCZwwLWWf6nV6
|
||||
Ws/ay+6hAoGBAOCpkN1c+uR48ypb+p2O6wY+xejuYYnAP6y908q+hGyc7cKSEmgL
|
||||
/berEsrpR4ZYAC5QXphfGsxiYf6YH1vRngETJseydPP/aGI/LplPXQWLbmJropmX
|
||||
zjqoIYuar8qQnpxCCKodU/geoIAE+okopUE2T3zEpMbxSqoQZ0JZKaW5AoGBANyi
|
||||
Sxes9UoNEJ07Xk1zxVkxDWUYAbwlM9wUCEqAI8PhyEWi+s7KcXswDXQ++h/rQfCc
|
||||
ezKZBxDmJR7Il2+vAJyQgk0yhHE7s1dQcI325wVeuYJ63lDgT3qcSS0DjfDail3U
|
||||
y+dJPvo8c9oc5slj9GpKCDn7U1GkkofgX3bAE6gdAoGAOo63/ZrQomCMMQxMZGju
|
||||
BXCzMSWBMuBzOFk6LOw/o/e7WS2tsoT9mrPycAUh6Xhig6/bGCgh2ggCttN7yPj4
|
||||
EBunzgFLzpVR5dnGEZvICTvwh6K6fQI+dLeCFts42rmbPetQStbeHhwNhZDGpJ19
|
||||
hWPckA7JTDl0VqNz5q1K17ECgYBdq8mV06iQN9vF5V60I2K16010jiyuZF0QIrEi
|
||||
cCS/FSyh4//3q5tiYZRUtigbRRZJwSXM5YtKcWtxFli04eewkOnBPKFeMaqCd3RR
|
||||
0XFjpkO8Uc3xKEqWE6Q9qDSq/R2hmKa5Gy/RrbjB8WNKPVWXirbTZxCIqQZNCcV9
|
||||
9S5jQQKBgQCBjwzjox8weiS+UsrxC/fF0nQ6LgObzn2kqjtKq/kKBYfZ7CJ8pQ1x
|
||||
lAq6C9cSeOi1PiGxHFiS5t61hNfPYQtlT2+sk11luSb6k+wfusNa1OCR7NYgZjP5
|
||||
KPgOP4G503KPIHCmhumDwz4USy1DMw9JuekuP8jsnP7NyvU473C99g==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"setting": {
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"uglifyFileName": false,
|
||||
"enhance": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
},
|
||||
"useCompilerPlugins": false,
|
||||
"minifyWXML": true,
|
||||
"compileWorklet": false,
|
||||
"uploadWithSourceMap": true,
|
||||
"packNpmManually": false,
|
||||
"minifyWXSS": true,
|
||||
"localPlugins": false,
|
||||
"disableUseStrict": false,
|
||||
"condition": false,
|
||||
"swc": false,
|
||||
"disableSWC": true
|
||||
},
|
||||
"compileType": "miniprogram",
|
||||
"simulatorPluginLibVersion": {},
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"appid": "wxe94413d023e0e7df",
|
||||
"editorSetting": {},
|
||||
"libVersion": "3.14.0"
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"libVersion": "3.14.0",
|
||||
"projectname": "RuoYi-App-master",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"coverView": true,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"compileHotReLoad": true,
|
||||
"useApiHook": true,
|
||||
"useStaticServer": false,
|
||||
"useLanDebug": false,
|
||||
"showES6CompileOption": false,
|
||||
"checkInvalidKey": true,
|
||||
"ignoreDevUnusedFiles": true,
|
||||
"bigPackageSizeSupport": false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
@font-face {
|
||||
font-family: "iconfont";
|
||||
src: url('@/static/font/iconfont.ttf') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-user:before {
|
||||
content: "\e7ae";
|
||||
}
|
||||
|
||||
.icon-password:before {
|
||||
content: "\e8b2";
|
||||
}
|
||||
|
||||
.icon-code:before {
|
||||
content: "\e699";
|
||||
}
|
||||
|
||||
.icon-setting:before {
|
||||
content: "\e6cc";
|
||||
}
|
||||
|
||||
.icon-share:before {
|
||||
content: "\e739";
|
||||
}
|
||||
|
||||
.icon-edit:before {
|
||||
content: "\e60c";
|
||||
}
|
||||
|
||||
.icon-version:before {
|
||||
content: "\e63f";
|
||||
}
|
||||
|
||||
.icon-service:before {
|
||||
content: "\e6ff";
|
||||
}
|
||||
|
||||
.icon-friendfill:before {
|
||||
content: "\e726";
|
||||
}
|
||||
|
||||
.icon-community:before {
|
||||
content: "\e741";
|
||||
}
|
||||
|
||||
.icon-people:before {
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.icon-dianzan:before {
|
||||
content: "\ec7f";
|
||||
}
|
||||
|
||||
.icon-right:before {
|
||||
content: "\e7eb";
|
||||
}
|
||||
|
||||
.icon-logout:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.icon-help:before {
|
||||
content: "\e616";
|
||||
}
|
||||
|
||||
.icon-github:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.icon-aixin:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.icon-clean:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.icon-refresh:before {
|
||||
content: "\e604";
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="<%= BASE_URL %>static/favicon.ico">
|
||||
<script>
|
||||
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||
document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
|
||||
</script>
|
||||
<link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
|
||||
<style>
|
||||
/* 重置样式,确保跨平台一致性 */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* 修复移动端1px边框问题 */
|
||||
.border-1px {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.border-1px::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
border: 1px solid #e0e0e0;
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 修复移动端字体模糊问题 */
|
||||
.text-blur-fix {
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
/* 修复移动端滚动问题 */
|
||||
.scroll-fix {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 修复移动端点击延迟 */
|
||||
.fast-click {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>本站点必须要开启JavaScript才能运行.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
/* 跨平台兼容样式文件 */
|
||||
|
||||
/* ==================== 基础重置 ==================== */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* ==================== 平台检测 ==================== */
|
||||
|
||||
/* H5平台 */
|
||||
/* #ifdef H5 */
|
||||
page {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* App平台 */
|
||||
/* #ifdef APP-PLUS */
|
||||
page {
|
||||
background-color: #f5f7fa;
|
||||
padding-top: var(--status-bar-height, 0px);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 微信小程序 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
page {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* ==================== 像素比处理 ==================== */
|
||||
|
||||
/* 高像素比设备(Retina屏幕) */
|
||||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||
.border-1px {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.border-1px::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
border: 1px solid var(--border-color, #e0e0e0);
|
||||
transform: scale(0.5);
|
||||
transform-origin: 0 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超高像素比设备 */
|
||||
@media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 288dpi) {
|
||||
.border-1px::after {
|
||||
width: 300%;
|
||||
height: 300%;
|
||||
transform: scale(0.333);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 字体优化 ==================== */
|
||||
|
||||
/* 修复移动端字体模糊 */
|
||||
.text-blur-fix {
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
/* 字体大小适配 */
|
||||
.font-size-adaptive {
|
||||
font-size: calc(16px + (20 - 16) * ((100vw - 320px) / (750 - 320)));
|
||||
}
|
||||
|
||||
@media (min-width: 750px) {
|
||||
.font-size-adaptive {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
.font-size-adaptive {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 布局兼容 ==================== */
|
||||
|
||||
/* Flex布局兼容 */
|
||||
.flex {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-webkit-align-items: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
-ms-flex-pack: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Grid布局兼容 */
|
||||
.grid {
|
||||
display: -ms-grid;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
/* ==================== 滚动优化 ==================== */
|
||||
|
||||
/* 修复移动端滚动 */
|
||||
.scroll-fix {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条但保留功能 */
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
/* ==================== 点击优化 ==================== */
|
||||
|
||||
/* 修复移动端点击延迟 */
|
||||
.fast-click {
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
/* 点击反馈 */
|
||||
.clickable {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.clickable:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* ==================== 图片优化 ==================== */
|
||||
|
||||
/* 图片自适应 */
|
||||
.img-responsive {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 图片懒加载占位 */
|
||||
.img-placeholder {
|
||||
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: placeholder-loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes placeholder-loading {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 文本优化 ==================== */
|
||||
|
||||
/* 文本省略 */
|
||||
.text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-ellipsis-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.text-ellipsis-3 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ==================== 安全区域 ==================== */
|
||||
|
||||
/* 刘海屏适配 */
|
||||
.safe-area-top {
|
||||
padding-top: constant(safe-area-inset-top);
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
.safe-area-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.safe-area-left {
|
||||
padding-left: constant(safe-area-inset-left);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
.safe-area-right {
|
||||
padding-right: constant(safe-area-inset-right);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
.safe-area-all {
|
||||
padding: constant(safe-area-inset-top) constant(safe-area-inset-right) constant(safe-area-inset-bottom) constant(safe-area-inset-left);
|
||||
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
/* 横屏适配 */
|
||||
@media screen and (orientation: landscape) {
|
||||
.safe-area-bottom {
|
||||
padding-bottom: constant(safe-area-inset-left);
|
||||
padding-bottom: env(safe-area-inset-left);
|
||||
}
|
||||
|
||||
.safe-area-top {
|
||||
padding-top: constant(safe-area-inset-right);
|
||||
padding-top: env(safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 平台特定样式 ==================== */
|
||||
|
||||
/* H5平台特定样式 */
|
||||
/* #ifdef H5 */
|
||||
.h5-only {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover-effect:hover {
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* App平台特定样式 */
|
||||
/* #ifdef APP-PLUS */
|
||||
.app-only {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 状态栏高度适配 */
|
||||
.status-bar-height {
|
||||
height: var(--status-bar-height, 20px);
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 微信小程序特定样式 */
|
||||
/* #ifdef MP-WEIXIN */
|
||||
.mp-weixin-only {
|
||||
display: block;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* ==================== 响应式断点优化 ==================== */
|
||||
|
||||
/* 超小屏设备 */
|
||||
@media screen and (max-width: 320px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏设备 */
|
||||
@media screen and (min-width: 321px) and (max-width: 375px) {
|
||||
.container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 33.33%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中等屏设备 */
|
||||
@media screen and (min-width: 376px) and (max-width: 414px) {
|
||||
.container {
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏设备 */
|
||||
@media screen and (min-width: 415px) and (max-width: 768px) {
|
||||
.container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超大屏设备 */
|
||||
@media screen and (min-width: 769px) {
|
||||
.container {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
width: 16.66%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 动画优化 ==================== */
|
||||
|
||||
/* 硬件加速 */
|
||||
.hardware-accelerate {
|
||||
-webkit-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
-webkit-backface-visibility: hidden;
|
||||
backface-visibility: hidden;
|
||||
-webkit-perspective: 1000;
|
||||
perspective: 1000;
|
||||
}
|
||||
|
||||
/* 平滑过渡 */
|
||||
.smooth-transition {
|
||||
-webkit-transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
/* ==================== 工具类 ==================== */
|
||||
|
||||
/* 清除浮动 */
|
||||
.clearfix::after {
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* 居中 */
|
||||
.center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* 隐藏元素 */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 可见性 */
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* 绝对定位 */
|
||||
.absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* 相对定位 */
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 固定定位 */
|
||||
.fixed {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
/* 层级 */
|
||||
.z-1 {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.z-10 {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.z-100 {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.z-1000 {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.font-13 {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.font-12 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.font-11 {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.text-grey1 {
|
||||
color: #888;
|
||||
}
|
||||
.text-grey2 {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.list-cell-arrow::before {
|
||||
content: ' ';
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-width: 2px 2px 0 0;
|
||||
border-color: #c0c0c0;
|
||||
border-style: solid;
|
||||
-webkit-transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
|
||||
transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
right: 30rpx;
|
||||
}
|
||||
|
||||
.list-cell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
padding: 26rpx 30rpx;
|
||||
}
|
||||
|
||||
.list-cell:first-child {
|
||||
border-radius: 8rpx 8rpx 0 0;
|
||||
}
|
||||
|
||||
.list-cell:last-child {
|
||||
border-radius: 0 0 8rpx 8rpx;
|
||||
}
|
||||
|
||||
.list-cell::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 1px solid #eaeef1;
|
||||
-webkit-transform: scaleY(0.5) translateZ(0);
|
||||
transform: scaleY(0.5) translateZ(0);
|
||||
transform-origin: 0 100%;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.menu-list {
|
||||
margin: 15px 15px;
|
||||
|
||||
.menu-item-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.menu-icon {
|
||||
color: #007AFF;
|
||||
font-size: 16px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
margin-left: auto;
|
||||
margin-right: 34rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// global
|
||||
@import "./global.scss";
|
||||
// color-ui
|
||||
@import "@/static/scss/colorui.css";
|
||||
// iconfont
|
||||
@import "@/static/font/iconfont.css";
|
||||
|
|
@ -0,0 +1,398 @@
|
|||
/* 全局样式配置文件 */
|
||||
|
||||
/* ==================== 主题色系 ==================== */
|
||||
|
||||
/* 浅色主题 */
|
||||
:root {
|
||||
--bg-primary: #f5f7fa;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #fafafa;
|
||||
--text-primary: #333333;
|
||||
--text-secondary: #888888;
|
||||
--text-tertiary: #999999;
|
||||
--border-color: #e0e0e0;
|
||||
--border-light: #f0f0f0;
|
||||
--shadow-color: rgba(0, 0, 0, 0.05);
|
||||
--shadow-strong: rgba(0, 0, 0, 0.1);
|
||||
--accent-color: #667eea;
|
||||
--accent-hover: #5568d3;
|
||||
--gradient-start: #f5f7fa;
|
||||
--gradient-end: #c3cfe2;
|
||||
--divider-color: #e0e0e0;
|
||||
--card-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
--card-border: 1rpx solid #e0e0e0;
|
||||
--radius-sm: 8rpx;
|
||||
--radius-md: 12rpx;
|
||||
--radius-lg: 16rpx;
|
||||
--radius-xl: 24rpx;
|
||||
--spacing-xs: 8rpx;
|
||||
--spacing-sm: 12rpx;
|
||||
--spacing-md: 16rpx;
|
||||
--spacing-lg: 20rpx;
|
||||
--spacing-xl: 24rpx;
|
||||
--spacing-xxl: 32rpx;
|
||||
--font-xs: 20rpx;
|
||||
--font-sm: 24rpx;
|
||||
--font-md: 28rpx;
|
||||
--font-lg: 32rpx;
|
||||
--font-xl: 36rpx;
|
||||
--font-xxl: 42rpx;
|
||||
--line-height-sm: 1.4;
|
||||
--line-height-md: 1.5;
|
||||
--line-height-lg: 1.6;
|
||||
}
|
||||
|
||||
/* 深色主题 */
|
||||
.theme-dark {
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-secondary: #2d2d2d;
|
||||
--bg-tertiary: #3a3a3a;
|
||||
--text-primary: #ffffff;
|
||||
--text-secondary: #aaaaaa;
|
||||
--text-tertiary: #888888;
|
||||
--border-color: #404040;
|
||||
--border-light: #3a3a3a;
|
||||
--shadow-color: rgba(0, 0, 0, 0.3);
|
||||
--shadow-strong: rgba(0, 0, 0, 0.5);
|
||||
--accent-color: #667eea;
|
||||
--accent-hover: #7a8fd8;
|
||||
--gradient-start: #1a1a1a;
|
||||
--gradient-end: #2d2d2d;
|
||||
--divider-color: #404040;
|
||||
--card-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);
|
||||
--card-border: 1rpx solid #404040;
|
||||
}
|
||||
|
||||
/* ==================== 响应式断点 ==================== */
|
||||
|
||||
/* 超小屏手机 */
|
||||
@media screen and (max-width: 375rpx) {
|
||||
:root {
|
||||
--font-xs: 18rpx;
|
||||
--font-sm: 22rpx;
|
||||
--font-md: 26rpx;
|
||||
--font-lg: 30rpx;
|
||||
--spacing-xs: 6rpx;
|
||||
--spacing-sm: 10rpx;
|
||||
--spacing-md: 14rpx;
|
||||
--spacing-lg: 18rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 小屏手机 */
|
||||
@media screen and (min-width: 376rpx) and (max-width: 480rpx) {
|
||||
:root {
|
||||
--font-xs: 20rpx;
|
||||
--font-sm: 24rpx;
|
||||
--font-md: 28rpx;
|
||||
--font-lg: 32rpx;
|
||||
--spacing-xs: 8rpx;
|
||||
--spacing-sm: 12rpx;
|
||||
--spacing-md: 16rpx;
|
||||
--spacing-lg: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中等屏幕手机 */
|
||||
@media screen and (min-width: 481rpx) and (max-width: 600rpx) {
|
||||
:root {
|
||||
--font-xs: 22rpx;
|
||||
--font-sm: 26rpx;
|
||||
--font-md: 30rpx;
|
||||
--font-lg: 34rpx;
|
||||
--spacing-xs: 10rpx;
|
||||
--spacing-sm: 14rpx;
|
||||
--spacing-md: 18rpx;
|
||||
--spacing-lg: 22rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏手机/平板 */
|
||||
@media screen and (min-width: 601rpx) and (max-width: 750rpx) {
|
||||
:root {
|
||||
--font-xs: 24rpx;
|
||||
--font-sm: 28rpx;
|
||||
--font-md: 32rpx;
|
||||
--font-lg: 36rpx;
|
||||
--spacing-xs: 12rpx;
|
||||
--spacing-sm: 16rpx;
|
||||
--spacing-md: 20rpx;
|
||||
--spacing-lg: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 超大屏/桌面 */
|
||||
@media screen and (min-width: 751rpx) {
|
||||
:root {
|
||||
--font-xs: 26rpx;
|
||||
--font-sm: 30rpx;
|
||||
--font-md: 34rpx;
|
||||
--font-lg: 38rpx;
|
||||
--spacing-xs: 14rpx;
|
||||
--spacing-sm: 18rpx;
|
||||
--spacing-md: 22rpx;
|
||||
--spacing-lg: 26rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==================== 通用工具类 ==================== */
|
||||
|
||||
/* 布局 */
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
/* 间距 */
|
||||
.gap-xs {
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.gap-sm {
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.gap-md {
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.gap-lg {
|
||||
gap: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.padding-xs {
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.padding-sm {
|
||||
padding: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.padding-md {
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
.padding-lg {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.padding-xl {
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
.margin-xs {
|
||||
margin: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.margin-sm {
|
||||
margin: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.margin-md {
|
||||
margin: var(--spacing-md);
|
||||
}
|
||||
|
||||
.margin-lg {
|
||||
margin: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.margin-xl {
|
||||
margin: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* 文本 */
|
||||
.text-xs {
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.text-md {
|
||||
font-size: var(--font-md);
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: var(--font-lg);
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: var(--font-xl);
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.text-tertiary {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--card-shadow);
|
||||
border: var(--card-border);
|
||||
}
|
||||
|
||||
.card-hover {
|
||||
box-shadow: var(--shadow-strong);
|
||||
transform: translateY(-2rpx);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 按钮 */
|
||||
.btn {
|
||||
background: var(--accent-color);
|
||||
color: #ffffff;
|
||||
border-radius: var(--radius-md);
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
font-size: var(--font-md);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-hover {
|
||||
background: var(--accent-hover);
|
||||
transform: translateY(-2rpx);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--accent-color);
|
||||
border: 2rpx solid var(--accent-color);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-sm);
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
font-size: var(--font-lg);
|
||||
}
|
||||
|
||||
/* 输入框 */
|
||||
.input {
|
||||
background: var(--bg-secondary);
|
||||
border: var(--card-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-md);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.input-focus {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 4rpx var(--shadow-color);
|
||||
}
|
||||
|
||||
/* 分割线 */
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
background: var(--divider-color);
|
||||
margin: var(--spacing-md) 0;
|
||||
}
|
||||
|
||||
.divider-vertical {
|
||||
width: 1rpx;
|
||||
background: var(--divider-color);
|
||||
margin: 0 var(--spacing-md);
|
||||
}
|
||||
|
||||
/* 徽章 */
|
||||
.badge {
|
||||
background: var(--accent-color);
|
||||
color: #ffffff;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: var(--font-xs);
|
||||
}
|
||||
|
||||
/* 骨架 */
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--bg-tertiary) 50%, var(--bg-secondary) 75%);
|
||||
border-radius: var(--radius-sm);
|
||||
animation: skeleton-loading 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes skeleton-loading {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6rpx;
|
||||
height: 6rpx;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
/* 安全区域适配 */
|
||||
.safe-area-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.safe-area-top {
|
||||
padding-top: constant(safe-area-inset-top);
|
||||
padding-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
/* 横屏适配 */
|
||||
@media screen and (orientation: landscape) {
|
||||
.safe-area-bottom {
|
||||
padding-bottom: constant(safe-area-inset-left);
|
||||
padding-bottom: env(safe-area-inset-left);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
const getters = {
|
||||
token: state => state.user.token,
|
||||
avatar: state => state.user.avatar,
|
||||
id: state => state.user.id,
|
||||
name: state => state.user.name,
|
||||
roles: state => state.user.roles,
|
||||
permissions: state => state.user.permissions
|
||||
}
|
||||
export default getters
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import user from '@/store/modules/user'
|
||||
import getters from './getters'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
user
|
||||
},
|
||||
getters
|
||||
})
|
||||
|
||||
export default store
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
import config from './../../config'
|
||||
import storage from '@/utils/storage'
|
||||
import constant from '@/utils/constant'
|
||||
import { isHttp, isEmpty } from "@/utils/validate"
|
||||
import { login, logout, getInfo } from '@/api/login'
|
||||
import { getToken, setToken, removeToken } from '@/utils/auth'
|
||||
import defAva from '@/static/images/profile.jpg'
|
||||
|
||||
const baseUrl = config.baseUrl
|
||||
console.log('User module base URL:', baseUrl)
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
token: getToken(),
|
||||
id: storage.get(constant.id),
|
||||
name: storage.get(constant.name),
|
||||
avatar: storage.get(constant.avatar),
|
||||
roles: storage.get(constant.roles),
|
||||
permissions: storage.get(constant.permissions)
|
||||
},
|
||||
|
||||
mutations: {
|
||||
SET_TOKEN: (state, token) => {
|
||||
state.token = token
|
||||
},
|
||||
SET_ID: (state, id) => {
|
||||
state.id = id
|
||||
storage.set(constant.id, id)
|
||||
},
|
||||
SET_NAME: (state, name) => {
|
||||
state.name = name
|
||||
storage.set(constant.name, name)
|
||||
},
|
||||
SET_AVATAR: (state, avatar) => {
|
||||
state.avatar = avatar
|
||||
storage.set(constant.avatar, avatar)
|
||||
},
|
||||
SET_ROLES: (state, roles) => {
|
||||
state.roles = roles
|
||||
storage.set(constant.roles, roles)
|
||||
},
|
||||
SET_PERMISSIONS: (state, permissions) => {
|
||||
state.permissions = permissions
|
||||
storage.set(constant.permissions, permissions)
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
// 登录
|
||||
Login({ commit }, userInfo) {
|
||||
const username = userInfo.username.trim()
|
||||
const password = userInfo.password
|
||||
const code = userInfo.code
|
||||
const uuid = userInfo.uuid
|
||||
return new Promise((resolve, reject) => {
|
||||
login(username, password, code, uuid).then(res => {
|
||||
setToken(res.token)
|
||||
commit('SET_TOKEN', res.token)
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 获取用户信息
|
||||
GetInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getInfo().then(res => {
|
||||
const user = res.user
|
||||
let avatar = (isEmpty(user) || isEmpty(user.avatar)) ? "" : user.avatar
|
||||
if (!isHttp(avatar)) {
|
||||
avatar = (isEmpty(avatar)) ? defAva : baseUrl + avatar
|
||||
}
|
||||
const userid = (isEmpty(user) || isEmpty(user.userId)) ? "" : user.userId
|
||||
const username = (isEmpty(user) || isEmpty(user.userName)) ? "" : user.userName
|
||||
if (res.roles && res.roles.length > 0) {
|
||||
commit('SET_ROLES', res.roles)
|
||||
commit('SET_PERMISSIONS', res.permissions)
|
||||
} else {
|
||||
commit('SET_ROLES', ['ROLE_DEFAULT'])
|
||||
}
|
||||
commit('SET_ID', userid)
|
||||
commit('SET_NAME', username)
|
||||
commit('SET_AVATAR', avatar)
|
||||
resolve(res)
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
// 退出系统
|
||||
LogOut({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token).then(() => {
|
||||
commit('SET_TOKEN', '')
|
||||
commit('SET_ROLES', [])
|
||||
commit('SET_PERMISSIONS', [])
|
||||
removeToken()
|
||||
storage.clean()
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default user
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* uni-app内置的常用样式变量
|
||||
*/
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: #007aff;
|
||||
$uni-color-success: #4cd964;
|
||||
$uni-color-warning: #f0ad4e;
|
||||
$uni-color-error: #dd524d;
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#e5e5e5;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:12px;
|
||||
$uni-font-size-base:14px;
|
||||
$uni-font-size-lg:16px;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:20px;
|
||||
$uni-img-size-base:26px;
|
||||
$uni-img-size-lg:40px;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 2px;
|
||||
$uni-border-radius-base: 3px;
|
||||
$uni-border-radius-lg: 6px;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 5px;
|
||||
$uni-spacing-row-base: 10px;
|
||||
$uni-spacing-row-lg: 15px;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 4px;
|
||||
$uni-spacing-col-base: 8px;
|
||||
$uni-spacing-col-lg: 12px;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:20px;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:26px;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:15px;
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
## 1.2.2(2023-01-28)
|
||||
- 修复 运行/打包 控制台警告问题
|
||||
## 1.2.1(2022-09-05)
|
||||
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
|
||||
## 1.2.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||
## 1.1.7(2021-11-08)
|
||||
- 优化 升级ui
|
||||
- 修改 size 属性默认值调整为 small
|
||||
- 修改 type 属性,默认值调整为 error,info 替换 default
|
||||
## 1.1.6(2021-09-22)
|
||||
- 修复 在字节小程序上样式不生效的 bug
|
||||
## 1.1.5(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.4(2021-07-29)
|
||||
- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
|
||||
## 1.1.3(2021-06-24)
|
||||
- 优化 示例项目
|
||||
## 1.1.1(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.0(2021-05-12)
|
||||
- 新增 uni-badge 的 absolute 属性,支持定位
|
||||
- 新增 uni-badge 的 offset 属性,支持定位偏移
|
||||
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
|
||||
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
|
||||
- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
|
||||
## 1.0.7(2021-05-07)
|
||||
- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
|
||||
- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
|
||||
- 新增 uni-badge 属性 custom-style, 支持自定义样式
|
||||
## 1.0.6(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
<template>
|
||||
<view class="uni-badge--x">
|
||||
<slot />
|
||||
<text v-if="text" :class="classNames" :style="[positionStyle, customStyle, dotStyle]"
|
||||
class="uni-badge" @click="onClick()">{{displayValue}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Badge 数字角标
|
||||
* @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
|
||||
* @property {String} text 角标内容
|
||||
* @property {String} size = [normal|small] 角标内容
|
||||
* @property {String} type = [info|primary|success|warning|error] 颜色类型
|
||||
* @value info 灰色
|
||||
* @value primary 蓝色
|
||||
* @value success 绿色
|
||||
* @value warning 黄色
|
||||
* @value error 红色
|
||||
* @property {String} inverted = [true|false] 是否无需背景颜色
|
||||
* @property {Number} maxNum 展示封顶的数字值,超过 99 显示 99+
|
||||
* @property {String} absolute = [rightTop|rightBottom|leftBottom|leftTop] 开启绝对定位, 角标将定位到其包裹的标签的四角上
|
||||
* @value rightTop 右上
|
||||
* @value rightBottom 右下
|
||||
* @value leftTop 左上
|
||||
* @value leftBottom 左下
|
||||
* @property {Array[number]} offset 距定位角中心点的偏移量,只有存在 absolute 属性时有效,例如:[-10, -10] 表示向外偏移 10px,[10, 10] 表示向 absolute 指定的内偏移 10px
|
||||
* @property {String} isDot = [true|false] 是否显示为一个小点
|
||||
* @event {Function} click 点击 Badge 触发事件
|
||||
* @example <uni-badge text="1"></uni-badge>
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniBadge',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
inverted: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isDot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxNum: {
|
||||
type: Number,
|
||||
default: 99
|
||||
},
|
||||
absolute: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
offset: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [0, 0]
|
||||
}
|
||||
},
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'small'
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
width() {
|
||||
return String(this.text).length * 8 + 12
|
||||
},
|
||||
classNames() {
|
||||
const {
|
||||
inverted,
|
||||
type,
|
||||
size,
|
||||
absolute
|
||||
} = this
|
||||
return [
|
||||
inverted ? 'uni-badge--' + type + '-inverted' : '',
|
||||
'uni-badge--' + type,
|
||||
'uni-badge--' + size,
|
||||
absolute ? 'uni-badge--absolute' : ''
|
||||
].join(' ')
|
||||
},
|
||||
positionStyle() {
|
||||
if (!this.absolute) return {}
|
||||
let w = this.width / 2,
|
||||
h = 10
|
||||
if (this.isDot) {
|
||||
w = 5
|
||||
h = 5
|
||||
}
|
||||
const x = `${- w + this.offset[0]}px`
|
||||
const y = `${- h + this.offset[1]}px`
|
||||
|
||||
const whiteList = {
|
||||
rightTop: {
|
||||
right: x,
|
||||
top: y
|
||||
},
|
||||
rightBottom: {
|
||||
right: x,
|
||||
bottom: y
|
||||
},
|
||||
leftBottom: {
|
||||
left: x,
|
||||
bottom: y
|
||||
},
|
||||
leftTop: {
|
||||
left: x,
|
||||
top: y
|
||||
}
|
||||
}
|
||||
const match = whiteList[this.absolute]
|
||||
return match ? match : whiteList['rightTop']
|
||||
},
|
||||
dotStyle() {
|
||||
if (!this.isDot) return {}
|
||||
return {
|
||||
width: '10px',
|
||||
minWidth: '0',
|
||||
height: '10px',
|
||||
padding: '0',
|
||||
borderRadius: '10px'
|
||||
}
|
||||
},
|
||||
displayValue() {
|
||||
const {
|
||||
isDot,
|
||||
text,
|
||||
maxNum
|
||||
} = this
|
||||
return isDot ? '' : (Number(text) > maxNum ? `${maxNum}+` : text)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" >
|
||||
$uni-primary: #2979ff !default;
|
||||
$uni-success: #4cd964 !default;
|
||||
$uni-warning: #f0ad4e !default;
|
||||
$uni-error: #dd524d !default;
|
||||
$uni-info: #909399 !default;
|
||||
|
||||
|
||||
$bage-size: 12px;
|
||||
$bage-small: scale(0.8);
|
||||
|
||||
.uni-badge--x {
|
||||
/* #ifdef APP-NVUE */
|
||||
// align-self: flex-start;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-badge--absolute {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.uni-badge--small {
|
||||
transform: $bage-small;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.uni-badge {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
font-feature-settings: "tnum";
|
||||
min-width: 20px;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
height: 20px;
|
||||
padding: 0 4px;
|
||||
line-height: 18px;
|
||||
color: #fff;
|
||||
border-radius: 100px;
|
||||
background-color: $uni-info;
|
||||
background-color: transparent;
|
||||
border: 1px solid #fff;
|
||||
text-align: center;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
font-size: $bage-size;
|
||||
/* #ifdef H5 */
|
||||
z-index: 999;
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
|
||||
&--info {
|
||||
color: #fff;
|
||||
background-color: $uni-info;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background-color: $uni-success;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: $uni-warning;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: $uni-error;
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
padding: 0 5px 0 0;
|
||||
color: $uni-info;
|
||||
}
|
||||
|
||||
&--info-inverted {
|
||||
color: $uni-info;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--primary-inverted {
|
||||
color: $uni-primary;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--success-inverted {
|
||||
color: $uni-success;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--warning-inverted {
|
||||
color: $uni-warning;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&--error-inverted {
|
||||
color: $uni-error;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"id": "uni-badge",
|
||||
"displayName": "uni-badge 数字角标",
|
||||
"version": "1.2.2",
|
||||
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
|
||||
"keywords": [
|
||||
"",
|
||||
"badge",
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"数字角标",
|
||||
"徽章"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-scss"],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "y",
|
||||
"联盟": "y"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
## Badge 数字角标
|
||||
> **组件名:uni-badge**
|
||||
> 代码块: `uBadge`
|
||||
|
||||
数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
## 1.3.1(2021-12-20)
|
||||
- 修复 在vue页面下略缩图显示不正常的bug
|
||||
## 1.3.0(2021-11-19)
|
||||
- 重构插槽的用法 ,header 替换为 title
|
||||
- 新增 actions 插槽
|
||||
- 新增 cover 封面图属性和插槽
|
||||
- 新增 padding 内容默认内边距离
|
||||
- 新增 margin 卡片默认外边距离
|
||||
- 新增 spacing 卡片默认内边距
|
||||
- 新增 shadow 卡片阴影属性
|
||||
- 取消 mode 属性,可使用组合插槽代替
|
||||
- 取消 note 属性 ,使用actions插槽代替
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
|
||||
## 1.2.1(2021-07-30)
|
||||
- 优化 vue3下事件警告的问题
|
||||
## 1.2.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.8(2021-07-01)
|
||||
- 优化 图文卡片无图片加载时,提供占位图标
|
||||
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
|
||||
- 修复 thumbnail 不存在仍然占位的 bug
|
||||
## 1.1.7(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.1.6(2021-02-04)
|
||||
- 调整为uni_modules目录规范
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
<template>
|
||||
<view class="uni-card" :class="{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}"
|
||||
:style="{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}">
|
||||
<!-- 封面 -->
|
||||
<slot name="cover">
|
||||
<view v-if="cover" class="uni-card__cover">
|
||||
<image class="uni-card__cover-image" mode="widthFix" @click="onClick('cover')" :src="cover"></image>
|
||||
</view>
|
||||
</slot>
|
||||
<slot name="title">
|
||||
<view v-if="title || extra" class="uni-card__header">
|
||||
<!-- 卡片标题 -->
|
||||
<view class="uni-card__header-box" @click="onClick('title')">
|
||||
<view v-if="thumbnail" class="uni-card__header-avatar">
|
||||
<image class="uni-card__header-avatar-image" :src="thumbnail" mode="aspectFit" />
|
||||
</view>
|
||||
<view class="uni-card__header-content">
|
||||
<text class="uni-card__header-content-title uni-ellipsis">{{ title }}</text>
|
||||
<text v-if="title&&subTitle"
|
||||
class="uni-card__header-content-subtitle uni-ellipsis">{{ subTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-card__header-extra" @click="onClick('extra')">
|
||||
<text class="uni-card__header-extra-text">{{ extra }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
<!-- 卡片内容 -->
|
||||
<view class="uni-card__content" :style="{padding:padding}" @click="onClick('content')">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="uni-card__actions" @click="onClick('actions')">
|
||||
<slot name="actions"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Card 卡片
|
||||
* @description 卡片视图组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
|
||||
* @property {String} title 标题文字
|
||||
* @property {String} subTitle 副标题
|
||||
* @property {Number} padding 内容内边距
|
||||
* @property {Number} margin 卡片外边距
|
||||
* @property {Number} spacing 卡片内边距
|
||||
* @property {String} extra 标题额外信息
|
||||
* @property {String} cover 封面图(本地路径需要引入)
|
||||
* @property {String} thumbnail 标题左侧缩略图
|
||||
* @property {Boolean} is-full = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
|
||||
* @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影
|
||||
* @property {String} shadow 卡片阴影
|
||||
* @property {Boolean} border 卡片边框
|
||||
* @event {Function} click 点击 Card 触发事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniCard',
|
||||
emits: ['click'],
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
padding: {
|
||||
type: String,
|
||||
default: '10px'
|
||||
},
|
||||
margin: {
|
||||
type: String,
|
||||
default: '15px'
|
||||
},
|
||||
spacing: {
|
||||
type: String,
|
||||
default: '0 10px'
|
||||
},
|
||||
extra: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
cover: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isFull: {
|
||||
// 内容区域是否通栏
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShadow: {
|
||||
// 是否开启阴影
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
shadow: {
|
||||
type: String,
|
||||
default: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick(type) {
|
||||
this.$emit('click', type)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$uni-border-3: #EBEEF5 !default;
|
||||
$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;
|
||||
$uni-main-color: #3a3a3a !default;
|
||||
$uni-base-color: #6a6a6a !default;
|
||||
$uni-secondary-color: #909399 !default;
|
||||
$uni-spacing-sm: 8px !default;
|
||||
$uni-border-color:$uni-border-3;
|
||||
$uni-shadow: $uni-shadow-base;
|
||||
$uni-card-title: 15px;
|
||||
$uni-cart-title-color:$uni-main-color;
|
||||
$uni-card-subtitle: 12px;
|
||||
$uni-cart-subtitle-color:$uni-secondary-color;
|
||||
$uni-card-spacing: 10px;
|
||||
$uni-card-content-color: $uni-base-color;
|
||||
|
||||
.uni-card {
|
||||
margin: $uni-card-spacing;
|
||||
padding: 0 $uni-spacing-sm;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
|
||||
background-color: #fff;
|
||||
flex: 1;
|
||||
|
||||
.uni-card__cover {
|
||||
position: relative;
|
||||
margin-top: $uni-card-spacing;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
.uni-card__cover-image {
|
||||
flex: 1;
|
||||
// width: 100%;
|
||||
/* #ifndef APP-PLUS */
|
||||
vertical-align: middle;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__header {
|
||||
display: flex;
|
||||
border-bottom: 1px $uni-border-color solid;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: $uni-card-spacing;
|
||||
overflow: hidden;
|
||||
|
||||
.uni-card__header-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-card__header-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
margin-right: $uni-card-spacing;
|
||||
.uni-card__header-avatar-image {
|
||||
flex: 1;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__header-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
// height: 40px;
|
||||
overflow: hidden;
|
||||
|
||||
.uni-card__header-content-title {
|
||||
font-size: $uni-card-title;
|
||||
color: $uni-cart-title-color;
|
||||
// line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-card__header-content-subtitle {
|
||||
font-size: $uni-card-subtitle;
|
||||
margin-top: 5px;
|
||||
color: $uni-cart-subtitle-color;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__header-extra {
|
||||
line-height: 12px;
|
||||
|
||||
.uni-card__header-extra-text {
|
||||
font-size: 12px;
|
||||
color: $uni-cart-subtitle-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card__content {
|
||||
padding: $uni-card-spacing;
|
||||
font-size: 14px;
|
||||
color: $uni-card-content-color;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-card__actions {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.uni-card--border {
|
||||
border: 1px solid $uni-border-color;
|
||||
}
|
||||
|
||||
.uni-card--shadow {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-shadow: $uni-shadow;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-card--full {
|
||||
margin: 0;
|
||||
border-left-width: 0;
|
||||
border-left-width: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-card--full:after {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-ellipsis {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
"id": "uni-card",
|
||||
"displayName": "uni-card 卡片",
|
||||
"version": "1.3.1",
|
||||
"description": "Card 组件,提供常见的卡片样式。",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"card",
|
||||
"",
|
||||
"卡片"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-icons",
|
||||
"uni-scss"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
|
||||
## Card 卡片
|
||||
> **组件名:uni-card**
|
||||
> 代码块: `uCard`
|
||||
|
||||
卡片视图组件。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
## 1.0.6(2024-10-22)
|
||||
- 新增 当 multiple 为 false 且传递的 value 为 数组时,使用数组第一项用作反显
|
||||
## 1.0.5(2024-03-20)
|
||||
- 修复 单选模式下选中样式不生效的bug
|
||||
## 1.0.4(2024-01-27)
|
||||
- 修复 修复错别字chagne为change
|
||||
## 1.0.3(2022-09-16)
|
||||
- 可以使用 uni-scss 控制主题色
|
||||
## 1.0.2(2022-06-30)
|
||||
- 优化 在 uni-forms 中的依赖注入方式
|
||||
## 1.0.1(2022-02-07)
|
||||
- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
|
||||
## 0.2.5(2021-08-23)
|
||||
- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
|
||||
## 0.2.4(2021-08-17)
|
||||
- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
|
||||
## 0.2.3(2021-08-11)
|
||||
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
|
||||
## 0.2.2(2021-07-30)
|
||||
- 优化 在uni-forms组件,与label不对齐的问题
|
||||
## 0.2.1(2021-07-27)
|
||||
- 修复 单选默认值为0不能选中的Bug
|
||||
## 0.2.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 0.1.11(2021-07-06)
|
||||
- 优化 删除无用日志
|
||||
## 0.1.10(2021-07-05)
|
||||
- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
|
||||
## 0.1.9(2021-07-05)
|
||||
- 修复 nvue 黑框样式问题
|
||||
## 0.1.8(2021-06-28)
|
||||
- 修复 selectedTextColor 属性不生效的Bug
|
||||
## 0.1.7(2021-06-02)
|
||||
- 新增 map 属性,可以方便映射text/value属性
|
||||
## 0.1.6(2021-05-26)
|
||||
- 修复 不关联服务空间的情况下组件报错的Bug
|
||||
## 0.1.5(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 0.1.4(2021-04-09)
|
||||
- 修复 nvue 下无法选中的问题
|
||||
## 0.1.3(2021-03-22)
|
||||
- 新增 disabled属性
|
||||
## 0.1.2(2021-02-24)
|
||||
- 优化 默认颜色显示
|
||||
## 0.1.1(2021-02-24)
|
||||
- 新增 支持nvue
|
||||
## 0.1.0(2021-02-18)
|
||||
- “暂无数据”显示居中
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
|
||||
const events = {
|
||||
load: 'load',
|
||||
error: 'error'
|
||||
}
|
||||
const pageMode = {
|
||||
add: 'add',
|
||||
replace: 'replace'
|
||||
}
|
||||
|
||||
const attrs = [
|
||||
'pageCurrent',
|
||||
'pageSize',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'getcount',
|
||||
'orderby',
|
||||
'where'
|
||||
]
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
listData: this.getone ? {} : [],
|
||||
paginationInternal: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
},
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let db = null;
|
||||
let dbCmd = null;
|
||||
|
||||
if(this.collection){
|
||||
this.db = uniCloud.database();
|
||||
this.dbCmd = this.db.command;
|
||||
}
|
||||
|
||||
this._isEnded = false
|
||||
|
||||
this.$watch(() => {
|
||||
let al = []
|
||||
attrs.forEach(key => {
|
||||
al.push(this[key])
|
||||
})
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
this.paginationInternal.pageSize = this.pageSize
|
||||
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (needReset) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.paginationInternal.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this._execLoadData()
|
||||
})
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList = []
|
||||
if (!window.unidev) {
|
||||
window.unidev = {
|
||||
clientDB: {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
unidev.clientDB.data.push(this._debugDataList)
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-TOUTIAO
|
||||
let changeName
|
||||
let events = this.$scope.dataset.eventOpts
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
let event = events[i]
|
||||
if (event[0].includes('^load')) {
|
||||
changeName = event[1][0][0]
|
||||
}
|
||||
}
|
||||
if (changeName) {
|
||||
let parent = this.$parent
|
||||
let maxDepth = 16
|
||||
this._changeDataFunction = null
|
||||
while (parent && maxDepth > 0) {
|
||||
let fun = parent[changeName]
|
||||
if (fun && typeof fun === 'function') {
|
||||
this._changeDataFunction = fun
|
||||
maxDepth = 0
|
||||
break
|
||||
}
|
||||
parent = parent.$parent
|
||||
maxDepth--;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// if (!this.manual) {
|
||||
// this.loadData()
|
||||
// }
|
||||
},
|
||||
// #ifdef H5
|
||||
beforeDestroy() {
|
||||
if (process.env.NODE_ENV === 'development' && window.unidev) {
|
||||
let cd = this._debugDataList
|
||||
let dl = unidev.clientDB.data
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
if (dl[i] === cd) {
|
||||
dl.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
loadData(args1, args2) {
|
||||
let callback = null
|
||||
if (typeof args1 === 'object') {
|
||||
if (args1.clear) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (args1.current !== undefined) {
|
||||
this.paginationInternal.current = args1.current
|
||||
}
|
||||
if (typeof args2 === 'function') {
|
||||
callback = args2
|
||||
}
|
||||
} else if (typeof args1 === 'function') {
|
||||
callback = args1
|
||||
}
|
||||
|
||||
this._execLoadData(callback)
|
||||
},
|
||||
loadMore() {
|
||||
if (this._isEnded) {
|
||||
return
|
||||
}
|
||||
this._execLoadData()
|
||||
},
|
||||
refresh() {
|
||||
this.clear()
|
||||
this._execLoadData()
|
||||
},
|
||||
clear() {
|
||||
this._isEnded = false
|
||||
this.listData = []
|
||||
},
|
||||
reset() {
|
||||
this.paginationInternal.current = 1
|
||||
},
|
||||
remove(id, {
|
||||
action,
|
||||
callback,
|
||||
confirmTitle,
|
||||
confirmContent
|
||||
} = {}) {
|
||||
if (!id || !id.length) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: confirmTitle || '提示',
|
||||
content: confirmContent || '是否删除该数据',
|
||||
showCancel: true,
|
||||
success: (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
this._execRemove(id, action, callback)
|
||||
}
|
||||
})
|
||||
},
|
||||
_execLoadData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMessage = ''
|
||||
|
||||
this._getExec().then((res) => {
|
||||
this.loading = false
|
||||
const {
|
||||
data,
|
||||
count
|
||||
} = res.result
|
||||
this._isEnded = data.length < this.pageSize
|
||||
|
||||
callback && callback(data, this._isEnded)
|
||||
this._dispatchEvent(events.load, data)
|
||||
|
||||
if (this.getone) {
|
||||
this.listData = data.length ? data[0] : undefined
|
||||
} else if (this.pageData === pageMode.add) {
|
||||
this.listData.push(...data)
|
||||
if (this.listData.length) {
|
||||
this.paginationInternal.current++
|
||||
}
|
||||
} else if (this.pageData === pageMode.replace) {
|
||||
this.listData = data
|
||||
this.paginationInternal.count = count
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList.length = 0
|
||||
this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData)))
|
||||
}
|
||||
// #endif
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
callback && callback()
|
||||
this.$emit(events.error, err)
|
||||
})
|
||||
},
|
||||
_getExec() {
|
||||
let exec = this.db
|
||||
if (this.action) {
|
||||
exec = exec.action(this.action)
|
||||
}
|
||||
|
||||
exec = exec.collection(this.collection)
|
||||
|
||||
if (!(!this.where || !Object.keys(this.where).length)) {
|
||||
exec = exec.where(this.where)
|
||||
}
|
||||
if (this.field) {
|
||||
exec = exec.field(this.field)
|
||||
}
|
||||
if (this.orderby) {
|
||||
exec = exec.orderBy(this.orderby)
|
||||
}
|
||||
|
||||
const {
|
||||
current,
|
||||
size
|
||||
} = this.paginationInternal
|
||||
exec = exec.skip(size * (current - 1)).limit(size).get({
|
||||
getCount: this.getcount
|
||||
})
|
||||
|
||||
return exec
|
||||
},
|
||||
_execRemove(id, action, callback) {
|
||||
if (!this.collection || !id) {
|
||||
return
|
||||
}
|
||||
|
||||
const ids = Array.isArray(id) ? id : [id]
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
mask: true
|
||||
})
|
||||
|
||||
let exec = this.db
|
||||
if (action) {
|
||||
exec = exec.action(action)
|
||||
}
|
||||
|
||||
exec.collection(this.collection).where({
|
||||
_id: dbCmd.in(ids)
|
||||
}).remove().then((res) => {
|
||||
callback && callback(res.result)
|
||||
if (this.pageData === pageMode.replace) {
|
||||
this.refresh()
|
||||
} else {
|
||||
this.removeData(ids)
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.showModal({
|
||||
content: err.message,
|
||||
showCancel: false
|
||||
})
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
removeData(ids) {
|
||||
let il = ids.slice(0)
|
||||
let dl = this.listData
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
let index = il.indexOf(dl[i]._id)
|
||||
if (index >= 0) {
|
||||
dl.splice(i, 1)
|
||||
il.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
_dispatchEvent(type, data) {
|
||||
if (this._changeDataFunction) {
|
||||
this._changeDataFunction(data, this._isEnded)
|
||||
} else {
|
||||
this.$emit(type, data, this._isEnded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,853 @@
|
|||
<template>
|
||||
<view class="uni-data-checklist" :style="{'margin-top':isTop+'px'}">
|
||||
<template v-if="!isLocal">
|
||||
<view class="uni-data-loading">
|
||||
<uni-load-more v-if="!mixinDatacomErrorMessage" status="loading" iconType="snow" :iconSize="18"
|
||||
:content-text="contentText"></uni-load-more>
|
||||
<text v-else>{{mixinDatacomErrorMessage}}</text>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list' || wrap}"
|
||||
@change="change">
|
||||
<label class="checklist-box"
|
||||
:class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<checkbox class="hidden" hidden :disabled="disabled || !!item.disabled" :value="item[map.value]+''"
|
||||
:checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')"
|
||||
class="checkbox__inner" :style="item.styleIcon">
|
||||
<view class="checkbox__inner-icon"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
|
||||
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :style="item.styleBackgroud"></view>
|
||||
</view>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="change">
|
||||
<label class="checklist-box"
|
||||
:class="['is--'+mode,item.selected?'is-checked':'',(disabled || !!item.disabled)?'is-disable':'',index!==0&&mode==='list'?'is-list-border':'']"
|
||||
:style="item.styleBackgroud" v-for="(item,index) in dataList" :key="index">
|
||||
<radio class="hidden" hidden :disabled="disabled || item.disabled" :value="item[map.value]+''"
|
||||
:checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
|
||||
:style="item.styleBackgroud">
|
||||
<view class="radio__inner-icon" :style="item.styleIcon"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text>
|
||||
<view v-if="mode === 'list' && icon === 'right'" :style="item.styleRightIcon" class="checkobx__list"></view>
|
||||
</view>
|
||||
</label>
|
||||
</radio-group>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* DataChecklist 数据选择器
|
||||
* @description 通过数据渲染 checkbox 和 radio
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
|
||||
* @property {String} mode = [default| list | button | tag] 显示模式
|
||||
* @value default 默认横排模式
|
||||
* @value list 列表模式
|
||||
* @value button 按钮模式
|
||||
* @value tag 标签模式
|
||||
* @property {Boolean} multiple = [true|false] 是否多选
|
||||
* @property {Array|String|Number} value 默认值
|
||||
* @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
|
||||
* @property {Number|String} min 最小选择个数 ,multiple为true时生效
|
||||
* @property {Number|String} max 最大选择个数 ,multiple为true时生效
|
||||
* @property {Boolean} wrap 是否换行显示
|
||||
* @property {String} icon = [left|right] list 列表模式下icon显示位置
|
||||
* @property {Boolean} selectedColor 选中颜色
|
||||
* @property {Boolean} emptyText 没有数据时显示的文字 ,本地数据无效
|
||||
* @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示
|
||||
* @property {Object} map 字段映射, 默认 map={text:'text',value:'value'}
|
||||
* @value left 左侧显示
|
||||
* @value right 右侧显示
|
||||
* @event {Function} change 选中发生变化触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniDataChecklist',
|
||||
mixins: [uniCloud.mixinDatacom || {}],
|
||||
emits: ['input', 'update:modelValue', 'change'],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
// TODO vue3
|
||||
modelValue: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
localdata: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
selectedColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selectedTextColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: '暂无数据'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
map: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
text: 'text',
|
||||
value: 'value'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
localdata: {
|
||||
handler(newVal) {
|
||||
this.range = newVal
|
||||
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
mixinDatacomResData(newVal) {
|
||||
this.range = newVal
|
||||
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||
},
|
||||
value(newVal) {
|
||||
this.dataList = this.getDataList(newVal)
|
||||
// fix by mehaotian is_reset 在 uni-forms 中定义
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem && this.formItem.setValue(newVal)
|
||||
// }
|
||||
},
|
||||
modelValue(newVal) {
|
||||
this.dataList = this.getDataList(newVal);
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem && this.formItem.setValue(newVal)
|
||||
// }
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataList: [],
|
||||
range: [],
|
||||
contentText: {
|
||||
contentdown: '查看更多',
|
||||
contentrefresh: '加载中',
|
||||
contentnomore: '没有更多'
|
||||
},
|
||||
isLocal: true,
|
||||
styles: {
|
||||
selectedColor: '#2979ff',
|
||||
selectedTextColor: '#666',
|
||||
},
|
||||
isTop: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
dataValue() {
|
||||
if (this.value === '') return this.modelValue
|
||||
if (this.modelValue === '') return this.value
|
||||
return this.value
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// this.form = this.getForm('uniForms')
|
||||
// this.formItem = this.getForm('uniFormsItem')
|
||||
// this.formItem && this.formItem.setValue(this.value)
|
||||
|
||||
// if (this.formItem) {
|
||||
// this.isTop = 6
|
||||
// if (this.formItem.name) {
|
||||
// // 如果存在name添加默认值,否则formData 中不存在这个字段不校验
|
||||
// if(!this.is_reset){
|
||||
// this.is_reset = false
|
||||
// this.formItem.setValue(this.dataValue)
|
||||
// }
|
||||
// this.rename = this.formItem.name
|
||||
// this.form.inputChildrens.push(this)
|
||||
// }
|
||||
// }
|
||||
|
||||
if (this.localdata && this.localdata.length !== 0) {
|
||||
this.isLocal = true
|
||||
this.range = this.localdata
|
||||
this.dataList = this.getDataList(this.getSelectedValue(this.range))
|
||||
} else {
|
||||
if (this.collection) {
|
||||
this.isLocal = false
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadData() {
|
||||
this.mixinDatacomGet().then(res => {
|
||||
this.mixinDatacomResData = res.result.data
|
||||
if (this.mixinDatacomResData.length === 0) {
|
||||
this.isLocal = false
|
||||
this.mixinDatacomErrorMessage = this.emptyText
|
||||
} else {
|
||||
this.isLocal = true
|
||||
}
|
||||
}).catch(err => {
|
||||
this.mixinDatacomErrorMessage = err.message
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
change(e) {
|
||||
const values = e.detail.value
|
||||
|
||||
let detail = {
|
||||
value: [],
|
||||
data: []
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
this.range.forEach(item => {
|
||||
|
||||
if (values.includes(item[this.map.value] + '')) {
|
||||
detail.value.push(item[this.map.value])
|
||||
detail.data.push(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const range = this.range.find(item => (item[this.map.value] + '') === values)
|
||||
if (range) {
|
||||
detail = {
|
||||
value: range[this.map.value],
|
||||
data: range
|
||||
}
|
||||
}
|
||||
}
|
||||
// this.formItem && this.formItem.setValue(detail.value)
|
||||
// TODO 兼容 vue2
|
||||
this.$emit('input', detail.value);
|
||||
// // TOTO 兼容 vue3
|
||||
this.$emit('update:modelValue', detail.value);
|
||||
this.$emit('change', {
|
||||
detail
|
||||
})
|
||||
if (this.multiple) {
|
||||
// 如果 v-model 没有绑定 ,则走内部逻辑
|
||||
// if (this.value.length === 0) {
|
||||
this.dataList = this.getDataList(detail.value, true)
|
||||
// }
|
||||
} else {
|
||||
this.dataList = this.getDataList(detail.value)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取渲染的新数组
|
||||
* @param {Object} value 选中内容
|
||||
*/
|
||||
getDataList(value) {
|
||||
// 解除引用关系,破坏原引用关系,避免污染源数据
|
||||
let dataList = JSON.parse(JSON.stringify(this.range))
|
||||
let list = []
|
||||
if (this.multiple) {
|
||||
if (!Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
} else {
|
||||
if (Array.isArray(value) && value.length) {
|
||||
value = value[0]
|
||||
}
|
||||
}
|
||||
dataList.forEach((item, index) => {
|
||||
item.disabled = item.disable || item.disabled || false
|
||||
if (this.multiple) {
|
||||
if (value.length > 0) {
|
||||
let have = value.find(val => val === item[this.map.value])
|
||||
item.selected = have !== undefined
|
||||
} else {
|
||||
item.selected = false
|
||||
}
|
||||
} else {
|
||||
item.selected = value === item[this.map.value]
|
||||
}
|
||||
|
||||
list.push(item)
|
||||
})
|
||||
return this.setRange(list)
|
||||
},
|
||||
/**
|
||||
* 处理最大最小值
|
||||
* @param {Object} list
|
||||
*/
|
||||
setRange(list) {
|
||||
let selectList = list.filter(item => item.selected)
|
||||
let min = Number(this.min) || 0
|
||||
let max = Number(this.max) || ''
|
||||
list.forEach((item, index) => {
|
||||
if (this.multiple) {
|
||||
if (selectList.length <= min) {
|
||||
let have = selectList.find(val => val[this.map.value] === item[this.map.value])
|
||||
if (have !== undefined) {
|
||||
item.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (selectList.length >= max && max !== '') {
|
||||
let have = selectList.find(val => val[this.map.value] === item[this.map.value])
|
||||
if (have === undefined) {
|
||||
item.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setStyles(item, index)
|
||||
list[index] = item
|
||||
})
|
||||
return list
|
||||
},
|
||||
/**
|
||||
* 设置 class
|
||||
* @param {Object} item
|
||||
* @param {Object} index
|
||||
*/
|
||||
setStyles(item, index) {
|
||||
// 设置自定义样式
|
||||
item.styleBackgroud = this.setStyleBackgroud(item)
|
||||
item.styleIcon = this.setStyleIcon(item)
|
||||
item.styleIconText = this.setStyleIconText(item)
|
||||
item.styleRightIcon = this.setStyleRightIcon(item)
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取选中值
|
||||
* @param {Object} range
|
||||
*/
|
||||
getSelectedValue(range) {
|
||||
if (!this.multiple) return this.dataValue
|
||||
let selectedArr = []
|
||||
range.forEach((item) => {
|
||||
if (item.selected) {
|
||||
selectedArr.push(item[this.map.value])
|
||||
}
|
||||
})
|
||||
return this.dataValue.length > 0 ? this.dataValue : selectedArr
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置背景样式
|
||||
*/
|
||||
setStyleBackgroud(item) {
|
||||
let styles = {}
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
if (this.selectedColor) {
|
||||
if (this.mode !== 'list') {
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
}
|
||||
if (this.mode === 'tag') {
|
||||
styles['background-color'] = item.selected ? selectedColor : '#f5f5f5'
|
||||
}
|
||||
}
|
||||
let classles = ''
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
return classles
|
||||
},
|
||||
setStyleIcon(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.selectedColor) {
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
styles['background-color'] = item.selected ? selectedColor : '#fff'
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
|
||||
if (!item.selected && item.disabled) {
|
||||
styles['background-color'] = '#F2F6FC'
|
||||
styles['border-color'] = item.selected ? selectedColor : '#DCDFE6'
|
||||
}
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
return classles
|
||||
},
|
||||
setStyleIconText(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.selectedColor) {
|
||||
let selectedColor = this.selectedColor ? this.selectedColor : '#2979ff'
|
||||
if (this.mode === 'tag') {
|
||||
styles.color = item.selected ? (this.selectedTextColor ? this.selectedTextColor : '#fff') : '#666'
|
||||
} else {
|
||||
styles.color = item.selected ? (this.selectedTextColor ? this.selectedTextColor : selectedColor) : '#666'
|
||||
}
|
||||
if (!item.selected && item.disabled) {
|
||||
styles.color = '#999'
|
||||
}
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
return classles
|
||||
},
|
||||
setStyleRightIcon(item) {
|
||||
let styles = {}
|
||||
let classles = ''
|
||||
if (this.mode === 'list') {
|
||||
styles['border-color'] = item.selected ? this.styles.selectedColor : '#DCDFE6'
|
||||
}
|
||||
for (let i in styles) {
|
||||
classles += `${i}:${styles[i]};`
|
||||
}
|
||||
|
||||
return classles
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$uni-primary: #2979ff !default;
|
||||
$border-color: #DCDFE6;
|
||||
$disable: 0.4;
|
||||
|
||||
@mixin flex {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-data-loading {
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
padding-left: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-data-checklist {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
flex: 1;
|
||||
|
||||
// 多选样式
|
||||
.checklist-group {
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&.is-list {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.checklist-box {
|
||||
@include flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
margin: 5px 0;
|
||||
margin-right: 25px;
|
||||
|
||||
.hidden {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
// 文字样式
|
||||
.checklist-content {
|
||||
@include flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.checklist-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.checkobx__list {
|
||||
border-right-width: 1px;
|
||||
border-right-color: #007aff;
|
||||
border-right-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #007aff;
|
||||
border-bottom-style: solid;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
left: -5px;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 多选样式
|
||||
.checkbox__inner {
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
position: absolute;
|
||||
/* #ifdef APP-NVUE */
|
||||
top: 2px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
top: 1px;
|
||||
/* #endif */
|
||||
left: 5px;
|
||||
height: 8px;
|
||||
width: 4px;
|
||||
border-right-width: 1px;
|
||||
border-right-color: #fff;
|
||||
border-right-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #fff;
|
||||
border-bottom-style: solid;
|
||||
opacity: 0;
|
||||
transform-origin: center;
|
||||
transform: rotate(40deg);
|
||||
}
|
||||
}
|
||||
|
||||
// 单选样式
|
||||
.radio__inner {
|
||||
@include flex;
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 16px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
.radio__inner-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 10px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认样式
|
||||
&.is--default {
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
|
||||
/* #endif */
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
// 选中
|
||||
&.is-checked {
|
||||
.checkbox__inner {
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
.checkbox__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮样式
|
||||
&.is--button {
|
||||
margin-right: 10px;
|
||||
padding: 5px 10px;
|
||||
border: 1px $border-color solid;
|
||||
border-radius: 3px;
|
||||
transition: border-color 0.2s;
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
border: 1px #eee solid;
|
||||
opacity: $disable;
|
||||
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
border-color: $uni-primary;
|
||||
|
||||
.checkbox__inner {
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
opacity: $disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 标签样式
|
||||
&.is--tag {
|
||||
margin-right: 10px;
|
||||
padding: 5px 10px;
|
||||
border: 1px $border-color solid;
|
||||
border-radius: 3px;
|
||||
background-color: #f5f5f5;
|
||||
|
||||
.checklist-text {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
background-color: $uni-primary;
|
||||
border-color: $uni-primary;
|
||||
|
||||
.checklist-text {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 列表样式
|
||||
&.is--list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
padding: 10px 15px;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
|
||||
&.is-list-border {
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
// 禁用
|
||||
&.is-disable {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
|
||||
/* #endif */
|
||||
.checkbox__inner {
|
||||
background-color: #F2F6FC;
|
||||
border-color: $border-color;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
.checkbox__inner {
|
||||
border-color: $uni-primary;
|
||||
background-color: $uni-primary;
|
||||
|
||||
.checkbox__inner-icon {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
border-color: $uni-primary;
|
||||
.radio__inner-icon {
|
||||
opacity: 1;
|
||||
background-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.checklist-content {
|
||||
.checkobx__list {
|
||||
opacity: 1;
|
||||
border-color: $uni-primary;
|
||||
}
|
||||
}
|
||||
|
||||
// 选中禁用
|
||||
&.is-disable {
|
||||
.checkbox__inner {
|
||||
opacity: $disable;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
opacity: $disable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
{
|
||||
"id": "uni-data-checkbox",
|
||||
"displayName": "uni-data-checkbox 数据选择器",
|
||||
"version": "1.0.6",
|
||||
"description": "通过数据驱动的单选框和复选框",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"checkbox",
|
||||
"单选",
|
||||
"多选",
|
||||
"单选多选"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.1"
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": ["uni-load-more","uni-scss"],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y",
|
||||
"alipay": "n"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y",
|
||||
"app-harmony": "u",
|
||||
"app-uvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
|
||||
## DataCheckbox 数据驱动的单选复选框
|
||||
> **组件名:uni-data-checkbox**
|
||||
> 代码块: `uDataCheckbox`
|
||||
|
||||
|
||||
本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括:
|
||||
|
||||
1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能
|
||||
2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验
|
||||
3. 本组件合并了单选多选
|
||||
4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性
|
||||
|
||||
在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||