mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 13:05:18 +00:00
1、系统配置修改上线 2、 pinia取代vuex
This commit is contained in:
@@ -1,32 +1,16 @@
|
||||
import request from "@/utils/request";
|
||||
|
||||
export function getConfigList(params) {
|
||||
export function getConfigInfo() {
|
||||
return request({
|
||||
url: "/api/v1/system/config/list",
|
||||
url: "/api/v1/system/config/info",
|
||||
method: "get",
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
export function getConfigDetail(params) {
|
||||
return request({
|
||||
url: "/api/v1/system/config/detail",
|
||||
method: "get",
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
export function createConfig(body) {
|
||||
export function updateConfig(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/config/create",
|
||||
method: "post",
|
||||
data: body,
|
||||
});
|
||||
}
|
||||
|
||||
export function batchConfig(body) {
|
||||
return request({
|
||||
url: "/api/v1/system/config/batch",
|
||||
url: "/api/v1/system/config/update",
|
||||
method: "put",
|
||||
data: body,
|
||||
});
|
||||
|
||||
@@ -150,7 +150,7 @@ import type { MenuProps, ItemType } from "ant-design-vue";
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import storage from 'store';
|
||||
import store from '@/store';
|
||||
import { useUserStore } from "@/store/index";
|
||||
import * as icons from '@ant-design/icons-vue';
|
||||
import { h } from 'vue';
|
||||
import { listToTree } from '@/utils/util';
|
||||
@@ -168,6 +168,9 @@ import {
|
||||
import { logout } from '@/api/system/auth';
|
||||
import { getNoticeList } from '@/api/system/notice'
|
||||
import { getInitConfig } from "@/api/system/config"
|
||||
import { useConfigStore } from "@/store/index";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 通知公告获取
|
||||
const dataSource = reactive({
|
||||
@@ -194,7 +197,7 @@ const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
// 计算属性
|
||||
const userInfo = computed(() => store.state.user.basicInfo);
|
||||
const userInfo = computed(() => userStore.basicInfo);
|
||||
const unreadCount = computed(() => dataSource.total); // 通知条数
|
||||
|
||||
// 菜单状态
|
||||
@@ -229,7 +232,7 @@ const handleLogout = async () => {
|
||||
await logout({ token: storage.get('Access-Token') });
|
||||
storage.remove('Access-Token');
|
||||
storage.remove('Refresh-Token');
|
||||
await store.dispatch('clearUserInfo');
|
||||
await userStore.clearUserInfo;
|
||||
router.push('/login');
|
||||
} catch (error) {
|
||||
console.error('退出登录失败:', error);
|
||||
@@ -244,7 +247,7 @@ const handleMenuClick: MenuProps['onClick'] = (menuInfo) => {
|
||||
|
||||
// 监听路由变化
|
||||
watch(() => route.path, (newPath) => {
|
||||
const routePath = store.state.user.routeList.map(item => item.route_path);
|
||||
const routePath = userStore.routeList.map(item => item.route_path);
|
||||
if (!routePath.includes(newPath)) {
|
||||
menuState.openKeys = [];
|
||||
menuState.selectedKeys = [];
|
||||
@@ -274,11 +277,11 @@ const generateMenuItem = (item: any): ItemType => {
|
||||
// 初始化菜单
|
||||
const initMenu = () => {
|
||||
// 确保路由列表存在
|
||||
if (!store.state.user.routeList?.length) {
|
||||
if (!userStore.routeList?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const menuTree = listToTree(store.state.user.routeList);
|
||||
const menuTree = listToTree(userStore.routeList);
|
||||
menuState.menus = menuTree
|
||||
.filter(item => !item.hidden)
|
||||
.map(generateMenuItem)
|
||||
@@ -328,38 +331,18 @@ const noticeState = reactive({
|
||||
loading: false
|
||||
});
|
||||
|
||||
const configStore = useConfigStore();
|
||||
|
||||
const initConfigState = reactive({
|
||||
web_title: '',
|
||||
web_favicon: '',
|
||||
web_title: configStore.getConfigValue("title"),
|
||||
web_favicon: configStore.getConfigValue("logo"),
|
||||
});
|
||||
|
||||
const initConfig = () => {
|
||||
getInitConfig()
|
||||
.then(response => {
|
||||
const { status_code, data } = response.data;
|
||||
if (status_code === 200) {
|
||||
const configData = JSON.parse(data);
|
||||
configData.forEach(item => {
|
||||
if (item.fied_key === 'web_title') {
|
||||
initConfigState.web_title = item.fied_value;
|
||||
} else if (item.fied_key === 'web_favicon') {
|
||||
initConfigState.web_favicon = item.fied_value;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
message.error('获取系统配置失败');
|
||||
console.error(error); // 打印错误信息以便调试
|
||||
});
|
||||
};
|
||||
|
||||
// 在页面加载时获取通知列表
|
||||
onMounted(() => {
|
||||
initMenu();
|
||||
handleNoticeList();
|
||||
initConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
+27
-29
@@ -6,39 +6,37 @@ import "ant-design-vue/dist/reset.css";
|
||||
import VChart from "vue-echarts";
|
||||
import "echarts";
|
||||
import './style.css'
|
||||
|
||||
import { getInitConfig } from "@/api/system/config"
|
||||
import { createPinia } from 'pinia';
|
||||
import { useConfigStore } from "@/store/index";
|
||||
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
|
||||
const initConfig = () => {
|
||||
return getInitConfig()
|
||||
.then((response) => {
|
||||
const { status_code, data } = response.data;
|
||||
if (status_code === 200) {
|
||||
const configData = JSON.parse(data);
|
||||
configData.forEach((item) => {
|
||||
if (item.fied_key === "login_title") {
|
||||
document.title = item.fied_value || "fastapi vue admin";
|
||||
} else if (item.fied_key === "login_logo") {
|
||||
const favicon = document.querySelector('link[rel="icon"]');
|
||||
if (favicon) {
|
||||
favicon.href = item.fied_value || "/logo.png";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
message.error("获取系统配置失败");
|
||||
console.error(error); // 打印错误信息以便调试
|
||||
});
|
||||
app.use(pinia);
|
||||
|
||||
const initConfig = async () => {
|
||||
const configStore = useConfigStore();
|
||||
await configStore.fetchConfig();
|
||||
|
||||
const loginTitle = configStore.getConfigValue("title");
|
||||
const loginLogo = configStore.getConfigValue("favicon");
|
||||
|
||||
if (loginTitle) {
|
||||
document.title = loginTitle;
|
||||
}
|
||||
|
||||
if (loginLogo) {
|
||||
const favicon = document.querySelector('link[rel="icon"]');
|
||||
if (favicon) {
|
||||
favicon.href = loginLogo;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化配置并挂载应用
|
||||
initConfig().then(() => {
|
||||
app.use(router);
|
||||
app.use(Antd);
|
||||
app.component("VChart", VChart);
|
||||
app.mount("#app");
|
||||
});
|
||||
app.use(router);
|
||||
app.use(Antd);
|
||||
app.component("VChart", VChart);
|
||||
app.mount("#app");
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
const modules = import.meta.glob("../views/**/**.vue");
|
||||
|
||||
export const generator = (routers) => {
|
||||
return routers.map((item) => {
|
||||
const currentRouter = {
|
||||
path: item.route_path,
|
||||
name: item.route_name,
|
||||
component: item.component_path
|
||||
? modules[`../views/${item.component_path}.vue`]
|
||||
: null,
|
||||
redirect: item.redirect,
|
||||
meta: {
|
||||
title: item.name,
|
||||
icon: item.icon || undefined,
|
||||
keepAlive: item.cache,
|
||||
hidden: item.hidden,
|
||||
order: item.order,
|
||||
},
|
||||
};
|
||||
if (item.children && item.children.length > 0) {
|
||||
currentRouter.children = generator(item.children);
|
||||
}
|
||||
return currentRouter;
|
||||
});
|
||||
};
|
||||
@@ -1,10 +1,37 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import storage from "store";
|
||||
|
||||
import BasicLayout from "@/layouts/basicLayout.vue";
|
||||
import Login from "@/views/system/auth/login.vue";
|
||||
import storage from "store";
|
||||
import store from "@/store";
|
||||
import { generator } from "./generateRouter";
|
||||
import { listToTree } from "@/utils/util";
|
||||
import { useUserStore } from "@/store/index";
|
||||
|
||||
const modules = import.meta.glob("../views/**/**.vue");
|
||||
|
||||
export const generator = (routers) => {
|
||||
return routers.map((item) => {
|
||||
const currentRouter = {
|
||||
path: item.route_path,
|
||||
name: item.route_name,
|
||||
component: item.component_path
|
||||
? modules[`../views/${item.component_path}.vue`]
|
||||
: null,
|
||||
redirect: item.redirect,
|
||||
meta: {
|
||||
title: item.name,
|
||||
icon: item.icon || undefined,
|
||||
keepAlive: item.cache,
|
||||
hidden: item.hidden,
|
||||
order: item.order,
|
||||
},
|
||||
};
|
||||
if (item.children && item.children.length > 0) {
|
||||
currentRouter.children = generator(item.children);
|
||||
}
|
||||
return currentRouter;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const routes = [
|
||||
{ path: "/login", name: "Login", component: Login },
|
||||
@@ -39,21 +66,21 @@ const router = createRouter({
|
||||
routes,
|
||||
});
|
||||
|
||||
router.beforeEach((to) => {
|
||||
router.beforeEach(async (to) => {
|
||||
const token = storage.get("Access-Token");
|
||||
const userStore = useUserStore();
|
||||
|
||||
if (!token && to.name !== "Login") {
|
||||
return { name: "Login" };
|
||||
} else if (token && to.name === "Login") {
|
||||
return { name: "Index" };
|
||||
} else if (token && !store.state.user.hasGetRoute) {
|
||||
return store.dispatch("getUserInfo").then(() => {
|
||||
const routersTree = listToTree(store.state.user.routeList);
|
||||
const routerMap = generator(routersTree);
|
||||
rootRouter.children = [...rootRouter.children, ...routerMap];
|
||||
router.addRoute(rootRouter);
|
||||
return to.fullPath;
|
||||
});
|
||||
} else if (token && !userStore.hasGetRoute) {
|
||||
await userStore.getUserInfo();
|
||||
const routersTree = listToTree(userStore.routeList);
|
||||
const routerMap = generator(routersTree);
|
||||
rootRouter.children = [...rootRouter.children, ...routerMap];
|
||||
router.addRoute(rootRouter);
|
||||
return to.fullPath;
|
||||
}
|
||||
});
|
||||
export default router;
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { getInitConfig } from "@/api/system/config";
|
||||
|
||||
export const useConfigStore = defineStore('config', {
|
||||
state: () => ({
|
||||
systemConfig: {},
|
||||
}),
|
||||
actions: {
|
||||
async getConfigInfo() {
|
||||
try {
|
||||
const response = await getInitConfig();
|
||||
const result = response.data;
|
||||
const configData = JSON.parse(result.data);
|
||||
this.systemConfig = configData;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
setSystemConfig(config) {
|
||||
this.systemConfig = config;
|
||||
},
|
||||
clearConfigInfo() {
|
||||
this.systemConfig = {};
|
||||
},
|
||||
},
|
||||
});
|
||||
+59
-10
@@ -1,14 +1,63 @@
|
||||
import { createStore } from "vuex";
|
||||
import user from "./modules/user";
|
||||
import { defineStore } from "pinia";
|
||||
import { getCurrentUserInfo } from "@/api/system/user";
|
||||
import { getInitConfig } from "@/api/system/config";
|
||||
|
||||
const store = createStore({
|
||||
modules: {
|
||||
user,
|
||||
|
||||
export const useUserStore = defineStore("user", {
|
||||
state: () => ({
|
||||
basicInfo: {},
|
||||
routeList: [],
|
||||
hasGetRoute: false,
|
||||
}),
|
||||
actions: {
|
||||
async getUserInfo() {
|
||||
try {
|
||||
const response = await getCurrentUserInfo();
|
||||
const result = response.data;
|
||||
const routers = result.data.menus;
|
||||
delete result.data.menus;
|
||||
this.setRoute(routers);
|
||||
this.basicInfo = { ...this.basicInfo, ...result.data };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
setRoute(routers) {
|
||||
this.routeList = routers;
|
||||
this.hasGetRoute = true;
|
||||
},
|
||||
setAvatar(avatar) {
|
||||
this.basicInfo = { ...this.basicInfo, avatar };
|
||||
},
|
||||
clearUserInfo() {
|
||||
this.basicInfo = {};
|
||||
this.routeList = [];
|
||||
this.hasGetRoute = false;
|
||||
},
|
||||
},
|
||||
state: {},
|
||||
mutations: {},
|
||||
actions: {},
|
||||
getters: {},
|
||||
});
|
||||
|
||||
export default store;
|
||||
export const useConfigStore = defineStore("config", {
|
||||
state: () => ({
|
||||
configData: {}, // 存储系统配置
|
||||
isConfigLoaded: false, // 标记配置是否已加载
|
||||
}),
|
||||
actions: {
|
||||
async fetchConfig() {
|
||||
try {
|
||||
const response = await getInitConfig();
|
||||
const { status_code, data } = response.data;
|
||||
if (status_code === 200) {
|
||||
this.configData = JSON.parse(data);
|
||||
console.log(this.configData);
|
||||
this.isConfigLoaded = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取系统配置失败", error);
|
||||
}
|
||||
},
|
||||
getConfigValue(key) {
|
||||
return this.configData[key] || null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
import { getCurrentUserInfo } from "@/api/system/user";
|
||||
|
||||
const user = {
|
||||
state: {
|
||||
basicInfo: {},
|
||||
routeList: [],
|
||||
hasGetRoute: false,
|
||||
},
|
||||
mutations: {
|
||||
setRoute(state, routers) {
|
||||
state.routeList = routers;
|
||||
state.hasGetRoute = true;
|
||||
},
|
||||
setAvatar(state, avatar) {
|
||||
state.basicInfo.avatar = avatar;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
getUserInfo({ commit, state }) {
|
||||
return new Promise((resolve) => {
|
||||
getCurrentUserInfo()
|
||||
.then((response) => {
|
||||
const result = response.data;
|
||||
const routers = result.data.menus;
|
||||
delete result.data.menus;
|
||||
commit("setRoute", routers);
|
||||
state.basicInfo = Object.assign(state.basicInfo || {}, result.data);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
clearUserInfo({ state }) {
|
||||
state.basicInfo = {};
|
||||
state.routeList = [];
|
||||
state.hasGetRoute = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default user;
|
||||
@@ -1,36 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { getCurrentUserInfo } from "@/api/system/user";
|
||||
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: () => ({
|
||||
basicInfo: {},
|
||||
routeList: [],
|
||||
hasGetRoute: false,
|
||||
}),
|
||||
actions: {
|
||||
async getUserInfo() {
|
||||
try {
|
||||
const response = await getCurrentUserInfo();
|
||||
const result = response.data;
|
||||
const routers = result.data.menus;
|
||||
delete result.data.menus;
|
||||
this.setRoute(routers);
|
||||
this.basicInfo = Object.assign(this.basicInfo || {}, result.data);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
setRoute(routers) {
|
||||
this.routeList = routers;
|
||||
this.hasGetRoute = true;
|
||||
},
|
||||
setAvatar(avatar) {
|
||||
this.basicInfo.avatar = avatar;
|
||||
},
|
||||
clearUserInfo() {
|
||||
this.basicInfo = {};
|
||||
this.routeList = [];
|
||||
this.hasGetRoute = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -122,15 +122,17 @@ import { ref, reactive } from 'vue';
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { timeFix } from '@/utils/util';
|
||||
import { PlusOutlined, UserOutlined } from '@ant-design/icons-vue';
|
||||
import store from '@/store';
|
||||
import { useUserStore } from "@/store/index";
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const loading = ref(true);
|
||||
|
||||
let timefix = timeFix();
|
||||
|
||||
const userInfo = store.state.user.basicInfo;
|
||||
const userInfo = userStore.basicInfo;
|
||||
|
||||
console.log("store.state.user.basicInfo",store.state.user.basicInfo)
|
||||
console.log("store.state.user.basicInfo",userStore.basicInfo)
|
||||
const welcome = '祝你开心每一天!';
|
||||
|
||||
const projectItems = [
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<a-layout>
|
||||
<!-- 页面主体 -->
|
||||
<div class="container" :style="{ backgroundImage: `url(${initConfigState.login_background})` }">
|
||||
<div class="container" :style="{ backgroundImage: `url(${initConfigState.background})` }">
|
||||
<a-layout-content :style="contentStyle">
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<a-image :src="initConfigState.login_logo" :preview="false" />
|
||||
<a-image :src="initConfigState.logo" :preview="false" />
|
||||
</div>
|
||||
<div class="title">{{ initConfigState.login_title }}</div>
|
||||
<div class="title">{{ initConfigState.title }}</div>
|
||||
</div>
|
||||
<div class="desc">{{ initConfigState.login_description }}</div>
|
||||
<div class="desc">{{ initConfigState.description }}</div>
|
||||
|
||||
<div class="login-main" style="width: 330px; margin: 0 auto;">
|
||||
<a-tabs centered>
|
||||
@@ -69,13 +69,13 @@
|
||||
{{ initConfigState.copyright }} |
|
||||
</a-button>
|
||||
<a-button type="link" :href="initConfigState.help_url">
|
||||
{{ initConfigState.help_name }} |
|
||||
帮助 |
|
||||
</a-button>
|
||||
<a-button type="link" :href="initConfigState.privacy_url">
|
||||
{{ initConfigState.privacy_name }} |
|
||||
隐私 |
|
||||
</a-button>
|
||||
<a-button type="link" :href="initConfigState.clause_url">
|
||||
{{ initConfigState.clause_name }}
|
||||
条款
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="footer-record">
|
||||
@@ -161,8 +161,8 @@ import { save_token } from "@/utils/util"
|
||||
import { message } from 'ant-design-vue';
|
||||
import { login, getCaptcha } from "@/api/system/auth"
|
||||
import { registerUser, forgetPassword } from "@/api/system/user"
|
||||
import { getInitConfig } from "@/api/system/config"
|
||||
import type { LoginForm, CaptchaState, ForgetPasswordForm, RegisterForm } from './types'
|
||||
import { useConfigStore } from "@/store/index";
|
||||
|
||||
const router = useRouter();
|
||||
const loginFlag = ref(false);
|
||||
@@ -287,69 +287,24 @@ const requestCaptcha = () => {
|
||||
.catch(() => captchaState.enable = false);
|
||||
};
|
||||
|
||||
const configStore = useConfigStore();
|
||||
|
||||
const initConfigState = reactive({
|
||||
login_title: '',
|
||||
login_description: '/logo.png',
|
||||
login_logo: '',
|
||||
login_background: '/background.png',
|
||||
copyright: '',
|
||||
copyright_name: '',
|
||||
keep_record: '',
|
||||
keep_record_name: '',
|
||||
help_url: '',
|
||||
help_name: '',
|
||||
privacy_url: '',
|
||||
privacy_name: '',
|
||||
clause_url: '',
|
||||
clause_name: '',
|
||||
code_url: '',
|
||||
title: configStore.getConfigValue("title"),
|
||||
description: configStore.getConfigValue("description"),
|
||||
logo: configStore.getConfigValue("logo"),
|
||||
background: configStore.getConfigValue("background"),
|
||||
copyright: configStore.getConfigValue("copyright"),
|
||||
keep_record: configStore.getConfigValue("keep_record"),
|
||||
help_url: configStore.getConfigValue("help_url"),
|
||||
privacy_url: configStore.getConfigValue("privacy_url"),
|
||||
clause_url: configStore.getConfigValue("clause_url"),
|
||||
code_url: configStore.getConfigValue("code_url"),
|
||||
});
|
||||
|
||||
const initConfig = () => {
|
||||
getInitConfig()
|
||||
.then(response => {
|
||||
const { status_code, data } = response.data;
|
||||
if (status_code === 200) {
|
||||
const configData = JSON.parse(data);
|
||||
configData.forEach(item => {
|
||||
if (item.fied_key === 'login_title') {
|
||||
initConfigState.login_title = item.fied_value;
|
||||
} else if (item.fied_key === 'login_description') {
|
||||
initConfigState.login_description = item.fied_value;
|
||||
} else if (item.fied_key === 'login_logo') {
|
||||
initConfigState.login_logo = item.fied_value;
|
||||
} else if (item.fied_key === 'login_background') {
|
||||
initConfigState.login_background = item.fied_value;
|
||||
} else if (item.fied_key === 'copyright') {
|
||||
initConfigState.copyright = item.fied_value;
|
||||
initConfigState.copyright_name = item.name;
|
||||
} else if (item.fied_key === 'keep_record') {
|
||||
initConfigState.keep_record = item.fied_value;
|
||||
initConfigState.keep_record_name = item.name;
|
||||
} else if (item.fied_key === 'help_url') {
|
||||
initConfigState.help_url = item.fied_value;
|
||||
initConfigState.help_name = item.name;
|
||||
} else if (item.fied_key === 'privacy_url') {
|
||||
initConfigState.privacy_url = item.fied_value;
|
||||
initConfigState.privacy_name = item.name;
|
||||
} else if (item.fied_key === 'clause_url') {
|
||||
initConfigState.clause_url = item.fied_value;
|
||||
initConfigState.clause_name = item.name;
|
||||
} else if (item.fied_key === 'code_url') {
|
||||
initConfigState.code_url = item.fied_value;
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
message.error('获取系统配置失败');
|
||||
console.error(error); // 打印错误信息以便调试
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
requestCaptcha();
|
||||
initConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,35 +7,94 @@
|
||||
<div class="config-edit-wrapper">
|
||||
<a-card title="系统配置" :bordered="false">
|
||||
<!-- 动态生成配置项 -->
|
||||
<template v-for="group in updateState" :key="group.id">
|
||||
<a-card :title="group.name" :bordered="false" style="margin-bottom: 24px;">
|
||||
<a-row :gutter="16">
|
||||
<template v-for="config in group.children" :key="config.id">
|
||||
<a-col :span="12">
|
||||
<!-- 图片上传类型 -->
|
||||
<a-form-item v-if="config.fied_key === 'web_favicon' || config.fied_key === 'login_logo' || config.fied_key === 'login_background'" :label="config.name">
|
||||
<a-upload
|
||||
v-model:file-list="config.fileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="(options) => handleUpload(options, config)"
|
||||
>
|
||||
<div v-if="!config.fileList || config.fileList.length === 0">
|
||||
<plus-outlined />
|
||||
<div style="margin-top: 8px">上传图片</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<!-- 网站标题 -->
|
||||
<a-form-item label="网站标题">
|
||||
<a-input v-model:value="configData.title" placeholder="请输入网站标题" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 输入框类型 -->
|
||||
<a-form-item v-else :label="config.name">
|
||||
<a-input v-model:value="config.fied_value" :placeholder="`请输入${config.name}`" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</template>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
<!-- 网站图标 -->
|
||||
<a-form-item label="网站图标">
|
||||
<a-upload
|
||||
v-model:file-list="faviconFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="(options) => handleUpload(options, 'favicon')"
|
||||
>
|
||||
<div v-if="!faviconFileList || faviconFileList.length === 0">
|
||||
<plus-outlined />
|
||||
<div style="margin-top: 8px">上传图标</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 登录页Logo -->
|
||||
<a-form-item label="登录页Logo">
|
||||
<a-upload
|
||||
v-model:file-list="logoFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="(options) => handleUpload(options, 'logo')"
|
||||
>
|
||||
<div v-if="!logoFileList || logoFileList.length === 0">
|
||||
<plus-outlined />
|
||||
<div style="margin-top: 8px">上传Logo</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 登录页背景图 -->
|
||||
<a-form-item label="登录页背景图">
|
||||
<a-upload
|
||||
v-model:file-list="backgroundFileList"
|
||||
list-type="picture-card"
|
||||
:before-upload="beforeUpload"
|
||||
:custom-request="(options) => handleUpload(options, 'background')"
|
||||
>
|
||||
<div v-if="!backgroundFileList || backgroundFileList.length === 0">
|
||||
<plus-outlined />
|
||||
<div style="margin-top: 8px">上传背景图</div>
|
||||
</div>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<a-form-item label="网站描述">
|
||||
<a-input v-model:value="configData.description" placeholder="请输入版权信息" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 版权信息 -->
|
||||
<a-form-item label="版权信息">
|
||||
<a-input v-model:value="configData.copyright" placeholder="请输入版权信息" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 备案号 -->
|
||||
<a-form-item label="备案号">
|
||||
<a-input v-model:value="configData.keep_record" placeholder="请输入备案号" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 帮助链接 -->
|
||||
<a-form-item label="帮助链接">
|
||||
<a-input v-model:value="configData.help_url" placeholder="请输入帮助链接" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 隐私条款链接 -->
|
||||
<a-form-item label="隐私条款链接">
|
||||
<a-input v-model:value="configData.privacy_url" placeholder="请输入隐私条款链接" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 服务条款链接 -->
|
||||
<a-form-item label="服务条款链接">
|
||||
<a-input v-model:value="configData.clause_url" placeholder="请输入服务条款链接" allowClear />
|
||||
</a-form-item>
|
||||
|
||||
<!-- 代码仓库链接 -->
|
||||
<a-form-item label="代码仓库链接">
|
||||
<a-input v-model:value="configData.code_url" placeholder="请输入代码仓库链接" allowClear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div style="text-align: right; margin-top: 24px;">
|
||||
@@ -47,46 +106,53 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, onMounted } from 'vue';
|
||||
import { reactive, ref, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { getConfigList, batchConfig, uploadFile } from '@/api/system/config';
|
||||
import { getConfigInfo, updateConfig, uploadFile } from '@/api/system/config';
|
||||
import PageHeader from '@/components/PageHeader.vue';
|
||||
import type { tableDataType } from './types'
|
||||
import type { tableDataType } from './types';
|
||||
|
||||
interface ConfigGroup extends tableDataType {
|
||||
children: tableDataType[];
|
||||
fileList?: any[];
|
||||
}
|
||||
// 配置数据
|
||||
const configData = reactive<tableDataType>({
|
||||
id: 1,
|
||||
title: 'FastAPI Vue Admin',
|
||||
favicon: 'http://example.com/favicon.png',
|
||||
logo: 'http://example.com/logo.png',
|
||||
background: 'http://example.com/background.png',
|
||||
description: 'FastAPI Vue Admin 是完全开源的权限管理系统',
|
||||
copyright: 'Copyright © 2021-2025 fastapi-vue-admin.com',
|
||||
keep_record: '晋ICP备18005113号-3',
|
||||
help_url: 'https://django-vue-admin.com',
|
||||
privacy_url: '/api/system/clause/privacy.html',
|
||||
clause_url: '/api/system/clause/terms_service.html',
|
||||
code_url: 'https://gitee.com/tao__tao/fastapi_vue_admin.git',
|
||||
});
|
||||
|
||||
const updateState = reactive<ConfigGroup[]>([]);
|
||||
// 文件上传列表
|
||||
const faviconFileList = ref<any[]>([]);
|
||||
const logoFileList = ref<any[]>([]);
|
||||
const backgroundFileList = ref<any[]>([]);
|
||||
|
||||
// 加载配置数据
|
||||
const loadConfigData = async () => {
|
||||
try {
|
||||
const response = await getConfigList({});
|
||||
const items = response.data.data.items;
|
||||
const response = await getConfigInfo({});
|
||||
const data = response.data.data;
|
||||
|
||||
// 将配置项按父级分组
|
||||
const groups = items
|
||||
.filter(item => item.parent_id === null) // 获取顶级配置项
|
||||
.map(group => ({
|
||||
...group,
|
||||
children: items.filter(item => item.parent_id === group.id) // 获取子配置项
|
||||
}));
|
||||
// 填充配置数据
|
||||
Object.assign(configData, data);
|
||||
|
||||
// 初始化图片上传列表
|
||||
groups.forEach(group => {
|
||||
group.children.forEach(config => {
|
||||
if (config.fied_key === 'web_favicon' || config.fied_key === 'login_logo' || config.fied_key === 'login_background') {
|
||||
config.fileList = config.fied_value ? [{ url: config.fied_value }] : [];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 避免直接修改 reactive 对象,使用重新赋值的方式
|
||||
updateState.length = 0; // 清空数组
|
||||
updateState.push(...groups); // 添加新数据
|
||||
// 初始化文件上传列表
|
||||
if (data.favicon) {
|
||||
faviconFileList.value = [{ url: data.favicon }];
|
||||
}
|
||||
if (data.logo) {
|
||||
logoFileList.value = [{ url: data.logo }];
|
||||
}
|
||||
if (data.background) {
|
||||
backgroundFileList.value = [{ url: data.background }];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载配置数据失败:', error);
|
||||
message.error('加载配置数据失败');
|
||||
@@ -103,7 +169,7 @@ const beforeUpload = (file: File) => {
|
||||
};
|
||||
|
||||
// 自定义上传逻辑
|
||||
const handleUpload = async (options: any, config: any) => {
|
||||
const handleUpload = async (options: any, type: string) => {
|
||||
const { file, onSuccess, onError } = options;
|
||||
|
||||
try {
|
||||
@@ -111,11 +177,19 @@ const handleUpload = async (options: any, config: any) => {
|
||||
formData.append('file', file);
|
||||
|
||||
const response = await uploadFile(formData);
|
||||
const fileUrl = response.data.data.file_url; // 使用 file_url 更新配置项的值
|
||||
const fileUrl = response.data.data.file_url;
|
||||
|
||||
// 更新配置项的值
|
||||
config.fied_value = fileUrl;
|
||||
config.fileList = [{ url: fileUrl }]; // 更新文件列表显示
|
||||
// 更新配置数据
|
||||
if (type === 'favicon') {
|
||||
configData.favicon = fileUrl;
|
||||
faviconFileList.value = [{ url: fileUrl }];
|
||||
} else if (type === 'logo') {
|
||||
configData.logo = fileUrl;
|
||||
logoFileList.value = [{ url: fileUrl }];
|
||||
} else if (type === 'background') {
|
||||
configData.background = fileUrl;
|
||||
backgroundFileList.value = [{ url: fileUrl }];
|
||||
}
|
||||
|
||||
onSuccess(response, file);
|
||||
message.success('上传成功');
|
||||
@@ -129,28 +203,8 @@ const handleUpload = async (options: any, config: any) => {
|
||||
// 保存配置
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 提取所有配置项(包括父级和子级)
|
||||
const configs = updateState.flatMap(group => [
|
||||
{
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
order: group.order,
|
||||
fied_key: group.fied_key,
|
||||
fied_value: group.fied_value,
|
||||
parent_id: group.parent_id,
|
||||
},
|
||||
...group.children.map(config => ({
|
||||
id: config.id,
|
||||
name: config.name,
|
||||
order: config.order,
|
||||
fied_key: config.fied_key,
|
||||
fied_value: config.fied_value,
|
||||
parent_id: config.parent_id,
|
||||
})),
|
||||
]);
|
||||
|
||||
// 调用批量保存接口,直接发送数组
|
||||
await batchConfig(configs); // 修改为直接发送数组
|
||||
// 调用更新配置接口
|
||||
await updateConfig(configData);
|
||||
message.success('配置保存成功');
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
export interface tableDataType {
|
||||
id?: number;
|
||||
name?: string;
|
||||
order?: number;
|
||||
fied_key?: string;
|
||||
fied_value?: string;
|
||||
parent_id?: number;
|
||||
fileList?: any[];
|
||||
children?: tableDataType[];
|
||||
}
|
||||
id?: number; // id 是可选的,类型为 number
|
||||
title: string; // 网站标题
|
||||
favicon: string; // 网站图标
|
||||
logo: string; // 登录页Logo
|
||||
background: string; // 登录页背景图
|
||||
description: string; // 网站描述
|
||||
copyright: string; // 版权信息
|
||||
keep_record: string; // 备案号
|
||||
help_url: string; // 帮助链接
|
||||
privacy_url: string; // 隐私条款链接
|
||||
clause_url: string; // 服务条款链接
|
||||
code_url: string; // 代码仓库链接
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user