This commit is contained in:
songgaoshuai 2023-10-18 15:17:38 +08:00
commit a6d47a8ad4
15 changed files with 202 additions and 197 deletions

View File

@ -18,7 +18,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "2.1.0",
"@vueup/vue-quill": "1.1.0",
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "9.5.0",
"animate.css": "4.1.1",
"await-to-js": "^3.0.0",

View File

@ -1,4 +1,5 @@
export interface OperLogQuery extends PageQuery {
operIp: string;
title: string;
operName: string;
businessType: string;

View File

@ -20,7 +20,7 @@ export function getConfig(configId: string | number): AxiosPromise<ConfigVO> {
}
// 根据参数键名查询参数值
export function getConfigKey(configKey: string): AxiosPromise<ConfigVO> {
export function getConfigKey(configKey: string): AxiosPromise<String> {
return request({
url: '/system/config/configKey/' + configKey,
method: 'get'

View File

@ -136,7 +136,7 @@ export const updateUserPwd = (oldPassword: string, newPassword: string) => {
headers: {
isEncrypt: true
},
params: data
data: data
});
};

View File

@ -227,23 +227,4 @@
margin-right: 16px;
}
}
// the scroll bar appears when the sub-menu is too long
> .el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@ -9,13 +9,13 @@
name="file"
:show-file-list="false"
:headers="upload.headers"
style="display: none"
ref="uploadRef"
v-if="type === 'url'"
>
</el-upload>
<div class="editor">
<quill-editor
ref="myQuillEditor"
ref="quillEditorRef"
v-model:content="content"
contentType="html"
@textChange="(e: any) => $emit('update:modelValue', content)"
@ -53,7 +53,7 @@ const upload = reactive<UploadOption>({
headers: globalHeaders(),
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
})
const myQuillEditor = ref();
const quillEditorRef = ref();
const options = ref({
theme: "snow",
@ -86,7 +86,7 @@ const options = ref({
},
}
},
placeholder: '请输入内容',
placeholder: "请输入内容",
readOnly: props.readOnly,
});
@ -110,10 +110,10 @@ watch(() => props.modelValue, (v) => {
//
const handleUploadSuccess = (res: any) => {
//
let quill = toRaw(myQuillEditor.value).getQuill();
//
if (res.code === 200) {
//
let quill = toRaw(quillEditorRef.value).getQuill();
//
let length = quill.selection.savedRange.index;
// res
@ -129,6 +129,13 @@ const handleUploadSuccess = (res: any) => {
//
const handleBeforeUpload = (file: any) => {
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
const isJPG = type.includes(file.type);
//
if (!isJPG) {
proxy?.$modal.msgError(`图片格式错误!`);
return false;
}
//
if (props.fileSize) {
const isLt = file.size / 1024 / 1024 < props.fileSize;
@ -149,95 +156,80 @@ const handleUploadError = (err: any) => {
</script>
<style>
.editor-img-uploader {
display: none;
}
.editor,
.ql-toolbar {
white-space: pre-wrap !important;
line-height: normal !important;
}
.quill-img {
display: none;
}
.ql-snow .ql-tooltip[data-mode="link"]::before {
content: "请输入链接地址:";
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
border-right: 0;
content: "保存";
padding-right: 0;
}
.ql-snow .ql-tooltip[data-mode="video"]::before {
content: "请输入视频地址:";
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
content: "14px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
content: "10px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
content: "18px";
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
content: "32px";
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
content: "文本";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
content: "标题1";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
content: "标题2";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
content: "标题3";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";

View File

@ -1,6 +1,6 @@
<template>
<div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click"/>
<el-select
ref="headerSearchSelectRef"
v-model="search"
@ -12,21 +12,22 @@
class="header-search-select"
@change="change"
>
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
<el-option v-for="option in options" :key="option.item.path" :value="option.item"
:label="option.item.title.join(' > ')"/>
</el-select>
</div>
</template>
<script setup lang="ts" name="HeaderSearch">
import Fuse from 'fuse.js';
import { getNormalPath } from '@/utils/ruoyi';
import { isHttp } from '@/utils/validate';
import {getNormalPath} from '@/utils/ruoyi';
import {isHttp} from '@/utils/validate';
import usePermissionStore from '@/store/modules/permission';
import { RouteOption } from 'vue-router';
import {RouteOption} from 'vue-router';
type Router = Array<{
path: string;
title: string[];
path: string;
title: string[];
}>
const search = ref('');
@ -39,88 +40,99 @@ const router = useRouter();
const routes = computed(() => usePermissionStore().routes);
const click = () => {
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
}
show.value = !show.value
if (show.value) {
headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
}
};
const close = () => {
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
options.value = []
show.value = false
headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
options.value = []
show.value = false
}
const change = (val: any) => {
const path = val.path;
if (isHttp(path)) {
// http(s)://
const pindex = path.indexOf("http");
window.open(path.substr(pindex, path.length), "_blank");
const path = val.path;
const query = val.query;
if (isHttp(path)) {
// http(s)://
const pindex = path.indexOf("http");
window.open(path.substr(pindex, path.length), "_blank");
} else {
if (query) {
router.push({ path: path, query: JSON.parse(query) });
} else {
router.push(path)
router.push(path)
}
search.value = ''
options.value = []
nextTick(() => {
show.value = false
})
}
search.value = ''
options.value = []
nextTick(() => {
show.value = false
})
}
const initFuse = (list: Router) => {
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
fuse.value = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
}
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = []) => {
let res: Router = []
routes.forEach(r => {
// skip hidden router
if (!r.hidden) {
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle]
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
const generateRoutes = (routes: RouteOption[], basePath = '', prefixTitle: string[] = [], query: any = {}) => {
let res: Router = []
routes.forEach(r => {
// skip hidden router
if (!r.hidden) {
const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
const data = {
path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
title: [...prefixTitle],
query: ''
}
if (r.meta && r.meta.title) {
data.title = [...data.title, r.meta.title];
if (r.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data);
}
})
return res;
}
if (r.query) {
data.query = r.query
}
// recursive child routes
if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title, data.query);
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes];
}
}
}
})
return res;
}
const querySearch = (query: string) => {
if (query !== '') {
options.value = fuse.value.search(query)
} else {
options.value = []
}
if (query !== '') {
options.value = fuse.value.search(query)
} else {
options.value = []
}
}
onMounted(() => {
searchPool.value = generateRoutes(routes.value);
searchPool.value = generateRoutes(routes.value);
})
// watchEffect(() => {
@ -128,54 +140,54 @@ onMounted(() => {
// })
watch(show, (value) => {
if (value) {
document.body.addEventListener('click', close)
} else {
document.body.removeEventListener('click', close)
}
if (value) {
document.body.addEventListener('click', close)
} else {
document.body.removeEventListener('click', close)
}
})
watch(searchPool, (list) => {
initFuse(list)
initFuse(list)
})
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
:deep(.el-input__inner) {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
width: 210px;
margin-left: 10px;
}
}
}
</style>

View File

@ -7,13 +7,25 @@
<el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button circle icon="Menu" @click="showColumn()" />
<el-tooltip class="item" effect="dark" content="显示/隐藏列" placement="top" v-if="columns">
<div>
<el-popover placement="bottom" trigger="click">
<div class="tree-header">显示/隐藏列</div>
<el-tree
ref="columnRef"
:data="columns"
show-checkbox
@check="columnChange"
node-key="key"
:props="{ label: 'label', children: 'children' }"
></el-tree>
<template #reference>
<el-button circle icon="Menu" />
</template>
</el-popover>
</div>
</el-tooltip>
</el-row>
<el-dialog :title="title" v-model="open" append-to-body>
<el-transfer :titles="['显示', '隐藏']" v-model="value" :data="columns" @change="dataChange"></el-transfer>
</el-dialog>
</div>
</template>
@ -29,15 +41,9 @@ const props = defineProps({
gutter: propTypes.number.def(10),
})
const columnRef = ref<ElTreeInstance>();
const emits = defineEmits(['update:showSearch', 'queryTable']);
//
const value = ref<Array<number>>([]);
//
const title = ref("显示/隐藏");
//
const open = ref(false);
const style = computed(() => {
const ret: any = {};
if (props.gutter) {
@ -56,23 +62,19 @@ function refresh() {
emits("queryTable");
}
//
function dataChange(data: TransferKey[]) {
props.columns?.forEach((item) => {
item.visible = !data.includes(item.key);
})
}
// dialog
const showColumn = () => {
open.value = true;
//
function columnChange(...args: any[]) {
props.columns?.forEach((item) => {
item.visible = args[1].checkedKeys.includes(item.key);
})
}
//
onMounted(() => {
props.columns?.forEach((item) => {
if (!item.visible) {
value.value.push(item.key);
if (item.visible) {
columnRef.value?.setChecked(item.key, true, false);
// value.value.push(item.key);
}
})
})
@ -91,4 +93,9 @@ onMounted(() => {
.my-el-transfer {
text-align: center;
}
.tree-header{
width: 100%;
line-height: 24px;
text-align: center;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<div v-if="!item.hidden">
<template v-if="hasOneShowingChild(item.children, item) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<template v-if="hasOneShowingChild(item, item.children) && (!onlyOneChild.children || onlyOneChild.noShowingChildren) && !item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path, onlyOneChild.query)">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{ 'submenu-title-noDropdown': !isNest }">
<svg-icon :icon-class="onlyOneChild.meta.icon || (item.meta && item.meta.icon)" />
@ -18,10 +18,10 @@
</template>
<sidebar-item
v-for="child in item.children"
v-for="(child) in item.children as RouteOption[]"
:key="child.path"
:is-nest="true"
:item="child as RouteOption"
:item="child"
:base-path="resolvePath(child.path)"
class="nest-menu"
/>
@ -34,7 +34,6 @@ import { isExternal } from '@/utils/validate'
import AppLink from './Link.vue'
import { getNormalPath } from '@/utils/ruoyi'
import { RouteOption } from "vue-router";
import { PropType } from "vue";
const props = defineProps({
// route object
@ -54,7 +53,7 @@ const props = defineProps({
const onlyOneChild = ref<any>({});
const hasOneShowingChild = (children:RouteOption[] = [], parent: RouteOption) => {
const hasOneShowingChild = (parent: RouteOption, children?:RouteOption[]) => {
if (!children) {
children = [];
}
@ -76,9 +75,13 @@ const hasOneShowingChild = (children:RouteOption[] = [], parent: RouteOption) =>
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
if (parent.name === '2222') {
console.log(onlyOneChild.value)
}
return true
}
return false
};

View File

@ -4,7 +4,7 @@
<el-scrollbar :class="sideTheme" wrap-class="scrollbar-wrapper">
<transition :enter-active-class="proxy?.animate.menuSearchAnimate.enter" mode="out-in">
<el-menu
:default-active="activeMenu as string"
:default-active="activeMenu"
:collapse="isCollapse"
:background-color="bgColor"
:text-color="textColor"
@ -27,6 +27,7 @@ import variables from '@/assets/styles/variables.module.scss'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
import { RouteOption } from "vue-router";
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const route = useRoute();
@ -34,7 +35,7 @@ const appStore = useAppStore()
const settingsStore = useSettingsStore()
const permissionStore = usePermissionStore()
const sidebarRouters = computed(() => permissionStore.sidebarRouters);
const sidebarRouters = computed<RouteOption[]>(() => permissionStore.sidebarRouters);
const showLogo = computed(() => settingsStore.sidebarLogo);
const sideTheme = computed(() => settingsStore.sideTheme);
const theme = computed(() => settingsStore.theme);

View File

@ -13,6 +13,7 @@ declare global {
key: number;
label: string;
visible: boolean;
children?: Array<FieldOption>;
}
/**

View File

@ -4,6 +4,9 @@
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
<el-form-item label="操作地址" prop="operIp">
<el-input v-model="queryParams.operIp" placeholder="请输入操作地址" clearable style="width: 240px;" @keyup.enter="handleQuery"/>
</el-form-item>
<el-form-item label="系统模块" prop="title">
<el-input v-model="queryParams.title" placeholder="请输入系统模块" clearable style="width: 240px;" @keyup.enter="handleQuery" />
</el-form-item>
@ -83,7 +86,7 @@
sortable="custom"
:sort-orders="['descending', 'ascending']"
/>
<el-table-column label="主机" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
<el-table-column label="操作地址" align="center" prop="operIp" width="130" :show-overflow-tooltip="true" />
<el-table-column label="操作状态" align="center" prop="status">
<template #default="scope">
<dict-tag :options="sys_common_status" :value="scope.row.status" />
@ -215,6 +218,7 @@ const data = reactive<PageData<OperLogForm, OperLogQuery>>({
queryParams: {
pageNum: 1,
pageSize: 10,
operIp: '',
title: '',
operName: '',
businessType: '',

View File

@ -229,7 +229,7 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12" v-if="form.menuType !== 'F'">
<el-col :span="12">
<el-form-item>
<template #label>
<span>

View File

@ -194,7 +194,7 @@ const { queryParams, form, rules } = toRefs(data);
const getList = async () => {
loading.value = true;
const res = await proxy?.getConfigKey("sys.oss.previewListResource");
previewListResource.value = res?.msg === undefined ? true : res.msg === 'true';
previewListResource.value = res?.data === undefined ? true : res.data === 'true';
const response = await listOss(proxy?.addDateRange(queryParams.value, dateRangeCreateTime.value, "CreateTime"));
ossList.value = response.rows;
total.value = response.total;

View File

@ -320,7 +320,7 @@ const total = ref(0);
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
const deptName = ref('');
const deptOptions = ref<DeptVO[]>([]);
const initPassword = ref('123456');
const initPassword = ref<String>('');
const postOptions = ref<PostVO[]>([]);
const roleOptions = ref<RoleVO[]>([]);
/*** 用户导入参数 */
@ -340,13 +340,13 @@ const upload = reactive<ImportOption>({
})
//
const columns = ref<FieldOption[]>([
{ key: 0, label: `用户编号`, visible: false },
{ key: 1, label: `用户名称`, visible: true },
{ key: 2, label: `用户昵称`, visible: true },
{ key: 3, label: `部门`, visible: true },
{ key: 4, label: `手机号码`, visible: true },
{ key: 5, label: `状态`, visible: true },
{ key: 6, label: `创建时间`, visible: true }
{ key: 0, label: `用户编号`, visible: false,children: [] },
{ key: 1, label: `用户名称`, visible: true,children: [] },
{ key: 2, label: `用户昵称`, visible: true,children: [] },
{ key: 3, label: `部门`, visible: true,children: [] },
{ key: 4, label: `手机号码`, visible: true,children: [] },
{ key: 5, label: `状态`, visible: true,children: [] },
{ key: 6, label: `创建时间`, visible: true,children: [] }
])
@ -561,7 +561,7 @@ const handleAdd = async () => {
await initTreeData();
postOptions.value = data.posts;
roleOptions.value = data.roles;
form.value.password = initPassword.value;
form.value.password = initPassword.value.toString();
}
/** 修改按钮操作 */
const handleUpdate = async (row?: UserForm) => {
@ -613,6 +613,9 @@ const resetForm = () => {
onMounted(() => {
getTreeSelect() //
getList() //
proxy?.getConfigKey("sys.user.initPassword").then(response => {
initPassword.value = response.data;
});
});
</script>