前端页面改造基本完成 待优化

This commit is contained in:
piexlmax
2021-12-30 22:41:53 +08:00
parent 8c2b6b2bc0
commit df9dc477ed
20 changed files with 729 additions and 720 deletions
+4
View File
@@ -59,6 +59,10 @@ func (a *api) Initialize() error {
{ApiGroup: "菜单", Method: "POST", Path: "/menu/getMenuAuthority", Description: "获取指定角色menu"}, {ApiGroup: "菜单", Method: "POST", Path: "/menu/getMenuAuthority", Description: "获取指定角色menu"},
{ApiGroup: "菜单", Method: "POST", Path: "/menu/addMenuAuthority", Description: "增加menu和角色关联关系"}, {ApiGroup: "菜单", Method: "POST", Path: "/menu/addMenuAuthority", Description: "增加menu和角色关联关系"},
{ApiGroup: "分片上传", Method: "POST", Path: "/fileUploadAndDownload/findFile", Description: "寻找目标文件(秒传)"},
{ApiGroup: "分片上传", Method: "POST", Path: "/fileUploadAndDownload/breakpointContinueFinish", Description: "断点续传"},
{ApiGroup: "分片上传", Method: "POST", Path: "/fileUploadAndDownload/removeChunk", Description: "上传完成"},
{ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/upload", Description: "文件上传示例"}, {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/upload", Description: "文件上传示例"},
{ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/deleteFile", Description: "删除文件"}, {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/deleteFile", Description: "删除文件"},
{ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/getFileList", Description: "获取上传文件列表"}, {ApiGroup: "文件上传与下载", Method: "POST", Path: "/fileUploadAndDownload/getFileList", Description: "获取上传文件列表"},
+4
View File
@@ -54,6 +54,10 @@ func (c *casbin) Initialize() error {
{PType: "p", V0: "888", V1: "/user/setUserAuthority", V2: "POST"}, {PType: "p", V0: "888", V1: "/user/setUserAuthority", V2: "POST"},
{PType: "p", V0: "888", V1: "/user/setUserAuthorities", V2: "POST"}, {PType: "p", V0: "888", V1: "/user/setUserAuthorities", V2: "POST"},
{PType: "p", V0: "888", V1: "/fileUploadAndDownload/findFile", V2: "GET"},
{PType: "p", V0: "888", V1: "/fileUploadAndDownload/breakpointContinueFinish", V2: "POST"},
{PType: "p", V0: "888", V1: "/fileUploadAndDownload/removeChunk", V2: "POST"},
{PType: "p", V0: "888", V1: "/fileUploadAndDownload/upload", V2: "POST"}, {PType: "p", V0: "888", V1: "/fileUploadAndDownload/upload", V2: "POST"},
{PType: "p", V0: "888", V1: "/fileUploadAndDownload/deleteFile", V2: "POST"}, {PType: "p", V0: "888", V1: "/fileUploadAndDownload/deleteFile", V2: "POST"},
{PType: "p", V0: "888", V1: "/fileUploadAndDownload/getFileList", V2: "POST"}, {PType: "p", V0: "888", V1: "/fileUploadAndDownload/getFileList", V2: "POST"},
+46 -44
View File
@@ -15,54 +15,56 @@
</template> </template>
<script> <script>
import noAvatar from '@/assets/noBody.png'
import { mapGetters } from 'vuex'
const path = import.meta.env.VITE_BASE_API
export default { export default {
name: 'CustomPic', name: 'CustomPic'
props: {
picType: {
type: String,
required: false,
default: 'avatar'
},
picSrc: {
type: String,
required: false,
default: ''
}
},
data() {
return {
noAvatar: noAvatar,
path: path + '/'
}
},
computed: {
...mapGetters('user', ['userInfo']),
avatar() {
if (this.picSrc === '') {
if (this.userInfo.headerImg !== '' && this.userInfo.headerImg.slice(0, 4) === 'http') {
return this.userInfo.headerImg
}
return this.path + this.userInfo.headerImg
} else {
if (this.picSrc !== '' && this.picSrc.slice(0, 4) === 'http') {
return this.picSrc
}
return this.path + this.picSrc
}
},
file() {
if (this.picSrc && this.picSrc.slice(0, 4) !== 'http') {
return this.path + this.picSrc
}
return this.picSrc
}
}
} }
</script> </script>
<script setup>
import noAvatarPng from '@/assets/noBody.png'
import { useStore } from 'vuex'
import { computed, ref, defineProps } from 'vue-demi'
const store = useStore()
const props = defineProps({
picType: {
type: String,
required: false,
default: 'avatar'
},
picSrc: {
type: String,
required: false,
default: ''
}
})
const path = ref(import.meta.env.VITE_BASE_API + '/')
const noAvatar = ref(noAvatarPng)
const userInfo = computed(() => store.getters['user/userInfo'])
const avatar = computed(() => {
if (props.picSrc === '') {
if (userInfo.value.headerImg !== '' && userInfo.value.headerImg.slice(0, 4) === 'http') {
return userInfo.value.headerImg
}
return path.value + userInfo.value.headerImg
} else {
if (props.picSrc !== '' && props.picSrc.slice(0, 4) === 'http') {
return props.picSrc
}
return path.value + props.picSrc
}
})
const file = computed(() => {
if (props.picSrc && props.picSrc.slice(0, 4) !== 'http') {
return path.value + props.picSrc
}
return props.picSrc
})
</script>
<style scoped> <style scoped>
.headerAvatar{ .headerAvatar{
display: flex; display: flex;
+32 -32
View File
@@ -14,17 +14,15 @@
</div> </div>
</template> </template>
<script> <script setup>
const path = import.meta.env.VITE_BASE_API
import { mapGetters } from 'vuex'
import ImageCompress from '@/utils/image' import ImageCompress from '@/utils/image'
export default { import { computed, ref } from 'vue'
name: 'UploadImage', import { useStore } from 'vuex'
model: { import { ElMessage } from 'element-plus'
prop: 'imageUrl', const store = useStore()
event: 'change'
}, const emit = defineEmits(['on-success'])
props: { const props = defineProps({
imageUrl: { imageUrl: {
type: String, type: String,
default: '' default: ''
@@ -37,43 +35,45 @@ export default {
type: Number, type: Number,
default: 1920 // 图片长宽上限 default: 1920 // 图片长宽上限
} }
}, })
data() {
return {
path: path const path = ref(import.meta.env.VITE_BASE_API)
} const token = computed(()=>store.getters['user/token'])
}, const showImageUrl = computed(()=>(props.imageUrl && props.imageUrl.slice(0, 4) !== 'http') ? path.value + props.imageUrl : props.imageUrl)
computed: {
...mapGetters('user', ['userInfo', 'token']), const beforeImageUpload = (file) => {
showImageUrl() {
return (this.imageUrl && this.imageUrl.slice(0, 4) !== 'http') ? path + this.imageUrl : this.imageUrl
}
},
methods: {
beforeImageUpload(file) {
const isJPG = file.type === 'image/jpeg' const isJPG = file.type === 'image/jpeg'
const isPng = file.type === 'image/png' const isPng = file.type === 'image/png'
if (!isJPG && !isPng) { if (!isJPG && !isPng) {
this.$message.error('上传头像图片只能是 jpg或png 格式!') ElMessage.error('上传头像图片只能是 jpg或png 格式!')
return false return false
} }
const isRightSize = file.size / 1024 < this.fileSize const isRightSize = file.size / 1024 < props.fileSize
if (!isRightSize) { if (!isRightSize) {
// 压缩 // 压缩
const compress = new ImageCompress(file, this.fileSize, this.maxWH) const compress = new ImageCompress(file, props.fileSize, props.maxWH)
return compress.compress() return compress.compress()
} }
return isRightSize return isRightSize
}, }
handleImageSuccess(res) {
// this.imageUrl = URL.createObjectURL(file.raw); const handleImageSuccess = (res) => {
const { data } = res const { data } = res
if (data.file) { if (data.file) {
this.$emit('change', data.file.url) emit('on-success',data.file.url)
this.$emit('on-success')
} }
} }
</script>
<script>
export default {
name: 'UploadImage',
methods: {
} }
} }
</script> </script>
+7 -12
View File
@@ -10,20 +10,15 @@
</span> </span>
</div> </div>
</template> </template>
<script> <script setup>
import { WarningFilled } from '@element-plus/icons' import { WarningFilled } from '@element-plus/icons'
import { defineProps } from 'vue'
export default { defineProps({
components: { title: {
[WarningFilled.name]: WarningFilled type: String,
default: ''
}, },
props: { })
title: {
type: String,
default: ''
},
}
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.warning-bar{ .warning-bar{
-61
View File
@@ -1,61 +0,0 @@
import { getDict } from '@/utils/dictionary'
import { formatTimeToStr } from '@/utils/date'
export default {
data() {
return {
page: 1,
total: 10,
pageSize: 10,
tableData: [],
searchInfo: {}
}
},
methods: {
formatBoolean: function(bool) {
if (bool !== null) {
return bool ? '是' : '否'
} else {
return ''
}
},
formatDate: function(time) {
if (time !== null && time !== '') {
var date = new Date(time)
return formatTimeToStr(date, 'yyyy-MM-dd hh:mm:ss')
} else {
return ''
}
},
filterDict(value, type) {
const rowLabel = this[type + 'Options'] && this[type + 'Options'].filter(item => item.value === value)
return rowLabel && rowLabel[0] && rowLabel[0].label
},
async getDict(type) {
const dicts = await getDict(type)
this[type + 'Options'] = dicts
return dicts
},
handleSizeChange(val) {
this.pageSize = val
this.getTableData()
},
handleCurrentChange(val) {
this.page = val
this.getTableData()
},
// @params beforeFunc function 请求发起前执行的函数 默认为空函数
// @params afterFunc function 请求完成后执行的函数 默认为空函数
async getTableData(beforeFunc = () => {}, afterFunc = () => {}) {
beforeFunc()
const table = await this.listApi({ page: this.page, pageSize: this.pageSize, ...this.searchInfo })
if (table.code === 0) {
this.tableData = table.data.list
this.total = table.data.total
this.page = table.data.page
this.pageSize = table.data.pageSize
}
afterFunc()
}
}
}
+11
View File
@@ -1,4 +1,5 @@
import { formatTimeToStr } from '@/utils/date' import { formatTimeToStr } from '@/utils/date'
import { getDict } from '@/utils/dictionary'
export const formatBoolean = (bool) => { export const formatBoolean = (bool) => {
if (bool !== null) { if (bool !== null) {
@@ -15,3 +16,13 @@ export const formatDate = (time) => {
return '' return ''
} }
} }
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
}
+37 -39
View File
@@ -108,50 +108,48 @@
</template> </template>
<script> <script>
import { Commits, Members } from '@/api/github'
export default { export default {
name: 'About', name: 'About',
data() {
return {
messageWhenNoItems: 'There arent commits',
members: [],
dataTimeline: [],
page: 0,
}
},
created() {
this.loadCommits()
this.loadMembers()
},
methods: {
loadMore() {
this.page++
this.loadCommits()
},
loadCommits() {
Commits(this.page).then(({ data }) => {
data.forEach((element) => {
if (element.commit.message) {
this.dataTimeline.push({
from: new Date(element.commit.author.date),
title: element.commit.author.name,
showDayAndMonth: true,
message: element.commit.message,
})
}
})
})
},
loadMembers() {
Members().then(({ data }) => {
this.members = data
this.members.sort()
})
},
},
} }
</script> </script>
<script setup>
import { ref } from 'vue'
import { Commits, Members } from '@/api/github'
const members = ref([])
const dataTimeline = ref([])
const page = ref(0)
const loadMore = () => {
page.value++
loadCommits()
}
const loadCommits = () => {
Commits(page.value).then(({ data }) => {
data.forEach((element) => {
if (element.commit.message) {
dataTimeline.value.push({
from: new Date(element.commit.author.date),
title: element.commit.author.name,
showDayAndMonth: true,
message: element.commit.message,
})
}
})
})
}
const loadMembers = () => {
Members().then(({ data }) => {
members.value = data
members.value.sort()
})
}
loadCommits()
loadMembers()
</script>
<style scoped> <style scoped>
.load-more { .load-more {
margin-left: 120px; margin-left: 120px;
@@ -9,9 +9,9 @@
/> />
</div> </div>
</template> </template>
<script> <script setup>
import * as echarts from 'echarts' import * as echarts from 'echarts'
import { toRaw } from 'vue' import { nextTick, onMounted, onUnmounted, ref } from 'vue'
import 'echarts/theme/macarons' import 'echarts/theme/macarons'
var dataAxis = [] var dataAxis = []
@@ -40,86 +40,79 @@ for (var i = 0; i < data.length; i++) {
dataShadow.push(yMax) dataShadow.push(yMax)
} }
export default { const chart = ref(null)
name: 'Line', const echart = ref(null)
data() { const initChart = () => {
return { chart.value = echarts.init(echart.value, 'macarons')
chart: null, setOptions()
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
beforeUnmount() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(this.$refs.echart, 'macarons')
this.setOptions()
},
setOptions() {
toRaw(this.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,
},
],
})
},
},
} }
const setOptions = () => {
chart.value.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.value) {
return
}
chart.value.dispose()
chart.value = null
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.dashboard-line-box { .dashboard-line-box {
@@ -14,37 +14,37 @@
</template> </template>
<script> <script>
import { Commits } from '@/api/github'
import { formatTimeToStr } from '@/utils/date.js'
export default { export default {
data() { name: 'DashboardTable',
return {
loading: true,
dataTimeline: [],
}
},
created() {
this.loadCommits()
},
methods: {
loadCommits() {
Commits(0).then(({ data }) => {
this.loading = false
data.forEach((element, index) => {
if (element.commit.message && index < 10) {
this.dataTimeline.push({
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
title: element.commit.author.name,
showDayAndMonth: true,
message: element.commit.message,
})
}
})
})
},
}
} }
</script> </script>
<script setup>
import { Commits } from '@/api/github'
import { formatTimeToStr } from '@/utils/date.js'
import { ref } from 'vue-demi'
const loading = ref(true)
const dataTimeline = ref([])
const loadCommits = () => {
Commits(0).then(({ data }) => {
loading.value = false
data.forEach((element, index) => {
if (element.commit.message && index < 10) {
dataTimeline.value.push({
from: formatTimeToStr(element.commit.author.date, 'yyyy-MM-dd'),
title: element.commit.author.name,
showDayAndMonth: true,
message: element.commit.message,
})
}
})
})
}
loadCommits()
</script>
<style lang="scss" scoped> <style lang="scss" scoped>
.commit-table{ .commit-table{
background-color: #fff; background-color: #fff;
+57 -57
View File
@@ -103,68 +103,68 @@
</div> </div>
</div> </div>
</template> </template>
<script>
<script setup>
import echartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue' import echartsLine from '@/view/dashboard/dashboardCharts/echartsLine.vue'
import dashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue' import dashboardTable from '@/view/dashboard/dashboardTable/dashboardTable.vue'
export default { import { ref } from 'vue'
name: 'Dashboard', import { useRouter } from 'vue-router'
components: {
echartsLine, const toolCards = ref([
dashboardTable {
label: '用户管理',
icon: 'monitor',
name: 'user',
color: '#ff9c6e',
bg: 'rgba(255, 156, 110,.3)'
}, },
data() { {
return { label: '角色管理',
toolCards: [ icon: 'setting',
{ name: 'authority',
label: '用户管理', color: '#69c0ff',
icon: 'monitor', bg: 'rgba(105, 192, 255,.3)'
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)'
}
]
}
}, },
methods: { {
toTarget(name) { label: '菜单管理',
this.$router.push({ name }) 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>
<script>
export default {
name: 'Dashboard'
} }
</script> </script>
+133 -119
View File
@@ -5,7 +5,7 @@
<form id="fromCont" method="post"> <form id="fromCont" method="post">
<div class="fileUpload" @click="inputChange"> <div class="fileUpload" @click="inputChange">
选择文件 选择文件
<input v-show="false" id="file" ref="Input" multiple="multiple" type="file" @change="choseFile"> <input v-show="false" id="file" ref="FileInput" multiple="multiple" type="file" @change="choseFile">
</div> </div>
</form> </form>
<el-button :disabled="limitFileSize" type="primary" size="mini" class="uploadBtn" @click="getFile">上传文件</el-button> <el-button :disabled="limitFileSize" type="primary" size="mini" class="uploadBtn" @click="getFile">上传文件</el-button>
@@ -28,7 +28,7 @@
</template> </template>
<script> <script setup>
import SparkMD5 from 'spark-md5' import SparkMD5 from 'spark-md5'
import axios from 'axios' import axios from 'axios'
import { import {
@@ -36,135 +36,149 @@ import {
breakpointContinueFinish, breakpointContinueFinish,
removeChunk removeChunk
} from '@/api/breakpoint' } from '@/api/breakpoint'
export default { import { ref } from 'vue'
name: 'BreakPoint', import { ElMessage } from 'element-plus'
data() {
return { const file = ref(null)
file: null, const fileMd5 = ref('')
fileMd5: '', const formDataList = ref([])
formDataList: [], const waitUpLoad = ref([])
waitUpLoad: [], const waitNum = ref(0)
waitNum: 0, const limitFileSize = ref(false)
limitFileSize: false, const percentage = ref(0)
percentage: 0, const percentageFlage = ref(true)
percentageFlage: true,
customColor: '#409eff' // 选中文件的函数
} const choseFile = async(e) => {
}, const fileR = new FileReader() // 创建一个reader用来读取文件流
methods: { const fileInput = e.target.files[0] // 获取当前文件
// 选中文件的函数 const maxSize = 5 * 1024 * 1024
async choseFile(e) { file.value = fileInput // file 丢全局方便后面用 可以改进为func传参形式
const fileR = new FileReader() // 创建一个reader用来读取文件流 percentage.value = 0
const file = e.target.files[0] // 获取当前文件 if (file.value.size < maxSize) {
const maxSize = 5 * 1024 * 1024 fileR.readAsArrayBuffer(file.value) // 把文件读成ArrayBuffer 主要为了保持跟后端的流一致
this.file = file // file 丢全局方便后面用 可以改进为func传参形式 fileR.onload = async e => {
this.percentage = 0 // 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
if (file.size < maxSize) { const blob = e.target.result
fileR.readAsArrayBuffer(file) // 把文件读成ArrayBuffer 主要为了保持跟后端的流一致 const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我)
fileR.onload = async e => { spark.append(blob) // 文件流丢进工具
// 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中 fileMd5.value = spark.end() // 工具结束 产生一个a 总文件的md5
const blob = e.target.result const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
const spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我) let start = 0 // 定义分片开始切的地方
spark.append(blob) // 文件流丢进工具 let end = 0 // 每片结束切的地方a
this.fileMd5 = spark.end() // 工具结束 产生一个a 总文件的md5 let i = 0 // 第几片
const FileSliceCap = 1 * 1024 * 1024 // 分片字节数 formDataList.value = [] // 分片存储的一个池子 丢全局
let start = 0 // 定义分片开始切的地方 while (end < file.value.size) {
let end = 0 // 每片结束切的地方a // 当结尾数字大于文件总size的时候 结束切片
let i = 0 // 第几片 start = i * FileSliceCap // 计算每片开始位置
this.formDataList = [] // 分片存储的一个池子 丢全局 end = (i + 1) * FileSliceCap // 计算每片结束位置
while (end < file.size) { var fileSlice = file.value.slice(start, end) // 开始切 file.slice 为 h5方法 对文件切片 参数为 起止字节数
// 当结尾数字大于文件总size的时候 结束切片 const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
start = i * FileSliceCap // 计算每片开始位置 formData.append('fileMd5', fileMd5.value) // 存储总文件的Md5 让后端知道自己是谁的切片
end = (i + 1) * FileSliceCap // 计算每片结束位置 formData.append('file', fileSlice) // 当前的切片
var fileSlice = this.file.slice(start, end) // 开始切 file.slice 为 h5方法 对文件切片 参数为 起止字节数 formData.append('chunkNumber', i) // 当前是第几片
const formData = new window.FormData() // 创建FormData用于存储传给后端的信息 formData.append('fileName', file.value.name) // 当前文件的文件名 用于后端文件切片的命名 formData.appen 为 formData对象添加参数的方法
formData.append('fileMd5', this.fileMd5) // 存储总文件的Md5 让后端知道自己是谁的切片 formDataList.value.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
formData.append('file', fileSlice) // 当前的切片 i++
formData.append('chunkNumber', i) // 当前是第几片 }
formData.append('fileName', this.file.name) // 当前文件的文件名 用于后端文件切片的命名 formData.appen 为 formData对象添加参数的方法 const params = {
this.formDataList.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子 fileName: file.value.name,
i++ fileMd5: fileMd5.value,
} chunkTotal: formDataList.value.length
const params = { }
fileName: this.file.name, const res = await findFile(params)
fileMd5: this.fileMd5, // 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片
chunkTotal: this.formDataList.length const finishList = res.data.file.ExaFileChunk // 上传成功的切片
} const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
const res = await findFile(params) if (!IsFinish) {
// 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片 // 当是断点续传时候
const finishList = res.data.file.ExaFileChunk // 上传成功的切片 waitUpLoad.value = formDataList.value.filter(all => {
const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能) return !(
if (!IsFinish) { finishList &&
// 当是断点续传时候
this.waitUpLoad = this.formDataList.filter(all => {
return !(
finishList &&
finishList.some(fi => fi.FileChunkNumber === all.key) finishList.some(fi => fi.FileChunkNumber === all.key)
) // 找出需要上传的切片 ) // 找出需要上传的切片
}) })
} else {
this.waitUpLoad = [] // 秒传则没有需要上传的切片
}
this.waitNum = this.waitUpLoad.length // 记录长度用于百分比展示
}
} else { } else {
this.limitFileSize = true waitUpLoad.value = [] // 秒传则没有需要上传的切片
this.$message('请上传小于5M文件')
} }
}, waitNum.value = waitUpLoad.value.length // 记录长度用于百分比展示
getFile() { }
// 确定按钮 } else {
if (this.file === null) { limitFileSize.value = true
this.$message('请先上传文件') ElMessage('请上传小于5M文件')
return }
} }
this.percentage = Math.floor(((this.formDataList.length - this.waitNum) / this.formDataList.length) * 100)
if (this.percentage === 100) { const getFile = () => {
this.percentageFlage = false // 确定按钮
} if (file.value === null) {
this.sliceFile() // 上传切片 ElMessage('请先上传文件')
}, return
sliceFile() { }
this.waitUpLoad && percentage.value = Math.floor(((formDataList.value.length - waitNum.value) / formDataList.value.length) * 100)
this.waitUpLoad.forEach(item => { if (percentage.value === 100) {
percentageFlage.value = false
}
sliceFile() // 上传切片
}
const sliceFile = () => {
waitUpLoad.value &&
waitUpLoad.value.forEach(item => {
// 需要上传的切片 // 需要上传的切片
item.formData.append('chunkTotal', this.formDataList.length) // 切片总数携带给后台 总有用的 item.formData.append('chunkTotal', formDataList.value.length) // 切片总数携带给后台 总有用的
const fileR = new FileReader() // 功能同上 const fileR = new FileReader() // 功能同上
const file = item.formData.get('file') const fileF = item.formData.get('file')
fileR.readAsArrayBuffer(file) fileR.readAsArrayBuffer(fileF)
fileR.onload = e => { fileR.onload = e => {
const spark = new SparkMD5.ArrayBuffer() const spark = new SparkMD5.ArrayBuffer()
spark.append(e.target.result) spark.append(e.target.result)
item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性 item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
this.upLoadFileSlice(item) upLoadFileSlice(item)
} }
}) })
}, }
async upLoadFileSlice(item) {
// 切片上传 const upLoadFileSlice = async(item) => {
await axios.post(import.meta.env.VITE_BASE_API + '/fileUploadAndDownload/breakpointContinue', item.formData) // 切片上传
this.waitNum-- // 百分数增加 await axios.post(import.meta.env.VITE_BASE_API + '/fileUploadAndDownload/breakpointContinue', item.formData)
if (this.waitNum === 0) { waitNum.value-- // 百分数增加
// 切片传完以后 合成文件 if (waitNum.value === 0) {
const params = { // 切片传完以后 合成文件
fileName: this.file.name, const params = {
fileMd5: this.fileMd5 fileName: file.value.name,
} fileMd5: fileMd5.value
const res = await breakpointContinueFinish(params)
if (res.success) {
// 合成文件过后 删除缓存切片
const params = {
fileName: this.file.name,
fileMd5: this.fileMd5,
filePath: res.data.filePath
}
await removeChunk(params)
}
}
},
inputChange() {
this.$refs.Input.dispatchEvent(new MouseEvent('click'))
} }
const res = await breakpointContinueFinish(params)
if (res.success) {
// 合成文件过后 删除缓存切片
const params = {
fileName: file.value.name,
fileMd5: fileMd5.value,
filePath: res.data.filePath
}
await removeChunk(params)
}
}
}
const FileInput = ref(null)
const inputChange = () => {
FileInput.value.dispatchEvent(new MouseEvent('click'))
}
</script>
<script>
export default {
name: 'BreakPoint',
data() {
return {
}
},
methods: {
} }
} }
</script> </script>
+98 -73
View File
@@ -68,7 +68,7 @@
</div> </div>
</template> </template>
<script> <script setup>
import { import {
createExaCustomer, createExaCustomer,
updateExaCustomer, updateExaCustomer,
@@ -76,81 +76,106 @@ import {
getExaCustomer, getExaCustomer,
getExaCustomerList getExaCustomerList
} from '@/api/customer' } from '@/api/customer'
import infoList from '@/mixins/infoList'
import warningBar from '@/components/warningBar/warningBar.vue' import warningBar from '@/components/warningBar/warningBar.vue'
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { formatDate } from '@/utils/format'
const dialogFormVisible = ref(false)
const type = ref('')
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 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) => {
row.visible = false
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
ElMessage({
type: 'success',
message: '删除成功'
})
if (tableData.value.length === 1 && page.value > 1) {
page.value--
}
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>
<script>
export default { export default {
name: 'Customer', name: 'Customer'
components: { warningBar },
mixins: [infoList],
data() {
return {
listApi: getExaCustomerList,
dialogFormVisible: false,
type: '',
form: {
customerName: '',
customerPhoneData: ''
}
}
},
created() {
this.getTableData()
},
methods: {
async updateCustomer(row) {
const res = await getExaCustomer({ ID: row.ID })
this.type = 'update'
if (res.code === 0) {
this.form = res.data.customer
this.dialogFormVisible = true
}
},
closeDialog() {
this.dialogFormVisible = false
this.form = {
customerName: '',
customerPhoneData: ''
}
},
async deleteCustomer(row) {
row.visible = false
const res = await deleteExaCustomer({ ID: row.ID })
if (res.code === 0) {
this.$message({
type: 'success',
message: '删除成功'
})
if (this.tableData.length === 1 && this.page > 1) {
this.page--
}
this.getTableData()
}
},
async enterDialog() {
let res
switch (this.type) {
case 'create':
res = await createExaCustomer(this.form)
break
case 'update':
res = await updateExaCustomer(this.form)
break
default:
res = await createExaCustomer(this.form)
break
}
if (res.code === 0) {
this.closeDialog()
this.getTableData()
}
},
openDialog() {
this.type = 'create'
this.dialogFormVisible = true
}
}
} }
</script> </script>
+41 -33
View File
@@ -34,43 +34,51 @@
</template> </template>
<script> <script>
const path = import.meta.env.VITE_BASE_API
import { mapGetters } from 'vuex'
import infoList from '@/mixins/infoList'
import { exportExcel, loadExcelData, downloadTemplate } from '@/api/excel'
import { getMenuList } from '@/api/menu'
export default { export default {
name: 'Excel', name: 'Excel',
mixins: [infoList], }
data() { </script>
return {
listApi: getMenuList, <script setup>
path: path import { useStore } from 'vuex'
} import { exportExcel, loadExcelData, downloadTemplate } from '@/api/excel'
}, import { getMenuList } from '@/api/menu'
computed: { import { computed, ref } from 'vue'
...mapGetters('user', ['userInfo', 'token']) const path = ref(import.meta.env.VITE_BASE_API)
},
created() { const page = ref(1)
this.pageSize = 999 const total = ref(0)
this.getTableData() const pageSize = ref(999)
}, const tableData = ref([])
methods: {
handleExcelExport(fileName) { // 查询
if (!fileName || typeof fileName !== 'string') { const getTableData = async(f = () => {}) => {
fileName = 'ExcelExport.xlsx' const table = await f({ page: page.value, pageSize: pageSize.value })
} if (table.code === 0) {
exportExcel(this.tableData, fileName) tableData.value = table.data.list
}, total.value = table.data.total
loadExcel() { page.value = table.data.page
this.listApi = loadExcelData pageSize.value = table.data.pageSize
this.getTableData()
},
downloadExcelTemplate() {
downloadTemplate('ExcelTemplate.xlsx')
}
} }
} }
getTableData(getMenuList)
const store = useStore()
const token = computed(() => store.getters['user/token'])
const handleExcelExport = (fileName) => {
if (!fileName || typeof fileName !== 'string') {
fileName = 'ExcelExport.xlsx'
}
exportExcel(tableData.value, fileName)
}
const loadExcel = () => {
getTableData(loadExcelData)
}
const downloadExcelTemplate = () => {
downloadTemplate('ExcelTemplate.xlsx')
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+123 -101
View File
@@ -14,7 +14,7 @@
<el-button size="mini" type="primary">普通上传</el-button> <el-button size="mini" type="primary">普通上传</el-button>
</el-upload> </el-upload>
<upload-image <upload-image
v-model="imageUrl" v-model:imageUrl="imageUrl"
:file-size="512" :file-size="512"
:max-w-h="1080" :max-w-h="1080"
class="upload-btn" class="upload-btn"
@@ -46,7 +46,7 @@
<el-table-column align="left" label="操作" width="160"> <el-table-column align="left" label="操作" width="160">
<template #default="scope"> <template #default="scope">
<el-button size="small" icon="download" type="text" @click="downloadFile(scope.row)">下载</el-button> <el-button size="small" icon="download" type="text" @click="downloadFile(scope.row)">下载</el-button>
<el-button size="small" icon="delete" type="text" @click="deleteFile(scope.row)">删除</el-button> <el-button size="small" icon="delete" type="text" @click="deleteFileFunc(scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -66,111 +66,133 @@
</div> </div>
</template> </template>
<script> <script setup>
const path = import.meta.env.VITE_BASE_API import { useStore } from 'vuex'
import { mapGetters } from 'vuex'
import infoList from '@/mixins/infoList'
import { getFileList, deleteFile } from '@/api/fileUploadAndDownload' import { getFileList, deleteFile } from '@/api/fileUploadAndDownload'
import { downloadImage } from '@/utils/downloadImg' import { downloadImage } from '@/utils/downloadImg'
import CustomPic from '@/components/customPic/index.vue' import CustomPic from '@/components/customPic/index.vue'
import UploadImage from '@/components/upload/image.vue' import UploadImage from '@/components/upload/image.vue'
export default { import { formatDate } from '@/utils/format'
name: 'Upload',
components: { import { computed, ref } from 'vue-demi'
CustomPic, import { ElMessage, ElMessageBox } from 'element-plus'
UploadImage
}, const store = useStore()
mixins: [infoList],
data() { const path = ref(import.meta.env.VITE_BASE_API)
return { const token = computed(() => store.getters['user/token'])
fullscreenLoading: false,
listApi: getFileList, const fullscreenLoading = ref(false)
path: path, const imageUrl = ref('')
tableData: [],
imageUrl: '' const page = ref(1)
} const total = ref(0)
}, const pageSize = ref(10)
computed: { const tableData = ref([])
...mapGetters('user', ['userInfo', 'token'])
}, // 分页
created() { const handleSizeChange = (val) => {
this.getTableData() pageSize.value = val
}, getTableData()
methods: { }
async deleteFile(row) {
this.$confirm('此操作将永久文件, 是否继续?', '提示', { const handleCurrentChange = (val) => {
confirmButtonText: '确定', page.value = val
cancelButtonText: '取消', getTableData()
type: 'warning' }
})
.then(async() => { // 查询
const res = await deleteFile(row) const getTableData = async() => {
if (res.code === 0) { const table = await getFileList({ page: page.value, pageSize: pageSize.value })
this.$message({ if (table.code === 0) {
type: 'success', tableData.value = table.data.list
message: '删除成功!' total.value = table.data.total
}) page.value = table.data.page
if (this.tableData.length === 1 && this.page > 1) { pageSize.value = table.data.pageSize
this.page--
}
this.getTableData()
}
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消删除'
})
})
},
checkFile(file) {
this.fullscreenLoading = true
const isJPG = file.type === 'image/jpeg'
const isPng = file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 0.5
if (!isJPG && !isPng) {
this.$message.error('上传图片只能是 jpg或png 格式!')
this.fullscreenLoading = false
}
if (!isLt2M) {
this.$message.error('未压缩未见上传图片大小不能超过 500KB,请使用压缩上传')
this.fullscreenLoading = false
}
return (isPng || isJPG) && isLt2M
},
uploadSuccess(res) {
this.fullscreenLoading = false
if (res.code === 0) {
this.$message({
type: 'success',
message: '上传成功'
})
if (res.code === 0) {
this.getTableData()
}
} else {
this.$message({
type: 'warning',
message: res.msg
})
}
},
uploadError() {
this.$message({
type: 'error',
message: '上传失败'
})
this.fullscreenLoading = false
},
downloadFile(row) {
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
downloadImage(row.url, row.name)
} else {
downloadImage(this.path + row.url, row.name)
}
}
} }
} }
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 checkFile = (file) => {
fullscreenLoading.value = true
const isJPG = file.type === 'image/jpeg'
const isPng = file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 0.5
if (!isJPG && !isPng) {
ElMessage.error('上传图片只能是 jpg或png 格式!')
fullscreenLoading.value = false
}
if (!isLt2M) {
ElMessage.error('未压缩未见上传图片大小不能超过 500KB,请使用压缩上传')
fullscreenLoading.value = false
}
return (isPng || isJPG) && isLt2M
}
const uploadSuccess = (res) => {
fullscreenLoading.value = false
if (res.code === 0) {
ElMessage({
type: 'success',
message: '上传成功'
})
if (res.code === 0) {
getTableData()
}
} else {
ElMessage({
type: 'warning',
message: res.msg
})
}
}
const uploadError = () => {
ElMessage({
type: 'error',
message: '上传失败'
})
fullscreenLoading.value = false
}
const downloadFile = (row) => {
if (row.url.indexOf('http://') > -1 || row.url.indexOf('https://') > -1) {
downloadImage(row.url, row.name)
} else {
downloadImage(path.value + row.url, row.name)
}
}
</script>
<script>
export default {
name: 'Upload'
}
</script> </script>
<style scoped> <style scoped>
.upload-btn+.upload-btn { .upload-btn+.upload-btn {
-5
View File
@@ -412,13 +412,8 @@ const editMenu = async(id) => {
</script> </script>
<script> <script>
// 获取列表内容封装在mixins内部 getTableData方法 初始化已封装完成
export default { export default {
name: 'Menus', name: 'Menus',
methods: {
}
} }
</script> </script>
@@ -209,9 +209,9 @@ const deleteSysOperationRecordFunc = async(row) => {
message: '删除成功' message: '删除成功'
}) })
if (tableData.value.length === 1 && page.value > 1) { if (tableData.value.length === 1 && page.value > 1) {
this.page-- page.value--
} }
this.getTableData() getTableData()
} }
} }
const fmtBody = (value) => { const fmtBody = (value) => {
+28 -28
View File
@@ -138,37 +138,37 @@
</div> </div>
</template> </template>
<script> <script setup>
import { getSystemState } from '@/api/system' import { getSystemState } from '@/api/system'
import { onUnmounted, ref } from 'vue'
const timer = ref(null)
const state = ref({})
const colors = ref([
{ color: '#5cb87a', percentage: 20 },
{ color: '#e6a23c', percentage: 40 },
{ color: '#f56c6c', percentage: 80 }
])
const reload = async() => {
const { data } = await getSystemState()
state.value = data.server
}
reload()
timer.value = setInterval(() => {
reload()
}, 1000 * 10)
onUnmounted(() => {
clearInterval(timer.value)
timer.value = null
})
</script>
<script>
export default { export default {
name: 'State', name: 'State',
data() {
return {
timer: null,
state: {},
colors: [
{ color: '#5cb87a', percentage: 20 },
{ color: '#e6a23c', percentage: 40 },
{ color: '#f56c6c', percentage: 80 }
]
}
},
created() {
this.reload()
this.timer = setInterval(() => {
this.reload()
}, 1000 * 10)
},
beforeUnmount() {
clearInterval(this.timer)
this.timer = null
},
methods: {
async reload() {
const { data } = await getSystemState()
this.state = data.server
}
}
} }
</script> </script>
@@ -25,6 +25,7 @@ const props = defineProps({
} }
}) })
const activeName = ref('')
onMounted(() => { onMounted(() => {
marked.setOptions({ marked.setOptions({
renderer: new marked.Renderer(), renderer: new marked.Renderer(),
@@ -41,15 +42,13 @@ onMounted(() => {
xhtml: false xhtml: false
}) })
for (const key in props.previewCode) { for (const key in props.previewCode) {
if (this.activeName === '') { if (activeName.value === '') {
this.activeName = key activeName.value = key
} }
document.getElementById(key).innerHTML = marked(props.previewCode[key]) document.getElementById(key).innerHTML = marked(props.previewCode[key])
} }
}) })
const activeName = ref('')
const selectText = () => { const selectText = () => {
const element = document.getElementById(activeName.value) const element = document.getElementById(activeName.value)
if (document.body.createTextRange) { if (document.body.createTextRange) {
+1 -1
View File
@@ -2,7 +2,7 @@
<div class="system"> <div class="system">
<el-form ref="form" :model="config" label-width="240px"> <el-form ref="form" :model="config" label-width="240px">
<!-- System start --> <!-- System start -->
<el-collapse v-model="activeNames" @change="handleChange"> <el-collapse v-model="activeNames">
<el-collapse-item title="系统配置" name="1"> <el-collapse-item title="系统配置" name="1">
<el-form-item label="环境值"> <el-form-item label="环境值">
<el-input v-model="config.system.env" /> <el-input v-model="config.system.env" />