基本框架

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) {
document.documentElement.classList.add("dark")
}
var weak = window.localStorage.getItem('APP_WEAK');
if (weak) {
document.documentElement.classList.add("weak")
}
</script>
<div id="app" class="pi">
<div class="app-loading">

View File

@ -6,22 +6,38 @@
<script setup>
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 config = {
const config = ref({
size: "default",
zIndex: 2000,
button: {
autoInsertSpace: false
}
}
})
//
const app_color = tools.data.get('APP_COLOR') || "#409EFF"
const locale2 = computed(() => {
return messages.value[locale.value]
})
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>
<style lang="scss">

View File

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

View File

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

View File

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

View File

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

View File

@ -289,3 +289,9 @@ function onLayoutResize() {
store.commit("SET_ismobile", document.body.clientWidth < 992)
}
</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 tools from '@/utils/tools'
import zh_cn from './lang/zh-cn.js'
import en from './lang/en.js'
import zh_cn from './lang/zh-cn'
import en from './lang/en'
const messages = {
'zh-cn': {
el: el_zh_cn,
...el_zh_cn,
...zh_cn
},
'en': {
el: el_en,
...el_en,
...en
}
}

View File

@ -2,10 +2,21 @@ export default {
system: {
webTitleDev: 'Property Management System (Testing)',
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: {
slogan: 'SpringCloud Alibaba Microservices Architecture',
describe: 'The community group',
signInTitle: 'Sign in',
accountLogin: 'Account sign in',
rememberMe: 'Remember me',
@ -18,18 +29,15 @@ export default {
codePlaceholder: 'Please input a code',
PWError: 'Please input a password',
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'
},
user: {
dynamic: 'Dynamic',
info: 'User Info',
settings: 'Settings',
nightmode: 'night mode',
nightmode: 'Night Mode',
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',
}
}

View File

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

View File

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

View File

@ -35,3 +35,6 @@ html.dark {
.el-table .el-table__body-wrapper {background: var(--el-bg-color);}
.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] || "未知错误"
console.warn(`[PI error]: ${error}`);
console.error(error);
console.error(`[PI error]: ${error}`);
//throw error;
vm.$nextTick(() => {

View File

@ -36,17 +36,17 @@
</div>
</div>
<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
:placeholder="t('login.userPlaceholder')"></el-input>
:placeholder="t('login.userPlaceholder')" @keyup.enter="login"></el-input>
</el-form-item>
<el-form-item prop="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 prop="veryCode">
<el-form-item prop="code">
<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>
<el-image :src="verImage" @click="getCode" style="padding: 1px;"/>
</template>
@ -159,8 +159,7 @@ async function getCode() {
async function login() {
//
const validate = await proxy.$refs.loginForm.validate().catch(() => {
});
const validate = await proxy.$refs.loginForm.validate().catch(() => {});
if (!validate) {
return false
}