This commit is contained in:
mixtan 2025-09-28 11:32:15 +08:00
commit 14371cb08e
46 changed files with 3307 additions and 593 deletions

62
JenkinsfileBuild Normal file
View File

@ -0,0 +1,62 @@
pipeline {
agent any
tools {
nodejs 'NodeJS16'
}
environment {
// 抽取部署目标路径为公共变量
DEPLOY_DIR = '/data/nginx/www/fafamall/admin'
}
stages {
stage('拉取代码') {
steps {
checkout scm
}
}
stage('安装依赖') {
steps {
sh '''
echo "清理旧依赖..."
rm -rf node_modules package-lock.json
npm cache clean --force
echo "设置国内腾讯云镜像源加速..."
npm config set registry https://mirrors.cloud.tencent.com/npm/
# 安装其他依赖
npm install
'''
}
}
stage('构建项目') {
steps {
sh 'npm run build'
}
}
stage('部署项目') {
steps {
sh '''
# 公共部署逻辑(创建目录、清理、复制)
mkdir -p ${DEPLOY_DIR}
rm -rf ${DEPLOY_DIR}/*
cp -r admin/* ${DEPLOY_DIR}/
'''
}
}
}
post {
success {
echo '项目部署成功.'
}
failure {
echo '项目部署失败.'
}
}
}

36
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,36 @@
pipeline {
agent any
environment {
DEPLOY_DIR = '/data/nginx/www/fafamall/admin'
DIST_PATH = 'admin'
}
stages {
stage('拉取代码并验证') {
steps {
checkout scm
sh """
[ -d "${DIST_PATH}" ] || { echo "错误:未找到 ${DIST_PATH} 文件夹"; exit 1; }
echo "${DIST_PATH} 验证通过"
"""
}
}
stage('部署') {
steps {
sh """
mkdir -p ${DEPLOY_DIR}
rm -rf ${DEPLOY_DIR}/*
cp -r ${DIST_PATH}/* ${DEPLOY_DIR}/
echo "部署完成:${DIST_PATH} → ${DEPLOY_DIR}"
"""
}
}
}
post {
success { echo "✅ 部署成功" }
failure { echo "❌ 部署失败" }
}
}

View File

@ -5,5 +5,5 @@
window.ver = "2.0.278"; window.ver = "2.0.278";
window.SYS = {CONFIG:{}, URL:{}};</script><script>window._AMapSecurityConfig = { window.SYS = {CONFIG:{}, URL:{}};</script><script>window._AMapSecurityConfig = {
securityJsCode:"07788e7ebd7e913985722bfc5986999f" securityJsCode:"07788e7ebd7e913985722bfc5986999f"
}</script><script src="https://mall.gpxscs.cn/admin/config.js?v=2.0.278"></script><script src="https://mall.gpxscs.cn/admin/im/libs3.6.0.min.js?v=2.0.278"></script><script src="https://mall.gpxscs.cn/admin/im/im.js?v=2.0.278"></script><link href="static/css/chunk-19648027.83a11e8c.css" rel="prefetch"><link href="static/css/chunk-250d32fa.ad6e6718.css" rel="prefetch"><link href="static/css/vab-extra.f3d407d7.css" rel="prefetch"><link href="static/js/chunk-19648027.6233a553.js" rel="prefetch"><link href="static/js/chunk-250d32fa.ee34e965.js" rel="prefetch"><link href="static/js/vab-extra.29abc81b.js" rel="prefetch"><link href="static/css/app.1f390f7c.css" rel="preload" as="style"><link href="static/css/element-ui.0e3a750b.css" rel="preload" as="style"><link href="static/js/app.7b6be8c5.js" rel="preload" as="script"><link href="static/js/element-ui.4e8e0db4.js" rel="preload" as="script"><link href="static/js/vue.11eaebc3.js" rel="preload" as="script"><link href="static/css/element-ui.0e3a750b.css" rel="stylesheet"><link href="static/css/app.1f390f7c.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="xiaofa-admin"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#ffffff"></head><body><noscript></noscript><div id="app"><div class="first-loading-wrp"><div class="loading-wrp"><span class="dot dot-spin"><i></i> <i></i> <i></i> <i></i></span></div><h1>小发同城</h1></div></div><script>if (window.location.hostname !== 'localhost') { }</script><script src="https://mall.gpxscs.cn/admin/config.js?v=2.0.278"></script><script src="https://mall.gpxscs.cn/admin/im/libs3.6.0.min.js?v=2.0.278"></script><script src="https://mall.gpxscs.cn/admin/im/im.js?v=2.0.278"></script><link href="static/css/chunk-19648027.83a11e8c.css" rel="prefetch"><link href="static/css/chunk-982af3b2.2e626fb8.css" rel="prefetch"><link href="static/css/vab-extra.9da8d2d7.css" rel="prefetch"><link href="static/js/chunk-19648027.6233a553.js" rel="prefetch"><link href="static/js/chunk-982af3b2.f27b9204.js" rel="prefetch"><link href="static/js/vab-extra.29abc81b.js" rel="prefetch"><link href="static/css/app.d3766ec2.css" rel="preload" as="style"><link href="static/css/element-ui.0e3a750b.css" rel="preload" as="style"><link href="static/js/app.22ca6c1e.js" rel="preload" as="script"><link href="static/js/element-ui.4e8e0db4.js" rel="preload" as="script"><link href="static/js/vue.11eaebc3.js" rel="preload" as="script"><link href="static/css/element-ui.0e3a750b.css" rel="stylesheet"><link href="static/css/app.d3766ec2.css" rel="stylesheet"><link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png"><link rel="manifest" href="manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="xiaofa-admin"><link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#ffffff"></head><body><noscript></noscript><div id="app"><div class="first-loading-wrp"><div class="loading-wrp"><span class="dot dot-spin"><i></i> <i></i> <i></i> <i></i></span></div><h1>小发同城</h1></div></div><script>if (window.location.hostname !== 'localhost') {
}</script><script src="static/js/element-ui.4e8e0db4.js"></script><script src="static/js/vue.11eaebc3.js"></script><script src="static/js/app.7b6be8c5.js"></script></body></html> }</script><script src="static/js/element-ui.4e8e0db4.js"></script><script src="static/js/vue.11eaebc3.js"></script><script src="static/js/app.22ca6c1e.js"></script></body></html>

View File

@ -2376,7 +2376,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "im/libs3.6.0.min.js" "url": "im/libs3.6.0.min.js"
}, },
{ {
"revision": "cf9154a132f8f4fd10d64989353a912e", "revision": "7ebe59974ea1890f83750ee0d5c03c81",
"url": "index.html" "url": "index.html"
}, },
{ {
@ -2392,16 +2392,16 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "robots.txt" "url": "robots.txt"
}, },
{ {
"revision": "9093e5e0114d448ce863", "revision": "056b89c76cfe8cf92d74",
"url": "static/css/app.1f390f7c.css" "url": "static/css/app.d3766ec2.css"
}, },
{ {
"revision": "f5dd29b853f67685e75d", "revision": "f5dd29b853f67685e75d",
"url": "static/css/chunk-19648027.83a11e8c.css" "url": "static/css/chunk-19648027.83a11e8c.css"
}, },
{ {
"revision": "c620221a39684b688176", "revision": "6984a81f0f371ff13778",
"url": "static/css/chunk-250d32fa.ad6e6718.css" "url": "static/css/chunk-982af3b2.2e626fb8.css"
}, },
{ {
"revision": "7b9212a0410ce12f6058", "revision": "7b9212a0410ce12f6058",
@ -2488,8 +2488,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "static/css/loading.css" "url": "static/css/loading.css"
}, },
{ {
"revision": "12a2a22f0838cb088347", "revision": "7d112341fd53f7fa582e",
"url": "static/css/vab-extra.f3d407d7.css" "url": "static/css/vab-extra.9da8d2d7.css"
}, },
{ {
"revision": "1e254113bacc83eb432837057e7dfeb1", "revision": "1e254113bacc83eb432837057e7dfeb1",
@ -2636,23 +2636,23 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "static/img/xiaofa-logo.20439423.png" "url": "static/img/xiaofa-logo.20439423.png"
}, },
{ {
"revision": "9093e5e0114d448ce863", "revision": "056b89c76cfe8cf92d74",
"url": "static/js/app.7b6be8c5.js" "url": "static/js/app.22ca6c1e.js"
}, },
{ {
"revision": "f5dd29b853f67685e75d", "revision": "f5dd29b853f67685e75d",
"url": "static/js/chunk-19648027.6233a553.js" "url": "static/js/chunk-19648027.6233a553.js"
}, },
{ {
"revision": "c620221a39684b688176", "revision": "6984a81f0f371ff13778",
"url": "static/js/chunk-250d32fa.ee34e965.js" "url": "static/js/chunk-982af3b2.f27b9204.js"
}, },
{ {
"revision": "7b9212a0410ce12f6058", "revision": "7b9212a0410ce12f6058",
"url": "static/js/element-ui.4e8e0db4.js" "url": "static/js/element-ui.4e8e0db4.js"
}, },
{ {
"revision": "12a2a22f0838cb088347", "revision": "7d112341fd53f7fa582e",
"url": "static/js/vab-extra.29abc81b.js" "url": "static/js/vab-extra.29abc81b.js"
}, },
{ {

View File

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js"); importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts( importScripts(
"precache-manifest.5a6aa66371ba042aaeb4213a18385bd8.js" "precache-manifest.9096de45da1fb13ceb1af813c8893e26.js"
); );
workbox.core.setCacheNameDetails({prefix: "xiaofa-admin"}); workbox.core.setCacheNameDetails({prefix: "xiaofa-admin"});

File diff suppressed because one or more lines are too long

View File

@ -1,7 +1,7 @@
/*! /*!
* build: xiaofa-admin * build: xiaofa-admin
* copyright: https://www.lancerdt.com * copyright: https://www.lancerdt.com
* time: 2025-8-2 15:53:06 * time: 2025-9-27 18:25:36
*/ */
/*! /*!
* Quill Editor v1.3.7 * Quill Editor v1.3.7

View File

@ -1,5 +1,5 @@
/*! /*!
* build: xiaofa-admin * build: xiaofa-admin
* copyright: https://www.lancerdt.com * copyright: https://www.lancerdt.com
* time: 2025-8-2 15:53:06 * time: 2025-9-27 18:25:36
*/@media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}} */@media only screen and (max-width:767px){.hidden-xs-only{display:none!important}}@media only screen and (min-width:768px){.hidden-sm-and-up{display:none!important}}@media only screen and (min-width:768px) and (max-width:991px){.hidden-sm-only{display:none!important}}@media only screen and (max-width:991px){.hidden-sm-and-down{display:none!important}}@media only screen and (min-width:992px){.hidden-md-and-up{display:none!important}}@media only screen and (min-width:992px) and (max-width:1199px){.hidden-md-only{display:none!important}}@media only screen and (max-width:1199px){.hidden-md-and-down{display:none!important}}@media only screen and (min-width:1200px){.hidden-lg-and-up{display:none!important}}@media only screen and (min-width:1200px) and (max-width:1919px){.hidden-lg-only{display:none!important}}@media only screen and (max-width:1919px){.hidden-lg-and-down{display:none!important}}@media only screen and (min-width:1920px){.hidden-xl-only{display:none!important}}

View File

@ -1,5 +1,5 @@
/*! /*!
* build: xiaofa-admin * build: xiaofa-admin
* copyright: https://www.lancerdt.com * copyright: https://www.lancerdt.com
* time: 2025-8-2 15:53:06 * time: 2025-9-27 18:25:36
*/[data-v-28093814]:export{menu-color:#fff;menu-color-active:#fff;menu-background:#282c34;column-second-menu-background:#fff}.vab-avatar-list[data-v-28093814] .el-avatar{display:inline-block;margin-left:-15px;cursor:pointer;border:3px solid #fff}.echarts{width:600px;height:400px}[data-v-7f7baab5]:export{menu-color:#fff;menu-color-active:#fff;menu-background:#282c34;column-second-menu-background:#fff}@media only screen and (max-width:767px){[data-v-7f7baab5] .vab-cropper-canvas{display:block;float:none;margin:0 auto}[data-v-7f7baab5] .vab-cropper-preview{display:none}}[data-v-7f7baab5] .el-textarea{margin-top:20px}[data-v-7f7baab5] .el-dialog__footer{height:72px}[data-v-7f7baab5] .el-dialog__footer:before{display:block;clear:both;content:""}[data-v-7f7baab5] .el-dialog__footer>div>div{display:inline}[data-v-7f7baab5] .el-dialog__footer>div>div .el-upload-list{display:none}[data-v-7f7baab5] .el-dialog__footer>div>div .el-upload--picture-card{float:right;width:auto;height:32px;line-height:32px;vertical-align:middle;background-color:transparent;border:0;border-radius:0}[data-v-7f7baab5] .el-dialog__footer>div .el-button{float:right;margin-left:10px}.icon-selector-popper .el-card__body{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;height:20px;cursor:pointer}.icon-selector-popper .el-card__body i{font-size:28px;color:rgba(0,0,0,.65);text-align:center;vertical-align:middle;pointer-events:none;cursor:pointer}.icon-selector-popper .el-pagination{margin:0}[data-v-3aef4cea]:export{menu-color:#fff;menu-color-active:#fff;menu-background:#282c34;column-second-menu-background:#fff}.upload[data-v-3aef4cea]{height:500px}.upload .upload-content .el-upload__tip[data-v-3aef4cea]{display:block;height:30px;line-height:30px}.upload .upload-content[data-v-3aef4cea] .el-upload--picture-card{width:128px;height:128px;margin:3px 8px 8px 8px;border:2px dashed #c0ccda}.upload .upload-content[data-v-3aef4cea] .el-upload-list--picture{margin-bottom:20px}.upload .upload-content[data-v-3aef4cea] .el-upload-list--picture-card .el-upload-list__item{width:128px;height:128px;margin:3px 8px 8px 8px} */[data-v-28093814]:export{menu-color:#fff;menu-color-active:#fff;menu-background:#282c34;column-second-menu-background:#fff}.vab-avatar-list[data-v-28093814] .el-avatar{display:inline-block;margin-left:-15px;cursor:pointer;border:3px solid #fff}.echarts{width:600px;height:400px}[data-v-7f7baab5]:export{menu-color:#fff;menu-color-active:#fff;menu-background:#282c34;column-second-menu-background:#fff}@media only screen and (max-width:767px){[data-v-7f7baab5] .vab-cropper-canvas{display:block;float:none;margin:0 auto}[data-v-7f7baab5] .vab-cropper-preview{display:none}}[data-v-7f7baab5] .el-textarea{margin-top:20px}[data-v-7f7baab5] .el-dialog__footer{height:72px}[data-v-7f7baab5] .el-dialog__footer:before{display:block;clear:both;content:""}[data-v-7f7baab5] .el-dialog__footer>div>div{display:inline}[data-v-7f7baab5] .el-dialog__footer>div>div .el-upload-list{display:none}[data-v-7f7baab5] .el-dialog__footer>div>div .el-upload--picture-card{float:right;width:auto;height:32px;line-height:32px;vertical-align:middle;background-color:transparent;border:0;border-radius:0}[data-v-7f7baab5] .el-dialog__footer>div .el-button{float:right;margin-left:10px}.icon-selector-popper .el-card__body{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;height:20px;cursor:pointer}.icon-selector-popper .el-card__body i{font-size:28px;color:rgba(0,0,0,.65);text-align:center;vertical-align:middle;pointer-events:none;cursor:pointer}.icon-selector-popper .el-pagination{margin:0}[data-v-3aef4cea]:export{menu-color:#fff;menu-color-active:#fff;menu-background:#282c34;column-second-menu-background:#fff}.upload[data-v-3aef4cea]{height:500px}.upload .upload-content .el-upload__tip[data-v-3aef4cea]{display:block;height:30px;line-height:30px}.upload .upload-content[data-v-3aef4cea] .el-upload--picture-card{width:128px;height:128px;margin:3px 8px 8px 8px;border:2px dashed #c0ccda}.upload .upload-content[data-v-3aef4cea] .el-upload-list--picture{margin-bottom:20px}.upload .upload-content[data-v-3aef4cea] .el-upload-list--picture-card .el-upload-list__item{width:128px;height:128px;margin:3px 8px 8px 8px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

24
src/api/baidu/baidu.js Normal file
View File

@ -0,0 +1,24 @@
import request from '@/utils/request'
import { URL } from '@/config'
/**
* 获取百度地图输入点提示词
* @author Seven
* @data 2025-3-5
* @param {
* query: value, //关键词
* region: this.citys[0], //城市名
* city_limit: true, //指定的区域的返回结果
* ret_coordtype: "gcj02ll", //坐标类型 1WGS84ll即GPS经纬度2GCJ02ll即国测局经纬度坐标 3BD09ll即百度经纬度坐标 4BD09mc即百度米制坐标
*}
* @returns { query,region }
* @see https://mall.gpxscs.cn/mobile/shop/merch/baidu/place/v2/suggestion
*/
export function getBaiduSuggestion(params) {
return request({
url: URL.baidu.getBaiduSuggestion,
method: 'get',
params,
})
}

View File

@ -64,3 +64,20 @@ export function getSmsRecord(params) {
params, params,
}) })
} }
export function getReportList(params){
return request({
url:URL.account.base.config.report_list,
method:'get',
params:params
})
}
export function dealReport(data){
return request({
url:URL.account.base.config.deal_report,
method:'post',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
})
}

View File

@ -126,7 +126,23 @@ export async function syncShopImages(data) {
method: 'post', method: 'post',
}) })
} }
export async function getImgList(data) {
data = stringify(data)
return request({
url: `/admin/shop/libraryProduct/list?${data}`,
method:'get',
})
}
export async function saveBatchBarcode(data) {
return request({
url:'/admin/shop/libraryProduct/saveBatch',
method:'put',
data,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
export default { export default {
getProductMapperList, getProductMapperList,
getShopList, getShopList,
@ -141,4 +157,6 @@ export default {
syncProductMaping, syncProductMaping,
importGoodsData, importGoodsData,
syncShopImages, syncShopImages,
getImgList,
saveBatchBarcode
} }

View File

@ -1,23 +1,22 @@
import request from '@/utils/request' import request from '@/utils/request'
import { URL } from '@/config' import { URL } from '@/config'
/** /**
* @name 获取商品分类列表下拉框 * @name 获取商品分类列表下拉框
* @api * @api
* @returns { * @returns {
* "id": 256, 1级分类ID * "id": 256, 1级分类ID
* "label": "安防监控", 分类名称 * "label": "安防监控", 分类名称
* "children": [ 二级分类数组 * "children": [ 二级分类数组
* { * {
* "id": 257, 二级分类ID * "id": 257, 二级分类ID
* "label": "室内摄像机", 分类名称 * "label": "室内摄像机", 分类名称
* "children": [], 三级分类数组 * "children": [], 三级分类数组
* "pid": null * "pid": null
* }, * },
* ], * ],
* "pid": null * "pid": null
*} *}
*/ */
export function getList(params) { export function getList(params) {
@ -28,8 +27,6 @@ export function getList(params) {
}) })
} }
export function doEdit(data) { export function doEdit(data) {
return request({ return request({
url: URL.shop.product.base.doEdit, url: URL.shop.product.base.doEdit,

View File

@ -126,3 +126,32 @@ export function createSubAccount(params) {
data: params, data: params,
}) })
} }
/**
* @name 检查店铺名字
* @api api_url + '/admin/shop/esign/signed/contract/file'
* @param { mchMobile }
* @returns
*/
export function checkShopName(params) {
return request({
url: URL.shop.merch.checkShopName,
method: 'post',
headers: {
'Content-Type': 'application/json',
},
data: params,
})
}
//编辑用户信息
export function updateUserInfo(data){
return request({
url: URL.shop.merch.update,
method: 'post',
headers: {
'Content-Type': 'application/json',
},
data: data,
})
}

33
src/api/upload.js Normal file
View File

@ -0,0 +1,33 @@
import request from '@/utils/request'
import { URL } from '@/config'
export function batchNoApi(file, type) {
const formData = new FormData()
formData.append('upfile', file)
formData.append('imgType', type)
return new Promise((resolve, reject) => {
request({
url: URL.ocr.batchNoApi,
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
})
.then((res) => {
resolve(res)
})
.catch((e) => reject(console.warn(e)))
})
}
export function imgOcrResultApi(data) {
return request({
url: URL.ocr.imgOcrResultApi,
method: 'POST',
data,
headers: {
'Content-Type': 'multipart/form-data',
},
})
}

View File

@ -29,6 +29,14 @@ let url = {
disableUpdateShopApp: disableUpdateShopApp:
api_url + '/admin/admin/app-market-update/enable-or-disable', api_url + '/admin/admin/app-market-update/enable-or-disable',
}, },
baidu: {
getBaiduSuggestion:
api_url + '/mobile/shop/merch/baidu/place/v2/suggestion',
},
ocr: {
batchNoApi: api_url + '/mobile/shop/lakala/tk/uploadOcrImg',
imgOcrResultApi: api_url + '/mobile/shop/lakala/tk/imgOcrResult',
},
orderPush: { orderPush: {
orderPushTest: api_url + '/mobile/account/login/push/testcase', orderPushTest: api_url + '/mobile/account/login/push/testcase',
}, },
@ -255,6 +263,8 @@ let url = {
editLanguage: editLanguage:
api_url + '/admin/account/account-base-config/editLanguage', api_url + '/admin/account/account-base-config/editLanguage',
cleanCache: api_url + '/admin/account/account-base-config/cleanCache', cleanCache: api_url + '/admin/account/account-base-config/cleanCache',
report_list: api_url + '/admin/sns/snsUserReport/list',
deal_report: '/admin/sns/snsUserReport/dealReport',
}, },
user: { user: {
@ -437,6 +447,8 @@ let url = {
merchDetail: api_url + '/admin/shop/merch/detail', merchDetail: api_url + '/admin/shop/merch/detail',
// 审批商家申请入驻 // 审批商家申请入驻
merchApproval: api_url + '/admin/shop/merch/approval', merchApproval: api_url + '/admin/shop/merch/approval',
//更新商家信息
update:api_url + '/admin/shop/merch/update',
// 发起合同签署 // 发起合同签署
createByFile: api_url + '/admin/shop/esign/sign-flow/create-by-file', createByFile: api_url + '/admin/shop/esign/sign-flow/create-by-file',
//查看商家签署合同 //查看商家签署合同
@ -445,6 +457,8 @@ let url = {
createShop: api_url + '/admin/shop/shop-store-base/mchinfo/to/storeinfo', createShop: api_url + '/admin/shop/shop-store-base/mchinfo/to/storeinfo',
//创建分账(补偿机制) //创建分账(补偿机制)
createSubAccount: api_url + '/mobile/pay/lakala/ledger/applyLedgerMer', createSubAccount: api_url + '/mobile/pay/lakala/ledger/applyLedgerMer',
//检查店铺名字可用不
checkShopName: api_url + '/mobile/shop/store/check-store-name-exists',
}, },
activity: { activity: {
lottery: { lottery: {

87
src/utils/file.js Normal file
View File

@ -0,0 +1,87 @@
export async function compressImage(file, options = {}) {
// 提取选项参数,设置默认值
const {
quality = 0.75,
maxWidth = 800,
maxHeight = 600
} = options;
// 验证输入是否为有效的File对象
if (!(file instanceof File)) {
throw new Error('输入参数必须是一个File对象');
}
// 检查文件类型是否为图片
if (!file.type.startsWith('image/')) {
throw new Error('输入文件必须是图片类型');
}
// 创建一个Promise来处理异步操作
return new Promise((resolve, reject) => {
// 创建Image对象用于加载图片
const img = new Image();
// 监听图片加载完成事件
img.onload = () => {
// 创建Canvas元素
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 获取原始图片尺寸
let width = img.width;
let height = img.height;
// 计算调整后的尺寸(保持原始比例)
if (width > maxWidth) {
height = height * (maxWidth / width);
width = maxWidth;
}
if (height > maxHeight) {
width = width * (maxHeight / height);
height = maxHeight;
}
// 设置Canvas尺寸
canvas.width = width;
canvas.height = height;
// 在Canvas上绘制图片
ctx.drawImage(img, 0, 0, width, height);
// 将Canvas内容转换为Blob对象
canvas.toBlob(
(blob) => {
if (!blob) {
reject(new Error('图片转换失败'));
return;
}
// 创建新的File对象
const compressedFile = new File(
[blob],
file.name,
{ type: blob.type, lastModified: Date.now() }
);
resolve(compressedFile);
},
file.type, // 使用原始图片类型
quality // 压缩质量
);
};
// 监听图片加载错误事件
img.onerror = () => reject(new Error('图片加载失败'));
// 读取图片数据
const reader = new FileReader();
reader.onload = () => {
img.src = reader.result;
};
reader.onerror = () => reject(new Error('文件读取失败'));
// 开始读取文件
reader.readAsDataURL(file);
});
}

View File

@ -59,7 +59,6 @@ const requestConf = (config) => {
let source_lang_json = JSON.parse(value) let source_lang_json = JSON.parse(value)
source_lang = source_lang_json.language source_lang = source_lang_json.language
} }
config.params['source_lang'] = source_lang config.params['source_lang'] = source_lang
const token = store.getters['user/token'] const token = store.getters['user/token']

View File

@ -30,7 +30,22 @@ export function convertRouter(asyncRoutes) {
route.children.push(obj) route.children.push(obj)
} }
if (route.meta.title == '商品' && route.name == 'Vab320') {
const obj = {
path: '/goodsImgs',
component: '@/views/product/goodsImg/goodsImgs',
name: 'Vab88000',
redirect: null,
meta: {
title: '商品图库管理',
icon: '',
noClosable: 0,
hidden: null,
},
menuHidden: false,
}
route.children.push(obj)
}
if (route.meta.title == '店铺' && route.name == 'Vab330') { if (route.meta.title == '店铺' && route.name == 'Vab330') {
const obj = { const obj = {
path: '/storeConf', path: '/storeConf',
@ -84,7 +99,23 @@ export function convertRouter(asyncRoutes) {
route.children.push(obj) route.children.push(obj)
} }
if (route.meta.title == '设置') {
const obj = {
path: '/report',
component: '@/views/settings/config/report.vue',
name: 'Vab86001',
redirect: null,
meta: {
title: '举报中心',
icon: '',
noClosable: 0,
hidden: null,
},
menuHidden: false,
}
route.children.push(obj)
}
if (route.meta.title == '基础') { if (route.meta.title == '基础') {
const cloudPrintRoute = { const cloudPrintRoute = {
path: '/cloudPrint', path: '/cloudPrint',

View File

@ -44,6 +44,11 @@
v-model="form.crontab_minute" v-model="form.crontab_minute"
:placeholder="__('每分')" :placeholder="__('每分')"
:style="{ width: '100%' }" :style="{ width: '100%' }"
filterable
allow-create
@blur="handleSelectBlur('crontab_minute')"
@input="handleSelectInput($event, 'crontab_minute')"
ref="minuteSelect"
> >
<el-option <el-option
v-for="item in crontab_minute_opt" v-for="item in crontab_minute_opt"
@ -60,6 +65,11 @@
v-model="form.crontab_hour" v-model="form.crontab_hour"
:placeholder="__('小时')" :placeholder="__('小时')"
:style="{ width: '100%' }" :style="{ width: '100%' }"
filterable
allow-create
@blur="handleSelectBlur('crontab_hour')"
@input="handleSelectInput($event, 'crontab_hour')"
ref="hourSelect"
> >
<el-option <el-option
v-for="item in crontab_hour_opt" v-for="item in crontab_hour_opt"
@ -78,6 +88,11 @@
v-model="form.crontab_day" v-model="form.crontab_day"
:placeholder="__('每天')" :placeholder="__('每天')"
:style="{ width: '100%' }" :style="{ width: '100%' }"
filterable
allow-create
@blur="handleSelectBlur('crontab_day')"
@input="handleSelectInput($event, 'crontab_day')"
ref="daySelect"
> >
<el-option <el-option
v-for="item in crontab_day_opt" v-for="item in crontab_day_opt"
@ -94,13 +109,18 @@
v-model="form.crontab_month" v-model="form.crontab_month"
:placeholder="__('每月')" :placeholder="__('每月')"
:style="{ width: '100%' }" :style="{ width: '100%' }"
filterable
allow-create
@blur="handleSelectBlur('crontab_month')"
@input="handleSelectInput($event, 'crontab_month')"
ref="monthSelect"
> >
<el-option <el-option
v-for="item in crontab_month_opt" v-for="item in crontab_month_opt"
:key="item.value" :key="item.value"
:label="item.label" :label="item.label"
:value="item.value" :value="item.value"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -112,6 +132,11 @@
v-model="form.crontab_week" v-model="form.crontab_week"
:placeholder="__('每周')" :placeholder="__('每周')"
:style="{ width: '100%' }" :style="{ width: '100%' }"
filterable
allow-create
@input="handleSelectInput($event, 'crontab_week')"
@blur="handleSelectBlur('crontab_week')"
ref="weekSelect"
> >
<el-option <el-option
v-for="item in crontab_week_opt" v-for="item in crontab_week_opt"
@ -239,6 +264,37 @@
} }
this.crontab_month_opt = crontab_month_opt this.crontab_month_opt = crontab_month_opt
}, },
handleSelectInput(value, field) {
// value null
if (value === null) {
//
const inputValue = this.$refs[`${field}Select`].$refs.input.value;
if (inputValue) {
this.$set(this.form, field, inputValue.trim());
}
} else {
this.$set(this.form, field, value);
}
},
// -
handleSelectBlur(field) {
const selectComp = this.$refs[`${field}Select`];
if (selectComp) {
const inputValue = selectComp.$refs.input.value.trim();
const currentValue = this.form[field];
//
if (inputValue && inputValue !== currentValue) {
this.$set(this.form, field, inputValue);
//
selectComp.currentValue = inputValue;
selectComp.selectedLabel = inputValue;
selectComp.$forceUpdate();
}
}
},
initHour() { initHour() {
let crontab_hour_opt = [] let crontab_hour_opt = []
crontab_hour_opt.push({ crontab_hour_opt.push({

View File

@ -675,6 +675,7 @@
return item.spec_id return item.spec_id
}) })
.join(',') .join(',')
}, },
// //
brandDefaultSelected(brandIds) { brandDefaultSelected(brandIds) {

View File

@ -361,6 +361,7 @@ export default {
// //
getSummaries(param) { getSummaries(param) {
const { columns, data } = param const { columns, data } = param
const sums = [] const sums = []
sums[0] = '申请总额' sums[0] = '申请总额'
columns.forEach((column, index) => { columns.forEach((column, index) => {

View File

@ -1295,7 +1295,6 @@
} }
const unitIndex = this.virtualHead.findIndex(item => item.property === 'item_unit_price'); const unitIndex = this.virtualHead.findIndex(item => item.property === 'item_unit_price');
if (unitIndex !== -1) { if (unitIndex !== -1) {
this.virtualHead[unitIndex].label = this.__('成本价'); this.virtualHead[unitIndex].label = this.__('成本价');
@ -1766,6 +1765,7 @@
this.row = Object.assign({}, row) this.row = Object.assign({}, row)
if (row != null) { if (row != null) {
this.categoryId = row.category_id this.categoryId = row.category_id
this.productForm.category_id = row.category_id this.productForm.category_id = row.category_id
this.productForm.text_category_id = row.category_name this.productForm.text_category_id = row.category_name
@ -1888,6 +1888,7 @@
productSpec: this.getProductSpec(), productSpec: this.getProductSpec(),
valid: validForm ? validForm : '', valid: validForm ? validForm : '',
} }
const specData = this.specData const specData = this.specData
for (let i = 0; i < specData.length; i++) { for (let i = 0; i < specData.length; i++) {
if (specData[i].item_unit_price <= 0 && specData[i].item_unit_points <= 0) { if (specData[i].item_unit_price <= 0 && specData[i].item_unit_points <= 0) {
@ -1967,15 +1968,18 @@
}, },
async getType(type_id) { async getType(type_id) {
const { data } = await get({ type_id: type_id }) const { data } = await get({ type_id: type_id })
this.brands = data.brands if(data){
this.assists = data.assists this.brands = data.brands
this.specs = data.specs this.assists = data.assists
this.specs = data.specs
// imagespec // imagespec
// this.initSpecImageList() // this.initSpecImageList()
// vue // vue
this.initAssistArr(this.assists) this.initAssistArr(this.assists)
this.initCheckSpecArr(this.specs) this.initCheckSpecArr(this.specs)
}
}, },
// vue assistArr // vue assistArr
initAssistArr(assists) { initAssistArr(assists) {

View File

@ -55,7 +55,7 @@
<el-input <el-input
v-model.trim="queryForm.product_id" v-model.trim="queryForm.product_id"
clearable clearable
:placeholder="__('品编号')" :placeholder="__('品编号')"
style="width: 100px" style="width: 100px"
/> />
</el-form-item> </el-form-item>
@ -68,12 +68,19 @@
/> />
</el-form-item> </el-form-item>
<el-form-item prop="store_name"> <el-form-item prop="store_name">
<el-input <el-select
v-model.trim="queryForm.store_name" v-model="queryForm.store_id"
clearable clearable
:placeholder="__('所属店铺')" filterable
style="width: 100px" placeholder="选择店铺"
/> >
<el-option
v-for="item in shopList"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"
/>
</el-select>
</el-form-item> </el-form-item>
<el-form-item prop="brand_id"> <el-form-item prop="brand_id">
<el-select <el-select
@ -98,7 +105,7 @@
<el-select <el-select
v-model.trim="queryForm.product_state_id" v-model.trim="queryForm.product_state_id"
clearable clearable
:placeholder="__('品状态')" :placeholder="__('品状态')"
style="width: 100px" style="width: 100px"
> >
<el-option <el-option
@ -230,7 +237,7 @@
<el-table-column <el-table-column
align="center" align="center"
fixed="left" fixed="left"
:label="__('品编号')" :label="__('品编号')"
prop="product_id" prop="product_id"
/> />
<el-table-column <el-table-column
@ -255,7 +262,7 @@
</el-table-column> </el-table-column>
<el-table-column <el-table-column
align="center" align="center"
:label="__('品名称')" :label="__('品名称')"
prop="product_name" prop="product_name"
show-overflow-tooltip show-overflow-tooltip
width="200" width="200"
@ -467,7 +474,7 @@ import {
import Table from './components/ProductItemTable' import Table from './components/ProductItemTable'
import StockTable from './components/OutOfStockTable' import StockTable from './components/OutOfStockTable'
import ReviewEdit from './components/ReviewEdit' import ReviewEdit from './components/ReviewEdit'
import GoodsToolApi from '@/api/goodsTool'
export default { export default {
name: 'ProductManagement', name: 'ProductManagement',
components: { TreeSelect, Table, StockTable, ReviewEdit }, components: { TreeSelect, Table, StockTable, ReviewEdit },
@ -526,7 +533,7 @@ import {
label:"是" label:"是"
} }
], ],
data: [], // data: [], //
defaultProps: { defaultProps: {
children: 'children', children: 'children',
label: 'label', label: 'label',
@ -554,6 +561,7 @@ import {
} }
this.queryData() this.queryData()
this.getTree() this.getTree()
this.getShopList()
}, },
methods: { methods: {
__, __,
@ -563,6 +571,10 @@ import {
outOfStock(row) { outOfStock(row) {
this.$refs['StockTable'].showTable(row) this.$refs['StockTable'].showTable(row)
}, },
async getShopList() {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items
},
reviewEdit(row) { reviewEdit(row) {
this.$refs['ReviewEdit'].showTable(row) this.$refs['ReviewEdit'].showTable(row)
}, },
@ -642,22 +654,30 @@ import {
async review(product_state_id) { async review(product_state_id) {
if (this.selectRows.length > 0) { if (this.selectRows.length > 0) {
let product_id = this.selectRows.map((item) => item.product_id) let product_id = this.selectRows.map((item) => item.product_id);
// 1
this.listLoading = true; //
// 2
await new Promise(resolve => setTimeout(resolve, 11000));
// 3
const { msg, status } = await editState({ const { msg, status } = await editState({
product_state_id: product_state_id, product_state_id: product_state_id,
product_id: product_id.toString(), product_id: product_id.toString(),
}) });
if (200 == status) { if (200 == status) {
this.$baseMessage(msg, 'success') this.$baseMessage(msg, 'success');
} else { } else {
this.$baseMessage(msg, 'error') this.$baseMessage(msg, 'error');
} }
await this.fetchData();
await this.fetchData() this.listLoading = false; //
} else { } else {
this.$baseMessage(this.__('未选中任何行'), 'error') this.$baseMessage(this.__('未选中任何行'), 'error');
return false return false;
} }
}, },

View File

@ -0,0 +1,177 @@
<template>
<el-dialog
title="批量编辑商品编码"
:visible.sync="visible"
width="600px"
:close-on-click-modal="false"
:max-height="600"
@close="handleClose"
>
<div class="batch-tip">
共选中
{{ selectedData.length }} 个商品请为每个商品设置编码仅填写需要修改的
</div>
<!-- 批量编辑列表 -->
<div
class="edit-list"
style="max-height: 400px; overflow-y: auto; padding-right: 10px"
>
<el-form ref="batchForm" :model="formData" label-width="100px">
<!-- 循环渲染每个选中的商品 -->
<div
v-for="(item, index) in formData.items"
:key="item.id"
class="edit-item"
>
<el-form-item label="商品ID" :label-width="100">
<div class="product-id">{{ item.id }}</div>
</el-form-item>
<el-form-item label="商品名称" :label-width="100">
<div class="product-name">{{ item.name }}</div>
</el-form-item>
<el-form-item
label="商品编码"
:prop="`items[${index}].barcode`"
:rules="[
{ required: false, message: '请输入商品编码', trigger: 'blur' },
]"
:label-width="100"
>
<el-input
v-model="item.barcode"
placeholder="请输入商品编码"
clearable
maxlength="50"
/>
</el-form-item>
<el-divider v-if="index !== formData.items.length - 1" />
</div>
</el-form>
</div>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交修改</el-button>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
export default {
data() {
return {
visible: false,
selectedData: [], //
formData: {
items: [], //
},
}
},
methods: {
//
open(selection) {
this.visible = true
this.selectedData = [...selection]
//
this.formData.items = this.selectedData.map((item) => ({
id: item.id,
name: item.name || '未知名称',
barcode: item.barcode || '', //
}))
//
this.$nextTick(() => {
if (this.$refs.batchForm) {
this.$refs.batchForm.clearValidate()
}
})
},
handleClose() {
this.visible = false
this.selectedData = []
this.formData.items = []
if (this.$refs.batchForm) {
this.$refs.batchForm.clearValidate()
}
},
//
async handleSubmit() {
//
try {
await this.$refs.batchForm.validate()
} catch (error) {
return false
}
// -
const submitData = this.formData.items
.filter((item) => item.barcode.trim() !== '') //
.map((item) => ({
id: item.id,
name: item.name,
barcode: item.barcode.trim(),
}))
if (submitData.length === 0) {
this.$message.warning('请至少填写一个商品编码')
return
}
try {
//
const res = await GoodsToolApi.saveBatchBarcode(submitData)
console.log('批量修改结果:', res)
this.$message.success(`成功修改 ${submitData.length} 个商品编码`)
this.handleClose()
this.$emit('success') //
} catch (error) {
console.error('批量修改失败:', error)
this.$message.error('修改失败,请重试')
}
},
},
}
</script>
<style lang="scss" scoped>
.batch-tip {
color: #666;
padding: 8px 12px;
background-color: #f5f7fa;
border-radius: 4px;
margin-bottom: 16px;
font-size: 13px;
border-left: 3px solid #409eff;
}
.edit-item {
padding: 10px 0;
}
.product-name {
line-height: 32px;
color: #333;
max-width: 400px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.product-id {
line-height: 32px;
color: #666;
font-size: 13px;
}
::v-deep .el-form-item {
margin-bottom: 10px;
}
</style>

View File

@ -0,0 +1,158 @@
<template>
<el-dialog
:title="dialogTitle"
:visible.sync="visible"
width="600px"
:close-on-click-modal="false"
@close="handleClose"
>
<el-form
ref="editForm"
:model="formData"
:rules="formRules"
label-width="120px"
class="edit-form"
>
<el-form-item label="商品id" prop="id">
<el-input
v-model="formData.id"
placeholder="请输入商品id"
disabled
/>
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input
v-model="formData.name"
placeholder="请输入商品名称"
disabled
/>
</el-form-item>
<el-form-item label="商品编码" prop="barcode">
<el-input
v-model="formData.barcode"
placeholder="请输入商品编码"
clearable
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
export default {
data() {
return {
visible: false,
isBatch: false, //
selectedData: [], //
formData: {
id: '',
barcode: '',
name: ''
},
formRules: {
barcode: [
{ required: false, message: '请输入商品编码', trigger: 'blur' }
],
}
}
},
computed: {
//
dialogTitle() {
return this.isBatch ? '批量编辑商品' : '编辑商品'
},
//
selectedCount() {
return this.selectedData.length
}
},
methods: {
//
open(options) {
this.isBatch = options.isBatch
this.selectedData = options.data instanceof Array ? options.data : [options.data]
this.visible = true
//
if (!this.isBatch && this.selectedData.length > 0) {
const item = this.selectedData[0]
this.formData = {
name: item.name || '',
barcode: item.barcode || '',
id: item.id || ''
}
} else {
//
this.formData = {
name: '',
barcode: '',
id: ''
}
}
//
this.$nextTick(() => {
this.$refs.editForm.clearValidate()
})
},
//
handleClose() {
this.visible = false
this.$refs.editForm.clearValidate()
},
//
async handleSubmit() {
//
try {
await this.$refs.editForm.validate()
} catch (error) {
return false
}
// -
const updateData = this.selectedData.map(item => {
const data = { id: item.id }
if (this.formData.barcode !== '') data.barcode = this.formData.barcode
return data
})
try {
let res=await GoodsToolApi.saveBatchBarcode(updateData)
this.$message.success(this.isBatch ? '批量编辑成功' : '编辑成功')
this.visible = false
//
this.$emit('success')
} catch (error) {
console.error('编辑商品失败:', error)
this.$message.error('编辑失败,请重试')
}
}
}
}
</script>
<style lang="scss" scoped>
.edit-form {
margin-top: 20px;
}
.batch-tip {
color: #666;
padding: 8px 12px;
background-color: #f5f7fa;
border-radius: 4px;
margin-bottom: 20px;
font-size: 13px;
border-left: 3px solid #409eff;
}
</style>

View File

@ -0,0 +1,351 @@
<template>
<div class="imgs-container">
<div class="filter">
<el-input
v-model="filter.name"
class="input_item"
clearable
placeholder="请输入商品名字"
prefix-icon="el-icon-search"
/>
<el-input
v-model="filter.barcode"
class="input_item"
clearable
placeholder="请输入商品条码"
prefix-icon="el-icon-barcode"
/>
<el-select
v-model="filter.barcodeEmty"
clearable
placeholder="条码是否为空"
class="input_item"
>
<el-option label="无条码" value="yes" />
<el-option label="有条码" value="no" />
</el-select>
<el-button size="medium" type="primary" @click="handleSearch">
查询
</el-button>
<el-button size="medium" @click="handleReset">重置</el-button>
<el-button size="medium" type="warning" @click="handleBatchEdit">
批量编辑
</el-button>
</div>
<div class="list">
<el-table
ref="imgTable"
:data="tableData"
style="width: 100%"
border
stripe
:empty-text="tableData.length === 0 ? '暂无商品数据' : '加载中...'"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<!-- 操作列编辑 + 新增保存按钮 -->
<el-table-column label="操作" width="100">
<template #default="scope">
<!-- 保存按钮点击提交当前行条码修改 -->
<el-button
plain
size="mini"
type="success"
@click="handleSaveSingle(scope.row)"
:loading="scope.row.saveLoading"
>
保存
</el-button>
</template>
</el-table-column>
<el-table-column label="商品条码" width="180">
<template #default="scope">
<el-input
v-model="scope.row.barcode"
placeholder="请输入商品条码"
clearable
size="small"
class="barcode-input"
:disabled="scope.row.saveLoading"
@change="handleBarcodeChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="商品主图" width="180px">
<template #default="scope">
<div v-if="scope.row.thumb" class="img-group">
<el-image
:src="scope.row.thumb"
:preview-src-list="[scope.row.thumb]"
class="img-thumb"
fit="cover"
:title="scope.row.name || '商品主图'"
/>
</div>
<span v-else class="no-data">无主图</span>
</template>
</el-table-column>
<el-table-column label="商品名称" prop="name" width="300">
<template #default="scope">
<div class="text-ellipsis" :title="scope.row.name">
{{ scope.row.name }}
</div>
</template>
</el-table-column>
<el-table-column label="商品id" prop="id" width="120px" />
<el-table-column label="分类" prop="category" width="120" />
<el-table-column label="价格" width="120">
<template #default="scope">
{{ Number(scope.row.price).toFixed(2) }}
</template>
</el-table-column>
<el-table-column label="创建时间" prop="createdAt" width="180" />
<el-table-column label="更新时间" prop="updatedAt" width="180" />
</el-table>
<el-pagination
background
:current-page="pagination.pageNum"
:page-size="pagination.pageSize"
:total="pagination.total"
layout="total, sizes, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
:disabled="pagination.total === 0"
/>
</div>
<batchEditBarcode ref="batchEditBarcode" @success="handleEditSuccess" />
</div>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
import batchEditBarcode from './batchEditBarcode.vue'
export default {
data() {
return {
tableData: [],
pagination: {
pageNum: 1,
pageSize: 10,
total: 0,
pages: 0,
},
filter: {
name: '',
barcode: '',
barcodeEmty: '', // : 'yes', 'no'
},
multipleSelection: [],
}
},
components: {
batchEditBarcode,
},
mounted() {
this.handleImgList()
},
methods: {
//
async handleImgList() {
try {
const params = {
pageNum: this.pagination.pageNum,
pageSize: this.pagination.pageSize,
...(this.filter.name && { name: this.filter.name }),
...(this.filter.barcode && { barcode: this.filter.barcode }),
...(this.filter.barcodeEmty && {
barcodeEmty: this.filter.barcodeEmty,
}),
}
const res = await GoodsToolApi.getImgList(params)
//
this.tableData = (res.records || []).map(item => ({
...item,
saveLoading: false, //
originalBarcode: item.barcode, //
}))
this.pagination.total = res.total || 0
this.pagination.pages = res.pages || 0
this.pagination.current = res.current || 1
console.log('商品列表数据获取成功', {
tableData: this.tableData,
pagination: this.pagination,
params: params,
})
} catch (error) {
this.$message.error('获取数据失败,请重试')
this.tableData = []
}
},
//
handleSearch() {
this.pagination.pageNum = 1
this.handleImgList()
},
//
handleReset() {
this.filter = {
name: '',
barcode: '',
barcodeEmty: '',
}
this.pagination.pageNum = 1
this.handleImgList()
},
//
handleCurrentChange(pageNum) {
this.pagination.pageNum = pageNum
this.handleImgList()
},
handleSizeChange(pageSize) {
this.pagination.pageSize = pageSize
this.pagination.pageNum = 1
this.handleImgList()
},
//
handleSelectionChange(val) {
this.multipleSelection = val
},
//
handleBatchEdit() {
if (this.multipleSelection.length === 0) {
this.$message.warning('请先选中需要编辑的商品');
return;
}
this.$refs.batchEditBarcode.open(this.multipleSelection);
},
//
async handleSaveSingle(row) {
//
if (row.barcode === row.originalBarcode) {
this.$message.info('商品条码未修改,无需保存');
return;
}
//
if (row.barcode && row.barcode.length > 50) {
this.$message.warning('商品条码长度不能超过50个字符');
return;
}
try {
//
row.saveLoading = true;
//
const updateData = [{
id: row.id,
barcode: row.barcode || '', //
}];
let res=await GoodsToolApi.saveBatchBarcode(updateData);
if(res.status==200){
//
row.originalBarcode = row.barcode;
this.$message.success('商品条码保存成功');
}else{
this.$message.error('操作异常');
}
} catch (error) {
//
row.barcode = row.originalBarcode;
this.$message.error('保存失败,请重试');
console.error('单条保存条码失败:', error);
} finally {
//
row.saveLoading = false;
}
},
handleBarcodeChange(row) {
if (row.barcode && row.barcode.length > 50) {
this.$message.warning('商品条码长度不能超过50个字符');
}
},
//
handleEditSuccess() {
this.handleImgList();
this.$refs.imgTable.clearSelection();
this.multipleSelection = [];
},
},
}
</script>
<style lang="scss" scoped>
.filter {
display: flex;
align-items: center;
padding: 15px 20px;
margin-bottom: 15px;
gap: 10px;
border-radius: 5px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.1);
background: #fff;
flex-wrap: wrap;
}
.input_item {
width: 200px;
}
.list {
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
border-radius: 5px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.1);
}
.img-group {
position: relative;
display: inline-block;
}
.img-thumb {
width: 50px;
height: 50px;
cursor: pointer;
border-radius: 3px;
}
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.no-data {
color: #999;
font-size: 12px;
}
// 使
.barcode-input {
width: 100%;
}
::v-deep .el-table__cell {
vertical-align: middle !important;
}
::v-deep .el-pagination {
margin-top: 10px;
text-align: right;
}
</style>

View File

@ -137,12 +137,7 @@
<el-checkbox <el-checkbox
v-for="item in spec.specItems" v-for="item in spec.specItems"
:key="item.spec_item_id" :key="item.spec_item_id"
:disabled="
!configs.config.product_spec_edit &&
((newSpecItemIdRow.indexOf(item.spec_item_id) > -1 &&
checkSpec[spec.spec_id].indexOf(item) > -1) ||
!(allowCheckSpecRow.indexOf(spec.spec_id) > -1))
"
:label="item" :label="item"
> >
{{ item.spec_item_name }} {{ item.spec_item_name }}
@ -607,7 +602,7 @@
:model="productForm" :model="productForm"
size="medium" size="medium"
> >
<el-col :span="12"> <!-- <el-col :span="12">
<el-form-item :label="__('运费设置')" prop="transport_type_id"> <el-form-item :label="__('运费设置')" prop="transport_type_id">
<el-select <el-select
v-model="productForm.transport_type_id" v-model="productForm.transport_type_id"
@ -623,7 +618,7 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col> -->
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="__('购买上限')" prop="product_buy_limit"> <el-form-item :label="__('购买上限')" prop="product_buy_limit">
<el-input <el-input
@ -1387,6 +1382,7 @@ export default {
let checkSpecElement = checkSpec[spec_id] let checkSpecElement = checkSpec[spec_id]
return checkSpecElement.length > 0 && spec.spec_format === 'image' return checkSpecElement.length > 0 && spec.spec_format === 'image'
}) })
return sp ? sp.spec_id : null return sp ? sp.spec_id : null
}, },
// //
@ -1953,6 +1949,7 @@ export default {
const strSpecs = this.shop_product_info const strSpecs = this.shop_product_info
? this.shop_product_info.product_spec ? this.shop_product_info.product_spec
: '{}' : '{}'
const specs = JSON.parse(strSpecs) const specs = JSON.parse(strSpecs)
console.info('+++++++++++++++++') console.info('+++++++++++++++++')
console.info(specs) console.info(specs)
@ -1966,11 +1963,13 @@ export default {
const items = this.getSpecItemByIds(spec_id, idsArr) const items = this.getSpecItemByIds(spec_id, idsArr)
// spec // spec
this.$set(this.checkSpec, spec_id, items) this.$set(this.checkSpec, spec_id, items)
console.info(items) console.info(items)
items.forEach((item, index) => { items.forEach((item, index) => {
that.newSpecItemIdRow.push(item.spec_item_id) that.newSpecItemIdRow.push(item.spec_item_id)
}) })
}) })
}, },
initTableHead() { initTableHead() {
const checkSpec = this.checkSpec const checkSpec = this.checkSpec
@ -2318,14 +2317,17 @@ export default {
}, },
async getType(type_id, productId) { async getType(type_id, productId) {
const { data } = await get({ type_id: type_id, product_id: productId }) const { data } = await get({ type_id: type_id, product_id: productId })
this.brands = data.brands if(data){
this.assists = data.assists this.brands = data.brands
this.specs = data.specs this.assists = data.assists
// imagespec this.specs = data.specs
// this.initSpecImageList() // imagespec
// // vue // this.initSpecImageList()
this.initAssistArr(this.assists) // // vue
this.initCheckSpecArr(this.specs) this.initAssistArr(this.assists)
this.initCheckSpecArr(this.specs)
}
}, },
// vue assistArr // vue assistArr
initAssistArr(assists) { initAssistArr(assists) {

View File

@ -0,0 +1,363 @@
<template>
<div class="report_container">
<div class="filter">
<el-input
v-model="filter.reporterNickname"
class="input_item"
clearable
placeholder="请输入举报人昵称"
prefix-icon="el-icon-search"
/>
<el-select
v-model="filter.processingStatus"
clearable
filterable
placeholder="选择处理状态"
>
<el-option
v-for="item in reportList"
:key="item.report_id"
:label="item.report_name"
:value="item.report_id"
/>
</el-select>
<el-button size="medium" type="primary" @click="handleSearch">
查询
</el-button>
</div>
<div class="list">
<el-table
ref="reportTable"
:data="tableData"
style="width: 100%"
border
stripe
>
<el-table-column label="举报正文" prop="reportContent" width="180px">
<template #default="scope">
<div class="text-ellipsis" :title="scope.row.reportContent">
{{ scope.row.reportContent }}
</div>
</template>
</el-table-column>
<el-table-column label="举报凭证" prop="reportVerify" width="180px">
<template #default="scope">
<div
v-if="scope.row.reportVerify && scope.row.reportVerify.length"
class="img-group"
>
<el-image
v-for="(img, index) in scope.row.reportVerify"
:key="index"
:src="img.url"
:preview-src-list="scope.row.reportVerify.map((item) => item.url)"
class="img-thumb"
fit="cover"
:title="img.description || `举报凭证${index + 1}`"
v-if="index < 3"
/>
<span v-if="scope.row.reportVerify.length > 3" class="img-count">
+{{ scope.row.reportVerify.length - 3 }}
</span>
</div>
<span v-else class="no-data">无凭证</span>
</template>
</el-table-column>
<el-table-column label="举报人昵称" prop="reporterNickname" width="120" />
<el-table-column
label="举报人手机号"
prop="reporterPhone"
width="130"
/>
<el-table-column label="被举报人昵称" prop="reportedNickname" width="120" />
<el-table-column label="被举报人用户ID" prop="reportedUserId" width="140" />
<el-table-column label="举报时间" prop="reporterTime" width="180" />
<el-table-column label="状态" prop="processingStatus" width="110">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.processingStatus)">
{{ getStatusText(scope.row.processingStatus) }}
</el-tag>
</template>
</el-table-column>
<!-- 处理凭证后台上传 -->
<el-table-column label="处理凭证" prop="processingVerify" width="180px">
<template #default="scope">
<div
v-if="scope.row.processingVerify && scope.row.processingVerify.length"
class="img-group"
>
<el-image
v-for="(img, index) in scope.row.processingVerify"
:key="index"
:src="img.url"
:preview-src-list="scope.row.processingVerify.map((item) => item.url)"
class="img-thumb"
fit="cover"
:title="img.description || `处理凭证${index + 1}`"
v-if="index < 3"
/>
<span v-if="scope.row.processingVerify.length > 3" class="img-count">
+{{ scope.row.processingVerify.length - 3 }}
</span>
</div>
<span v-else class="no-data">无处理凭证</span>
</template>
</el-table-column>
<!-- 处理结果 -->
<el-table-column label="处理结果" prop="processingResult" width="180px">
<template #default="scope">
<div class="text-ellipsis" :title="scope.row.processingResult">
{{ scope.row.processingResult || '未处理' }}
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button
plain
size="mini"
type="primary"
@click="handleReport(scope.row)"
:disabled="scope.row.processingStatus === '处理完成'"
>
处理举报
</el-button>
<el-button
plain
size="mini"
type="primary"
@click="handleDetail(scope.row)"
>
详情
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
background
:current-page="pagination.pageNum"
layout="total, sizes, prev, pager, next, jumper"
:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<ReportDeal ref="handleReport" @success="handleSuccess" />
<ReportDetail ref="handleDetail" @success="handleSuccess" />
</div>
</div>
</template>
<script>
import ReportDeal from './reportDeal.vue'
import ReportDetail from './reportDetail.vue'
import { getReportList } from '@/api/base/config'
export default {
components: {
ReportDeal,
ReportDetail,
},
data() {
return {
filter: {
reporterNickname: '',
processingStatus: '',
},
reportList: [
{ report_id: 0, report_name: '未处理' },
{ report_id: 1, report_name: '已受理' },
{ report_id: 2, report_name: '已驳回' },
{ report_id: 3, report_name: '处理完成' },
],
tableData: [],
pagination: {
pageNum: 1,
pageSize: 10,
total: 0,
pages: 0,
},
multipleSelection: [],
}
},
mounted() {
this.handleReportList()
},
methods: {
handleReport(row) {
this.$refs.handleReport.open(row)
},
handleDetail(row) {
this.$refs.handleDetail.open(row)
},
handleSuccess() {
this.handleReportList()
},
handleSelectionChange(val) {
this.multipleSelection = val
},
async handleReportList() {
try {
const params = {
pageNum: this.pagination.pageNum,
pageSize: this.pagination.pageSize,
reporterNickname: this.filter.reporterNickname,
processingStatus: this.filter.processingStatus,
}
const res = await getReportList(params)
console.log('ssssss', params)
this.tableData = (res.records || []).map((item) => ({
id: item.id,
reportContent: item.reportContent,
//
reportVerify: item.evidenceImages
? JSON.parse(item.evidenceImages)
: [],
//
processingVerify: item.processingEvidenceImages
? JSON.parse(item.processingEvidenceImages)
: [],
//
processingResult: item.processingResult || '',
reporterNickname: item.reporterNickname,
reporterPhone: item.reporterPhone || '无',
reporterTime: item.createdAt,
processingStatus: this.getStatusText(item.processingStatus),
reportedNickname: item.reportedNickname,
reportedUserId: item.reportedUserId || '无',
// 使
rawData: item,
}))
this.pagination = {
...this.pagination,
total: res.total || 0,
pageNum: res.current || 1,
pageSize: res.size || 10,
pages: res.pages || 0,
}
} catch (error) {
console.error('获取举报列表失败:', error)
this.$message.error('获取数据失败,请重试')
}
},
handleCurrentChange(val) {
this.pagination.pageNum = val
this.handleReportList()
},
handleSizeChange(val) {
this.pagination.pageSize = val
this.pagination.pageNum = 1
this.handleReportList()
},
handleSearch() {
this.pagination.pageNum = 1
this.handleReportList()
},
getStatusText(status) {
switch (status) {
case 0:
return '未处理'
case 1:
return '已受理'
case 2:
return '已驳回'
case 3:
return '处理完成'
default:
return '未知状态'
}
},
getStatusTagType(state) {
switch (state) {
case '未处理':
return 'warning'
case '已受理':
return 'info'
case '已驳回':
return 'danger'
case '处理完成':
return 'success'
default:
return 'default'
}
},
},
}
</script>
<style lang="scss" scoped>
.filter {
display: flex;
align-items: center;
padding: 20px;
margin-bottom: 15px;
gap: 10px;
border-radius: 5px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.1);
background: #fff;
.input_item {
width: 250px;
}
}
.list {
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
gap: 10px;
border-radius: 5px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.1);
}
//
.img-group {
position: relative;
display: inline-flex;
gap: 5px;
}
.img-thumb {
width: 50px;
height: 50px;
cursor: pointer;
border-radius: 3px;
}
.img-count {
position: absolute;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
color: white;
font-size: 12px;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
//
.text-ellipsis {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
//
.no-data {
color: #999;
font-size: 12px;
}
//
::v-deep .el-table__cell {
vertical-align: middle !important;
}
</style>

View File

@ -0,0 +1,263 @@
<template>
<el-dialog
append-to-body
title="处理举报"
:visible.sync="visible"
width="600px"
:before-close="handleClose"
>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<!-- 处理状态选择 -->
<el-form-item label="处理状态" prop="processingStatus">
<el-select
v-model="form.processingStatus"
placeholder="请选择处理状态"
@change="handleStatusChange"
clearable
>
<el-option label="受理中" value="1" />
<el-option label="证据不足(已驳回)" value="2" />
<el-option label="已处理" value="3" />
</el-select>
</el-form-item>
<!-- 处理结果状态2/3显示 -->
<el-form-item
label="处理结果"
prop="processingResult"
v-if="['2', '3'].includes(form.processingStatus)"
>
<el-input
v-model="form.processingResult"
type="textarea"
rows="3"
placeholder="请输入处理结果已核实违规封禁账号7天"
/>
</el-form-item>
<!-- 核心上传区域仅状态3显示适配项目路由的OSS上传接口 -->
<el-form-item
label="佐证材料"
v-if="form.processingStatus === '3'"
prop="processingEvidenceImages"
>
<el-upload
ref="evidenceUpload"
:action="uploadUrl"
:auto-upload="true"
:data="uploadParams"
:file-list="uploadFileList"
:multiple="true"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleFileRemove"
list-type="picture-card"
accept=".png,.jpg,.jpeg,.mp4,.avi"
>
<i class="el-icon-plus"></i>
<div slot="tip" class="el-upload__tip">
支持上传 PNG/JPG/MP4/AVI 格式单个文件不超过50MB
</div>
</el-upload>
<!-- 已上传文件预览 -->
<div v-if="uploadFileList.length > 0" style="margin-top: 15px;">
<span class="el-text--primary">已上传文件</span>
<div style="display: flex; gap: 10px; margin-top: 8px; flex-wrap: wrap;">
<!-- 图片预览 -->
<div
v-for="(file, index) in uploadFileList.filter(f => f.raw?.type.startsWith('image/'))"
:key="index"
style="position: relative; width: 80px; height: 80px;"
>
<el-image
:src="file.url"
style="width: 100%; height: 100%; object-fit: cover;"
:preview-src-list="[file.url]"
/>
<el-button
type="text"
size="mini"
style="position: absolute; top: -5px; right: -5px; color: #f56c6c;"
@click.stop="handleFileRemove(file, index)"
>
<i class="el-icon-close"></i>
</el-button>
</div>
<!-- 视频预览 -->
<div
v-for="(file, index) in uploadFileList.filter(f => f.raw?.type.startsWith('video/'))"
:key="index"
style="position: relative; width: 80px; height: 80px; background: #f5f7fa; display: flex; flex-direction: column; align-items: center; justify-content: center;"
>
<i class="el-icon-video-play" style="font-size: 24px; color: #409eff;"></i>
<span style="font-size: 12px; margin-top: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; width: 70px;">
{{ file.name }}
</span>
<el-button
type="text"
size="mini"
style="position: absolute; top: -5px; right: -5px; color: #f56c6c;"
@click.stop="handleFileRemove(file, index)"
>
<i class="el-icon-close"></i>
</el-button>
</div>
</div>
</div>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交处理</el-button>
</div>
</el-dialog>
</template>
<script>
import { URL } from '@/config';
import { dealReport } from '@/api/base/config';
import { getToken } from '@/utils/token';
export default {
name: 'ReportDeal',
data() {
return {
visible: false,
form: {
id: null,
processingStatus: '',
processingResult: '',
processingEvidenceImages: [] //
},
//
rules: {
processingStatus: [
{ required: true, message: '请选择处理状态', trigger: 'change' }
],
processingResult: [
{ required: true, message: '请输入处理结果', trigger: 'blur' },
{ min: 10, message: '处理结果至少10个字符', trigger: 'blur' }
],
processingEvidenceImages: [
{ required: true, message: '请上传佐证材料', trigger: 'change' }
]
},
//
uploadUrl: URL.upload, // OSSapi_url + '/admin/oss/upload'
uploadParams: {
authorization: getToken(),
gallery_type: 'image' //
},
uploadFileList: [] //
};
},
methods: {
//
open(row) {
this.form = {
...this.form,
id: row.id, // ID
processingStatus: '',
processingResult: '',
processingEvidenceImages: []
};
this.uploadFileList = []; //
this.visible = true;
},
// ""
handleStatusChange(val) {
if (val !== '3') {
this.uploadFileList = [];
this.form.processingEvidenceImages = [];
} else {
// ""gallery_type
this.uploadParams.gallery_type = 'image';
}
},
//
handleUploadSuccess(response, file, fileList) {
if (response.code == 0) {
file.url = response.data.media_url; // URL
this.form.processingEvidenceImages = fileList.map(item => ({
url: item.url,
description: `佐证材料_${item.name}`
}));
this.$message.success(`文件 ${file.name} 上传成功`);
} else {
this.$message.error(`文件 ${file.name} 上传失败:${response.msg || '未知错误'}`);
//
this.uploadFileList = this.uploadFileList.filter(item => item.uid !== file.uid);
}
},
//
handleUploadError(error, file) {
this.$message.error(`文件 ${file.name} 上传失败:网络错误或接口异常`);
this.uploadFileList = this.uploadFileList.filter(item => item.uid !== file.uid);
},
//
handleFileRemove(file, index) {
this.uploadFileList.splice(index, 1);
//
this.form.processingEvidenceImages = this.uploadFileList.map(item => ({
url: item.url,
description: `佐证材料_${item.name}`
}));
},
//
async handleSubmit() {
this.$refs.form.validate(async (valid) => {
if (!valid) return;
//
const submitData = {
id: this.form.id,
processingStatus: this.form.processingStatus,
processingResult: this.form.processingResult
};
// 3JSON
if (this.form.processingStatus === '3') {
submitData.processingEvidenceImages = JSON.stringify(this.form.processingEvidenceImages);
}
try {
await dealReport(submitData);
this.$message.success('处理成功!');
this.visible = false;
this.$emit('success'); //
} catch (err) {
this.$message.error(`处理失败:${err.message || '未知错误'}`);
}
});
},
handleClose() {
this.visible = false;
this.$refs.form?.resetFields();
this.uploadFileList = [];
this.form.processingEvidenceImages = [];
}
}
};
</script>
<style scoped>
::v-deep .el-upload {
margin-bottom: 10px;
}
::v-deep .el-image-viewer__mask {
background: rgba(0, 0, 0, 0.7) !important;
}
.el-upload__tip {
color: #666 !important;
font-size: 12px !important;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<el-dialog
:visible.sync="visible"
title="举报详情"
width="600px"
:before-close="handleClose"
>
<div>
<p>
<strong>举报正文</strong>
{{ detailData.reportContent }}
</p>
<div>
<p><strong>举报凭证</strong></p>
<div class="verify_container">
<div v-for="verify_item in detailData.reportVerify">
<img :src="verify_item.url" class="verify_img" />
</div>
</div>
</div>
<p>
<strong>举报人</strong>
{{ detailData.reporterName }}
</p>
<p>
<strong>举报时间</strong>
{{ detailData.reporterTime }}
</p>
<p>
<strong>状态</strong>
<el-tag :type="detailData.state === '未处理' ? 'warning' : 'success'">
{{ detailData.state }}
</el-tag>
</p>
</div>
</el-dialog>
</template>
<script>
export default {
name: 'ReportDetail',
data() {
return {
visible: false,
detailData: {},
}
},
methods: {
//
open(row) {
this.detailData = row
this.visible = true //
},
//
handleClose() {
this.visible = false
},
},
}
</script>
<style scoped>
.verify_container {
display: flex;
flex-direction: row;
gap: 10px;
flex-wrap: wrap;
margin: 10px 0;
}
.verify_img {
width: 100px;
height: 100px;
}
</style>

View File

@ -61,6 +61,9 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="__('分账比例')" prop="split_ratio">
<el-input v-model.trim="form.split_ratio" :placeholder="__('比例在70%-99%之间')" clearable />
</el-form-item>
<el-form-item :label="__('所属分站')" prop="subsite_id"> <el-form-item :label="__('所属分站')" prop="subsite_id">
<el-select <el-select
v-model.trim="form.subsite_id" v-model.trim="form.subsite_id"
@ -155,6 +158,7 @@ export default {
store_type: 1, store_type: 1,
store_o2o_flag: 0, store_o2o_flag: 0,
subsite_id: 0, subsite_id: 0,
split_ratio:0,
}, },
rules: { rules: {
user_password: [ user_password: [

View File

@ -92,7 +92,7 @@
<el-form-item <el-form-item
label="营业状态" label="营业状态"
label-width="150px" label-width="150px"
prop="store_district_id" prop="store_biz_state"
> >
<el-switch <el-switch
v-model="storeForm.store_biz_state" v-model="storeForm.store_biz_state"
@ -102,8 +102,47 @@
inactive-color="#ff4949" inactive-color="#ff4949"
active-text="营业中" active-text="营业中"
@change="handleStoreStatus" @change="handleStoreStatus"
inactive-text="已打烊"> inactive-text="已打烊"
</el-switch> ></el-switch>
</el-form-item>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="grid-content bg-purple">
<el-form-item
label="语音播报"
label-width="150px"
prop="ringtone_is_enable"
>
<el-switch
v-model="storeForm.ringtone_is_enable"
:active-value="1"
:inactive-value="2"
@change="handleRingStatus"
active-text="开启"
inactive-text="关闭"
></el-switch>
</el-form-item>
</div>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<div class="grid-content bg-purple">
<el-form-item
label="打包费"
label-width="150px"
prop="store_longitude"
:style="{ width: '100%' }"
>
<el-input
v-model="storeForm.packing_fee"
clearable
placeholder="请输入打包费"
:style="{ width: '80%' }"
/>
</el-form-item> </el-form-item>
</div> </div>
</el-col> </el-col>
@ -444,268 +483,76 @@
</template> </template>
<script> <script>
import { translateTitle as __ } from '@/utils/i18n' import { translateTitle as __ } from '@/utils/i18n'
import VabQuill from '@/extra/VabQuill' import VabQuill from '@/extra/VabQuill'
import Area from '@/components/VabArea/area' import Area from '@/components/VabArea/area'
import upload from '@/components/upload' import upload from '@/components/upload'
import tree from '@/components/universalTreeSelect.vue' import tree from '@/components/universalTreeSelect.vue'
import keyword from '@/components/baiduMap/keyword' import keyword from '@/components/baiduMap/keyword'
import { get, storeSetUp } from '@/api/store/base' import { get, storeSetUp } from '@/api/store/base'
import { getTree } from '@/api/base/market/category' import { getTree } from '@/api/base/market/category'
import { handleMatched } from '@/utils/routes' import { handleMatched } from '@/utils/routes'
export default { export default {
name: 'SetUpShop', name: 'SetUpShop',
components: { VabQuill, Area, upload, keyword, tree }, components: { VabQuill, Area, upload, keyword, tree },
data() { data() {
return { return {
tableConfig: { tableConfig: {
height: window.innerHeight - 220 - 90, height: window.innerHeight - 220 - 90,
},
test: 666,
treeData: [],
activeName: 'first',
optionslist: [
{
value: '1001',
label: this.__('免费停车'),
}, },
{ test: 666,
value: '1002', treeData: [],
label: this.__('免费WiFi'), activeName: 'first',
}, optionslist: [
{ {
value: '1003', value: '1001',
label: this.__('免费送货'), label: this.__('免费停车'),
}, },
], {
value: '', value: '1002',
options: { label: this.__('免费WiFi'),
theme: 'snow', },
bounds: document.body, {
debug: 'warn', value: '1003',
modules: { label: this.__('免费送货'),
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ color: [] }, { background: [] }],
['blockquote', 'code-block'],
[{ list: 'ordered' }, { list: 'bullet' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ align: [] }],
[{ direction: 'rtl' }],
[{ font: [] }],
['clean'],
['link', 'image', 'vab-upload-image'],
],
}, },
},
placeholder: '',
readOnly: false,
isRest:false
},
defaultProps: {
label: 'category_name',
children: 'children',
disabled: 'disabled',
},
storeForm: {},
slideShowForm: {
store_slide: [
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
], ],
}, value: '',
physicalStoreForm: { options: {
store_notice: undefined, theme: 'snow',
store_o2o_tags: [], bounds: document.body,
store_opening_hours: undefined, debug: 'warn',
store_close_hours: undefined, modules: {
store_discount: undefined, toolbar: {
store_circle: undefined, container: [
}, ['bold', 'italic', 'underline', 'strike'],
storeConfigForm: { [{ header: [1, 2, 3, 4, 5, 6, false] }],
sc_is_enabled_invoice: undefined, [{ size: ['small', false, 'large', 'huge'] }],
}, [{ color: [] }, { background: [] }],
form: { ['blockquote', 'code-block'],
store_name: undefined, [{ list: 'ordered' }, { list: 'bullet' }],
store_logo: undefined, [{ script: 'sub' }, { script: 'super' }],
store_slogan: undefined, [{ indent: '-1' }, { indent: '+1' }],
store_address: undefined, [{ align: [] }],
store_longitude: '', [{ direction: 'rtl' }],
store_latitude: '', [{ font: [] }],
str_store_area: undefined, ['clean'],
store_tel: undefined, ['link', 'image', 'vab-upload-image'],
store_opening_hours: undefined, ],
store_circle: undefined, },
store_discount: undefined, },
store_close_hours: undefined, placeholder: '',
store_qq: undefined, readOnly: false,
company_description: undefined, isRest: false,
store_banner: undefined, },
store_ww: undefined, defaultProps: {
store_notice: undefined, label: 'category_name',
store_o2o_tags: [], children: 'children',
store_district_id: '', disabled: 'disabled',
}, },
queryArea: { storeForm: {},
province: { code: '', name: '' }, slideShowForm: {
city: { code: '', name: '' },
district: { code: '', name: '' },
},
srcList: [],
}
},
created() {
this.fetchData()
this.getTree()
},
methods: {
getPhysicalStoreParams() {
const form = this.physicalStoreForm
let params = {
store_notice: form.store_notice,
// store_o2o_tags: form.store_o2o_tags.toString(),
store_opening_hours: form.store_opening_hours,
store_close_hours: form.store_close_hours,
store_discount: form.store_discount,
store_circle: form.store_circle,
}
return params
},
getStoreParams() {
const form = this.storeForm
let params = {
store_name: form.store_name,
store_slogan: form.store_slogan,
store_logo: form.store_logo,
store_banner: form.store_banner,
store_address: form.store_address,
store_longitude: form.store_longitude,
store_latitude: form.store_latitude,
store_tel: form.store_tel,
store_qq: form.store_qq,
store_ww: form.store_ww,
company_description: form.company_description,
store_biz_state:form.store_biz_state
}
if (this.queryArea.province.name) {
params.store_area = this.queryArea.province.name
}
if (this.queryArea.city.name) {
params.store_area += '/' + this.queryArea.city.name
}
if (this.queryArea.district.name) {
params.store_area += '/' + this.queryArea.district.name
}
if (this.queryArea.province.code) {
params.store_district_id = this.queryArea.province.code
}
if (this.queryArea.city.code) {
params.store_district_id += '/' + this.queryArea.city.code
}
if (this.queryArea.district.code) {
params.store_district_id += '/' + this.queryArea.district.code
}
return params
},
handleStoreStatus(e){
this.storeForm.store_biz_state = e;
},
getSlideShowForm() {
const form = this.slideShowForm
let params = {
store_slide: JSON.stringify(form.store_slide),
}
return params
},
getStoreConfigForm() {
const form = this.storeConfigForm
let params = {
sc_is_enabled_invoice: JSON.stringify(form.sc_is_enabled_invoice),
}
return params
},
__,
upImage(image, index) {
this.form.store_slide[index].img = image
},
async handleEdit(params) {
this.$baseConfirm(this.__('修改立马生效,是否继续?'), null, async () => {
const { msg, status } = await storeSetUp(params)
if (200 == status) {
this.$baseMessage(msg, 'success')
} else {
this.$baseMessage(msg, 'error')
}
await this.fetchData()
})
},
handleParams() {
let params = Object.assign({}, this.form)
params.store_o2o_tags = this.form.store_o2o_tags.toString()
params.store_slide = JSON.stringify(params.store_slide)
return params
},
async fetchData(params) {
const { data } = await get({ params })
this.initData(data)
},
async getTree() {
const { data } = await getTree()
console.log(data)
if (data) {
this.treeData = data
} else {
this.treeData = []
}
},
initData(data) {
this.physicalStoreForm = {
store_notice: data.info.store_notice,
store_opening_hours: data.info.store_opening_hours,
store_close_hours: data.info.store_close_hours,
store_discount: data.info.store_discount,
store_circle: Number(data.store_circle),
}
this.storeConfigForm = {
sc_is_enabled_invoice: data.sc_is_enabled_invoice,
}
if (data.store_o2o_tags) {
this.physicalStoreForm.store_o2o_tags = data.store_o2o_tags.split(',')
}
this.storeForm = {
store_name: data.store_name,
store_slogan: data.store_slogan,
store_logo: data.store_logo,
store_banner: data.info.store_banner,
store_address: data.store_address,
store_longitude: data.store_longitude,
store_latitude: data.store_latitude,
store_tel: data.info.store_tel,
store_qq: data.info.store_qq,
store_ww: data.info.store_ww,
company_description: data.company.company_description,
wx_qrcode: data.wx_qrcode,
store_biz_state:data.store_biz_state
}
this.srcList.push(data.wx_qrcode)
if (data.info.store_slide && data.info.store_slide.length > 0) {
this.slideShowForm.store_slide = data.info.store_slide
} else {
this.slideShowForm = {
store_slide: [ store_slide: [
{ img: '', check: true, name: '' }, { img: '', check: true, name: '' },
{ img: '', check: true, name: '' }, { img: '', check: true, name: '' },
@ -713,70 +560,275 @@ export default {
{ img: '', check: true, name: '' }, { img: '', check: true, name: '' },
{ img: '', check: true, name: '' }, { img: '', check: true, name: '' },
], ],
},
physicalStoreForm: {
store_notice: undefined,
store_o2o_tags: [],
store_opening_hours: undefined,
store_close_hours: undefined,
store_discount: undefined,
store_circle: undefined,
},
storeConfigForm: {
sc_is_enabled_invoice: undefined,
},
form: {
store_name: undefined,
store_logo: undefined,
store_slogan: undefined,
store_address: undefined,
store_longitude: '',
store_latitude: '',
str_store_area: undefined,
store_tel: undefined,
store_opening_hours: undefined,
store_circle: undefined,
store_discount: undefined,
store_close_hours: undefined,
store_qq: undefined,
company_description: undefined,
store_banner: undefined,
store_ww: undefined,
store_notice: undefined,
store_o2o_tags: [],
store_district_id: '',
packing_fee: 0,
},
queryArea: {
province: { code: '', name: '' },
city: { code: '', name: '' },
district: { code: '', name: '' },
},
srcList: [],
}
},
created() {
this.fetchData()
this.getTree()
},
methods: {
getPhysicalStoreParams() {
const form = this.physicalStoreForm
let params = {
store_notice: form.store_notice,
// store_o2o_tags: form.store_o2o_tags.toString(),
store_opening_hours: form.store_opening_hours,
store_close_hours: form.store_close_hours,
store_discount: form.store_discount,
store_circle: form.store_circle,
}
return params
},
getStoreParams() {
const form = this.storeForm
let params = {
store_name: form.store_name,
store_slogan: form.store_slogan,
store_logo: form.store_logo,
store_banner: form.store_banner,
store_address: form.store_address,
store_longitude: form.store_longitude,
store_latitude: form.store_latitude,
store_tel: form.store_tel,
ringtone_is_enable:form.ringtone_is_enable,
store_qq: form.store_qq,
store_ww: form.store_ww,
company_description: form.company_description,
store_biz_state: form.store_biz_state,
packing_fee: form.packing_fee,
} }
}
const store_area = data.store_area.split('/') if (this.queryArea.province.name) {
const store_district_id = data.store_district_id.split(',') params.store_area = this.queryArea.province.name
}
if (this.queryArea.city.name) {
params.store_area += '/' + this.queryArea.city.name
}
if (this.queryArea.district.name) {
params.store_area += '/' + this.queryArea.district.name
}
this.initQueryArea(store_district_id, store_area) if (this.queryArea.province.code) {
this.form.store_o2o_flag = data.store_o2o_flag params.store_district_id = this.queryArea.province.code
}
if (this.queryArea.city.code) {
params.store_district_id += '/' + this.queryArea.city.code
}
if (this.queryArea.district.code) {
params.store_district_id += '/' + this.queryArea.district.code
}
return params
},
handleStoreStatus(e) {
this.storeForm.store_biz_state = e
},
handleRingStatus(e) {
this.storeForm.ringtone_is_enable = e
console.log('ringtone_is_enable:', this.storeForm.ringtone_is_enable)
},
getSlideShowForm() {
const form = this.slideShowForm
let params = {
store_slide: JSON.stringify(form.store_slide),
}
return params
},
getStoreConfigForm() {
const form = this.storeConfigForm
let params = {
sc_is_enabled_invoice: JSON.stringify(form.sc_is_enabled_invoice),
}
return params
},
__,
upImage(image, index) {
this.form.store_slide[index].img = image
},
async handleEdit(params) {
console.log('handleEdit params:', params)
this.$baseConfirm(
this.__('修改立马生效,是否继续?'),
null,
async () => {
const { msg, status } = await storeSetUp(params)
if (200 == status) {
this.$baseMessage(msg, 'success')
} else {
this.$baseMessage(msg, 'error')
}
await this.fetchData()
}
)
},
handleParams() {
let params = Object.assign({}, this.form)
params.store_o2o_tags = this.form.store_o2o_tags.toString()
params.store_slide = JSON.stringify(params.store_slide)
return params
},
async fetchData(params) {
const { data } = await get({ params })
this.initData(data)
},
async getTree() {
const { data } = await getTree()
if (data) {
this.treeData = data
} else {
this.treeData = []
}
},
initData(data) {
this.physicalStoreForm = {
store_notice: data.info.store_notice,
store_opening_hours: data.info.store_opening_hours,
store_close_hours: data.info.store_close_hours,
store_discount: data.info.store_discount,
store_circle: Number(data.store_circle),
}
this.storeConfigForm = {
sc_is_enabled_invoice: data.sc_is_enabled_invoice,
}
if (data.store_o2o_tags) {
this.physicalStoreForm.store_o2o_tags = data.store_o2o_tags.split(',')
}
this.storeForm = {
store_name: data.store_name,
store_slogan: data.store_slogan,
store_logo: data.store_logo,
store_banner: data.info.store_banner,
store_address: data.store_address,
store_longitude: data.store_longitude,
store_latitude: data.store_latitude,
store_tel: data.info.store_tel,
store_qq: data.info.store_qq,
store_ww: data.info.store_ww,
company_description: data.company.company_description,
wx_qrcode: data.wx_qrcode,
store_biz_state: data.store_biz_state,
packing_fee: data.packing_fee,
ringtone_is_enable: data.ringtone_is_enable,
}
this.srcList.push(data.wx_qrcode)
if (data.info.store_slide && data.info.store_slide.length > 0) {
this.slideShowForm.store_slide = data.info.store_slide
} else {
this.slideShowForm = {
store_slide: [
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
{ img: '', check: true, name: '' },
],
}
}
const store_area = data.store_area.split('/')
const store_district_id = data.store_district_id.split(',')
this.initQueryArea(store_district_id, store_area)
this.form.store_o2o_flag = data.store_o2o_flag
},
initQueryArea(store_district_id, store_area) {
if (!store_district_id && store_area) return
if (store_district_id[0]) {
this.queryArea.province.code = store_district_id[0]
}
if (store_district_id[1]) {
this.queryArea.city.code = store_district_id[1]
}
if (store_district_id[2]) {
this.queryArea.district.code = store_district_id[2]
}
if (store_area[0]) {
this.queryArea.province.name = store_area[0]
}
if (store_area[1]) {
this.queryArea.city.name = store_area[1]
}
if (store_area[2]) {
this.queryArea.district.name = store_area[2]
}
},
getAddress(address) {
this.storeForm.store_address = address.value
this.storeForm.store_longitude = address.lng
this.storeForm.store_latitude = address.lat
},
}, },
initQueryArea(store_district_id, store_area) { }
if (!store_district_id && store_area) return
if (store_district_id[0]) {
this.queryArea.province.code = store_district_id[0]
}
if (store_district_id[1]) {
this.queryArea.city.code = store_district_id[1]
}
if (store_district_id[2]) {
this.queryArea.district.code = store_district_id[2]
}
if (store_area[0]) {
this.queryArea.province.name = store_area[0]
}
if (store_area[1]) {
this.queryArea.city.name = store_area[1]
}
if (store_area[2]) {
this.queryArea.district.name = store_area[2]
}
},
getAddress(address) {
this.storeForm.store_address = address.value
this.storeForm.store_longitude = address.lng
this.storeForm.store_latitude = address.lat
},
},
}
</script> </script>
<style lang="scss"> <style lang="scss">
.config-save { .config-save {
color: white; color: white;
text-align: center; text-align: center;
background-color: #2c2e30; background-color: #2c2e30;
padding: 12px; padding: 12px;
} }
.config-save:hover { .config-save:hover {
background-color: #3d3d3d; background-color: #3d3d3d;
} }
.form-item-img-list { .form-item-img-list {
.el-form-item__content { .el-form-item__content {
display: flex;
margin: 0;
}
}
.QRcode {
display: flex; display: flex;
margin: 0; margin-left: 40px;
}
}
.QRcode { .QRcode-img {
display: flex; border: 1px solid #cccccc;
margin-left: 40px; border-radius: 6px;
}
.QRcode-img {
border: 1px solid #cccccc;
border-radius: 6px;
} }
}
</style> </style>

View File

@ -189,10 +189,6 @@ export default {
prop: 'has_apply_mer', prop: 'has_apply_mer',
label: '商家进件状态', label: '商家进件状态',
}, },
{
prop: 'store_status',
label: '店铺创建状态',
},
{ {
prop: 'has_apply_split', prop: 'has_apply_split',
label: '申请分账业务状态', label: '申请分账业务状态',
@ -205,6 +201,10 @@ export default {
prop: 'has_bind_receiver', prop: 'has_bind_receiver',
label: '绑定分账接收方', label: '绑定分账接收方',
}, },
{
prop: 'store_status',
label: '店铺创建状态',
},
], ],
tableData: [ tableData: [
{ {

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,22 @@
<el-form-item :label="__('模版编号')" prop="tplmsg_tpl_id"> <el-form-item :label="__('模版编号')" prop="tplmsg_tpl_id">
<el-input v-model="form.tplmsg_tpl_id" :placeholder="__('模版编号')" /> <el-input v-model="form.tplmsg_tpl_id" :placeholder="__('模版编号')" />
</el-form-item> </el-form-item>
<el-form-item :label="__('店铺')" prop="store_id">
<el-select
v-model="form.store_id"
clearable
filterable
placeholder="选择店铺">
<el-option
v-for="item in shopList"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"/>
</el-select>
</el-form-item>
<el-form-item :label="__('跳转路径')" prop="link_url">
<el-input v-model="form.link_url" :placeholder="__('微信公众号或者小程序跳转路径')" />
</el-form-item>
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button @click="close">{{ __('取消') }}</el-button> <el-button @click="close">{{ __('取消') }}</el-button>
@ -21,7 +37,7 @@
import { translateTitle as __ } from '@/utils/i18n' import { translateTitle as __ } from '@/utils/i18n'
import { doEdit } from '@/api/wechat/tplmsg' import { doEdit } from '@/api/wechat/tplmsg'
import GoodsToolApi from '@/api/goodsTool'
export default { export default {
name: 'WechatTplmsgEdit', name: 'WechatTplmsgEdit',
data() { data() {
@ -30,11 +46,13 @@
rules: {}, rules: {},
title: '', title: '',
dialogFormVisible: false, dialogFormVisible: false,
shopList:[],
} }
}, },
created() {}, created() {},
mounted() { mounted() {
this.$forceUpdate() this.$forceUpdate()
this.getShopList()
}, },
methods: { methods: {
__, __,
@ -65,6 +83,10 @@
} }
}) })
}, },
async getShopList() {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items
},
}, },
} }
</script> </script>

View File

@ -34,7 +34,7 @@
/> />
<el-table-column <el-table-column
align="center" align="center"
:label="__('模版编号')" :label="__('微信模版编号')"
prop="tplmsg_tpl_id" prop="tplmsg_tpl_id"
show-overflow-tooltip show-overflow-tooltip
width="150" width="150"
@ -76,6 +76,12 @@
prop="tplmsg_is_buildin" prop="tplmsg_is_buildin"
show-overflow-tooltip show-overflow-tooltip
/> />
<el-table-column
align="center"
:label="__('跳转路径')"
prop="link_url"
show-overflow-tooltip
/>
<el-table-column <el-table-column
align="center" align="center"
:label="__('备注')" :label="__('备注')"