基本框架

This commit is contained in:
zhang zhuo 2025-06-05 11:09:26 +08:00
parent be9750b9eb
commit b1695084f2
14 changed files with 197 additions and 123 deletions

View File

@ -20,6 +20,10 @@
if (dark) { if (dark) {
document.documentElement.classList.add("dark") document.documentElement.classList.add("dark")
} }
var weak = window.localStorage.getItem('APP_WEAK');
if (weak) {
document.documentElement.classList.add("weak")
}
</script> </script>
<div id="app" class="pi"> <div id="app" class="pi">
<div class="app-loading"> <div class="app-loading">

View File

@ -6,22 +6,38 @@
<script setup> <script setup>
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {computed} from 'vue' import {computed, ref} from 'vue'
import tools from "@/utils/tools.js";
import colorTool from '@/utils/color'
const {locale, messages} = useI18n() const {locale, messages} = useI18n()
const config = { const config = ref({
size: "default", size: "default",
zIndex: 2000, zIndex: 2000,
button: { button: {
autoInsertSpace: false autoInsertSpace: false
} }
} })
//
const app_color = tools.data.get('APP_COLOR') || "#409EFF"
const locale2 = computed(() => { const locale2 = computed(() => {
return messages.value[locale.value] return messages.value[locale.value]
}) })
console.log('%c SAAS %c 里派提供技术支持', 'background:#4caf50;color:#fff;border-radius:3px;', '') console.log('%c SAAS %c 里派提供技术支持', 'background:#4caf50;color:#fff;border-radius:3px;', '')
if(app_color){
document.documentElement.style.setProperty('--el-color-primary', app_color);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(app_color,i/10));
}
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(app_color,i/10));
}
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -15,4 +15,7 @@ export default {
TOKEN_NAME: "Authorization", TOKEN_NAME: "Authorization",
//Token前缀注意最后有个空格如不需要需设置空字符串 //Token前缀注意最后有个空格如不需要需设置空字符串
TOKEN_PREFIX: "Bearer ", TOKEN_PREFIX: "Bearer ",
//布局 默认default | 通栏header | 经典menu | 功能坞dock
//dock将关闭标签和面包屑栏
APP_LAYOUT: 'header',
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<el-form ref="form" label-width="120px" label-position="left" style="padding:0 20px;"> <el-form ref="form" label-width="120px" label-position="left" style="padding:0 20px;">
<el-divider></el-divider> <el-divider></el-divider>
<el-form-item :label="$t('user.nightmode')"> <el-form-item :label="t('user.nightmode')">
<el-switch v-model="dark"></el-switch> <el-switch v-model="dark"></el-switch>
</el-form-item> </el-form-item>
<el-form-item :label="$t('user.language')"> <el-form-item :label="$t('user.language')">
@ -11,84 +11,103 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-divider></el-divider> <el-divider></el-divider>
<el-form-item label="主题颜色"> <el-form-item :label="t('system.primaryColor')">
<el-color-picker v-model="colorPrimary" :predefine="colorList">></el-color-picker> <el-color-picker v-model="colorPrimary" :predefine="colorList"></el-color-picker>
</el-form-item>
<el-form-item :label="t('system.weakMode')">
<el-switch v-model="weakMode"></el-switch>
</el-form-item> </el-form-item>
<el-divider></el-divider> <el-divider></el-divider>
<el-form-item label="框架布局"> <el-form-item :label="t('system.layout')">
<el-select v-model="layout" placeholder="请选择"> <el-select v-model="layout" :placeholder="t('system.pleaseSelect')">
<el-option label="默认" value="default"></el-option> <el-option :label="t('system.default')" value="default"></el-option>
<el-option label="通栏" value="header"></el-option> <el-option :label="t('system.fullWidth')" value="header"></el-option>
<el-option label="经典" value="menu"></el-option> <el-option :label="t('system.classic')" value="menu"></el-option>
<el-option label="功能坞" value="dock"></el-option> <el-option :label="t('system.dock')" value="dock"></el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="折叠菜单"> <el-form-item :label="t('system.collapseMenu')">
<el-switch v-model="menuIsCollapse"></el-switch> <el-switch v-model="menuIsCollapse"></el-switch>
</el-form-item> </el-form-item>
<el-form-item label="标签栏"> <el-form-item :label="t('system.showLabels')">
<el-switch v-model="layoutTags"></el-switch> <el-switch v-model="layoutTags"></el-switch>
</el-form-item> </el-form-item>
<el-divider></el-divider> <el-divider></el-divider>
</el-form> </el-form>
</template> </template>
<script> <script setup>
import {ref, watch} from "vue";
import colorTool from '@/utils/color' import colorTool from '@/utils/color'
import tools from "@/utils/tools";
import config from "@/config";
import store from '@/store'
import {useI18n} from "vue-i18n";
export default { const {t, locale} = useI18n()
data(){
return { let layout = ref(tools.data.get('APP_LAYOUT') || store.state.global.layout);
layout: this.$TOOL.data.get('APP_LAYOUT') || this.$store.state.global.layout, let menuIsCollapse = ref(store.state.global.menuIsCollapse);
menuIsCollapse: this.$store.state.global.menuIsCollapse, let layoutTags = ref(store.state.global.layoutTags);
layoutTags: this.$store.state.global.layoutTags, let lang = ref(tools.data.get('APP_LANG') || config.LANG);
lang: this.$TOOL.data.get('APP_LANG') || this.$CONFIG.LANG, let dark = ref(tools.data.get('APP_DARK') || false);
dark: this.$TOOL.data.get('APP_DARK') || false, let colorList = ref(['#409EFF', '#009688', '#536dfe', '#ff5c93', '#c62f2f', '#fd726d']);
colorList: ['#409EFF', '#009688', '#536dfe', '#ff5c93', '#c62f2f', '#fd726d'], let colorPrimary = ref(tools.data.get('APP_COLOR') || '#409EFF');
colorPrimary: this.$TOOL.data.get('APP_COLOR') || this.$CONFIG.COLOR || '#409EFF' let weakMode = ref(tools.data.get('APP_WEAK') || false)
}
}, watch(layout, (val) => {
watch: { tools.data.set("APP_LAYOUT", val);
layout(val) { store.commit("SET_layout", val)
this.$TOOL.data.set("APP_LAYOUT", val); })
this.$store.commit("SET_layout", val)
}, watch(menuIsCollapse, () => {
menuIsCollapse(){ store.commit("TOGGLE_menuIsCollapse")
this.$store.commit("TOGGLE_menuIsCollapse") })
},
layoutTags(){ watch(layoutTags, () => {
this.$store.commit("TOGGLE_layoutTags") store.commit("TOGGLE_layoutTags")
}, })
dark(val){
if(val){ watch(dark, (val) => {
document.documentElement.classList.add("dark") if (val) {
this.$TOOL.data.set("APP_DARK", val) document.documentElement.classList.add("dark")
}else{ tools.data.set("APP_DARK", val)
document.documentElement.classList.remove("dark") } else {
this.$TOOL.data.remove("APP_DARK") document.documentElement.classList.remove("dark")
} tools.data.remove("APP_DARK")
},
lang(val){
this.$i18n.locale = val
this.$TOOL.data.set("APP_LANG", val);
},
colorPrimary(val){
if(!val){
val = '#409EFF'
this.colorPrimary = '#409EFF'
}
document.documentElement.style.setProperty('--el-color-primary', val);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(val,i/10));
}
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(val,i/10));
}
this.$TOOL.data.set("APP_COLOR", val);
}
} }
} })
watch(weakMode, (val) => {
if (val) {
document.documentElement.classList.add("weak")
tools.data.set("APP_WEAK", val)
} else {
document.documentElement.classList.remove("weak")
tools.data.remove("APP_WEAK")
}
})
watch(lang, (val) => {
locale.value = val
tools.data.set("APP_LANG", val);
})
watch(colorPrimary, (val) => {
if (!val) {
val = '#409EFF'
colorPrimary = '#409EFF'
}
document.documentElement.style.setProperty('--el-color-primary', val);
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-light-${i}`, colorTool.lighten(val, i / 10));
}
for (let i = 1; i <= 9; i++) {
document.documentElement.style.setProperty(`--el-color-primary-dark-${i}`, colorTool.darken(val, i / 10));
}
tools.data.set("APP_COLOR", val);
})
</script> </script>
<style> <style scoped>
</style> </style>

View File

@ -174,15 +174,15 @@ function closeSelectedTag(tag, autoPushLatestView = true) {
function openContextMenu(e, tag) { function openContextMenu(e, tag) {
contextMenuItem = tag; contextMenuItem = tag;
contextMenuVisible = true; contextMenuVisible = true;
left = e.clientX + 1; left.value = e.clientX + 1;
top = e.clientY + 1; top.value = e.clientY + 1;
//FIX //FIX
nextTick(() => { nextTick(() => {
let sp = document.getElementById("contextmenu"); let sp = document.getElementById("contextmenu");
if (document.body.offsetWidth - e.clientX < sp.offsetWidth) { if (document.body.offsetWidth - e.clientX < sp.offsetWidth) {
left = document.body.offsetWidth - sp.offsetWidth + 1; left.value = document.body.offsetWidth - sp.offsetWidth + 1;
top = e.clientY + 1; top.value = e.clientY + 1;
} }
}) })
} }
@ -190,13 +190,13 @@ function openContextMenu(e, tag) {
// //
function closeMenu() { function closeMenu() {
contextMenuItem = null; contextMenuItem = null;
contextMenuVisible = false contextMenuVisible.value = false
} }
//TAB //TAB
function refreshTab() { function refreshTab() {
const nowTag = contextMenuItem; const nowTag = contextMenuItem;
contextMenuVisible = false contextMenuVisible.value = false
// //
if (route.fullPath != nowTag.fullPath) { if (route.fullPath != nowTag.fullPath) {
router.push({ router.push({
@ -220,7 +220,7 @@ function closeTabs() {
var nowTag = contextMenuItem; var nowTag = contextMenuItem;
if (!nowTag.meta.affix) { if (!nowTag.meta.affix) {
closeSelectedTag(nowTag) closeSelectedTag(nowTag)
contextMenuVisible = false contextMenuVisible.value = false
} }
} }
@ -242,13 +242,13 @@ function closeOtherTabs() {
closeSelectedTag(tag, false) closeSelectedTag(tag, false)
} }
}) })
contextMenuVisible = false contextMenuVisible.value = false
} }
//TAB //TAB
function maximize() { function maximize() {
var nowTag = contextMenuItem; var nowTag = contextMenuItem;
contextMenuVisible = false contextMenuVisible.value = false
// //
if (route.fullPath != nowTag.fullPath) { if (route.fullPath != nowTag.fullPath) {
router.push({ router.push({

View File

@ -51,7 +51,7 @@
<el-icon> <el-icon>
<pi-icon-brush/> <pi-icon-brush/>
</el-icon> </el-icon>
<el-drawer title="页面布局" v-model="settingDialog" :size="400" append-to-body destroy-on-close> <el-drawer :title="t('system.pageLayout')" v-model="settingDialog" :size="400" append-to-body destroy-on-close>
<setting></setting> <setting></setting>
</el-drawer> </el-drawer>
</div> </div>
@ -64,8 +64,8 @@
</el-drawer> </el-drawer>
</template> </template>
<script setup> <script setup name="userbar">
import {ref, onMounted} from "vue"; import {ref, onMounted, getCurrentInstance} from "vue";
import search from './search.vue' import search from './search.vue'
import message from './message.vue' import message from './message.vue'
import setting from './setting.vue' import setting from './setting.vue'
@ -74,12 +74,18 @@ import websocket from "@/utils/websocket";
import {ElNotification} from 'element-plus'; import {ElNotification} from 'element-plus';
import tools from "@/utils/tools.js"; import tools from "@/utils/tools.js";
import api from "@/api/index.js"; import api from "@/api/index.js";
import {useRouter} from "vue-router";
import {useI18n} from "vue-i18n";
const {proxy} = getCurrentInstance()
const router = useRouter()
const {t} = useI18n()
let searchVisible = ref(false) let searchVisible = ref(false)
let msg = ref(false) let msg = ref(false)
let msgNum = ref(0) let msgNum = ref(0)
const tasksVisible = ref(false) const tasksVisible = ref(false)
const taskNum = ref(0) let taskNum = ref(0)
const settingDialog = ref(false) const settingDialog = ref(false)
const userInfo = tools.data.get("USER_INFO"); const userInfo = tools.data.get("USER_INFO");
@ -119,9 +125,9 @@ function messageHandle(message) {
}) })
} }
if (data.type == "message") { if (data.type == "message") {
this.msgNum++ msgNum.value++
} else if (data.type == "task") { } else if (data.type == "task") {
this.taskNum++ taskNum.value++
} }
return; return;
} }
@ -135,38 +141,40 @@ function closeHandle() {
// //
function handleUser(command) { function handleUser(command) {
if (command === "uc") { if (command === "uc") {
this.$router.push({path: '/usercenter'}); router.push({path: '/usercenter'});
} }
if (command === "cmd") { if (command === "cmd") {
this.$router.push({path: '/cmd'}); router.push({path: '/cmd'});
} }
if (command === "clearCache") { if (command === "clearCache") {
this.$confirm('清除缓存会将系统初始化,包括登录状态、主题、语言设置等,是否继续?', '提示', { proxy.$confirm('清除缓存会将系统初始化,包括登录状态、主题、语言设置等,是否继续?', '提示', {
type: 'info', type: 'info',
}).then(async () => { }).then(async () => {
const loading = this.$loading() const loading = proxy.$loading()
tools.data.clear() tools.data.clear()
const [res, err] = await tools.go(api.auth.logout()) const [res, err] = await tools.go(api.auth.logout())
if (!err) { if (!err) {
loading.close() loading.close()
location.reload() location.reload()
this.$message.success(res.msg) proxy.$message.success(res.msg)
this.$router.replace({path: '/login'}); router.replace({path: '/login'});
} }
}) }).catch(() => {
});
} }
if (command === "outLogin") { if (command === "outLogin") {
this.$confirm('确认是否退出当前用户?', '提示', { proxy.$confirm('确认是否退出当前用户?', '提示', {
type: 'warning', type: 'warning',
confirmButtonText: '退出', confirmButtonText: t("system.logout"),
confirmButtonClass: 'el-button--danger' confirmButtonClass: 'el-button--danger'
}).then(async () => { }).then(async () => {
const [res, err] = await tools.go(api.auth.logout()) const [res, err] = await tools.go(api.auth.logout())
if (!err) { if (!err) {
this.$message.success(res.msg) proxy.$message.success(res.msg)
this.$router.replace({path: '/login'}); router.replace({path: '/login'});
} }
}) }).catch(() => {
});
} }
} }
@ -177,14 +185,14 @@ function screen() {
// //
function showMsg() { function showMsg() {
msg = true msg.value = true
} }
function closeMsg(num) { function closeMsg(num) {
if (num > -1) { if (num > -1) {
msgNum = num msgNum.value = num
} }
msg = false msg.value = false
} }
async function loadData() { async function loadData() {

View File

@ -289,3 +289,9 @@ function onLayoutResize() {
store.commit("SET_ismobile", document.body.clientWidth < 992) store.commit("SET_ismobile", document.body.clientWidth < 992)
} }
</script> </script>
<style lang="scss" scoped>
:deep(.el-menu--horizontal) {
height: auto;
}
</style>

View File

@ -4,16 +4,16 @@ import el_en from 'element-plus/dist/locale/en'
import config from "@/config" import config from "@/config"
import tools from '@/utils/tools' import tools from '@/utils/tools'
import zh_cn from './lang/zh-cn.js' import zh_cn from './lang/zh-cn'
import en from './lang/en.js' import en from './lang/en'
const messages = { const messages = {
'zh-cn': { 'zh-cn': {
el: el_zh_cn, ...el_zh_cn,
...zh_cn ...zh_cn
}, },
'en': { 'en': {
el: el_en, ...el_en,
...en ...en
} }
} }

View File

@ -2,10 +2,21 @@ export default {
system: { system: {
webTitleDev: 'Property Management System (Testing)', webTitleDev: 'Property Management System (Testing)',
webTitle: 'Property Management System', webTitle: 'Property Management System',
logout: 'Log out',
cancel: 'Cancel',
primaryColor: 'Primary Color',
weakMode: 'Weak Mode',
layout: 'Layout',
pageLayout: 'Page Layout',
pleaseSelect: 'Please Select',
default: 'Default',
fullWidth: 'Full-width',
classic: 'Classic',
dock: 'Dock',
collapseMenu: 'Collapse Menu',
showLabels: 'Show Labels',
}, },
login: { login: {
slogan: 'SpringCloud Alibaba Microservices Architecture',
describe: 'The community group',
signInTitle: 'Sign in', signInTitle: 'Sign in',
accountLogin: 'Account sign in', accountLogin: 'Account sign in',
rememberMe: 'Remember me', rememberMe: 'Remember me',
@ -18,18 +29,15 @@ export default {
codePlaceholder: 'Please input a code', codePlaceholder: 'Please input a code',
PWError: 'Please input a password', PWError: 'Please input a password',
codeError: 'Please input a code', codeError: 'Please input a code',
wechatLoginTitle: 'QR code sign in',
wechatLoginMsg: 'Please use wechat to scan and log in | Auto scan after 3 seconds of simulation',
wechatLoginResult: 'Scanned | Please click authorize login in the device',
backLogin: 'Return to Login' backLogin: 'Return to Login'
}, },
user: { user: {
dynamic: 'Dynamic', dynamic: 'Dynamic',
info: 'User Info', info: 'User Info',
settings: 'Settings', settings: 'Settings',
nightmode: 'night mode', nightmode: 'Night Mode',
nightmode_msg: 'Suitable for low light environment,The current night mode is beta', nightmode_msg: 'Suitable for low light environment,The current night mode is beta',
language: 'language', language: 'Language',
language_msg: 'Translation in progress,Temporarily translated the text of this view', language_msg: 'Translation in progress,Temporarily translated the text of this view',
} }
} }

View File

@ -2,10 +2,21 @@ export default {
system: { system: {
webTitleDev: '物业管理系统(测试)', webTitleDev: '物业管理系统(测试)',
webTitle: '物业管理系统', webTitle: '物业管理系统',
logout: '退出',
cancel: '取消',
primaryColor: '主题颜色',
weakMode: '色弱模式',
layout: '框架布局',
pageLayout: '页面布局',
pleaseSelect: '请选择',
default: '默认',
fullWidth: '通栏',
classic: '经典',
dock: '功能坞',
collapseMenu: '折叠菜单',
showLabels: '显示标签',
}, },
login: { login: {
slogan: 'SpringCloud Alibaba 微服务架构',
describe: '便民服务',
signInTitle: '用户登录', signInTitle: '用户登录',
accountLogin: '账号登录', accountLogin: '账号登录',
rememberMe: '记住密码', rememberMe: '记住密码',
@ -18,9 +29,6 @@ export default {
codePlaceholder: '请输入验证码', codePlaceholder: '请输入验证码',
PWError: '请输入密码', PWError: '请输入密码',
codeError: '请输入验证码', codeError: '请输入验证码',
wechatLoginTitle: '二维码登录',
wechatLoginMsg: '请使用微信扫一扫登录 | 模拟3秒后自动扫描',
wechatLoginResult: '已扫描 | 请点击授权登录',
backLogin: '返回登录' backLogin: '返回登录'
}, },
user: { user: {

View File

@ -1,11 +1,12 @@
import tools from "@/utils/tools"; import tools from "@/utils/tools";
import config from "@/config";
export default { export default {
state: { state: {
//移动端布局 //移动端布局
ismobile: false, ismobile: false,
//布局 //布局
layout: tools.data.get('APP_LAYOUT') || 'default', layout: tools.data.get('APP_LAYOUT') || config.APP_LAYOUT || 'header',
//菜单是否折叠 toggle //菜单是否折叠 toggle
menuIsCollapse: false, menuIsCollapse: false,
//多标签栏 //多标签栏

View File

@ -35,3 +35,6 @@ html.dark {
.el-table .el-table__body-wrapper {background: var(--el-bg-color);} .el-table .el-table__body-wrapper {background: var(--el-bg-color);}
.el-table th.is-sortable:hover {background: #111;} .el-table th.is-sortable:hover {background: #111;}
} }
// 色弱模式
html.weak {-webkit-filter: invert(80%);filter: invert(80%);}

View File

@ -21,8 +21,7 @@ export default (error, vm)=>{
// } // }
// var errorName = errorMap[error.name] || "未知错误" // var errorName = errorMap[error.name] || "未知错误"
console.warn(`[PI error]: ${error}`); console.error(`[PI error]: ${error}`);
console.error(error);
//throw error; //throw error;
vm.$nextTick(() => { vm.$nextTick(() => {

View File

@ -36,17 +36,17 @@
</div> </div>
</div> </div>
<el-form ref="loginForm" :model="form" :rules="rules" label-width="0" size="large"> <el-form ref="loginForm" :model="form" :rules="rules" label-width="0" size="large">
<el-form-item prop="user"> <el-form-item prop="username">
<el-input v-model="form.username" prefix-icon="el-icon-user" clearable <el-input v-model="form.username" prefix-icon="el-icon-user" clearable
:placeholder="t('login.userPlaceholder')"></el-input> :placeholder="t('login.userPlaceholder')" @keyup.enter="login"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input v-model="form.password" prefix-icon="el-icon-lock" clearable show-password <el-input v-model="form.password" prefix-icon="el-icon-lock" clearable show-password
:placeholder="t('login.PWPlaceholder')"></el-input> :placeholder="t('login.PWPlaceholder')" @keyup.enter="login"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="veryCode"> <el-form-item prop="code">
<el-input v-model="form.code" prefix-icon="el-icon-iphone" clearable <el-input v-model="form.code" prefix-icon="el-icon-iphone" clearable
:placeholder="t('login.codePlaceholder')"> :placeholder="t('login.codePlaceholder')" @keyup.enter="login">
<template #append> <template #append>
<el-image :src="verImage" @click="getCode" style="padding: 1px;"/> <el-image :src="verImage" @click="getCode" style="padding: 1px;"/>
</template> </template>
@ -159,8 +159,7 @@ async function getCode() {
async function login() { async function login() {
// //
const validate = await proxy.$refs.loginForm.validate().catch(() => { const validate = await proxy.$refs.loginForm.validate().catch(() => {});
});
if (!validate) { if (!validate) {
return false return false
} }