init
This commit is contained in:
parent
04873151f8
commit
83758dfee9
|
|
@ -0,0 +1,222 @@
|
|||
<template>
|
||||
<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="open"
|
||||
/>
|
||||
<upload-image
|
||||
:image-url="imageUrl"
|
||||
:file-size="512"
|
||||
:max-w-h="1080"
|
||||
class="upload-btn-media-library"
|
||||
@on-success="open"
|
||||
/>
|
||||
<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="open"
|
||||
>查询</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)"
|
||||
@click="chooseImg(item.url,target,targetKey)"
|
||||
>
|
||||
<template #error>
|
||||
<div class="header-img-box-list">
|
||||
<el-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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getUrl } from '@/utils/image'
|
||||
import { ref } from 'vue'
|
||||
import { getFileList, editFileName } from '@/api/fileUploadAndDownload'
|
||||
import UploadImage from '@/components/upload/image.vue'
|
||||
import UploadCommon from '@/components/upload/common.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
const imageUrl = ref('')
|
||||
const imageCommon = ref('')
|
||||
|
||||
const search = ref({})
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(20)
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
open()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
open()
|
||||
}
|
||||
|
||||
const emit = defineEmits(['enterImg'])
|
||||
defineProps({
|
||||
target: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
targetKey: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
const drawer = ref(false)
|
||||
const picList = ref([])
|
||||
|
||||
const chooseImg = (url, target, targetKey) => {
|
||||
if (target && targetKey) {
|
||||
target[targetKey] = url
|
||||
}
|
||||
emit('enterImg', url)
|
||||
drawer.value = false
|
||||
}
|
||||
|
||||
const open = async() => {
|
||||
const res = await getFileList({ page: page.value, pageSize: pageSize.value, ...search.value })
|
||||
if (res.code === 0) {
|
||||
picList.value = res.data.list
|
||||
total.value = res.data.total
|
||||
page.value = res.data.page
|
||||
pageSize.value = res.data.pageSize
|
||||
drawer.value = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑文件名或者备注
|
||||
* @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)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '编辑成功!',
|
||||
})
|
||||
open()
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消修改'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.upload-btn-media-library {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.media {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.media-box {
|
||||
width: 120px;
|
||||
margin-left: 20px;
|
||||
|
||||
.img-title {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 36px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-img-box-list {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
line-height: 120px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
.el-image__inner {
|
||||
max-width: 120px;
|
||||
max-height: 120px;
|
||||
vertical-align: middle;
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
width="30%"
|
||||
class="overlay"
|
||||
:show-close="false"
|
||||
>
|
||||
<template #header>
|
||||
<input
|
||||
v-model="searchInput"
|
||||
class="quick-input"
|
||||
placeholder="请输入你需要快捷到达的功能"
|
||||
>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-for="(option,index) in options"
|
||||
:key="index"
|
||||
>
|
||||
<div
|
||||
v-if="option.children.length"
|
||||
class="quick-title"
|
||||
>{{ option.label }}</div>
|
||||
<div
|
||||
v-for="(item,key) in option.children"
|
||||
:key="index+'-'+key"
|
||||
class="quick-item"
|
||||
@click="item.func"
|
||||
>
|
||||
{{ item.label }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="close">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'CommandMenu',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const routerStore = useRouterStore()
|
||||
const dialogVisible = ref(false)
|
||||
const searchInput = ref('')
|
||||
const options = reactive([])
|
||||
const deepMenus = (menus) => {
|
||||
const arr = []
|
||||
menus.forEach(menu => {
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
arr.push(...deepMenus(menu.children))
|
||||
} else {
|
||||
if (menu.meta.title && menu.meta.title.indexOf(searchInput.value) > -1) {
|
||||
arr.push({
|
||||
label: menu.meta.title,
|
||||
func: () => changeRouter(menu)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
const addQuickMenu = () => {
|
||||
const option = {
|
||||
label: '跳转',
|
||||
children: []
|
||||
}
|
||||
const menus = deepMenus(routerStore.asyncRouters[0].children)
|
||||
option.children.push(...menus)
|
||||
options.push(option)
|
||||
}
|
||||
|
||||
const addQuickOption = () => {
|
||||
const option = {
|
||||
label: '操作',
|
||||
children: []
|
||||
}
|
||||
const quickArr = [
|
||||
{
|
||||
label: '亮色主题',
|
||||
func: () => changeMode('light')
|
||||
}, {
|
||||
label: '暗色主题',
|
||||
func: () => changeMode('dark')
|
||||
}, {
|
||||
label: '退出登录',
|
||||
func: () => userStore.LoginOut()
|
||||
}
|
||||
]
|
||||
option.children.push(...quickArr.filter(item => item.label.indexOf(searchInput.value) > -1))
|
||||
options.push(option)
|
||||
}
|
||||
|
||||
addQuickMenu()
|
||||
addQuickOption()
|
||||
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const changeRouter = (e) => {
|
||||
const index = e.name
|
||||
const query = {}
|
||||
const params = {}
|
||||
routerStore.routeMap[index]?.parameters &&
|
||||
routerStore.routeMap[index]?.parameters.forEach((item) => {
|
||||
if (item.type === 'query') {
|
||||
query[item.key] = item.value
|
||||
} else {
|
||||
params[item.key] = item.value
|
||||
}
|
||||
})
|
||||
if (index === route.name) return
|
||||
if (e.name.indexOf('http://') > -1 || e.name.indexOf('https://') > -1) {
|
||||
window.open(e.name)
|
||||
} else {
|
||||
router.push({ name: index, query, params })
|
||||
}
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const changeMode = (e) => {
|
||||
if (e === null) {
|
||||
userStore.changeSideMode('dark')
|
||||
return
|
||||
}
|
||||
userStore.changeSideMode(e)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
|
||||
watch(searchInput, () => {
|
||||
options.length = 0
|
||||
addQuickMenu()
|
||||
addQuickOption()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.overlay {
|
||||
border-radius: 4px;
|
||||
.el-dialog__header{
|
||||
padding:0 !important;
|
||||
margin-right:0 !important;
|
||||
}
|
||||
.el-dialog__body{
|
||||
padding: 12px !important;
|
||||
height: 50vh;
|
||||
overflow: auto !important;
|
||||
}
|
||||
.quick-title{
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
.quick-input{
|
||||
color: #666;
|
||||
border-radius: 4px 4px 0 0;
|
||||
border:none;
|
||||
padding: 12px 16px;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.quick-item{
|
||||
font-size: 14px;
|
||||
padding: 8px;
|
||||
margin: 4px 0;
|
||||
&:hover{
|
||||
cursor: pointer;
|
||||
background: #eee;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
// using ES6 modules
|
||||
import mitt from 'mitt'
|
||||
|
||||
export const emitter = mitt()
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { emitter } from '@/utils/bus.js'
|
||||
|
||||
export const closeThisPage = () => {
|
||||
emitter.emit('closeThisPage')
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// 对Date的扩展,将 Date 转化为指定格式的String
|
||||
// 月(M)、日(d)、小时(h)、分(m)、秒(s)、季度(q) 可以用 1-2 个占位符,
|
||||
// 年(y)可以用 1-4 个占位符,毫秒(S)只能用 1 个占位符(是 1-3 位的数字)
|
||||
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
|
||||
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
|
||||
// eslint-disable-next-line no-extend-native
|
||||
Date.prototype.Format = function(fmt) {
|
||||
var o = {
|
||||
'M+': this.getMonth() + 1, // 月份
|
||||
'd+': this.getDate(), // 日
|
||||
'h+': this.getHours(), // 小时
|
||||
'm+': this.getMinutes(), // 分
|
||||
's+': this.getSeconds(), // 秒
|
||||
'q+': Math.floor((this.getMonth() + 3) / 3), // 季度
|
||||
'S': this.getMilliseconds() // 毫秒
|
||||
}
|
||||
if (/(y+)/.test(fmt)) { fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '').substr(4 - RegExp.$1.length)) }
|
||||
for (var k in o) {
|
||||
if (new RegExp('(' + k + ')').test(fmt)) { fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) }
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
|
||||
export function formatTimeToStr(times, pattern) {
|
||||
var d = new Date(times).Format('yyyy-MM-dd hh:mm:ss')
|
||||
if (pattern) {
|
||||
d = new Date(times).Format(pattern)
|
||||
}
|
||||
return d.toLocaleString()
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { useDictionaryStore } from '@/pinia/modules/dictionary'
|
||||
// 获取字典方法 使用示例 getDict('sex').then(res) 或者 async函数下 const res = await getDict('sex')
|
||||
export const getDict = async(type) => {
|
||||
const dictionaryStore = useDictionaryStore()
|
||||
await dictionaryStore.getDictionary(type)
|
||||
return dictionaryStore.dictionaryMap[type]
|
||||
}
|
||||
|
||||
// 字典文字展示方法
|
||||
export const showDictLabel = (
|
||||
dict,
|
||||
code,
|
||||
keyCode = 'value',
|
||||
valueCode = 'label'
|
||||
) => {
|
||||
if (!dict) {
|
||||
return ''
|
||||
}
|
||||
const dictMap = {}
|
||||
dict.forEach(item => {
|
||||
if (Reflect.has(item, keyCode) && Reflect.has(item, valueCode)) {
|
||||
dictMap[item[keyCode]] = item[valueCode]
|
||||
}
|
||||
})
|
||||
return Reflect.has(dictMap, code) ? dictMap[code] : ''
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export const toDoc = (url) => {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
export const downloadImage = (imgsrc, name) => { // 下载图片地址和图片名
|
||||
var image = new Image()
|
||||
image.setAttribute('crossOrigin', 'anonymous')
|
||||
image.onload = function() {
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
var context = canvas.getContext('2d')
|
||||
context.drawImage(image, 0, 0, image.width, image.height)
|
||||
var url = canvas.toDataURL('image/png') // 得到图片的base64编码数据
|
||||
|
||||
var a = document.createElement('a') // 生成一个a元素
|
||||
var event = new MouseEvent('click') // 创建一个单击事件
|
||||
a.download = name || 'photo' // 设置图片名称
|
||||
a.href = url // 将生成的URL设置为a.href属性
|
||||
a.dispatchEvent(event) // 触发a的单击事件
|
||||
}
|
||||
image.src = imgsrc
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
export const fmtTitle = (title, now) => {
|
||||
const reg = /\$\{(.+?)\}/
|
||||
const reg_g = /\$\{(.+?)\}/g
|
||||
const result = title.match(reg_g)
|
||||
if (result) {
|
||||
result.forEach((item) => {
|
||||
const key = item.match(reg)[1]
|
||||
const value = now.params[key] || now.query[key]
|
||||
title = title.replace(item, value)
|
||||
})
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
import { formatTimeToStr } from '@/utils/date'
|
||||
import { getDict } from '@/utils/dictionary'
|
||||
|
||||
export const formatBoolean = (bool) => {
|
||||
if (bool !== null) {
|
||||
return bool ? '开启' : '关闭'
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
export const formatDate = (time) => {
|
||||
if (time !== null && time !== '') {
|
||||
var date = new Date(time)
|
||||
return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
export const formatOnlyDate = (time) => {
|
||||
if (time !== null && time !== '') {
|
||||
var date = new Date(time)
|
||||
return formatTimeToStr(date, 'yyyy-MM-dd')
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
export const formatPriceType = (priceType) => {
|
||||
switch (priceType) {
|
||||
case 1: return '出厂价'
|
||||
default: '未定义'
|
||||
}
|
||||
}
|
||||
|
||||
export const filterDict = (value, options) => {
|
||||
const rowLabel = options && options.filter(item => item.value === value)
|
||||
return rowLabel && rowLabel[0] && rowLabel[0].label
|
||||
}
|
||||
|
||||
export const getDictFunc = async (type) => {
|
||||
const dicts = await getDict(type)
|
||||
return dicts
|
||||
}
|
||||
|
||||
const path = import.meta.env.VITE_BASE_PATH + ':' + import.meta.env.VITE_SERVER_PORT + '/'
|
||||
export const ReturnArrImg = (arr) => {
|
||||
const imgArr = []
|
||||
if (arr instanceof Array) { // 如果是数组类型
|
||||
for (const arrKey in arr) {
|
||||
if (arr[arrKey].slice(0, 4) !== 'http') {
|
||||
imgArr.push(path + arr[arrKey])
|
||||
} else {
|
||||
imgArr.push(arr[arrKey])
|
||||
}
|
||||
}
|
||||
} else { // 如果不是数组类型
|
||||
if (arr.slice(0, 4) !== 'http') {
|
||||
imgArr.push(path + arr)
|
||||
} else {
|
||||
imgArr.push(arr)
|
||||
}
|
||||
}
|
||||
return imgArr
|
||||
}
|
||||
|
||||
export const onDownloadFile = (url) => {
|
||||
window.open(path + url)
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
export default class ImageCompress {
|
||||
constructor(file, fileSize, maxWH = 1920) {
|
||||
this.file = file
|
||||
this.fileSize = fileSize
|
||||
this.maxWH = maxWH // 最大长宽
|
||||
}
|
||||
|
||||
compress() {
|
||||
// 压缩
|
||||
const fileType = this.file.type
|
||||
const fileSize = this.file.size / 1024
|
||||
return new Promise(resolve => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsDataURL(this.file)
|
||||
reader.onload = () => {
|
||||
const canvas = document.createElement('canvas')
|
||||
const img = document.createElement('img')
|
||||
img.src = reader.result
|
||||
img.onload = () => {
|
||||
const ctx = canvas.getContext('2d')
|
||||
const _dWH = this.dWH(img.width, img.height, this.maxWH)
|
||||
canvas.width = _dWH.width
|
||||
canvas.height = _dWH.height
|
||||
|
||||
// 清空后, 重写画布
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
||||
|
||||
const newImgData = canvas.toDataURL(fileType, 0.90)
|
||||
|
||||
// 压缩宽高后的图像大小
|
||||
const newImgSize = this.fileSizeKB(newImgData)
|
||||
|
||||
if (newImgSize > this.fileSize) {
|
||||
console.log('图片尺寸太大!' + fileSize + ' >> ' + newImgSize)
|
||||
}
|
||||
|
||||
const blob = this.dataURLtoBlob(newImgData, fileType)
|
||||
const nfile = new File([blob], this.file.name)
|
||||
resolve(nfile)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 长宽等比缩小
|
||||
* 图像的一边(长或宽)为最大目标值
|
||||
*/
|
||||
dWH(srcW, srcH, dMax) {
|
||||
const defaults = {
|
||||
width: srcW,
|
||||
height: srcH
|
||||
}
|
||||
if (Math.max(srcW, srcH) > dMax) {
|
||||
if (srcW > srcH) {
|
||||
defaults.width = dMax
|
||||
defaults.height = Math.round(srcH * (dMax / srcW))
|
||||
return defaults
|
||||
} else {
|
||||
defaults.height = dMax
|
||||
defaults.width = Math.round(srcW * (dMax / srcH))
|
||||
return defaults
|
||||
}
|
||||
} else {
|
||||
return defaults
|
||||
}
|
||||
}
|
||||
|
||||
fileSizeKB(dataURL) {
|
||||
let sizeKB = 0
|
||||
sizeKB = Math.round((dataURL.split(',')[1].length * 3 / 4) / 1024)
|
||||
return sizeKB
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为Blob
|
||||
*/
|
||||
dataURLtoBlob(dataURL, fileType) {
|
||||
const byteString = atob(dataURL.split(',')[1])
|
||||
let mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]
|
||||
const ab = new ArrayBuffer(byteString.length)
|
||||
const ia = new Uint8Array(ab)
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
ia[i] = byteString.charCodeAt(i)
|
||||
}
|
||||
if (fileType) {
|
||||
mimeString = fileType
|
||||
}
|
||||
return new Blob([ab], { type: mimeString, lastModifiedDate: new Date() })
|
||||
}
|
||||
}
|
||||
|
||||
const path = import.meta.env.VITE_FILE_API + '/'
|
||||
export const getUrl = (url) => url && url.slice(0, 4) !== 'http' ? path + url : url
|
||||
|
||||
export const isVideoExt = (url) => url.endsWith('.mp4') || url.endsWith('.mov') || url.endsWith('.webm') || url.endsWith('.ogg');
|
||||
|
||||
export const isVideoMime = (type) => type == 'video/mp4' || type == 'video/webm' || type == 'video/ogg';
|
||||
|
||||
export const isImageMime = (type) => type == 'image/jpeg' || type == 'image/png' || type == 'image/webp' || type == 'image/svg+xml';
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { fmtTitle } from '@/utils/fmtRouterTitle'
|
||||
import config from '@/core/config'
|
||||
export default function getPageTitle(pageTitle, route) {
|
||||
if (pageTitle) {
|
||||
const title = fmtTitle(pageTitle, route)
|
||||
return `${title} - ${config.appName}`
|
||||
}
|
||||
return `${config.appName}`
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
export const initDom = () => {
|
||||
if (import.meta.env.MODE === 'development') {
|
||||
document.onmousedown = function(e) {
|
||||
if (e.shiftKey && e.altKey && e.button === 0) {
|
||||
e.preventDefault()
|
||||
sendRequestToOpenFileInEditor(getFilePath(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getFilePath = (e) => {
|
||||
let element = e
|
||||
if (e.target) {
|
||||
element = e.target
|
||||
}
|
||||
if (!element || !element.getAttribute) return null
|
||||
if (element.getAttribute('code-location')) {
|
||||
return element.getAttribute('code-location')
|
||||
}
|
||||
return getFilePath(element.parentNode)
|
||||
}
|
||||
|
||||
const sendRequestToOpenFileInEditor = (filePath) => {
|
||||
const protocol = window.location.protocol
|
||||
? window.location.protocol
|
||||
: 'http:'
|
||||
const hostname = window.location.hostname
|
||||
? window.location.hostname
|
||||
: 'localhost'
|
||||
const port = window.location.port ? window.location.port : '80'
|
||||
fetch(`${protocol}//${hostname}:${port}/gvaPositionCode?filePath=${filePath}`)
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
import axios from 'axios' // 引入axios
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import router from '@/router/index'
|
||||
import { ElLoading } from 'element-plus'
|
||||
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_BASE_API,
|
||||
timeout: 99999
|
||||
})
|
||||
let activeAxios = 0
|
||||
let timer
|
||||
let loadingInstance
|
||||
const showLoading = (option = {
|
||||
target: null,
|
||||
}) => {
|
||||
const loadDom = document.getElementById('gva-base-load-dom')
|
||||
activeAxios++
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
}
|
||||
timer = setTimeout(() => {
|
||||
if (activeAxios > 0) {
|
||||
if (!option.target) option.target = loadDom
|
||||
loadingInstance = ElLoading.service(option)
|
||||
}
|
||||
}, 400)
|
||||
}
|
||||
|
||||
const closeLoading = () => {
|
||||
activeAxios--
|
||||
if (activeAxios <= 0) {
|
||||
clearTimeout(timer)
|
||||
loadingInstance && loadingInstance.close()
|
||||
}
|
||||
}
|
||||
// http request 拦截器
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
if (!config.donNotShowLoading) {
|
||||
showLoading(config.loadingOption)
|
||||
}
|
||||
const userStore = useUserStore()
|
||||
config.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'x-token': userStore.token,
|
||||
'x-user-id': userStore.userInfo.ID,
|
||||
...config.headers
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
if (!error.config.donNotShowLoading) {
|
||||
closeLoading()
|
||||
}
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: error,
|
||||
type: 'error'
|
||||
})
|
||||
return error
|
||||
}
|
||||
)
|
||||
|
||||
// http response 拦截器
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
const userStore = useUserStore()
|
||||
if (!response.config.donNotShowLoading) {
|
||||
closeLoading()
|
||||
}
|
||||
if (response.headers['new-token']) {
|
||||
userStore.setToken(response.headers['new-token'])
|
||||
}
|
||||
if (response.data.code === 0 || response.headers.success === 'true') {
|
||||
if (response.headers.msg) {
|
||||
response.data.msg = decodeURI(response.headers.msg)
|
||||
}
|
||||
return response.data
|
||||
} else {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
message: response.data.msg || decodeURI(response.headers.msg),
|
||||
type: 'error'
|
||||
})
|
||||
if (response.data.data && response.data.data.reload) {
|
||||
userStore.token = ''
|
||||
window.localStorage.removeItem('token')
|
||||
router.push({ name: 'Login', replace: true })
|
||||
}
|
||||
return response.data.msg ? response.data : response
|
||||
}
|
||||
},
|
||||
error => {
|
||||
if (!error.config.donNotShowLoading) {
|
||||
closeLoading()
|
||||
}
|
||||
|
||||
if (!error.response) {
|
||||
ElMessageBox.confirm(`
|
||||
<p>检测到请求错误</p>
|
||||
<p>${error}</p>
|
||||
`, '请求报错', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: '稍后重试',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
switch (error.response.status) {
|
||||
case 500:
|
||||
ElMessageBox.confirm(`
|
||||
<p>检测到接口错误${error}</p>
|
||||
<p>错误码<span style="color:red"> 500 </span>:此类错误内容常见于后台panic,请先查看后台日志,如果影响您正常使用可强制登出清理缓存</p>
|
||||
`, '接口报错', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: '清理缓存',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
.then(() => {
|
||||
const userStore = useUserStore()
|
||||
userStore.ClearStorage()
|
||||
router.push({ name: 'Login', replace: true })
|
||||
})
|
||||
break
|
||||
case 404:
|
||||
ElMessageBox.confirm(`
|
||||
<p>检测到接口错误${error}</p>
|
||||
<p>错误码<span style="color:red"> 404 </span>:此类错误多为接口未注册(或未重启)或者请求路径(方法)与api路径(方法)不符--如果为自动化代码请检查是否存在空格</p>
|
||||
`, '接口报错', {
|
||||
dangerouslyUseHTMLString: true,
|
||||
distinguishCancelAndClose: true,
|
||||
confirmButtonText: '我知道了',
|
||||
cancelButtonText: '取消'
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
)
|
||||
export default service
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/* eslint-disable */
|
||||
export const toUpperCase = (str) => {
|
||||
if (str[0]) {
|
||||
return str.replace(str[0], str[0].toUpperCase())
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
export const toLowerCase = (str) => {
|
||||
if (str[0]) {
|
||||
return str.replace(str[0], str[0].toLowerCase())
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// 驼峰转换下划线
|
||||
export const toSQLLine = (str) => {
|
||||
if (str === 'ID') return 'ID'
|
||||
return str.replace(/([A-Z])/g, "_$1").toLowerCase();
|
||||
}
|
||||
|
||||
// 下划线转换驼峰
|
||||
export const toHump = (name) => {
|
||||
return name.replace(/\_(\w)/g, function(all, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="24">
|
||||
<div>
|
||||
email: phpcrazy@163.com
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineOptions({
|
||||
name: 'About'
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,458 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item label="创建日期" prop="createdAt">
|
||||
<template #label>
|
||||
<span>
|
||||
创建日期
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期"
|
||||
:disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
|
||||
—
|
||||
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期"
|
||||
:disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="ID" prop="ID" width="80" />
|
||||
<el-table-column align="left" label="产品" prop="productId" width="120">
|
||||
<template #default="scope">{{ formatProduct(scope.row.productId) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="产品价格Id" prop="productPriceId" width="100" />
|
||||
<el-table-column align="left" label="数据源Id" prop="sourceId" width="100" />
|
||||
<el-table-column align="left" label="公司" prop="companyName" width="240" />
|
||||
<el-table-column align="left" label="品牌" prop="producer" width="120" />
|
||||
<el-table-column align="left" label="价格" prop="price" width="80" />
|
||||
<el-table-column align="left" label="单位" prop="priceUnit" width="60" />
|
||||
<el-table-column align="left" label="发布日期" width="100">
|
||||
<template #default="scope">{{ formatOnlyDate(scope.row.publishDate) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="创建时间" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<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 icon="edit" class="table-button"
|
||||
@click="updateCompanyPriceFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{ type==='create'?'添加':'修改' }}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="产品Id:" prop="productId">
|
||||
<el-input v-model.number="formData.productId" :clearable="true" placeholder="请输入产品Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品价格Id:" prop="productPriceId">
|
||||
<el-input v-model.number="formData.productPriceId" :clearable="true" placeholder="请输入产品价格Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据源Id:" prop="sourceId">
|
||||
<el-input v-model.number="formData.sourceId" :clearable="true" placeholder="请输入数据源Id" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司:" prop="companyName">
|
||||
<el-input v-model="formData.companyName" :clearable="true" placeholder="请输入公司" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌:" prop="producer">
|
||||
<el-input v-model="formData.producer" :clearable="true" placeholder="请输入品牌" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格:" prop="price">
|
||||
<el-input-number v-model="formData.price" style="width:100%" :precision="2" :clearable="true" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-date-picker v-model="formData.publishDate" type="date" style="width:100%" placeholder="选择日期" :clearable="true" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品">
|
||||
{{ formatProduct(formData.productId) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品价格Id">
|
||||
{{ formData.productPriceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据源Id">
|
||||
{{ formData.sourceId }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司">
|
||||
{{ formData.companyName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="品牌">
|
||||
{{ formData.producer }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格">
|
||||
{{ formData.price }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">
|
||||
{{ formData.priceUnit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布日期">
|
||||
{{ formatOnlyDate(formData.publishDate) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getProductList } from '@/api/product'
|
||||
import {
|
||||
createCompanyPrice,
|
||||
deleteCompanyPrice,
|
||||
deleteCompanyPriceByIds,
|
||||
updateCompanyPrice,
|
||||
findCompanyPrice,
|
||||
getCompanyPriceList
|
||||
} from '@/api/companyPrice'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { formatDate, formatOnlyDate } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'CompanyPrice'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
sourceId: 0,
|
||||
companyName: '',
|
||||
producer: '',
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
publishDate: new Date(),
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
const productList = ref([])
|
||||
|
||||
const formatProduct = (id) => {
|
||||
for (let i = 0; i < productList.value.length; i++) {
|
||||
if (productList.value[i].ID === id) {
|
||||
return productList.value[i].name
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const getProductData = async() => {
|
||||
const resp = await getProductList({ page: 1, pageSize: 100 })
|
||||
if (resp.code === 0 && resp.data.list) {
|
||||
productList.value = resp.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getProductData()
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getCompanyPriceList({ page: page.value, pageSize: pageSize.value, ...searchInfo.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 setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteCompanyPriceFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(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 deleteCompanyPriceByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateCompanyPriceFunc = async(row) => {
|
||||
const res = await findCompanyPrice({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recompanyPrice
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteCompanyPriceFunc = async(row) => {
|
||||
const res = await deleteCompanyPrice({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findCompanyPrice({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recompanyPrice
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
sourceId: 0,
|
||||
companyName: '',
|
||||
producer: '',
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
publishDate: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
sourceId: 0,
|
||||
companyName: '',
|
||||
producer: '',
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
publishDate: new Date(),
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createCompanyPrice(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateCompanyPrice(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createCompanyPrice(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="产品Id:" prop="productId">
|
||||
<el-input v-model.number="formData.productId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品价格Id:" prop="productPriceId">
|
||||
<el-input v-model.number="formData.productPriceId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据源Id:" prop="sourceId">
|
||||
<el-input v-model.number="formData.sourceId" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司:" prop="companyName">
|
||||
<el-input v-model="formData.companyName" :clearable="true" placeholder="请输入公司" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌:" prop="producer">
|
||||
<el-input v-model="formData.producer" :clearable="true" placeholder="请输入品牌" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格:" prop="price">
|
||||
<el-input-number v-model="formData.price" :precision="2" :clearable="true"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-date-picker v-model="formData.publishDate" type="date" placeholder="选择日期" :clearable="true"></el-date-picker>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createCompanyPrice,
|
||||
updateCompanyPrice,
|
||||
findCompanyPrice
|
||||
} from '@/api/companyPrice'
|
||||
|
||||
defineOptions({
|
||||
name: 'CompanyPriceForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
productId: 0,
|
||||
productPriceId: 0,
|
||||
sourceId: 0,
|
||||
companyName: '',
|
||||
producer: '',
|
||||
price: 0,
|
||||
priceUnit: '',
|
||||
publishDate: new Date(),
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async() => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findCompanyPrice({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recompanyPrice
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createCompanyPrice(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateCompanyPrice(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createCompanyPrice(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,581 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" :rules="searchRule"
|
||||
@keyup.enter="onSubmit">
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-select v-model="searchInfo.status" placeholder="请选择" :clearable="true">
|
||||
<el-option v-for="item in statusOptions" :key="item.key" :label="item.label" :value="item.key" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建日期" prop="createdAt">
|
||||
<template #label>
|
||||
<span>
|
||||
创建日期
|
||||
<el-tooltip content="搜索范围是开始日期(包含)至结束日期(不包含)">
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</template>
|
||||
<el-date-picker v-model="searchInfo.startCreatedAt" type="datetime" placeholder="开始日期"
|
||||
:disabled-date="time=> searchInfo.endCreatedAt ? time.getTime() > searchInfo.endCreatedAt.getTime() : false"></el-date-picker>
|
||||
—
|
||||
<el-date-picker v-model="searchInfo.endCreatedAt" type="datetime" placeholder="结束日期"
|
||||
:disabled-date="time=> searchInfo.startCreatedAt ? time.getTime() < searchInfo.startCreatedAt.getTime() : false"></el-date-picker>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onDelete">删除</el-button>
|
||||
<el-button icon="check" style="margin-left: 10px;" :disabled="!multipleSelection.length"
|
||||
@click="onSyncClick">重新同步</el-button>
|
||||
</div>
|
||||
<el-table ref="multipleTable" style="width: 100%" tooltip-effect="dark" :data="tableData" row-key="ID"
|
||||
@selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column align="left" label="ID" prop="ID" width="80" />
|
||||
<el-table-column align="left" label="产品Code" prop="code" width="100" />
|
||||
<el-table-column align="left" label="产品名称" prop="name" width="120" />
|
||||
<el-table-column align="left" label="规格" prop="norm" width="300" />
|
||||
<el-table-column align="left" label="品牌" prop="producer" width="120" />
|
||||
<el-table-column align="left" label="价格类型" prop="priceType" width="120" />
|
||||
<el-table-column align="left" label="单位" prop="priceUnit" width="80" />
|
||||
<el-table-column align="left" label="价格" prop="price" width="80" />
|
||||
<el-table-column align="left" label="地区" prop="areaDetail" width="120" />
|
||||
<el-table-column align="left" label="公司" prop="dealer" width="240" />
|
||||
<el-table-column align="left" label="发布日期" prop="publishDate" width="100" />
|
||||
<el-table-column align="left" label="数据唯一值" prop="uniqueCode" width="300" />
|
||||
<el-table-column align="left" label="抓取时间" prop="crawlTime" width="180" />
|
||||
<el-table-column align="left" label="状态" prop="status" width="80">
|
||||
<template #default="scope">{{ formatCrawlStatus(scope.row.status) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="left" label="同步失败原因" prop="syncError" width="240" />
|
||||
|
||||
<el-table-column align="left" label="创建时间" width="180">
|
||||
<template #default="scope">{{ formatDate(scope.row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="320">
|
||||
<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 icon="edit" class="table-button"
|
||||
@click="updateCrawl100ppiFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="check" @click="syncRow(scope.row)">重新同步</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination layout="total, sizes, prev, pager, next, jumper" :current-page="page" :page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]" :total="total" @current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange" />
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{ type==='create'?'添加':'修改' }}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="产品Code:" prop="code">
|
||||
<el-input v-model="formData.code" :clearable="true" placeholder="请输入产品Code" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入产品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格:" prop="norm">
|
||||
<el-input v-model="formData.norm" :clearable="true" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌:" prop="producer">
|
||||
<el-input v-model="formData.producer" :clearable="true" placeholder="请输入品牌" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型:" prop="priceType">
|
||||
<el-input v-model="formData.priceType" :clearable="true" placeholder="请输入价格类型" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格:" prop="price">
|
||||
<el-input v-model="formData.price" :clearable="true" placeholder="请输入价格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区:" prop="areaDetail">
|
||||
<el-input v-model="formData.areaDetail" :clearable="true" placeholder="请输入地区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司:" prop="dealer">
|
||||
<el-input v-model="formData.dealer" :clearable="true" placeholder="请输入公司" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-input v-model="formData.publishDate" :clearable="true" placeholder="请输入发布日期" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据唯一值:" prop="uniqueCode">
|
||||
<el-input v-model="formData.uniqueCode" :clearable="true" placeholder="请输入数据唯一值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="抓取时间:" prop="crawlTime">
|
||||
<el-input v-model="formData.crawlTime" :clearable="true" placeholder="请输入抓取时间" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-input v-model.number="formData.status" :clearable="true" placeholder="请输入状态" />
|
||||
</el-form-item>
|
||||
<el-form-item label="同步失败原因:" prop="syncError">
|
||||
<el-input v-model="formData.syncError" :clearable="true" placeholder="请输入同步失败原因" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.ID }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品Code">
|
||||
{{ formData.code }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="产品名称">
|
||||
{{ formData.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="规格">
|
||||
{{ formData.norm }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="品牌">
|
||||
{{ formData.producer }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格类型">
|
||||
{{ formData.priceType }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="单位">
|
||||
{{ formData.priceUnit }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="价格">
|
||||
{{ formData.price }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="地区">
|
||||
{{ formData.areaDetail }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司">
|
||||
{{ formData.dealer }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发布日期">
|
||||
{{ formData.publishDate }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="数据唯一值">
|
||||
{{ formData.uniqueCode }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="抓取时间">
|
||||
{{ formData.crawlTime }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
{{ formatCrawlStatus(formData.status) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="同步失败原因">
|
||||
{{ formData.syncError }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">
|
||||
{{ formatDate(formData.createdAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间">
|
||||
{{ formatDate(formData.updatedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createCrawl100ppi,
|
||||
deleteCrawl100ppi,
|
||||
syncCrawl100ppi,
|
||||
deleteCrawl100ppiByIds,
|
||||
updateCrawl100ppi,
|
||||
findCrawl100ppi,
|
||||
getCrawl100ppiList
|
||||
} from '@/api/crawl100ppi'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
import { getDictFunc, formatDate, filterDict, ReturnArrImg, onDownloadFile } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Crawl100ppi'
|
||||
})
|
||||
|
||||
const statusOptions = [
|
||||
{ key: 1, label: '未同步' },
|
||||
{ key: 2, label: '同步成功' },
|
||||
{ key: 3, label: '同步失败' },
|
||||
]
|
||||
|
||||
const formatCrawlStatus = (status) => {
|
||||
let item
|
||||
statusOptions.forEach((option, index) => {
|
||||
if (option.key === status) {
|
||||
item = option
|
||||
return
|
||||
}
|
||||
})
|
||||
if (item) {
|
||||
return item.label
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
code: '',
|
||||
name: '',
|
||||
norm: '',
|
||||
producer: '',
|
||||
priceType: '',
|
||||
priceUnit: '',
|
||||
price: '',
|
||||
areaDetail: '',
|
||||
dealer: '',
|
||||
publishDate: '',
|
||||
uniqueCode: '',
|
||||
crawlTime: '',
|
||||
status: 0,
|
||||
syncError: '',
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const searchRule = reactive({
|
||||
createdAt: [
|
||||
{ validator: (rule, value, callback) => {
|
||||
if (searchInfo.value.startCreatedAt && !searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写结束日期'))
|
||||
} else if (!searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt) {
|
||||
callback(new Error('请填写开始日期'))
|
||||
} else if (searchInfo.value.startCreatedAt && searchInfo.value.endCreatedAt && (searchInfo.value.startCreatedAt.getTime() === searchInfo.value.endCreatedAt.getTime() || searchInfo.value.startCreatedAt.getTime() > searchInfo.value.endCreatedAt.getTime())) {
|
||||
callback(new Error('开始日期应当早于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}, trigger: 'change' }
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getCrawl100ppiList({ page: page.value, pageSize: pageSize.value, ...searchInfo.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 setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteCrawl100ppiFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(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 deleteCrawl100ppiByIds({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 同步行
|
||||
const syncRow = (row) => {
|
||||
ElMessageBox.confirm('确定要同步吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
syncCrawl100ppiFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选同步
|
||||
const onSyncClick = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(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 syncCrawl100ppi({ IDs })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '同步操作成功'
|
||||
})
|
||||
if (tableData.value.length === IDs.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateCrawl100ppiFunc = async(row) => {
|
||||
const res = await findCrawl100ppi({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawl100ppi
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteCrawl100ppiFunc = async(row) => {
|
||||
const res = await deleteCrawl100ppi({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 同步行
|
||||
const syncCrawl100ppiFunc = async(row) => {
|
||||
const res = await syncCrawl100ppi({ IDs: [row.ID] })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '同步操作成功'
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findCrawl100ppi({ ID: row.ID })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawl100ppi
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
code: '',
|
||||
name: '',
|
||||
norm: '',
|
||||
producer: '',
|
||||
priceType: '',
|
||||
priceUnit: '',
|
||||
price: '',
|
||||
areaDetail: '',
|
||||
dealer: '',
|
||||
publishDate: '',
|
||||
uniqueCode: '',
|
||||
crawlTime: '',
|
||||
status: 0,
|
||||
syncError: '',
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
code: '',
|
||||
name: '',
|
||||
norm: '',
|
||||
producer: '',
|
||||
priceType: '',
|
||||
priceUnit: '',
|
||||
price: '',
|
||||
areaDetail: '',
|
||||
dealer: '',
|
||||
publishDate: '',
|
||||
uniqueCode: '',
|
||||
crawlTime: '',
|
||||
status: 0,
|
||||
syncError: '',
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createCrawl100ppi(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateCrawl100ppi(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createCrawl100ppi(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="产品Code:" prop="code">
|
||||
<el-input v-model="formData.code" :clearable="true" placeholder="请输入产品Code" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品名称:" prop="name">
|
||||
<el-input v-model="formData.name" :clearable="true" placeholder="请输入产品名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="规格:" prop="norm">
|
||||
<el-input v-model="formData.norm" :clearable="true" placeholder="请输入规格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="品牌:" prop="producer">
|
||||
<el-input v-model="formData.producer" :clearable="true" placeholder="请输入品牌" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格类型:" prop="priceType">
|
||||
<el-input v-model="formData.priceType" :clearable="true" placeholder="请输入价格类型" />
|
||||
</el-form-item>
|
||||
<el-form-item label="单位:" prop="priceUnit">
|
||||
<el-input v-model="formData.priceUnit" :clearable="true" placeholder="请输入单位" />
|
||||
</el-form-item>
|
||||
<el-form-item label="价格:" prop="price">
|
||||
<el-input v-model="formData.price" :clearable="true" placeholder="请输入价格" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地区:" prop="areaDetail">
|
||||
<el-input v-model="formData.areaDetail" :clearable="true" placeholder="请输入地区" />
|
||||
</el-form-item>
|
||||
<el-form-item label="公司:" prop="dealer">
|
||||
<el-input v-model="formData.dealer" :clearable="true" placeholder="请输入公司" />
|
||||
</el-form-item>
|
||||
<el-form-item label="发布日期:" prop="publishDate">
|
||||
<el-input v-model="formData.publishDate" :clearable="true" placeholder="请输入发布日期" />
|
||||
</el-form-item>
|
||||
<el-form-item label="数据唯一值:" prop="uniqueCode">
|
||||
<el-input v-model="formData.uniqueCode" :clearable="true" placeholder="请输入数据唯一值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="抓取时间:" prop="crawlTime">
|
||||
<el-input v-model="formData.crawlTime" :clearable="true" placeholder="请输入抓取时间" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态:" prop="status">
|
||||
<el-input v-model.number="formData.status" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="同步失败原因:" prop="syncError">
|
||||
<el-input v-model="formData.syncError" :clearable="true" placeholder="请输入同步失败原因" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createCrawl100ppi,
|
||||
updateCrawl100ppi,
|
||||
findCrawl100ppi
|
||||
} from '@/api/crawl100ppi'
|
||||
|
||||
defineOptions({
|
||||
name: 'Crawl100ppiForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
code: '',
|
||||
name: '',
|
||||
norm: '',
|
||||
producer: '',
|
||||
priceType: '',
|
||||
priceUnit: '',
|
||||
price: '',
|
||||
areaDetail: '',
|
||||
dealer: '',
|
||||
publishDate: '',
|
||||
uniqueCode: '',
|
||||
crawlTime: '',
|
||||
status: 0,
|
||||
syncError: '',
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async() => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findCrawl100ppi({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawl100ppi
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createCrawl100ppi(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateCrawl100ppi(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createCrawl100ppi(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
@ -0,0 +1,409 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-search-box">
|
||||
<el-form ref="elSearchFormRef" :inline="true" :model="searchInfo" class="demo-form-inline" @keyup.enter="onSubmit">
|
||||
<el-form-item label="字段名" prop="fieldName">
|
||||
<template #label>
|
||||
<span>字段名</span>
|
||||
</template>
|
||||
<el-input v-model="searchInfo.fieldName" type="input" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否同步:" prop="isSync">
|
||||
<el-select v-model="searchInfo.isSync" placeholder="请选择" :clearable="true">
|
||||
<el-option v-for="item in ['yes', 'no']" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="search" @click="onSubmit">查询</el-button>
|
||||
<el-button icon="refresh" @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button type="primary" icon="plus" @click="openDialog">新增</el-button>
|
||||
<el-button icon="delete" style="margin-left: 10px;" :disabled="!multipleSelection.length" @click="onDelete">删除</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
:data="tableData"
|
||||
row-key="id"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="sortChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column sortable align="left" label="ID" prop="id" width="80" />
|
||||
<el-table-column align="left" label="字段名" prop="fieldName" width="120" />
|
||||
<el-table-column align="left" label="原始值" prop="fieldSource" width="300" />
|
||||
<el-table-column align="left" label="替换后的值" prop="fieldReplace" width="200" />
|
||||
<el-table-column align="left" label="是否同步" prop="isSync" width="120" />
|
||||
<el-table-column align="left" label="操作" fixed="right" min-width="240">
|
||||
<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 icon="edit" class="table-button" @click="updateCrawlFieldReplaceFunc(scope.row)">变更</el-button>
|
||||
<el-button type="primary" link icon="delete" @click="deleteRow(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="gva-pagination">
|
||||
<el-pagination
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[10, 30, 50, 100]"
|
||||
:total="total"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-drawer size="800" v-model="dialogFormVisible" :show-close="false" :before-close="closeDialog">
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">{{ type==='create'?'添加':'修改' }}</span>
|
||||
<div>
|
||||
<el-button type="primary" @click="enterDialog">确 定</el-button>
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form :model="formData" label-position="top" ref="elFormRef" :rules="rule" label-width="80px">
|
||||
<el-form-item label="字段名:" prop="fieldName">
|
||||
<el-input v-model="formData.fieldName" :clearable="true" placeholder="请输入字段名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="原始值:" prop="fieldSource">
|
||||
<el-input v-model="formData.fieldSource" :clearable="true" placeholder="请输入原始值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="替换后的值:" prop="fieldReplace">
|
||||
<el-input v-model="formData.fieldReplace" :clearable="true" placeholder="请输入替换后的值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否同步:" prop="isSync">
|
||||
<el-select v-model="formData.isSync" placeholder="请选择是否同步" style="width:100%" :clearable="true" >
|
||||
<el-option v-for="item in ['yes', 'no']" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-drawer>
|
||||
|
||||
<el-drawer size="800" v-model="detailShow" :before-close="closeDetailShow" destroy-on-close>
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg">查看详情</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="ID">
|
||||
{{ formData.id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="字段名">
|
||||
{{ formData.fieldName }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="原始值">
|
||||
{{ formData.fieldSource }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="替换后的值">
|
||||
{{ formData.fieldReplace }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否同步">
|
||||
{{ formData.isSync }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createCrawlFieldReplace,
|
||||
deleteCrawlFieldReplace,
|
||||
deleteCrawlFieldReplaceByIds,
|
||||
updateCrawlFieldReplace,
|
||||
findCrawlFieldReplace,
|
||||
getCrawlFieldReplaceList
|
||||
} from '@/api/crawlFieldReplace'
|
||||
|
||||
// 全量引入格式化工具 请按需保留
|
||||
// import { getDictFunc, formatDate, formatBoolean, filterDict, ReturnArrImg, onDownloadFile } from '@/utils/format'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'CrawlFieldReplace'
|
||||
})
|
||||
|
||||
// 自动化生成的字典(可能为空)以及字段
|
||||
const formData = ref({
|
||||
id: 0,
|
||||
fieldName: '',
|
||||
fieldReplace: '',
|
||||
fieldSource: '',
|
||||
isSync: '',
|
||||
})
|
||||
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
fieldName: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
{
|
||||
whitespace: true,
|
||||
message: '不能只输入空格',
|
||||
trigger: ['input', 'blur'],
|
||||
}
|
||||
],
|
||||
id: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
],
|
||||
isSync: [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input', 'blur'],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
const elSearchFormRef = ref()
|
||||
|
||||
// =========== 表格控制部分 ===========
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
const searchInfo = ref({})
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
const sortMap = {
|
||||
id: 'id',
|
||||
}
|
||||
|
||||
let sort = sortMap[prop]
|
||||
if (!sort) {
|
||||
sort = prop.replace(/[A-Z]/g, match => `_${match.toLowerCase()}`)
|
||||
}
|
||||
|
||||
searchInfo.value.sort = sort
|
||||
searchInfo.value.order = order
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 重置
|
||||
const onReset = () => {
|
||||
searchInfo.value = {}
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const onSubmit = () => {
|
||||
elSearchFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
page.value = 1
|
||||
pageSize.value = 10
|
||||
getTableData()
|
||||
})
|
||||
}
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 修改页面容量
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getCrawlFieldReplaceList({ page: page.value, pageSize: pageSize.value, ...searchInfo.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 setOptions = async() => {
|
||||
}
|
||||
|
||||
// 获取需要的字典 可能为空 按需保留
|
||||
setOptions()
|
||||
|
||||
// 多选数据
|
||||
const multipleSelection = ref([])
|
||||
// 多选
|
||||
const handleSelectionChange = (val) => {
|
||||
multipleSelection.value = val
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteRow = (row) => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteCrawlFieldReplaceFunc(row)
|
||||
})
|
||||
}
|
||||
|
||||
// 多选删除
|
||||
const onDelete = async() => {
|
||||
ElMessageBox.confirm('确定要删除吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(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 deleteCrawlFieldReplaceByIds({ ids })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === ids.length && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 行为控制标记(弹窗内部需要增还是改)
|
||||
const type = ref('')
|
||||
|
||||
// 更新行
|
||||
const updateCrawlFieldReplaceFunc = async(row) => {
|
||||
const res = await findCrawlFieldReplace({ id: row.id })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawlFieldReplace
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 删除行
|
||||
const deleteCrawlFieldReplaceFunc = async(row) => {
|
||||
const res = await deleteCrawlFieldReplace({ id: row.id })
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功'
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗控制标记
|
||||
const dialogFormVisible = ref(false)
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
|
||||
// 打开详情弹窗
|
||||
const openDetailShow = () => {
|
||||
detailShow.value = true
|
||||
}
|
||||
|
||||
// 打开详情
|
||||
const getDetails = async(row) => {
|
||||
// 打开弹窗
|
||||
const res = await findCrawlFieldReplace({ id: row.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawlFieldReplace
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
formData.value = {
|
||||
fieldName: '',
|
||||
fieldReplace: '',
|
||||
fieldSource: '',
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
formData.value = {
|
||||
fieldName: '',
|
||||
fieldReplace: '',
|
||||
fieldSource: '',
|
||||
id: 0,
|
||||
}
|
||||
}
|
||||
// 弹窗确定
|
||||
const enterDialog = async() => {
|
||||
elFormRef.value?.validate(async(valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createCrawlFieldReplace(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateCrawlFieldReplace(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createCrawlFieldReplace(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="gva-form-box">
|
||||
<el-form :model="formData" ref="elFormRef" label-position="right" :rules="rule" label-width="80px">
|
||||
<el-form-item label="字段名:" prop="fieldName">
|
||||
<el-input v-model="formData.fieldName" :clearable="true" placeholder="请输入字段名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="替换后的值:" prop="fieldReplace">
|
||||
<el-input v-model="formData.fieldReplace" :clearable="true" placeholder="请输入替换后的值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="原始值:" prop="fieldSource">
|
||||
<el-input v-model="formData.fieldSource" :clearable="true" placeholder="请输入原始值" />
|
||||
</el-form-item>
|
||||
<el-form-item label="ID:" prop="id">
|
||||
<el-input v-model.number="formData.id" :clearable="true" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否同步:" prop="isSync">
|
||||
<el-select v-model="formData.isSync" placeholder="请选择" style="width:100%" :clearable="true">
|
||||
<el-option v-for="item in [100]" :key="item" :label="item" :value="item" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="save">保存</el-button>
|
||||
<el-button type="primary" @click="back">返回</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createCrawlFieldReplace,
|
||||
updateCrawlFieldReplace,
|
||||
findCrawlFieldReplace
|
||||
} from '@/api/crawlFieldReplace'
|
||||
|
||||
defineOptions({
|
||||
name: 'CrawlFieldReplaceForm'
|
||||
})
|
||||
|
||||
// 自动获取字典
|
||||
import { getDictFunc } from '@/utils/format'
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref, reactive } from 'vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const type = ref('')
|
||||
const formData = ref({
|
||||
fieldName: '',
|
||||
fieldReplace: '',
|
||||
fieldSource: '',
|
||||
id: 0,
|
||||
})
|
||||
// 验证规则
|
||||
const rule = reactive({
|
||||
fieldName : [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input','blur'],
|
||||
}],
|
||||
id : [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input','blur'],
|
||||
}],
|
||||
isSync : [{
|
||||
required: true,
|
||||
message: '',
|
||||
trigger: ['input','blur'],
|
||||
}],
|
||||
})
|
||||
|
||||
const elFormRef = ref()
|
||||
|
||||
// 初始化方法
|
||||
const init = async () => {
|
||||
// 建议通过url传参获取目标数据ID 调用 find方法进行查询数据操作 从而决定本页面是create还是update 以下为id作为url参数示例
|
||||
if (route.query.id) {
|
||||
const res = await findCrawlFieldReplace({ ID: route.query.id })
|
||||
if (res.code === 0) {
|
||||
formData.value = res.data.recrawlFieldReplace
|
||||
type.value = 'update'
|
||||
}
|
||||
} else {
|
||||
type.value = 'create'
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
// 保存按钮
|
||||
const save = async() => {
|
||||
elFormRef.value?.validate( async (valid) => {
|
||||
if (!valid) return
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createCrawlFieldReplace(formData.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateCrawlFieldReplace(formData.value)
|
||||
break
|
||||
default:
|
||||
res = await createCrawlFieldReplace(formData.value)
|
||||
break
|
||||
}
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '创建/更改成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 返回按钮
|
||||
const back = () => {
|
||||
router.go(-1)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@/api/crawlFieldReplace
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<template>
|
||||
<div class="dashboard-line-box">
|
||||
<div class="dashboard-line-title">
|
||||
访问趋势
|
||||
</div>
|
||||
<div
|
||||
ref="echart"
|
||||
class="dashboard-line"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import * as echarts from 'echarts'
|
||||
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useWindowResize } from '@/hooks/use-windows-resize'
|
||||
var dataAxis = []
|
||||
for (var i = 1; i < 13; i++) {
|
||||
dataAxis.push(`${i}月`)
|
||||
}
|
||||
var data = [
|
||||
220,
|
||||
182,
|
||||
191,
|
||||
234,
|
||||
290,
|
||||
330,
|
||||
310,
|
||||
123,
|
||||
442,
|
||||
321,
|
||||
90,
|
||||
149,
|
||||
]
|
||||
var yMax = 500
|
||||
var dataShadow = []
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
dataShadow.push(yMax)
|
||||
}
|
||||
|
||||
let chart = null
|
||||
const echart = ref(null)
|
||||
|
||||
useWindowResize(() => {
|
||||
if (!chart) {
|
||||
return
|
||||
}
|
||||
chart.resize()
|
||||
})
|
||||
|
||||
const initChart = () => {
|
||||
if (chart) {
|
||||
chart = null
|
||||
}
|
||||
chart = echarts.init(echart.value)
|
||||
setOptions()
|
||||
}
|
||||
const setOptions = () => {
|
||||
chart.setOption({
|
||||
grid: {
|
||||
left: '40',
|
||||
right: '20',
|
||||
top: '40',
|
||||
bottom: '20',
|
||||
},
|
||||
xAxis: {
|
||||
data: dataAxis,
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
z: 10,
|
||||
},
|
||||
yAxis: {
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisTick: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
},
|
||||
},
|
||||
},
|
||||
dataZoom: [
|
||||
{
|
||||
type: 'inside',
|
||||
},
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
barWidth: '40%',
|
||||
itemStyle: {
|
||||
borderRadius: [5, 5, 0, 0],
|
||||
color: '#188df0',
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: '#188df0',
|
||||
},
|
||||
},
|
||||
data: data,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async() => {
|
||||
await nextTick()
|
||||
initChart()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (!chart) {
|
||||
return
|
||||
}
|
||||
chart.dispose()
|
||||
chart = null
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dashboard-line-box {
|
||||
.dashboard-line {
|
||||
background-color: #fff;
|
||||
height: 360px;
|
||||
width: 100%;
|
||||
}
|
||||
.dashboard-line-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
<template>
|
||||
<div class="commit-table">
|
||||
<div class="commit-table-title">
|
||||
更新日志
|
||||
</div>
|
||||
<div class="log">
|
||||
<div
|
||||
v-for="(item,key) in dataTimeline"
|
||||
:key="key"
|
||||
class="log-item"
|
||||
>
|
||||
<div class="flex-1 flex key-box">
|
||||
<span
|
||||
class="key"
|
||||
:class="key<3&&'top'"
|
||||
>{{ key+1 }}</span>
|
||||
</div>
|
||||
<div class="flex-5 flex message">{{ item.message }}</div>
|
||||
<div class="flex-3 flex form">{{ item.from }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'DashboardTable',
|
||||
})
|
||||
|
||||
const dataTimeline = ref([])
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.commit-table{
|
||||
background-color: #fff;
|
||||
height: 400px;
|
||||
&-title{
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.log{
|
||||
&-item{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 14px;
|
||||
.key-box{
|
||||
justify-content: center;
|
||||
}
|
||||
.key{
|
||||
&.top{
|
||||
background: #314659;
|
||||
color: #FFFFFF;;
|
||||
}
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #F0F2F5;
|
||||
text-align: center;
|
||||
color:rgba($color: #000000, $alpha: 0.65)
|
||||
}
|
||||
.message{
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
.form{
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
margin-left: 12px;
|
||||
}
|
||||
.flex{
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.flex-1{
|
||||
flex:1;
|
||||
}
|
||||
.flex-2{
|
||||
flex:2;
|
||||
}
|
||||
.flex-3{
|
||||
flex:3;
|
||||
}
|
||||
.flex-4{
|
||||
flex:4;
|
||||
}
|
||||
.flex-5{
|
||||
flex:5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
<template>
|
||||
<div class="page">
|
||||
<div class="gva-card-box">
|
||||
<div class="gva-card gva-top-card">
|
||||
<div class="gva-top-card-left">
|
||||
<div class="gva-top-card-left-title">早安,请开始一天的工作吧</div>
|
||||
<div class="gva-top-card-left-dot">{{ weatherInfo }}</div>
|
||||
<div>
|
||||
<!-- <div class="gva-top-card-left-item">
|
||||
插件仓库:
|
||||
<a
|
||||
style="color:#409EFF"
|
||||
target="view_window"
|
||||
href="https://plugin.gin-vue-admin.com/#/layout/home"
|
||||
>https://plugin.gin-vue-admin.com</a>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/dashboard.png"
|
||||
class="gva-top-card-right"
|
||||
alt
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gva-card-box">
|
||||
<div class="gva-card quick-entrance">
|
||||
<div class="gva-card-title">快捷入口</div>
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
v-for="(card, key) in toolCards"
|
||||
:key="key"
|
||||
:span="4"
|
||||
:xs="8"
|
||||
class="quick-entrance-items"
|
||||
@click="toTarget(card.name)"
|
||||
>
|
||||
<div class="quick-entrance-item">
|
||||
<div
|
||||
class="quick-entrance-item-icon"
|
||||
:style="{ backgroundColor: card.bg }"
|
||||
>
|
||||
<el-icon>
|
||||
<component
|
||||
:is="card.icon"
|
||||
:style="{ color: card.color }"
|
||||
/>
|
||||
</el-icon>
|
||||
</div>
|
||||
<p>{{ card.label }}</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gva-card-box">
|
||||
<div class="gva-card">
|
||||
<div class="gva-card-title">数据统计</div>
|
||||
<div class="p-4">
|
||||
<el-row :gutter="20">
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="18"
|
||||
>
|
||||
<echarts-line />
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="24"
|
||||
:sm="6"
|
||||
>
|
||||
<dashboard-table />
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import EchartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue'
|
||||
import DashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useWeatherInfo } from '@/view/dashboard/weather.js'
|
||||
|
||||
defineOptions({
|
||||
name: 'Dashboard'
|
||||
})
|
||||
|
||||
const weatherInfo = useWeatherInfo()
|
||||
|
||||
const toolCards = ref([
|
||||
{
|
||||
label: '用户管理',
|
||||
icon: 'monitor',
|
||||
name: 'user',
|
||||
color: '#ff9c6e',
|
||||
bg: 'rgba(255, 156, 110,.3)'
|
||||
},
|
||||
{
|
||||
label: '角色管理',
|
||||
icon: 'setting',
|
||||
name: 'authority',
|
||||
color: '#69c0ff',
|
||||
bg: 'rgba(105, 192, 255,.3)'
|
||||
},
|
||||
{
|
||||
label: '菜单管理',
|
||||
icon: 'menu',
|
||||
name: 'menu',
|
||||
color: '#b37feb',
|
||||
bg: 'rgba(179, 127, 235,.3)'
|
||||
},
|
||||
{
|
||||
label: '代码生成器',
|
||||
icon: 'cpu',
|
||||
name: 'autoCode',
|
||||
color: '#ffd666',
|
||||
bg: 'rgba(255, 214, 102,.3)'
|
||||
},
|
||||
{
|
||||
label: '表单生成器',
|
||||
icon: 'document-checked',
|
||||
name: 'formCreate',
|
||||
color: '#ff85c0',
|
||||
bg: 'rgba(255, 133, 192,.3)'
|
||||
},
|
||||
{
|
||||
label: '关于我们',
|
||||
icon: 'user',
|
||||
name: 'about',
|
||||
color: '#5cdbd3',
|
||||
bg: 'rgba(92, 219, 211,.3)'
|
||||
}
|
||||
])
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const toTarget = (name) => {
|
||||
router.push({ name })
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
@apply p-0;
|
||||
.gva-card-box{
|
||||
@apply p-4;
|
||||
&+.gva-card-box{
|
||||
@apply pt-0;
|
||||
}
|
||||
}
|
||||
.gva-card {
|
||||
@apply box-border bg-white rounded h-auto px-6 py-8 overflow-hidden shadow-sm;
|
||||
.gva-card-title{
|
||||
@apply pb-5 border-t-0 border-l-0 border-r-0 border-b border-solid border-gray-100;
|
||||
}
|
||||
}
|
||||
.gva-top-card {
|
||||
@apply h-72 flex items-center justify-between text-gray-500;
|
||||
&-left {
|
||||
@apply h-full flex flex-col w-auto;
|
||||
&-title {
|
||||
@apply text-3xl text-gray-600;
|
||||
}
|
||||
&-dot {
|
||||
@apply mt-4 text-gray-600 text-lg;
|
||||
}
|
||||
&-item{
|
||||
+.gva-top-card-left-item{
|
||||
margin-top: 24px;
|
||||
}
|
||||
margin-top: 14px;
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
height: 600px;
|
||||
width: 600px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
}
|
||||
::v-deep(.el-card__header){
|
||||
@apply p-0 border-gray-200;
|
||||
}
|
||||
.card-header{
|
||||
@apply pb-5 border-b border-solid border-gray-200 border-t-0 border-l-0 border-r-0;
|
||||
}
|
||||
.quick-entrance-items {
|
||||
@apply flex items-center justify-center text-center text-gray-800;
|
||||
.quick-entrance-item {
|
||||
@apply px-8 py-6 flex items-center flex-col transition-all duration-100 ease-in-out rounded-lg cursor-pointer;
|
||||
&:hover{
|
||||
@apply shadow-lg;
|
||||
}
|
||||
&-icon {
|
||||
@apply flex items-center h-16 w-16 rounded-lg justify-center mx-0 my-auto text-2xl;
|
||||
}
|
||||
p {
|
||||
@apply mt-2.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.dashboard-icon {
|
||||
@apply flex items-center text-xl mr-2 text-blue-400;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
import axios from 'axios'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const weatherInfo = ref('今日晴,0℃ - 10℃,天气寒冷,注意添加衣物。')
|
||||
const amapKey = '1f0c8b27920dc41d204800793d629d8e'
|
||||
|
||||
export const useWeatherInfo = () => {
|
||||
ip()
|
||||
return weatherInfo
|
||||
}
|
||||
|
||||
export const ip = async () => {
|
||||
// key换成你自己的 https://console.amap.com/dev/index
|
||||
if (amapKey === '') {
|
||||
return false
|
||||
}
|
||||
const res = await axios.get('https://restapi.amap.com/v3/ip?key=' + amapKey)
|
||||
var cityCode
|
||||
if (res.data.status !== '0') {
|
||||
cityCode = res.data.adcode
|
||||
} else {
|
||||
cityCode = '100000'
|
||||
}
|
||||
getWeather(cityCode)
|
||||
}
|
||||
|
||||
const getWeather = async (code) => {
|
||||
const url = 'https://restapi.amap.com/v3/weather/weatherInfo?key=' + amapKey + '&extensions=base&city=' + code
|
||||
const response = await axios.get(url)
|
||||
if (response.data.status === '1') {
|
||||
const s = response.data.lives[0]
|
||||
weatherInfo.value = s.city + ' 天气:' + s.weather + ' 温度:' + s.temperature + '摄氏度 风向:' + s.winddirection + ' 风力:' + s.windpower + '级 空气湿度:' + s.humidity
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="w-full h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div class="flex flex-col items-center text-2xl gap-4">
|
||||
<img src="../../assets/notFound.png">
|
||||
<p>页面被神秘力量吸走了(如果您是开源版请联系我们修复)</p>
|
||||
<p style="font-size:18px;line-height:40px;">常见问题为当前此角色无当前路由,如果确定要使用本路由,请到角色管理进行分配</p>
|
||||
<p>↓</p>
|
||||
<img
|
||||
src="../../assets/qm.png"
|
||||
class="w-16 h-16 mt-20"
|
||||
>
|
||||
<el-button @click="toDashboard">返回首页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({
|
||||
name: 'Error'
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const toDashboard = () => {
|
||||
router.push({ name: userStore.userInfo.authority.defaultRouter })
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({
|
||||
name: 'Reload'
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
router.go(-1)
|
||||
</script>
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<template>
|
||||
<div class="break-point">
|
||||
<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"
|
||||
>
|
||||
</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"
|
||||
>
|
||||
<div
|
||||
v-if="file"
|
||||
class="list-item"
|
||||
>
|
||||
<el-icon>
|
||||
<document />
|
||||
</el-icon>
|
||||
<span>{{ file.name }}</span>
|
||||
<span class="percentage">{{ percentage }}%</span>
|
||||
<el-progress
|
||||
:show-text="false"
|
||||
:text-inside="false"
|
||||
:stroke-width="2"
|
||||
:percentage="percentage"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
<div class="tips">此版本为先行体验功能测试版,样式美化和性能优化正在进行中,上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</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'
|
||||
|
||||
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 choseFile = async(e) => {
|
||||
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 // 记录长度用于百分比展示
|
||||
}
|
||||
} 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
|
||||
}
|
||||
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'))
|
||||
}
|
||||
</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;
|
||||
color: #000;
|
||||
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;
|
||||
}
|
||||
|
||||
.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-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>
|
||||
|
|
@ -0,0 +1,232 @@
|
|||
<template>
|
||||
<div>
|
||||
<warning-bar title="在资源权限中将此角色的资源权限清空 或者不包含创建者的角色 即可屏蔽此客户资源的显示" />
|
||||
<div class="gva-table-box">
|
||||
<div class="gva-btn-list">
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="plus"
|
||||
@click="openDialog"
|
||||
>新增</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
ref="multipleTable"
|
||||
:data="tableData"
|
||||
style="width: 100%"
|
||||
tooltip-effect="dark"
|
||||
row-key="ID"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="接入日期"
|
||||
width="180"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span>{{ formatDate(scope.row.CreatedAt) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="姓名"
|
||||
prop="customerName"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="电话"
|
||||
prop="customerPhoneData"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="接入人ID"
|
||||
prop="sysUserId"
|
||||
width="120"
|
||||
/>
|
||||
<el-table-column
|
||||
align="left"
|
||||
label="操作"
|
||||
min-width="160"
|
||||
>
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="edit"
|
||||
@click="updateCustomer(scope.row)"
|
||||
>变更</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
icon="delete"
|
||||
@click="deleteCustomer(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]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@current-change="handleCurrentChange"
|
||||
@size-change="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="dialogFormVisible"
|
||||
:before-close="closeDialog"
|
||||
title="客户"
|
||||
>
|
||||
<el-form
|
||||
:inline="true"
|
||||
:model="form"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="客户名">
|
||||
<el-input
|
||||
v-model="form.customerName"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户电话">
|
||||
<el-input
|
||||
v-model="form.customerPhoneData"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeDialog">取 消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="enterDialog"
|
||||
>确 定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
createExaCustomer,
|
||||
updateExaCustomer,
|
||||
deleteExaCustomer,
|
||||
getExaCustomer,
|
||||
getExaCustomerList
|
||||
} from '@/api/customer'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { formatDate } from '@/utils/format'
|
||||
|
||||
defineOptions({
|
||||
name: 'Customer'
|
||||
})
|
||||
|
||||
const form = ref({
|
||||
customerName: '',
|
||||
customerPhoneData: ''
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const pageSize = ref(10)
|
||||
const tableData = ref([])
|
||||
|
||||
// 分页
|
||||
const handleSizeChange = (val) => {
|
||||
pageSize.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
const handleCurrentChange = (val) => {
|
||||
page.value = val
|
||||
getTableData()
|
||||
}
|
||||
|
||||
// 查询
|
||||
const getTableData = async() => {
|
||||
const table = await getExaCustomerList({ page: page.value, pageSize: pageSize.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 dialogFormVisible = ref(false)
|
||||
const type = ref('')
|
||||
const updateCustomer = async(row) => {
|
||||
const res = await getExaCustomer({ ID: row.ID })
|
||||
type.value = 'update'
|
||||
if (res.code === 0) {
|
||||
form.value = res.data.customer
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
}
|
||||
const closeDialog = () => {
|
||||
dialogFormVisible.value = false
|
||||
form.value = {
|
||||
customerName: '',
|
||||
customerPhoneData: ''
|
||||
}
|
||||
}
|
||||
const deleteCustomer = async(row) => {
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
const enterDialog = async() => {
|
||||
let res
|
||||
switch (type.value) {
|
||||
case 'create':
|
||||
res = await createExaCustomer(form.value)
|
||||
break
|
||||
case 'update':
|
||||
res = await updateExaCustomer(form.value)
|
||||
break
|
||||
default:
|
||||
res = await createExaCustomer(form.value)
|
||||
break
|
||||
}
|
||||
|
||||
if (res.code === 0) {
|
||||
closeDialog()
|
||||
getTableData()
|
||||
}
|
||||
}
|
||||
const openDialog = () => {
|
||||
type.value = 'create'
|
||||
dialogFormVisible.value = true
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
defineOptions({
|
||||
name: 'Example'
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
<template>
|
||||
<div v-loading.fullscreen.lock="fullscreenLoading">
|
||||
<div class="gva-table-box">
|
||||
<warning-bar
|
||||
title="点击“文件名/备注”可以编辑文件名或者备注内容。"
|
||||
/>
|
||||
<div class="gva-btn-list">
|
||||
<upload-common
|
||||
:image-common="imageCommon"
|
||||
@on-success="getTableData"
|
||||
/>
|
||||
<upload-image
|
||||
:image-url="imageUrl"
|
||||
:file-size="512"
|
||||
:max-w-h="1080"
|
||||
@on-success="getTableData"
|
||||
/>
|
||||
<el-input
|
||||
v-model="search.keyword"
|
||||
class="keyword"
|
||||
placeholder="请输入文件名或备注"
|
||||
/>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="search"
|
||||
@click="getTableData"
|
||||
>查询</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 === '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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getFileList, deleteFile, editFileName } 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 { formatDate } from '@/utils/format'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
|
||||
import { ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
defineOptions({
|
||||
name: 'Upload',
|
||||
})
|
||||
|
||||
const path = ref(import.meta.env.VITE_BASE_API)
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
getTableData()
|
||||
|
||||
const deleteFileFunc = async(row) => {
|
||||
ElMessageBox.confirm('此操作将永久删除文件, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(async() => {
|
||||
const res = await deleteFile(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '删除成功!',
|
||||
})
|
||||
if (tableData.value.length === 1 && page.value > 1) {
|
||||
page.value--
|
||||
}
|
||||
getTableData()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '已取消删除',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const downloadFile = (row) => {
|
||||
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
|
||||
downloadImage(row.url, row.name)
|
||||
} else {
|
||||
debugger
|
||||
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 }) => {
|
||||
row.name = value
|
||||
// console.log(row)
|
||||
const res = await editFileName(row)
|
||||
if (res.code === 0) {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '编辑成功!',
|
||||
})
|
||||
getTableData()
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage({
|
||||
type: 'info',
|
||||
message: '取消修改'
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.name {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,361 @@
|
|||
<template>
|
||||
<div class="rounded-lg flex items-center justify-evenly w-full h-full relative bg-white md:w-screen md:h-screen md:bg-[#194bfb] overflow-hidden">
|
||||
<div class="rounded-md w-full h-full flex items-center justify-center overflow-hidden">
|
||||
<div class="oblique h-[130%] w-3/5 bg-white transform -rotate-12 absolute -ml-80" />
|
||||
<div
|
||||
v-if="!page.showForm"
|
||||
:class="[page.showReadme ?'slide-out-right' :'slide-in-fwd-top' ]"
|
||||
>
|
||||
<div class=" text-lg">
|
||||
<div class="font-sans text-4xl font-bold text-center mb-4">WEB-ADMIN</div>
|
||||
<p class="text-gray-600 mb-2">初始化须知</p>
|
||||
<p class="text-gray-600 mb-2">1.您需有用一定的VUE和GOLANG基础</p>
|
||||
<p class="text-gray-600 mb-2">2.请您确认是否已经阅读过官方文档初始化视频</p>
|
||||
<p class="text-gray-600 mb-2">3.请您确认是否了解后续的配置流程</p>
|
||||
<p class="text-gray-600 mb-2">4.如果您使用mysql数据库,请确认数据库引擎为<span class="text-red-600 font-bold text-3xl ml-2 ">innoDB</span></p>
|
||||
<p class="text-gray-600 mb-2">注:开发组不为文档中书写过的内容提供无偿服务</p>
|
||||
<p class="flex items-center justify-between mt-8">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="showNext"
|
||||
>
|
||||
我已确认
|
||||
</el-button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="page.showForm "
|
||||
:class="[ page.showForm ? 'slide-in-left' : 'slide-out-right' ]"
|
||||
class="w-96"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
label-width="100px"
|
||||
size="large"
|
||||
>
|
||||
<el-form-item label="数据库类型">
|
||||
<el-select
|
||||
v-model="form.dbType"
|
||||
placeholder="请选择"
|
||||
class="w-full"
|
||||
@change="changeDB"
|
||||
>
|
||||
<el-option
|
||||
key="mysql"
|
||||
label="mysql"
|
||||
value="mysql"
|
||||
/>
|
||||
<el-option
|
||||
key="pgsql"
|
||||
label="pgsql"
|
||||
value="pgsql"
|
||||
/>
|
||||
<el-option
|
||||
key="oracle"
|
||||
label="oracle"
|
||||
value="oracle"
|
||||
/>
|
||||
<el-option
|
||||
key="mssql"
|
||||
label="mssql"
|
||||
value="mssql"
|
||||
/>
|
||||
<el-option
|
||||
key="sqlite"
|
||||
label="sqlite"
|
||||
value="sqlite"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.dbType !== 'sqlite'"
|
||||
label="host"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.host"
|
||||
placeholder="请输入数据库链接"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.dbType !== 'sqlite'"
|
||||
label="port"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.port"
|
||||
placeholder="请输入数据库端口"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.dbType !== 'sqlite'"
|
||||
label="userName"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.userName"
|
||||
placeholder="请输入数据库用户名"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.dbType !== 'sqlite'"
|
||||
label="password"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
placeholder="请输入数据库密码(没有则为空)"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="dbName">
|
||||
<el-input
|
||||
v-model="form.dbName"
|
||||
placeholder="请输入数据库名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="form.dbType === 'sqlite'"
|
||||
label="dbPath"
|
||||
>
|
||||
<el-input
|
||||
v-model="form.dbPath"
|
||||
placeholder="请输入sqlite数据库文件存放路径"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<div style="text-align: right">
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="onSubmit"
|
||||
>立即初始化</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hidden md:block w-1/2 h-full float-right bg-[#194bfb]"><img
|
||||
class="h-full"
|
||||
src="@/assets/login_right_banner.jpg"
|
||||
alt="banner"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// @ts-ignore
|
||||
import { initDB } from '@/api/initdb'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { ElLoading, ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({
|
||||
name: 'Init',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const page = reactive({
|
||||
showReadme: false,
|
||||
showForm: false
|
||||
})
|
||||
|
||||
const showNext = () => {
|
||||
page.showReadme = false
|
||||
setTimeout(() => {
|
||||
page.showForm = true
|
||||
}, 20)
|
||||
}
|
||||
|
||||
const goDoc = () => {
|
||||
// test
|
||||
}
|
||||
|
||||
const out = ref(false)
|
||||
|
||||
const form = reactive({
|
||||
dbType: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: '3306',
|
||||
userName: 'root',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
|
||||
const changeDB = (val) => {
|
||||
switch (val) {
|
||||
case 'mysql':
|
||||
Object.assign(form, {
|
||||
dbType: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: '3306',
|
||||
userName: 'root',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
break
|
||||
case 'pgsql':
|
||||
Object.assign(form, {
|
||||
dbType: 'pgsql',
|
||||
host: '127.0.0.1',
|
||||
port: '5432',
|
||||
userName: 'postgres',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
break
|
||||
case 'oracle':
|
||||
Object.assign(form, {
|
||||
dbType: 'oracle',
|
||||
host: '127.0.0.1',
|
||||
port: '1521',
|
||||
userName: 'oracle',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
break
|
||||
case 'mssql':
|
||||
Object.assign(form, {
|
||||
dbType: 'mssql',
|
||||
host: '127.0.0.1',
|
||||
port: '1433',
|
||||
userName: 'mssql',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
break
|
||||
case 'sqlite':
|
||||
Object.assign(form, {
|
||||
dbType: 'sqlite',
|
||||
host: '',
|
||||
port: '',
|
||||
userName: '',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
break
|
||||
default:
|
||||
Object.assign(form, {
|
||||
dbType: 'mysql',
|
||||
host: '127.0.0.1',
|
||||
port: '3306',
|
||||
userName: 'root',
|
||||
password: '',
|
||||
dbName: 'gva',
|
||||
dbPath: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
const onSubmit = async() => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '正在初始化数据库,请稍候',
|
||||
spinner: 'loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
})
|
||||
try {
|
||||
const res = await initDB(form)
|
||||
if (res.code === 0) {
|
||||
out.value = true
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: res.msg,
|
||||
})
|
||||
router.push({ name: 'Login' })
|
||||
}
|
||||
loading.close()
|
||||
} catch (err) {
|
||||
loading.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.slide-in-fwd-top {
|
||||
-webkit-animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94)
|
||||
both;
|
||||
animation: slide-in-fwd-top 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
.slide-out-right {
|
||||
-webkit-animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53)
|
||||
both;
|
||||
animation: slide-out-right 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53) both;
|
||||
}
|
||||
.slide-in-left {
|
||||
-webkit-animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94)
|
||||
both;
|
||||
animation: slide-in-left 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) both;
|
||||
}
|
||||
@-webkit-keyframes slide-in-fwd-top {
|
||||
0% {
|
||||
transform: translateZ(-1400px) translateY(-800px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateZ(0) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-in-fwd-top {
|
||||
0% {
|
||||
transform: translateZ(-1400px) translateY(-800px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateZ(0) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes slide-out-right {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes slide-out-right {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes slide-in-left {
|
||||
0% {
|
||||
transform: translateX(-1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes slide-in-left {
|
||||
0% {
|
||||
transform: translateX(-1000px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@media (max-width: 750px) {
|
||||
.form {
|
||||
width: 94vw !important;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,149 @@
|
|||
<template>
|
||||
<div :style="{ background: userStore.sideMode }">
|
||||
<el-scrollbar style="height: calc(100vh - 60px)">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<el-menu
|
||||
:collapse="isCollapse"
|
||||
:collapse-transition="false"
|
||||
:default-active="active"
|
||||
:background-color="theme.background"
|
||||
:active-text-color="theme.active"
|
||||
class="el-menu-vertical"
|
||||
unique-opened
|
||||
@select="selectMenuItem"
|
||||
>
|
||||
<template v-for="item in routerStore.asyncRouters[0].children">
|
||||
<aside-component
|
||||
v-if="!item.hidden"
|
||||
:key="item.name"
|
||||
:is-collapse="isCollapse"
|
||||
:router-info="item"
|
||||
:theme="theme"
|
||||
/>
|
||||
</template>
|
||||
</el-menu>
|
||||
</transition>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AsideComponent from '@/view/layout/aside/asideComponent/index.vue'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { ref, watch, onUnmounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
|
||||
defineOptions({
|
||||
name: 'Aside',
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const routerStore = useRouterStore()
|
||||
|
||||
const theme = ref({})
|
||||
|
||||
const getTheme = () => {
|
||||
switch (userStore.sideMode) {
|
||||
case '#fff':
|
||||
theme.value = {
|
||||
background: '#fff',
|
||||
activeBackground: 'var(--el-color-primary)',
|
||||
activeText: '#fff',
|
||||
normalText: '#333',
|
||||
hoverBackground: 'rgba(64, 158, 255, 0.08)',
|
||||
hoverText: '#333',
|
||||
}
|
||||
break
|
||||
case '#191a23':
|
||||
theme.value = {
|
||||
background: '#191a23',
|
||||
activeBackground: 'var(--el-color-primary)',
|
||||
activeText: '#fff',
|
||||
normalText: '#fff',
|
||||
hoverBackground: 'rgba(64, 158, 255, 0.08)',
|
||||
hoverText: '#fff',
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
getTheme()
|
||||
|
||||
const active = ref('')
|
||||
watch(() => route, () => {
|
||||
active.value = route.meta.activeName || route.name
|
||||
}, { deep: true })
|
||||
|
||||
watch(() => userStore.sideMode, () => {
|
||||
getTheme()
|
||||
})
|
||||
|
||||
const isCollapse = ref(false)
|
||||
const initPage = () => {
|
||||
active.value = route.meta.activeName || route.name
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
isCollapse.value = !isCollapse.value
|
||||
}
|
||||
|
||||
emitter.on('collapse', (item) => {
|
||||
isCollapse.value = item
|
||||
})
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
onUnmounted(() => {
|
||||
emitter.off('collapse')
|
||||
})
|
||||
|
||||
const selectMenuItem = (index, _, ele, aaa) => {
|
||||
const query = {}
|
||||
const params = {}
|
||||
routerStore.routeMap[index]?.parameters &&
|
||||
routerStore.routeMap[index]?.parameters.forEach((item) => {
|
||||
if (item.type === 'query') {
|
||||
query[item.key] = item.value
|
||||
} else {
|
||||
params[item.key] = item.value
|
||||
}
|
||||
})
|
||||
if (index === route.name) return
|
||||
if (index.indexOf('http://') > -1 || index.indexOf('https://') > -1) {
|
||||
window.open(index)
|
||||
} else {
|
||||
router.push({ name: index, query, params })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.el-sub-menu__title:hover,
|
||||
.el-menu-item:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.el-scrollbar {
|
||||
.el-scrollbar__view {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.menu-info {
|
||||
.menu-contorl {
|
||||
line-height: 52px;
|
||||
font-size: 20px;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,374 @@
|
|||
<template>
|
||||
<el-container class="layout-cont">
|
||||
<el-container :class="[isSider?'openside':'hideside',isMobile ? 'mobile': '']">
|
||||
<el-row
|
||||
:class="[isShadowBg && isMobile?'bg-black opacity-30 w-full h-full absolute top-0 left-0 z-[1001]':'']"
|
||||
@click="changeShadow()"
|
||||
/>
|
||||
<el-aside
|
||||
class="main-cont gva-aside"
|
||||
:style="{width:asideWidth()}"
|
||||
>
|
||||
<div
|
||||
class="min-h-[60px] text-center transition-all duration-300 flex items-center justify-center gap-2"
|
||||
:style="{background: backgroundColor}"
|
||||
>
|
||||
<img
|
||||
alt
|
||||
class="w-9 h-9 p-1 bg-white rounded-full"
|
||||
:src="$GIN_VUE_ADMIN.appLogo"
|
||||
>
|
||||
<div
|
||||
v-if="isSider"
|
||||
class="inline-flex font-bold text-2xl"
|
||||
:style="{color:textColor}"
|
||||
>{{ $GIN_VUE_ADMIN.appName }}</div>
|
||||
</div>
|
||||
<Aside class="aside" />
|
||||
</el-aside>
|
||||
<!-- 分块滑动功能 -->
|
||||
<el-main class="main-cont main-right">
|
||||
<transition
|
||||
:duration="{ enter: 800, leave: 100 }"
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<div
|
||||
:style="{width: `calc(100% - ${getAsideWidth()})`}"
|
||||
class="fixed top-0 box-border z-50"
|
||||
>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-header class="header-cont">
|
||||
<el-row class="p-0 h-full">
|
||||
<el-col
|
||||
:xs="2"
|
||||
:lg="1"
|
||||
:md="1"
|
||||
:sm="1"
|
||||
:xl="1"
|
||||
class="z-50 flex items-center pl-3"
|
||||
>
|
||||
<div
|
||||
class="text-black cursor-pointer text-lg leading-5"
|
||||
@click="totalCollapse"
|
||||
>
|
||||
<div
|
||||
v-if="isCollapse"
|
||||
class="gvaIcon gvaIcon-arrow-double-right"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="gvaIcon gvaIcon-arrow-double-left"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="10"
|
||||
:lg="14"
|
||||
:md="14"
|
||||
:sm="9"
|
||||
:xl="14"
|
||||
:pull="1"
|
||||
class="flex items-center"
|
||||
>
|
||||
<!-- 修改为手机端不显示顶部标签 -->
|
||||
<el-breadcrumb
|
||||
v-show="!isMobile"
|
||||
class="breadcrumb"
|
||||
>
|
||||
<el-breadcrumb-item
|
||||
v-for="item in matched.slice(1,matched.length)"
|
||||
:key="item.path"
|
||||
>{{ fmtTitle(item.meta.title,route) }}</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</el-col>
|
||||
<el-col
|
||||
:xs="12"
|
||||
:lg="9"
|
||||
:md="9"
|
||||
:sm="14"
|
||||
:xl="9"
|
||||
class="flex items-center justify-end"
|
||||
>
|
||||
<div class="mr-1.5 flex items-center">
|
||||
<Search />
|
||||
<el-dropdown>
|
||||
<div class="flex justify-center items-center h-full w-full">
|
||||
<span class="cursor-pointer flex justify-center items-center">
|
||||
<CustomPic />
|
||||
<span
|
||||
v-show="!isMobile"
|
||||
style="margin-left: 5px"
|
||||
>{{ userStore.userInfo.nickName }}</span>
|
||||
<el-icon>
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item>
|
||||
<span class="font-bold">
|
||||
当前角色:{{ userStore.userInfo.authority.authorityName }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
<template v-if="userStore.userInfo.authorities">
|
||||
<el-dropdown-item
|
||||
v-for="item in userStore.userInfo.authorities.filter(i=>i.authorityId!==userStore.userInfo.authorityId)"
|
||||
:key="item.authorityId"
|
||||
@click="changeUserAuth(item.authorityId)"
|
||||
>
|
||||
<span>
|
||||
切换为:{{ item.authorityName }}
|
||||
</span>
|
||||
</el-dropdown-item>
|
||||
</template>
|
||||
<el-dropdown-item icon="avatar">
|
||||
<div
|
||||
class="command-box"
|
||||
style="display: flex"
|
||||
@click="handleCommand"
|
||||
>
|
||||
<div>指令菜单</div>
|
||||
<div style="margin-left: 8px">
|
||||
<span class="button">{{ first }}</span>
|
||||
+
|
||||
<span class="button">K</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="avatar"
|
||||
@click="toPerson"
|
||||
>个人信息</el-dropdown-item>
|
||||
<el-dropdown-item
|
||||
icon="reading-lamp"
|
||||
@click="userStore.LoginOut"
|
||||
>登 出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 当前面包屑用路由自动生成可根据需求修改 -->
|
||||
<!--
|
||||
:to="{ path: item.path }" 暂时注释不用-->
|
||||
<HistoryComponent ref="layoutHistoryComponent" />
|
||||
</div>
|
||||
</transition>
|
||||
<router-view
|
||||
v-if="reloadFlag"
|
||||
v-slot="{ Component }"
|
||||
class="admin-box"
|
||||
>
|
||||
<div
|
||||
id="gva-base-load-dom"
|
||||
>
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</div>
|
||||
</router-view>
|
||||
<BottomInfo />
|
||||
<setting />
|
||||
<CommandMenu ref="command" />
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Aside from '@/view/layout/aside/index.vue'
|
||||
import HistoryComponent from '@/view/layout/aside/historyComponent/history.vue'
|
||||
import Search from '@/view/layout/search/search.vue'
|
||||
import BottomInfo from '@/view/layout/bottomInfo/bottomInfo.vue'
|
||||
import CustomPic from '@/components/customPic/index.vue'
|
||||
import CommandMenu from '@/components/commandMenu/index.vue'
|
||||
import Setting from './setting/index.vue'
|
||||
import { setUserAuthority } from '@/api/user'
|
||||
import { emitter } from '@/utils/bus.js'
|
||||
import { computed, ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
import { fmtTitle } from '@/utils/fmtRouterTitle'
|
||||
import { useUserStore } from '@/pinia/modules/user'
|
||||
|
||||
defineOptions({
|
||||
name: 'Layout',
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const routerStore = useRouterStore()
|
||||
// 三种窗口适配
|
||||
const isCollapse = ref(false)
|
||||
const isSider = ref(true)
|
||||
const isMobile = ref(false)
|
||||
|
||||
const first = ref('')
|
||||
const initPage = () => {
|
||||
// 判断当前用户的操作系统
|
||||
if (window.localStorage.getItem('osType') === 'WIN') {
|
||||
first.value = 'Ctrl'
|
||||
} else {
|
||||
first.value = '⌘'
|
||||
}
|
||||
// 当用户同时按下ctrl和k键的时候
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.ctrlKey && e.key === 'k') {
|
||||
// 阻止浏览器默认事件
|
||||
e.preventDefault()
|
||||
handleCommand()
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
|
||||
const screenWidth = document.body.clientWidth
|
||||
if (screenWidth < 1000) {
|
||||
isMobile.value = true
|
||||
isSider.value = false
|
||||
isCollapse.value = true
|
||||
} else if (screenWidth >= 1000 && screenWidth < 1200) {
|
||||
isMobile.value = false
|
||||
isSider.value = false
|
||||
isCollapse.value = true
|
||||
} else {
|
||||
isMobile.value = false
|
||||
isSider.value = true
|
||||
isCollapse.value = false
|
||||
}
|
||||
}
|
||||
|
||||
initPage()
|
||||
|
||||
const command = ref()
|
||||
const handleCommand = () => {
|
||||
command.value.open()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 挂载一些通用的事件
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
emitter.emit('mobile', isMobile.value)
|
||||
emitter.on('reload', reload)
|
||||
window.onresize = () => {
|
||||
return (() => {
|
||||
initPage()
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
emitter.emit('mobile', isMobile.value)
|
||||
})()
|
||||
}
|
||||
if (userStore.loadingInstance) {
|
||||
userStore.loadingInstance.close()
|
||||
}
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const asideWidth = () => {
|
||||
if (isMobile.value) {
|
||||
return isCollapse.value ? '0px' : '220px'
|
||||
}
|
||||
return isCollapse.value ? '54px' : '220px'
|
||||
}
|
||||
|
||||
const getAsideWidth = () => {
|
||||
if (isMobile.value) return '0px'
|
||||
return isCollapse.value ? '54px' : '220px'
|
||||
}
|
||||
|
||||
const textColor = computed(() => {
|
||||
if (userStore.sideMode === 'dark') {
|
||||
return '#fff'
|
||||
} else if (userStore.sideMode === 'light') {
|
||||
return '#191a23'
|
||||
} else {
|
||||
return userStore.baseColor
|
||||
}
|
||||
})
|
||||
|
||||
const backgroundColor = computed(() => {
|
||||
if (userStore.sideMode === 'dark') {
|
||||
return '#191a23'
|
||||
} else if (userStore.sideMode === 'light') {
|
||||
return '#fff'
|
||||
} else {
|
||||
return userStore.sideMode
|
||||
}
|
||||
})
|
||||
|
||||
const matched = computed(() => route.meta.matched)
|
||||
|
||||
const changeUserAuth = async(id) => {
|
||||
const res = await setUserAuthority({
|
||||
authorityId: id
|
||||
})
|
||||
if (res.code === 0) {
|
||||
window.sessionStorage.setItem('needCloseAll', 'true')
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
const isShadowBg = ref(false)
|
||||
const totalCollapse = () => {
|
||||
isCollapse.value = !isCollapse.value
|
||||
isSider.value = !isCollapse.value
|
||||
isShadowBg.value = !isCollapse.value
|
||||
emitter.emit('collapse', isCollapse.value)
|
||||
}
|
||||
|
||||
const toPerson = () => {
|
||||
router.push({ name: 'person' })
|
||||
}
|
||||
const changeShadow = () => {
|
||||
isShadowBg.value = !isShadowBg.value
|
||||
isSider.value = !!isCollapse.value
|
||||
totalCollapse()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.button {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
background: rgb(250,250,250);
|
||||
width: 25px!important;
|
||||
padding: 4px 8px !important;
|
||||
border: 1px solid #eaeaea;
|
||||
margin-right: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep .el-overlay {
|
||||
background-color: hsla(0,0%,100%,.9) !important;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<!-- 此路由可作为父类路由通用路由页面使用 如需自定义父类路由页面 请参考 @/view/superAdmin/index.vue -->
|
||||
<template>
|
||||
<div>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition
|
||||
mode="out-in"
|
||||
name="el-fade-in-linear"
|
||||
>
|
||||
<keep-alive :include="routerStore.keepAliveRouters">
|
||||
<div>
|
||||
<component :is="Component" />
|
||||
</div>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineOptions({
|
||||
name: 'RouterHolder'
|
||||
})
|
||||
import { useRouterStore } from '@/pinia/modules/router'
|
||||
const routerStore = useRouterStore()
|
||||
</script>
|
||||
Loading…
Reference in New Issue