feat: 店铺数据库初始化配置

This commit is contained in:
mixtan 2025-07-03 10:02:02 +08:00
parent 322fd727e0
commit 72943a4cd1
4 changed files with 683 additions and 2 deletions

62
src/api/storeConf.js Normal file
View File

@ -0,0 +1,62 @@
import request from '@/utils/request'
import { stringify } from 'qs'
export async function geConfList(data) {
data = stringify(data)
return request({
url: `/admin/shop/shop-sync-storeDbConfig/list?${data}`,
method: 'get',
})
}
export async function createConfList(data) {
return request({
url: '/admin/shop/shop-sync-storeDbConfig/saveStoreDbConfig',
method: 'post',
headers: {
'Content-Type': 'application/json',
},
data,
})
}
export async function updateConfList(data) {
return request({
url: '/admin/shop/shop-sync-storeDbConfig/updateStoreDbConfig',
method: 'put',
headers: {
'Content-Type': 'application/json',
},
data,
})
}
export async function deleteConfList(data) {
return request({
url: '/admin/shop/shop-sync-storeDbConfig/delStoreDbConfig',
method: 'put',
headers: {
'Content-Type': 'application/json',
},
data,
})
}
export async function createConfSecretkKey(data) {
return request({
url: '/admin/shop/shop-sync-storeDbConfig/getPrimaryKey',
method: 'put',
headers: {
'Content-Type': 'application/json',
},
data,
})
}
export default {
geConfList,
createConfList,
updateConfList,
deleteConfList,
createConfSecretkKey,
}

View File

@ -28,9 +28,27 @@ export function convertRouter(asyncRoutes) {
menuHidden: false,
}
route.children.splice(0, 0, obj)
route.children.push(obj)
}
if (route.meta.title == '店铺' && route.name == 'Vab330') {
const obj = {
path: '/goodsTool',
component: '@/views/product/goodsTool/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 = {
path: '/shopAudit',

View File

@ -0,0 +1,281 @@
<template>
<el-drawer
direction="rtl"
size="50%"
:title="isEdit ? '编辑店铺数据库配置' : '新增店铺数据库配置'"
:visible.sync="myVisible"
>
<div class="scrollable-content">
<el-form
ref="formRef"
label-width="120px"
:model="innerFormData"
:rules="rules"
>
<el-form-item label="店铺ID" prop="storeId">
<el-select v-model="innerFormData.storeId" placeholder="请选择店铺">
<el-option
v-for="item in storeIdOptions"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"
/>
</el-select>
</el-form-item>
<el-form-item label="数据库IP地址" prop="dbIp">
<el-input v-model="innerFormData.dbIp" />
</el-form-item>
<el-form-item label="数据库类型" prop="dbType">
<el-select
v-model="innerFormData.dbType"
placeholder="请选择数据库类型"
>
<el-option label="SQL Server" value="sqlserver" />
<el-option label="MySQL" value="mysql" />
<el-option label="Oracle" value="oracle" />
</el-select>
</el-form-item>
<el-form-item label="数据库名称" prop="dbName">
<el-input v-model="innerFormData.dbName" />
</el-form-item>
<el-form-item label="数据库端口" prop="dbPort">
<el-input v-model="innerFormData.dbPort" type="number" />
</el-form-item>
<el-form-item label="数据库用户名" prop="dbUsername">
<el-input v-model="innerFormData.dbUsername" />
</el-form-item>
<el-form-item label="数据库密码" prop="dbPassword">
<el-input v-model="innerFormData.dbPassword" type="text" />
</el-form-item>
<el-form-item label="定时同步表达式" prop="cronExpression">
<el-input v-model="innerFormData.cronExpression" />
</el-form-item>
<el-form-item label="刷新时间" prop="refreshTime">
<el-date-picker
v-model="innerFormData.refreshTime"
:disabled="isEdit"
placeholder="请选择刷新时间"
type="datetime"
>
>
</el-date-picker>
</el-form-item>
<el-form-item label="是否有外网访问" prop="hasInternet">
<el-radio-group v-model="innerFormData.hasInternet">
<el-radio :label="'1'"></el-radio>
<el-radio :label="'0'"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="同步模式" prop="syncMode">
<el-radio-group v-model="innerFormData.syncMode">
<el-radio :label="'1'">定时同步</el-radio>
<el-radio :label="'2'">间隔同步</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否启用" prop="hasStart">
<el-radio-group v-model="innerFormData.hasStart">
<el-radio :label="'1'"></el-radio>
<el-radio :label="'0'"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否双向同步" prop="isTowSync">
<el-radio-group v-model="innerFormData.isTowSync" :disabled="isEdit">
<el-radio :label="'1'"></el-radio>
<el-radio :label="'0'"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="备注信息" prop="remark">
<el-input v-model="innerFormData.remark" type="textarea" />
</el-form-item>
</el-form>
</div>
<div class="drawer-footer">
<el-button type="primary" @click="handleSubmit">确定</el-button>
<el-button @click="myVisible = false">取消</el-button>
</div>
</el-drawer>
</template>
<script>
export default {
props: {
visible: {
type: Boolean,
default: false,
},
formData: {
type: Object,
default: () => ({}),
},
storeIdOptions: {
type: Array,
default: () => [],
},
isEdit: {
type: Boolean,
default: false,
},
},
data() {
return {
// props
innerFormData: this.initFormData(),
rules: {
storeId: [{ required: true, message: '请选择店铺', trigger: 'blur' }],
dbIp: [
{ required: true, message: '请输入数据库IP地址', trigger: 'blur' },
{
pattern:
/^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,
message: '请输入有效的IP地址',
trigger: 'blur',
},
],
dbType: [
{ required: true, message: '请选择数据库类型', trigger: 'change' },
],
dbName: [
{ required: true, message: '请输入数据库名称', trigger: 'blur' },
],
dbPort: [
{ required: true, message: '请输入数据库端口', trigger: 'blur' },
],
dbUsername: [
{ required: true, message: '请输入数据库用户名', trigger: 'blur' },
],
dbPassword: [
{ required: true, message: '请输入数据库密码', trigger: 'blur' },
],
hasInternet: [
{
required: true,
message: '请选择是否有外网访问',
trigger: 'change',
},
],
syncMode: [
{ required: true, message: '请选择同步模式', trigger: 'change' },
],
hasStart: [
{ required: true, message: '请选择是否启用', trigger: 'change' },
],
remark: [
{ required: true, message: '请输入备注信息', trigger: 'change' },
],
refreshTime: [
{ required: true, message: '请选择刷新时间', trigger: 'change' },
],
isTowSync: [
{
required: true,
message: '请选择是否双向同步',
trigger: 'change',
},
],
cronExpression: [
{
required: true,
message: '请输入定时同步表达式',
trigger: 'blur',
},
],
},
myVisible: false,
}
},
watch: {
// props
formData: {
handler(newVal) {
this.innerFormData = this.cloneData(newVal)
this.$nextTick(() => {
this.$refs.formRef && this.$refs.formRef.validate()
})
},
deep: true,
immediate: true,
},
},
methods: {
//
open() {
this.myVisible = true
},
//
initFormData() {
return {
storeId: '',
dbIp: '',
dbType: '',
dbName: '',
dbPort: '',
dbUsername: '',
dbPassword: '',
hasInternet: '1',
syncMode: '1',
hasStart: '1',
cronExpression: '',
remark: '',
refreshTime: '',
isTowSync: '0',
}
},
//
cloneData(data) {
return JSON.parse(JSON.stringify(data))
},
//
resetForm() {
this.innerFormData = this.isEdit
? this.cloneData(this.formData)
: this.initFormData()
this.$refs.formRef && this.$refs.formRef.resetFields()
},
handleSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.$emit('save', this.cloneData(this.innerFormData))
} else {
this.$message.error('请完善表单信息')
return false
}
})
},
},
}
</script>
<style lang="scss" scoped>
::v-deep .el-drawer__header {
margin-bottom: 15px !important;
}
.drawer-footer {
display: flex;
justify-content: flex-start;
padding: 15px 20px;
border-top: 1px solid #ebeef5;
}
.scrollable-content {
padding: 12px;
height: calc(100vh - 150px);
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,320 @@
<template>
<div class="db-config-management">
<!-- 查询条件区域 -->
<el-card class="filter">
<el-form class="demo-form-inline" :inline="true" :model="searchForm">
<el-form-item label="店铺ID">
<el-select v-model="searchForm.storeId" placeholder="请选择店铺ID">
<el-option
v-for="item in storeIdOptions"
:key="item.store_id"
:label="item.store_name"
:value="item.store_id"
/>
</el-select>
</el-form-item>
<el-form-item label="是否有外网访问">
<el-select v-model="searchForm.hasInternet" placeholder="请选择">
<el-option label="有" value="1" />
<el-option label="无" value="0" />
</el-select>
</el-form-item>
<el-form-item label="是否启用">
<el-select v-model="searchForm.hasStart" placeholder="请选择">
<el-option label="是" value="1" />
<el-option label="否" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- 数据列表区域 -->
<el-card class="list">
<div slot="header" class="tool">
<el-button type="primary" @click="openAddDrawer">新增</el-button>
</div>
<el-table border :data="dbConfigList" stripe>
<el-table-column label="店铺ID" prop="storeId" />
<el-table-column label="数据库IP地址" prop="dbIp" />
<el-table-column label="数据库类型" prop="dbType" />
<el-table-column label="数据库名称" prop="dbName" />
<el-table-column label="数据库端口" prop="dbPort" />
<el-table-column label="数据库用户名" prop="dbUsername" />
<el-table-column label="数据库密码" prop="dbPassword" />
<el-table-column label="外网访问" prop="hasInternet">
<template slot-scope="scope">
<el-tag :type="scope.row.hasInternet === '1' ? 'success' : 'info'">
{{ scope.row.hasInternet === '1' ? '有' : '无' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="同步模式" prop="syncMode">
<template slot-scope="scope">
{{ scope.row.syncMode === '1' ? '定时同步' : '间隔同步' }}
</template>
</el-table-column>
<el-table-column label="是否启用" prop="hasStart">
<template slot-scope="scope">
<el-tag :type="scope.row.hasStart === '1' ? 'success' : 'danger'">
{{ scope.row.hasStart === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="同步表达式" prop="cronExpression" />
<el-table-column label="备注信息" prop="remark" />
<el-table-column label="刷新时间" prop="refreshTime" />
<el-table-column label="是否双向同步" prop="isTowSync">
<template slot-scope="scope">
<el-tag :type="scope.row.isTowSync === '1' ? 'success' : 'info'">
{{ scope.row.isTowSync === '1' ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="250">
<template slot-scope="scope">
<el-button
size="mini"
type="primary"
@click="openEditDrawer(scope.row)"
>
编辑
</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.row)"
>
删除
</el-button>
<el-button
size="mini"
type="warning"
@click="generateKey(scope.row)"
>
密钥生成
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页组件 -->
<el-pagination
:current-page="pagination.pageNum"
layout="total, sizes, prev, pager, next, jumper"
:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-card>
<!-- 新增/编辑抽屉组件 -->
<db-config-form
ref="storeConfDrawerRef"
:form-data="currentFormData"
:is-edit="isEditMode"
:store-id-options="storeIdOptions"
@save="handleSave"
/>
<!-- 密钥生成弹窗 -->
<el-dialog title="生成密钥结果" :visible="keyDialogVisible" width="30%">
<div>
<p>您的密钥为</p>
<el-input v-model="generatedKey" readonly />
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="keyDialogVisible = false">
好的
</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script>
import DbConfigForm from './DbConfigForm.vue'
import StoreConfApi from '@/api/storeConf'
import GoodsToolApi from '@/api/goodsTool'
import { omit } from 'lodash'
export default {
components: {
DbConfigForm,
},
data() {
return {
searchForm: {
storeId: '',
hasInternet: '',
hasStart: '',
},
storeIdOptions: [],
dbConfigList: [],
pagination: {
pageNum: 1,
pageSize: 10,
total: 0,
},
total: 0,
drawerVisible: false,
currentFormData: {},
isEditMode: false,
keyDialogVisible: false,
generatedKey: '',
}
},
created() {
this.getShopList()
this.fetchDbConfigList()
},
methods: {
async getShopList() {
let res = await GoodsToolApi.getShopList()
if (res.status == 200) {
this.storeIdOptions = res.data.items
}
},
//
async fetchDbConfigList() {
const params = {
...this.searchForm,
pageNum: this.pagination.pageNum,
pageSize: this.pagination.pageSize,
}
let res = {}
res = await StoreConfApi.geConfList(params)
if (res.status == 200) {
this.dbConfigList = res.data.items
this.pagination.total = res.data.records
}
},
//
handleSearch() {
this.pagination.pageNum = 1
this.pagination.pageSize = 10
this.fetchDbConfigList()
},
//
resetSearch() {
this.searchForm = {
storeId: '',
hasInternet: '',
hasStart: '',
}
this.pagination.pageNum = 1
this.pagination.pageSize = 10
this.handleSearch()
},
//
handleSizeChange(newSize) {
this.pagination.pageSize = newSize
this.fetchDbConfigList()
},
handleCurrentChange(newPage) {
this.pagination.pageNum = newPage
this.fetchDbConfigList()
},
//
openAddDrawer() {
this.isEditMode = false
this.currentFormData = {}
this.$refs.storeConfDrawerRef.open()
},
//
openEditDrawer(row) {
this.isEditMode = true
this.currentFormData = { ...row } //
this.$refs.storeConfDrawerRef.open()
},
//
async handleSave(data) {
let res = null
if (this.isEditMode) {
res = await StoreConfApi.updateConfList({
...omit(data, ['refreshTime', 'isTowSync']),
})
} else {
res = await StoreConfApi.createConfList(data)
}
if (res.status == 200) {
this.$message.success('操作成功')
this.fetchDbConfigList()
}
this.drawerVisible = false
},
//
handleDelete(row) {
this.$confirm('确定要删除该配置吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
let res = await StoreConfApi.deleteConfList(row)
if (res.status == 200) {
this.$message.success('删除成功')
this.fetchDbConfigList()
} else {
this.$message.error(res?.msg)
}
})
},
//
generateKey(row) {
this.$confirm('确定要生成新的密钥吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => {
let res = await StoreConfApi.createConfSecretkKey(row)
if (res.status == 200) {
this.generatedKey = res.data
this.keyDialogVisible = true
} else {
this.$message.error(res?.msg)
}
})
},
},
}
</script>
<style scoped>
.filter {
margin-bottom: 15px;
}
.list {
.tool {
display: flex;
gap: 10px;
::v-deep .el-button {
margin-left: 0 !important;
}
}
}
</style>