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:
2026-03-19 23:05:59 +08:00
parent 1ffd56efc2
commit 96b032d262
22 changed files with 13830 additions and 2268 deletions
+65 -65
View File
@@ -47,8 +47,8 @@
</div>
<div class="page-container">
<el-pagination background
:page-sizes="$store.state.pageSizeOpts"
:layout="$store.state.pageLayout"
:page-sizes="store.state.pageSizeOpts"
:layout="store.state.pageLayout"
:current-page="search.pageNum"
:total="total"
@size-change="pageSizeChange"
@@ -66,73 +66,15 @@
</el-dialog>
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useStore } from 'vuex'
import HitokotoAdd from './HitokotoAdd.vue'
import { Options, Vue } from 'vue-class-component'
import BaseList from '@/model/baselist'
import { useBaseList } from '@/model/baselist'
import { Page } from '@/model/common.dto'
import HitokotoModel from '@/model/api/hitokoto'
import { ElMessage, ElMessageBox } from 'element-plus'
import { VForm } from '@/types'
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
}
}
import http from '@/utils/http'
class HitokotoPage extends Page {
content?: string
@@ -145,4 +87,62 @@ class HitokotoPage extends Page {
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>
+19 -20
View File
@@ -18,26 +18,25 @@
</el-form>
</div>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component'
<script setup lang="ts">
import { ref } from 'vue'
import type { VForm } from '@/types'
@Options({
name: 'SystemConfigAdd',
props: {
typeList: Array,
formData: Object
}
})
export default class HitokotoAdd extends Vue {
typeList!: {label: string, value: string}[]
formData!: {[propName:string]: string | null}
ruleValidate = {
hitokoto: [
{ required: true, message: '请输入内容', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择类型', trigger: 'blur' }
],
}
defineProps<{
typeList: {label: string, value: string}[]
formData: {[propName: string]: string | null}
}>()
const hitokotoForm = ref<VForm>()
const ruleValidate = {
hitokoto: [
{ required: true, message: '请输入内容', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择类型', trigger: 'blur' }
],
}
defineExpose({ hitokotoForm })
</script>
+196 -200
View File
@@ -79,8 +79,8 @@
</div>
<div class="page-container">
<el-pagination background
:page-sizes="$store.state.pageSizeOpts"
:layout="$store.state.pageLayout"
:page-sizes="store.state.pageSizeOpts"
:layout="store.state.pageLayout"
:current-page="search.pageNum"
:total="total"
@size-change="pageSizeChange"
@@ -118,7 +118,7 @@
action="/api/v2/music/upload"
name="file"
accept=".mp3,.flac"
:headers="{token: $store.state.loginInfo.token}"
:headers="{token: store.state.loginInfo.token}"
:on-success="uploadSuccess"
:on-error="uploadError"
:auto-upload="false"
@@ -148,207 +148,17 @@
</div>
</template>
<script lang="ts">
import { Options } from 'vue-class-component'
import BaseList from '@/model/baselist'
<script setup lang="ts">
import { ref, reactive } from 'vue'
import { useStore } from 'vuex'
import { useBaseList } from '@/model/baselist'
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 APlayer from './aplayer/vue-aplayer.vue'
import prettyBytes from 'pretty-bytes'
import { VForm } from '@/types'
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)
}
})
}
}
import type { VForm } from '@/types'
import http from '@/utils/http'
class MusicPage extends Page {
name?: string
@@ -367,4 +177,190 @@ class MusicPage extends Page {
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>
+77 -75
View File
@@ -34,7 +34,7 @@
action="/api/v2/photoWall/upload"
accept="image/jpeg,image/png"
name="image"
:headers="{token: $store.state.loginInfo.token}"
:headers="{token: store.state.loginInfo.token}"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
:on-error="uploadError"
@@ -67,8 +67,8 @@
</div>
<div class="page-container">
<el-pagination background
:page-sizes="$store.state.pageSizeOpts"
:layout="$store.state.pageLayout"
:page-sizes="store.state.pageSizeOpts"
:layout="store.state.pageLayout"
:current-page="search.pageNum"
:total="total"
@size-change="pageSizeChange"
@@ -77,81 +77,14 @@
</div>
</div>
</template>
<script lang="ts">
import { Options } from 'vue-class-component'
<script setup lang="ts">
import { ref, h } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus'
import { MsgResult, Page } from '@/model/common.dto'
import BaseList from '@/model/baselist'
import { useBaseList } from '@/model/baselist'
import PhotoWallModel from '@/model/api/photowall'
import { h } from 'vue'
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()
}
}
import http from '@/utils/http'
class PhotoWallPage extends Page {
name?: string
@@ -168,4 +101,73 @@ class PhotoWallPage extends Page {
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>
+99 -97
View File
@@ -9,7 +9,7 @@
action="/api/source-image/upload"
accept="image/jpeg,image/png,image/svg+xml,image/x-icon"
name="image"
:headers="{token: $store.state.loginInfo.token}"
:headers="{token: store.state.loginInfo.token}"
:before-upload="beforeUpload"
:on-success="uploadSuccess"
:on-error="uploadError"
@@ -50,8 +50,8 @@
</div>
<div class="page-container">
<el-pagination background
:page-sizes="$store.state.pageSizeOpts"
:layout="$store.state.pageLayout"
:page-sizes="store.state.pageSizeOpts"
:layout="store.state.pageLayout"
:current-page="search.pageNum"
:total="total"
@size-change="pageSizeChange"
@@ -74,105 +74,107 @@
</el-dialog>
</div>
</template>
<script lang="ts">
import { Options } from 'vue-class-component'
<script setup lang="ts">
import { ref, computed, h } from 'vue'
import { useStore } from 'vuex'
import { ElMessage, ElMessageBox } from 'element-plus'
import prettyBytes from 'pretty-bytes'
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 { h } from 'vue'
import http from '@/utils/http'
const store = useStore()
const { loading, total, search, setLoadData, loadDataBase, reset, pageChange, pageSizeChange, datetimeFormat } = useBaseList(new Page())
const allowUploadExt = ['jpg', 'jpeg', 'png', 'svg', 'ico']
const sourceImageData = ref<SourceImageModel[]>([])
const curModifyLabels = ref<string[]>([])
const labelList = ref<string[]>([])
const curId = ref<string | null>(null)
const modifyModal = ref(false)
const isUploading = ref(false)
function renderFunc(h: Function, option: any) {
return h('span', null, option.label)
}
const labels = computed(() => {
return labelList.value.map(item => {
return { key: item, label: item }
})
})
let selectedData: string[] = []
@Options({
name: 'SourceImage',
})
export default class SourceImage extends BaseList<Page> {
prettyBytes = prettyBytes
search = new Page()
allowUploadExt = ['jpg','jpeg','png','svg','ico']
sourceImageData: SourceImageModel[] = []
curModifyLabels: string[] = []
labelList: string[] = []
curId: string | null = null
modifyModal: boolean = false
isUploading: boolean = false
renderFunc(h: Function, option: any) {
return h('span', null, option.label)
}
get labels() {
return this.labelList.map(item => {
return { key: item, label: item }
})
}
async loadData(): Promise<void> {
this.loading = true
const data = await this.$http.get<Page, any>('/api/v1/source-image/list', {params:this.search})
selectedData = []
this.loading = false
this.total = data.total
this.sourceImageData = data.data
}
deleteAll(): void {
if(!selectedData.length) {
ElMessage.warning('请选择要删除的数据')
return
}
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
await this.$http.delete('/api/v1/source-image/delete', {params:{_ids: selectedData}})
ElMessage.success('删除成功')
this.loadData()
}).catch(() => {})
}
dataSelect(selection: SourceImageModel[]): void {
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): void {
if(response.status) {
ElMessage.success(response.message)
this.loadData()
} else {
ElMessage.warning(response.message)
}
this.isUploading = false
}
uploadError(error: Error): void {
this.isUploading = false
ElMessage.error(error.message)
}
preview(row: SourceImageModel): void {
ElMessageBox({
title: '图片预览',
message: h('img', { style: `width:500px`, src: `/api/v1/common/randomBg?id=${row._id}` }, ''),
showCancelButton: false,
confirmButtonText: '关闭',
customStyle: {width: '530px', maxWidth: 'unset'}
}).catch(() => {})
}
modifyTags(item: SourceImageModel): void {
this.curModifyLabels.length = 0
if(item.label) {
this.curModifyLabels.push(...item.label)
}
this.curId = item._id
this.modifyModal = true
}
async tarnsferChange(newTargetKeys: string[], direction: 'right' | 'left', moveKeys: string[]) {
await this.$http.post('/api/v1/source-image/updateLabel', {id: this.curId, labels: newTargetKeys})
}
created() {
this.$http.get<never, any>('/api/v1/common/config/image_label').then(data => {
this.labelList.push(...data)
this.loadData()
})
}
async function loadData(): Promise<void> {
loading.value = true
const data = await http.get<Page, any>('/api/v1/source-image/list', {params: search})
selectedData = []
loading.value = false
total.value = data.total
sourceImageData.value = data.data
}
setLoadData(loadData)
function deleteAll(): void {
if (!selectedData.length) {
ElMessage.warning('请选择要删除的数据')
return
}
ElMessageBox.confirm(`是否确认删除选中的${selectedData.length}条数据?`, '确认删除', {type: 'warning'}).then(async () => {
await http.delete('/api/v1/source-image/delete', {params: {_ids: selectedData}})
ElMessage.success('删除成功')
loadData()
}).catch(() => {})
}
function dataSelect(selection: SourceImageModel[]): void {
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): void {
if (response.status) {
ElMessage.success(response.message)
loadData()
} else {
ElMessage.warning(response.message)
}
isUploading.value = false
}
function uploadError(error: Error): void {
isUploading.value = false
ElMessage.error(error.message)
}
function preview(row: SourceImageModel): void {
ElMessageBox({
title: '图片预览',
message: h('img', { style: `width:500px`, src: `/api/v1/common/randomBg?id=${row._id}` }, ''),
showCancelButton: false,
confirmButtonText: '关闭',
customStyle: {width: '530px', maxWidth: 'unset'}
}).catch(() => {})
}
function modifyTags(item: SourceImageModel): void {
curModifyLabels.value.length = 0
if (item.label) {
curModifyLabels.value.push(...item.label)
}
curId.value = item._id
modifyModal.value = true
}
async function tarnsferChange(newTargetKeys: string[], direction: 'right' | 'left', moveKeys: string[]) {
await http.post('/api/v1/source-image/updateLabel', {id: curId.value, labels: newTargetKeys})
}
// created
http.get<never, any>('/api/v1/common/config/image_label').then(data => {
labelList.value.push(...data)
loadData()
})
</script>