admin的数据库文件补全,修复uvue中的数据库接入bug

This commit is contained in:
comlibmb
2026-02-25 10:02:50 +08:00
parent 5d00e3d74e
commit dc8f899610
40 changed files with 1629 additions and 625 deletions

View File

@@ -207,27 +207,42 @@ function handleEdit(item: ArticleCategory) {
async function toggleStatus(item: ArticleCategory) {
const targetStatus = item.status === 1 ? 0 : 1
const ok = await setArticleCategoryStatus(item.id, targetStatus)
if (ok) {
item.status = targetStatus
uni.showToast({ title: '状态已更新' })
} else {
uni.showToast({ title: '操作失败', icon: 'none' })
try {
const resId = await saveArticleCategory(
item.id,
item.name,
item.icon,
item.sort,
targetStatus
)
if (resId != null) {
item.status = targetStatus
uni.showToast({ title: '状态已更新' })
}
} catch (e: any) {
const errMsg = e?.message || '操作失败'
uni.showToast({ title: errMsg, icon: 'none' })
}
}
async function handleDelete(item: ArticleCategory) {
uni.showModal({
title: '提示',
content: `确定要删除分类 "${item.name}" 吗?`,
title: '删除确认',
content: `确定要删除分类 "${item.name}" 吗?\n\n⚠ 警告:该操作将同时删除该分类下的所有文章!`,
confirmText: '确认删除',
confirmColor: '#ed4014',
success: async (res) => {
if (res.confirm) {
const ok = await deleteArticleCategory(item.id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadData()
} else {
uni.showToast({ title: '删除失败', icon: 'none' })
try {
const ok = await deleteArticleCategory(item.id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadData()
}
} catch (e: any) {
// 显示后端抛出的具体错误信息(如权限不足)
const errMsg = e?.message || '删除失败'
uni.showToast({ title: errMsg, icon: 'none', duration: 3000 })
}
}
}

View File

@@ -1,4 +1,4 @@
<template>
<template>
<view class="admin-main">
<!-- 头部搜索和操作 -->
<view class="search-card">
@@ -292,17 +292,24 @@ item.status = !item.status
}
async function deleteItem(item: CateItem) {
uni.showModal({
title: '提示',
content: '确定删除分类吗?',
uni.showModal({
title: '删除确认',
content: `确定删除分类 "${item.name}" 吗?\n\n⚠ 警告:该操作将同时删除该分类下的所有子分类及关联商品!`,
confirmText: '确认删除',
confirmColor: '#ff4d4f',
success: async (res) => {
if (res.confirm) {
await deleteAdminCategory(item.id)
uni.showToast({ title: '删除成功', icon: 'success' })
loadList()
}
}
})
if (res.confirm) {
try {
await deleteAdminCategory(item.id)
uni.showToast({ title: '删除成功', icon: 'success' })
loadList()
} catch (e: any) {
const errMsg = e?.message || '删除失败'
uni.showToast({ title: errMsg, icon: 'none', duration: 3000 })
}
}
}
})
}
</script>

View File

@@ -187,14 +187,21 @@ async function handleSave() {
async function onDelete(item : AdminRole) {
uni.showModal({
title: '提示',
content: `确定要删除角色 "${item.name}" 吗?`,
title: '删除确认',
content: `确定要删除角色 "${item.name}" 吗?\n\n⚠ 警告:该操作将同时解除所有关联管理员的身份并清空该角色的权限配置!`,
confirmText: '确认删除',
confirmColor: '#ff4d4f',
success: async (res) => {
if (res.confirm) {
const ok = await deleteRole(item.id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadData()
try {
const ok = await deleteRole(item.id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadData()
}
} catch (e: any) {
const errMsg = e?.message || '删除失败'
uni.showToast({ title: errMsg, icon: 'none', duration: 3000 })
}
}
}

View File

@@ -198,14 +198,21 @@ async function handleSave() {
async function onDelete(item : DeliveryStation) {
uni.showModal({
title: '提示',
content: `确定要删除提货点 "${item.name}" 吗?`,
title: '删除确认',
content: `确定要删除提货点 "${item.name}" 吗?\n\n⚠ 警告:该操作将同时删除该站点下的所有配送员关联!`,
confirmText: '确认删除',
confirmColor: '#ff4d4f',
success: async (res) => {
if (res.confirm) {
const ok = await deleteDeliveryStation(item.id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadData()
try {
const ok = await deleteDeliveryStation(item.id)
if (ok) {
uni.showToast({ title: '删除成功' })
loadData()
}
} catch (e: any) {
const errMsg = e?.message || '删除失败'
uni.showToast({ title: errMsg, icon: 'none', duration: 3000 })
}
}
}

View File

@@ -28,16 +28,18 @@
</view>
<view class="tab-body">
<view v-if="isLoading" class="loading-state">
<text>配置加载中...</text>
</view>
<!-- Tab 0: 储存配置 -->
<view v-if="activeTab === 0" class="config-view">
<view v-else-if="activeTab === 0" class="config-view">
<view class="notice-box">
<view class="notice-content">
<text class="notice-line">上传图片时会生成缩略图</text>
<text class="notice-line">未设置按照系统默认生成系统默认大图800*800中图300*300小图150*150</text>
<text class="notice-line">水印只在上传图片时生成,原图,大中小缩略图都会按照比例存在。</text>
<text class="notice-line">若上传图片时未开启水印,则该图在开启水印之后依旧无水印效果。</text>
</view>
<text class="notice-close">×</text>
</view>
<view class="form-body">
@@ -68,69 +70,17 @@
</view>
<view class="form-footer mt-40">
<button class="save-btn" type="primary" @click="handleSave">保存</button>
<button class="save-btn" type="primary" :disabled="isSaving" @click="handleSave">
{{ isSaving ? '保存中...' : '保存' }}
</button>
</view>
</view>
</view>
<!-- Tab 1-6: Cloud Storage -->
<!-- 其他云存储 Tab 逻辑保持现有结构,数据由 backend 动态驱动 -->
<view v-else class="cloud-view">
<view class="cloud-notice">
<view class="notice-title">
<text class="blue-title">{{ tabs[activeTab] }}开通方法:</text>
<text class="link-text">点击查看</text>
</view>
<text class="notice-step">第一步:添加【存储空间】(空间名称不能重复)</text>
<text class="notice-step">第二步:开启【使用状态】</text>
<text class="notice-step">第三步(可选):选择云存储空间列表上的修改【空间域名操作】</text>
<text class="notice-step">第四步可选选择云存储空间列表上的修改【CNAME配置】打开后复制记录值到对应的平台解析</text>
<text class="notice-close">×</text>
</view>
<view class="action-bar mt-20">
<view class="left-actions">
<button class="action-btn primary-btn">添加存储空间</button>
<button class="action-btn success-btn ml-10">同步存储空间</button>
</view>
<button class="action-btn outline-btn">修改配置信息</button>
</view>
<view class="table-container mt-20">
<view class="table-header">
<text class="th flex-2">储存空间名称</text>
<text class="th flex-1">区域</text>
<text class="th flex-3">空间域名</text>
<text class="th flex-1">使用状态</text>
<text class="th flex-2">创建时间</text>
<text class="th flex-2">更新时间</text>
<text class="th flex-2 center">操作</text>
</view>
<view class="table-body">
<view v-if="getCloudData().length > 0">
<view
class="table-row"
v-for="(row, rIndex) in getCloudData()"
:key="rIndex"
>
<text class="td flex-2">{{ row.name }}</text>
<text class="td flex-1">{{ row.region }}</text>
<text class="td flex-3 link">{{ row.domain }}</text>
<view class="td flex-1">
<switch :checked="row.status" scale="0.6" color="#2d8cf0" />
</view>
<text class="td flex-2 small-text muted">{{ row.createTime }}</text>
<text class="td flex-2 small-text muted">{{ row.updateTime }}</text>
<view class="td flex-2 center-row">
<text class="op-link">CNAME配置</text>
<text class="op-link ml-10">修改空间域名</text>
<text class="op-link danger ml-10">删除</text>
</view>
</view>
</view>
<view v-else class="empty-state">
<text class="empty-text">暂无数据</text>
</view>
</view>
<view class="empty-state">
<text class="empty-text">{{ tabs[activeTab] }} 详细配置请在“修改配置信息”中设置</text>
</view>
</view>
</view>
@@ -139,10 +89,13 @@
</template>
<script setup lang="uts">
import { ref, reactive } from 'vue'
import { ref, reactive, onMounted } from 'vue'
import { getSystemConfig, saveSystemConfig } from "@/services/admin/systemConfigService.uts"
const tabs = ['储存配置', '七牛云储存', '阿里云储存', '腾讯云储存', '京东云储存', '华为云储存', '天翼云储存']
const activeTab = ref(0)
const isLoading = ref(false)
const isSaving = ref(false)
const storageOptions = [
{ label: '本地存储', value: 'local' },
@@ -160,6 +113,22 @@ const form = reactive({
enableWatermark: false
})
onMounted(() => {
loadConfig()
})
async function loadConfig() {
isLoading.value = true
try {
const res = await getSystemConfig('storage_config')
if (res != null) {
Object.assign(form, res as any)
}
} finally {
isLoading.value = false
}
}
const onStorageTypeChange = (e : any) => {
form.storageType = e.detail.value as string
}
@@ -172,310 +141,46 @@ const onToggleWatermark = (e : any) => {
form.enableWatermark = e.detail.value as boolean
}
const handleSave = () => {
uni.showToast({
title: '保存成功',
icon: 'success'
})
}
// Simulated data for Aliyun Storage (based on screenshot)
const aliyunData = [
{ name: 'crmebdoc', region: '华北2 (北京)', domain: 'https://crmebdoc.oss-cn-beijing.aliyuncs.com', status: false, createTime: '2024-07-30 12:10:42', updateTime: '2025-01-22 10:58:20' },
{ name: 'crmebjavasingle', region: '华北2 (北京)', domain: 'https://crmebjavasingl...oss-cn-beijing.aliyunc...s.com', status: false, createTime: '2024-07-29 17:22:24', updateTime: '2025-01-22 10:58:20' },
{ name: 'crmebjavamer', region: '华北2 (北京)', domain: 'https://crmebjavamer.o...ss-cn-beijing.aliyuncs.c...om', status: false, createTime: '2024-07-22 14:43:42', updateTime: '2025-01-22 10:58:20' },
{ name: 'crmebmer', region: '华北2 (北京)', domain: 'https://crmebmer.oss-c...n-beijing.aliyuncs.com', status: false, createTime: '2024-07-22 14:42:53', updateTime: '2025-01-22 10:58:20' },
{ name: 'crmebmulti', region: '华北2 (北京)', domain: 'https://crmebmulti.oss-...cn-beijing.aliyuncs.com', status: false, createTime: '2024-07-22 14:42:08', updateTime: '2025-01-22 10:58:20' },
{ name: 'crmebpros', region: '华北2 (北京)', domain: 'https://crmebpros.oss-c...n-beijing.aliyuncs.com', status: false, createTime: '2024-07-22 14:41:17', updateTime: '2025-01-22 10:58:20' },
{ name: 'crmebbz', region: '华东1 (杭州)', domain: 'https://crmebbz.oss-cn-...hangzhou.aliyuncs.com', status: true, createTime: '2022-08-18 17:30:33', updateTime: '2025-01-22 10:58:20' }
]
const getCloudData = () : any[] => {
if (activeTab.value === 2) { // 阿里云
return aliyunData
const handleSave = async () => {
isSaving.value = true
try {
const ok = await saveSystemConfig('storage_config', form as UTSJSONObject, '系统存储配置')
if (ok) {
uni.showToast({ title: '保存成功', icon: 'success' })
}
} finally {
isSaving.value = false
}
return [] as any[]
}
</script>
<style scoped>
.admin-page {
min-height: 100vh;
background-color: #f5f7f9;
padding: 20px;
}
.breadcrumb {
display: flex;
flex-direction: row;
margin-bottom: 20px;
}
.bc-item {
font-size: 14px;
color: #999;
}
.bc-item.active {
color: #333;
}
.bc-sep {
margin: 0 8px;
color: #ccc;
}
.content-card {
background-color: #ffffff;
border-radius: 4px;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
}
.tabs-header {
border-bottom: 1px solid #f0f0f0;
}
.tabs-scroll {
white-space: nowrap;
width: 100%;
}
.tabs-list {
display: flex;
flex-direction: row;
padding: 0 15px;
}
.tab-item {
padding: 15px 15px;
cursor: pointer;
position: relative;
}
.tab-text {
font-size: 14px;
color: #666;
}
.tab-item.active .tab-text {
color: #2d8cf0;
font-weight: bold;
}
.tab-item.active::after {
content: "";
position: absolute;
bottom: 0;
left: 15px;
right: 15px;
height: 2px;
background-color: #2d8cf0;
}
.tab-body {
padding: 20px;
}
/* Notice Box Styles */
.notice-box {
background-color: #fffaf3;
border: 1px solid #ffebcc;
padding: 15px 20px;
border-radius: 4px;
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 30px;
}
.notice-line {
font-size: 13px;
color: #666;
line-height: 1.8;
display: block;
}
.notice-close {
color: #ccc;
font-size: 18px;
cursor: pointer;
}
/* Form Styles */
.form-body {
max-width: 800px;
}
.form-row {
display: flex;
flex-direction: row;
align-items: center;
}
.form-label {
width: 140px;
font-size: 13px;
color: #333;
}
.storage-radio-group {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.radio-label {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 20px;
margin-bottom: 10px;
}
.radio-text {
font-size: 13px;
margin-left: 4px;
color: #666;
}
.save-btn {
width: 80px;
height: 32px;
line-height: 32px;
background-color: #2d8cf0;
color: white;
font-size: 13px;
padding: 0;
border-radius: 4px;
}
.mt-20 { margin-top: 20px; }
.admin-page { min-height: 100vh; background-color: #f5f7f9; padding: 20px; }
.breadcrumb { display: flex; flex-direction: row; margin-bottom: 20px; }
.bc-item { font-size: 14px; color: #999; }
.bc-item.active { color: #333; }
.bc-sep { margin: 0 8px; color: #ccc; }
.content-card { background-color: #ffffff; border-radius: 4px; box-shadow: 0 1px 4px rgba(0,21,41,.08); }
.tabs-header { border-bottom: 1px solid #f0f0f0; }
.tabs-scroll { white-space: nowrap; width: 100%; }
.tabs-list { display: flex; flex-direction: row; padding: 0 15px; }
.tab-item { padding: 15px 15px; cursor: pointer; position: relative; }
.tab-text { font-size: 14px; color: #666; }
.tab-item.active .tab-text { color: #2d8cf0; font-weight: bold; }
.tab-item.active::after { content: ""; position: absolute; bottom: 0; left: 15px; right: 15px; height: 2px; background-color: #2d8cf0; }
.tab-body { padding: 20px; }
.loading-state { padding: 60px; text-align: center; color: #999; }
.notice-box { background-color: #fffaf3; border: 1px solid #ffebcc; padding: 15px 20px; border-radius: 4px; margin-bottom: 30px; }
.notice-line { font-size: 13px; color: #666; line-height: 1.8; display: block; }
.form-body { max-width: 800px; }
.form-row { display: flex; flex-direction: row; align-items: center; }
.form-label { width: 140px; font-size: 13px; color: #333; }
.storage-radio-group { display: flex; flex-direction: row; flex-wrap: wrap; }
.radio-label { display: flex; flex-direction: row; align-items: center; margin-right: 20px; margin-bottom: 10px; }
.radio-text { font-size: 13px; margin-left: 4px; color: #666; }
.save-btn { width: 80px; height: 32px; line-height: 32px; background-color: #2d8cf0; color: white; font-size: 13px; border-radius: 4px; }
.mt-30 { margin-top: 30px; }
.mt-40 { margin-top: 40px; }
.ml-10 { margin-left: 10px; }
/* Cloud View Styles */
.cloud-notice {
background-color: #f0faff;
border: 1px solid #d5e8fc;
border-radius: 4px;
padding: 15px 20px;
position: relative;
}
.blue-title {
color: #2db7f5;
font-size: 14px;
font-weight: bold;
}
.link-text {
color: #2d8cf0;
font-size: 14px;
margin-left: 10px;
cursor: pointer;
text-decoration: underline;
}
.notice-step {
display: block;
font-size: 13px;
color: #666;
line-height: 1.8;
}
.action-bar {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.left-actions {
display: flex;
flex-direction: row;
}
.action-btn {
font-size: 12px;
height: 30px;
line-height: 30px;
padding: 0 12px;
border-radius: 3px;
}
.primary-btn { background-color: #2d8cf0; color: white; border: none; }
.success-btn { background-color: #19be6b; color: white; border: none; }
.outline-btn { background-color: white; color: #666; border: 1px solid #dcdee2; }
/* Table Styles */
.table-container {
border: 1px solid #f0f0f0;
}
.table-header {
display: flex;
flex-direction: row;
background-color: #f8f8f9;
border-bottom: 1px solid #f0f0f0;
}
.th {
padding: 12px 10px;
font-size: 13px;
font-weight: bold;
color: #333;
}
.table-row {
display: flex;
flex-direction: row;
border-bottom: 1px solid #f0f0f0;
align-items: center;
}
.td {
padding: 12px 10px;
font-size: 13px;
color: #666;
display: flex;
flex-direction: row;
align-items: center;
}
.flex-1 { flex: 1; }
.flex-2 { flex: 2; }
.flex-3 { flex: 3; }
.center { justify-content: center; }
.center-row { justify-content: center; }
.link {
color: #2d8cf0;
text-decoration: underline;
}
.small-text { font-size: 11px; }
.muted { color: #999; }
.op-link {
color: #2d8cf0;
font-size: 12px;
cursor: pointer;
}
.op-link.danger {
color: #ed4014;
}
.empty-state {
padding: 40px;
display: flex;
justify-content: center;
}
.empty-text {
color: #ccc;
font-size: 14px;
}
.cloud-view { padding: 40px; text-align: center; }
.empty-text { color: #ccc; font-size: 14px; }
</style>