统计分析

This commit is contained in:
灌糖包子 2021-10-04 20:10:38 +08:00
parent 4a64f8f492
commit c751f26122
14 changed files with 298 additions and 98 deletions

View File

@ -8,6 +8,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^0.22.0", "axios": "^0.22.0",
"echarts": "^5.2.1",
"element-plus": "^1.1.0-beta.19", "element-plus": "^1.1.0-beta.19",
"hyperdown": "^2.4.29", "hyperdown": "^2.4.29",
"moment": "^2.29.1", "moment": "^2.29.1",

View File

@ -7,6 +7,7 @@ import SystemUser from './views/system/SystemUser.vue'
import SystemRole from './views/system/SystemRole.vue' import SystemRole from './views/system/SystemRole.vue'
import SystemConfig from './views/system/SystemConfig.vue' import SystemConfig from './views/system/SystemConfig.vue'
import Article from './views/system/Article.vue' import Article from './views/system/Article.vue'
import Statistics from './views/system/Statistics.vue'
import Music from './views/api/Music.vue' import Music from './views/api/Music.vue'
import Hitokoto from './views/api/Hitokoto.vue' import Hitokoto from './views/api/Hitokoto.vue'
@ -20,6 +21,7 @@ const routes: Array<RouteRecordRaw> = [
{ path: '/system/role', name: 'SystemRole', component: SystemRole }, { path: '/system/role', name: 'SystemRole', component: SystemRole },
{ path: '/system/config', name: 'SystemConfig', component: SystemConfig }, { path: '/system/config', name: 'SystemConfig', component: SystemConfig },
{ path: '/system/article', name: 'Article', component: Article }, { path: '/system/article', name: 'Article', component: Article },
{ path: '/system/statistics', name: 'Statistics', component: Statistics },
{ path: '/api/music', name: 'Music', component: Music }, { path: '/api/music', name: 'Music', component: Music },
{ path: '/api/hitokoto', name: 'Hitokoto', component: Hitokoto }, { path: '/api/hitokoto', name: 'Hitokoto', component: Hitokoto },

8
src/types.ts Normal file
View File

@ -0,0 +1,8 @@
import { Vue } from 'vue-class-component'
export type VForm = Vue & {
validate: (callback: (valid: boolean) => void) => void
resetValidation: () => boolean
reset: () => void
clearValidate: () => void
}

View File

@ -19,16 +19,15 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Options, Vue } from 'vue-class-component' import { Options, Vue } from 'vue-class-component'
import { ref } from 'vue'
import { ElMessage, ElButton, ElForm, ElFormItem, ElInput, ElTooltip } from 'element-plus' import { ElMessage, ElButton, ElForm, ElFormItem, ElInput, ElTooltip } from 'element-plus'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import { VForm } from "../types"
@Options({ @Options({
name: 'Login', name: 'Login',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTooltip } components: { ElButton, ElForm, ElFormItem, ElInput, ElTooltip }
}) })
export default class Login extends Vue { export default class Login extends Vue {
private readonly loginForm: any = ref('loginForm')
userInfo: UserInfo = { userInfo: UserInfo = {
username: null, username: null,
password: null password: null
@ -45,7 +44,7 @@ export default class Login extends Vue {
* 登录 * 登录
*/ */
async login() { async login() {
this.loginForm.validate(async (valid: boolean) => { (this.$refs.loginForm as VForm).validate(async (valid: boolean) => {
if(!valid) return if(!valid) return
const { data } = await this.$http.post<UserInfo, AxiosResponse<any>>('/api/common/login', this.userInfo) const { data } = await this.$http.post<UserInfo, AxiosResponse<any>>('/api/common/login', this.userInfo)
if(data.token) { if(data.token) {

View File

@ -67,13 +67,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import HitokotoAdd from './HitokotoAdd.vue' import HitokotoAdd from './HitokotoAdd.vue'
import { Options } from 'vue-class-component' import { Options, Vue } from 'vue-class-component'
import BaseList from '../../model/baselist' import BaseList from '../../model/baselist'
import { Page } from '../../model/common.dto' import { Page } from '../../model/common.dto'
import HitokotoModel from '../../model/api/hitokoto' import HitokotoModel from '../../model/api/hitokoto'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElDatePicker, ElMessage, ElMessageBox } from 'element-plus' import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElDatePicker, ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue' import { VForm } from '../../types'
let selectedData: string[] = [] let selectedData: string[] = []
@Options({ @Options({
@ -81,7 +81,6 @@ let selectedData: string[] = []
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElDatePicker, HitokotoAdd } components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElDatePicker, HitokotoAdd }
}) })
export default class Hitokoto extends BaseList<HitokotoPage> { export default class Hitokoto extends BaseList<HitokotoPage> {
private readonly addForm: any = ref('addForm')
search = new HitokotoPage() search = new HitokotoPage()
typeList: {label: string, value: string}[] = [] typeList: {label: string, value: string}[] = []
hitokotoData: HitokotoModel[] = [] hitokotoData: HitokotoModel[] = []
@ -97,7 +96,7 @@ export default class Hitokoto extends BaseList<HitokotoPage> {
this.hitokotoData = data.data this.hitokotoData = data.data
} }
async save() { async save() {
this.addForm.$refs.hitokotoForm.validate(async (valid: boolean) => { ((this.$refs.addForm as Vue).$refs.hitokotoForm as VForm).validate(async (valid: boolean) => {
if(!valid) return if(!valid) return
this.modalLoading = true this.modalLoading = true
const { data } = await this.$http.post<any, AxiosResponse<any>>('/api/hitokoto/save', this.formData) const { data } = await this.$http.post<any, AxiosResponse<any>>('/api/hitokoto/save', this.formData)

View File

@ -117,10 +117,9 @@ import BaseList from '../../model/baselist'
import { Page } from '../../model/common.dto' import { Page } from '../../model/common.dto'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElMessage, ElMessageBox } from 'element-plus' import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio, ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue'
import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '../../model/api/music' import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '../../model/api/music'
import prettyBytes from 'pretty-bytes' import prettyBytes from 'pretty-bytes'
import { VForm } from '../../types'
let selectedIds: string[] = [] let selectedIds: string[] = []
@Options({ @Options({
@ -128,8 +127,6 @@ let selectedIds: string[] = []
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio } components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElRadioGroup, ElRadio }
}) })
export default class Music extends BaseList<MusicPage> { export default class Music extends BaseList<MusicPage> {
// @Ref('player') private readonly player!: APlayerComponent
private readonly lyricForm: any = ref('lyricForm')
search = new MusicPage() search = new MusicPage()
currentRow: MusicModel | null = null currentRow: MusicModel | null = null
exts: string[] = [] exts: string[] = []
@ -226,7 +223,7 @@ export default class Music extends BaseList<MusicPage> {
} }
} }
async saveLyric() { async saveLyric() {
this.lyricForm.validate(async (valid: boolean) => { (this.$refs.lyricForm as VForm).validate(async (valid: boolean) => {
if(!valid) return if(!valid) return
this.modalLoading = true this.modalLoading = true
const { data } = await this.$http.post<MusicLyricModel, AxiosResponse<any>>(`/api/music/lyric/save?musicId=${this.currentRow ? this.currentRow._id : ''}`, this.lyricFormData) const { data } = await this.$http.post<MusicLyricModel, AxiosResponse<any>>(`/api/music/lyric/save?musicId=${this.currentRow ? this.currentRow._id : ''}`, this.lyricFormData)

View File

@ -34,14 +34,19 @@
<div class="btn-container"> <div class="btn-container">
<el-button type="primary" @click="splitWord" size="small" >分词处理</el-button> <el-button type="primary" @click="splitWord" size="small" >分词处理</el-button>
<el-button @click="pullArticles" size="small" >拉取文章</el-button> <el-button @click="pullArticles" size="small" >拉取文章</el-button>
<!-- <Upload :action="($http.defaults.baseURL || '') + '/api/system/deployBlog'" <el-upload
name="blogZip" :show-upload-list="false" action="/api/system/deployBlog"
:format="allowUploadExt" :headers="uploadHeaders" :max-size="102400" accept=".zip"
:before-upload="beforeUpload" :on-success="uploadSuccess" @on-error="uploadError" name="blogZip"
:on-format-error="uploadFormatError" :on-exceeded-size="uploadFileSizeError" :headers="uploadHeaders"
style="display: inline-block;"> :before-upload="beforeUpload"
<Button type="primary" icon="ios-cloud-upload-outline">发布博客</Button> :on-success="uploadSuccess"
</Upload> --> :on-error="uploadError"
auto-upload
:show-file-list="false"
style="display: inline-block;margin-left: 10px;">
<el-button size="small" type="primary" icon="el-icon-upload" :loading="isUploading">发布博客</el-button>
</el-upload>
<div class="search-btn"> <div class="search-btn">
<el-button type="primary" @click="loadDataBase(true)" size="small" icon="el-icon-search">搜索</el-button> <el-button type="primary" @click="loadDataBase(true)" size="small" icon="el-icon-search">搜索</el-button>
<el-button @click="reset" size="small" icon="el-icon-refresh-right">重置</el-button> <el-button @click="reset" size="small" icon="el-icon-refresh-right">重置</el-button>
@ -62,12 +67,12 @@
</el-table-column> </el-table-column>
<el-table-column prop="categories" label="分类" width="150" > <el-table-column prop="categories" label="分类" width="150" >
<template #default="scope"> <template #default="scope">
{{ typeof scope.row.categories === 'string' ? scope.row.categories : scope.row.categories.join('') }} {{ typeof scope.row.categories === 'string' ? scope.row.categories : scope.row.categories?.join('') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="tags" label="标签" width="180" > <el-table-column prop="tags" label="标签" width="180" >
<template #default="scope"> <template #default="scope">
{{ typeof scope.row.tags === 'string' ? scope.row.tags : scope.row.tags.join('') }} {{ typeof scope.row.tags === 'string' ? scope.row.tags : scope.row.tags?.join('') }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="content_len" label="正文长度" width="100" /> <el-table-column prop="content_len" label="正文长度" width="100" />
@ -84,18 +89,6 @@
</el-table> </el-table>
</el-col> </el-col>
</el-row> </el-row>
<!-- <Row :gutter="16">
<Col span="4" style="height:520px;overflow:auto;">
<Tree :data="articleTree" :load-data="loadTreeData" @on-select-change="articlePreview"></Tree>
</Col>
<Col span="20" >
<div class="table-container">
<Table border :loading="loading" :columns="articleColumns" :data="articleData" height="520" @on-selection-change="dataSelect"></Table>
</div>
</Col>
</Row> -->
<div class="page-container"> <div class="page-container">
<el-pagination background <el-pagination background
:page-sizes="$store.state.pageSizeOpts" :page-sizes="$store.state.pageSizeOpts"
@ -115,42 +108,38 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
// import moment from 'moment'
import hyperdown from 'hyperdown' import hyperdown from 'hyperdown'
import prismjs from 'prismjs' import prismjs from 'prismjs'
import 'prismjs/themes/prism.css' import 'prismjs/themes/prism.css'
import { ArticleModel, TreeNodeData, TreeNodeSource } from '../../model/system/article' import { ArticleModel, TreeNodeData, TreeNodeSource } from '../../model/system/article'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElSelect, ElOption, ElDatePicker, ElRow, ElCol, ElTree, ElDrawer, ElMessage, ElMessageBox } from 'element-plus' import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElSelect, ElOption, ElDatePicker, ElRow, ElCol, ElTree, ElDrawer, ElUpload, ElMessage, ElMessageBox } from 'element-plus'
import Node from 'element-plus/lib/components/tree/src/model/node' import Node from 'element-plus/lib/components/tree/src/model/node'
import { Options } from 'vue-class-component' import { Options } from 'vue-class-component'
import BaseList from '../../model/baselist' import BaseList from '../../model/baselist'
import { Page } from '../../model/common.dto' import { Page, MsgResult } from '../../model/common.dto'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
let selectedData: string[] = [] let selectedData: string[] = []
let closeUploadTip: Function | void | null
@Options({ @Options({
name: 'Article', name: 'Article',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElSelect, ElOption, ElDatePicker, ElRow, ElCol, ElTree, ElDrawer } components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElSelect, ElOption, ElDatePicker, ElRow, ElCol, ElTree, ElDrawer, ElUpload }
}) })
export default class Article extends BaseList<ArticlePage> { export default class Article extends BaseList<ArticlePage> {
search = new ArticlePage() search = new ArticlePage()
allowUploadExt = ['zip']
uploadHeaders = {token: localStorage.getItem('login_token')} uploadHeaders = {token: localStorage.getItem('login_token')}
articleData: ArticleModel[] = [] articleData: ArticleModel[] = []
tags: string[] = [] // tags: string[] = [] //
categories: string[] = [] // categories: string[] = [] //
markdownPreview :{ // markdown markdownPreview :{ // markdown
show?: boolean, show: boolean,
title?: string | null, title: string | null,
content?: string | null content: string | null
} = { } = {
show: false, show: false,
title: null, title: null,
content: null content: null
} }
isUploading: boolean = false
async loadData() { async loadData() {
this.loading = true this.loading = true
const { data } = await this.$http.get<ArticlePage, AxiosResponse<any>>('/api/article/list', {params:this.search}) const { data } = await this.$http.get<ArticlePage, AxiosResponse<any>>('/api/article/list', {params:this.search})
@ -187,43 +176,22 @@ export default class Article extends BaseList<ArticlePage> {
dataSelect(selection: ArticleModel[]) { dataSelect(selection: ArticleModel[]) {
selectedData = selection.map(item => item._id) selectedData = selection.map(item => item._id)
} }
// beforeUpload(file: File) { beforeUpload(file: File) {
// let filenameCut = undefined this.isUploading = true
// if(file.name.length > 15) { return true
// filenameCut = file.name.substr(0, 15) + '...' }
// } uploadSuccess(response: MsgResult) {
// closeUploadTip = ElMessage.loading({ if(response.status) {
// content: (filenameCut || file.name) + ' ...', ElMessage.success(response.message)
// duration: 0 } else {
// }) ElMessage.warning(response.message)
// return true }
// } this.isUploading = false
// uploadFormatError() { }
// this.closeUploadTip() uploadError() {
// ElMessage.error(` ${this.allowUploadExt.join('')} `) this.isUploading = false
// } ElMessage.error('上传失败')
// uploadFileSizeError() { }
// this.closeUploadTip()
// ElMessage.error(`100MB`)
// }
// uploadSuccess(response: MsgResult) {
// this.closeUploadTip()
// if(response.status) {
// ElMessage.success(response.message)
// } else {
// ElMessage.warning(response.message)
// }
// }
// uploadError() {
// this.closeUploadTip()
// ElMessage.error('')
// }
// closeUploadTip() {
// if(typeof closeUploadTip === 'function') {
// closeUploadTip.call(this)
// closeUploadTip = null
// }
// }
readonly treeProps = { readonly treeProps = {
label: 'name', label: 'name',
children: 'children', children: 'children',

View File

@ -0,0 +1,210 @@
<template>
<div>
<div class="echarts-container">
<div ref="categoriesChart" v-loading="categoriesChartLoading"></div>
<div ref="publishDatesChart" v-loading="publishDatesChartLoading"></div>
<div class="timeline-chart" ref="timelineWordsChart" v-loading="timelineWordsChartLoading"></div>
</div>
</div>
</template>
<script lang="ts">
import * as echarts from 'echarts'
import { Options, Vue } from 'vue-class-component'
import { AxiosResponse } from 'axios'
@Options({
name: 'Statistics'
})
export default class Statistics extends Vue {
categoriesChartLoading: boolean = false
publishDatesChartLoading: boolean = false
timelineWordsChartLoading: boolean = false
async mounted() {
this.categoriesChartLoading = true
this.publishDatesChartLoading = true
this.timelineWordsChartLoading = true
const articleData = (await this.$http.get<{params:{type:string}}, AxiosResponse<any>>('/api/article/statistics', {params:{type:'normal'}})).data
this.categoriesChartOption.legend.data = articleData.categories.map((item: any) => item._id)
this.categoriesChartOption.series.data = articleData.categories.map((item: any) => {
return {name: item._id, value: item.cnt}
})
this.publishDatesChartOption.xAxis.data = articleData.publishDates.map((item: any) => item._id)
this.publishDatesChartOption.series.data = articleData.publishDates.map((item: any) => item.cnt)
const categoriesChart = echarts.init(this.$refs.categoriesChart as HTMLElement)
categoriesChart.setOption(this.categoriesChartOption)
const publishDatesChart = echarts.init(this.$refs.publishDatesChart as HTMLElement)
publishDatesChart.setOption(this.publishDatesChartOption)
this.categoriesChartLoading = false
this.publishDatesChartLoading = false
const timelineData = (await this.$http.get<{params:{type:string}}, AxiosResponse<any>>('/api/article/statistics', {params:{type:'timelineWords'}})).data
this.timelineWordsChartOption.timeline.data = timelineData.timelineWords.map((item: any) => item._id)
timelineData.timelineWords.forEach((item: any) => {
this.timelineWordsChartOption.options.push({
title: { text: `${item._id}年发布的文章` },
xAxis: { data: item.keys.map((keyItem: any) => keyItem.key) },
series: { data: item.keys.map((keyItem: any) => keyItem.total) }
})
})
const timelineWordsChart = echarts.init(this.$refs.timelineWordsChart as HTMLElement)
timelineWordsChart.setOption(this.timelineWordsChartOption)
this.timelineWordsChartLoading = false
}
categoriesChartOption = {
title : {
text: '文章分类',
x: 'center',
top: 10
},
tooltip : {
trigger: 'item',
formatter: "{a} <br/>{b} : {c} ({d}%)"
},
legend: {
type: 'scroll',
orient: 'vertical',
right: 10,
top: 50,
bottom: 20,
data: [],
},
series: {
name: '类别',
type: 'pie',
radius: ['40%', '70%'],
center: ['40%', '50%'],
itemStyle: {
borderRadius: 5,
borderColor: '#FFF',
borderWidth: 1
},
data: []
}
}
publishDatesChartOption = {
title: {
left: 'center',
text: '文章发布时间',
top: 10
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
animation: false,
label: {
backgroundColor: '#ccc',
borderColor: '#aaa',
borderWidth: 1,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 0,
color: '#222'
}
},
},
xAxis: {
name: '发布时间',
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
name: '文章数量',
type: 'value',
max: function(value: {max: number}) {
return value.max + 10
}
},
dataZoom: [{
type: 'inside',
start: 0,
end: 100
},{
start: 0,
end: 10
}],
series: {
name: '文章数量',
type: 'line',
smooth: true,
symbol: 'none',
sampling: 'average',
itemStyle: {
color: 'rgb(255, 70, 131)'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 158, 68)'
},{
offset: 1,
color: 'rgb(255, 70, 131)'
}
])
},
data: []
}
}
timelineWordsChartOption: any = {
options: [],
timeline: {
axisType: 'category',
autoPlay: false,
playInterval: 1000,
data: [],
},
title: {
left: 'center',
subtext: '数据来自文章分词结果'
},
calculable : true,
grid: {
top: 80,
bottom: 80
},
xAxis: {
name: '高频词汇',
type: 'category',
splitLine: {show: false}
},
yAxis: {
name: '词汇出现次数',
type: 'value'
},
tooltip : {
trigger: 'axis',
axisPointer : { //
type : 'shadow' // 线'line' | 'shadow'
}
},
series: {
type: 'bar',
itemStyle: {
borderRadius: 5
}
}
}
}
</script>
<style lang="less" scoped>
.echarts-container {
display: grid;
grid-template-columns: 50% 50%;
grid-template-rows: 50% 50%;
height: 100%;
> .echarts {
border: 1px solid #ccc;
width: auto;
height: auto;
}
}
.timeline-chart {
grid-column-start: 1;
grid-column-end: 3;
}
</style>

View File

@ -59,17 +59,16 @@
import { Options, Vue } from 'vue-class-component' import { Options, Vue } from 'vue-class-component'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElDialog, ElMessage, ElMessageBox } from 'element-plus' import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElDialog, ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue'
import moment from 'moment' import moment from 'moment'
import SystemConfigAdd from './SystemConfigAdd.vue' import SystemConfigAdd from './SystemConfigAdd.vue'
import { SystemConfigModel } from '../../model/system/system-config' import { SystemConfigModel } from '../../model/system/system-config'
import { VForm } from '../../types'
@Options({ @Options({
name: 'SystemConfig', name: 'SystemConfig',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElDialog, SystemConfigAdd } components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElDialog, SystemConfigAdd }
}) })
export default class SystemConfig extends Vue { export default class SystemConfig extends Vue {
private readonly addForm: any = ref('addForm')
modalLoading: boolean = false modalLoading: boolean = false
loading: boolean = false loading: boolean = false
search: {name?:string} = {} search: {name?:string} = {}
@ -111,7 +110,7 @@ export default class SystemConfig extends Vue {
this.addModal = true this.addModal = true
} }
async save() { async save() {
this.addForm.$refs.configForm.validate(async (valid: boolean) => { ((this.$refs.addForm as Vue).$refs.configForm as VForm).validate(async (valid: boolean) => {
if(!valid) return if(!valid) return
this.modalLoading = true this.modalLoading = true
const { data } = await this.$http.post<SystemConfigModel, AxiosResponse<any>>('/api/system/config/save', this.formData) const { data } = await this.$http.post<SystemConfigModel, AxiosResponse<any>>('/api/system/config/save', this.formData)
@ -137,7 +136,7 @@ export default class SystemConfig extends Vue {
} }
clearValidate() { clearValidate() {
this.$nextTick(() => { this.$nextTick(() => {
this.addForm.$refs.configForm.clearValidate() ((this.$refs.addForm as Vue).$refs.configForm as VForm).clearValidate()
}) })
} }
/** /**

View File

@ -97,14 +97,13 @@ import { Page } from '../../model/common.dto'
import { SystemRoleModel } from '../../model/system/system-role' import { SystemRoleModel } from '../../model/system/system-role'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption, ElMessage, ElMessageBox } from 'element-plus' import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption, ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue' import { VForm } from '../../types'
@Options({ @Options({
name: 'SystemRole', name: 'SystemRole',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption } components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption }
}) })
export default class SystemRole extends BaseList<SystemRolePage> { export default class SystemRole extends BaseList<SystemRolePage> {
private readonly roleForm: any = ref('roleForm')
ruleValidate = { ruleValidate = {
name: [ name: [
{ required: true, message: '请输入角色名称', trigger: 'blur' } { required: true, message: '请输入角色名称', trigger: 'blur' }
@ -192,7 +191,7 @@ export default class SystemRole extends BaseList<SystemRolePage> {
}).catch(() => {}) }).catch(() => {})
} }
async save() { async save() {
this.roleForm.validate(async (valid: boolean) => { (this.$refs.roleForm as VForm).validate(async (valid: boolean) => {
if(!valid) return if(!valid) return
this.modalLoading = true this.modalLoading = true
const { data } = await this.$http.post<SystemRoleModel, AxiosResponse<any>>('/api/system/role/save', this.formData) const { data } = await this.$http.post<SystemRoleModel, AxiosResponse<any>>('/api/system/role/save', this.formData)
@ -204,7 +203,7 @@ export default class SystemRole extends BaseList<SystemRolePage> {
} }
clearValidate() { clearValidate() {
this.$nextTick(() => { this.$nextTick(() => {
this.roleForm.clearValidate() (this.$refs.roleForm as VForm).clearValidate()
}) })
} }
} }

View File

@ -77,14 +77,13 @@ import { SystemUserModel } from '../../model/system/system-user'
import { SystemRoleModel } from '../../model/system/system-role' import { SystemRoleModel } from '../../model/system/system-role'
import { AxiosResponse } from 'axios' import { AxiosResponse } from 'axios'
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElMessage, ElMessageBox } from 'element-plus' import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption, ElMessage, ElMessageBox } from 'element-plus'
import { ref } from 'vue' import { VForm } from '../../types'
@Options({ @Options({
name: 'SystemUser', name: 'SystemUser',
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption } components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElPagination, ElDialog, ElSelect, ElOption }
}) })
export default class SystemUser extends BaseList<SystemUserPage> { export default class SystemUser extends BaseList<SystemUserPage> {
private readonly userForm: any = ref('userForm')
get ruleValidate() { get ruleValidate() {
return { return {
username: [ username: [
@ -148,8 +147,7 @@ export default class SystemUser extends BaseList<SystemUserPage> {
this.clearValidate() this.clearValidate()
} }
async save() { async save() {
console.log(this.userForm) (this.$refs.userForm as VForm).validate(async (valid: boolean) => {
this.userForm.validate(async (valid: boolean) => {
if(!valid) return if(!valid) return
this.modalLoading = true this.modalLoading = true
const { data } = await this.$http.post<SystemUserModel, AxiosResponse<any>>('/api/system/user/save', this.formData) const { data } = await this.$http.post<SystemUserModel, AxiosResponse<any>>('/api/system/user/save', this.formData)
@ -178,7 +176,7 @@ export default class SystemUser extends BaseList<SystemUserPage> {
} }
clearValidate() { clearValidate() {
this.$nextTick(() => { this.$nextTick(() => {
this.userForm.clearValidate() (this.$refs.userForm as VForm).clearValidate()
}) })
} }
} }

View File

@ -23,14 +23,12 @@
<script lang="ts"> <script lang="ts">
import { Options, Vue } from 'vue-class-component' import { Options, Vue } from 'vue-class-component'
import { ElForm, ElFormItem, ElInput, ElButtonGroup, ElButton, ElMessage } from 'element-plus' import { ElForm, ElFormItem, ElInput, ElButtonGroup, ElButton, ElMessage } from 'element-plus'
import { ref } from 'vue'
@Options({ @Options({
name: 'SqlReplace', name: 'SqlReplace',
components: { ElForm, ElFormItem, ElInput, ElButtonGroup, ElButton } components: { ElForm, ElFormItem, ElInput, ElButtonGroup, ElButton }
}) })
export default class SqlReplace extends Vue { export default class SqlReplace extends Vue {
private readonly inputInstance: any = ref('resultInput')
sql: string = '' sql: string = ''
params: string = '' params: string = ''
replaceResult: string = '' replaceResult: string = ''
@ -57,7 +55,7 @@ export default class SqlReplace extends Vue {
} }
this.replaceResult = sql this.replaceResult = sql
Promise.resolve('已复制到剪贴板').then(message => { Promise.resolve('已复制到剪贴板').then(message => {
this.inputInstance.select() (this.$refs.resultInput as HTMLInputElement).select()
if (document.execCommand('copy')) { if (document.execCommand('copy')) {
ElMessage.success(message) ElMessage.success(message)
} else { } else {

View File

@ -27,6 +27,8 @@ export default defineConfig({
manualChunks(id) { manualChunks(id) {
if (id.includes('element-plus')) { if (id.includes('element-plus')) {
return 'element-plus' return 'element-plus'
} else if(id.includes('echarts')) {
return 'echarts'
} else if(id.includes('node_modules')) { } else if(id.includes('node_modules')) {
return 'vendor' return 'vendor'
} }

View File

@ -342,6 +342,14 @@ domutils@^2.5.2:
domelementtype "^2.2.0" domelementtype "^2.2.0"
domhandler "^4.2.0" domhandler "^4.2.0"
echarts@^5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.2.1.tgz#bd58ec011cd82def4a714e4038ef4b73b8417bc3"
integrity sha512-OJ79b22eqRfbSV8vYmDKmA+XWfNbr0Uk/OafWcFNIGDWti2Uw9A6eVCiJLmqPa9Sk+EWL+t5v26aak0z3gxiZw==
dependencies:
tslib "2.3.0"
zrender "5.2.1"
element-plus@^1.1.0-beta.19: element-plus@^1.1.0-beta.19:
version "1.1.0-beta.19" version "1.1.0-beta.19"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.1.0-beta.19.tgz#234f3bc3f1d822a7102045e7ee1239b675f27acc" resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.1.0-beta.19.tgz#234f3bc3f1d822a7102045e7ee1239b675f27acc"
@ -938,6 +946,11 @@ token-stream@1.0.0:
resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4" resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4"
integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ= integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=
tslib@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^1.10.0: tslib@^1.10.0:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -1182,3 +1195,10 @@ yallist@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
zrender@5.2.1:
version "5.2.1"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.2.1.tgz#5f4bbda915ba6d412b0b19dc2431beaad05417bb"
integrity sha512-M3bPGZuyLTNBC6LiNKXJwSCtglMp8XUEqEBG+2MdICDI3d1s500Y4P0CzldQGsqpRVB7fkvf3BKQQRxsEaTlsw==
dependencies:
tslib "2.3.0"