This commit is contained in:
hufflzp 2025-12-30 18:20:04 +08:00
commit 68cbfe3bc1
26 changed files with 1360 additions and 45 deletions

View File

@ -5,5 +5,5 @@
window.ver = "2.0.278";
window.SYS = {CONFIG:{}, URL:{}};</script><script>window._AMapSecurityConfig = {
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-3b9411e6.b0d59fca.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-3b9411e6.008e954c.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.7aab1e38.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.7aab1e38.js"></script></body></html>
}</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-801acd4c.afe24c41.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-801acd4c.760f3270.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.df3b691f.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.df3b691f.js"></script></body></html>

View File

@ -2376,7 +2376,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "im/libs3.6.0.min.js"
},
{
"revision": "42aa58b41d3e5a7ee51d3813266b1755",
"revision": "352e407a09c074af51ec5ea2b89edb64",
"url": "index.html"
},
{
@ -2392,7 +2392,7 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "robots.txt"
},
{
"revision": "319d08d5594ace9aaf2d",
"revision": "7b4e1047aabe7f32a5f9",
"url": "static/css/app.d3766ec2.css"
},
{
@ -2400,8 +2400,8 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "static/css/chunk-19648027.83a11e8c.css"
},
{
"revision": "205d59c37c9ea360b608",
"url": "static/css/chunk-3b9411e6.b0d59fca.css"
"revision": "054187581134215965fa",
"url": "static/css/chunk-801acd4c.afe24c41.css"
},
{
"revision": "7b9212a0410ce12f6058",
@ -2636,16 +2636,16 @@ self.__precacheManifest = (self.__precacheManifest || []).concat([
"url": "static/img/xiaofa-logo.20439423.png"
},
{
"revision": "319d08d5594ace9aaf2d",
"url": "static/js/app.7aab1e38.js"
"revision": "7b4e1047aabe7f32a5f9",
"url": "static/js/app.df3b691f.js"
},
{
"revision": "f5dd29b853f67685e75d",
"url": "static/js/chunk-19648027.6233a553.js"
},
{
"revision": "205d59c37c9ea360b608",
"url": "static/js/chunk-3b9411e6.008e954c.js"
"revision": "054187581134215965fa",
"url": "static/js/chunk-801acd4c.760f3270.js"
},
{
"revision": "7b9212a0410ce12f6058",

View File

@ -14,7 +14,7 @@
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
importScripts(
"precache-manifest.073b518e041ab2c5f645b4dac923d8c8.js"
"precache-manifest.025fd2536572cf5272c5c580bcfc1a00.js"
);
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
* copyright: https://www.lancerdt.com
* time: 2025-12-24 11:47:55
* time: 2025-12-30 11:59:51
*/
/*!
* Quill Editor v1.3.7

View File

@ -1,5 +1,5 @@
/*!
* build: xiaofa-admin
* copyright: https://www.lancerdt.com
* time: 2025-12-24 11:47:55
* time: 2025-12-30 11:59:51
*/@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
* copyright: https://www.lancerdt.com
* time: 2025-12-24 11:47:55
* time: 2025-12-30 11:59:51
*/[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

View File

@ -45,6 +45,28 @@ export async function downloadTempGoods() {
responseType: 'blob',
})
}
//商品导入 下载三个模板
export async function downloadBrandTemplate() {
return request({
url: '/admin/shop/shop-sync-import/brandTemplate',
method: 'get',
responseType: 'blob',
})
}
export async function downloadCategoryTemplate() {
return request({
url: '/admin/shop/shop-sync-import/categoryTemplate',
method: 'get',
responseType: 'blob',
})
}
export async function downloadGoodsTemplate() {
return request({
url: '/admin/shop/shop-sync-import/shopTemplate',
method: 'get',
responseType: 'blob',
})
}
export async function exportUncheckShopData(storeId) {
return request({
url: '/admin/shop/shop-sync-productMapper/exportUncheckShopData',
@ -128,6 +150,37 @@ export async function importGoodsData(data) {
data,
})
}
//商品导入的三个导入
export async function categoryImportData(data) {
return request({
url: '/admin/shop/shop-sync-import/categoryImportData',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data,
})
}
export async function brandImportData(data) {
return request({
url: '/admin/shop/shop-sync-import/brandImportData',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data,
})
}
export async function shopImportData(data) {
return request({
url: '/admin/shop/shop-sync-import/shopImportData',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data,
})
}
export async function syncShopImages(data) {
data = stringify(data)
@ -169,5 +222,11 @@ export default {
importGoodsData,
syncShopImages,
getImgList,
saveBatchBarcode
saveBatchBarcode,
downloadBrandTemplate,
downloadCategoryTemplate,
downloadGoodsTemplate,
categoryImportData,
brandImportData,
shopImportData
}

36
src/api/syncApp.js Normal file
View File

@ -0,0 +1,36 @@
import request from '@/utils/request'
import { stringify } from 'qs'
export async function getSyncAppList() {
return request({
url: '/admin/shop/sync-app/list',
method: 'get',
})
}
export async function saveSyncApp(data) {
return request({
url: '/admin/shop/sync-app/saveSyncApp',
method: 'post',
data:data,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
export async function editSyncApp(data) {
return request({
url: '/admin/shop/sync-app/editSyncApp',
method: 'put',
data:data,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
export async function deleteSyncApp(syncId) {
return request({
url: '/admin/shop/sync-app/delSyncApp',
method: 'delete',
params: { syncAppId: syncId },
})
}

View File

@ -34,7 +34,7 @@ export function convertRouter(asyncRoutes) {
const obj = {
path: '/goodsImgs',
component: '@/views/product/goodsImg/goodsImgs',
name: 'Vab88000',
name: 'Vab88003',
redirect: null,
meta: {
title: '商品图库管理',
@ -46,6 +46,23 @@ export function convertRouter(asyncRoutes) {
}
route.children.push(obj)
}
if (route.meta.title == '商品' && route.name == 'Vab320') {
const obj = {
path: '/goodsImport',
component: '@/views/product/goodsImport/index',
name: 'Vab88004',
redirect: null,
meta: {
title: '商品导入管理',
icon: '',
noClosable: 0,
hidden: null,
},
menuHidden: false,
}
route.children.push(obj)
}
if (route.meta.title == '店铺' && route.name == 'Vab330') {
const obj = {
path: '/storeConf',
@ -63,6 +80,23 @@ export function convertRouter(asyncRoutes) {
route.children.push(obj)
}
if (route.meta.title == '店铺' && route.name == 'Vab330') {
const obj = {
path: '/SyncApp',
component: '@/views/store/storeSyncApp/index',
name: 'Vab88001',
redirect: null,
meta: {
title: '店铺密钥配置',
icon: '',
noClosable: 0,
hidden: null,
},
menuHidden: false,
}
route.children.push(obj)
}
if (route.meta.title == '店铺' && route.name == 'Vab330') {
const obj = {

View File

@ -0,0 +1,199 @@
<template>
<el-dialog
title="品牌数据导入"
width="50%"
:visible.sync="visible"
height="300px"
>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<el-select
v-model="filter.storeId"
clearable
filterable
placeholder="选择店铺"
style="width: 200px;"
>
<el-option
v-for="item in shopList"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"
/>
</el-select>
<div style="margin-left: 10px; position: relative;">
<input
type="file"
ref="fileInput"
accept=".xlsx,.xls"
style="display: none;"
@change="handleFileChange"
/>
<el-button
type="default"
size="small"
@click="openFileSelector"
>
选择文件
</el-button>
<span v-if="fileName" style="margin-left: 10px; font-size: 12px;">
{{ fileName }}
</span>
</div>
</div>
<div v-if="uploadMessage" :style="{ color: uploadMessageType === 'success' ? 'green' : 'red', fontSize: '12px', marginTop: '10px' }">
{{ uploadMessage }}
</div>
<template #footer>
<span>
<el-button @click="close">取消</el-button>
<el-button
type="primary"
size="small"
@click="handlerImportCategory"
:disabled="!filter.storeId || !selectedFile"
>
导入
</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
export default {
name: 'importCategory',
data() {
return {
filter: {
productName: '',
storeId: '',
},
visible: false,
shopList: [],
selectedFile: null,
fileName: '',
uploadMessage: '',
uploadMessageType: '' // success/error
}
},
mounted() {
this.getShopList()
},
methods: {
open() {
this.visible = true
this.selectedFile = null
this.fileName = ''
this.uploadMessage = ''
this.uploadMessageType = ''
},
async getShopList() {
try {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items || []
} catch (error) {
console.error('获取店铺列表失败:', error)
}
},
//
openFileSelector() {
this.$refs.fileInput.click()
},
//
handleFileChange(e) {
const file = e.target.files[0]
if (file) {
//
const fileExt = file.name.split('.').pop().toLowerCase()
if (fileExt !== 'xlsx' && fileExt !== 'xls') {
this.uploadMessage = '请选择Excel文件(.xlsx/.xls)'
this.uploadMessageType = 'error'
this.selectedFile = null
this.fileName = ''
// input便
this.$refs.fileInput.value = ''
return
}
//
const maxSize = 5 * 1024 * 1024 // 5MB
if (file.size > maxSize) {
this.uploadMessage = '文件大小不能超过5MB'
this.uploadMessageType = 'error'
this.selectedFile = null
this.fileName = ''
this.$refs.fileInput.value = ''
return
}
this.selectedFile = file
this.fileName = file.name
this.uploadMessage = '' //
}
},
async handlerImportCategory() {
if (!this.filter.storeId) {
this.uploadMessage = '请先选择店铺'
this.uploadMessageType = 'error'
return
}
if (!this.selectedFile) {
this.uploadMessage = '请先选择要上传的Excel文件'
this.uploadMessageType = 'error'
return
}
try {
const formData = new FormData()
formData.append('file', this.selectedFile)
formData.append('storeId', this.filter.storeId)
this.uploadMessage = '正在上传文件,请稍候...'
this.uploadMessageType = 'success'
const res = await GoodsToolApi.brandImportData(formData)
if (res.status == 200 || res.success) {
this.uploadMessage = '文件上传成功!'
this.uploadMessageType = 'success'
//
this.selectedFile = null
this.fileName = ''
this.$refs.fileInput.value = ''
this.$message.success('导入品牌成功!')
this.visible=false
} else {
this.uploadMessage = res.msg || '文件上传失败,请重试'
this.uploadMessageType = 'error'
}
} catch (error) {
this.uploadMessage = '文件上传失败:' + (error.message || '网络错误')
this.uploadMessageType = 'error'
}
},
close() {
this.visible = false
this.selectedFile = null
this.fileName = ''
this.uploadMessage = ''
this.filter.storeId = ''
if (this.$refs.fileInput) {
this.$refs.fileInput.value = ''
}
}
}
}
</script>
<style scoped>
.el-dialog {
height: 300px !important;
}
</style>

View File

@ -0,0 +1,201 @@
<template>
<el-dialog
title="商品分类数据导入"
width="50%"
:visible.sync="visible"
height="300px"
>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<el-select
v-model="filter.storeId"
clearable
filterable
placeholder="选择店铺"
style="width: 200px;"
>
<el-option
v-for="item in shopList"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"
/>
</el-select>
<div style="margin-left: 10px; position: relative;">
<input
type="file"
ref="fileInput"
accept=".xlsx,.xls"
style="display: none;"
@change="handleFileChange"
/>
<el-button
type="default"
size="small"
@click="openFileSelector"
>
选择文件
</el-button>
<span v-if="fileName" style="margin-left: 10px; font-size: 12px;">
{{ fileName }}
</span>
</div>
</div>
<div v-if="uploadMessage" :style="{ color: uploadMessageType === 'success' ? 'green' : 'red', fontSize: '12px', marginTop: '10px' }">
{{ uploadMessage }}
</div>
<template #footer>
<span>
<el-button @click="close">取消</el-button>
<el-button
type="primary"
size="small"
@click="handlerImportCategory"
:disabled="!filter.storeId || !selectedFile"
>
导入
</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
export default {
name: 'importCategory',
data() {
return {
filter: {
productName: '',
storeId: '',
},
visible: false,
shopList: [],
selectedFile: null,
fileName: '',
uploadMessage: '',
uploadMessageType: '' // success/error
}
},
mounted() {
this.getShopList()
},
methods: {
open() {
this.visible = true
this.selectedFile = null
this.fileName = ''
this.uploadMessage = ''
this.uploadMessageType = ''
},
async getShopList() {
try {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items || []
} catch (error) {
console.error('获取店铺列表失败:', error)
}
},
//
openFileSelector() {
this.$refs.fileInput.click()
},
//
handleFileChange(e) {
const file = e.target.files[0]
if (file) {
//
const fileExt = file.name.split('.').pop().toLowerCase()
if (fileExt !== 'xlsx' && fileExt !== 'xls') {
this.uploadMessage = '请选择Excel文件(.xlsx/.xls)'
this.uploadMessageType = 'error'
this.selectedFile = null
this.fileName = ''
// input便
this.$refs.fileInput.value = ''
return
}
//
const maxSize = 5 * 1024 * 1024 // 5MB
if (file.size > maxSize) {
this.uploadMessage = '文件大小不能超过5MB'
this.uploadMessageType = 'error'
this.selectedFile = null
this.fileName = ''
this.$refs.fileInput.value = ''
return
}
this.selectedFile = file
this.fileName = file.name
this.uploadMessage = '' //
}
},
async handlerImportCategory() {
if (!this.filter.storeId) {
this.uploadMessage = '请先选择店铺'
this.uploadMessageType = 'error'
return
}
if (!this.selectedFile) {
this.uploadMessage = '请先选择要上传的Excel文件'
this.uploadMessageType = 'error'
return
}
try {
const formData = new FormData()
formData.append('file', this.selectedFile)
formData.append('storeId', this.filter.storeId)
this.uploadMessage = '正在上传文件,请稍候...'
this.uploadMessageType = 'success'
const res = await GoodsToolApi.categoryImportData(formData)
console.log("?????",res)
if (res.status === 200) {
this.uploadMessage = '文件上传成功!'
this.uploadMessageType = 'success'
//
this.selectedFile = null
this.fileName = ''
this.$refs.fileInput.value = ''
this.$message.success('导入分类成功!')
this.visible=false
} else {
this.uploadMessage = res.msg || '文件上传失败,请重试'
this.uploadMessageType = 'error'
}
} catch (error) {
console.error('文件上传失败:', error)
this.uploadMessage = '文件上传失败:' + (error.message || '网络错误')
this.uploadMessageType = 'error'
}
},
close() {
this.visible = false
this.selectedFile = null
this.fileName = ''
this.uploadMessage = ''
this.filter.storeId = ''
if (this.$refs.fileInput) {
this.$refs.fileInput.value = ''
}
}
}
}
</script>
<style scoped>
.el-dialog {
height: 300px !important;
}
</style>

View File

@ -0,0 +1,201 @@
<template>
<el-dialog
title="品牌数据导入"
width="50%"
:visible.sync="visible"
height="300px"
>
<div style="display: flex; align-items: center; margin-bottom: 20px;">
<el-select
v-model="filter.storeId"
clearable
filterable
placeholder="选择店铺"
style="width: 200px;"
>
<el-option
v-for="item in shopList"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"
/>
</el-select>
<div style="margin-left: 10px; position: relative;">
<input
type="file"
ref="fileInput"
accept=".xlsx,.xls"
style="display: none;"
@change="handleFileChange"
/>
<el-button
type="default"
size="small"
@click="openFileSelector"
>
选择文件
</el-button>
<span v-if="fileName" style="margin-left: 10px; font-size: 12px;">
{{ fileName }}
</span>
</div>
</div>
<div v-if="uploadMessage" :style="{ color: uploadMessageType === 'success' ? 'green' : 'red', fontSize: '12px', marginTop: '10px' }">
{{ uploadMessage }}
</div>
<template #footer>
<span>
<el-button @click="close">取消</el-button>
<el-button
type="primary"
size="small"
@click="handlerImportCategory"
:disabled="!filter.storeId || !selectedFile"
>
导入
</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
export default {
name: 'importCategory',
data() {
return {
filter: {
productName: '',
storeId: '',
},
visible: false,
shopList: [],
selectedFile: null,
fileName: '',
uploadMessage: '',
uploadMessageType: '' // success/error
}
},
mounted() {
this.getShopList()
},
methods: {
open() {
this.visible = true
this.selectedFile = null
this.fileName = ''
this.uploadMessage = ''
this.uploadMessageType = ''
},
async getShopList() {
try {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items || []
} catch (error) {
console.error('获取店铺列表失败:', error)
}
},
//
openFileSelector() {
this.$refs.fileInput.click()
},
//
handleFileChange(e) {
const file = e.target.files[0]
if (file) {
//
const fileExt = file.name.split('.').pop().toLowerCase()
if (fileExt !== 'xlsx' && fileExt !== 'xls') {
this.uploadMessage = '请选择Excel文件(.xlsx/.xls)'
this.uploadMessageType = 'error'
this.selectedFile = null
this.fileName = ''
// input便
this.$refs.fileInput.value = ''
return
}
//
const maxSize = 5 * 1024 * 1024 // 5MB
if (file.size > maxSize) {
this.uploadMessage = '文件大小不能超过5MB'
this.uploadMessageType = 'error'
this.selectedFile = null
this.fileName = ''
this.$refs.fileInput.value = ''
return
}
this.selectedFile = file
this.fileName = file.name
this.uploadMessage = '' //
}
},
async handlerImportCategory() {
if (!this.filter.storeId) {
this.uploadMessage = '请先选择店铺'
this.uploadMessageType = 'error'
return
}
if (!this.selectedFile) {
this.uploadMessage = '请先选择要上传的Excel文件'
this.uploadMessageType = 'error'
return
}
try {
const formData = new FormData()
formData.append('file', this.selectedFile)
formData.append('storeId', this.filter.storeId)
this.uploadMessage = '正在上传文件,请稍候...'
this.uploadMessageType = 'success'
const res = await GoodsToolApi.shopImportData(formData)
if (res.status == 200) {
this.uploadMessage = '文件上传成功!'
this.uploadMessageType = 'success'
//
this.selectedFile = null
this.fileName = ''
this.$refs.fileInput.value = ''
this.$message.success('导入商品数据成功!')
this.visible=false
} else {
this.uploadMessage = res.msg || '文件上传失败,请重试'
this.uploadMessageType = 'error'
}
} catch (error) {
console.error('文件上传失败:', error)
this.uploadMessage = '文件上传失败:' + (error.message || '网络错误')
this.uploadMessageType = 'error'
}
},
close() {
this.visible = false
this.selectedFile = null
this.fileName = ''
this.uploadMessage = ''
this.filter.storeId = ''
if (this.$refs.fileInput) {
this.$refs.fileInput.value = ''
}
}
}
}
</script>
<style scoped>
.el-dialog {
height: 300px !important;
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<div class="goods_tool_container">
<div class="list">
<div class="tool">
<el-button type="primary" @click="downloadBrandTemplate">品牌模板下载</el-button>
<el-button type="success" @click="downloadCategoryTemplate">商品分类导入模板下载</el-button>
<el-button plain size="mini" type="info" @click="downloadGoodsTemplate">商品导入模板下载</el-button>
<el-button type="warning" @click="openBrandImport">品牌数据导入</el-button>
<el-button type="danger" @click="openCategoryImport">商品分类数据导入</el-button>
<el-button @click="openGoodsImport">商品数据导入</el-button>
</div>
</div>
<importCategory ref="importCategoryRef"></importCategory>
<importBrand ref="importBrandRef"></importBrand>
<importGoods ref="importGoodsRef"></importGoods>
</div>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
import importBrand from '@/views/product/goodsImport/importBrand.vue'
import importCategory from '@/views/product/goodsImport/importCategory.vue'
import importGoods from '@/views/product/goodsImport/importGoods.vue'
export default {
components: {importBrand,importCategory,importGoods},
data() {
return {
importCategoryRef:null,
importBrandRef:null,
importGoodsRef:null,
}
},
mounted() {},
methods: {
async downloadBrandTemplate() {
const res = await GoodsToolApi.downloadBrandTemplate()
if (res) {
this.$message.success('操作成功')
this.downloadFile(res, '品牌模板.xlsx')
}
},
async downloadCategoryTemplate() {
const res = await GoodsToolApi.downloadCategoryTemplate()
if (res) {
this.$message.success('操作成功')
this.downloadFile(res, '商品分类导入模板.xlsx')
}
},
async downloadGoodsTemplate() {
const res = await GoodsToolApi.downloadGoodsTemplate()
if (res) {
this.$message.success('操作成功')
this.downloadFile(res, '商品导入模板.xlsx')
}
},
downloadFile(blobData, fileName) {
const url = window.URL.createObjectURL(new Blob([blobData]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
},
openBrandImport(){
this.$refs.importBrandRef?.open()
},
openCategoryImport(){
this.$refs.importCategoryRef?.open()
},
openGoodsImport(){
this.$refs.importGoodsRef?.open()
}
},
}
</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);
}
.tool {
display: flex;
gap: 10px;
::v-deep .el-button {
margin-left: 0 !important;
}
}
</style>

View File

@ -57,15 +57,19 @@
<el-row :gutter="20">
<el-col :span="12">
<div class="grid-content bg-purple">
<el-form-item :label="__('砍价有效期')" prop="cut_hour">
<el-input v-model="form.cut_hour" placeholder="请输入有效期(小时)" clearable />
<el-form-item :label="__('砍价有效期(小时)')" prop="cut_hour">
<el-input v-model="form.cut_hour" type="number" placeholder="请输入有效期(小时)" clearable />
</el-form-item>
</div>
</el-col>
<el-col :span="12">
<div class="grid-content bg-purple"></div>
<el-form-item :label="__('活动商品总数')" prop="product_count">
<el-input v-model="form.product_count" placeholder="请输入参与活动商品总数" clearable />
<el-input v-model="form.product_count" type="number" placeholder="请输入参与活动商品总数" clearable
:max="form.item_quantity || ''"
@input="handleProductCountInput"
:disabled="!form.item_id"
/>
</el-form-item>
</el-col>
</el-row>
@ -112,7 +116,7 @@
:min="2"
/>
<el-tooltip
content="砍价成功的人数例如设置为3人一共要3个人砍价才能达到底价下单默认最少为2"
content="砍价成功的人数例如设置为3人底价为0.013个人砍价之后才能达到底价0.01,然后下单默认最少为2"
placement="top"
effect="dark"
style="margin-left: 8px;"
@ -134,7 +138,7 @@
style="width: 200px;"
/>
<el-tooltip
content="在活动期间每个人一共可以助力好友的人数默认最少为5"
content="在活动期间每个人一共可以助力好友的次数,即该活动你一共可以帮多少人砍价默认最少为5"
placement="top"
effect="dark"
style="margin-left: 8px;"
@ -145,7 +149,7 @@
</el-form-item>
<el-form-item :label="__('砍价第一刀')" prop="cut_first_type">
<el-radio-group v-model="form.cut_first_type">
<el-radio-group v-model="form.cut_first_type" @change="handleCutFirstTypeChange">
<el-radio :label="1" >
<span>{{ __('首刀比例') }}</span>
<el-tooltip
@ -171,19 +175,32 @@
</el-form-item>
<el-form-item label="首刀比例(单位-%" size="normal" v-if='form.cut_first_type == 1'>
<el-input v-model="form.cut_first_percent" placeholder="请输入首刀比例" clearable />
<el-input v-model="form.cut_first_percent" type="number" placeholder="请输入首刀比例" clearable />
</el-form-item>
<el-form-item label="首刀金额(单位/块)" size="normal" v-if='form.cut_first_type == 2'>
<el-input v-model="form.cut_first_price " placeholder="请输入首刀金额" clearable />
<el-form-item label="首刀金额(单位/块)" size="normal" v-if='form.cut_first_type == 2'>
<el-input v-model="form.cut_first_price" type="number" placeholder="请输入首刀金额" clearable />
</el-form-item>
</el-form>
<el-form ref="touzi" label-width="250px" :model="touzi" :rules="touziRules">
<el-form-item label="砍价幸运骰子个数" label-width="150px">
<div style="display: flex; align-items: center;">
<el-input v-model="touzi.lottery_num" placeholder="请输入骰子的个数" clearable style="width: 200px;" type="number"/>
<el-tooltip
content="该活动一共可以投骰子用于翻倍砍价金额的次数"
placement="top"
effect="dark"
style="margin-left: 8px;"
>
<i class="el-icon-question" style="cursor: pointer; color: #909399; font-size: 14px;"></i>
</el-tooltip>
</div>
</el-form-item>
<!-- 总标签 + 问号提示 -->
<el-form-item label="砍价后投骰子的点数(单位-%" class="total-label-item">
<el-tooltip
content="设置骰子不同点数对应的中奖比例6个点数比例总和不能超过100%"
content="设置骰子不同点数对应的中奖比例6个点数比例总和不能超过100%,可以使用默认预设点数145%点数245%点数32%点数43%点数52%点数63%"
placement="top"
effect="dark"
style="margin-left: 8px;"
@ -231,6 +248,7 @@
form: {
product_item_name: '',
item_id: '',
item_quantity:0,
cut_down_type: 2,
cut_down_user_num: 0,
cut_down_fixed_price: '',
@ -254,7 +272,7 @@
{doubleValue:'5',probability:'2'},
{doubleValue:"6",probability:'3'},
],
lottery_num:1
lottery_num:3
},
touziErrorMsg: '', //
touziRules: {
@ -314,6 +332,32 @@
],
product_count: [
{ required: true, trigger: 'blur', message: this.__('参与活动商品总数') },
{
validator: (rule, value, callback) => {
// item_id
if (!this.form.item_id) {
callback(new Error(this.__('请先选择砍价商品')));
return;
}
// 00
if (Number(this.form.item_quantity) <= 0) {
callback(new Error(this.__('该商品库存为0无法设置活动商品总数')));
return;
}
const count = Number(value);
const max = Number(this.form.item_quantity);
if (isNaN(count)) {
callback(new Error(this.__('请输入有效的数字')));
} else if (count <= 0) {
callback(new Error(this.__('活动商品总数不能小于等于0')));
} else if (count > max) {
callback(new Error(this.__(`活动商品总数不能超过商品库存${max}`)));
} else {
callback();
}
},
trigger: ['blur', 'change', 'submit']
}
],
},
}
@ -332,6 +376,12 @@
getCheckItem(row) {
this.form.item_id = row.item_id
this.form.product_item_name = row.product_item_name
this.form.item_quantity = row.item_quantity;
//
if (this.form.product_count && Number(this.form.product_count) > Number(this.form.item_quantity)) {
this.form.product_count = this.form.item_quantity;
this.$baseMessage(`活动商品总数不能超过商品库存${this.form.item_quantity},已自动重置`, 'warning');
}
this.$refs['form'].validateField(
['product_item_name', 'item_id'],
(errorMsg) => {
@ -361,6 +411,7 @@
cut_down_user_num: row.activity_rule.cut_down_user_num,
cut_hour:row.cut_hour,
product_count:row.product_count,
//item_quantity: row.activity_rule.item_quantity
user_cutprice_num:row.activity_rule.user_cutprice_num,
cut_first_type:row.activity_rule.cut_first_type,//
cut_first_percent:row.activity_rule.cut_first_percent,
@ -374,12 +425,29 @@
},
tranformData(lucky_turn){
if(this.form.cut_first_percent){
this.form.cut_first_type=1
this.form.cut_first_type=1;
this.form.cut_first_price = ''
}else if(this.form.cut_first_price){
this.form.cut_first_type=2
this.form.cut_first_percent = ''
}
this.touzi=JSON.parse(lucky_turn)
},
// methods
handleProductCountInput() {
if (!this.form.item_quantity) return;
const count = Number(this.form.product_count);
const max = Number(this.form.item_quantity);
//
if (count > max) {
this.form.product_count = max;
this.$baseMessage(`活动商品总数最大为${max}`, 'warning');
}
// /
if (count <= 0 || isNaN(count)) {
this.form.product_count = '';
}
},
close() {
this.form = {
product_item_name: '',
@ -402,6 +470,16 @@
showItemTable() {
this.$refs['productItemTable'].showTable()
},
//
handleCutFirstTypeChange(type) {
if (type === 1) {
//
this.form.cut_first_price = '';
} else if (type === 2) {
//
this.form.cut_first_percent = '';
}
},
diceObjToStr(){
this.form.diceObj = JSON.stringify(this.touzi);
},

View File

@ -0,0 +1,155 @@
<template>
<el-dialog title="新增密钥" width="50%" :visible.sync="visible">
<div>
<el-form v-model="syncAppForm" label-position="left" label-width="80px">
<el-form-item label="app_key" size="normal">
<el-input
v-model="syncAppForm.app_key"
placeholder="请输入app_key"
clearable
/>
</el-form-item>
<el-form-item label="app_secret" size="normal">
<el-input
v-model="syncAppForm.app_secret"
placeholder="请输入app_secret"
clearable
/>
</el-form-item>
<el-form-item label="店铺" size="normal">
<el-select
v-model="syncAppForm.store_id"
clearable
filterable
placeholder="选择店铺"
style="width: 200px"
@change="handleStoreChange"
>
<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="介绍" size="normal">
<el-input
v-model="syncAppForm.intro"
style="width: 300px"
placeholder="请输入介绍"
clearable
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="syncAppForm.status"
clearable
filterable
@change="handleSyncAppStatus"
>
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submit">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
import { saveSyncApp } from '@/api/syncApp.js'
export default {
name: 'addSyncApp',
data() {
return {
visible: false,
syncAppForm: {
app_key: '',
app_secret: '',
store_id: '',
name: '',
intro: '',
status: 0,
},
filter: {
productName: '',
storeId: '',
},
shopList: [],
statusList: [
{
label: '可用',
value: 1,
},
{
label: '不可用',
value: 2,
},
{
label: '不校验签名',
value: 3,
},
],
}
},
mounted() {
this.getShopList()
},
methods: {
open() {
//
this.syncAppForm = {
app_key: '',
app_secret: '',
store_id: '',
name: '',
intro: '',
status: 1,
}
this.visible = true
},
async getShopList() {
try {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items || []
} catch (error) {
console.error('获取店铺列表失败:', error)
}
},
handleStoreChange(storeId) {
this.syncAppForm.name = ''
const selectedShop = this.shopList.find(
(item) => item.store_id === storeId
)
if (selectedShop) {
this.syncAppForm.name = selectedShop.store_name
}
},
async submit() {
let res = await saveSyncApp(this.syncAppForm)
console.log(res)
if (res.status == 200) {
this.visible = false
this.$emit('success')
}
},
handleSyncAppStatus(e) {
this.syncAppForm.status = e
},
},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,131 @@
<template>
<el-dialog title="编辑密钥" width="50%" :visible.sync="visible">
<div>
<el-form v-model="syncAppForm" label-position="left" label-width="80px">
<el-form-item label="app_key" size="normal">
<el-input
v-model="syncAppForm.app_key"
placeholder="请输入app_key"
clearable
/>
</el-form-item>
<el-form-item label="app_secret" size="normal">
<el-input
v-model="syncAppForm.app_secret"
placeholder="请输入app_secret"
clearable
/>
</el-form-item>
<el-form-item label="店铺" size="normal">
<el-input
v-model="syncAppForm.name"
style="width: 200px;"
disabled
clearable/>
</el-form-item>
<el-form-item label="介绍" size="normal">
<el-input
v-model="syncAppForm.intro"
style="width: 200px;"
placeholder="请输入介绍"
clearable
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="syncAppForm.status"
clearable
filterable
@change="handleSyncAppStatus"
>
<el-option
v-for="item in statusList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-form>
</div>
<template #footer>
<span>
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="changeSumbit">确认</el-button>
</span>
</template>
</el-dialog>
</template>
<script>
import GoodsToolApi from '@/api/goodsTool'
import { editSyncApp } from '@/api/syncApp';
export default {
name: 'editSyncApp',
data() {
return {
visible: false,
syncAppForm: {
app_key: '',
app_secret: '',
store_id: '',
name: '',
intro: '',
status: 0,
},
filter: {
productName: '',
storeId: '',
},
shopList: [],
statusList: [
{
label: '可用',
value: 1,
},
{
label: '不可用',
value: 2,
},
{
label: '不校验签名',
value: 3,
},
],
}
},
mounted() {
this.getShopList()
},
methods: {
open(row) {
this.visible = true
this.syncAppForm = { ...row }
},
close(){
this.visible=false
},
async getShopList() {
try {
let res = await GoodsToolApi.getShopList()
this.shopList = res.data.items || []
} catch (error) {
console.error('获取店铺列表失败:', error)
}
},
async changeSumbit(){
let res = await editSyncApp(this.syncAppForm)
console.log("sdfasa",res)
if(res.status== 200){
this.visible=false
this.$emit('success')
}
},
handleSyncAppStatus(e) {
this.syncAppForm.status = e
},
},
}
</script>
<style scoped></style>

View File

@ -0,0 +1,106 @@
<template>
<div class="db-config-management">
<el-card class="list">
<div slot="header" class="tool">
<el-button type="primary" @click="openAddSyncApp">新增</el-button>
</div>
<el-table border :data="SyncAppList" stripe>
<el-table-column label="操作" width="250">
<template slot-scope="scope">
<el-button size="mini" type="primary" @click="openEdit(scope.row)">
编辑
</el-button>
<el-button size="mini" type="danger" @click="handlerDelete(scope.row.id)">
删除
</el-button>
</template>
</el-table-column>
<el-table-column label="店铺id" prop="store_id" />
<el-table-column label="店铺名字" prop="name" />
<el-table-column label="app_key" prop="app_key" />
<el-table-column label="密钥" prop="app_secret" />
<el-table-column label="店铺介绍" prop="intro" />
<el-table-column label="状态" prop="status">
<template slot-scope="scope">
{{
scope.row.status == 1 ? '可用' :
scope.row.status == 2 ? '不可用' :
scope.row.status == 3 ? '不校验签名':
'其他'
}}
</template>
</el-table-column>
</el-table>
</el-card>
<addSyncApp ref="addSyncAppRef" @success="refreshList"></addSyncApp>
<editSync ref="editSyncAppRef" @success="refreshList"></editSync>
</div>
</template>
<script>
import addSyncApp from '@/views/store/storeSyncApp/addSyncApp.vue'
import editSync from '@/views/store/storeSyncApp/editSync.vue'
import {
getSyncAppList,
saveSyncApp,
editSyncApp,
deleteSyncApp,
} from '@/api/syncApp.js'
export default {
components: { addSyncApp, editSync },
data() {
return {
addSyncAppRef: null,
editSyncAppRef: null,
SyncAppList: [],
}
},
created() {
this.getSyncList()
},
methods: {
openAddSyncApp() {
this.$refs.addSyncAppRef?.open()
},
openEdit(row) {
this.$refs.editSyncAppRef?.open(row)
},
async getSyncList() {
const res = await getSyncAppList()
this.SyncAppList = res.records
},
refreshList(){
this.getSyncList()
},
async handlerDelete(id) {
try {
await this.$confirm(
'此操作将永久删除该条记录, 是否继续?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
const res = await deleteSyncApp(id)
if(res.status==200){
this.$message.success('删除成功!')
this.getSyncList()
}
} catch (error) {
if (error !== 'cancel') {
this.$message.error('删除失败!')
console.error('删除出错:', error)
} else {
this.$message.info('已取消删除')
}
}
},
},
}
</script>
<style scoped></style>