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