feat: 小程序移除管理后台入口,新增admin-web前端项目

将管理后台功能从微信小程序中剥离,独立为Vue.js前端项目admin-web

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
刘正航
2026-05-14 13:49:07 +08:00
parent f342fdc9b4
commit 49c946dd55
39 changed files with 10760 additions and 257 deletions

View File

@@ -0,0 +1,58 @@
<template>
<transition name="fade">
<div v-if="visible" class="modal-mask" @click.self="handleCancel">
<div class="modal-card">
<div class="modal-title">{{ title }}</div>
<div class="modal-content">{{ content }}</div>
<div class="btn-row">
<button v-if="showCancel" class="btn btn-ghost" type="button" @click="handleCancel">{{ cancelText }}</button>
<button class="btn btn-primary" type="button" @click="handleConfirm">{{ confirmText }}</button>
</div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'ConfirmHost',
data() {
return {
visible: false,
title: '提示',
content: '',
confirmText: '确定',
cancelText: '取消',
showCancel: true,
onConfirm: null,
onCancel: null
}
},
methods: {
open(opts) {
this.title = opts.title || '提示'
this.content = opts.content || ''
this.confirmText = opts.confirmText || '确定'
this.cancelText = opts.cancelText || '取消'
this.showCancel = opts.showCancel !== false
this.onConfirm = opts.onConfirm
this.onCancel = opts.onCancel
this.visible = true
},
handleConfirm() {
this.visible = false
const fn = this.onConfirm
this.onConfirm = null
this.onCancel = null
if (typeof fn === 'function') fn()
},
handleCancel() {
this.visible = false
const fn = this.onCancel
this.onConfirm = null
this.onCancel = null
if (typeof fn === 'function') fn()
}
}
}
</script>

View File

@@ -0,0 +1,57 @@
<template>
<transition name="fade">
<div v-if="visible" class="preview-mask" @click.self="close">
<div class="preview-stage">
<div class="preview-close" @click="close">×</div>
<div v-if="urls.length > 1" class="preview-nav preview-prev" @click="prev"></div>
<img class="preview-img" :src="currentUrl" alt="preview" />
<div v-if="urls.length > 1" class="preview-nav preview-next" @click="next"></div>
</div>
</div>
</transition>
</template>
<script>
export default {
name: 'ImagePreviewHost',
data() {
return {
visible: false,
urls: [],
index: 0
}
},
computed: {
currentUrl() {
return this.urls[this.index] || ''
}
},
methods: {
open({ urls, current }) {
this.urls = Array.isArray(urls) ? urls.slice() : [urls]
const idx = this.urls.indexOf(current)
this.index = idx >= 0 ? idx : 0
this.visible = true
document.addEventListener('keydown', this.handleKey)
},
close() {
this.visible = false
document.removeEventListener('keydown', this.handleKey)
},
prev() {
this.index = (this.index - 1 + this.urls.length) % this.urls.length
},
next() {
this.index = (this.index + 1) % this.urls.length
},
handleKey(e) {
if (e.key === 'Escape') this.close()
else if (e.key === 'ArrowLeft') this.prev()
else if (e.key === 'ArrowRight') this.next()
}
},
beforeDestroy() {
document.removeEventListener('keydown', this.handleKey)
}
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<div class="toast-host">
<div
v-for="item in list"
:key="item.id"
class="toast-item"
:class="{ 'is-error': item.type === 'error', 'is-success': item.type === 'success' }"
>
{{ item.message }}
</div>
</div>
</template>
<script>
let uid = 0
export default {
name: 'ToastHost',
data() {
return {
list: []
}
},
methods: {
push({ message, type = 'info', duration = 2000 }) {
const id = ++uid
this.list.push({ id, message, type })
setTimeout(() => {
this.list = this.list.filter((item) => item.id !== id)
}, duration)
}
}
}
</script>