Merge branch 'flipped-aurora:main' into main

This commit is contained in:
薛定饿了不养喵
2024-03-28 01:59:21 +08:00
committed by GitHub
96 changed files with 1809 additions and 1339 deletions
+3 -4
View File
@@ -1,8 +1,7 @@
ENV = 'production'
VITE_CLI_PORT = 8080
VITE_SERVER_PORT = 8888
#下方为上线需要用到的程序代理前缀,一般用于nginx代理转发
VITE_BASE_API = /api
VITE_FILE_API = /api
#下方修改为你的线上ip
VITE_BASE_PATH = https://demo.gin-vue-admin.com
#下方修改为你的线上ip(如果需要在线使用表单构建工具时使用,其余情况无需使用以下环境变量)
VITE_BASE_PATH = https://demo.gin-vue-admin.com
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "gin-vue-admin",
"version": "2.5.9",
"version": "2.6.1",
"private": true,
"scripts": {
"serve": "node openDocument.js && vite --host --mode development",
@@ -32,7 +32,7 @@
"screenfull": "^6.0.2",
"spark-md5": "^3.0.2",
"tailwindcss": "^3.3.3",
"vue": "^3.3.4",
"vue": "^3.4.21",
"vue-router": "^4.2.4"
},
"devDependencies": {
-31
View File
@@ -1,31 +0,0 @@
import service from '@/utils/request'
export const getTableApi = (data) => {
return service({
url: '/chatGpt/getTable',
method: 'post',
data
})
}
export const createSKApi = (data) => {
return service({
url: '/chatGpt/createSK',
method: 'post',
data
})
}
export const getSKApi = () => {
return service({
url: '/chatGpt/getSK',
method: 'get',
})
}
export const deleteSKApi = () => {
return service({
url: '/chatGpt/deleteSK',
method: 'delete'
})
}
+2 -1
View File
@@ -9,7 +9,8 @@ export const initDB = (data) => {
return service({
url: '/init/initdb',
method: 'post',
data
data,
donNotShowLoading: true
})
}
+2 -3
View File
@@ -15,11 +15,10 @@ export const login = (data) => {
// @Produce application/json
// @Param data body {username:"string",password:"string"}
// @Router /base/captcha [post]
export const captcha = (data) => {
export const captcha = () => {
return service({
url: '/base/captcha',
method: 'post',
data: data
method: 'post'
})
}
+2 -2
View File
@@ -9,12 +9,12 @@
/>
<div class="gva-btn-list">
<upload-common
v-model:imageCommon="imageCommon"
:image-common="imageCommon"
class="upload-btn-media-library"
@on-success="open"
/>
<upload-image
v-model:imageUrl="imageUrl"
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
+31 -1
View File
@@ -11,6 +11,22 @@ const props = defineProps({
templateId: {
type: String,
required: true
},
condition: {
type: Object,
default: () => ({})
},
limit: {
type: Number,
default: 0
},
offset: {
type: Number,
default: 0
},
order: {
type: String,
default: ''
}
})
@@ -22,7 +38,21 @@ const exportExcelFunc = async() => {
return
}
const baseUrl = import.meta.env.VITE_BASE_API
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}`
const paramsCopy = JSON.parse(JSON.stringify(props.condition))
if (props.limit) {
paramsCopy.limit = props.limit
}
if (props.offset) {
paramsCopy.offset = props.offset
}
if (props.order) {
paramsCopy.order = props.order
}
const params = Object.entries(paramsCopy)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
const url = `${baseUrl}/sysExportTemplate/exportExcel?templateID=${props.templateId}${params ? '&' + params : ''}`
window.open(url, '_blank')
}
</script>
+13 -14
View File
@@ -1,36 +1,35 @@
<template>
<vue-office-docx :src="docx" @rendered="rendered">
</vue-office-docx>
<vue-office-docx
:src="docx"
@rendered="rendered"
/>
</template>
<script>
export default {
name: "Docx"
name: 'Docx'
}
</script>
<script setup>
import {ref, watch} from 'vue'
import { ref, watch } from 'vue'
// 引入VueOfficeDocx组件
import VueOfficeDocx from '@vue-office/docx'
// 引入相关样式
import '@vue-office/docx/lib/index.css'
const props = defineProps({
modelValue: {
type: String,
default: () => ""
}
const model = defineModel({
type: String,
})
const docx = ref(null)
watch(
() => props.modelValue,
value => docx.value = value,
{immediate: true}
() => model,
value => { docx.value = value },
{ immediate: true }
)
const rendered = () => {
}
</script>
<style lang="scss" scoped>
</style>
</style>
+3 -7
View File
@@ -33,16 +33,12 @@ import Excel from '@/components/office/excel.vue'
const path = ref(import.meta.env.VITE_BASE_API)
const props = defineProps({
modelValue: {
type: String,
default: () => ''
}
})
const model = defineModel({ type: String})
const fileUrl = ref('')
const ext = ref('')
watch(
() => props.modelValue,
() => model,
val => {
fileUrl.value = val
const fileExt = val.split('.')[1] || ''
+177 -181
View File
@@ -1,208 +1,209 @@
<template>
<div
v-if="!multiple"
class="update-image"
:style="{
'background-image': `url(${getUrl(modelValue)})`,
'position': 'relative',
}"
>
<el-icon
v-if="isVideoExt(modelValue || '')"
:size="32"
class="video video-icon"
style=""
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(modelValue || '')"
class="avatar video-avatar video"
muted
preload="metadata"
style=""
@click="openChooseImg"
>
<source :src="getUrl(modelValue) + '#t=1'">
</video>
<span
v-if="modelValue"
class="update"
style="position: absolute;"
@click="openChooseImg"
>
<el-icon>
<delete />
</el-icon>
删除</span>
<span
v-else
class="update text-gray-600"
@click="openChooseImg"
>
<el-icon>
<plus />
</el-icon>
上传</span>
</div>
<div
v-else
class="multiple-img"
>
<div>
<div
v-for="(item, index) in multipleValue"
:key="index"
v-if="!multiple"
class="update-image"
:style="{
'background-image': `url(${getUrl(item)})`,
'background-image': `url(${getUrl(model)})`,
'position': 'relative',
}"
>
<el-icon
v-if="isVideoExt(item || '')"
v-if="isVideoExt(model || '')"
:size="32"
class="video video-icon"
style=""
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item || '')"
v-if="isVideoExt(model || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="deleteImg(index)"
style=""
@click="openChooseImg"
>
<source :src="getUrl(item) + '#t=1'">
<source :src="getUrl(model) + '#t=1'">
</video>
<span
v-if="model"
class="update"
style="position: absolute;"
@click="deleteImg(index)"
@click="openChooseImg"
>
<el-icon>
<delete />
</el-icon>
删除</span>
</div>
<div
v-if="!maxUpdateCount || maxUpdateCount>multipleValue.length"
class="add-image"
>
<span
class="update text-gray-600"
v-else
class="update text-gray-600"
@click="openChooseImg"
>
<el-icon>
<Plus />
<plus />
</el-icon>
上传</span>
</div>
</div>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<div class="gva-btn-list">
<upload-common
v-model:imageCommon="imageCommon"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<upload-image
v-model:imageUrl="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<el-form
ref="searchForm"
:inline="true"
:model="search"
>
<el-form-item label="">
<el-input
v-model="search.keyword"
class="keyword"
placeholder="请输入文件名或备注"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="search"
@click="getImageList"
>查询
</el-button>
</el-form-item>
</el-form>
</div>
<div class="media">
<div
v-else
class="multiple-img"
>
<div
v-for="(item,key) in picList"
:key="key"
class="media-box"
v-for="(item, index) in multipleValue"
:key="index"
class="update-image"
:style="{
'background-image': `url(${getUrl(item)})`,
'position': 'relative',
}"
>
<div class="header-img-box-list">
<el-image
:key="key"
:src="getUrl(item.url)"
fit="cover"
style="width: 100%;height: 100%;"
@click="chooseImg(item.url)"
>
<template #error>
<el-icon
v-if="isVideoExt(item.url || '')"
:size="32"
class="video video-icon"
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item.url || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="chooseImg(item.url)"
>
<source :src="getUrl(item.url) + '#t=1'">
您的浏览器不支持视频播放
</video>
<div
v-else
class="header-img-box-list"
>
<el-icon class="lost-image">
<icon-picture />
</el-icon>
</div>
</template>
</el-image>
</div>
<div
class="img-title"
@click="editFileNameFunc(item)"
>{{ item.name }}
</div>
<el-icon
v-if="isVideoExt(item || '')"
:size="32"
class="video video-icon"
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="deleteImg(index)"
>
<source :src="getUrl(item) + '#t=1'">
</video>
<span
class="update"
style="position: absolute;"
@click="deleteImg(index)"
>
<el-icon>
<delete />
</el-icon>
删除</span>
</div>
<div
v-if="!maxUpdateCount || maxUpdateCount>multipleValue.length"
class="add-image"
>
<span
class="update text-gray-600"
@click="openChooseImg"
>
<el-icon>
<Plus />
</el-icon>
上传</span>
</div>
</div>
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
:style="{'justify-content':'center'}"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-drawer>
<el-drawer
v-model="drawer"
title="媒体库"
size="650px"
>
<warning-bar
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
/>
<div class="gva-btn-list">
<upload-common
:image-common="imageCommon"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<upload-image
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
class="upload-btn-media-library"
@on-success="getImageList"
/>
<el-form
ref="searchForm"
:inline="true"
:model="search"
>
<el-form-item label="">
<el-input
v-model="search.keyword"
class="keyword"
placeholder="请输入文件名或备注"
/>
</el-form-item>
<el-form-item>
<el-button
type="primary"
icon="search"
@click="getImageList"
>查询
</el-button>
</el-form-item>
</el-form>
</div>
<div class="media">
<div
v-for="(item,key) in picList"
:key="key"
class="media-box"
>
<div class="header-img-box-list">
<el-image
:key="key"
:src="getUrl(item.url)"
fit="cover"
style="width: 100%;height: 100%;"
@click="chooseImg(item.url)"
>
<template #error>
<el-icon
v-if="isVideoExt(item.url || '')"
:size="32"
class="video video-icon"
>
<VideoPlay />
</el-icon>
<video
v-if="isVideoExt(item.url || '')"
class="avatar video-avatar video"
muted
preload="metadata"
@click="chooseImg(item.url)"
>
<source :src="getUrl(item.url) + '#t=1'">
您的浏览器不支持视频播放
</video>
<div
v-else
class="header-img-box-list"
>
<el-icon class="lost-image">
<icon-picture />
</el-icon>
</div>
</template>
</el-image>
</div>
<div
class="img-title"
@click="editFileNameFunc(item)"
>{{ item.name }}
</div>
</div>
</div>
<el-pagination
:current-page="page"
:page-size="pageSize"
:total="total"
:style="{'justify-content':'center'}"
layout="total, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</el-drawer>
</div>
</template>
<script setup>
@@ -214,7 +215,7 @@ import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, FolderAdd, Plus, Picture as IconPicture } from '@element-plus/icons-vue'
import { Delete, Plus, Picture as IconPicture } from '@element-plus/icons-vue'
const imageUrl = ref('')
const imageCommon = ref('')
@@ -224,11 +225,9 @@ const page = ref(1)
const total = ref(0)
const pageSize = ref(20)
const model = defineModel({ type: [String, Array] })
const props = defineProps({
modelValue: {
type: [String, Array],
default: ''
},
multiple: {
type: Boolean,
default: false
@@ -246,15 +245,12 @@ const multipleValue = ref([])
onMounted(() => {
if (props.multiple) {
multipleValue.value = props.modelValue
multipleValue.value = model.value
}
})
const emits = defineEmits(['update:modelValue'])
const deleteImg = (index) => {
multipleValue.value.splice(index, 1)
emits('update:modelValue', multipleValue.value)
model.value = multipleValue.value
}
// 分页
@@ -322,15 +318,15 @@ const chooseImg = (url) => {
}
if (props.multiple) {
multipleValue.value.push(url)
emits('update:modelValue', multipleValue.value)
model.value = multipleValue.value
} else {
emits('update:modelValue', url)
model.value = url
}
drawer.value = false
}
const openChooseImg = async() => {
if (props.modelValue && !props.multiple) {
emits('update:modelValue', '')
if (model.value && !props.multiple) {
model.value = ''
return
}
await getImageList()
+1 -1
View File
@@ -11,7 +11,7 @@
</svg>
</template>
<script setup>
import { defineProps, computed } from 'vue'
import { computed } from 'vue'
const props = defineProps({
name: {
type: String,
-2
View File
@@ -17,7 +17,6 @@
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import { isVideoMime, isImageMime } from '@/utils/image'
defineOptions({
@@ -27,7 +26,6 @@ defineOptions({
const emit = defineEmits(['on-success'])
const path = ref(import.meta.env.VITE_BASE_API)
const userStore = useUserStore()
const fullscreenLoading = ref(false)
const checkFile = (file) => {
+2 -2
View File
@@ -19,12 +19,12 @@ export const viteLogo = (env) => {
)
console.log(
chalk.green(
`> 当前版本:v2.5.9`
`> 当前版本:v2.6.1`
)
)
console.log(
chalk.green(
`> 加群方式:微信:shouzi_1994 QQ群:622360840`
`> 加群方式:微信:shouzi_1994 QQ群:470239250`
)
)
console.log(
+1 -1
View File
@@ -10,7 +10,7 @@ export default {
register(app)
console.log(`
欢迎使用 Gin-Vue-Admin
当前版本:v2.5.9
当前版本:v2.6.1
加群方式:微信:shouzi_1994 QQ群:622360840
GVA讨论社区:https://support.qq.com/products/371961
插件市场:https://plugin.gin-vue-admin.com
+2 -1
View File
@@ -21,7 +21,8 @@ export const useDictionaryStore = defineStore('dictionary', () => {
res.data.resysDictionary.sysDictionaryDetails && res.data.resysDictionary.sysDictionaryDetails.forEach(item => {
dict.push({
label: item.label,
value: item.value
value: item.value,
extend: item.extend
})
})
dictionaryRes[res.data.resysDictionary.type] = dict
+1 -2
View File
@@ -31,7 +31,6 @@ export const useUserStore = defineStore('user', () => {
const NeedInit = () => {
token.value = ''
window.localStorage.removeItem('token')
localStorage.clear()
router.push({ name: 'Init', replace: true })
}
@@ -101,7 +100,7 @@ export const useUserStore = defineStore('user', () => {
const ClearStorage = async() => {
token.value = ''
sessionStorage.clear()
localStorage.clear()
window.localStorage.removeItem('token')
cookie.remove('x-token')
}
/* 设置侧边栏模式*/
+7
View File
@@ -33,3 +33,10 @@
}
}
.el-drawer__header{
margin-bottom: 0 !important;
padding-top: 16px !important;
padding-bottom: 16px !important;
@apply border-0 border-b border-solid border-gray-200;
}
+8
View File
@@ -691,3 +691,11 @@ li {
.gva-customer-icon{
@apply w-4 h-4;
}
.el-form--inline {
.el-form-item {
& > .el-input, .el-cascader, .el-select, .el-date-editor, .el-autocomplete {
@apply w-52;
}
}
}
+10 -3
View File
@@ -7,13 +7,20 @@ export const getDict = async(type) => {
}
// 字典文字展示方法
export const showDictLabel = (dict, code) => {
export const showDictLabel = (
dict,
code,
keyCode = 'value',
valueCode = 'label'
) => {
if (!dict) {
return ''
}
const dictMap = {}
dict.forEach(item => {
dictMap[item.value] = item.label
if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) {
dictMap[item[keyCode]] = item[valueCode]
}
})
return dictMap[code]
return Reflect.has(dictMap, code) ? dictMap[code] : ''
}
+31 -15
View File
@@ -1,39 +1,44 @@
import axios from 'axios' // 引入axios
import { ElMessage, ElMessageBox } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import { emitter } from '@/utils/bus.js'
import router from '@/router/index'
import { ElLoading } from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_BASE_API,
timeout: 99999
})
let acitveAxios = 0
let activeAxios = 0
let timer
const showLoading = () => {
acitveAxios++
let loadingInstance
const showLoading = (option = {
target: null,
}) => {
const loadDom = document.getElementById('gva-base-load-dom')
activeAxios++
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
if (acitveAxios > 0) {
emitter.emit('showLoading')
if (activeAxios > 0) {
if (!option.target) option.target = loadDom
loadingInstance = ElLoading.service(option)
}
}, 400)
}
const closeLoading = () => {
acitveAxios--
if (acitveAxios <= 0) {
activeAxios--
if (activeAxios <= 0) {
clearTimeout(timer)
emitter.emit('closeLoading')
loadingInstance && loadingInstance.close()
}
}
// http request 拦截器
service.interceptors.request.use(
config => {
if (!config.donNotShowLoading) {
showLoading()
showLoading(config.loadingOption)
}
const userStore = useUserStore()
config.headers = {
@@ -78,11 +83,6 @@ service.interceptors.response.use(
message: response.data.msg || decodeURI(response.headers.msg),
type: 'error'
})
if (response.data.data && response.data.data.reload) {
userStore.token = ''
localStorage.clear()
router.push({ name: 'Login', replace: true })
}
return response.data.msg ? response.data : response
}
},
@@ -132,6 +132,22 @@ service.interceptors.response.use(
cancelButtonText: '取消'
})
break
case 401:
ElMessageBox.confirm(`
<p>无效的令牌</p>
<p>错误码:<span style="color:red"> 401 </span>错误信息:${error}</p>
`, '身份信息', {
dangerouslyUseHTMLString: true,
distinguishCancelAndClose: true,
confirmButtonText: '重新登录',
cancelButtonText: '取消'
})
.then(() => {
const userStore = useUserStore()
userStore.ClearStorage()
router.push({ name: 'Login', replace: true })
})
break
}
return error
-199
View File
@@ -1,199 +0,0 @@
<template>
<div class="gva-table-box">
<warning-bar title="使用GPT-3.5模型,存在一定不稳定性,成功率为50%左右,使用GPT-4可以极大提升成功率,但是费用较高。" />
<div v-if="!chatToken">
<el-input
v-model="skObj.sk"
class="query-ipt"
placeholder="请输入您的ChatGpt SK"
clearable
/>
<el-button
type="primary"
@click="save"
>保存</el-button>
<div class="secret">
<el-empty description="请到gpt网站获取您的sk:https://platform.openai.com/account/api-keys" />
</div>
</div>
<div v-else>
<el-form
:model="form"
label-width="120px"
>
<el-form-item label="删除当前sk:">
<el-popover
placement="top"
width="160"
>
<p>确定要删除并返回吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
@click="deleteSK"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
>删除</el-button>
</template>
</el-popover>
</el-form-item>
<el-form-item label="查询db名称:">
<el-select
v-model="form.dbname"
placeholder="请选择库"
style="width: 115px"
>
<el-option
v-for="(item, index) in dbArr"
:key="index"
:label="item.database"
:value="item.database"
/>
</el-select>
</el-form-item>
<el-form-item label="查询db描述:">
<el-input
v-model="form.chat"
:autosize="{ minRows: 2, maxRows: 4 }"
type="textarea"
clearable
placeholder="请输入对话"
/>
</el-form-item>
<el-form-item label="GPT生成SQL:">
<el-input
v-model="sql"
:autosize="{ minRows: 2, maxRows: 4 }"
type="textarea"
disabled
placeholder="此处展示自动生成的sql"
/>
</el-form-item>
<el-button
type="primary"
@click="handleQueryTable"
>查询</el-button>
</el-form>
<div class="tables">
<el-table
v-if="tableData.length"
ref="multipleTable"
:data="tableData"
style="width: 100%"
tooltip-effect="dark"
height="400px"
>
<el-table-column
v-for="(item, index) in tableData[0]"
:key="index"
:prop="index"
:label="index"
min-width="200"
show-overflow-tooltip
/>
</el-table>
<p
v-else
class="text"
>请在对话框输入你需要AI帮你查询的内容:)</p>
</div>
</div>
</div>
</template>
<script setup>
import WarningBar from '@/components/warningBar/warningBar.vue'
import { getTableApi,
createSKApi,
getSKApi,
deleteSKApi } from '@/api/chatgpt'
import { getDB as getDBAPI } from '@/api/autoCode'
import { ref, reactive } from 'vue'
const chatToken = ref(null)
const skObj = reactive({
sk: '',
})
const sql = ref('')
const getSK = async() => {
const res = await getSKApi()
chatToken.value = res.data.ok
}
const getDB = async() => {
const res = await getDBAPI()
if (res.code === 0) {
dbArr.value = res.data.dbs
}
}
getSK()
getDB()
const save = async() => {
const res = await createSKApi(skObj)
if (res.code === 0) {
await getSK()
}
}
const deleteSK = async() => {
const res = await deleteSKApi()
if (res.code === 0) {
await getSK()
}
}
const form = ref({
dbname: '',
chat: '',
})
const dbArr = ref([])
const tableData = ref([])
const handleQueryTable = async() => {
const res = await getTableApi(form.value)
if (res.code === 0) {
tableData.value = res.data.results || []
}
sql.value = res.data.sql
// 根据后台返回值动态渲染表格
}
</script>
<style scoped lang="scss">
.secret{
padding: 30px;
margin-top: 20px;
background: #F5F5F5;
p {
line-height: 30px;
}
}
.query-ipt{
width: 300px;
margin-right: 30px;
}
.content{
p {
font-size: 16px;
line-height: 20px;
}
padding: 10px;
width: 100%;
background: #F5F5F5;
margin-top: 30px;
}
.tables{
width: 100%;
margin-top: 30px;
.text{
font-size: 18px;
color: #6B7687;
text-align: center;
}
}
</style>
+1
View File
@@ -129,6 +129,7 @@ import DashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useWeatherInfo } from '@/view/dashboard/weather.js'
import SelectImage from '@/components/selectImage/selectImage.vue'
defineOptions({
name: 'Dashboard'
+23 -39
View File
@@ -59,32 +59,12 @@
icon="edit"
@click="updateCustomer(scope.row)"
>变更</el-button>
<el-popover
v-model="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteCustomer(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteCustomer(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -146,7 +126,7 @@ import {
} from '@/api/customer'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatDate } from '@/utils/format'
defineOptions({
@@ -205,18 +185,23 @@ const closeDialog = () => {
}
}
const deleteCustomer = async(row) => {
row.visible = false
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
getTableData()
}
})
}
const enterDialog = async() => {
let res
@@ -244,5 +229,4 @@ const openDialog = () => {
</script>
<style></style>
+2 -2
View File
@@ -6,11 +6,11 @@
/>
<div class="gva-btn-list">
<upload-common
v-model:imageCommon="imageCommon"
:image-common="imageCommon"
@on-success="getTableData"
/>
<upload-image
v-model:imageUrl="imageUrl"
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
@on-success="getTableData"
@@ -10,9 +10,9 @@
>
<el-tab-pane
v-for="item in historys"
:key="name(item)"
:key="getFmtString(item)"
:label="item.meta.title"
:name="name(item)"
:name="getFmtString(item)"
:tab="item"
class="gva-tab"
>
@@ -20,13 +20,13 @@
<span
:tab="item"
:style="{
color: activeValue === name(item) ? userStore.activeColor : '#333',
color: activeValue === getFmtString(item) ? userStore.activeColor : '#333',
}"
><i
class="dot"
:style="{
backgroundColor:
activeValue === name(item) ? userStore.activeColor : '#ddd',
activeValue === getFmtString(item) ? userStore.activeColor : '#ddd',
}"
/>
{{ fmtTitle(item.meta.title,item) }}</span>
@@ -48,10 +48,9 @@
</div>
</template>
<script setup>
import { emitter } from '@/utils/bus.js'
import { computed, onUnmounted, ref, watch } from 'vue'
import { computed, onUnmounted, ref, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/pinia/modules/user'
import { fmtTitle } from '@/utils/fmtRouterTitle'
@@ -73,12 +72,6 @@ const contextMenuVisible = ref(false)
const userStore = useUserStore()
const name = (item) => {
return (
item.name + JSON.stringify(item.query) + JSON.stringify(item.params)
)
}
const left = ref(0)
const top = ref(0)
const isCollapse = ref(false)
@@ -280,7 +273,7 @@ watch(() => historys.value, () => {
const initPage = () => {
// 全局监听 关闭当前页面函数
emitter.on('closeThisPage', () => {
removeTab(name(route))
removeTab(getFmtString(route))
})
// 全局监听 关闭所有页面函数
emitter.on('closeAllPage', () => {
@@ -292,6 +285,36 @@ const initPage = () => {
emitter.on('collapse', (data) => {
isCollapse.value = data
})
emitter.on('setQuery', (data) => {
const index = historys.value.findIndex(
(item) => getFmtString(item) === activeValue.value
)
historys.value[index].query = data
activeValue.value = getFmtString(historys.value[index])
const currentUrl = window.location.href.split('?')[0]
const currentSearchParams = new URLSearchParams(data).toString()
window.history.replaceState({}, '', `${currentUrl}?${currentSearchParams}`)
sessionStorage.setItem('historys', JSON.stringify(historys.value))
})
emitter.on('switchTab', async(data) => {
const index = historys.value.findIndex((item) => item.name === data.name)
if (index < 0) {
return
}
for (const key in data.query) {
data.query[key] = String(data.query[key])
}
for (const key in data.params) {
data.params[key] = String(data.params[key])
}
historys.value[index].query = data.query || {}
historys.value[index].params = data.params || {}
await nextTick()
router.push(historys.value[index])
})
const initHistorys = [
{
name: defaultRouter.value,
@@ -302,6 +325,7 @@ const initPage = () => {
params: {},
},
]
setTab(route)
historys.value =
JSON.parse(sessionStorage.getItem('historys')) || initHistorys
if (!window.sessionStorage.getItem('activeValue')) {
@@ -309,7 +333,6 @@ const initPage = () => {
} else {
activeValue.value = window.sessionStorage.getItem('activeValue')
}
setTab(route)
if (window.sessionStorage.getItem('needCloseAll') === 'true') {
closeAll()
window.sessionStorage.removeItem('needCloseAll')
+1 -10
View File
@@ -167,8 +167,7 @@
class="admin-box"
>
<div
v-loading="loadingFlag"
element-loading-text="正在加载中"
id="gva-base-load-dom"
>
<transition
mode="out-in"
@@ -218,7 +217,6 @@ const isSider = ref(true)
const isMobile = ref(false)
const first = ref('')
const dialogVisible = ref(false)
const initPage = () => {
// 判断当前用户的操作系统
if (window.localStorage.getItem('osType') === 'WIN') {
@@ -259,18 +257,11 @@ const handleCommand = () => {
command.value.open()
}
const loadingFlag = ref(false)
onMounted(() => {
// 挂载一些通用的事件
emitter.emit('collapse', isCollapse.value)
emitter.emit('mobile', isMobile.value)
emitter.on('reload', reload)
emitter.on('showLoading', () => {
loadingFlag.value = true
})
emitter.on('closeLoading', () => {
loadingFlag.value = false
})
window.onresize = () => {
return (() => {
initPage()
+10 -11
View File
@@ -183,18 +183,17 @@ const checkPassword = (rule, value, callback) => {
}
// 获取验证码
const loginVerify = () => {
captcha({}).then(async(ele) => {
rules.captcha.push({
max: ele.data.captchaLength,
min: ele.data.captchaLength,
message: `请输入${ele.data.captchaLength}位验证码`,
trigger: 'blur',
})
picPath.value = ele.data.picPath
loginFormData.captchaId = ele.data.captchaId
loginFormData.openCaptcha = ele.data.openCaptcha
const loginVerify = async() => {
const ele = await captcha()
rules.captcha.push({
max: ele.data.captchaLength,
min: ele.data.captchaLength,
message: `请输入${ele.data.captchaLength}位验证码`,
trigger: 'blur',
})
picPath.value = ele.data.picPath
loginFormData.captchaId = ele.data.captchaId
loginFormData.openCaptcha = ele.data.openCaptcha
}
loginVerify()
+39 -71
View File
@@ -62,55 +62,15 @@
class="cursor-pointer"
@click="toDoc('https://www.bilibili.com/video/BV1kv4y1g7nT?p=7&vd_source=f2640257c21e3b547a790461ed94875e')"
><VideoCameraFilled /></el-icon>
<el-popover
v-model="deleteVisible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="deleteVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onDelete"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
:disabled="!apis.length"
@click="deleteVisible = true"
>删除</el-button>
</template>
</el-popover>
<el-popover
v-model="freshVisible"
placement="top"
width="160"
>
<p>确定要刷新Casbin缓存吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="freshVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onFresh"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="Refresh"
@click="freshVisible = true"
>刷新缓存</el-button>
</template>
</el-popover>
<el-button
icon="delete"
:disabled="!apis.length"
@click="onDelete"
>删除</el-button>
<el-button
icon="Refresh"
@click="onFresh"
>刷新缓存</el-button>
</div>
<el-table
:data="tableData"
@@ -399,32 +359,40 @@ const handleSelectionChange = (val) => {
apis.value = val
}
const deleteVisible = ref(false)
const onDelete = async() => {
const ids = apis.value.map(item => item.ID)
const res = await deleteApisByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const ids = apis.value.map(item => item.ID)
const res = await deleteApisByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
deleteVisible.value = false
getTableData()
}
})
}
const freshVisible = ref(false)
const onFresh = async() => {
const res = await freshCasbin()
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
}
freshVisible.value = false
ElMessageBox.confirm('确定要刷新缓存吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await freshCasbin()
if (res.code === 0) {
ElMessage({
type: 'success',
message: res.msg
})
}
})
}
// 弹窗相关
@@ -41,7 +41,7 @@
type="primary"
link
@click="opdendrawer(scope.row)"
@click="openDrawer(scope.row)"
>设置权限</el-button>
<el-button
icon="plus"
@@ -135,7 +135,6 @@
<el-drawer
v-if="drawer"
v-model="drawer"
custom-class="auth-drawer"
:with-header="false"
size="40%"
title="角色配置"
@@ -279,7 +278,7 @@ const copyAuthorityFunc = (row) => {
copyForm.value = row
dialogFormVisible.value = true
}
const opdendrawer = (row) => {
const openDrawer = (row) => {
drawer.value = true
activeRow.value = row
}
@@ -402,7 +401,6 @@ const setOptions = () => {
setAuthorityOptions(tableData.value, AuthorityOption.value, false)
}
const setAuthorityOptions = (AuthorityData, optionsData, disabled) => {
form.value.authorityId = String(form.value.authorityId)
AuthorityData &&
AuthorityData.forEach(item => {
if (item.children && item.children.length) {
@@ -34,32 +34,13 @@
>
<Edit />
</el-icon>
<el-popover
v-model:visible="dictionary.visible"
placement="top"
width="160"
<el-icon
class="ml-2 group-hover:text-red-500"
:class="selectID === dictionary.ID ? 'text-white-800':'text-red-500'"
@click="deleteSysDictionaryFunc(dictionary)"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="dictionary.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteSysDictionaryFunc(dictionary)"
>确定</el-button>
</div>
<template #reference>
<el-icon
class="ml-2 group-hover:text-red-500"
:class="selectID === dictionary.ID ? 'text-white-800':'text-red-500'"
>
<Delete />
</el-icon>
</template>
</el-popover>
<Delete />
</el-icon>
</div>
</div>
</el-scrollbar>
@@ -148,7 +129,7 @@ import {
} from '@/api/sysDictionary' // 此处请自行替换地址
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import sysDictionaryDetail from './sysDictionaryDetail.vue'
import { Edit } from '@element-plus/icons-vue'
@@ -225,15 +206,20 @@ const closeDialog = () => {
}
}
const deleteSysDictionaryFunc = async(row) => {
row.visible = false
const res = await deleteSysDictionary({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功',
})
getTableData()
}
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteSysDictionary({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功',
})
getTableData()
}
})
}
const dialogForm = ref(null)
@@ -74,32 +74,12 @@
icon="edit"
@click="updateSysDictionaryDetailFunc(scope.row)"
>变更</el-button>
<el-popover
v-model:visible="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteSysDictionaryDetailFunc(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteSysDictionaryDetailFunc(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -143,15 +123,11 @@
label="字典值"
prop="value"
>
<el-input-number
v-model.number="formData.value"
step-strictly
:step="1"
<el-input
v-model="formData.value"
placeholder="请输入字典值"
clearable
:style="{width: '100%'}"
min="-2147483648"
max="2147483647"
/>
</el-form-item>
<el-form-item
@@ -208,7 +184,7 @@ import {
getSysDictionaryDetailList
} from '@/api/sysDictionaryDetail' // 此处请自行替换地址
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatBoolean, formatDate } from '@/utils/format'
defineOptions({
@@ -307,18 +283,23 @@ const closeDialog = () => {
}
}
const deleteSysDictionaryDetailFunc = async(row) => {
row.visible = false
const res = await deleteSysDictionaryDetail({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteSysDictionaryDetail({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
getTableData()
}
})
}
const dialogForm = ref(null)
+9 -16
View File
@@ -1,21 +1,16 @@
<template>
<div>
<span
class="gva-icon"
style="position: absolute; z-index: 9999; padding: 3px 10px 0; "
>
<el-icon>
<component :is="metaData.icon" />
</el-icon>
</span>
<el-select
v-model="metaData.icon"
style="width:100%"
clearable
filterable
class="gva-select"
placeholder="请选择"
>
<template #prefix>
<el-icon>
<component :is="metaData.icon" />
</el-icon>
</template>
<el-option
v-for="item in options"
:key="item.key"
@@ -40,7 +35,7 @@
<script setup>
import { ref, reactive } from 'vue'
import config from "@/core/config";
import config from '@/core/config'
defineOptions({
name: 'Icon',
@@ -1180,7 +1175,9 @@ const options = reactive([
])
const metaData = ref(props.meta)
if (!metaData.value.icon) {
metaData.value.icon = options[0].label
// metaData.value.icon = options[0].label
// 传参无icon则显示空即可,否则默认会加一个aim的icon
metaData.value.icon = ''
}
</script>
@@ -1192,10 +1189,6 @@ if (!metaData.value.icon) {
margin-right: 10px;
}
.gva-select .el-input__inner {
padding: 0 30px !important
}
.select__option_item {
display: flex;
align-items: center;
@@ -38,33 +38,11 @@
</div>
<div class="gva-table-box">
<div class="gva-btn-list">
<el-popover
v-model="deleteVisible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="deleteVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onDelete"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
style="margin-left: 10px;"
:disabled="!multipleSelection.length"
@click="deleteVisible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
icon="delete"
:disabled="!multipleSelection.length"
@click="onDelete"
>删除</el-button>
</div>
<el-table
ref="multipleTable"
@@ -136,7 +114,6 @@
<el-popover
v-if="scope.row.body"
placement="left-start"
trigger="click"
>
<div class="popover-box">
<pre>{{ fmtBody(scope.row.body) }}</pre>
@@ -161,7 +138,6 @@
<el-popover
v-if="scope.row.resp"
placement="left-start"
trigger="click"
>
<div class="popover-box">
<pre>{{ fmtBody(scope.row.resp) }}</pre>
@@ -179,32 +155,12 @@
label="操作"
>
<template #default="scope">
<el-popover
v-model="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteSysOperationRecordFunc(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
type="primary"
link
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
icon="delete"
type="primary"
link
@click="deleteSysOperationRecordFunc(scope.row)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -231,7 +187,7 @@ import {
} from '@/api/sysOperationRecord' // 此处请自行替换地址
import { formatDate } from '@/utils/format'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
defineOptions({
name: 'SysOperationRecord'
@@ -283,43 +239,52 @@ const getTableData = async() => {
getTableData()
const deleteVisible = ref(false)
const multipleSelection = ref([])
const handleSelectionChange = (val) => {
multipleSelection.value = val
}
const onDelete = async() => {
const ids = []
multipleSelection.value &&
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const ids = []
multipleSelection.value &&
multipleSelection.value.forEach(item => {
ids.push(item.ID)
})
const res = await deleteSysOperationRecordByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
const res = await deleteSysOperationRecordByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
deleteVisible.value = false
getTableData()
}
})
}
const deleteSysOperationRecordFunc = async(row) => {
row.visible = false
const res = await deleteSysOperationRecord({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const res = await deleteSysOperationRecord({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
getTableData()
}
getTableData()
}
})
}
const fmtBody = (value) => {
try {
+22 -37
View File
@@ -95,31 +95,12 @@
fixed="right"
>
<template #default="scope">
<el-popover
v-model:visible="scope.row.visible"
placement="top"
width="160"
>
<p>确定要删除此用户吗</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteUserFunc(scope.row)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteUserFunc(scope.row)"
>删除</el-button>
<el-button
type="primary"
link
@@ -236,6 +217,11 @@
v-else
class="header-img-box"
>从媒体库选择</div>
<ChooseImg
ref="chooseImg"
:target="userInfo"
:target-key="`headerImg`"
/>
</div>
</el-form-item>
@@ -253,15 +239,9 @@
</div>
</template>
</el-dialog>
<ChooseImg
ref="chooseImg"
:target="userInfo"
:target-key="`headerImg`"
/>
</div>
</template>
<script setup>
import {
@@ -391,12 +371,17 @@ const setOptions = (authData) => {
}
const deleteUserFunc = async(row) => {
const res = await deleteUser({ id: row.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
row.visible = false
await getTableData()
}
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await deleteUser({ id: row.ID })
if (res.code === 0) {
ElMessage.success('删除成功')
await getTableData()
}
})
}
// 弹窗相关
@@ -96,6 +96,7 @@
>
<el-select
v-model="middleDate.fieldSearchType"
:disabled="middleDate.fieldType === 'json'"
style="width:100%"
placeholder="请选择字段查询条件"
clearable
@@ -116,7 +117,7 @@
<el-select
v-model="middleDate.dictType"
style="width:100%"
:disabled="middleDate.fieldType!=='int'"
:disabled="middleDate.fieldType!=='int'&&middleDate.fieldType!=='string'"
placeholder="请选择字典"
clearable
>
@@ -128,6 +129,9 @@
/>
</el-select>
</el-form-item>
<el-form-item label="主键">
<el-checkbox v-model="middleDate.primaryKey" />
</el-form-item>
<el-form-item label="是否排序">
<el-switch v-model="middleDate.sort" />
</el-form-item>
+101 -39
View File
@@ -8,13 +8,13 @@
<div class="gva-search-box">
<el-collapse
v-model="activeNames"
style="margin-bottom:12px"
class="mb-3"
>
<el-collapse-item name="1">
<template #title>
<div :style="{fontSize:'16px',paddingLeft:'20px'}">
<div class="text-xl pl-4 flex items-center">
点这里从现有数据库创建代码
<el-icon class="header-icon ">
<el-icon>
<pointer />
</el-icon>
</div>
@@ -42,7 +42,6 @@
<el-select
v-model="dbform.businessDB"
clearable
style="width:194px"
placeholder="选择业务库"
@change="getDbFunc"
>
@@ -186,7 +185,6 @@
>
<el-select
v-model="form.package"
style="width:194px"
>
<el-option
v-for="item in pkgs"
@@ -219,7 +217,6 @@
</template>
<el-select
v-model="form.businessDB"
style="width:194px"
placeholder="选择业务库"
>
<el-option
@@ -236,6 +233,22 @@
</el-option>
</el-select>
</el-form-item>
<div>
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体global.Model其中包含主键和软删除相关操作配置"
placement="bottom"
effect="light"
>
<div> 使用GVA结构 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox
v-model="form.gvaModel"
@change="useGva"
/>
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
@@ -255,11 +268,23 @@
placement="bottom"
effect="light"
>
<div> 自动创建API </div>
<div> 自动创建API <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的菜单注册进数据库"
placement="bottom"
effect="light"
>
<div> 自动创建菜单 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateMenuToSql" />
</el-form-item>
<el-form-item>
<template #label>
<el-tooltip
@@ -267,11 +292,12 @@
placement="bottom"
effect="light"
>
<div> 自动移动文件 </div>
<div> 自动移动文件 <el-icon><QuestionFilled /></el-icon></div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoMoveFile" />
</el-form-item>
</div>
</el-form>
</div>
<!-- 组件列表 -->
@@ -289,6 +315,17 @@
label="序列"
width="60"
/>
<el-table-column
align="left"
type="index"
label="主键"
width="60"
>
<template #default="{row}">
<el-checkbox v-model="row.primaryKey" />
</template>
</el-table-column>
<el-table-column
align="left"
prop="fieldName"
@@ -397,6 +434,7 @@
style="width:100%"
placeholder="请选择字段查询条件"
clearable
:disabled="row.fieldType!=='json'"
>
<el-option
v-for="item in typeSearchOptions"
@@ -440,31 +478,12 @@
:disabled="(scope.$index + 1) === form.fields.length"
@click="moveDownField(scope.$index)"
>下移</el-button>
<el-popover
v-model="scope.row.visible"
placement="top"
>
<p>确定删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="scope.row.visible = false"
>取消</el-button>
<el-button
type="primary"
@click="deleteField(scope.$index)"
>确定</el-button>
</div>
<template #reference>
<el-button
type="primary"
link
icon="delete"
@click="scope.row.visible = true"
>删除</el-button>
</template>
</el-popover>
<el-button
type="primary"
link
icon="delete"
@click="deleteField(scope.$index)"
>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -547,12 +566,13 @@ import { createTemp, getDB, getTable, getColumn, preview, getMeta, getPackageApi
import { getDict } from '@/utils/dictionary'
import { ref, getCurrentInstance, reactive, watch, toRaw } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { ElMessage, ElMessageBox } from 'element-plus'
import WarningBar from '@/components/warningBar/warningBar.vue'
defineOptions({
name: 'AutoCode'
})
const gormModelList = ['id', 'created_at', 'updated_at', 'deleted_at']
const typeOptions = ref([
{
@@ -598,6 +618,10 @@ const typeOptions = ref([
{
label: '文件(json字符串)',
value: 'file',
},
{
label: 'JSON',
value: 'json',
}
])
@@ -644,6 +668,7 @@ const fieldTemplate = {
require: false,
sort: false,
errorText: '',
primaryKey: false,
clearable: true,
fieldSearchType: '',
dictType: ''
@@ -669,7 +694,9 @@ const form = ref({
description: '',
businessDB: '',
autoCreateApiToSql: true,
autoCreateMenuToSql: true,
autoMoveFile: true,
gvaModel: true,
autoCreateResource: false,
fields: []
})
@@ -699,6 +726,26 @@ const bk = ref({})
const dialogFlag = ref(false)
const previewFlag = ref(false)
const useGva = (e) => {
if (e && form.value.fields.length) {
ElMessageBox.confirm(
'如果您开启GVA默认结构,会自动添加ID,CreatedAt,UpdatedAt,DeletedAt字段,此行为将自动清除您目前在下方创建的重名字段,是否继续?',
'注意',
{
confirmButtonText: '继续',
cancelButtonText: '取消',
type: 'warning',
}
)
.then(() => {
form.value.fields = form.value.fields.filter(item => !gormModelList.some(gormfd => gormfd === item.columnName))
})
.catch(() => {
form.value.gvaModel = false
})
}
}
const toLowerCaseFunc = (form, key) => {
form[key] = toLowerCase(form[key])
}
@@ -738,9 +785,9 @@ const moveDownField = (index) => {
form.value.fields.splice(index, 0, oldDownField)
}
const currentInstance = getCurrentInstance()
const fieldDialogNode = ref(null)
const enterDialog = () => {
currentInstance.refs.fieldDialogNode.fieldDialogFrom.validate(valid => {
fieldDialogNode.value.fieldDialogFrom.validate(valid => {
if (valid) {
dialogMiddle.value.fieldName = toUpperCase(
dialogMiddle.value.fieldName
@@ -761,7 +808,13 @@ const closeDialog = () => {
dialogFlag.value = false
}
const deleteField = (index) => {
form.value.fields.splice(index, 1)
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
form.value.fields.splice(index, 1)
})
}
const autoCodeForm = ref(null)
const enterForm = async(isPreview) => {
@@ -772,6 +825,15 @@ const enterForm = async(isPreview) => {
})
return false
}
if (!form.value.gvaModel && form.value.fields.every(item => !item.primaryKey)) {
ElMessage({
type: 'error',
message: '您至少需要创建一个主键才能保证自动化代码的可行性'
})
return false
}
if (
form.value.fields.some(item => item.fieldName === form.value.structName)
) {
@@ -875,7 +937,6 @@ const getTableFunc = async() => {
}
const getColumnFunc = async() => {
const gormModelList = ['id', 'created_at', 'updated_at', 'deleted_at']
const res = await getColumn(dbform.value)
if (res.code === 0) {
let dbtype = ''
@@ -895,7 +956,7 @@ const getColumnFunc = async() => {
form.value.fields = []
res.data.columns &&
res.data.columns.forEach(item => {
if (!gormModelList.some(gormfd => gormfd === item.columnName)) {
if (!form.value.gvaModel || (!gormModelList.some(gormfd => gormfd === item.columnName))) {
const fbHump = toHump(item.columnName)
form.value.fields.push({
fieldName: toUpperCase(fbHump),
@@ -903,6 +964,7 @@ const getColumnFunc = async() => {
fieldType: fdMap.value[item.dataType],
dataType: item.dataType,
fieldJson: fbHump,
primaryKey: item.primaryKey,
dataTypeLong: item.dataTypeLong && item.dataTypeLong.split(',')[0],
columnName: dbtype === 'oracle' ? item.columnName.toUpperCase() : item.columnName,
comment: item.columnComment,
@@ -89,33 +89,13 @@
icon="plus"
@click="openDialog"
>新增</el-button>
<el-popover
v-model:visible="deleteVisible"
<el-button
icon="delete"
style="margin-left: 10px;"
:disabled="!multipleSelection.length"
placement="top"
width="160"
>
<p>确定要删除吗?</p>
<div style="text-align: right; margin-top: 8px;">
<el-button
type="primary"
link
@click="deleteVisible = false"
>取消</el-button>
<el-button
type="primary"
@click="onDelete"
>确定</el-button>
</div>
<template #reference>
<el-button
icon="delete"
style="margin-left: 10px;"
:disabled="!multipleSelection.length"
@click="deleteVisible = true"
>删除</el-button>
</template>
</el-popover>
@click="onDelete"
>删除</el-button>
</div>
<el-table
ref="multipleTable"
@@ -136,6 +116,16 @@
>
<template #default="scope">{{ formatDate(scope.row.CreatedAt) }}</template>
</el-table-column>
<el-table-column
align="left"
label="数据库"
prop="name"
width="120"
>
<template #defalut="scope">
<span>{{ scope.row.dbNname || "GVA库" }}</span>
</template>
</el-table-column>
<el-table-column
align="left"
label="模板名称"
@@ -166,15 +156,6 @@
min-width="120"
>
<template #default="scope">
<el-button
type="primary"
link
class="table-button"
@click="getDetails(scope.row)"
>
<el-icon style="margin-right: 5px"><InfoFilled /></el-icon>
查看详情
</el-button>
<el-button
type="primary"
link
@@ -217,6 +198,40 @@
:rules="rule"
label-width="100px"
>
<el-form-item
label="业务库"
prop="dbName"
>
<template #label>
<el-tooltip
content="注:需要提前到db-list自行配置多数据库,如未配置需配置后重启服务方可使用。若无法选择,请到config.yaml中设置disabled:false,选择导入导出的目标库。"
placement="bottom"
effect="light"
>
<div> 业务库 <el-icon><QuestionFilled /></el-icon> </div>
</el-tooltip>
</template>
<el-select
v-model="formData.dbName"
clearable
placeholder="选择业务库"
>
<el-option
v-for="item in dbList"
:key="item.aliasName"
:value="item.aliasName"
:label="item.aliasName"
:disabled="item.disable"
>
<div>
<span>{{ item.aliasName }}</span>
<span style="float:right;color:#8492a6;font-size:13px">{{ item.dbName }}</span>
</div>
</el-option>
</el-select>
</el-form-item>
<el-form-item
label="模板名称:"
prop="name"
@@ -254,11 +269,70 @@
<el-input
v-model="formData.templateInfo"
type="textarea"
:rows="16"
:rows="12"
:clearable="true"
:placeholder="templatePlaceholder"
/>
</el-form-item>
<el-form-item
label="默认导出条数:"
>
<el-input-number
v-model="formData.limit"
:step="1"
:step-strictly="true"
:precision="0"
/>
</el-form-item>
<el-form-item
label="默认排序条件:"
>
<el-input
v-model="formData.order"
placeholder="例:id desc"
/>
</el-form-item>
<el-form-item
label="导出条件:"
>
<div
v-for="(condition,key) in formData.conditions"
:key="key"
class="flex gap-4 w-full mb-2"
>
<el-input
v-model="condition.from"
placeholder="需要从查询条件取的json key"
/>
<el-input
v-model="condition.column"
placeholder="表对应的column"
/>
<el-select
v-model="condition.operator"
placeholder="请选择查询条件"
>
<el-option
v-for="item in typeSearchOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button
type="danger"
icon="delete"
@click="() => formData.conditions.splice(key, 1)"
>删除</el-button>
</div>
<div class="flex justify-end w-full">
<el-button
type="primary"
icon="plus"
@click="addCondition"
>添加条件</el-button>
</div>
</el-form-item>
</el-form>
</el-scrollbar>
<template #footer>
@@ -271,35 +345,6 @@
</div>
</template>
</el-dialog>
<el-dialog
v-model="detailShow"
style="width: 800px"
lock-scroll
:before-close="closeDetailShow"
title="查看详情"
destroy-on-close
>
<el-scrollbar height="550px">
<el-descriptions
column="1"
border
>
<el-descriptions-item label="模板名称">
{{ formData.name }}
</el-descriptions-item>
<el-descriptions-item label="表名称">
{{ formData.tableName }}
</el-descriptions-item>
<el-descriptions-item label="模板标识">
{{ formData.templateID }}
</el-descriptions-item>
<el-descriptions-item label="模板信息">
{{ formData.templateInfo }}
</el-descriptions-item>
</el-descriptions>
</el-scrollbar>
</el-dialog>
</div>
</template>
@@ -318,6 +363,7 @@ import { formatDate } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { getDB } from '@/api/autoCode'
defineOptions({
name: 'ExportTemplate'
@@ -329,7 +375,8 @@ const templatePlaceholder = `模板信息格式:key标识数据库column列名
"table_name2":"第二列",
"table_name3":"第三列",
"table_name4":"第四列",
}`
}
`
// 自动化生成的字典(可能为空)以及字段
const formData = ref({
@@ -337,8 +384,50 @@ const formData = ref({
tableName: '',
templateID: '',
templateInfo: '',
limit: 0,
order: '',
conditions: [],
})
const typeSearchOptions = ref([
{
label: '=',
value: '='
},
{
label: '<>',
value: '<>'
},
{
label: '>',
value: '>'
},
{
label: '<',
value: '<'
},
{
label: 'LIKE',
value: 'LIKE'
},
{
label: 'BETWEEN',
value: 'BETWEEN'
},
{
label: 'NOT BETWEEN',
value: 'NOT BETWEEN'
}
])
const addCondition = () => {
formData.value.conditions.push({
from: '',
column: '',
operator: ''
})
}
// 验证规则
const rule = reactive({
name: [{
@@ -412,6 +501,16 @@ const total = ref(0)
const pageSize = ref(10)
const tableData = ref([])
const searchInfo = ref({})
const dbList = ref([])
const getDbFunc = async() => {
const res = await getDB()
if (res.code === 0) {
dbList.value = res.data.dbList
}
}
getDbFunc()
// 重置
const onReset = () => {
@@ -481,35 +580,37 @@ const deleteRow = (row) => {
})
}
// 批量删除控制标记
const deleteVisible = ref(false)
// 多选删除
const onDelete = async() => {
const ids = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
multipleSelection.value &&
multipleSelection.value.map(item => {
ids.push(item.ID)
})
const res = await deleteSysExportTemplateByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
ElMessageBox.confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async() => {
const ids = []
if (multipleSelection.value.length === 0) {
ElMessage({
type: 'warning',
message: '请选择要删除的数据'
})
return
}
deleteVisible.value = false
getTableData()
}
multipleSelection.value &&
multipleSelection.value.map(item => {
ids.push(item.ID)
})
const res = await deleteSysExportTemplateByIds({ ids })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === ids.length && page.value > 1) {
page.value--
}
getTableData()
}
})
}
// 行为控制标记(弹窗内部需要增还是改)
@@ -521,6 +622,9 @@ const updateSysExportTemplateFunc = async(row) => {
type.value = 'update'
if (res.code === 0) {
formData.value = res.data.resysExportTemplate
if (!formData.value.conditions) {
formData.value.conditions = []
}
dialogFormVisible.value = true
}
}
@@ -543,35 +647,6 @@ const deleteSysExportTemplateFunc = async(row) => {
// 弹窗控制标记
const dialogFormVisible = ref(false)
// 查看详情控制标记
const detailShow = ref(false)
// 打开详情弹窗
const openDetailShow = () => {
detailShow.value = true
}
// 打开详情
const getDetails = async(row) => {
// 打开弹窗
const res = await findSysExportTemplate({ ID: row.ID })
if (res.code === 0) {
formData.value = res.data.resysExportTemplate
openDetailShow()
}
}
// 关闭详情弹窗
const closeDetailShow = () => {
detailShow.value = false
formData.value = {
name: '',
tableName: '',
templateID: '',
templateInfo: '',
}
}
// 打开弹窗
const openDialog = () => {
type.value = 'create'
@@ -586,6 +661,9 @@ const closeDialog = () => {
tableName: '',
templateID: '',
templateInfo: '',
limit: 0,
order: '',
conditions: [],
}
}
// 弹窗确定
@@ -601,18 +679,30 @@ const enterDialog = async() => {
return
}
const reqData = JSON.parse(JSON.stringify(formData.value))
for (let i = 0; i < reqData.conditions.length; i++) {
if (!reqData.conditions[i].from || !reqData.conditions[i].column || !reqData.conditions[i].operator) {
ElMessage({
type: 'error',
message: '请填写完整的导出条件'
})
return
}
reqData.conditions[i].templateID = reqData.templateID
}
elFormRef.value?.validate(async(valid) => {
if (!valid) return
let res
switch (type.value) {
case 'create':
res = await createSysExportTemplate(formData.value)
res = await createSysExportTemplate(reqData)
break
case 'update':
res = await updateSysExportTemplate(formData.value)
res = await updateSysExportTemplate(reqData)
break
default:
res = await createSysExportTemplate(formData.value)
res = await createSysExportTemplate(reqData)
break
}
if (res.code === 0) {
@@ -11,16 +11,6 @@
title="系统配置"
name="1"
>
<el-form-item label="环境值">
<!-- <el-input v-model="config.system.env" />-->
<el-select
v-model="config.system.env"
style="width:100%"
>
<el-option value="public" />
<el-option value="develop" />
</el-select>
</el-form-item>
<el-form-item label="端口值">
<el-input v-model.number="config.system.addr" />
</el-form-item>
@@ -453,31 +443,6 @@
<el-input v-model="config.autocode['web-table']" />
</el-form-item>
</el-collapse-item>
<el-collapse-item
title="Timer(定时任务)"
name="13"
>
<el-form-item label="Start(是否启用)">
<el-checkbox v-model="config.timer['start']" />
</el-form-item>
<el-form-item label="Spec(CRON表达式)">
<el-input v-model="config.timer.spec" />
</el-form-item>
<template v-for="(item,k) in config.timer.detail">
<div
v-for="(_,k2) in item"
:key="k2"
>
<el-form-item
:key="k+k2"
:label="k2"
>
<el-input v-model="item[k2]" />
</el-form-item>
</div>
</template>
</el-collapse-item>
</el-collapse>
</el-form>
<div class="mt-4">
+3 -3
View File
@@ -43,9 +43,9 @@ export default ({
const rollupOptions = {
output: {
entryFileNames: '087AC4D233B64EB0[name].js',
chunkFileNames: '087AC4D233B64EB0[name].js',
assetFileNames: '087AC4D233B64EB0[name].[ext]',
entryFileNames: 'assets/087AC4D233B64EB0[name].[hash].js',
chunkFileNames: 'assets/087AC4D233B64EB0[name].[hash].js',
assetFileNames: 'assets/087AC4D233B64EB0[name].[hash].[ext]',
},
}
+8
View File
@@ -40,10 +40,18 @@ function findSvgFile(dir) {
export const svgBuilder = (path) => {
if (path === '') return
const res = findSvgFile(path)
const timestamp = Date.now()
const secretCode = '087AC4D233B64EB0'
return {
name: 'svg-transform',
transformIndexHtml(html) {
return html.replace(
'<head>',
`
<head>
<meta name="keywords" content="${timestamp},${secretCode}">
`
).replace(
'<body>',
`
<body>