refactor: 全面迁移 vue-class-component 至 Vue 3 Composition API
- 将所有页面组件从 class 语法重写为 <script setup> 风格 - App.vue / Login.vue / Home.vue / Welcome.vue - api: Hitokoto.vue / HitokotoAdd.vue / Music.vue / PhotoWall.vue / SourceImage.vue - system: Article.vue / Statistics.vue / SystemConfig.vue / SystemConfigAdd.vue / SystemRole.vue / SystemUser.vue - 新增 src/utils/http.ts:独立 axios 实例,含请求/响应拦截器,替代 vue-axios 插件 - baselist.ts:abstract class BaseList<T> → useBaseList<T>() 组合式函数 - types.ts:VForm 类型改用 Element Plus 原生 FormInstance - main.ts:移除 vue-axios 及内联 axios 配置,路由守卫直接引用 store - 依赖清理:移除 vue-class-component、vue-axios
This commit is contained in:
parent
1ffd56efc2
commit
96b032d262
11539
package-lock.json
generated
Normal file
11539
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,8 +15,6 @@
|
|||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"pretty-bytes": "^5.6.0",
|
"pretty-bytes": "^5.6.0",
|
||||||
"vue": "^3.2.45",
|
"vue": "^3.2.45",
|
||||||
"vue-axios": "^3.3.7",
|
|
||||||
"vue-class-component": "8.0.0-rc.1",
|
|
||||||
"vue-router": "^4.0.11",
|
"vue-router": "^4.0.11",
|
||||||
"vuex": "^4.0.2"
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
|
|||||||
10
src/App.vue
10
src/App.vue
@ -6,16 +6,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
|
||||||
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
|
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
|
||||||
|
|
||||||
@Options({
|
const locale = zhCn
|
||||||
name: 'App'
|
|
||||||
})
|
|
||||||
export default class App extends Vue{
|
|
||||||
locale = zhCn
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import url('./static/common.less');
|
@import url('./static/common.less');
|
||||||
|
|||||||
51
src/main.ts
51
src/main.ts
@ -3,58 +3,18 @@ import App from './App.vue'
|
|||||||
import { router, filterExclude } from './router'
|
import { router, filterExclude } from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
|
|
||||||
import VueAxios from 'vue-axios'
|
import { ElLoading } from 'element-plus'
|
||||||
import axios from 'axios'
|
|
||||||
// 配置默认axios参数
|
|
||||||
const service = axios.create({
|
|
||||||
timeout: 10000
|
|
||||||
})
|
|
||||||
|
|
||||||
import { ElMessage, ElLoading } from 'element-plus'
|
|
||||||
import 'element-plus/theme-chalk/el-message.css'
|
import 'element-plus/theme-chalk/el-message.css'
|
||||||
import 'element-plus/theme-chalk/el-message-box.css'
|
import 'element-plus/theme-chalk/el-message-box.css'
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
|
|
||||||
// 添加请求拦截器
|
|
||||||
service.interceptors.request.use(config => {
|
|
||||||
// 在发送请求之前添加token到请求头
|
|
||||||
const token = mountedApp.$store.state.loginInfo.token
|
|
||||||
if (token !== null && config.headers) {
|
|
||||||
config.headers.token = token
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
}, err => {
|
|
||||||
// 请求错误的处理
|
|
||||||
ElMessage.error('请求超时,请稍后再试')
|
|
||||||
return Promise.reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
service.interceptors.response.use(res=> {
|
|
||||||
return res.data
|
|
||||||
}, err => {
|
|
||||||
if (err.response.status >= 500) {
|
|
||||||
ElMessage.error('服务器内部错误')
|
|
||||||
} else if (err.response.status >= 400) {
|
|
||||||
if(typeof err.response.data.message === 'string') {
|
|
||||||
ElMessage.warning(err.response.data.message)
|
|
||||||
} else if (Array.isArray(err.response.data.message)) {
|
|
||||||
let message = err.response.data.message.join('<br/>')
|
|
||||||
ElMessage.warning(message)
|
|
||||||
}
|
|
||||||
if (err.response.status === 403) {
|
|
||||||
mountedApp.$router.push('/login')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 全局路由导航前置守卫
|
// 全局路由导航前置守卫
|
||||||
router.beforeEach(function (to, from, next) {
|
router.beforeEach(function (to, from, next) {
|
||||||
if (to.meta?.title) {
|
if (to.meta?.title) {
|
||||||
mountedApp.$store.commit('addTab', { title: to.meta.title, path: to.path, name: to.name })
|
store.commit('addTab', { title: to.meta.title, path: to.path, name: to.name })
|
||||||
}
|
}
|
||||||
mountedApp.$store.state.activeTab = to.path
|
store.state.activeTab = to.path
|
||||||
if(filterExclude.indexOf(to.path) !== -1 || mountedApp.$store.state.loginInfo.token) {
|
if(filterExclude.indexOf(to.path) !== -1 || store.state.loginInfo.token) {
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
next('/login')
|
next('/login')
|
||||||
@ -66,8 +26,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|||||||
app.component(key, component)
|
app.component(key, component)
|
||||||
}
|
}
|
||||||
|
|
||||||
const mountedApp = app.use(router)
|
app.use(router)
|
||||||
.use(store)
|
.use(store)
|
||||||
.use(VueAxios, service)
|
|
||||||
.directive('loading', ElLoading.directive)
|
.directive('loading', ElLoading.directive)
|
||||||
.mount('#app')
|
.mount('#app')
|
||||||
|
|||||||
@ -1,65 +1,44 @@
|
|||||||
import { Vue } from 'vue-class-component'
|
import { ref, reactive } from 'vue'
|
||||||
import { Page } from './common.dto'
|
import { Page } from './common.dto'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
|
||||||
export default abstract class BaseList<T extends Page> extends Vue {
|
export function useBaseList<T extends Page>(searchInit: T) {
|
||||||
/**
|
const loading = ref(false)
|
||||||
* 表格数据加载中
|
const modalLoading = ref(false)
|
||||||
*/
|
const total = ref(0)
|
||||||
loading: boolean = false
|
const search = reactive(searchInit) as T
|
||||||
/**
|
|
||||||
* 表单提交中
|
let _loadData: (() => Promise<void>) | null = null
|
||||||
*/
|
|
||||||
modalLoading: boolean = false
|
function setLoadData(fn: () => Promise<void>) {
|
||||||
/**
|
_loadData = fn
|
||||||
* 数据总数
|
}
|
||||||
*/
|
|
||||||
total: number = 0
|
function loadDataBase(resetPage: boolean = false): void {
|
||||||
abstract search: T
|
|
||||||
/**
|
|
||||||
* 加载数据的实现
|
|
||||||
*/
|
|
||||||
abstract loadData(): Promise<void>
|
|
||||||
/**
|
|
||||||
* 加载数据
|
|
||||||
* @param resetPage 是否重置页码
|
|
||||||
*/
|
|
||||||
loadDataBase(resetPage: boolean = false): void {
|
|
||||||
if (resetPage) {
|
if (resetPage) {
|
||||||
this.search.pageNum = 1
|
search.pageNum = 1
|
||||||
}
|
}
|
||||||
this.loadData()
|
_loadData?.()
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 重置搜索项
|
|
||||||
*/
|
|
||||||
reset(): void {
|
|
||||||
this.search.reset()
|
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 页码变更
|
|
||||||
* @param pageNum 页码
|
|
||||||
*/
|
|
||||||
pageChange(pageNum: number): void {
|
|
||||||
this.search.pageNum = pageNum
|
|
||||||
this.loadData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function reset(): void {
|
||||||
* 每页数据条数变更
|
search.reset()
|
||||||
* @param pageSize 每页数据条数
|
_loadData?.()
|
||||||
*/
|
|
||||||
pageSizeChange(pageSize: number): void {
|
|
||||||
this.search.limit = pageSize
|
|
||||||
this.loadData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function pageChange(pageNum: number): void {
|
||||||
* 日期时间格式化
|
search.pageNum = pageNum
|
||||||
* @param dateStr 日期时间
|
_loadData?.()
|
||||||
*/
|
}
|
||||||
datetimeFormat(dateStr: string): string | null {
|
|
||||||
|
function pageSizeChange(pageSize: number): void {
|
||||||
|
search.limit = pageSize
|
||||||
|
_loadData?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
function datetimeFormat(dateStr: string): string | null {
|
||||||
return dateStr ? moment(dateStr).format('YYYY-MM-DD HH:mm:ss') : null
|
return dateStr ? moment(dateStr).format('YYYY-MM-DD HH:mm:ss') : null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { loading, modalLoading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat }
|
||||||
}
|
}
|
||||||
@ -1,8 +1,3 @@
|
|||||||
import { Vue } from 'vue-class-component'
|
import type { FormInstance } from 'element-plus'
|
||||||
|
|
||||||
export type VForm = Vue & {
|
export type VForm = FormInstance
|
||||||
validate: (callback: (valid: boolean) => void) => void
|
|
||||||
resetValidation: () => boolean
|
|
||||||
reset: () => void
|
|
||||||
clearValidate: () => void
|
|
||||||
}
|
|
||||||
41
src/utils/http.ts
Normal file
41
src/utils/http.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import store from '@/store'
|
||||||
|
import { router } from '@/router'
|
||||||
|
|
||||||
|
const http = axios.create({
|
||||||
|
timeout: 10000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加请求拦截器
|
||||||
|
http.interceptors.request.use(config => {
|
||||||
|
const token = store.state.loginInfo.token
|
||||||
|
if (token !== null && config.headers) {
|
||||||
|
config.headers.token = token
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
}, err => {
|
||||||
|
ElMessage.error('请求超时,请稍后再试')
|
||||||
|
return Promise.reject(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
http.interceptors.response.use(res => {
|
||||||
|
return res.data
|
||||||
|
}, err => {
|
||||||
|
if (err.response.status >= 500) {
|
||||||
|
ElMessage.error('服务器内部错误')
|
||||||
|
} else if (err.response.status >= 400) {
|
||||||
|
if (typeof err.response.data.message === 'string') {
|
||||||
|
ElMessage.warning(err.response.data.message)
|
||||||
|
} else if (Array.isArray(err.response.data.message)) {
|
||||||
|
let message = err.response.data.message.join('<br/>')
|
||||||
|
ElMessage.warning(message)
|
||||||
|
}
|
||||||
|
if (err.response.status === 403) {
|
||||||
|
router.push('/login')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.reject(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
export default http
|
||||||
@ -35,9 +35,9 @@
|
|||||||
</el-aside>
|
</el-aside>
|
||||||
<el-main class="layout-main">
|
<el-main class="layout-main">
|
||||||
<div class="layout-tabs">
|
<div class="layout-tabs">
|
||||||
<el-tabs type="card" class="nav-tabs" v-model="$store.state.activeTab" @tab-change="openMenu" @tab-remove="removeTab">
|
<el-tabs type="card" class="nav-tabs" v-model="store.state.activeTab" @tab-change="openMenu" @tab-remove="removeTab">
|
||||||
<el-tab-pane label="首页" name="/"></el-tab-pane>
|
<el-tab-pane label="首页" name="/"></el-tab-pane>
|
||||||
<el-tab-pane v-for="tab in $store.state.tabs" :key="tab.name" :label="tab.title" :name="tab.path" closable></el-tab-pane>
|
<el-tab-pane v-for="tab in store.state.tabs" :key="tab.name" :label="tab.title" :name="tab.path" closable></el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
<div class="layout-content">
|
<div class="layout-content">
|
||||||
@ -53,72 +53,74 @@
|
|||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import { ref, computed } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import menus from '../config/menu'
|
import menus from '../config/menu'
|
||||||
|
import http from '@/utils/http'
|
||||||
|
|
||||||
@Options({
|
const store = useStore()
|
||||||
name: 'Home'
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const version = process.env.VERSION
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
const defaultActiveMenuKey = ref<string | null>(null)
|
||||||
|
const openMenuNames = ref<string[]>([])
|
||||||
|
|
||||||
|
const realname = computed((): null | string => {
|
||||||
|
return store.state.loginInfo.userInfo
|
||||||
|
? store.state.loginInfo.userInfo.realname : null
|
||||||
})
|
})
|
||||||
export default class Home extends Vue{
|
const keepViews = computed((): string[] => {
|
||||||
public version?: string = process.env.VERSION
|
return store.state.tabs
|
||||||
currentYear = new Date().getFullYear()
|
.filter((item: any) => item.name)
|
||||||
// 菜单项
|
.map((item: any) => item.name)
|
||||||
menus = menus
|
})
|
||||||
defaultActiveMenuKey: string | null = null
|
|
||||||
openMenuNames: string[] = []
|
// created
|
||||||
get realname(): null | string { // 当前用户的显示名称
|
defaultActiveMenuKey.value = route.path
|
||||||
return this.$store.state.loginInfo.userInfo
|
if (defaultActiveMenuKey.value) {
|
||||||
? this.$store.state.loginInfo.userInfo.realname : null
|
let result = /^\/(.*)\//.exec(defaultActiveMenuKey.value)
|
||||||
}
|
|
||||||
get keepViews(): string[] {
|
|
||||||
return this.$store.state.tabs
|
|
||||||
.filter(item => item.name)
|
|
||||||
.map(item => item.name)
|
|
||||||
}
|
|
||||||
async created(): Promise<void> {
|
|
||||||
this.defaultActiveMenuKey = this.$route.path
|
|
||||||
if(this.defaultActiveMenuKey) {
|
|
||||||
let result = /^\/(.*)\//.exec(this.defaultActiveMenuKey)
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this.openMenuNames.push(result[1])
|
openMenuNames.value.push(result[1])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!this.$store.state.loginInfo.token) {
|
if (!store.state.loginInfo.token) {
|
||||||
this.$router.push('/login')
|
router.push('/login')
|
||||||
return
|
|
||||||
}
|
|
||||||
const data = await this.$http.post<{token: string}, any>('/api/v1/common/verifyToken', {token: this.$store.state.loginInfo.token})
|
|
||||||
if(data.status) {
|
|
||||||
// 如果是已过期的token 服务端会签发新的token
|
|
||||||
this.$store.commit('login', {token: data.newToken || this.$store.state.loginInfo.token, userInfo: data.userInfo})
|
|
||||||
} else {
|
} else {
|
||||||
this.$router.push('/login')
|
http.post<{token: string}, any>('/api/v1/common/verifyToken', {token: store.state.loginInfo.token}).then(data => {
|
||||||
|
if (data.status) {
|
||||||
|
store.commit('login', {token: data.newToken || store.state.loginInfo.token, userInfo: data.userInfo})
|
||||||
|
} else {
|
||||||
|
router.push('/login')
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
dropdownMenuCommand(command: string): void {
|
|
||||||
|
function dropdownMenuCommand(command: string): void {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'home': // 返回首页
|
case 'home':
|
||||||
this.$router.push('/')
|
router.push('/')
|
||||||
break
|
break
|
||||||
case 'changePassword': // 修改密码
|
case 'changePassword':
|
||||||
// TODO
|
// TODO
|
||||||
break
|
break
|
||||||
case 'logout': //注销
|
case 'logout':
|
||||||
this.$store.commit('logout')
|
store.commit('logout')
|
||||||
this.$router.push('/login')
|
router.push('/login')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
openMenu(path: string): void {
|
function openMenu(path: string): void {
|
||||||
this.$router.push(path)
|
router.push(path)
|
||||||
}
|
|
||||||
removeTab(path: string): void {
|
|
||||||
this.$store.commit('removeTab', path)
|
|
||||||
if (this.$store.state.activeTab === path) { // 关闭的是当前打开的页签
|
|
||||||
const { tabs } = this.$store.state
|
|
||||||
this.openMenu(tabs[tabs.length - 1]?.path || '/')
|
|
||||||
}
|
}
|
||||||
|
function removeTab(path: string): void {
|
||||||
|
store.commit('removeTab', path)
|
||||||
|
if (store.state.activeTab === path) {
|
||||||
|
const { tabs } = store.state
|
||||||
|
openMenu(tabs[tabs.length - 1]?.path || '/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -14,20 +14,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import { reactive, ref } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { VForm } from "../types"
|
import type { VForm } from '../types'
|
||||||
|
import http from '@/utils/http'
|
||||||
|
|
||||||
@Options({
|
type UserInfo = {
|
||||||
name: 'Login'
|
username: string | null,
|
||||||
})
|
password: string | null
|
||||||
export default class Login extends Vue {
|
}
|
||||||
userInfo: UserInfo = {
|
|
||||||
|
const store = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
const loginForm = ref<VForm>()
|
||||||
|
|
||||||
|
const userInfo: UserInfo = reactive({
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
}
|
})
|
||||||
ruleValidate = {
|
const ruleValidate = {
|
||||||
username: [
|
username: [
|
||||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
@ -35,31 +43,24 @@ export default class Login extends Vue {
|
|||||||
{ required: true, message: '请输入密码', trigger: 'blur' }
|
{ required: true, message: '请输入密码', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
created(): void {
|
|
||||||
this.$store.commit('logout')
|
// created
|
||||||
this.$store.commit('clearTabs')
|
store.commit('logout')
|
||||||
|
store.commit('clearTabs')
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
}
|
|
||||||
/**
|
async function login() {
|
||||||
* 登录
|
loginForm.value?.validate(async (valid: boolean) => {
|
||||||
*/
|
|
||||||
async login() {
|
|
||||||
(this.$refs.loginForm as VForm).validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
const data = await this.$http.post<UserInfo, any>('/api/v1/common/login', this.userInfo)
|
const data = await http.post<UserInfo, any>('/api/v1/common/login', userInfo)
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
this.$store.commit('login', data)
|
store.commit('login', data)
|
||||||
this.$router.push('/')
|
router.push('/')
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(data.message)
|
ElMessage.error(data.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
type UserInfo = {
|
|
||||||
username: string | null,
|
|
||||||
password: string | null
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.login-wrapper {
|
.login-wrapper {
|
||||||
|
|||||||
@ -12,16 +12,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
|
||||||
import menus from '../config/menu'
|
import menus from '../config/menu'
|
||||||
|
|
||||||
@Options({
|
|
||||||
name: 'Welcome'
|
|
||||||
})
|
|
||||||
export default class Welcome extends Vue {
|
|
||||||
menus = menus
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.nav-list {
|
.nav-list {
|
||||||
|
|||||||
@ -47,8 +47,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -66,73 +66,15 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import HitokotoAdd from './HitokotoAdd.vue'
|
import HitokotoAdd from './HitokotoAdd.vue'
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import { useBaseList } 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 { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { VForm } from '@/types'
|
import http from '@/utils/http'
|
||||||
|
|
||||||
let selectedData: string[] = []
|
|
||||||
@Options({
|
|
||||||
name: 'Hitokoto',
|
|
||||||
components: { HitokotoAdd }
|
|
||||||
})
|
|
||||||
export default class Hitokoto extends BaseList<HitokotoPage> {
|
|
||||||
search = new HitokotoPage()
|
|
||||||
typeList: {label: string, value: string}[] = []
|
|
||||||
hitokotoData: HitokotoModel[] = []
|
|
||||||
formData: {[propName:string]: string | null} = {}
|
|
||||||
addModal: boolean = false
|
|
||||||
|
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
|
||||||
const data = await this.$http.get<HitokotoPage, any>('/api/v1/hitokoto/list', {params:this.search})
|
|
||||||
selectedData = []
|
|
||||||
this.loading = false
|
|
||||||
this.total = data.total
|
|
||||||
this.hitokotoData = data.data
|
|
||||||
}
|
|
||||||
async save() {
|
|
||||||
((this.$refs.addForm as Vue).$refs.hitokotoForm as VForm).validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return
|
|
||||||
this.modalLoading = true
|
|
||||||
const data = await this.$http.post<any, any>('/api/v1/hitokoto/save', this.formData)
|
|
||||||
this.modalLoading = false
|
|
||||||
this.addModal = false
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
// 清空表单
|
|
||||||
this.formData = {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
deleteAll() {
|
|
||||||
if(!selectedData.length) {
|
|
||||||
ElMessage.warning('请选择要删除的数据')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
|
|
||||||
const data = await this.$http.delete<any, any>('/api/v1/hitokoto/delete', {params:{_ids: selectedData}})
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
dataSelect(selection: HitokotoModel[]) {
|
|
||||||
selectedData = selection.map(item => item._id)
|
|
||||||
}
|
|
||||||
created() {
|
|
||||||
this.loadData()
|
|
||||||
this.$http.get<never, any>('/api/v1/common/config/hitokoto_type').then(data => {
|
|
||||||
this.typeList = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
findTypeText(value: string): string | null {
|
|
||||||
const type = this.typeList.find(item => item.value === value)
|
|
||||||
return type ? type.label : null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HitokotoPage extends Page {
|
class HitokotoPage extends Page {
|
||||||
content?: string
|
content?: string
|
||||||
@ -145,4 +87,62 @@ class HitokotoPage extends Page {
|
|||||||
this.createdAt = undefined
|
this.createdAt = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const { loading, modalLoading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new HitokotoPage())
|
||||||
|
|
||||||
|
const typeList = ref<{label: string, value: string}[]>([])
|
||||||
|
const hitokotoData = ref<HitokotoModel[]>([])
|
||||||
|
const formData = reactive<{[propName: string]: string | null}>({})
|
||||||
|
const addModal = ref(false)
|
||||||
|
const addForm = ref<InstanceType<typeof HitokotoAdd>>()
|
||||||
|
|
||||||
|
let selectedData: string[] = []
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<HitokotoPage, any>('/api/v1/hitokoto/list', {params: search})
|
||||||
|
selectedData = []
|
||||||
|
loading.value = false
|
||||||
|
total.value = data.total
|
||||||
|
hitokotoData.value = data.data
|
||||||
|
}
|
||||||
|
setLoadData(loadData)
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
addForm.value?.hitokotoForm?.validate(async (valid: boolean) => {
|
||||||
|
if (!valid) return
|
||||||
|
modalLoading.value = true
|
||||||
|
const data = await http.post<any, any>('/api/v1/hitokoto/save', formData)
|
||||||
|
modalLoading.value = false
|
||||||
|
addModal.value = false
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
Object.keys(formData).forEach(key => delete formData[key])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function deleteAll() {
|
||||||
|
if (!selectedData.length) {
|
||||||
|
ElMessage.warning('请选择要删除的数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
|
const data = await http.delete<any, any>('/api/v1/hitokoto/delete', {params: {_ids: selectedData}})
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
function dataSelect(selection: HitokotoModel[]) {
|
||||||
|
selectedData = selection.map(item => item._id)
|
||||||
|
}
|
||||||
|
function findTypeText(value: string): string | null {
|
||||||
|
const type = typeList.value.find(item => item.value === value)
|
||||||
|
return type ? type.label : null
|
||||||
|
}
|
||||||
|
|
||||||
|
// created
|
||||||
|
loadData()
|
||||||
|
http.get<never, any>('/api/v1/common/config/hitokoto_type').then(data => {
|
||||||
|
typeList.value = data
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -18,20 +18,18 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import { ref } from 'vue'
|
||||||
|
import type { VForm } from '@/types'
|
||||||
|
|
||||||
@Options({
|
defineProps<{
|
||||||
name: 'SystemConfigAdd',
|
typeList: {label: string, value: string}[]
|
||||||
props: {
|
formData: {[propName: string]: string | null}
|
||||||
typeList: Array,
|
}>()
|
||||||
formData: Object
|
|
||||||
}
|
const hitokotoForm = ref<VForm>()
|
||||||
})
|
|
||||||
export default class HitokotoAdd extends Vue {
|
const ruleValidate = {
|
||||||
typeList!: {label: string, value: string}[]
|
|
||||||
formData!: {[propName:string]: string | null}
|
|
||||||
ruleValidate = {
|
|
||||||
hitokoto: [
|
hitokoto: [
|
||||||
{ required: true, message: '请输入内容', trigger: 'blur' }
|
{ required: true, message: '请输入内容', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
@ -39,5 +37,6 @@ export default class HitokotoAdd extends Vue {
|
|||||||
{ required: true, message: '请选择类型', trigger: 'blur' }
|
{ required: true, message: '请选择类型', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
defineExpose({ hitokotoForm })
|
||||||
</script>
|
</script>
|
||||||
@ -79,8 +79,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -118,7 +118,7 @@
|
|||||||
action="/api/v2/music/upload"
|
action="/api/v2/music/upload"
|
||||||
name="file"
|
name="file"
|
||||||
accept=".mp3,.flac"
|
accept=".mp3,.flac"
|
||||||
:headers="{token: $store.state.loginInfo.token}"
|
:headers="{token: store.state.loginInfo.token}"
|
||||||
:on-success="uploadSuccess"
|
:on-success="uploadSuccess"
|
||||||
:on-error="uploadError"
|
:on-error="uploadError"
|
||||||
:auto-upload="false"
|
:auto-upload="false"
|
||||||
@ -148,207 +148,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options } from 'vue-class-component'
|
import { ref, reactive } from 'vue'
|
||||||
import BaseList from '@/model/baselist'
|
import { useStore } from 'vuex'
|
||||||
|
import { useBaseList } from '@/model/baselist'
|
||||||
import { MsgResult, Page } from '@/model/common.dto'
|
import { MsgResult, Page } from '@/model/common.dto'
|
||||||
import { ElUpload, ElMessage, ElMessageBox } from 'element-plus'
|
import { ElUploadInstance, ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '@/model/api/music'
|
import { MusicModel, MusicLibModel, MusicLyricModel, MusicPlayerItem } from '@/model/api/music'
|
||||||
import APlayer from './aplayer/vue-aplayer.vue'
|
import APlayer from './aplayer/vue-aplayer.vue'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { VForm } from '@/types'
|
import type { VForm } from '@/types'
|
||||||
|
import http from '@/utils/http'
|
||||||
let selectedIds: string[] = []
|
|
||||||
@Options({
|
|
||||||
name: 'Music',
|
|
||||||
components: { ElUpload, APlayer }
|
|
||||||
})
|
|
||||||
export default class Music extends BaseList<MusicPage> {
|
|
||||||
search = new MusicPage()
|
|
||||||
currentRow: MusicModel | null = null
|
|
||||||
libIdSelected: string | null = null
|
|
||||||
exts: string[] = []
|
|
||||||
musicLibs: MusicLibModel[] = []
|
|
||||||
musicData: MusicModel[] = []
|
|
||||||
uploadModal: boolean = false
|
|
||||||
modifyLyricModal: boolean = false
|
|
||||||
lyricRuleValidate = {
|
|
||||||
cloud_id: [
|
|
||||||
{ required: true, message: '请输入网易云ID', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
name: [
|
|
||||||
{ required: true, message: '请输入名称', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
lyric: [
|
|
||||||
{ required: true, message: '请输入歌词正文', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
prettyBytes = prettyBytes
|
|
||||||
lyricFormData: MusicLyricModel = {}
|
|
||||||
// 是否正在播放音乐
|
|
||||||
musicPlaying: boolean = false
|
|
||||||
musicList: MusicPlayerItem[] = []
|
|
||||||
currentMusic?: MusicPlayerItem
|
|
||||||
created() {
|
|
||||||
this.$http.get<never, any>('/api/v1/music/listLibs').then(data => {
|
|
||||||
this.musicLibs = data
|
|
||||||
this.loadData()
|
|
||||||
})
|
|
||||||
this.$http.get<never, any>('/api/v1/music/listExts').then(data => {
|
|
||||||
this.exts = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
|
||||||
const data = await this.$http.get<MusicPage, any>('/api/v1/music/list', {params: this.search})
|
|
||||||
selectedIds = []
|
|
||||||
this.loading = false
|
|
||||||
this.total = data.total
|
|
||||||
this.musicData = data.data
|
|
||||||
}
|
|
||||||
dataSelect(selection: MusicModel[]) {
|
|
||||||
selectedIds = selection.map(item => item._id)
|
|
||||||
}
|
|
||||||
findMusicLib(value: string): string | null {
|
|
||||||
const musicLib = this.musicLibs.find(item => item._id === value)
|
|
||||||
return musicLib ? musicLib.name : null
|
|
||||||
}
|
|
||||||
// 根据当前搜索条件播放音乐
|
|
||||||
async playMusic() {
|
|
||||||
try {
|
|
||||||
const data = await this.$http.get<any, any>('/api/v1/music/list/all', {params: selectedIds.length ? {ids: selectedIds} : this.search})
|
|
||||||
this.musicList = data.map((item: MusicModel, index: number) => {
|
|
||||||
const musicItem: MusicPlayerItem = {
|
|
||||||
id: index,
|
|
||||||
title: item.title || item.name,
|
|
||||||
artist: item.artist,
|
|
||||||
album: item.album,
|
|
||||||
src: `/api/v2/common/music/load/${item._id}`,
|
|
||||||
pic: `/api/v2/common/music/album/${item._id}`,
|
|
||||||
}
|
|
||||||
if(item.lyric_id) {
|
|
||||||
musicItem.lrc = `${location.origin}/api/v2/common/music/lyric/${item.lyric_id}`
|
|
||||||
}
|
|
||||||
return musicItem
|
|
||||||
})
|
|
||||||
this.currentMusic = this.musicList[0]
|
|
||||||
this.musicPlaying = true
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
ElMessage.error('获取播放列表失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateLib(row: MusicModel) {
|
|
||||||
this.currentRow = { ...row }
|
|
||||||
row.isEditing = true
|
|
||||||
}
|
|
||||||
download(row: MusicModel) {
|
|
||||||
const link = document.createElement('a')
|
|
||||||
link.setAttribute('href', `/api/v2/common/music/load/${row._id}`)
|
|
||||||
link.setAttribute('download', row.name)
|
|
||||||
link.setAttribute('target', '_blank')
|
|
||||||
link.click()
|
|
||||||
}
|
|
||||||
remove(row: MusicModel) {
|
|
||||||
ElMessageBox.confirm(`是否确认删除 ${row.name} ?`, '确认删除', {type: 'warning'}).then(async () => {
|
|
||||||
const data = await this.$http.delete<{params: {id: string}}, any>('/api/v2/music/delete', {params: {id: row._id}})
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
async updateLyric(row: MusicModel) {
|
|
||||||
this.currentRow = { ...row }
|
|
||||||
this.modifyLyricModal = true
|
|
||||||
if (row.lyric_id) {
|
|
||||||
const data = (await this.$http.get<any, any>('/api/v1/music/lyric/get', {params: {lyricId: row.lyric_id}}))
|
|
||||||
data.cloud_id = data.cloud_id ? data.cloud_id.toString() : null
|
|
||||||
this.lyricFormData = data
|
|
||||||
} else {
|
|
||||||
this.lyricFormData = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async saveLyric() {
|
|
||||||
(this.$refs.lyricForm as VForm).validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return
|
|
||||||
this.modalLoading = true
|
|
||||||
const data = await this.$http.post<MusicLyricModel, any>(`/api/v1/music/lyric/save?musicId=${this.currentRow ? this.currentRow._id : ''}`, this.lyricFormData)
|
|
||||||
this.modalLoading = false
|
|
||||||
this.modifyLyricModal = false
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
// 清空表单
|
|
||||||
this.lyricFormData = {}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async saveMusicLib(row: MusicModel) {
|
|
||||||
if (!this.currentRow) return
|
|
||||||
const data = await this.$http.post<{id: string, libId: string}, any>('/api/v2/music/updateLib', {id: this.currentRow._id, libId: this.currentRow.lib_id})
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
row.lib_id = this.currentRow.lib_id
|
|
||||||
row.isEditing = false
|
|
||||||
}
|
|
||||||
openUploadModal() {
|
|
||||||
this.uploadModal = true
|
|
||||||
this.libIdSelected = null
|
|
||||||
}
|
|
||||||
async uploadMusic() {
|
|
||||||
if (!this.libIdSelected) {
|
|
||||||
ElMessage.warning('请选择歌单')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 执行上传
|
|
||||||
(this.$refs.musicUpload as typeof ElUpload).submit()
|
|
||||||
}
|
|
||||||
uploadSuccess(response: MsgResult) {
|
|
||||||
if(response.code === 0) {
|
|
||||||
ElMessage.success(response.message)
|
|
||||||
this.loadData()
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(response.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uploadError(error: Error) {
|
|
||||||
ElMessage.error(error.message)
|
|
||||||
}
|
|
||||||
uploadModalClosed() {
|
|
||||||
(this.$refs.musicUpload as typeof ElUpload).clearFiles()
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 创建媒体信息
|
|
||||||
*/
|
|
||||||
musicPlay() {
|
|
||||||
if(!('mediaSession' in window.navigator) || !this.currentMusic) return;
|
|
||||||
const player = <any>this.$refs.player
|
|
||||||
const currentId = this.currentMusic.id
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata({
|
|
||||||
title: this.currentMusic.title,
|
|
||||||
artist: this.currentMusic.artist,
|
|
||||||
album: this.currentMusic.album,
|
|
||||||
artwork: [{src: location.origin + this.currentMusic.pic}]
|
|
||||||
})
|
|
||||||
navigator.mediaSession.setActionHandler('play', () => { // 播放
|
|
||||||
player.play()
|
|
||||||
})
|
|
||||||
navigator.mediaSession.setActionHandler('pause', () => { // 暂停
|
|
||||||
player.pause()
|
|
||||||
})
|
|
||||||
navigator.mediaSession.setActionHandler('previoustrack', () => { // 上一首
|
|
||||||
if (currentId === 0) { // 已经是第一首
|
|
||||||
player.switch(this.musicList.length - 1)
|
|
||||||
} else {
|
|
||||||
player.switch(currentId - 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
navigator.mediaSession.setActionHandler('nexttrack', () => { // 下一首
|
|
||||||
if (currentId === this.musicList.length - 1) { // 已经是最后一首
|
|
||||||
player.switch(0)
|
|
||||||
} else {
|
|
||||||
player.switch(currentId + 1)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MusicPage extends Page {
|
class MusicPage extends Page {
|
||||||
name?: string
|
name?: string
|
||||||
@ -367,4 +177,190 @@ class MusicPage extends Page {
|
|||||||
this.lib_id = []
|
this.lib_id = []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const { loading, modalLoading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new MusicPage())
|
||||||
|
|
||||||
|
const currentRow = ref<MusicModel | null>(null)
|
||||||
|
const libIdSelected = ref<string | null>(null)
|
||||||
|
const exts = ref<string[]>([])
|
||||||
|
const musicLibs = ref<MusicLibModel[]>([])
|
||||||
|
const musicData = ref<MusicModel[]>([])
|
||||||
|
const uploadModal = ref(false)
|
||||||
|
const modifyLyricModal = ref(false)
|
||||||
|
const lyricFormData = ref<MusicLyricModel>({})
|
||||||
|
const musicPlaying = ref(false)
|
||||||
|
const musicList = ref<MusicPlayerItem[]>([])
|
||||||
|
const currentMusic = ref<MusicPlayerItem>()
|
||||||
|
const lyricForm = ref<VForm>()
|
||||||
|
const musicUpload = ref<ElUploadInstance>()
|
||||||
|
const player = ref<any>()
|
||||||
|
|
||||||
|
const lyricRuleValidate = {
|
||||||
|
cloud_id: [
|
||||||
|
{ required: true, message: '请输入网易云ID', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
lyric: [
|
||||||
|
{ required: true, message: '请输入歌词正文', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedIds: string[] = []
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<MusicPage, any>('/api/v1/music/list', {params: search})
|
||||||
|
selectedIds = []
|
||||||
|
loading.value = false
|
||||||
|
total.value = data.total
|
||||||
|
musicData.value = data.data
|
||||||
|
}
|
||||||
|
setLoadData(loadData)
|
||||||
|
|
||||||
|
function dataSelect(selection: MusicModel[]) {
|
||||||
|
selectedIds = selection.map(item => item._id)
|
||||||
|
}
|
||||||
|
function findMusicLib(value: string): string | null {
|
||||||
|
const musicLib = musicLibs.value.find(item => item._id === value)
|
||||||
|
return musicLib ? musicLib.name : null
|
||||||
|
}
|
||||||
|
async function playMusic() {
|
||||||
|
try {
|
||||||
|
const data = await http.get<any, any>('/api/v1/music/list/all', {params: selectedIds.length ? {ids: selectedIds} : search})
|
||||||
|
musicList.value = data.map((item: MusicModel, index: number) => {
|
||||||
|
const musicItem: MusicPlayerItem = {
|
||||||
|
id: index,
|
||||||
|
title: item.title || item.name,
|
||||||
|
artist: item.artist,
|
||||||
|
album: item.album,
|
||||||
|
src: `/api/v2/common/music/load/${item._id}`,
|
||||||
|
pic: `/api/v2/common/music/album/${item._id}`,
|
||||||
|
}
|
||||||
|
if (item.lyric_id) {
|
||||||
|
musicItem.lrc = `${location.origin}/api/v2/common/music/lyric/${item.lyric_id}`
|
||||||
|
}
|
||||||
|
return musicItem
|
||||||
|
})
|
||||||
|
currentMusic.value = musicList.value[0]
|
||||||
|
musicPlaying.value = true
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
ElMessage.error('获取播放列表失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateLib(row: MusicModel) {
|
||||||
|
currentRow.value = { ...row }
|
||||||
|
row.isEditing = true
|
||||||
|
}
|
||||||
|
function download(row: MusicModel) {
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.setAttribute('href', `/api/v2/common/music/load/${row._id}`)
|
||||||
|
link.setAttribute('download', row.name)
|
||||||
|
link.setAttribute('target', '_blank')
|
||||||
|
link.click()
|
||||||
|
}
|
||||||
|
function remove(row: MusicModel) {
|
||||||
|
ElMessageBox.confirm(`是否确认删除 ${row.name} ?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
|
const data = await http.delete<{params: {id: string}}, any>('/api/v2/music/delete', {params: {id: row._id}})
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
async function updateLyric(row: MusicModel) {
|
||||||
|
currentRow.value = { ...row }
|
||||||
|
modifyLyricModal.value = true
|
||||||
|
if (row.lyric_id) {
|
||||||
|
const data = (await http.get<any, any>('/api/v1/music/lyric/get', {params: {lyricId: row.lyric_id}}))
|
||||||
|
data.cloud_id = data.cloud_id ? data.cloud_id.toString() : null
|
||||||
|
lyricFormData.value = data
|
||||||
|
} else {
|
||||||
|
lyricFormData.value = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function saveLyric() {
|
||||||
|
lyricForm.value?.validate(async (valid: boolean) => {
|
||||||
|
if (!valid) return
|
||||||
|
modalLoading.value = true
|
||||||
|
const data = await http.post<MusicLyricModel, any>(`/api/v1/music/lyric/save?musicId=${currentRow.value ? currentRow.value._id : ''}`, lyricFormData.value)
|
||||||
|
modalLoading.value = false
|
||||||
|
modifyLyricModal.value = false
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
lyricFormData.value = {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function saveMusicLib(row: MusicModel) {
|
||||||
|
if (!currentRow.value) return
|
||||||
|
const data = await http.post<{id: string, libId: string}, any>('/api/v2/music/updateLib', {id: currentRow.value._id, libId: currentRow.value.lib_id})
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
row.lib_id = currentRow.value.lib_id
|
||||||
|
row.isEditing = false
|
||||||
|
}
|
||||||
|
function openUploadModal() {
|
||||||
|
uploadModal.value = true
|
||||||
|
libIdSelected.value = null
|
||||||
|
}
|
||||||
|
async function uploadMusic() {
|
||||||
|
if (!libIdSelected.value) {
|
||||||
|
ElMessage.warning('请选择歌单')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
musicUpload.value?.submit()
|
||||||
|
}
|
||||||
|
function uploadSuccess(response: MsgResult) {
|
||||||
|
if (response.code === 0) {
|
||||||
|
ElMessage.success(response.message)
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(response.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function uploadError(error: Error) {
|
||||||
|
ElMessage.error(error.message)
|
||||||
|
}
|
||||||
|
function uploadModalClosed() {
|
||||||
|
musicUpload.value?.clearFiles()
|
||||||
|
}
|
||||||
|
function musicPlay() {
|
||||||
|
if (!('mediaSession' in window.navigator) || !currentMusic.value) return
|
||||||
|
const currentId = currentMusic.value.id
|
||||||
|
navigator.mediaSession.metadata = new MediaMetadata({
|
||||||
|
title: currentMusic.value.title,
|
||||||
|
artist: currentMusic.value.artist,
|
||||||
|
album: currentMusic.value.album,
|
||||||
|
artwork: [{src: location.origin + currentMusic.value.pic}]
|
||||||
|
})
|
||||||
|
navigator.mediaSession.setActionHandler('play', () => {
|
||||||
|
player.value.play()
|
||||||
|
})
|
||||||
|
navigator.mediaSession.setActionHandler('pause', () => {
|
||||||
|
player.value.pause()
|
||||||
|
})
|
||||||
|
navigator.mediaSession.setActionHandler('previoustrack', () => {
|
||||||
|
if (currentId === 0) {
|
||||||
|
player.value.switch(musicList.value.length - 1)
|
||||||
|
} else {
|
||||||
|
player.value.switch(currentId - 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
navigator.mediaSession.setActionHandler('nexttrack', () => {
|
||||||
|
if (currentId === musicList.value.length - 1) {
|
||||||
|
player.value.switch(0)
|
||||||
|
} else {
|
||||||
|
player.value.switch(currentId + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// created
|
||||||
|
http.get<never, any>('/api/v1/music/listLibs').then(data => {
|
||||||
|
musicLibs.value = data
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
http.get<never, any>('/api/v1/music/listExts').then(data => {
|
||||||
|
exts.value = data
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -34,7 +34,7 @@
|
|||||||
action="/api/v2/photoWall/upload"
|
action="/api/v2/photoWall/upload"
|
||||||
accept="image/jpeg,image/png"
|
accept="image/jpeg,image/png"
|
||||||
name="image"
|
name="image"
|
||||||
:headers="{token: $store.state.loginInfo.token}"
|
:headers="{token: store.state.loginInfo.token}"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
:on-success="uploadSuccess"
|
:on-success="uploadSuccess"
|
||||||
:on-error="uploadError"
|
:on-error="uploadError"
|
||||||
@ -67,8 +67,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -77,81 +77,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options } from 'vue-class-component'
|
import { ref, h } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { MsgResult, Page } from '@/model/common.dto'
|
import { MsgResult, Page } from '@/model/common.dto'
|
||||||
import BaseList from '@/model/baselist'
|
import { useBaseList } from '@/model/baselist'
|
||||||
import PhotoWallModel from '@/model/api/photowall'
|
import PhotoWallModel from '@/model/api/photowall'
|
||||||
import { h } from 'vue'
|
import http from '@/utils/http'
|
||||||
|
|
||||||
let selectedData: string[] = []
|
|
||||||
@Options({
|
|
||||||
name: 'PhotoWall'
|
|
||||||
})
|
|
||||||
export default class PhotoWall extends BaseList<PhotoWallPage> {
|
|
||||||
search = new PhotoWallPage()
|
|
||||||
allowUploadExt = ['jpg','jpeg','png']
|
|
||||||
photowallData = []
|
|
||||||
isUploading: boolean = false
|
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
|
||||||
const data = await this.$http.get<PhotoWallPage, any>('/api/v1/photowall/list', {params:this.search})
|
|
||||||
selectedData = []
|
|
||||||
this.loading = false
|
|
||||||
this.total = data.total
|
|
||||||
this.photowallData = data.data
|
|
||||||
}
|
|
||||||
deleteAll() {
|
|
||||||
if(!selectedData || !selectedData.length) {
|
|
||||||
ElMessage.warning('请选择要删除的数据')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
|
|
||||||
await this.$http.delete('/api/v1/photowall/delete', {params:{_ids: selectedData}})
|
|
||||||
ElMessage.success('删除成功')
|
|
||||||
this.loadData()
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
dataSelect(selection: PhotoWallModel[]) {
|
|
||||||
selectedData = selection.map(item => item._id)
|
|
||||||
}
|
|
||||||
beforeUpload(file: File): boolean {
|
|
||||||
if(file.size > 10 << 20) {
|
|
||||||
ElMessage.warning('文件大小超过10MB')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
this.isUploading = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
uploadSuccess(response: MsgResult) {
|
|
||||||
if(response.code === 0) {
|
|
||||||
ElMessage.success(response.message)
|
|
||||||
this.loadData()
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(response.message)
|
|
||||||
}
|
|
||||||
this.isUploading = false
|
|
||||||
}
|
|
||||||
uploadError(error: Error) {
|
|
||||||
this.isUploading = false
|
|
||||||
ElMessage.error(error.message)
|
|
||||||
}
|
|
||||||
async preview(row: PhotoWallModel) {
|
|
||||||
const previewHeight = Math.floor(row.height * (500 / row.width))
|
|
||||||
const pictureCdn = await this.$http.get('/api/v1/common/config/picture_cdn')
|
|
||||||
ElMessageBox({
|
|
||||||
title: '图片预览',
|
|
||||||
message: h('img', { style: `width:500px;height:${previewHeight}px;`, src: `${pictureCdn}/${row.name}` }, ''),
|
|
||||||
showCancelButton: false,
|
|
||||||
confirmButtonText: '关闭',
|
|
||||||
customStyle: {width: '530px', maxWidth: 'unset'}
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
created() {
|
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PhotoWallPage extends Page {
|
class PhotoWallPage extends Page {
|
||||||
name?: string
|
name?: string
|
||||||
@ -168,4 +101,73 @@ class PhotoWallPage extends Page {
|
|||||||
this.heightMax = 0
|
this.heightMax = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const { loading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange } = useBaseList(new PhotoWallPage())
|
||||||
|
|
||||||
|
const allowUploadExt = ['jpg', 'jpeg', 'png']
|
||||||
|
const photowallData = ref([])
|
||||||
|
const isUploading = ref(false)
|
||||||
|
|
||||||
|
let selectedData: string[] = []
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<PhotoWallPage, any>('/api/v1/photowall/list', {params: search})
|
||||||
|
selectedData = []
|
||||||
|
loading.value = false
|
||||||
|
total.value = data.total
|
||||||
|
photowallData.value = data.data
|
||||||
|
}
|
||||||
|
setLoadData(loadData)
|
||||||
|
|
||||||
|
function deleteAll() {
|
||||||
|
if (!selectedData || !selectedData.length) {
|
||||||
|
ElMessage.warning('请选择要删除的数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
|
await http.delete('/api/v1/photowall/delete', {params: {_ids: selectedData}})
|
||||||
|
ElMessage.success('删除成功')
|
||||||
|
loadData()
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
function dataSelect(selection: PhotoWallModel[]) {
|
||||||
|
selectedData = selection.map(item => item._id)
|
||||||
|
}
|
||||||
|
function beforeUpload(file: File): boolean {
|
||||||
|
if (file.size > 10 << 20) {
|
||||||
|
ElMessage.warning('文件大小超过10MB')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
isUploading.value = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
function uploadSuccess(response: MsgResult) {
|
||||||
|
if (response.code === 0) {
|
||||||
|
ElMessage.success(response.message)
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(response.message)
|
||||||
|
}
|
||||||
|
isUploading.value = false
|
||||||
|
}
|
||||||
|
function uploadError(error: Error) {
|
||||||
|
isUploading.value = false
|
||||||
|
ElMessage.error(error.message)
|
||||||
|
}
|
||||||
|
async function preview(row: PhotoWallModel) {
|
||||||
|
const previewHeight = Math.floor(row.height * (500 / row.width))
|
||||||
|
const pictureCdn = await http.get('/api/v1/common/config/picture_cdn')
|
||||||
|
ElMessageBox({
|
||||||
|
title: '图片预览',
|
||||||
|
message: h('img', { style: `width:500px;height:${previewHeight}px;`, src: `${pictureCdn}/${row.name}` }, ''),
|
||||||
|
showCancelButton: false,
|
||||||
|
confirmButtonText: '关闭',
|
||||||
|
customStyle: {width: '530px', maxWidth: 'unset'}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
// created
|
||||||
|
loadData()
|
||||||
</script>
|
</script>
|
||||||
@ -9,7 +9,7 @@
|
|||||||
action="/api/source-image/upload"
|
action="/api/source-image/upload"
|
||||||
accept="image/jpeg,image/png,image/svg+xml,image/x-icon"
|
accept="image/jpeg,image/png,image/svg+xml,image/x-icon"
|
||||||
name="image"
|
name="image"
|
||||||
:headers="{token: $store.state.loginInfo.token}"
|
:headers="{token: store.state.loginInfo.token}"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
:on-success="uploadSuccess"
|
:on-success="uploadSuccess"
|
||||||
:on-error="uploadError"
|
:on-error="uploadError"
|
||||||
@ -50,8 +50,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -74,81 +74,84 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options } from 'vue-class-component'
|
import { ref, computed, h } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import prettyBytes from 'pretty-bytes'
|
import prettyBytes from 'pretty-bytes'
|
||||||
import { MsgResult, Page } from '@/model/common.dto'
|
import { MsgResult, Page } from '@/model/common.dto'
|
||||||
import BaseList from '@/model/baselist'
|
import { useBaseList } from '@/model/baselist'
|
||||||
import { SourceImageModel } from '@/model/api/source-image'
|
import { SourceImageModel } from '@/model/api/source-image'
|
||||||
import { h } from 'vue'
|
import http from '@/utils/http'
|
||||||
|
|
||||||
let selectedData: string[] = []
|
const store = useStore()
|
||||||
@Options({
|
const { loading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new Page())
|
||||||
name: 'SourceImage',
|
|
||||||
})
|
const allowUploadExt = ['jpg', 'jpeg', 'png', 'svg', 'ico']
|
||||||
export default class SourceImage extends BaseList<Page> {
|
const sourceImageData = ref<SourceImageModel[]>([])
|
||||||
prettyBytes = prettyBytes
|
const curModifyLabels = ref<string[]>([])
|
||||||
search = new Page()
|
const labelList = ref<string[]>([])
|
||||||
allowUploadExt = ['jpg','jpeg','png','svg','ico']
|
const curId = ref<string | null>(null)
|
||||||
sourceImageData: SourceImageModel[] = []
|
const modifyModal = ref(false)
|
||||||
curModifyLabels: string[] = []
|
const isUploading = ref(false)
|
||||||
labelList: string[] = []
|
|
||||||
curId: string | null = null
|
function renderFunc(h: Function, option: any) {
|
||||||
modifyModal: boolean = false
|
|
||||||
isUploading: boolean = false
|
|
||||||
renderFunc(h: Function, option: any) {
|
|
||||||
return h('span', null, option.label)
|
return h('span', null, option.label)
|
||||||
}
|
}
|
||||||
get labels() {
|
const labels = computed(() => {
|
||||||
return this.labelList.map(item => {
|
return labelList.value.map(item => {
|
||||||
return { key: item, label: item }
|
return { key: item, label: item }
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
async loadData(): Promise<void> {
|
|
||||||
this.loading = true
|
let selectedData: string[] = []
|
||||||
const data = await this.$http.get<Page, any>('/api/v1/source-image/list', {params:this.search})
|
|
||||||
|
async function loadData(): Promise<void> {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<Page, any>('/api/v1/source-image/list', {params: search})
|
||||||
selectedData = []
|
selectedData = []
|
||||||
this.loading = false
|
loading.value = false
|
||||||
this.total = data.total
|
total.value = data.total
|
||||||
this.sourceImageData = data.data
|
sourceImageData.value = data.data
|
||||||
}
|
}
|
||||||
deleteAll(): void {
|
setLoadData(loadData)
|
||||||
|
|
||||||
|
function deleteAll(): void {
|
||||||
if (!selectedData.length) {
|
if (!selectedData.length) {
|
||||||
ElMessage.warning('请选择要删除的数据')
|
ElMessage.warning('请选择要删除的数据')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
|
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
await this.$http.delete('/api/v1/source-image/delete', {params:{_ids: selectedData}})
|
await http.delete('/api/v1/source-image/delete', {params: {_ids: selectedData}})
|
||||||
ElMessage.success('删除成功')
|
ElMessage.success('删除成功')
|
||||||
this.loadData()
|
loadData()
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
dataSelect(selection: SourceImageModel[]): void {
|
function dataSelect(selection: SourceImageModel[]): void {
|
||||||
selectedData = selection.map(item => item._id)
|
selectedData = selection.map(item => item._id)
|
||||||
}
|
}
|
||||||
beforeUpload(file: File): boolean {
|
function beforeUpload(file: File): boolean {
|
||||||
if (file.size > 10 << 20) {
|
if (file.size > 10 << 20) {
|
||||||
ElMessage.warning('文件大小超过10MB')
|
ElMessage.warning('文件大小超过10MB')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
this.isUploading = true
|
isUploading.value = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
uploadSuccess(response: MsgResult): void {
|
function uploadSuccess(response: MsgResult): void {
|
||||||
if (response.status) {
|
if (response.status) {
|
||||||
ElMessage.success(response.message)
|
ElMessage.success(response.message)
|
||||||
this.loadData()
|
loadData()
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning(response.message)
|
ElMessage.warning(response.message)
|
||||||
}
|
}
|
||||||
this.isUploading = false
|
isUploading.value = false
|
||||||
}
|
}
|
||||||
uploadError(error: Error): void {
|
function uploadError(error: Error): void {
|
||||||
this.isUploading = false
|
isUploading.value = false
|
||||||
ElMessage.error(error.message)
|
ElMessage.error(error.message)
|
||||||
}
|
}
|
||||||
preview(row: SourceImageModel): void {
|
function preview(row: SourceImageModel): void {
|
||||||
ElMessageBox({
|
ElMessageBox({
|
||||||
title: '图片预览',
|
title: '图片预览',
|
||||||
message: h('img', { style: `width:500px`, src: `/api/v1/common/randomBg?id=${row._id}` }, ''),
|
message: h('img', { style: `width:500px`, src: `/api/v1/common/randomBg?id=${row._id}` }, ''),
|
||||||
@ -157,22 +160,21 @@ export default class SourceImage extends BaseList<Page> {
|
|||||||
customStyle: {width: '530px', maxWidth: 'unset'}
|
customStyle: {width: '530px', maxWidth: 'unset'}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
modifyTags(item: SourceImageModel): void {
|
function modifyTags(item: SourceImageModel): void {
|
||||||
this.curModifyLabels.length = 0
|
curModifyLabels.value.length = 0
|
||||||
if (item.label) {
|
if (item.label) {
|
||||||
this.curModifyLabels.push(...item.label)
|
curModifyLabels.value.push(...item.label)
|
||||||
}
|
}
|
||||||
this.curId = item._id
|
curId.value = item._id
|
||||||
this.modifyModal = true
|
modifyModal.value = true
|
||||||
}
|
}
|
||||||
async tarnsferChange(newTargetKeys: string[], direction: 'right' | 'left', moveKeys: string[]) {
|
async function tarnsferChange(newTargetKeys: string[], direction: 'right' | 'left', moveKeys: string[]) {
|
||||||
await this.$http.post('/api/v1/source-image/updateLabel', {id: this.curId, labels: newTargetKeys})
|
await http.post('/api/v1/source-image/updateLabel', {id: curId.value, labels: newTargetKeys})
|
||||||
}
|
}
|
||||||
created() {
|
|
||||||
this.$http.get<never, any>('/api/v1/common/config/image_label').then(data => {
|
// created
|
||||||
this.labelList.push(...data)
|
http.get<never, any>('/api/v1/common/config/image_label').then(data => {
|
||||||
this.loadData()
|
labelList.value.push(...data)
|
||||||
|
loadData()
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
@ -38,7 +38,7 @@
|
|||||||
action="/api/system/deployBlog"
|
action="/api/system/deployBlog"
|
||||||
accept="application/zip"
|
accept="application/zip"
|
||||||
name="blogZip"
|
name="blogZip"
|
||||||
:headers="{token: $store.state.loginInfo.token}"
|
:headers="{token: store.state.loginInfo.token}"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
:on-success="uploadSuccess"
|
:on-success="uploadSuccess"
|
||||||
:on-error="uploadError"
|
:on-error="uploadError"
|
||||||
@ -92,8 +92,8 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -111,129 +111,16 @@
|
|||||||
</el-drawer>
|
</el-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue'
|
||||||
|
import { useStore } from 'vuex'
|
||||||
import hyperdown from 'hyperdown'
|
import hyperdown from 'hyperdown'
|
||||||
import { ArticleModel, TreeNodeData, TreeNodeSource } from '@/model/system/article'
|
import { ArticleModel, TreeNodeData, TreeNodeSource } from '@/model/system/article'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { 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 { useBaseList } from '@/model/baselist'
|
||||||
import BaseList from '@/model/baselist'
|
|
||||||
import { Page, MsgResult } from '@/model/common.dto'
|
import { Page, MsgResult } from '@/model/common.dto'
|
||||||
|
import http from '@/utils/http'
|
||||||
let selectedData: string[] = []
|
|
||||||
@Options({
|
|
||||||
name: 'Article'
|
|
||||||
})
|
|
||||||
export default class Article extends BaseList<ArticlePage> {
|
|
||||||
search = new ArticlePage()
|
|
||||||
articleData: ArticleModel[] = []
|
|
||||||
tags: string[] = [] // 所有标签
|
|
||||||
categories: string[] = [] // 所有分类
|
|
||||||
markdownPreview :{ // markdown内容预览
|
|
||||||
show: boolean,
|
|
||||||
title: string | null,
|
|
||||||
content: string | null
|
|
||||||
} = {
|
|
||||||
show: false,
|
|
||||||
title: null,
|
|
||||||
content: null
|
|
||||||
}
|
|
||||||
isUploading: boolean = false
|
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
|
||||||
const data = await this.$http.get<ArticlePage, any>('/api/v1/article/list', {params:this.search})
|
|
||||||
selectedData = []
|
|
||||||
this.loading = false
|
|
||||||
this.total = data.total
|
|
||||||
this.articleData = data.data
|
|
||||||
}
|
|
||||||
splitWord() {
|
|
||||||
if(!selectedData.length) {
|
|
||||||
ElMessage.warning('请选择要执行分词的文章')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ElMessageBox.confirm(`是否确认对选中的${selectedData.length}篇文章执行分词处理?`, '操作确认', {type: 'info'}).then(async () => {
|
|
||||||
const data = await this.$http.put<{_ids: string[]}, any>('/api/v1/article/splitWord', {_ids: selectedData})
|
|
||||||
if(data.status) {
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(data.message)
|
|
||||||
}
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
pullArticles() {
|
|
||||||
ElMessageBox.confirm('确认拉取全部文章?', '操作确认', {type: 'info'}).then(async () => {
|
|
||||||
const data = await this.$http.put<never, any>('/api/v1/article/pull')
|
|
||||||
if(data.status) {
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(data.message)
|
|
||||||
}
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
dataSelect(selection: ArticleModel[]) {
|
|
||||||
selectedData = selection.map(item => item._id)
|
|
||||||
}
|
|
||||||
beforeUpload(file: File): boolean {
|
|
||||||
this.isUploading = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
uploadSuccess(response: MsgResult) {
|
|
||||||
if(response.status) {
|
|
||||||
ElMessage.success(response.message)
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(response.message)
|
|
||||||
}
|
|
||||||
this.isUploading = false
|
|
||||||
}
|
|
||||||
uploadError(error: Error) {
|
|
||||||
this.isUploading = false
|
|
||||||
ElMessage.error(error.message)
|
|
||||||
}
|
|
||||||
readonly treeProps = {
|
|
||||||
label: 'name',
|
|
||||||
children: 'children',
|
|
||||||
isLeaf: 'isLeaf',
|
|
||||||
}
|
|
||||||
async loadTreeData(node: Node, resolve: Function) {
|
|
||||||
const childItems: TreeNodeSource[] = await this.$http.get('/api/v1/article/tree', {params:{deep: node.level, parent: node.data.name}})
|
|
||||||
resolve(childItems.map((childItem): TreeNodeData => {
|
|
||||||
const treeNode: TreeNodeData = {
|
|
||||||
name: childItem._id,
|
|
||||||
title: childItem.article_id ? childItem._id : `${childItem._id}(${childItem.cnt})`,
|
|
||||||
id: childItem.article_id || childItem._id,
|
|
||||||
isLeaf: !!childItem.article_id
|
|
||||||
}
|
|
||||||
return treeNode
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 树节点选中事件
|
|
||||||
* @param selectNodes 当前已选中的节点(适用于带复选框的)
|
|
||||||
* @param curNode 本次选中的节点
|
|
||||||
*/
|
|
||||||
async articlePreview(node: TreeNodeData) {
|
|
||||||
if(!node.isLeaf) return
|
|
||||||
// 加载文章markdown预览
|
|
||||||
const mdText = await this.$http.get<never, any>('/api/v1/article/markdown', {params:{id: node.id}})
|
|
||||||
this.markdownPreview.show = true
|
|
||||||
const markdownHtml = new hyperdown().makeHtml(mdText)
|
|
||||||
this.markdownPreview.content = markdownHtml.replace(/(?<=<pre><code[^>]*?>)[\s\S]*?(?=<\/code><\/pre>)/gi, content => {
|
|
||||||
return content.replace(/_&/g, ' ').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&')
|
|
||||||
})
|
|
||||||
this.markdownPreview.title = node.name
|
|
||||||
}
|
|
||||||
created() {
|
|
||||||
this.loadData()
|
|
||||||
this.$http.get<never, any>('/api/v1/article/listCategories').then(data => {
|
|
||||||
this.categories = data
|
|
||||||
})
|
|
||||||
this.$http.get<never, any>('/api/v1/article/listTags').then(data => {
|
|
||||||
this.tags = data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ArticlePage extends Page {
|
class ArticlePage extends Page {
|
||||||
title?: string
|
title?: string
|
||||||
@ -250,4 +137,115 @@ class ArticlePage extends Page {
|
|||||||
this.isSplited = undefined
|
this.isSplited = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const { loading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new ArticlePage())
|
||||||
|
|
||||||
|
const articleData = ref<ArticleModel[]>([])
|
||||||
|
const tags = ref<string[]>([])
|
||||||
|
const categories = ref<string[]>([])
|
||||||
|
const markdownPreview = reactive<{
|
||||||
|
show: boolean
|
||||||
|
title: string | null
|
||||||
|
content: string | null
|
||||||
|
}>({
|
||||||
|
show: false,
|
||||||
|
title: null,
|
||||||
|
content: null
|
||||||
|
})
|
||||||
|
const isUploading = ref(false)
|
||||||
|
|
||||||
|
let selectedData: string[] = []
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<ArticlePage, any>('/api/v1/article/list', {params: search})
|
||||||
|
selectedData = []
|
||||||
|
loading.value = false
|
||||||
|
total.value = data.total
|
||||||
|
articleData.value = data.data
|
||||||
|
}
|
||||||
|
setLoadData(loadData)
|
||||||
|
|
||||||
|
function splitWord() {
|
||||||
|
if (!selectedData.length) {
|
||||||
|
ElMessage.warning('请选择要执行分词的文章')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ElMessageBox.confirm(`是否确认对选中的${selectedData.length}篇文章执行分词处理?`, '操作确认', {type: 'info'}).then(async () => {
|
||||||
|
const data = await http.put<{_ids: string[]}, any>('/api/v1/article/splitWord', {_ids: selectedData})
|
||||||
|
if (data.status) {
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(data.message)
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
function pullArticles() {
|
||||||
|
ElMessageBox.confirm('确认拉取全部文章?', '操作确认', {type: 'info'}).then(async () => {
|
||||||
|
const data = await http.put<never, any>('/api/v1/article/pull')
|
||||||
|
if (data.status) {
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(data.message)
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
function dataSelect(selection: ArticleModel[]) {
|
||||||
|
selectedData = selection.map(item => item._id)
|
||||||
|
}
|
||||||
|
function beforeUpload(file: File): boolean {
|
||||||
|
isUploading.value = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
function uploadSuccess(response: MsgResult) {
|
||||||
|
if (response.status) {
|
||||||
|
ElMessage.success(response.message)
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(response.message)
|
||||||
|
}
|
||||||
|
isUploading.value = false
|
||||||
|
}
|
||||||
|
function uploadError(error: Error) {
|
||||||
|
isUploading.value = false
|
||||||
|
ElMessage.error(error.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeProps = {
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
isLeaf: 'isLeaf',
|
||||||
|
}
|
||||||
|
async function loadTreeData(node: Node, resolve: Function) {
|
||||||
|
const childItems: TreeNodeSource[] = await http.get('/api/v1/article/tree', {params: {deep: node.level, parent: node.data.name}})
|
||||||
|
resolve(childItems.map((childItem): TreeNodeData => {
|
||||||
|
const treeNode: TreeNodeData = {
|
||||||
|
name: childItem._id,
|
||||||
|
title: childItem.article_id ? childItem._id : `${childItem._id}(${childItem.cnt})`,
|
||||||
|
id: childItem.article_id || childItem._id,
|
||||||
|
isLeaf: !!childItem.article_id
|
||||||
|
}
|
||||||
|
return treeNode
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
async function articlePreview(node: TreeNodeData) {
|
||||||
|
if (!node.isLeaf) return
|
||||||
|
const mdText = await http.get<never, any>('/api/v1/article/markdown', {params: {id: node.id}})
|
||||||
|
markdownPreview.show = true
|
||||||
|
const markdownHtml = new hyperdown().makeHtml(mdText)
|
||||||
|
markdownPreview.content = markdownHtml.replace(/(?<=<pre><code[^>]*?>)[\s\S]*?(?=<\/code><\/pre>)/gi, (content: string) => {
|
||||||
|
return content.replace(/_&/g, ' ').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&')
|
||||||
|
})
|
||||||
|
markdownPreview.title = node.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// created
|
||||||
|
loadData()
|
||||||
|
http.get<never, any>('/api/v1/article/listCategories').then(data => {
|
||||||
|
categories.value = data
|
||||||
|
})
|
||||||
|
http.get<never, any>('/api/v1/article/listTags').then(data => {
|
||||||
|
tags.value = data
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -7,52 +7,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import http from '@/utils/http'
|
||||||
|
|
||||||
@Options({
|
const categoriesChart = ref<HTMLElement>()
|
||||||
name: 'Statistics'
|
const publishDatesChart = ref<HTMLElement>()
|
||||||
})
|
const timelineWordsChart = ref<HTMLElement>()
|
||||||
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}}, any>('/api/v1/article/statistics', {params:{type:'normal'}})
|
const categoriesChartLoading = ref(false)
|
||||||
this.categoriesChartOption.legend.data = articleData.categories.map((item: any) => item._id)
|
const publishDatesChartLoading = ref(false)
|
||||||
this.categoriesChartOption.series.data = articleData.categories.map((item: any) => {
|
const timelineWordsChartLoading = ref(false)
|
||||||
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)
|
const categoriesChartOption: any = {
|
||||||
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}}, any>('/api/v1/article/statistics', {params:{type:'timelineWords'}})
|
|
||||||
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: {
|
title: {
|
||||||
text: '文章分类',
|
text: '文章分类',
|
||||||
x: 'center',
|
x: 'center',
|
||||||
@ -83,7 +51,7 @@ export default class Statistics extends Vue {
|
|||||||
data: []
|
data: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
publishDatesChartOption = {
|
const publishDatesChartOption: any = {
|
||||||
title: {
|
title: {
|
||||||
left: 'center',
|
left: 'center',
|
||||||
text: '文章发布时间',
|
text: '文章发布时间',
|
||||||
@ -149,7 +117,7 @@ export default class Statistics extends Vue {
|
|||||||
data: []
|
data: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timelineWordsChartOption: any = {
|
const timelineWordsChartOption: any = {
|
||||||
options: [],
|
options: [],
|
||||||
timeline: {
|
timeline: {
|
||||||
axisType: 'category',
|
axisType: 'category',
|
||||||
@ -177,8 +145,8 @@ export default class Statistics extends Vue {
|
|||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
axisPointer : { // 坐标轴指示器,坐标轴触发有效
|
axisPointer: {
|
||||||
type : 'shadow' // 默认为直线,可选为:'line' | 'shadow'
|
type: 'shadow'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
series: {
|
series: {
|
||||||
@ -188,7 +156,40 @@ export default class Statistics extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
onMounted(async () => {
|
||||||
|
categoriesChartLoading.value = true
|
||||||
|
publishDatesChartLoading.value = true
|
||||||
|
timelineWordsChartLoading.value = true
|
||||||
|
|
||||||
|
const articleData = await http.get<{params:{type:string}}, any>('/api/v1/article/statistics', {params: {type: 'normal'}})
|
||||||
|
categoriesChartOption.legend.data = articleData.categories.map((item: any) => item._id)
|
||||||
|
categoriesChartOption.series.data = articleData.categories.map((item: any) => {
|
||||||
|
return {name: item._id, value: item.cnt}
|
||||||
|
})
|
||||||
|
publishDatesChartOption.xAxis.data = articleData.publishDates.map((item: any) => item._id)
|
||||||
|
publishDatesChartOption.series.data = articleData.publishDates.map((item: any) => item.cnt)
|
||||||
|
|
||||||
|
const categoriesChartInstance = echarts.init(categoriesChart.value as HTMLElement)
|
||||||
|
categoriesChartInstance.setOption(categoriesChartOption)
|
||||||
|
const publishDatesChartInstance = echarts.init(publishDatesChart.value as HTMLElement)
|
||||||
|
publishDatesChartInstance.setOption(publishDatesChartOption)
|
||||||
|
categoriesChartLoading.value = false
|
||||||
|
publishDatesChartLoading.value = false
|
||||||
|
|
||||||
|
const timelineData = await http.get<{params:{type:string}}, any>('/api/v1/article/statistics', {params: {type: 'timelineWords'}})
|
||||||
|
timelineWordsChartOption.timeline.data = timelineData.timelineWords.map((item: any) => item._id)
|
||||||
|
timelineData.timelineWords.forEach((item: any) => {
|
||||||
|
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 timelineWordsChartInstance = echarts.init(timelineWordsChart.value as HTMLElement)
|
||||||
|
timelineWordsChartInstance.setOption(timelineWordsChartOption)
|
||||||
|
timelineWordsChartLoading.value = false
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.echarts-container {
|
.echarts-container {
|
||||||
|
|||||||
@ -55,95 +55,93 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import { ref, nextTick } from 'vue'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import http from '@/utils/http'
|
||||||
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'
|
import { VForm } from '@/types'
|
||||||
|
|
||||||
@Options({
|
const modalLoading = ref(false)
|
||||||
name: 'SystemConfig',
|
const loading = ref(false)
|
||||||
components: { SystemConfigAdd }
|
const search = ref<{name?: string}>({})
|
||||||
})
|
const systemConfigData = ref<SystemConfigModel[]>([])
|
||||||
export default class SystemConfig extends Vue {
|
const addModal = ref(false)
|
||||||
modalLoading: boolean = false
|
const modalTitle = ref('')
|
||||||
loading: boolean = false
|
const formData = ref<SystemConfigModel>({
|
||||||
search: {name?:string} = {}
|
|
||||||
systemConfigData: SystemConfigModel[] = []
|
|
||||||
addModal: boolean = false
|
|
||||||
modalTitle: string = ''
|
|
||||||
formData: SystemConfigModel = {
|
|
||||||
name: '',
|
name: '',
|
||||||
value: '',
|
value: '',
|
||||||
description: '',
|
description: '',
|
||||||
is_public: false
|
is_public: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const addForm = ref<InstanceType<typeof SystemConfigAdd>>()
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
search.value = {}
|
||||||
|
loadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
async function loadData() {
|
||||||
this.search = {}
|
loading.value = true
|
||||||
this.loadData()
|
systemConfigData.value = await http.get('/api/v1/system/config/list', {params: search.value})
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
function add() {
|
||||||
this.systemConfigData = await this.$http.get('/api/v1/system/config/list', {params:this.search})
|
formData.value = {
|
||||||
this.loading = false
|
|
||||||
}
|
|
||||||
add() {
|
|
||||||
// 清空表单
|
|
||||||
this.formData = {
|
|
||||||
name: '',
|
name: '',
|
||||||
value: '',
|
value: '',
|
||||||
description: '',
|
description: '',
|
||||||
is_public: false
|
is_public: false
|
||||||
}
|
}
|
||||||
this.modalTitle = '新增配置项'
|
modalTitle.value = '新增配置项'
|
||||||
this.addModal = true
|
addModal.value = true
|
||||||
}
|
}
|
||||||
update(row: SystemConfigModel) {
|
|
||||||
const formData = Object.assign({}, row)
|
function update(row: SystemConfigModel) {
|
||||||
formData.value = JSON.stringify(formData.value, null, ' ')
|
const data = Object.assign({}, row)
|
||||||
this.formData = formData
|
data.value = JSON.stringify(data.value, null, ' ')
|
||||||
this.modalTitle = '修改配置项'
|
formData.value = data
|
||||||
this.addModal = true
|
modalTitle.value = '修改配置项'
|
||||||
|
addModal.value = true
|
||||||
}
|
}
|
||||||
async save() {
|
|
||||||
((this.$refs.addForm as Vue).$refs.configForm as VForm).validate(async (valid: boolean) => {
|
async function save() {
|
||||||
|
addForm.value?.configForm?.validate(async (valid: boolean) => {
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
this.modalLoading = true
|
modalLoading.value = true
|
||||||
const data = await this.$http.post<SystemConfigModel, any>('/api/v1/system/config/save', this.formData)
|
const data = await http.post<SystemConfigModel, any>('/api/v1/system/config/save', formData.value)
|
||||||
this.modalLoading = false
|
modalLoading.value = false
|
||||||
this.addModal = false
|
addModal.value = false
|
||||||
ElMessage.success(data.message)
|
ElMessage.success(data.message)
|
||||||
this.loadData()
|
loadData()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
remove(row: SystemConfigModel) {
|
|
||||||
|
function remove(row: SystemConfigModel) {
|
||||||
ElMessageBox.confirm(`是否确认删除 ${row.name} 配置项?`, '确认删除', {type: 'warning'}).then(async () => {
|
ElMessageBox.confirm(`是否确认删除 ${row.name} 配置项?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
const data = await this.$http.delete<{params: {id: string}}, any>('/api/v1/system/config/delete', {params: {id: row._id}})
|
const data = await http.delete<{params: {id: string}}, any>('/api/v1/system/config/delete', {params: {id: row._id}})
|
||||||
if(data.status) {
|
if(data.status) {
|
||||||
ElMessage.success(data.message)
|
ElMessage.success(data.message)
|
||||||
this.loadData()
|
loadData()
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning(data.message)
|
ElMessage.warning(data.message)
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
created() {
|
|
||||||
this.loadData()
|
function clearValidate() {
|
||||||
}
|
nextTick(() => {
|
||||||
clearValidate() {
|
addForm.value?.configForm?.clearValidate()
|
||||||
this.$nextTick(() => {
|
|
||||||
((this.$refs.addForm as Vue).$refs.configForm as VForm).clearValidate()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 日期时间格式化
|
function datetimeFormat(dateStr: string) {
|
||||||
* @param dateStr 日期时间
|
|
||||||
*/
|
|
||||||
datetimeFormat(dateStr: string) {
|
|
||||||
return dateStr ? moment(dateStr).format('YYYY-MM-DD HH:mm:ss') : null
|
return dateStr ? moment(dateStr).format('YYYY-MM-DD HH:mm:ss') : null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
loadData()
|
||||||
</script>
|
</script>
|
||||||
@ -19,24 +19,23 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options, Vue } from 'vue-class-component'
|
import { computed, ref } from 'vue'
|
||||||
|
import http from '@/utils/http'
|
||||||
import { SystemConfigModel } from '@/model/system/system-config'
|
import { SystemConfigModel } from '@/model/system/system-config'
|
||||||
|
import { VForm } from '@/types'
|
||||||
|
|
||||||
@Options({
|
const props = defineProps<{
|
||||||
name: 'SystemConfigAdd',
|
formData: SystemConfigModel
|
||||||
props: {
|
}>()
|
||||||
formData: Object
|
|
||||||
}
|
const configForm = ref<VForm>()
|
||||||
})
|
|
||||||
export default class SystenConfigAdd extends Vue {
|
const ruleValidate = computed(() => ({
|
||||||
formData!: SystemConfigModel
|
|
||||||
get ruleValidate() {
|
|
||||||
return {
|
|
||||||
name: [
|
name: [
|
||||||
{ required: true, message: '请输入配置项名称', trigger: 'blur' },
|
{ required: true, message: '请输入配置项名称', trigger: 'blur' },
|
||||||
{ validator: (rule: object, value: string, callback: Function) => {
|
{ validator: (rule: object, value: string, callback: Function) => {
|
||||||
this.$http.get<any, any>('/api/v1/system/config/exists', {params: {name: value, id: this.formData._id}}).then(data => {
|
http.get<any, any>('/api/v1/system/config/exists', {params: {name: value, id: props.formData._id}}).then(data => {
|
||||||
if(data.data.exists) {
|
if(data.data.exists) {
|
||||||
callback(new Error('配置项名称已存在'))
|
callback(new Error('配置项名称已存在'))
|
||||||
} else {
|
} else {
|
||||||
@ -58,7 +57,7 @@ export default class SystenConfigAdd extends Vue {
|
|||||||
}, trigger: 'blur'
|
}, trigger: 'blur'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}))
|
||||||
}
|
|
||||||
}
|
defineExpose({ configForm })
|
||||||
</script>
|
</script>
|
||||||
@ -40,8 +40,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -91,122 +91,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options } from 'vue-class-component'
|
import { ref, reactive, nextTick } from 'vue'
|
||||||
import BaseList from '@/model/baselist'
|
import { useStore } from 'vuex'
|
||||||
|
import { useBaseList } from '@/model/baselist'
|
||||||
import { Page } from '@/model/common.dto'
|
import { Page } from '@/model/common.dto'
|
||||||
|
import http from '@/utils/http'
|
||||||
import { SystemRoleModel } from '@/model/system/system-role'
|
import { SystemRoleModel } from '@/model/system/system-role'
|
||||||
import { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption, ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { VForm } from '@/types'
|
import { VForm } from '@/types'
|
||||||
|
|
||||||
@Options({
|
const store = useStore()
|
||||||
name: 'SystemRole',
|
|
||||||
components: { ElButton, ElForm, ElFormItem, ElInput, ElTable, ElTableColumn, ElTag, ElPagination, ElDialog, ElSelect, ElOption }
|
|
||||||
})
|
|
||||||
export default class SystemRole extends BaseList<SystemRolePage> {
|
|
||||||
ruleValidate = {
|
|
||||||
name: [
|
|
||||||
{ required: true, message: '请输入角色名称', trigger: 'blur' }
|
|
||||||
],
|
|
||||||
}
|
|
||||||
systemRoleData: SystemRoleModel[] = []
|
|
||||||
search = new SystemRolePage()
|
|
||||||
addModal: boolean = false
|
|
||||||
modalTitle: string | null = null
|
|
||||||
uri: {
|
|
||||||
include: string | null,
|
|
||||||
exclude: string | null
|
|
||||||
} = {
|
|
||||||
include: null,
|
|
||||||
exclude: null
|
|
||||||
}
|
|
||||||
formData: SystemRoleModel = {
|
|
||||||
_id: null,
|
|
||||||
name: null,
|
|
||||||
description: null,
|
|
||||||
methods: [],
|
|
||||||
include_uri: [],
|
|
||||||
exclude_uri: []
|
|
||||||
}
|
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
|
||||||
const data = await this.$http.get<{params: SystemRolePage}, any>('/api/v1/system/role/list', {params:this.search})
|
|
||||||
this.loading = false
|
|
||||||
this.total = data.total
|
|
||||||
this.systemRoleData = data.data
|
|
||||||
}
|
|
||||||
created() {
|
|
||||||
this.loadData()
|
|
||||||
}
|
|
||||||
add() {
|
|
||||||
// 清空表单
|
|
||||||
this.uri.include = null
|
|
||||||
this.uri.exclude = null
|
|
||||||
this.formData = {
|
|
||||||
_id: null,
|
|
||||||
name: null,
|
|
||||||
description: null,
|
|
||||||
methods: [],
|
|
||||||
include_uri: [],
|
|
||||||
exclude_uri: []
|
|
||||||
}
|
|
||||||
this.modalTitle = '新增角色'
|
|
||||||
this.addModal = true
|
|
||||||
this.clearValidate()
|
|
||||||
}
|
|
||||||
addUri(fieldName: 'include_uri' | 'exclude_uri', uri: string | null) {
|
|
||||||
if(!uri) return
|
|
||||||
if(this.formData[fieldName].indexOf(uri) === -1) {
|
|
||||||
this.formData[fieldName].push(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
removeUri(fieldName: 'include_uri' | 'exclude_uri', uri: string) {
|
|
||||||
let index = this.formData[fieldName].indexOf(uri)
|
|
||||||
if(index !== -1) {
|
|
||||||
this.formData[fieldName].splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
update(row: SystemRoleModel) {
|
|
||||||
this.uri.include = null
|
|
||||||
this.uri.exclude = null
|
|
||||||
this.formData._id = row._id
|
|
||||||
this.formData.name = row.name
|
|
||||||
this.formData.description = row.description
|
|
||||||
this.formData.methods = row.methods
|
|
||||||
this.formData.include_uri = row.include_uri
|
|
||||||
this.formData.exclude_uri = row.exclude_uri
|
|
||||||
this.modalTitle = '修改角色'
|
|
||||||
this.addModal = true
|
|
||||||
this.clearValidate()
|
|
||||||
}
|
|
||||||
remove(row: SystemRoleModel) {
|
|
||||||
ElMessageBox.confirm(`是否确认删除 ${row.name} 角色?`, '确认删除', {type: 'warning'}).then(async () => {
|
|
||||||
const data = await this.$http.delete<{params: {id: string}}, any>('/api/v1/system/role/delete', {params: {id: row._id}})
|
|
||||||
if(data.status) {
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(data.message)
|
|
||||||
}
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
async save() {
|
|
||||||
(this.$refs.roleForm as VForm).validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return
|
|
||||||
this.modalLoading = true
|
|
||||||
const data = await this.$http.post<SystemRoleModel, any>('/api/v1/system/role/save', this.formData)
|
|
||||||
this.modalLoading = false
|
|
||||||
this.addModal = false
|
|
||||||
ElMessage.success(data.message)
|
|
||||||
this.loadData()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
clearValidate() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
(this.$refs.roleForm as VForm).clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SystemRolePage extends Page {
|
class SystemRolePage extends Page {
|
||||||
name?: string
|
name?: string
|
||||||
@ -215,4 +110,118 @@ class SystemRolePage extends Page {
|
|||||||
this.name = undefined
|
this.name = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { loading, modalLoading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new SystemRolePage())
|
||||||
|
|
||||||
|
const ruleValidate = {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入角色名称', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const systemRoleData = ref<SystemRoleModel[]>([])
|
||||||
|
const addModal = ref(false)
|
||||||
|
const modalTitle = ref<string | null>(null)
|
||||||
|
const uri = reactive<{
|
||||||
|
include: string | null,
|
||||||
|
exclude: string | null
|
||||||
|
}>({
|
||||||
|
include: null,
|
||||||
|
exclude: null
|
||||||
|
})
|
||||||
|
const formData = reactive<SystemRoleModel>({
|
||||||
|
_id: null,
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
methods: [],
|
||||||
|
include_uri: [],
|
||||||
|
exclude_uri: []
|
||||||
|
})
|
||||||
|
const roleForm = ref<VForm>()
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<{params: SystemRolePage}, any>('/api/v1/system/role/list', {params: search})
|
||||||
|
loading.value = false
|
||||||
|
total.value = data.total
|
||||||
|
systemRoleData.value = data.data
|
||||||
|
}
|
||||||
|
setLoadData(loadData)
|
||||||
|
|
||||||
|
function add() {
|
||||||
|
uri.include = null
|
||||||
|
uri.exclude = null
|
||||||
|
Object.assign(formData, {
|
||||||
|
_id: null,
|
||||||
|
name: null,
|
||||||
|
description: null,
|
||||||
|
methods: [],
|
||||||
|
include_uri: [],
|
||||||
|
exclude_uri: []
|
||||||
|
})
|
||||||
|
modalTitle.value = '新增角色'
|
||||||
|
addModal.value = true
|
||||||
|
clearValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addUri(fieldName: 'include_uri' | 'exclude_uri', uriValue: string | null) {
|
||||||
|
if(!uriValue) return
|
||||||
|
if(formData[fieldName].indexOf(uriValue) === -1) {
|
||||||
|
formData[fieldName].push(uriValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeUri(fieldName: 'include_uri' | 'exclude_uri', uriValue: string) {
|
||||||
|
let index = formData[fieldName].indexOf(uriValue)
|
||||||
|
if(index !== -1) {
|
||||||
|
formData[fieldName].splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(row: SystemRoleModel) {
|
||||||
|
uri.include = null
|
||||||
|
uri.exclude = null
|
||||||
|
Object.assign(formData, {
|
||||||
|
_id: row._id,
|
||||||
|
name: row.name,
|
||||||
|
description: row.description,
|
||||||
|
methods: row.methods,
|
||||||
|
include_uri: row.include_uri,
|
||||||
|
exclude_uri: row.exclude_uri
|
||||||
|
})
|
||||||
|
modalTitle.value = '修改角色'
|
||||||
|
addModal.value = true
|
||||||
|
clearValidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(row: SystemRoleModel) {
|
||||||
|
ElMessageBox.confirm(`是否确认删除 ${row.name} 角色?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
|
const data = await http.delete<{params: {id: string}}, any>('/api/v1/system/role/delete', {params: {id: row._id}})
|
||||||
|
if(data.status) {
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(data.message)
|
||||||
|
}
|
||||||
|
}).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function save() {
|
||||||
|
roleForm.value?.validate(async (valid: boolean) => {
|
||||||
|
if (!valid) return
|
||||||
|
modalLoading.value = true
|
||||||
|
const data = await http.post<SystemRoleModel, any>('/api/v1/system/role/save', formData)
|
||||||
|
modalLoading.value = false
|
||||||
|
addModal.value = false
|
||||||
|
ElMessage.success(data.message)
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearValidate() {
|
||||||
|
nextTick(() => {
|
||||||
|
roleForm.value?.clearValidate()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData()
|
||||||
</script>
|
</script>
|
||||||
@ -36,8 +36,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<el-pagination background
|
<el-pagination background
|
||||||
:page-sizes="$store.state.pageSizeOpts"
|
:page-sizes="store.state.pageSizeOpts"
|
||||||
:layout="$store.state.pageLayout"
|
:layout="store.state.pageLayout"
|
||||||
:current-page="search.pageNum"
|
:current-page="search.pageNum"
|
||||||
:total="total"
|
:total="total"
|
||||||
@size-change="pageSizeChange"
|
@size-change="pageSizeChange"
|
||||||
@ -70,25 +70,47 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Options } from 'vue-class-component'
|
import { ref, reactive, computed, nextTick } from 'vue'
|
||||||
import BaseList from '@/model/baselist'
|
import { useStore } from 'vuex'
|
||||||
|
import { useBaseList } from '@/model/baselist'
|
||||||
import { Page } from '@/model/common.dto'
|
import { Page } from '@/model/common.dto'
|
||||||
|
import http from '@/utils/http'
|
||||||
import { SystemUserModel } from '@/model/system/system-user'
|
import { SystemUserModel } from '@/model/system/system-user'
|
||||||
import { SystemRoleModel } from '@/model/system/system-role'
|
import { SystemRoleModel } from '@/model/system/system-role'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { VForm } from '@/types'
|
import { VForm } from '@/types'
|
||||||
|
|
||||||
@Options({
|
const store = useStore()
|
||||||
name: 'SystemUser'
|
|
||||||
|
class SystemUserPage extends Page {
|
||||||
|
username?: string
|
||||||
|
reset() {
|
||||||
|
super.reset()
|
||||||
|
this.username = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { loading, modalLoading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new SystemUserPage())
|
||||||
|
|
||||||
|
const systemUserData = ref<SystemUserModel[]>([])
|
||||||
|
const roles = ref<SystemRoleModel[]>([])
|
||||||
|
const addModal = ref(false)
|
||||||
|
const modalTitle = ref<string | null>(null)
|
||||||
|
const formData = reactive<SystemUserModel>({
|
||||||
|
_id: null,
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
realname: null,
|
||||||
|
role_ids: []
|
||||||
})
|
})
|
||||||
export default class SystemUser extends BaseList<SystemUserPage> {
|
const userForm = ref<VForm>()
|
||||||
get ruleValidate() {
|
|
||||||
return {
|
const ruleValidate = computed(() => ({
|
||||||
username: [
|
username: [
|
||||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||||
{ validator: async (rule: object, value: string, callback: Function) => {
|
{ validator: async (rule: object, value: string, callback: Function) => {
|
||||||
const data = await this.$http.get<any, any>('/api/v1/system/user/exists', {params: {username: value, id: this.formData._id}})
|
const data = await http.get<any, any>('/api/v1/system/user/exists', {params: {username: value, id: formData._id}})
|
||||||
if(data.data.exists) {
|
if(data.data.exists) {
|
||||||
callback(new Error('用户名已存在'))
|
callback(new Error('用户名已存在'))
|
||||||
} else {
|
} else {
|
||||||
@ -102,89 +124,74 @@ export default class SystemUser extends BaseList<SystemUserPage> {
|
|||||||
{ min: 8, max: 16, message: '密码长度8~16位', trigger: 'blur' },
|
{ min: 8, max: 16, message: '密码长度8~16位', trigger: 'blur' },
|
||||||
{ pattern: /^(?![\d]+$)(?![a-zA-Z]+$)(?![-=+_.,]+$)[\da-zA-Z-=+_.,]{8,16}$/, message: '密码由字母、数字、特殊字符中的任意两种组成', trigger: 'blur' }
|
{ pattern: /^(?![\d]+$)(?![a-zA-Z]+$)(?![-=+_.,]+$)[\da-zA-Z-=+_.,]{8,16}$/, message: '密码由字母、数字、特殊字符中的任意两种组成', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
|
}))
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
loading.value = true
|
||||||
|
const data = await http.get<{params: SystemUserPage}, any>('/api/v1/system/user/list', {params: search})
|
||||||
|
loading.value = false
|
||||||
|
total.value = data.total
|
||||||
|
systemUserData.value = data.data
|
||||||
}
|
}
|
||||||
}
|
setLoadData(loadData)
|
||||||
search = new SystemUserPage()
|
|
||||||
systemUserData: SystemUserModel[] = []
|
function add() {
|
||||||
roles: SystemRoleModel[] = []
|
Object.assign(formData, {
|
||||||
addModal: boolean = false
|
|
||||||
modalTitle: string | null = null
|
|
||||||
formData: SystemUserModel = {
|
|
||||||
_id: null,
|
_id: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null,
|
password: null,
|
||||||
realname: null,
|
realname: null,
|
||||||
role_ids: []
|
role_ids: []
|
||||||
|
})
|
||||||
|
modalTitle.value = '新增用户'
|
||||||
|
addModal.value = true
|
||||||
|
clearValidate()
|
||||||
}
|
}
|
||||||
async loadData() {
|
|
||||||
this.loading = true
|
function update(row: SystemUserModel) {
|
||||||
const data = await this.$http.get<{params: SystemUserPage}, any>('/api/v1/system/user/list', {params:this.search})
|
Object.assign(formData, {
|
||||||
this.loading = false
|
_id: row._id,
|
||||||
this.total = data.total
|
username: row.username,
|
||||||
this.systemUserData = data.data
|
realname: row.realname,
|
||||||
|
role_ids: row.role_ids
|
||||||
|
})
|
||||||
|
modalTitle.value = '修改用户'
|
||||||
|
addModal.value = true
|
||||||
|
clearValidate()
|
||||||
}
|
}
|
||||||
add() {
|
|
||||||
// 清空表单
|
async function save() {
|
||||||
this.formData = {
|
userForm.value?.validate(async (valid: boolean) => {
|
||||||
_id: null,
|
|
||||||
username: null,
|
|
||||||
password: null,
|
|
||||||
realname: null,
|
|
||||||
role_ids: []
|
|
||||||
}
|
|
||||||
this.modalTitle = '新增用户'
|
|
||||||
this.addModal = true
|
|
||||||
this.clearValidate()
|
|
||||||
}
|
|
||||||
update(row: SystemUserModel) {
|
|
||||||
this.formData._id = row._id
|
|
||||||
this.formData.username = row.username
|
|
||||||
this.formData.realname = row.realname
|
|
||||||
this.formData.role_ids = row.role_ids
|
|
||||||
this.modalTitle = '修改用户'
|
|
||||||
this.addModal = true
|
|
||||||
this.clearValidate()
|
|
||||||
}
|
|
||||||
async save() {
|
|
||||||
(this.$refs.userForm as VForm).validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return
|
if (!valid) return
|
||||||
this.modalLoading = true
|
modalLoading.value = true
|
||||||
const data = await this.$http.post<SystemUserModel, any>('/api/v1/system/user/save', this.formData)
|
const data = await http.post<SystemUserModel, any>('/api/v1/system/user/save', formData)
|
||||||
this.modalLoading = false
|
modalLoading.value = false
|
||||||
this.addModal = false
|
addModal.value = false
|
||||||
ElMessage.success(data.message)
|
ElMessage.success(data.message)
|
||||||
this.loadData()
|
loadData()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
remove(row: SystemUserModel) {
|
|
||||||
|
function remove(row: SystemUserModel) {
|
||||||
ElMessageBox.confirm(`是否确认删除 ${row.username} 用户?`, '确认删除', {type: 'warning'}).then(async () => {
|
ElMessageBox.confirm(`是否确认删除 ${row.username} 用户?`, '确认删除', {type: 'warning'}).then(async () => {
|
||||||
const data = await this.$http.delete<{params: {id: string}}, any>('/api/v1/system/user/delete', {params: {id: row._id}})
|
const data = await http.delete<{params: {id: string}}, any>('/api/v1/system/user/delete', {params: {id: row._id}})
|
||||||
if(data.status) {
|
if(data.status) {
|
||||||
ElMessage.success(data.message)
|
ElMessage.success(data.message)
|
||||||
this.loadData()
|
loadData()
|
||||||
} else {
|
} else {
|
||||||
ElMessage.warning(data.message)
|
ElMessage.warning(data.message)
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {})
|
||||||
}
|
}
|
||||||
created() {
|
|
||||||
this.loadData()
|
function clearValidate() {
|
||||||
this.$http.get<never, any>('/api/v1/system/role/listAll').then(data => {
|
nextTick(() => {
|
||||||
this.roles = data
|
userForm.value?.clearValidate()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
clearValidate() {
|
|
||||||
this.$nextTick(() => {
|
|
||||||
(this.$refs.userForm as VForm).clearValidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SystemUserPage extends Page {
|
loadData()
|
||||||
username?: string
|
http.get<never, any>('/api/v1/system/role/listAll').then(data => {
|
||||||
reset() {
|
roles.value = data
|
||||||
super.reset()
|
})
|
||||||
this.username = undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
Loading…
x
Reference in New Issue
Block a user