feat: 暗黑模式临时提交

Former-commit-id: 8653f21636d0c32f48caae65a52aca98cbef8710
This commit is contained in:
haoxr
2023-01-16 00:48:55 +08:00
parent af4fd8cb6a
commit 50cc85bad3
15 changed files with 281 additions and 271 deletions

View File

@@ -1,6 +1,7 @@
{ {
"globals": { "globals": {
"EffectScope": true, "EffectScope": true,
"ElForm": true,
"ElMessage": true, "ElMessage": true,
"ElMessageBox": true, "ElMessageBox": true,
"asyncComputed": true, "asyncComputed": true,

View File

@@ -32,13 +32,13 @@ function toggleClick() {
} }
</script> </script>
<style scoped> <style lang="scss" scoped>
.hamburger { .hamburger {
display: inline-block;
width: 20px; width: 20px;
height: 20px; height: 20px;
} &.is-active {
transform: rotate(180deg);
.hamburger.is-active { }
transform: rotate(180deg);
} }
</style> </style>

View File

@@ -69,7 +69,7 @@ onMounted(() => {
placeholder="点击选择图标" placeholder="点击选择图标"
> >
<template #prepend> <template #prepend>
<svg-icon :iconName="inputValue"></svg-icon> <svg-icon :icon-class="inputValue"></svg-icon>
</template> </template>
</el-input> </el-input>

View File

@@ -32,6 +32,7 @@ const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`);
<style scoped> <style scoped>
.svg-icon { .svg-icon {
display: inline-block;
outline: none; outline: none;
width: 1em; width: 1em;
height: 1em; height: 1em;

View File

@@ -4,25 +4,24 @@ import { Sunny, Moon } from '@element-plus/icons-vue';
import { useSettingsStore } from '@/store/modules/settings'; import { useSettingsStore } from '@/store/modules/settings';
import { useDark, useToggle } from '@vueuse/core'; import { useDark, useToggle } from '@vueuse/core';
import { ElDivider, ElSwitch, ElTooltip } from 'element-plus'; /**
import { onMounted } from 'vue'; * 暗黑模式
*/
const settingsStore = useSettingsStore(); const settingsStore = useSettingsStore();
const isDark = useDark(); const isDark = useDark();
const toggleDark = useToggle(isDark);
function toggleTheme() { /**
const isDark = useDark(); * 切换布局
useToggle(isDark); */
function changeLayout(layout: string) {
settingsStore.changeSetting({ key: 'layout', value: layout });
window.document.body.setAttribute('layout', settingsStore.layout);
} }
onMounted(() => { onMounted(() => {
window.document.body.setAttribute('layout', settingsStore.layout); window.document.body.setAttribute('layout', settingsStore.layout);
}); });
function changeLayout(layout: string) {
settingsStore.changeSetting({ key: 'layout', value: layout });
window.document.body.setAttribute('layout', settingsStore.layout);
}
</script> </script>
<template> <template>
@@ -46,11 +45,12 @@ function changeLayout(layout: string) {
<el-divider>主题</el-divider> <el-divider>主题</el-divider>
<div class="flex justify-center" @click.stop> <button @click="toggleDark()">当前状态是: {{ isDark }}</button>
<div class="flex justify-center" @click="toggleDark()">
<el-switch <el-switch
v-model="isDark" v-model="isDark"
inline-prompt inline-prompt
@change="toggleTheme"
:active-icon="Sunny" :active-icon="Sunny"
:inactive-icon="Moon" :inactive-icon="Moon"
/> />

View File

@@ -1,12 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { ComponentInternalInstance } from 'vue';
getCurrentInstance,
nextTick,
ref,
watch,
onMounted,
ComponentInternalInstance
} from 'vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import path from 'path-browserify'; import path from 'path-browserify';

View File

@@ -15,7 +15,7 @@ import i18n from '@/lang/index';
import '@/styles/index.scss'; import '@/styles/index.scss';
import 'element-plus/theme-chalk/index.css'; import 'element-plus/theme-chalk/index.css';
//import 'element-plus/theme-chalk/dark/css-vars.css'; import 'element-plus/theme-chalk/dark/css-vars.css';
const app = createApp(App); const app = createApp(App);
// 自定义指令 // 自定义指令

11
src/styles/dark.scss Normal file
View File

@@ -0,0 +1,11 @@
// only scss variables
$--colors: (
"primary": (
"base": #589ef8,
),
);
@forward "element-plus/theme-chalk/src/dark/var.scss" with (
$colors: $--colors
);

View File

@@ -10,12 +10,6 @@
font-weight: 400 !important; font-weight: 400 !important;
} }
.el-upload__input {
display: none;
}
// 选中行背景色值 // 选中行背景色值
.el-table__body tr.current-row td { .el-table__body tr.current-row td {
background-color: #e1f3d8b5 !important; background-color: #e1f3d8b5 !important;

View File

@@ -2,10 +2,8 @@
@import 'src/styles/element-plus'; @import 'src/styles/element-plus';
@import './sidebar.scss'; @import './sidebar.scss';
@import './tailwind.scss'; @import './tailwind.scss';
@import './reset.scss';
html,body,#app{
height: 100%;
}
// main-container global css // main-container global css
.app-container { .app-container {
@@ -20,6 +18,3 @@ html,body,#app{
box-shadow: 1px 1px 1px #eee; box-shadow: 1px 1px 1px #eee;
} }
svg{
display: inline-block;
}

4
src/styles/reset.scss Normal file
View File

@@ -0,0 +1,4 @@
svg{
display: inline-block;
}

View File

@@ -2,6 +2,7 @@
export {} export {}
declare global { declare global {
const EffectScope: typeof import('vue')['EffectScope'] const EffectScope: typeof import('vue')['EffectScope']
const ElForm: typeof import('element-plus/es')['ElForm']
const ElMessage: typeof import('element-plus/es')['ElMessage'] const ElMessage: typeof import('element-plus/es')['ElMessage']
const ElMessageBox: typeof import('element-plus/es')['ElMessageBox'] const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']
const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] const asyncComputed: typeof import('@vueuse/core')['asyncComputed']
@@ -266,6 +267,7 @@ import { UnwrapRef } from 'vue'
declare module 'vue' { declare module 'vue' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']> readonly EffectScope: UnwrapRef<typeof import('vue')['EffectScope']>
readonly ElForm: UnwrapRef<typeof import('element-plus/es')['ElForm']>
readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']> readonly ElMessage: UnwrapRef<typeof import('element-plus/es')['ElMessage']>
readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']> readonly ElMessageBox: UnwrapRef<typeof import('element-plus/es')['ElMessageBox']>
readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']> readonly asyncComputed: UnwrapRef<typeof import('@vueuse/core')['asyncComputed']>

View File

@@ -55,6 +55,8 @@ declare module '@vue/runtime-core' {
IEpClose: typeof import('~icons/ep/close')['default'] IEpClose: typeof import('~icons/ep/close')['default']
IEpDownload: typeof import('~icons/ep/download')['default'] IEpDownload: typeof import('~icons/ep/download')['default']
IEpPlus: typeof import('~icons/ep/plus')['default'] IEpPlus: typeof import('~icons/ep/plus')['default']
IEpRefresh: typeof import('~icons/ep/refresh')['default']
IEpSearch: typeof import('~icons/ep/search')['default']
IEpTop: typeof import('~icons/ep/top')['default'] IEpTop: typeof import('~icons/ep/top')['default']
LangSelect: typeof import('./../components/LangSelect/index.vue')['default'] LangSelect: typeof import('./../components/LangSelect/index.vue')['default']
MultiUpload: typeof import('./../components/Upload/MultiUpload.vue')['default'] MultiUpload: typeof import('./../components/Upload/MultiUpload.vue')['default']

View File

@@ -1,3 +1,215 @@
<script lang="ts">
export default {
name: 'cmenu'
};
</script>
<script setup lang="ts">
import { MenuQuery, MenuForm, Menu } from '@/api/menu/types';
// API 依赖
import {
listMenus,
getMenuDetail,
listMenuOptions,
addMenu,
deleteMenus,
updateMenu
} from '@/api/menu';
import SvgIcon from '@/components/SvgIcon/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';
const emit = defineEmits(['menuClick']);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
queryParams: {} as MenuQuery,
menuList: [] as Menu[],
dialog: { visible: false } as DialogType,
formData: {
parentId: '0',
name: '',
visible: 1,
sort: 1,
component: undefined,
type: 'MENU'
} as MenuForm,
rules: {
parentId: [{ required: true, message: '请选择顶级菜单', trigger: 'blur' }],
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择菜单类型', trigger: 'blur' }],
path: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],
component: [
{ required: true, message: '请输入组件完整路径', trigger: 'blur' }
]
},
menuOptions: [] as OptionType[],
currentRow: undefined,
cacheData: {
menuType: '',
menuPath: ''
}
});
const {
loading,
queryParams,
menuList,
dialog,
formData,
rules,
menuOptions,
cacheData
} = toRefs(state);
/**
* 查询
*/
function handleQuery() {
// 重置父组件
emit('menuClick', null);
loading.value = true;
listMenus(state.queryParams).then(({ data }) => {
menuList.value = data;
loading.value = false;
});
}
/**
* 下拉菜单
*/
async function loadMenuData() {
listMenuOptions().then(({ data }) => {
menuOptions.value = [{ value: '0', label: '顶级菜单', children: data }];
});
}
/**
* 查询重置
*/
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleRowClick(row: any) {
state.currentRow = JSON.parse(JSON.stringify(row));
emit('menuClick', row);
}
/**
* 新增菜单
*/
async function handleAdd(row: any) {
dialog.value = {
title: '添加菜单',
visible: true
};
await loadMenuData();
if (row.id) {
// 行点击新增
formData.value.parentId = row.id;
} else {
// 工具栏新增
if (state.currentRow) {
// 选择行
formData.value.parentId = (state.currentRow as any).id;
} else {
// 未选择行
formData.value.parentId = '0';
}
}
}
/**
* 编辑菜单
*/
async function handleUpdate(row: MenuForm) {
await loadMenuData();
dialog.value = {
title: '编辑菜单',
visible: true
};
const id = row.id as string;
getMenuDetail(id).then(({ data }) => {
state.formData = data;
cacheData.value.menuType = data.type;
cacheData.value.menuPath = data.path;
});
}
/**
* 菜单类型 change
*/
function handleMenuTypeChange(menuType: any) {
if (menuType !== cacheData.value.menuType) {
formData.value.path = '';
} else {
formData.value.path = cacheData.value.menuPath;
}
}
/**
* 菜单提交
*/
function submitForm() {
dataFormRef.value.validate((isValid: boolean) => {
if (isValid) {
if (state.formData.id) {
updateMenu(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addMenu(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 删除菜单
*
*/
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
deleteMenus(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
/**
* 取消关闭弹窗
*/
function cancel() {
formData.value.id = undefined;
dataFormRef.value.resetFields();
dialog.value.visible = false;
}
onMounted(() => {
handleQuery();
});
</script>
<template> <template>
<div class="app-container"> <div class="app-container">
<div class="search"> <div class="search">
@@ -11,10 +223,13 @@
/> />
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" :icon="Search" @click="handleQuery" <el-button type="primary" @click="handleQuery"
>搜索</el-button ><template #icon><i-ep-search /></template>搜索</el-button
>
<el-button @click="resetQuery">
<template #icon><i-ep-refresh /></template>
重置</el-button
> >
<el-button :icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@@ -22,8 +237,9 @@
<!-- 数据表格 --> <!-- 数据表格 -->
<el-card shadow="never"> <el-card shadow="never">
<template #header> <template #header>
<el-button type="success" :icon="Plus" @click="handleAdd" <el-button type="success" @click="handleAdd">
>新增</el-button <template #icon><i-ep-plus /></template>
新增</el-button
> >
</template> </template>
@@ -122,7 +338,6 @@
</el-table> </el-table>
</el-card> </el-card>
<!-- dialog -->
<el-dialog <el-dialog
:title="dialog.title" :title="dialog.title"
v-model="dialog.visible" v-model="dialog.visible"
@@ -251,214 +466,3 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts">
import { Search, Plus, Refresh } from '@element-plus/icons-vue';
import { ElForm, ElMessage, ElMessageBox } from 'element-plus';
import { MenuQuery, MenuForm, Menu } from '@/api/menu/types';
// API 依赖
import {
listMenus,
getMenuDetail,
listMenuOptions,
addMenu,
deleteMenus,
updateMenu
} from '@/api/menu';
import SvgIcon from '@/components/SvgIcon/index.vue';
import IconSelect from '@/components/IconSelect/index.vue';
const emit = defineEmits(['menuClick']);
const queryFormRef = ref(ElForm);
const dataFormRef = ref(ElForm);
const state = reactive({
loading: true,
// 选中ID数组
ids: [],
queryParams: {} as MenuQuery,
menuList: [] as Menu[],
dialog: { visible: false } as DialogType,
formData: {
parentId: '0',
name: '',
visible: 1,
sort: 1,
component: undefined,
type: 'MENU'
} as MenuForm,
rules: {
parentId: [{ required: true, message: '请选择顶级菜单', trigger: 'blur' }],
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
type: [{ required: true, message: '请选择菜单类型', trigger: 'blur' }],
path: [{ required: true, message: '请输入路由路径', trigger: 'blur' }],
component: [
{ required: true, message: '请输入组件完整路径', trigger: 'blur' }
]
},
menuOptions: [] as OptionType[],
currentRow: undefined,
cacheData: {
menuType: '',
menuPath: ''
}
});
const {
loading,
queryParams,
menuList,
dialog,
formData,
rules,
menuOptions,
cacheData
} = toRefs(state);
/**
* 查询
*/
function handleQuery() {
// 重置父组件
emit('menuClick', null);
loading.value = true;
listMenus(state.queryParams).then(({ data }) => {
menuList.value = data;
loading.value = false;
});
}
/**
* 下拉菜单
*/
async function loadMenuData() {
await listMenuOptions().then(({ data }) => {
menuOptions.value = [{ value: '0', label: '顶级菜单', children: data }];
});
}
/**
* 查询重置
*/
function resetQuery() {
queryFormRef.value.resetFields();
handleQuery();
}
function handleRowClick(row: any) {
state.currentRow = JSON.parse(JSON.stringify(row));
emit('menuClick', row);
}
/**
* 新增菜单
*/
async function handleAdd(row: any) {
formData.value.id = undefined;
await loadMenuData();
dialog.value = {
title: '添加菜单',
visible: true
};
if (row.id) {
// 行点击新增
formData.value.parentId = row.id;
} else {
// 工具栏新增
if (state.currentRow) {
// 选择行
formData.value.parentId = (state.currentRow as any).id;
} else {
// 未选择行
formData.value.parentId = '0';
}
}
}
/**
* 编辑菜单
*/
async function handleUpdate(row: MenuForm) {
await loadMenuData();
dialog.value = {
title: '编辑菜单',
visible: true
};
const id = row.id as string;
getMenuDetail(id).then(({ data }) => {
state.formData = data;
cacheData.value.menuType = data.type;
cacheData.value.menuPath = data.path;
});
}
/**
* 菜单类型 change
*/
function handleMenuTypeChange(menuType: any) {
if (menuType !== cacheData.value.menuType) {
formData.value.path = '';
} else {
formData.value.path = cacheData.value.menuPath;
}
}
/**
* 菜单提交
*/
function submitForm() {
dataFormRef.value.validate((isValid: boolean) => {
if (isValid) {
if (state.formData.id) {
updateMenu(state.formData.id, state.formData).then(() => {
ElMessage.success('修改成功');
cancel();
handleQuery();
});
} else {
addMenu(state.formData).then(() => {
ElMessage.success('新增成功');
cancel();
handleQuery();
});
}
}
});
}
/**
* 删除菜单
*
* @param row
*/
function handleDelete(row: any) {
const ids = [row.id || state.ids].join(',');
ElMessageBox.confirm('确认删除已选中的数据项?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
deleteMenus(ids).then(() => {
ElMessage.success('删除成功');
handleQuery();
});
})
.catch(() => ElMessage.info('已取消删除'));
}
/**
* 取消关闭弹窗
*/
function cancel() {
dataFormRef.value.resetFields();
state.dialog.visible = false;
}
onMounted(() => {
handleQuery();
});
</script>

View File

@@ -40,14 +40,17 @@ const state = reactive({
title: '', title: '',
visible: false visible: false
} as DialogType, } as DialogType,
formData: {} as RoleForm, formData: {
sort: 1,
status: 1
} as RoleForm,
rules: { rules: {
name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }], name: [{ required: true, message: '请输入角色名称', trigger: 'blur' }],
code: [{ required: true, message: '请输入角色编码', trigger: 'blur' }], code: [{ required: true, message: '请输入角色编码', trigger: 'blur' }],
dataScope: [{ required: true, message: '请选择数据权限', trigger: 'blur' }], dataScope: [{ required: true, message: '请选择数据权限', trigger: 'blur' }],
status: [{ required: true, message: '请选择状态', trigger: 'blur' }] status: [{ required: true, message: '请选择状态', trigger: 'blur' }]
}, },
allocationDialogVisible: false, resourceDialogVisible: false,
resourceOptions: [] as OptionType[], resourceOptions: [] as OptionType[],
// 勾选的菜单ID // 勾选的菜单ID
checkedMenuIds: new Set([]), checkedMenuIds: new Set([]),
@@ -67,7 +70,7 @@ const {
dialog, dialog,
formData, formData,
rules, rules,
allocationDialogVisible, resourceDialogVisible,
checkedRole, checkedRole,
resourceOptions resourceOptions
} = toRefs(state); } = toRefs(state);
@@ -171,8 +174,8 @@ function handleDelete(row: any) {
/** /**
* 资源分配弹窗 * 资源分配弹窗
*/ */
function showAllocationDialog(row: Role) { function openResourceDialog(row: Role) {
allocationDialogVisible.value = true; resourceDialogVisible.value = true;
loading.value = true; loading.value = true;
const roleId: any = row.id; const roleId: any = row.id;
@@ -206,7 +209,7 @@ function handleAllocationSubmit() {
updateRoleMenus(checkedRole.value.id, checkedMenuIds).then(res => { updateRoleMenus(checkedRole.value.id, checkedMenuIds).then(res => {
ElMessage.success('分配权限成功'); ElMessage.success('分配权限成功');
allocationDialogVisible.value = false; resourceDialogVisible.value = false;
handleQuery(); handleQuery();
}); });
} }
@@ -214,8 +217,8 @@ function handleAllocationSubmit() {
/** /**
* 关闭资源弹窗 * 关闭资源弹窗
*/ */
function closeAllocationDialog() { function closeResourceDailog() {
allocationDialogVisible.value = false; resourceDialogVisible.value = false;
} }
onMounted(() => { onMounted(() => {
@@ -288,7 +291,7 @@ onMounted(() => {
<el-button <el-button
type="success" type="success"
link link
@click.stop="showAllocationDialog(scope.row)" @click.stop="openResourceDialog(scope.row)"
> >
资源分配 资源分配
</el-button> </el-button>
@@ -306,12 +309,12 @@ onMounted(() => {
</el-table-column> </el-table-column>
</el-table> </el-table>
<!-- pagination --> <!-- 分页 -->
<pagination <pagination
v-if="total > 0" v-if="total > 0"
:total="total" :total="total"
v-model:page="queryParams.pageNum" :page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" :limit="queryParams.pageSize"
@pagination="handleQuery" @pagination="handleQuery"
/> />
</el-card> </el-card>
@@ -374,7 +377,7 @@ onMounted(() => {
<!-- assign permission dialog --> <!-- assign permission dialog -->
<el-dialog <el-dialog
:title="'【' + checkedRole.name + '】资源分配'" :title="'【' + checkedRole.name + '】资源分配'"
v-model="allocationDialogVisible" v-model="resourceDialogVisible"
width="800px" width="800px"
> >
<el-scrollbar max-height="600px" v-loading="loading"> <el-scrollbar max-height="600px" v-loading="loading">
@@ -396,7 +399,7 @@ onMounted(() => {
<el-button type="primary" @click="handleAllocationSubmit" <el-button type="primary" @click="handleAllocationSubmit"
> </el-button > </el-button
> >
<el-button @click="closeAllocationDialog"> </el-button> <el-button @click="closeResourceDailog"> </el-button>
</div> </div>
</template> </template>
</el-dialog> </el-dialog>