* 媒体库增加分类,图库多选择时优化。

* 重构 JWT token 生成,使用 `New()` 函数代替直接创建实例

* 上传组件支持查看大图 (#1982)

* 将参数缓存,媒体库增加分类,图库多选择时优化。 (#1978)

* 媒体库增加分类,图库多选择时优化。

*修复文件上传进度显示bug&按钮样式优化 (#1986)

* fix:添加内部 iframe 展示网页,优化 permission 代码

* 俩个uuid库合并一个,更新库到当前版本。

* 优化关于我们界面

* feat: 个人中心头像调整,媒体库兼容性调整。

* feat: 自动化代码前端页面美化,多余按钮收入专家模式

* feat: 增加单独生成server功能

* feat: 限制单独生成前后端的情况下的细节配置

* feat: 修复全选失败报错的问题

---------

Co-authored-by: task <121913992@qq.com>
Co-authored-by: Feng.YJ <32027253+huiyifyj@users.noreply.github.com>
Co-authored-by: will0523 <dygsunshine@163.com>
Co-authored-by: task <ms.yangdan@gmail.com>
Co-authored-by: sslee <57312216+GIS142857@users.noreply.github.com>
Co-authored-by: bypanghu <bypanghu@163.com>
Co-authored-by: Azir <2075125282@qq.com>
Co-authored-by: piexlMax(奇淼 <qimiaojiangjizhao@gmail.com>
Co-authored-by: krank <emosick@qq.com>
This commit is contained in:
PiexlMax(奇淼
2025-02-13 15:25:10 +08:00
committed by GitHub
co-authored by task Feng.YJ will0523 task sslee bypanghu Azir piexlMax(奇淼 krank
parent ea39d83907
commit d40c815760
61 changed files with 2288 additions and 1529 deletions
+23 -38
View File
@@ -1,34 +1,29 @@
<template>
<div class="mt-2">
<el-row :gutter="10">
<el-col :span="12">
<el-card>
<div class="flex flex-col md:flex-row gap-4">
<div class="w-full md:w-1/2">
<el-card class="min-w-96">
<template #header>
<el-divider>gin-vue-admin</el-divider>
</template>
<div>
<el-row>
<el-col :span="8" :offset="8">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
<div class="w-full flex items-center justify-center">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
<img
class="org-img dom-center"
src="@/assets/logo.png"
alt="gin-vue-admin"
/>
</a>
</el-col>
</el-row>
<el-row :gutter="10">
<el-col :span="8">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
</div>
<div class="w-full flex items-center justify-around">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
<img
class="dom-center"
src="https://img.shields.io/github/watchers/flipped-aurora/gin-vue-admin.svg?label=Watch"
alt=""
/>
</a>
</el-col>
<el-col :span="8">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
<img
class="dom-center"
@@ -36,8 +31,6 @@
alt=""
/>
</a>
</el-col>
<el-col :span="8">
<a href="https://github.com/flipped-aurora/gin-vue-admin">
<img
class="dom-center"
@@ -45,17 +38,15 @@
alt=""
/>
</a>
</el-col>
</el-row>
</div>
</div>
</el-card>
<el-card style="margin-top: 20px">
<el-card class="min-w-96 mt-5">
<template #header>
<div>flipped-aurora团队</div>
</template>
<div>
<el-row>
<el-col :span="8" :offset="8">
<div class="w-full flex items-center justify-center">
<a href="https://github.com/flipped-aurora">
<img
class="org-img dom-center"
@@ -63,17 +54,13 @@
alt="flipped-aurora"
/>
</a>
</el-col>
</el-row>
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mt-4"
>
<div v-for="(item, index) in members" :key="index" :span="8">
<a :href="item.html_url" class="flex items-center">
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4 mt-4">
<div v-for="(item, index) in members" :key="index" class="min-h-10 flex items-center">
<a :href="item.html_url" class="flex items-center group">
<img class="w-8 h-8 rounded-full" :src="item.avatar_url" />
<el-link
class="text-blue-700 ml-2 text-xl font-bold font-sans"
style=""
class="text-blue-700 ml-2 text-lg font-bold font-sans break-all"
>{{ item.login }}</el-link
>
</a>
@@ -81,13 +68,13 @@
</div>
</div>
</el-card>
</el-col>
<el-col :span="12">
</div>
<div class="w-full md:w-1/2">
<el-card>
<template #header>
<div>提交记录</div>
</template>
<div>
<div class="h-[calc(100vh-300px)] overflow-y-auto">
<el-timeline>
<el-timeline-item
v-for="(item, index) in dataTimeline"
@@ -102,12 +89,14 @@
</el-timeline-item>
</el-timeline>
</div>
<div class="w-full flex items-center justify-center">
<el-button class="load-more" type="primary" link @click="loadMore">
Load more
</el-button>
</div>
</el-card>
</el-col>
</el-row>
</div>
</div>
</div>
</template>
@@ -155,10 +144,6 @@
</script>
<style scoped>
.load-more {
margin-left: 120px;
}
.avatar-img {
float: left;
height: 40px;
+4 -4
View File
@@ -2,19 +2,19 @@
<div
class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-7 py-2 gap-4 md:gap-2 gva-container2"
>
<gva-card custom-class="col-span-1 lg:col-span-2 h-32">
<gva-card custom-class="col-span-1 lg:col-span-2 ">
<gva-chart :type="1" title="访问人数" />
</gva-card>
<gva-card custom-class="col-span-1 lg:col-span-2 h-32 ">
<gva-card custom-class="col-span-1 lg:col-span-2 ">
<gva-chart :type="2" title="新增客户" />
</gva-card>
<gva-card custom-class="col-span-1 lg:col-span-2 h-32">
<gva-card custom-class="col-span-1 lg:col-span-2 ">
<gva-chart :type="3" title="解决数量" />
</gva-card>
<gva-card
title="快捷功能"
show-action
custom-class="col-start-1 md:col-start-3 lg:col-start-7 row-span-2 h-38"
custom-class="col-start-1 md:col-start-3 lg:col-start-7 row-span-2 "
>
<gva-quick-link />
</gva-card>
+291 -245
View File
@@ -3,25 +3,27 @@
<div class="gva-table-box">
<el-divider content-position="left">大文件上传</el-divider>
<form id="fromCont" method="post">
<div class="fileUpload" @click="inputChange">
选择文件
<input
v-show="false"
id="file"
ref="FileInput"
multiple="multiple"
type="file"
@change="choseFile"
/>
<!-- 新增按钮容器,使用 Flexbox 对齐按钮 -->
<div class="button-container">
<div class="fileUpload" @click="inputChange">
<span class="takeFile">选择文件</span>
<input
v-show="false"
id="file"
ref="FileInput"
multiple="multiple"
type="file"
@change="choseFile"
/>
</div>
<el-button
:disabled="limitFileSize"
type="primary"
class="uploadBtn"
@click="getFile"
>上传文件</el-button>
</div>
</form>
<el-button
:disabled="limitFileSize"
type="primary"
class="uploadBtn"
@click="getFile"
>上传文件</el-button
>
<div class="el-upload__tip">请上传不超过5MB的文件</div>
<div class="list">
<transition name="list" tag="p">
@@ -48,247 +50,291 @@
</template>
<script setup>
import SparkMD5 from 'spark-md5'
import {
findFile,
breakpointContinueFinish,
removeChunk,
breakpointContinue
} from '@/api/breakpoint'
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import SparkMD5 from 'spark-md5'
import {
findFile,
breakpointContinueFinish,
removeChunk,
breakpointContinue
} from '@/api/breakpoint'
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
defineOptions({
name: 'BreakPoint'
})
defineOptions({
name: 'BreakPoint'
})
const file = ref(null)
const fileMd5 = ref('')
const formDataList = ref([])
const waitUpLoad = ref([])
const waitNum = ref(NaN)
const limitFileSize = ref(false)
const percentage = ref(0)
const percentageFlage = ref(true)
const file = ref(null)
const fileMd5 = ref('')
const formDataList = ref([])
const waitUpLoad = ref([])
const waitNum = ref(NaN)
const limitFileSize = ref(false)
const percentage = ref(0)
const percentageFlage = ref(true)
// 选中文件的函数
const choseFile = async (e) => {
// 点击选择文件后取消 直接return
if (!e.target.files.length) {
return
}
const fileR = new FileReader() // 创建一个reader用来读取文件流
const fileInput = e.target.files[0] // 获取当前文件
const maxSize = 5 * 1024 * 1024
file.value = fileInput // file 丢全局方便后面用 可以改进为func传参形式
percentage.value = 0
if (file.value.size < maxSize) {
fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer 主要为了保持跟后端的流一致
fileR.onload = async (e) => {
// 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
const blob = e.target.result
const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我)
spark.append(blob) // 文件流丢进工具
fileMd5.value = spark.end() // 工具结束 产生一个a 总文件的md5
const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
let start = 0 // 定义分片开始切的地方
let end = 0 // 每片结束切的地方a
let i = 0 // 第几片
formDataList.value = [] // 分片存储的一个池子 丢全局
while (end < file.value.size) {
// 当结尾数字大于文件总size的时候 结束切片
start = i * FileSliceCap // 计算每片开始位置
end = (i + 1) * FileSliceCap // 计算每片结束位置
var fileSlice = file.value.slice(start, end) // 开始切 file.slice 为 h5方法 对文件切片 参数为 起止字节数
const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
formData.append('fileMd5', fileMd5.value) // 存储总文件的Md5 让后端知道自己是谁的切片
formData.append('file', fileSlice) // 当前的切片
formData.append('chunkNumber', i) // 当前是第几片
formData.append('fileName', file.value.name) // 当前文件的文件名 用于后端文件切片的命名 formData.appen 为 formData对象添加参数的方法
formDataList.value.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
i++
}
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
chunkTotal: formDataList.value.length
}
const res = await findFile(params)
// 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片
const finishList = res.data.file.ExaFileChunk // 上传成功的切片
const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
if (!IsFinish) {
// 当是断点续传时候
waitUpLoad.value = formDataList.value.filter((all) => {
return !(
finishList &&
finishList.some((fi) => fi.FileChunkNumber === all.key)
) // 找出需要上传的切片
})
} else {
waitUpLoad.value = [] // 秒传则没有需要上传的切片
ElMessage.success('文件已秒传')
}
waitNum.value = waitUpLoad.value.length // 记录长度用于百分比展示
// 选中文件的函数
const choseFile = async (e) => {
// 点击选择文件后取消 直接return
if (!e.target.files.length) {
return
}
const fileR = new FileReader() // 创建一个reader用来读取文件流
const fileInput = e.target.files[0] // 获取当前文件
const maxSize = 5 * 1024 * 1024
file.value = fileInput // file 丢全局方便后面用 可以改进为func传参形式
percentage.value = 0
if (file.value.size < maxSize) {
fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer 主要为了保持跟后端的流一致
fileR.onload = async (e) => {
// 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
const blob = e.target.result
const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我)
spark.append(blob) // 文件流丢进工具
fileMd5.value = spark.end() // 工具结束 产生一个a 总文件的md5
const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
let start = 0 // 定义分片开始切的地方
let end = 0 // 每片结束切的地方a
let i = 0 // 第几片
formDataList.value = [] // 分片存储的一个池子 丢全局
while (end < file.value.size) {
// 当结尾数字大于文件总size的时候 结束切片
start = i * FileSliceCap // 计算每片开始位置
end = (i + 1) * FileSliceCap // 计算每片结束位置
var fileSlice = file.value.slice(start, end) // 开始切 file.slice 为 h5方法 对文件切片 参数为 起止字节数
const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
formData.append('fileMd5', fileMd5.value) // 存储总文件的Md5 让后端知道自己是谁的切片
formData.append('file', fileSlice) // 当前的切片
formData.append('chunkNumber', i) // 当前是第几片
formData.append('fileName', file.value.name) // 当前文件的文件名 用于后端文件切片的命名 formData.appen 为 formData对象添加参数的方法
formDataList.value.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
i++
}
} else {
limitFileSize.value = true
ElMessage('请上传小于5M文件')
}
}
const getFile = () => {
// 确定按钮
if (file.value === null) {
ElMessage('请先上传文件')
return
}
if (percentage.value === 100) {
percentageFlage.value = false
}
sliceFile() // 上传切片
}
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach((item) => {
// 需要上传的切片
item.formData.append('chunkTotal', formDataList.value.length) // 切片总数携带给后台 总有用的
const fileR = new FileReader() // 功能同上
const fileF = item.formData.get('file')
fileR.readAsArrayBuffer(fileF)
fileR.onload = (e) => {
const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
upLoadFileSlice(item)
}
})
}
watch(
() => waitNum.value,
() => {
percentage.value = Math.floor(
((formDataList.value.length - waitNum.value) /
formDataList.value.length) *
100
)
}
)
const upLoadFileSlice = async (item) => {
// 切片上传
const fileRe = await breakpointContinue(item.formData)
if (fileRe.code !== 0) {
return
}
waitNum.value-- // 百分数增加
if (waitNum.value === 0) {
// 切片传完以后 合成文件
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value
fileMd5: fileMd5.value,
chunkTotal: formDataList.value.length
}
const res = await breakpointContinueFinish(params)
if (res.code === 0) {
// 合成文件过后 删除缓存切片
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
filePath: res.data.filePath
}
ElMessage.success('上传成功')
await removeChunk(params)
const res = await findFile(params)
// 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片
const finishList = res.data.file.ExaFileChunk // 上传成功的切片
const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
if (!IsFinish) {
// 当是断点续传时候
waitUpLoad.value = formDataList.value.filter((all) => {
return !(
finishList &&
finishList.some((fi) => fi.FileChunkNumber === all.key)
) // 找出需要上传的切片
})
} else {
waitUpLoad.value = [] // 秒传则没有需要上传的切片
ElMessage.success('文件已秒传!')
}
waitNum.value = waitUpLoad.value.length // 记录长度用于百分比展示
}
} else {
limitFileSize.value = true
ElMessage('请上传小于5M文件!')
}
}
const getFile = () => {
// 确定按钮
if (file.value === null) {
ElMessage('请先上传文件!')
return
}
// 检查文件上传进度
if (percentage.value === 100) {
ElMessage.success('上传已完成!') // 添加提示消息
percentageFlage.value = false
return // 如果进度已完成,阻止继续执行后续代码
}
// 如果文件未上传完成,继续上传切片
sliceFile() // 上传切片
}
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach((item) => {
// 需要上传的切片
item.formData.append('chunkTotal', formDataList.value.length) // 切片总数携带给后台 总有用的
const fileR = new FileReader() // 功能同上
const fileF = item.formData.get('file')
fileR.readAsArrayBuffer(fileF)
fileR.onload = (e) => {
const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
upLoadFileSlice(item)
}
})
}
watch(
() => waitNum.value,
() => {
percentage.value = Math.floor(
((formDataList.value.length - waitNum.value) /
formDataList.value.length) *
100
)
}
)
const upLoadFileSlice = async (item) => {
// 切片上传
const fileRe = await breakpointContinue(item.formData)
if (fileRe.code !== 0) {
return
}
waitNum.value-- // 百分数增加
if (waitNum.value === 0) {
// 切片传完以后 合成文件
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value
}
const res = await breakpointContinueFinish(params)
if (res.code === 0) {
// 合成文件过后 删除缓存切片
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
filePath: res.data.filePath
}
ElMessage.success('上传成功')
await removeChunk(params)
}
}
}
const FileInput = ref(null)
const inputChange = () => {
FileInput.value.dispatchEvent(new MouseEvent('click'))
}
const FileInput = ref(null)
const inputChange = () => {
FileInput.value.dispatchEvent(new MouseEvent('click'))
}
</script>
<style lang="scss" scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
#fromCont {
display: inline-block;
}
.fileUpload {
padding: 3px 10px;
font-size: 12px;
height: 20px;
line-height: 20px;
position: relative;
cursor: pointer;
border: 1px solid #c1c1c1;
border-radius: 4px;
overflow: hidden;
display: inline-block;
input {
position: absolute;
font-size: 100px;
right: 0;
top: 0;
opacity: 0;
cursor: pointer;
}
}
.fileName {
display: inline-block;
vertical-align: top;
margin: 6px 15px 0 15px;
}
.uploadBtn {
position: relative;
top: -10px;
margin-left: 15px;
}
.tips {
margin-top: 30px;
font-size: 14px;
font-weight: 400;
color: #606266;
}
.el-divider {
margin: 0 0 30px 0;
}
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
#fromCont {
display: inline-block;
}
.list {
margin-top: 15px;
.gva-table-box {
display: block;
}
.button-container {
display: flex;
align-items: center;
}
.fileUpload,
.uploadBtn {
width: 90px;
height: 35px;
line-height: 35px;
font-size: 14px;
display: inline-flex;
justify-content: center;
align-items: center;
border-radius: 5px;
cursor: pointer;
}
.fileUpload {
padding: 0 15px;
background-color: #007bff;
color: #ffffff;
font-weight: 500;
transition: all 0.3s ease-in-out;
margin-right: 5px;
}
.uploadBtn {
background-color: #007bff;
color: #fff;
margin-left: 10px;
}
.fileUpload:hover {
background-color: #0056b3;
}
.uploadBtn:hover {
background-color: #0056b3;
}
.fileUpload:active,
.uploadBtn:active {
transform: translateY(2px);
}
.fileUpload input {
position: relative;
font-size: 100px;
right: 0;
top: 0;
opacity: 0;
cursor: pointer;
width: 100%;
height: 100%;
}
.fileName {
display: inline-block;
vertical-align: top;
margin: 6px 15px 0 15px;
}
.tips {
margin-top: 30px;
font-size: 14px;
font-weight: 400;
color: #606266;
}
.el-divider {
margin: 0 0 30px 0;
}
.list {
margin-top: 15px;
}
.list-item {
display: block;
margin-right: 10px;
color: #606266;
line-height: 25px;
margin-bottom: 5px;
width: 40%;
.percentage {
float: right;
}
.list-item {
display: block;
margin-right: 10px;
color: #606266;
line-height: 25px;
margin-bottom: 5px;
width: 40%;
.percentage {
float: right;
}
}
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {
opacity: 0;
transform: translateY(-30px);
}
</style>
}
.list-enter-active,
.list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {
opacity: 0;
transform: translateY(-30px);
}
</style>
+343 -180
View File
@@ -1,161 +1,233 @@
<template>
<div v-loading.fullscreen.lock="fullscreenLoading">
<div class="gva-table-box">
<warning-bar title="点击“文件名/备注”可以编辑文件名或者备注内容。" />
<div class="gva-btn-list gap-3">
<upload-common :image-common="imageCommon" @on-success="getTableData" />
<upload-image
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
@on-success="getTableData"
/>
<el-button type="primary" icon="upload" @click="importUrlFunc">
导入URL
</el-button>
<el-input
v-model="search.keyword"
class="w-72"
placeholder="请输入文件名或备注"
/>
<el-button type="primary" icon="search" @click="getTableData"
>查询</el-button
>
<div class="flex gap-4 p-2">
<div class="flex-none w-64 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900 rounded p-4">
<el-scrollbar style="height: calc(100vh - 300px)">
<el-tree
:data="categories"
node-key="id"
:props="defaultProps"
@node-click="handleNodeClick"
default-expand-all
>
<template #default="{ node, data }">
<div class="w-36" :class="search.classId === data.ID ? 'text-blue-500 font-bold' : ''">{{ data.name }}
</div>
<el-dropdown>
<el-icon class="ml-3 text-right" v-if="data.ID > 0"><MoreFilled /></el-icon>
<el-icon class="ml-3 text-right mt-1" v-else><Plus /></el-icon>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="addCategoryFun(data)">添加分类</el-dropdown-item>
<el-dropdown-item @click="editCategory(data)" v-if="data.ID > 0">编辑分类</el-dropdown-item>
<el-dropdown-item @click="deleteCategoryFun(data.ID)" v-if="data.ID > 0">删除分类</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
</el-tree>
</el-scrollbar>
</div>
<div class="flex-1 bg-white text-slate-700 dark:text-slate-400 dark:bg-slate-900">
<div class="gva-table-box mt-0 mb-0">
<warning-bar title="点击“文件名”可以编辑;选择的类别即是上传的类别。" />
<div class="gva-btn-list gap-3">
<upload-common :image-common="imageCommon" :classId="search.classId" @on-success="onSuccess" />
<upload-image
:image-url="imageUrl"
:file-size="512"
:max-w-h="1080"
:classId="search.classId"
@on-success="onSuccess"
/>
<el-button type="primary" icon="upload" @click="importUrlFunc">
导入URL
</el-button>
<el-input
v-model="search.keyword"
class="w-72"
placeholder="请输入文件名或备注"
/>
<el-button type="primary" icon="search" @click="onSubmit"
>查询
</el-button
>
</div>
<el-table :data="tableData">
<el-table-column align="left" label="预览" width="100">
<template #default="scope">
<CustomPic pic-type="file" :pic-src="scope.row.url" preview />
</template>
</el-table-column>
<el-table-column align="left" label="日期" prop="UpdatedAt" width="180">
<template #default="scope">
<div>{{ formatDate(scope.row.UpdatedAt) }}</div>
</template>
</el-table-column>
<el-table-column
align="left"
label="文件名/备注"
prop="name"
width="180"
>
<template #default="scope">
<div class="name" @click="editFileNameFunc(scope.row)">
{{ scope.row.name }}
</div>
</template>
</el-table-column>
<el-table-column align="left" label="链接" prop="url" min-width="300" />
<el-table-column align="left" label="标签" prop="tag" width="100">
<template #default="scope">
<el-tag
:type="scope.row.tag?.toLowerCase() === 'jpg' ? 'info' : 'success'"
disable-transitions
>{{ scope.row.tag }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="left" label="操作" width="160">
<template #default="scope">
<el-button
icon="download"
type="primary"
link
@click="downloadFile(scope.row)"
>下载</el-button
<el-table :data="tableData">
<el-table-column align="left" label="预览" width="100">
<template #default="scope">
<CustomPic pic-type="file" :pic-src="scope.row.url" preview/>
</template>
</el-table-column>
<el-table-column align="left" label="日期" prop="UpdatedAt" width="180">
<template #default="scope">
<div>{{ formatDate(scope.row.UpdatedAt) }}</div>
</template>
</el-table-column>
<el-table-column
align="left"
label="文件名/备注"
prop="name"
width="180"
>
<el-button
icon="delete"
type="primary"
link
@click="deleteFileFunc(scope.row)"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:style="{ float: 'right', padding: '20px' }"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
<template #default="scope">
<div class="cursor-pointer" @click="editFileNameFunc(scope.row)">
{{ scope.row.name }}
</div>
</template>
</el-table-column>
<el-table-column align="left" label="链接" prop="url" min-width="300"/>
<el-table-column align="left" label="标签" prop="tag" width="100">
<template #default="scope">
<el-tag
:type="scope.row.tag?.toLowerCase() === 'jpg' ? 'info' : 'success'"
disable-transitions
>{{ scope.row.tag }}
</el-tag>
</template>
</el-table-column>
<el-table-column align="left" label="操作" width="160">
<template #default="scope">
<el-button
icon="download"
type="primary"
link
@click="downloadFile(scope.row)"
>下载
</el-button
>
<el-button
icon="delete"
type="primary"
link
@click="deleteFileFunc(scope.row)"
>删除
</el-button
>
</template>
</el-table-column>
</el-table>
<div class="gva-pagination">
<el-pagination
:current-page="page"
:page-size="pageSize"
:page-sizes="[10, 30, 50, 100]"
:style="{ float: 'right', padding: '20px' }"
:total="total"
layout="total, sizes, prev, pager, next, jumper"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</div>
</div>
</div>
</div>
<!-- 添加分类弹窗 -->
<el-dialog v-model="categoryDialogVisible" @close="closeAddCategoryDialog" width="520"
:title="(categoryFormData.ID === 0 ? '添加' : '编辑') + '分类'"
draggable
>
<el-form ref="categoryForm" :rules="rules" :model="categoryFormData" label-width="80px">
<el-form-item label="上级分类">
<el-tree-select
v-model="categoryFormData.pid"
:data="categories"
check-strictly
:props="defaultProps"
:render-after-expand="false"
style="width: 240px"
/>
</el-form-item>
<el-form-item label="分类名称" prop="name">
<el-input v-model.trim="categoryFormData.name" placeholder="分类名称"></el-input>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="closeAddCategoryDialog">取消</el-button>
<el-button type="primary" @click="confirmAddCategory">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import {
getFileList,
deleteFile,
editFileName,
importURL
} from '@/api/fileUploadAndDownload'
import { downloadImage } from '@/utils/downloadImg'
import CustomPic from '@/components/customPic/index.vue'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import { CreateUUID, formatDate } from '@/utils/format'
import WarningBar from '@/components/warningBar/warningBar.vue'
import {
getFileList,
deleteFile,
editFileName,
importURL
} from '@/api/fileUploadAndDownload'
import {downloadImage} from '@/utils/downloadImg'
import CustomPic from '@/components/customPic/index.vue'
import UploadImage from '@/components/upload/image.vue'
import UploadCommon from '@/components/upload/common.vue'
import {CreateUUID, formatDate} from '@/utils/format'
import WarningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {ref} from 'vue'
import {ElMessage, ElMessageBox} from 'element-plus'
import {addCategory, deleteCategory, getCategoryList} from "@/api/attachmentCategory";
defineOptions({
name: 'Upload'
})
defineOptions({
name: 'Upload'
})
const path = ref(import.meta.env.VITE_BASE_API)
const path = ref(import.meta.env.VITE_BASE_API)
const imageUrl = ref('')
const imageCommon = ref('')
const imageUrl = ref('')
const imageCommon = ref('')
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const search = ref({})
const tableData = ref([])
const page = ref(1)
const total = ref(0)
const pageSize = ref(10)
const search = ref({
keyword: null,
classId: 0
})
const tableData = ref([])
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
// 查询
const getTableData = async () => {
const table = await getFileList({
page: page.value,
pageSize: pageSize.value,
...search.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
// 分页
const handleSizeChange = (val) => {
pageSize.value = val
getTableData()
}
const deleteFileFunc = async (row) => {
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
const handleCurrentChange = (val) => {
page.value = val
getTableData()
}
const onSubmit = () => {
search.value.classId = 0
page.value = 1
getTableData()
}
// 查询
const getTableData = async () => {
const table = await getFileList({
page: page.value,
pageSize: pageSize.value,
...search.value
})
if (table.code === 0) {
tableData.value = table.data.list
total.value = table.data.total
page.value = table.data.page
pageSize.value = table.data.pageSize
}
}
getTableData()
const deleteFileFunc = async (row) => {
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
const res = await deleteFile(row)
if (res.code === 0) {
@@ -175,30 +247,30 @@
message: '已取消删除'
})
})
}
}
const downloadFile = (row) => {
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
downloadImage(row.url, row.name)
} else {
downloadImage(path.value + '/' + row.url, row.name)
}
const downloadFile = (row) => {
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
downloadImage(row.url, row.name)
} else {
downloadImage(path.value + '/' + row.url, row.name)
}
}
/**
* 编辑文件名或者备注
* @param row
* @returns {Promise<void>}
*/
const editFileNameFunc = async (row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
})
.then(async ({ value }) => {
/**
* 编辑文件名或者备注
* @param row
* @returns {Promise<void>}
*/
const editFileNameFunc = async (row) => {
ElMessageBox.prompt('请输入文件名或者备注', '编辑', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /\S/,
inputErrorMessage: '不能为空',
inputValue: row.name
})
.then(async ({value}) => {
row.name = value
// console.log(row)
const res = await editFileName(row)
@@ -216,22 +288,22 @@
message: '取消修改'
})
})
}
}
/**
* 导入URL
*/
const importUrlFunc = () => {
ElMessageBox.prompt('格式:文件名|链接或者仅链接。', '导入', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder:
/**
* 导入URL
*/
const importUrlFunc = () => {
ElMessageBox.prompt('格式:文件名|链接或者仅链接。', '导入', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder:
'我的图片|https://my-oss.com/my.png\nhttps://my-oss.com/my_1.png',
inputPattern: /\S/,
inputErrorMessage: '不能为空'
})
.then(async ({ value }) => {
inputPattern: /\S/,
inputErrorMessage: '不能为空'
})
.then(async ({value}) => {
let data = value.split('\n')
let importData = []
data.forEach((item) => {
@@ -249,6 +321,7 @@
importData.push({
name: name,
url: url,
classId: search.value.classId,
tag: url.substring(url.lastIndexOf('.') + 1),
key: CreateUUID()
})
@@ -270,11 +343,101 @@
message: '取消导入'
})
})
}
</script>
}
<style scoped>
.name {
cursor: pointer;
const onSuccess = () => {
search.value.keyword = null
page.value = 1
getTableData()
}
const defaultProps = {
children: 'children',
label: 'name',
value: 'ID'
}
const categories = ref([])
const fetchCategories = async () => {
const res = await getCategoryList()
let data = {
name: '全部分类',
ID: 0,
pid: 0,
children:[]
}
</style>
if (res.code === 0) {
categories.value = res.data || []
categories.value.unshift(data)
}
}
const handleNodeClick = (node) => {
search.value.keyword = null
search.value.classId = node.ID
page.value = 1
getTableData()
}
const categoryDialogVisible = ref(false)
const categoryFormData = ref({
ID: 0,
pid: 0,
name: ''
})
const categoryForm = ref(null)
const rules = ref({
name: [
{required: true, message: '请输入分类名称', trigger: 'blur'},
{max: 20, message: '最多20位字符', trigger: 'blur'}
]
})
const addCategoryFun = (category) => {
categoryDialogVisible.value = true
categoryFormData.value.ID = 0
categoryFormData.value.pid = category.ID
}
const editCategory = (category) => {
categoryFormData.value = {
ID: category.ID,
pid: category.pid,
name: category.name
}
categoryDialogVisible.value = true
}
const deleteCategoryFun = async (id) => {
const res = await deleteCategory({id: id})
if (res.code === 0) {
ElMessage.success({type: 'success', message: '删除成功'})
await fetchCategories()
}
}
const confirmAddCategory = async () => {
categoryForm.value.validate(async valid => {
if (valid) {
const res = await addCategory(categoryFormData.value)
if (res.code === 0) {
ElMessage({type: 'success', message: '操作成功'})
await fetchCategories()
closeAddCategoryDialog()
}
}
})
}
const closeAddCategoryDialog = () => {
categoryDialogVisible.value = false
categoryFormData.value = {
ID: 0,
pid: 0,
name: ''
}
}
fetchCategories()
</script>
@@ -95,7 +95,6 @@
return config.value.layout_side_collapsed_width
}
})
watchEffect(() => {
active.value = route.meta.activeName || route.name
})
@@ -123,8 +122,10 @@
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
} else {
window.open(index, '_blank')
return
}
if (!top) {
router.push({ name: index, query, params })
return
@@ -136,7 +137,7 @@
}
const firstMenu = leftMenu.find((item) => !item.hidden && item.path.indexOf("http://") === -1 && item.path.indexOf("https://") === -1)
router.push({ name: firstMenu.name, query, params })
}
}
const toggleCollapse = () => {
+16 -1
View File
@@ -40,6 +40,10 @@
const isCollapse = ref(false)
const active = ref('')
watchEffect(() => {
if (route.name === 'Iframe') {
active.value = decodeURIComponent(route.query.url)
return
}
active.value = route.meta.activeName || route.name
})
@@ -66,7 +70,18 @@
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
if (index === 'Iframe') {
query.url = decodeURIComponent(index)
router.push({
name: 'Iframe',
query,
params
})
return
} else {
window.open(index, '_blank')
return
}
} else {
router.push({ name: index, query, params })
}
+17 -2
View File
@@ -15,7 +15,7 @@
unique-opened
@select="selectMenuItem"
>
<template v-for="item in routerStore.asyncRouters[0].children">
<template v-for="item in routerStore.asyncRouters[0]?.children || []">
<aside-component
v-if="!item.hidden"
:key="item.name"
@@ -65,6 +65,10 @@
}
})
watchEffect(() => {
if (route.name === 'Iframe') {
active.value = decodeURIComponent(route.query.url)
return
}
active.value = route.meta.activeName || route.name
})
@@ -91,7 +95,18 @@
})
if (index === route.name) return
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
window.open(index)
if (index === 'Iframe') {
query.url = decodeURIComponent(index)
router.push({
name: 'Iframe',
query,
params
})
return
} else {
window.open(index, '_blank')
return
}
} else {
router.push({ name: index, query, params })
}
+70
View File
@@ -0,0 +1,70 @@
<template>
<div
class="bg-gray-50 text-slate-700 dark:text-slate-500 dark:bg-slate-800 w-screen h-screen"
>
<iframe
v-if="reloadFlag"
id="gva-base-load-dom"
class="gva-body-h bg-gray-50 dark:bg-slate-800 w-full border-t border-gray-200 dark:border-slate-700"
src="https://www.gin-vue-admin.com"
></iframe>
</div>
</template>
<script setup>
import useResponsive from '@/hooks/responsive'
import { emitter } from '@/utils/bus.js'
import { ref, onMounted, nextTick, reactive, watchEffect } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/pinia/modules/user'
import { useAppStore } from '@/pinia'
import { storeToRefs } from 'pinia'
const appStore = useAppStore()
const { isDark } = storeToRefs(appStore)
defineOptions({
name: 'GvaLayoutIframe'
})
useResponsive(true)
const font = reactive({
color: 'rgba(0, 0, 0, .15)'
})
watchEffect(() => {
font.color = isDark.value ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'
})
const router = useRouter()
const route = useRoute()
onMounted(() => {
// 挂载一些通用的事件
emitter.on('reload', reload)
if (userStore.loadingInstance) {
userStore.loadingInstance.close()
}
})
const userStore = useUserStore()
const reloadFlag = ref(true)
let reloadTimer = null
const reload = async () => {
if (reloadTimer) {
window.clearTimeout(reloadTimer)
}
reloadTimer = window.setTimeout(async () => {
if (route.meta.keepAlive) {
reloadFlag.value = false
await nextTick()
reloadFlag.value = true
} else {
const title = route.meta.title
router.push({ name: 'Reload', params: { title } })
}
}, 400)
}
</script>
<style lang="scss"></style>
+1 -2
View File
@@ -74,8 +74,7 @@
})
watchEffect(() => {
font.color =
isDark.value ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'
font.color = isDark.value ? 'rgba(255,255,255, .15)' : 'rgba(0, 0, 0, .15)'
})
const router = useRouter()
+74 -45
View File
@@ -12,18 +12,24 @@
<div class="flex flex-col lg:flex-row items-start gap-8">
<!-- 左侧头像 -->
<div class="profile-avatar-wrapper flex-shrink-0 mx-auto lg:mx-0">
<ProfileAvatar
v-model="userStore.userInfo.headerImg"
@update:modelValue="handleAvatarChange"
<SelectImage
v-model="userStore.userInfo.headerImg"
file-type="image"
rounded
/>
</div>
<!-- 右侧信息 -->
<div class="flex-1 pt-20 w-full">
<div class="flex flex-col lg:flex-row items-start lg:items-center justify-between gap-4">
<div>
<div class="flex-1 pt-12 lg:pt-20 w-full">
<div
class="flex flex-col lg:flex-row items-start lg:items-start justify-between gap-4"
>
<div class="lg:mt-4">
<div class="flex items-center gap-4 mb-4">
<div v-if="!editFlag" class="text-2xl font-bold flex items-center gap-3 text-gray-800 dark:text-gray-100">
<div
v-if="!editFlag"
class="text-2xl font-bold flex items-center gap-3 text-gray-800 dark:text-gray-100"
>
{{ userStore.userInfo.nickName }}
<el-icon
class="cursor-pointer text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors duration-200"
@@ -32,29 +38,27 @@
<edit />
</el-icon>
</div>
<div v-else class="flex items-center gap-3">
<el-input
v-model="nickName"
class="w-48"
size="large"
/>
<el-button type="success" circle @click="enterEdit">
<el-icon><check /></el-icon>
<div v-else class="flex items-center">
<el-input v-model="nickName" class="w-48 mr-4" />
<el-button type="primary" plain @click="enterEdit">
确认
</el-button>
<el-button type="danger" circle @click="closeEdit">
<el-icon><close /></el-icon>
<el-button type="danger" plain @click="closeEdit">
取消
</el-button>
</div>
</div>
<div class="flex flex-col lg:flex-row items-start lg:items-center gap-4 lg:gap-8 text-gray-500 dark:text-gray-400">
<div
class="flex flex-col lg:flex-row items-start lg:items-center gap-4 lg:gap-8 text-gray-500 dark:text-gray-400"
>
<div class="flex items-center gap-2">
<el-icon><location /></el-icon>
<span>中国·北京市·朝阳区</span>
</div>
<div class="flex items-center gap-2">
<el-icon><office-building /></el-icon>
<span>北京反转极光科技有限公司</span>
<span>北京翻转极光科技有限公司</span>
</div>
<div class="flex items-center gap-2">
<el-icon><user /></el-icon>
@@ -63,15 +67,11 @@
</div>
</div>
<div class="flex gap-4 mt-4 lg:mt-0">
<el-button type="primary" plain>
<el-icon><message /></el-icon>
<div class="flex gap-4 mt-4">
<el-button type="primary" plain icon="message">
发送消息
</el-button>
<el-button>
<el-icon><share /></el-icon>
分享主页
</el-button>
<el-button icon="share"> 分享主页 </el-button>
</div>
</div>
</div>
@@ -83,13 +83,17 @@
<div class="grid lg:grid-cols-12 md:grid-cols-1 gap-8">
<!-- 左侧信息栏 -->
<div class="lg:col-span-4">
<div class="bg-white dark:bg-slate-800 rounded-xl p-6 mb-6 profile-card">
<div
class="bg-white dark:bg-slate-800 rounded-xl p-6 mb-6 profile-card"
>
<h2 class="text-lg font-semibold mb-4 flex items-center gap-2">
<el-icon class="text-blue-500"><info-filled /></el-icon>
基本信息
</h2>
<div class="space-y-4">
<div class="flex items-center gap-3 text-gray-600 dark:text-gray-300">
<div
class="flex items-center gap-1 lg:gap-3 text-gray-600 dark:text-gray-300"
>
<el-icon class="text-blue-500"><phone /></el-icon>
<span class="font-medium">手机号码:</span>
<span>{{ userStore.userInfo.phone || '未设置' }}</span>
@@ -102,9 +106,11 @@
修改
</el-button>
</div>
<div class="flex items-center gap-3 text-gray-600 dark:text-gray-300">
<div
class="flex items-center gap-1 lg:gap-3 text-gray-600 dark:text-gray-300"
>
<el-icon class="text-green-500"><message /></el-icon>
<span class="font-medium">邮箱地址:</span>
<span class="font-medium flex-shrink-0">邮箱地址:</span>
<span>{{ userStore.userInfo.email || '未设置' }}</span>
<el-button
link
@@ -115,7 +121,9 @@
修改
</el-button>
</div>
<div class="flex items-center gap-3 text-gray-600 dark:text-gray-300">
<div
class="flex items-center gap-1 lg:gap-3 text-gray-600 dark:text-gray-300"
>
<el-icon class="text-purple-500"><lock /></el-icon>
<span class="font-medium">账号密码:</span>
<span>已设置</span>
@@ -162,19 +170,35 @@
</template>
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 lg:gap-6 py-6">
<div class="stat-card">
<div class="text-2xl lg:text-4xl font-bold text-blue-500 mb-2">138</div>
<div
class="text-2xl lg:text-4xl font-bold text-blue-500 mb-2"
>
138
</div>
<div class="text-gray-500 text-sm">项目参与</div>
</div>
<div class="stat-card">
<div class="text-2xl lg:text-4xl font-bold text-green-500 mb-2">2.3k</div>
<div
class="text-2xl lg:text-4xl font-bold text-green-500 mb-2"
>
2.3k
</div>
<div class="text-gray-500 text-sm">代码提交</div>
</div>
<div class="stat-card">
<div class="text-2xl lg:text-4xl font-bold text-purple-500 mb-2">95%</div>
<div
class="text-2xl lg:text-4xl font-bold text-purple-500 mb-2"
>
95%
</div>
<div class="text-gray-500 text-sm">任务完成</div>
</div>
<div class="stat-card">
<div class="text-2xl lg:text-4xl font-bold text-yellow-500 mb-2">12</div>
<div
class="text-2xl lg:text-4xl font-bold text-yellow-500 mb-2"
>
12
</div>
<div class="text-gray-500 text-sm">获得勋章</div>
</div>
</div>
@@ -196,7 +220,9 @@
:hollow="true"
class="pb-6"
>
<h3 class="text-base font-medium mb-1">{{ activity.title }}</h3>
<h3 class="text-base font-medium mb-1">
{{ activity.title }}
</h3>
<p class="text-gray-500 text-sm">{{ activity.content }}</p>
</el-timeline-item>
</el-timeline>
@@ -258,7 +284,7 @@
<div class="flex gap-4">
<el-input
v-model="phoneForm.code"
placeholder="请输入验证码"
placeholder="请输入验证码[模拟]"
class="flex-1"
>
<template #prefix>
@@ -302,7 +328,7 @@
<div class="flex gap-4">
<el-input
v-model="emailForm.code"
placeholder="请输入验证码"
placeholder="请输入验证码[模拟]"
class="flex-1"
>
<template #prefix>
@@ -332,11 +358,10 @@
<script setup>
import { setSelfInfo, changePassword } from '@/api/user.js'
import { reactive, ref } from 'vue'
import { reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/pinia/modules/user'
import ProfileAvatar from '@/components/Avatar/ProfileAvatar.vue'
import SelectImage from '@/components/selectImage/selectImage.vue'
defineOptions({
name: 'Person'
})
@@ -486,12 +511,16 @@
}
}
const handleAvatarChange = async (newUrl) => {
const res = await setSelfInfo({ headerImg: newUrl })
watch(() => userStore.userInfo.headerImg, async(val) => {
const res = await setSelfInfo({ headerImg: val })
if (res.code === 0) {
userStore.ResetUserInfo({ headerImg: newUrl })
userStore.ResetUserInfo({ headerImg: val })
ElMessage({
type: 'success',
message: '设置成功',
})
}
}
})
// 添加活动数据
const activities = [
+3 -1
View File
@@ -1,5 +1,6 @@
<template>
<div>
<warning-bar title="获取参数且缓存方法已在前端utils/params 已经封装完成 不必自己书写 使用方法查看文件内注释" />
<div class="gva-search-box">
<el-form
ref="elSearchFormRef"
@@ -231,7 +232,7 @@
<p class="mb-2 text-sm text-gray-600">
前端可以通过引入
<code class="bg-blue-100 px-1 py-0.5 rounded"
>import { getParams } from '@/utils/dictionary'</code
>import { getParams } from '@/utils/params'</code
>
然后通过
<code class="bg-blue-100 px-1 py-0.5 rounded"
@@ -297,6 +298,7 @@
import { formatDate } from '@/utils/format'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ref, reactive } from 'vue'
import WarningBar from "@/components/warningBar/warningBar.vue";
defineOptions({
name: 'SysParams'
@@ -115,5 +115,5 @@
ElMessage.success('复制成功')
}
defineExpose({ copy })
defineExpose({ copy, selectText })
</script>
+195 -134
View File
@@ -329,142 +329,159 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="3">
<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-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注:把自动生成的API注册进数据库"
placement="bottom"
effect="light"
>
<div>
自动创建API <el-icon><QuestionFilled /></el-icon>
</div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateApiToSql" />
</el-form-item>
</el-col>
<el-col :span="3">
<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-col>
<el-col :span="3">
<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.autoMigrate" />
</el-form-item>
</el-col>
<el-col :span="3">
<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.autoCreateBtnAuth" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动在结构体添加 created_by updated_by deleted_by,方便用户进行资源权限控制"
placement="bottom"
effect="light"
>
<div>
创建资源标识 <el-icon><QuestionFilled /></el-icon>
</div>
</el-tooltip>
</template>
<el-checkbox v-model="form.autoCreateResource" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item>
<template #label>
<el-tooltip
content="注:使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑"
placement="bottom"
effect="light"
>
<div>
基础模板 <el-icon><QuestionFilled /></el-icon>
</div>
</el-tooltip>
</template>
<el-checkbox v-model="form.onlyTemplate" />
</el-form-item>
</el-col>
<el-col :span="9">
<el-form-item>
<template #label>
<el-tooltip
content="注:会自动创建parentID来进行父子关系关联,仅支持主键为int类型"
placement="bottom"
effect="light"
>
<div>
树型结构 <el-icon><QuestionFilled /></el-icon>
</div>
</el-tooltip>
</template>
<div class="flex gap-2 items-center">
<el-checkbox v-model="form.isTree" />
<el-input v-model="form.treeJson" :disabled="!form.isTree" placeholder="前端展示json属性"></el-input>
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="gva-search-box">
<el-collapse class="no-border-collapse">
<el-collapse-item>
<template #title>
<div class="text-lg text-gray-600 font-normal">
专家模式
</div>
</template>
<template #icon="{ isActive }">
<span class="text-lg ml-auto mr-4 font-normal">
{{ isActive ? '收起' : '展开' }}
</span>
</template>
<div class="p-4">
<!-- 基础设置组 -->
<div class="border-b border-gray-200 last:border-0">
<h3 class="text-lg font-medium mb-4 text-gray-700">基础设置</h3>
<el-row :gutter="20">
<el-col :span="3">
<el-tooltip
content="注:会自动在结构体global.Model其中包含主键和软删除相关操作配置"
placement="top"
effect="light"
>
<el-form-item label="使用GVA结构">
<el-checkbox v-model="form.gvaModel" @change="useGva" />
</el-form-item>
</el-tooltip>
</el-col>
<el-col :span="3">
<el-tooltip
content="注:会自动产生页面内的按钮权限配置,若不在角色管理中进行按钮分配则按钮不可见"
placement="top"
effect="light"
>
<el-form-item label="创建按钮权限">
<el-checkbox :disabled="!form.generateWeb" v-model="form.autoCreateBtnAuth" />
</el-form-item>
</el-tooltip>
</el-col>
<el-col :span="3">
<el-form-item label="生成前端">
<el-checkbox v-model="form.generateWeb" />
</el-form-item>
</el-col>
<el-col :span="3">
<el-form-item label="生成后端">
<el-checkbox v-model="form.generateServer" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 自动化设置组 -->
<div class="border-b border-gray-200 last:border-0">
<h3 class="text-lg font-medium mb-4 text-gray-700">自动化设置</h3>
<el-row :gutter="20">
<el-col :span="3">
<el-tooltip
content="注:把自动生成的API注册进数据库"
placement="top"
effect="light"
>
<el-form-item label="自动创建API">
<el-checkbox :disabled="!form.generateServer" v-model="form.autoCreateApiToSql" />
</el-form-item>
</el-tooltip>
</el-col>
<el-col :span="3">
<el-tooltip
content="注:把自动生成的菜单注册进数据库"
placement="top"
effect="light"
>
<el-form-item label="自动创建菜单">
<el-checkbox :disabled="!form.generateWeb" v-model="form.autoCreateMenuToSql" />
</el-form-item>
</el-tooltip>
</el-col>
<el-col :span="3">
<el-tooltip
content="注:自动同步数据库表结构,如果不需要可以选择关闭"
placement="top"
effect="light"
>
<el-form-item label="同步表结构">
<el-checkbox :disabled="!form.generateServer" v-model="form.autoMigrate" />
</el-form-item>
</el-tooltip>
</el-col>
</el-row>
</div>
<!-- 高级设置组 -->
<div class="border-b border-gray-200 last:border-0">
<h3 class="text-lg font-medium mb-4 text-gray-700">高级设置</h3>
<el-row :gutter="20">
<el-col :span="3">
<el-tooltip
content="注:会自动在结构体添加 created_by updated_by deleted_by,方便用户进行资源权限控制"
placement="top"
effect="light"
>
<el-form-item label="创建资源标识">
<el-checkbox v-model="form.autoCreateResource" />
</el-form-item>
</el-tooltip>
</el-col>
<el-col :span="3">
<el-tooltip
content="注:使用基础模板将不会生成任何结构体和CURD,仅仅配置enter等属性方便自行开发非CURD逻辑"
placement="top"
effect="light"
>
<el-form-item label="基础模板">
<el-checkbox v-model="form.onlyTemplate" />
</el-form-item>
</el-tooltip>
</el-col>
</el-row>
</div>
<!-- 树形结构设置 -->
<div class="last:pb-0">
<h3 class="text-lg font-medium mb-4 text-gray-700">树形结构设置</h3>
<el-row :gutter="20" align="middle">
<el-col :span="24">
<el-form-item label="树型结构">
<div class="flex items-center gap-4">
<el-tooltip
content="注:会自动创建parentID来进行父子关系关联,仅支持主键为int类型"
placement="top"
effect="light"
>
<el-checkbox v-model="form.isTree" />
</el-tooltip>
<el-input
v-model="form.treeJson"
:disabled="!form.isTree"
placeholder="前端展示json属性"
class="flex-1"
/>
</div>
</el-form-item>
</el-col>
</el-row>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
<!-- 组件列表 -->
<div class="gva-table-box">
<div class="gva-btn-list">
@@ -929,6 +946,10 @@
for (let key in json) {
form.value[key] = json[key]
}
form.value.generateServer = true
form.value.generateWeb = true
}
}
@@ -1118,6 +1139,8 @@
autoCreateResource: false,
onlyTemplate: false,
isTree: false,
generateWeb:true,
generateServer:true,
treeJson: "",
fields: []
})
@@ -1239,6 +1262,13 @@
})
return false
}
if(!form.value.generateWeb && !form.value.generateServer){
ElMessage({
type: 'error',
message: '请至少选择一个生成项'
})
return false
}
if (!form.value.onlyTemplate) {
if (form.value.fields.length <= 0) {
ElMessage({
@@ -1403,6 +1433,8 @@
form.value.abbreviation = toLowerCase(tbHump)
form.value.description = tbHump + '表'
form.value.autoCreateApiToSql = true
form.value.generateServer = true
form.value.generateWeb = true
form.value.fields = []
res.data.columns &&
res.data.columns.forEach((item) => {
@@ -1520,6 +1552,20 @@
}
)
watch(()=>form.value.generateServer,()=>{
if(!form.value.generateServer){
form.value.autoCreateApiToSql = false
form.value.autoMigrate = false
}
})
watch(()=>form.value.generateWeb,()=>{
if(!form.value.generateWeb){
form.value.autoCreateMenuToSql = false
form.value.autoCreateBtnAuth = false
}
})
const catchData = () => {
window.sessionStorage.setItem('autoCode', JSON.stringify(form.value))
}
@@ -1607,3 +1653,18 @@
}
)
</script>
<style>
.no-border-collapse{
@apply border-none;
.el-collapse-item__header{
@apply border-none;
}
.el-collapse-item__wrap{
@apply border-none;
}
.el-collapse-item__content{
@apply pb-0;
}
}
</style>