Merge pull request 'dev' (#1) from dev into master

Reviewed-on: #1
表格拖拽排序列,重置和点击不显示无反应
This commit is contained in:
bunny 2024-05-13 14:19:28 +08:00
commit faba062950
46 changed files with 2291 additions and 1729 deletions

View File

@ -1,8 +0,0 @@
#!/bin/sh
# shellcheck source=./_/husky.sh
. "$(dirname "$0")/_/husky.sh"
PATH="/usr/local/bin:$PATH"
npx --no-install commitlint --edit "$1"

View File

@ -1,9 +0,0 @@
#!/bin/sh
command_exists () {
command -v "$1" >/dev/null 2>&1
}
# Workaround for Windows 10, Git Bash and Pnpm
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi

View File

@ -1,10 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
. "$(dirname "$0")/common.sh"
. "$(dirname -- "$0")/_/husky.sh"
[ -n "$CI" ] && exit 0
PATH="/usr/local/bin:$PATH"
# Perform lint check on files in the staging area through .lintstagedrc configuration
pnpm exec lint-staged
pnpm exec lint-staged

View File

@ -2,7 +2,7 @@
/** @type {import("prettier").Config} */
export default {
printWidth: 230,
printWidth: 200,
bracketSpacing: true,
singleQuote: false,
arrowParens: "avoid",

View File

@ -4,13 +4,13 @@ import dayjs, { type Dayjs } from "dayjs";
import duration from "dayjs/plugin/duration";
import gradientString from "gradient-string";
import boxen, { type Options as BoxenOptions } from "boxen";
dayjs.extend(duration);
const welcomeMessage = gradientString("cyan", "magenta").multiline(
`您好! 欢迎使用 pure-admin 开源项目
https://pure-admin.github.io/pure-admin-doc
https://pure-admin-utils.netlify.app`
`您好! 欢迎使用 bunny-admin 后台管理
访
http://localhost:8848/`
);
const boxenOptions: BoxenOptions = {
@ -42,16 +42,7 @@ export function viteBuildInfo(): Plugin {
getPackageSize({
folder: outDir,
callback: (size: string) => {
console.log(
boxen(
gradientString("cyan", "magenta").multiline(
`🎉 恭喜打包完成(总用时${dayjs
.duration(endTime.diff(startTime))
.format("mm分ss秒")}${size}`
),
boxenOptions
)
);
console.log(boxen(gradientString("cyan", "magenta").multiline(`🎉 恭喜打包完成(总用时${dayjs.duration(endTime.diff(startTime)).format("mm分ss秒")},打包后的大小为${size}`), boxenOptions));
}
});
}

View File

@ -1,35 +1,80 @@
// @ts-check
// @see: https://cz-git.qbenben.com/zh/guide
/** @type {import('cz-git').UserConfig} */
/** @type {import("@commitlint/types").UserConfig} */
export default {
ignores: [commit => commit.includes("init")],
extends: ["@commitlint/config-conventional"],
rules: {
// @see: https://commitlint.js.org/#/reference-rules
"body-leading-blank": [2, "always"],
"footer-leading-blank": [1, "always"],
"header-max-length": [2, "always", 108],
"subject-empty": [2, "never"],
"type-empty": [2, "never"],
"subject-case": [0],
"type-enum": [
2,
"always",
[
"feat",
"fix",
"perf",
"style",
"docs",
"test",
"refactor",
"build",
"ci",
"chore",
"revert",
"wip",
"workflow",
"types",
"release"
]
["init", "optimize", "feat", "fix", "fixbug", "media", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert", "wip", "workflow", "types", "release"]
]
},
prompt: {
messages: {
type: "选择你要提交的类型 :",
scope: "选择一个提交范围(可选):",
customScope: "请输入自定义的提交范围 :",
subject: "填写简短精炼的变更描述 :\n",
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixsSelect: "选择关联issue前缀可选:",
customFooterPrefixs: "输入自定义issue前缀 :",
footer: "列举关联issue (可选) 例如: #31, #I3244 :\n",
confirmCommit: "是否提交或修改commit ?"
},
types: [
{ value: "init", name: "初始化: ⏳ 初始化项目", emoji: "⏳" },
{ value: "optimize", name: "优化代码: ♻️ 优化项目代码", emoji: "♻️" },
{ value: "feat", name: "特性: 🚀 新增功能", emoji: "🚀" },
{ value: "media", name: "媒体: 🎁 新增媒体资源", emoji: "🎁" },
{ value: "fix", name: "修复: 🧩 修复缺陷", emoji: "🧩" },
{ value: "docs", name: "文档: 📚 文档变更", emoji: "📚" },
{ value: "style", name: "格式: 🎨 代码格式(不影响功能,例如空格、分号等格式修正)", emoji: "🎨" },
{ value: "fixbug", name: "bug: 🐛 修改bug", emoji: "🐛" },
{ value: "refactor", name: "重构: ♻️ 代码重构(不包括 bug 修复、功能新增)", emoji: "♻️" },
{ value: "perf", name: "性能: ⚡️ 性能优化", emoji: "⚡️" },
{ value: "test", name: "测试: ✅ 添加疏漏测试或已有测试改动", emoji: "✅" },
{ value: "build", name: "构建: 📦️ 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)", emoji: "📦️" },
{ value: "ci", name: "集成: 🎡 修改 CI 配置、脚本", emoji: "🎡" },
{ value: "revert", name: "回退: ⏪️ 回滚 commit", emoji: "⏪️" },
{ value: "chore", name: "其他: 🔨 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)", emoji: "🔨" }
],
useEmoji: true,
themeColorCode: "",
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: "bottom",
customScopesAlias: "custom",
emptyScopesAlias: "empty",
upperCaseSubject: false,
allowBreakingChanges: ["feat", "fix"],
breaklineNumber: 100,
breaklineChar: "|",
skipQuestions: [],
issuePrefixs: [{ value: "closed", name: "closed: ISSUES has been processed" }],
customIssuePrefixsAlign: "top",
emptyIssuePrefixsAlias: "skip",
customIssuePrefixsAlias: "custom",
allowCustomIssuePrefixs: true,
allowEmptyIssuePrefixs: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: "",
defaultIssues: "",
defaultScope: "",
defaultSubject: ""
}
};

View File

@ -1,87 +1,87 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<head>
<meta charset="UTF-8"/>
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"/>
<meta content="webkit" name="renderer"/>
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
name="viewport"
/>
<title>vue-pure-admin</title>
<link rel="icon" href="/favicon.ico" />
<title>bunny-admin</title>
<link href="/favicon.ico" rel="icon"/>
<script>
window.process = {};
window.process = {};
</script>
</head>
</head>
<body>
<div id="app">
<style>
<body>
<div id="app">
<style>
html,
body,
#app {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow: hidden;
align-items: center;
display: flex;
height: 100%;
justify-content: center;
overflow: hidden;
position: relative;
width: 100%;
}
.loader,
.loader::before,
.loader::after {
width: 2.5em;
height: 2.5em;
border-radius: 50%;
animation: load-animation 1.8s infinite ease-in-out;
animation-fill-mode: both;
animation: load-animation 1.8s infinite ease-in-out;
animation-fill-mode: both;
border-radius: 50%;
height: 2.5em;
width: 2.5em;
}
.loader {
position: relative;
top: 0;
margin: 80px auto;
font-size: 10px;
color: #406eeb;
text-indent: -9999em;
transform: translateZ(0);
transform: translate(-50%, 0);
animation-delay: -0.16s;
animation-delay: -0.16s;
color: #406eeb;
font-size: 10px;
margin: 80px auto;
position: relative;
text-indent: -9999em;
top: 0;
transform: translate(-50%, 0);
transform: translateZ(0);
}
.loader::before,
.loader::after {
position: absolute;
top: 0;
content: "";
content: "";
position: absolute;
top: 0;
}
.loader::before {
left: -3.5em;
animation-delay: -0.32s;
animation-delay: -0.32s;
left: -3.5em;
}
.loader::after {
left: 3.5em;
left: 3.5em;
}
@keyframes load-animation {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
</style>
<div class="loader"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</style>
<div class="loader"></div>
</div>
<script src="/src/main.ts" type="module"></script>
</body>
</html>

8
lint-staged.config.js Normal file
View File

@ -0,0 +1,8 @@
export default {
"*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"],
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": ["prettier --write--parser json"],
"package.json": ["prettier --write"],
"*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"],
"*.{scss,less,styl,html}": ["stylelint --fix", "prettier --write"],
"*.md": ["prettier --write"]
};

View File

@ -19,6 +19,7 @@ buttons:
pureBackTop: BackTop
pureOpenText: Open
pureCloseText: Close
rest: Rest
search:
pureTotal: Total
pureHistory: History
@ -180,6 +181,7 @@ menus:
pureMindMap: Mind Map
pureMenuOverflow: Menu Overflow Show Tooltip Text
pureChildMenuOverflow: Child Menu Overflow Show Tooltip Text
systemctlTest: Systemctl lTest
status:
pureLoad: Loading...
pureMessage: Message
@ -229,4 +231,10 @@ login:
purePassWordRuleReg: The password format should be any combination of 8-18 digits
purePassWordSureReg: Please enter confirm password
purePassWordDifferentReg: The two passwords do not match!
purePassWordUpdateReg: Password has been updated
purePassWordUpdateReg: Password has been updated
table:
tableNumber: Table Number
style:
larger: Larger
default: Default
small: Small

View File

@ -19,6 +19,7 @@ buttons:
pureBackTop: 回到顶部
pureOpenText:
pureCloseText:
rest: 重置
search:
pureTotal:
pureHistory: 搜索历史
@ -180,6 +181,7 @@ menus:
pureMindMap: 思维导图
pureMenuOverflow: 目录超出显示 Tooltip 文字提示
pureChildMenuOverflow: 菜单超出显示 Tooltip 文字提示
systemctlTest: 系统测试
status:
pureLoad: 加载中...
pureMessage: 消息
@ -230,3 +232,9 @@ login:
purePassWordSureReg: 请输入确认密码
purePassWordDifferentReg: 两次密码不一致!
purePassWordUpdateReg: 修改密码成功
table:
tableNumber: 序号
style:
larger: 宽松
default: 默认
small: 紧凑

View File

@ -1,6 +1,6 @@
// 模拟后端动态生成路由
import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { frame, monitor, permission, system, tabs } from "@/router/enums";
import { frame, monitor, permission, system } from "@/router/enums";
/**
* roles "admin""common"
@ -102,6 +102,16 @@ const systemMonitorRouter = {
title: "menus.pureSystemLog",
roles: ["admin"]
}
},
{
path: "/monitor/system-test",
component: "monitor/test/index",
name: "SystemTest",
meta: {
icon: "ri:file-search-line",
title: "menus.systemctlTest",
roles: ["admin"]
}
}
]
};
@ -257,66 +267,24 @@ const frameRouter = {
]
};
const tabsRouter = {
path: "/tabs",
const about = {
path: "/about",
meta: {
icon: "ri:bookmark-2-line",
title: "menus.pureTabs",
rank: tabs
title: "menus.pureAbout"
},
children: [
{
path: "/tabs/index",
name: "Tabs",
path: "/about/index",
name: "About",
meta: {
title: "menus.pureTabs",
roles: ["admin", "common"]
}
},
// query 传参模式
{
path: "/tabs/query-detail",
name: "TabQueryDetail",
meta: {
// 不在menu菜单中显示
showLink: false,
activePath: "/tabs/index",
roles: ["admin", "common"]
}
},
// params 传参模式
{
path: "/tabs/params-detail/:id",
component: "params-detail",
name: "TabParamsDetail",
meta: {
// 不在menu菜单中显示
showLink: false,
activePath: "/tabs/index",
roles: ["admin", "common"]
icon: "ri:bookmark-2-line",
title: "menus.pureAbout"
}
}
]
};
const about = {
path: "/about",
meta: {
icon: "ri:bookmark-2-line",
title: "menus.pureAbout",
rank: tabs
},
children: [
{
path: "/about/index",
name: "about",
meta: {
title: "menus.pureAbout",
roles: ["admin", "common"]
}
}
]
};
export default defineFakeRoute([
{
url: "/get-async-routes",
@ -324,7 +292,7 @@ export default defineFakeRoute([
response: () => {
return {
success: true,
data: [systemManagementRouter, systemMonitorRouter, permissionRouter, frameRouter, about /*tabsRouter*/]
data: [systemManagementRouter, systemMonitorRouter, permissionRouter, frameRouter, about]
};
}
}

View File

@ -10,7 +10,7 @@ export default defineFakeRoute([
return {
success: true,
data: {
avatar: "https://avatars.githubusercontent.com/u/44761321",
avatar: "https://pic3.zhimg.com/80/v2-0888fd6c14153bb36fb630230f73a802_720w.webp",
username: "admin",
nickname: "小铭",
// 一个用户可能有多个角色
@ -24,7 +24,7 @@ export default defineFakeRoute([
return {
success: true,
data: {
avatar: "https://avatars.githubusercontent.com/u/52823142",
avatar: "https://pic2.zhimg.com/v2-9ee815c09ec2e1f3a007e8d77ec06375_r.jpg",
username: "common",
nickname: "小林",
roles: ["common"],

View File

@ -45,7 +45,8 @@
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
"preinstall": "npx only-allow pnpm",
"commit": "git pull && git add -A && git-cz && git push"
},
"dependencies": {
"@amap/amap-jsapi-loader": "^1.0.1",
@ -135,7 +136,10 @@
"@vitejs/plugin-vue-jsx": "^3.1.0",
"autoprefixer": "^10.4.19",
"boxen": "^7.1.1",
"commitizen": "^4.2.4",
"commitlint": "^17.0.1",
"cssnano": "^7.0.1",
"cz-git": "^1.3.2",
"dagre": "^0.8.5",
"eslint": "^9.2.0",
"eslint-config-prettier": "^9.1.0",
@ -143,7 +147,7 @@
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-vue": "^9.25.0",
"gradient-string": "^2.0.2",
"husky": "^9.0.11",
"husky": "^8.0.1",
"lint-staged": "^15.2.2",
"postcss": "^8.4.38",
"postcss-html": "^1.7.0",
@ -188,5 +192,10 @@
"eslint": "9"
}
}
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
import vxeTableBar from "./src/bar";
import { withInstall } from "@pureadmin/utils";
/** 配合 `vxe-table` 实现快速便捷的表格操作 */
export const VxeTableBar = withInstall(vxeTableBar);

View File

@ -1,251 +0,0 @@
import Sortable from "sortablejs";
import { transformI18n } from "@/plugins/i18n";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { cloneDeep, delay, getKeyList } from "@pureadmin/utils";
import { computed, defineComponent, getCurrentInstance, nextTick, type PropType, ref, unref } from "vue";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
import SettingIcon from "@/assets/table-bar/settings.svg?component";
import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
const props = {
/** 头部最左边的标题 */
title: {
type: String,
default: "列表"
},
vxeTableRef: {
type: Object as PropType<any>
},
/** 需要展示的列 */
columns: {
type: Array as PropType<any>,
default: () => []
},
/** 是否为树列表 */
tree: {
type: Boolean,
default: false
},
isExpandAll: {
type: Boolean,
default: true
},
tableKey: {
type: [String, Number] as PropType<string | number>,
default: "0"
}
};
export default defineComponent({
name: "VxeTableBar",
props,
emits: ["refresh"],
setup(props, { emit, slots, attrs }) {
const size = ref("small");
const loading = ref(false);
const checkAll = ref(true);
const isIndeterminate = ref(false);
const instance = getCurrentInstance()!;
const isExpandAll = ref(props.isExpandAll);
let checkColumnList = getKeyList(cloneDeep(props?.columns), "title");
const checkedColumns = ref(getKeyList(cloneDeep(props?.columns), "title"));
const dynamicColumns = ref(cloneDeep(props?.columns));
const getDropdownItemStyle = computed(() => {
return s => {
return {
background: s === size.value ? useEpThemeStoreHook().epThemeColor : "",
color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
};
};
});
const iconClass = computed(() => {
return ["text-black", "dark:text-white", "duration-100", "hover:!text-primary", "cursor-pointer", "outline-none"];
});
const topClass = computed(() => {
return ["flex", "justify-between", "pt-[3px]", "px-[11px]", "border-b-[1px]", "border-solid", "border-[#dcdfe6]", "dark:border-[#303030]"];
});
function onReFresh() {
loading.value = true;
emit("refresh");
delay(500).then(() => (loading.value = false));
}
function onExpand() {
isExpandAll.value = !isExpandAll.value;
isExpandAll.value ? props.vxeTableRef.setAllTreeExpand(true) : props.vxeTableRef.clearTreeExpand();
props.vxeTableRef.refreshColumn();
}
function reloadColumn() {
const curCheckedColumns = cloneDeep(dynamicColumns.value).filter(item => checkedColumns.value.includes(item.title));
props.vxeTableRef.reloadColumn(curCheckedColumns);
}
function handleCheckAllChange(val: boolean) {
checkedColumns.value = val ? checkColumnList : [];
isIndeterminate.value = false;
reloadColumn();
}
function handleCheckedColumnsChange(value: string[]) {
checkedColumns.value = value;
const checkedCount = value.length;
checkAll.value = checkedCount === checkColumnList.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < checkColumnList.length;
}
async function onReset() {
checkAll.value = true;
isIndeterminate.value = false;
dynamicColumns.value = cloneDeep(props?.columns);
checkColumnList = [];
checkColumnList = await getKeyList(cloneDeep(props?.columns), "title");
checkedColumns.value = getKeyList(cloneDeep(props?.columns), "title");
props.vxeTableRef.refreshColumn();
}
function changeSize(curSize: string) {
size.value = curSize;
props.vxeTableRef.refreshColumn();
}
const dropdown = {
dropdown: () => (
<el-dropdown-menu class="translation">
<el-dropdown-item style={getDropdownItemStyle.value("medium")} onClick={() => changeSize("medium")}>
</el-dropdown-item>
<el-dropdown-item style={getDropdownItemStyle.value("small")} onClick={() => changeSize("small")}>
</el-dropdown-item>
<el-dropdown-item style={getDropdownItemStyle.value("mini")} onClick={() => changeSize("mini")}>
</el-dropdown-item>
</el-dropdown-menu>
)
};
/** 列展示拖拽排序 */
const rowDrop = (event: { preventDefault: () => void }) => {
event.preventDefault();
nextTick(() => {
const wrapper: HTMLElement = (instance?.proxy?.$refs[`VxeGroupRef${unref(props.tableKey)}`] as any).$el.firstElementChild;
Sortable.create(wrapper, {
animation: 300,
handle: ".drag-btn",
onEnd: ({ newIndex, oldIndex, item }) => {
const targetThElem = item;
const wrapperElem = targetThElem.parentNode as HTMLElement;
const oldColumn = dynamicColumns.value[oldIndex];
const newColumn = dynamicColumns.value[newIndex];
if (oldColumn?.fixed || newColumn?.fixed) {
// 当前列存在fixed属性 则不可拖拽
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
if (newIndex > oldIndex) {
wrapperElem.insertBefore(targetThElem, oldThElem);
} else {
wrapperElem.insertBefore(targetThElem, oldThElem ? oldThElem.nextElementSibling : oldThElem);
}
return;
}
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
dynamicColumns.value.splice(newIndex, 0, currentRow);
reloadColumn();
}
});
});
};
const isFixedColumn = (title: string) => {
return dynamicColumns.value.filter(item => transformI18n(item.title) === transformI18n(title))[0].fixed ? true : false;
};
const rendTippyProps = (content: string) => {
// https://vue-tippy.netlify.app/props
return {
content,
offset: [0, 18],
duration: [300, 0],
followCursor: true,
hideOnClick: "toggle"
};
};
const reference = {
reference: () => <SettingIcon class={["w-[16px]", iconClass.value]} v-tippy={rendTippyProps("列设置")} />
};
return () => (
<>
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
{slots?.title ? slots.title() : <p class="font-bold truncate">{props.title}</p>}
<div class="flex items-center justify-around">
{slots?.buttons ? <div class="flex mr-4">{slots.buttons()}</div> : null}
{props.tree ? (
<>
<ExpandIcon
class={["w-[16px]", iconClass.value]}
style={{
transform: isExpandAll.value ? "none" : "rotate(-90deg)"
}}
v-tippy={rendTippyProps(isExpandAll.value ? "折叠" : "展开")}
onClick={() => onExpand()}
/>
<el-divider direction="vertical" />
</>
) : null}
<RefreshIcon class={["w-[16px]", iconClass.value, loading.value ? "animate-spin" : ""]} v-tippy={rendTippyProps("刷新")} onClick={() => onReFresh()} />
<el-divider direction="vertical" />
<el-dropdown v-slots={dropdown} trigger="click" v-tippy={rendTippyProps("密度")}>
<CollapseIcon class={["w-[16px]", iconClass.value]} />
</el-dropdown>
<el-divider direction="vertical" />
<el-popover v-slots={reference} placement="bottom-start" popper-style={{ padding: 0 }} width="200" trigger="click">
<div class={[topClass.value]}>
<el-checkbox class="!-mr-1" label="列展示" v-model={checkAll.value} indeterminate={isIndeterminate.value} onChange={value => handleCheckAllChange(value)} />
<el-button type="primary" link onClick={() => onReset()}>
</el-button>
</div>
<div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh">
<el-checkbox-group ref={`VxeGroupRef${unref(props.tableKey)}`} modelValue={checkedColumns.value} onChange={value => handleCheckedColumnsChange(value)}>
<el-space direction="vertical" alignment="flex-start" size={0}>
{checkColumnList.map((item, index) => {
return (
<div class="flex items-center">
<DragIcon class={["drag-btn w-[16px] mr-2", isFixedColumn(item) ? "!cursor-no-drop" : "!cursor-grab"]} onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)} />
<el-checkbox key={index} label={item} value={item} onChange={reloadColumn}>
<span title={transformI18n(item)} class="inline-block w-[120px] truncate hover:text-text_color_primary">
{transformI18n(item)}
</span>
</el-checkbox>
</div>
);
})}
</el-space>
</el-checkbox-group>
</el-scrollbar>
</div>
</el-popover>
</div>
</div>
{slots.default({
size: size.value,
dynamicColumns: dynamicColumns.value
})}
</div>
</>
);
}
});

View File

@ -0,0 +1,48 @@
<template>
<div class="main">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:size="size"
adaptive
align-whole="center"
border
row-key="id"
table-layout="auto"
/>
</div>
</template>
<script lang="ts" setup>
import { cellHeaderStyle } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
import type { PropType } from "vue";
// *
defineProps({
//
dataList: {
type: Array<any>,
default: []
},
//
column: {
type: Array<any>,
default: []
},
loading: {
type: Boolean,
default: false
},
size: {
type: String as PropType<any>,
default: "default"
}
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,303 @@
<template>
<div class="main">
<!-- 表单设置外加插槽 -->
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<slot name="tableForm" />
</el-form>
<!-- 表格头部设置 -->
<div class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
<!-- 自定义左边头部内容 -->
<slot name="tableTitle">
<p class="font-bold truncate">{{ tableTitle ? tableTitle : t(route.meta.title) }}</p>
</slot>
<!-- 自定义表格操作内容 -->
<slot name="tableOperation">
<div class="flex items-center justify-around">
<!-- 插槽内容 -->
<div class="mr-4">
<slot name="tableButtons" />
</div>
<!-- 表格刷新按钮 -->
<RefreshIcon v-tippy="rendTipProps('刷新')" :class="`w-[16px] ${iconClass()}} ${loading ? 'animate-spin' : ''}`" @click="onReFresh" />
<el-divider direction="vertical" />
<!-- 选择表格大小 -->
<el-dropdown trigger="click">
<CollapseIcon :class="`w-[16px] ${iconClass()}`" />
<template #dropdown>
<el-dropdown-menu class="translation">
<el-dropdown-item :style="getDropdownItemStyle(size, 'large')" @click="handleTableSizeClick('large')">
{{ $t("style.larger") }}
</el-dropdown-item>
<el-dropdown-item :style="getDropdownItemStyle(size, 'default')" @click="handleTableSizeClick('default')"> {{ t("style.default") }} </el-dropdown-item>
<el-dropdown-item :style="getDropdownItemStyle(size, 'small')" @click="handleTableSizeClick('small')">
{{ t("style.small") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-divider direction="vertical" />
<!-- 表格列设置 -->
<el-popover :popper-style="{ padding: 0 }" placement="bottom-start" trigger="click" width="200">
<template #reference>
<SettingIcon v-tippy="rendTipProps('列设置')" :class="`w-[16px] ${iconClass()}`" />
</template>
<div :class="topClass()">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" class="!-mr-1" label="列展示" @change="handleCheckAllChange" />
<el-button link type="primary" @click="onReset"> {{ t("buttons.rest") }}</el-button>
</div>
<div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh">
<el-checkbox-group :ref="`GroupRef${unref(props.tableKey)}`" :modelValue="checkedColumns" @change="handleCheckedColumnsChange">
<el-space :alignment="'flex-start'" :size="0" direction="vertical">
<div v-for="(item, index) in checkColumnList" :key="index" class="flex items-center">
<DragIcon :class="`drag-btn w-[16px] mr-2 ${isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab'}`" @mouseenter="rowDrop" />
<el-checkbox :key="index" :label="item" :value="item" @change="handleCheckColumnListChange(item)">
<span :title="transformI18n(item)" class="inline-block w-[120px] truncate hover:text-text_color_primary"> {{ transformI18n(item) }} </span>
</el-checkbox>
</div>
</el-space>
</el-checkbox-group>
</el-scrollbar>
</div>
</el-popover>
</div>
</slot>
</div>
<slot name="tableSelect" />
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
border
table-layout="fixed"
v-bind="$attrs"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template v-for="(item, index) in column" :key="index" v-slot:[item.slot]="slotProps">
<slot :name="item.slot" v-bind="slotProps" />
</template>
</pure-table>
</div>
</div>
</template>
<script lang="ts" setup>
import { getCurrentInstance, nextTick, onMounted, PropType, ref, unref, watch } from "vue";
import { rendTipProps } from "@/components/TableBar/utils/tableConfig";
import { cellHeaderStyle, getDropdownItemStyle, iconClass, topClass } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
import SettingIcon from "@/assets/table-bar/settings.svg?component";
import { cloneDeep, getKeyList, isBoolean, isFunction } from "@pureadmin/utils";
import { transformI18n } from "@/plugins/i18n";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import Sortable from "sortablejs";
// *
const props = defineProps({
//
dataList: { type: Array<any>, default: [] },
//
column: { type: Array<any>, default: [] },
//
loading: { type: Boolean, default: false },
// small | default | large
size: { type: String as PropType<any>, default: "default" },
//
pagination: { type: Object, default: Object },
//
handleSelectionChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleSizeChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleCurrentChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
form: {
type: Object as PropType<any>,
default: Object
},
//
onReFresh: {
type: Function as PropType<Function>,
default: () => {}
},
// key
tableKey: {
type: [String, Number] as PropType<string | number>,
default: "0"
},
//
tableTitle: { type: String, default: "" }
});
const emit = defineEmits(["changeColumn"]);
const { t } = useI18n();
const route = useRoute();
//
const checkAll = ref(true);
//
const size = ref(props.size);
const isIndeterminate = ref(false);
//
const dynamicColumns = ref(props.column);
//
const filterColumns = cloneDeep(props.column).filter(column => (isBoolean(column?.hide) ? !column.hide : !(isFunction(column?.hide) && column?.hide())));
//
const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), "label"));
const checkColumnList = ref(getKeyList(cloneDeep(dynamicColumns.value), "label"));
const instance = getCurrentInstance()!;
/**
* * 修改表格样式大小
* @param value 修改样式大小 larger | default | small
*/
const handleTableSizeClick = (value: string) => {
size.value = value;
};
/**
* 设置表格列是否全部显示
* @param val 是否全部显示
*/
const handleCheckAllChange = (val: boolean) => {
checkedColumns.value = val ? checkColumnList.value : [];
isIndeterminate.value = false;
dynamicColumns.value.map(column => (val ? (column.hide = false) : (column.hide = true)));
};
/**
* * 选中的表格列---是否显示
* @param value
*/
const handleCheckedColumnsChange = (value: string[]) => {
checkedColumns.value = value;
const checkedCount = value.length;
checkAll.value = checkedCount === checkColumnList.value.length;
isIndeterminate.value = checkedCount > 0 && checkedCount < checkColumnList.value.length;
};
/**
* * 点击是否显示列
* @param label
*/
const handleCheckColumnListChange = (label: string) => {
dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].hide = !(event.target as any).checked;
};
/**
* * 是否为固定列
* @param label
*/
const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].fixed;
};
/**
* * 重置自定义表格列字段
*/
const onReset = async () => {
//
const list = [];
// true
checkAll.value = true;
isIndeterminate.value = false;
//
checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label");
// ? ref reactive
// ? Proxy 访使
checkColumnList.value = [];
await nextTick(() => {
checkColumnList.value = getKeyList(filterColumns, "label");
});
// checkedColumns list
checkedColumns.value.forEach(item => {
dynamicColumns.value.forEach(column => {
if (column.label == item) {
list.push(column);
}
});
});
emit("changeColumn", list);
};
/** 列展示拖拽排序 */
const rowDrop = (event: any) => {
event.preventDefault();
nextTick(() => {
const wrapper: HTMLElement = (instance?.proxy?.$refs[`GroupRef${unref(props.tableKey)}`] as any).$el.firstElementChild;
Sortable.create(wrapper, {
animation: 300,
handle: ".drag-btn",
onEnd: ({ newIndex, oldIndex, item }) => {
const targetThElem = item;
const wrapperElem = targetThElem.parentNode as HTMLElement;
const oldColumn = dynamicColumns.value[oldIndex];
const newColumn = dynamicColumns.value[newIndex];
if (oldColumn?.fixed || newColumn?.fixed) {
// fixed
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
if (newIndex > oldIndex) {
wrapperElem.insertBefore(targetThElem, oldThElem);
} else {
wrapperElem.insertBefore(targetThElem, oldThElem ? oldThElem.nextElementSibling : oldThElem);
}
return;
}
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
dynamicColumns.value.splice(newIndex, 0, currentRow);
emit("changeColumn", dynamicColumns.value);
}
});
}).then();
};
onMounted(() => {
watch([() => props.column], () => {
dynamicColumns.value = props.column;
});
});
</script>
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<div class="main mt-2 p-2 bg-bg_color">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
border
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
/>
</div>
</template>
<script lang="ts" setup>
import { cellHeaderStyle } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
import type { PropType } from "vue";
// *
defineProps({
//
dataList: {
type: Array<any>,
default: []
},
//
column: {
type: Array<any>,
default: []
},
//
loading: {
type: Boolean,
default: false
},
// small | default | large
size: {
type: String as PropType<any>,
default: "default"
},
//
pagination: {
type: Object,
default: Object
},
//
handleSelectionChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleSizeChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleCurrentChange: {
type: Function as PropType<Function>,
default: () => {}
}
});
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,100 @@
<template>
<div class="main">
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<slot name="tableForm" />
</el-form>
<div class="mt-2 p-2 bg-bg_color">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 108 }"
:columns="column"
:data="dataList"
:header-cell-style="cellHeaderStyle"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
border
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { cellHeaderStyle } from "@/components/TableBar/utils/tableStyle";
import PureTable from "@pureadmin/table";
// *
defineProps({
//
dataList: {
type: Array<any>,
default: []
},
//
column: {
type: Array<any>,
default: []
},
//
loading: {
type: Boolean,
default: false
},
// small | default | large
size: {
type: String as PropType<any>,
default: "default"
},
//
pagination: {
type: Object,
default: Object
},
//
handleSelectionChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleSizeChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
handleCurrentChange: {
type: Function as PropType<Function>,
default: () => {}
},
//
form: {
type: Object as PropType<any>,
default: Object
}
});
</script>
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
.main-content {
margin: 24px 24px 0 !important;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

View File

@ -3,7 +3,6 @@ import { transformI18n } from "@/plugins/i18n";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
import { computed, defineComponent, getCurrentInstance, nextTick, type PropType, ref, unref } from "vue";
import { cloneDeep, delay, getKeyList, isBoolean, isFunction } from "@pureadmin/utils";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import ExpandIcon from "@/assets/table-bar/expand.svg?component";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
@ -32,6 +31,34 @@ const props = {
tableKey: {
type: [String, Number] as PropType<string | number>,
default: "0"
},
dataList: {
type: Array,
default: []
},
loading: {
type: Boolean,
default: false
},
pagination: {
type: Object,
default: {}
},
size: {
type: String as PropType<any>,
default: "default"
},
handleSelectionChange: {
type: String as PropType<any>,
default: () => {}
},
handleSizeChange: {
type: String as PropType<any>,
default: () => {}
},
handleCurrentChange: {
type: String as PropType<any>,
default: () => {}
}
};
@ -52,7 +79,7 @@ export default defineComponent({
const dynamicColumns = ref(cloneDeep(props?.columns));
const getDropdownItemStyle = computed(() => {
return s => {
return (s: string) => {
return {
background: s === size.value ? useEpThemeStoreHook().epThemeColor : "",
color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
@ -110,7 +137,7 @@ export default defineComponent({
isIndeterminate.value = false;
dynamicColumns.value = cloneDeep(props?.columns);
checkColumnList = [];
checkColumnList = await getKeyList(cloneDeep(props?.columns), "label");
checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label");
}
@ -157,11 +184,11 @@ export default defineComponent({
dynamicColumns.value.splice(newIndex, 0, currentRow);
}
});
});
}).then();
};
const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].fixed ? true : false;
return dynamicColumns.value.filter(item => transformI18n(item.label) === transformI18n(label))[0].fixed;
};
const rendTippyProps = (content: string) => {
@ -181,7 +208,7 @@ export default defineComponent({
return () => (
<>
<div {...attrs} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div {...attrs} size={props.size} class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4">
{slots?.title ? slots.title() : <p class="font-bold truncate">{props.title}</p>}
<div class="flex items-center justify-around">
@ -217,11 +244,14 @@ export default defineComponent({
<div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh">
<el-checkbox-group ref={`GroupRef${unref(props.tableKey)}`} modelValue={checkedColumns.value} onChange={value => handleCheckedColumnsChange(value)}>
<el-space direction="vertical" alignment="flex-start" size={0}>
<el-space direction="vertical" alignment={"flex-start"} size={0}>
{checkColumnList.map((item, index) => {
return (
<div class="flex items-center">
<DragIcon class={["drag-btn w-[16px] mr-2", isFixedColumn(item) ? "!cursor-no-drop" : "!cursor-grab"]} onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)} />
<DragIcon
class={["drag-btn w-[16px] mr-2", isFixedColumn(item) ? "!cursor-no-drop" : "!cursor-grab"]}
onMouseenter={(event: { preventDefault: () => void }) => rowDrop(event)}
/>
<el-checkbox key={index} label={item} value={item} onChange={value => handleCheckColumnListChange(value, item)}>
<span title={transformI18n(item)} class="inline-block w-[120px] truncate hover:text-text_color_primary">
{transformI18n(item)}

View File

@ -0,0 +1,7 @@
export const rendTipProps = (content: string) => ({
content,
offset: [0, 18],
duration: [300, 0],
followCursor: true,
hideOnClick: "toggle"
});

View File

@ -0,0 +1,28 @@
import { computed } from "vue";
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
/**
* *
*/
export const cellHeaderStyle = () => ({
background: "var(--el-fill-color-light)",
color: "var(--el-text-color-primary)"
});
// * icon 样式
export const iconClass = () => "text-black dark:text-white duration-100 hover:!text-primary cursor-pointer outline-none";
// * 顶部样式
export const topClass = () => "flex justify-between pt-[3px] px-[11px] border-b-[1px] border-solid border-[#dcdfe6] dark:border-[#303030]";
/**
* *
*/
export const getDropdownItemStyle = computed(() => {
return (size: string, s: string) => {
return {
background: s === size ? useEpThemeStoreHook().epThemeColor : "",
color: s === size ? "#fff" : "var(--el-text-color-primary)"
};
};
});

View File

@ -1,7 +1,7 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import { getOnlineLogsList } from "@/api/system";
import { reactive, ref, onMounted, toRaw } from "vue";
import { onMounted, reactive, ref, toRaw } from "vue";
import type { PaginationProps } from "@pureadmin/table";
export function useRole() {
@ -51,8 +51,7 @@ export function useRole() {
label: "登录时间",
prop: "loginTime",
minWidth: 180,
formatter: ({ loginTime }) =>
dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss")
formatter: ({ loginTime }) => dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
import { ref, computed } from "vue";
import { computed, ref } from "vue";
import { noticesData } from "./data";
import NoticeList from "./components/NoticeList.vue";
import BellIcon from "@iconify-icons/ep/bell";
@ -12,23 +12,13 @@ const activeKey = ref(noticesData[0]?.key);
notices.value.map(v => (noticesNum.value += v.list.length));
const getLabel = computed(
() => item =>
t(item.name) + (item.list.length > 0 ? `(${item.list.length})` : "")
);
const getLabel = computed(() => item => t(item.name) + (item.list.length > 0 ? `(${item.list.length})` : ""));
</script>
<template>
<el-dropdown trigger="click" placement="bottom-end">
<span
:class="[
'dropdown-badge',
'navbar-bg-hover',
'select-none',
Number(noticesNum) !== 0 && 'mr-[10px]'
]"
>
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
<el-dropdown placement="bottom-end" trigger="click">
<span :class="['dropdown-badge', 'navbar-bg-hover', 'select-none', Number(noticesNum) !== 0 && 'mr-[10px]']">
<el-badge :max="99" :value="Number(noticesNum) === 0 ? '' : noticesNum">
<span class="header-notice-icon">
<IconifyIconOffline :icon="BellIcon" />
</span>
@ -36,23 +26,14 @@ const getLabel = computed(
</span>
<template #dropdown>
<el-dropdown-menu>
<el-tabs
v-model="activeKey"
:stretch="true"
class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
>
<el-empty
v-if="notices.length === 0"
:description="t('status.pureNoMessage')"
:image-size="60"
/>
<el-tabs v-model="activeKey" :stretch="true" :style="{ width: notices.length === 0 ? '200px' : '330px' }" class="dropdown-tabs">
<el-empty v-if="notices.length === 0" :description="t('status.pureNoMessage')" :image-size="60" />
<span v-else>
<template v-for="item in notices" :key="item.key">
<el-tab-pane :label="getLabel(item)" :name="`${item.key}`">
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList :list="item.list" :emptyText="item.emptyText" />
<NoticeList :emptyText="item.emptyText" :list="item.list" />
</div>
</el-scrollbar>
</el-tab-pane>

View File

@ -13,7 +13,6 @@ import { injectResponsiveStorage } from "@/utils/responsive";
import { FontIcon, IconifyIconOffline, IconifyIconOnline } from "./components/ReIcon";
import Table from "@pureadmin/table";
import PureDescriptions from "@pureadmin/descriptions";
// 引入重置样式
import "./style/reset.scss";
// 导入公共样式
@ -42,7 +41,6 @@ Object.keys(directives).forEach(key => {
app.component("IconifyIconOffline", IconifyIconOffline);
app.component("IconifyIconOnline", IconifyIconOnline);
app.component("FontIcon", FontIcon);
app.component("Auth", Auth);
app.use(VueTippy);

View File

@ -18,7 +18,7 @@ const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以
tabs = 15,
about = 16,
editor = 17,
flowchart = 18,
bills = 18,
formdesign = 19,
board = 20,
ppt = 21,
@ -26,4 +26,4 @@ const home = 0, // 平台规定只有 home 路由的 rank 才能为 0 ,所以
guide = 23,
menuoverflow = 24;
export { home, vueflow, ganttastic, components, able, table, form, list, result, error, frame, nested, permission, system, monitor, tabs, about, editor, flowchart, formdesign, board, ppt, mind, guide, menuoverflow };
export { home, vueflow, ganttastic, components, able, table, form, list, result, error, frame, nested, permission, system, monitor, tabs, about, editor, bills, formdesign, board, ppt, mind, guide, menuoverflow };

View File

@ -1,4 +1,5 @@
import { $t } from "@/plugins/i18n";
const Layout = () => import("@/layout/index.vue");
export default [
@ -32,7 +33,7 @@ export default [
{
path: "/empty",
name: "Empty",
component: () => import("@/views/empty/index.vue"),
component: () => import("@/components/Empty/index.vue"),
meta: {
title: $t("menus.pureEmpty"),
showLink: false,

View File

@ -155,19 +155,19 @@ watch(loginDay, value => {
]"
prop="username"
>
<el-input v-model="ruleForm.username" :placeholder="t('login.pureUsername')" :prefix-icon="useRenderIcon(User)" clearable />
<el-input v-model="ruleForm.username" :placeholder="t('login.pureUsername')" :prefix-icon="useRenderIcon(User)" clearable @keydown.enter="onLogin(ruleFormRef)" />
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="password">
<el-input v-model="ruleForm.password" :placeholder="t('login.purePassword')" :prefix-icon="useRenderIcon(Lock)" clearable show-password />
<el-input v-model="ruleForm.password" :placeholder="t('login.purePassword')" :prefix-icon="useRenderIcon(Lock)" clearable show-password @keydown.enter="onLogin(ruleFormRef)" />
</el-form-item>
</Motion>
<Motion :delay="200">
<el-form-item prop="verifyCode">
<el-input v-model="ruleForm.verifyCode" :placeholder="t('login.pureVerifyCode')" :prefix-icon="useRenderIcon('ri:shield-keyhole-line')" clearable>
<el-input v-model="ruleForm.verifyCode" :placeholder="t('login.pureVerifyCode')" :prefix-icon="useRenderIcon('ri:shield-keyhole-line')" clearable @keydown.enter="onLogin(ruleFormRef)">
<template v-slot:append>
<ReImageVerify v-model:code="imgCode" />
</template>

View File

@ -8,11 +8,10 @@ import { useUserStoreHook } from "@/store/modules/user";
export const REGEXP_SIX = /^\d{6}$/;
/** 密码正则密码格式应为8-18位数字、字母、符号的任意两种组合 */
export const REGEXP_PWD =
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
export const REGEXP_PWD = /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
/** 登录校验 */
const loginRules = reactive<FormRules>({
export const loginRules = reactive<FormRules>({
password: [
{
validator: (rule, value, callback) => {
@ -33,9 +32,7 @@ const loginRules = reactive<FormRules>({
if (value === "") {
callback(new Error(transformI18n($t("login.pureVerifyCodeReg"))));
} else if (useUserStoreHook().verifyCode !== value) {
callback(
new Error(transformI18n($t("login.pureVerifyCodeCorrectReg")))
);
callback(new Error(transformI18n($t("login.pureVerifyCodeCorrectReg"))));
} else {
callback();
}
@ -46,7 +43,7 @@ const loginRules = reactive<FormRules>({
});
/** 手机登录校验 */
const phoneRules = reactive<FormRules>({
export const phoneRules = reactive<FormRules>({
phone: [
{
validator: (rule, value, callback) => {
@ -78,7 +75,7 @@ const phoneRules = reactive<FormRules>({
});
/** 忘记密码校验 */
const updateRules = reactive<FormRules>({
export const updateRules = reactive<FormRules>({
phone: [
{
validator: (rule, value, callback) => {
@ -122,5 +119,3 @@ const updateRules = reactive<FormRules>({
}
]
});
export { loginRules, phoneRules, updateRules };

View File

@ -1,168 +0,0 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import { getKeyList } from "@pureadmin/utils";
import { getLoginLogsList } from "@/api/system";
import { usePublicHooks } from "@/hooks/system/usePublicHooks";
import type { PaginationProps } from "@pureadmin/table";
import { onMounted, reactive, ref, type Ref, toRaw } from "vue";
export function useRole(tableRef: Ref) {
const form = reactive({
username: "",
status: "",
loginTime: ""
});
const dataList = ref([]);
const loading = ref(true);
const selectedNum = ref(0);
const { tagStyle } = usePublicHooks();
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "序号",
prop: "id",
minWidth: 90
},
{
label: "用户名",
prop: "username",
minWidth: 100
},
{
label: "登录 IP",
prop: "ip",
minWidth: 140
},
{
label: "登录地点",
prop: "address",
minWidth: 140
},
{
label: "操作系统",
prop: "system",
minWidth: 100
},
{
label: "浏览器类型",
prop: "browser",
minWidth: 100
},
{
label: "登录状态",
prop: "status",
minWidth: 100,
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} style={tagStyle.value(row.status)}>
{row.status === 1 ? "成功" : "失败"}
</el-tag>
)
},
{
label: "登录行为",
prop: "behavior",
minWidth: 100
},
{
label: "登录时间",
prop: "loginTime",
minWidth: 180,
formatter: ({ loginTime }) => dayjs(loginTime).format("YYYY-MM-DD HH:mm:ss")
}
];
function handleSizeChange(val: number) {
console.log(`${val} items per page`);
}
function handleCurrentChange(val: number) {
console.log(`current page: ${val}`);
}
/** 当CheckBox选择项发生变化时会触发该事件 */
function handleSelectionChange(val) {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
}
/** 取消选择 */
function onSelectionCancel() {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
}
/** 批量删除 */
function onbatchDel() {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
// 接下来根据实际业务通过选中行的某项数据比如下面的id调用接口进行批量删除
message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
}
/** 清空日志 */
function clearAll() {
// 根据实际业务,调用接口删除所有日志数据
message("已删除所有日志数据", {
type: "success"
});
onSearch();
}
async function onSearch() {
loading.value = true;
const { data } = await getLoginLogsList(toRaw(form));
dataList.value = data.list;
pagination.total = data.total;
pagination.pageSize = data.pageSize;
pagination.currentPage = data.currentPage;
setTimeout(() => {
loading.value = false;
}, 500);
}
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
onMounted(() => {
onSearch();
});
return {
form,
loading,
columns,
dataList,
pagination,
selectedNum,
onSearch,
clearAll,
resetForm,
onbatchDel,
handleSizeChange,
onSelectionCancel,
handleCurrentChange,
handleSelectionChange
};
}

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import { useRole } from "./hook";
import { useRole } from "@/hooks/monitor/useRole";
import { getPickerShortcuts } from "../../utils";
import { PureTableBar } from "@/components/RePureTableBar";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete";
import Refresh from "@iconify-icons/ep/refresh";
import PureTable from "@pureadmin/table";
defineOptions({
name: "LoginLog"
@ -15,130 +15,68 @@ defineOptions({
const formRef = ref();
const tableRef = ref();
const {
form,
loading,
columns,
dataList,
pagination,
selectedNum,
onSearch,
clearAll,
resetForm,
onbatchDel,
handleSizeChange,
onSelectionCancel,
handleCurrentChange,
handleSelectionChange
} = useRole(tableRef);
const { form, loading, columns, dataList, pagination, selectedNum, onSearch, clearAll, resetForm, onbatchDel, handleSizeChange, onSelectionCancel, handleCurrentChange, handleSelectionChange } =
useRole(tableRef);
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="用户名" prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户名"
clearable
class="!w-[150px]"
/>
<el-input v-model="form.username" class="!w-[150px]" clearable placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="登录状态" prop="status">
<el-select
v-model="form.status"
placeholder="请选择"
clearable
class="!w-[150px]"
>
<el-select v-model="form.status" class="!w-[150px]" clearable placeholder="请选择">
<el-option label="成功" value="1" />
<el-option label="失败" value="0" />
</el-select>
</el-form-item>
<el-form-item label="登录时间" prop="loginTime">
<el-date-picker
v-model="form.loginTime"
:shortcuts="getPickerShortcuts()"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
/>
<el-date-picker v-model="form.loginTime" :shortcuts="getPickerShortcuts()" end-placeholder="结束日期时间" range-separator="" start-placeholder="开始日期时间" type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button>
</el-form-item>
</el-form>
<PureTableBar
title="登录日志(仅演示,操作后不生效)"
:columns="columns"
@refresh="onSearch"
>
<PureTableBar :columns="columns" title="登录日志(仅演示,操作后不生效)" @refresh="onSearch">
<template #buttons>
<el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll">
<template #reference>
<el-button type="danger" :icon="useRenderIcon(Delete)">
清空日志
</el-button>
<el-button :icon="useRenderIcon(Delete)" type="danger"> 清空日志</el-button>
</template>
</el-popconfirm>
</template>
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div v-motion-fade class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center">
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }}
</span>
<el-button type="primary" text @click="onSelectionCancel">
取消选择
</el-button>
<span class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" style="font-size: var(--el-font-size-base)"> 已选 {{ selectedNum }} </span>
<el-button text type="primary" @click="onSelectionCancel"> 取消选择</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<template #reference>
<el-button type="danger" text class="mr-1"> 批量删除 </el-button>
<el-button class="mr-1" text type="danger"> 批量删除</el-button>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
align-whole="center"
table-layout="auto"
:loading="loading"
:size="size"
adaptive
:adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
@ -148,7 +86,7 @@ const {
</div>
</template>
<style scoped lang="scss">
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}

View File

@ -1,174 +0,0 @@
import dayjs from "dayjs";
import { message } from "@/utils/message";
import { getKeyList } from "@pureadmin/utils";
import { getOperationLogsList } from "@/api/system";
import { usePublicHooks } from "@/views/system/hooks";
import type { PaginationProps } from "@pureadmin/table";
import { type Ref, reactive, ref, onMounted, toRaw } from "vue";
export function useRole(tableRef: Ref) {
const form = reactive({
module: "",
status: "",
operatingTime: ""
});
const dataList = ref([]);
const loading = ref(true);
const selectedNum = ref(0);
const { tagStyle } = usePublicHooks();
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
const columns: TableColumnList = [
{
label: "勾选列", // 如果需要表格多选此处label必须设置
type: "selection",
fixed: "left",
reserveSelection: true // 数据刷新后保留选项
},
{
label: "序号",
prop: "id",
minWidth: 90
},
{
label: "操作人员",
prop: "username",
minWidth: 100
},
{
label: "所属模块",
prop: "module",
minWidth: 140
},
{
label: "操作概要",
prop: "summary",
minWidth: 140
},
{
label: "操作 IP",
prop: "ip",
minWidth: 100
},
{
label: "操作地点",
prop: "address",
minWidth: 140
},
{
label: "操作系统",
prop: "system",
minWidth: 100
},
{
label: "浏览器类型",
prop: "browser",
minWidth: 100
},
{
label: "操作状态",
prop: "status",
minWidth: 100,
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} style={tagStyle.value(row.status)}>
{row.status === 1 ? "成功" : "失败"}
</el-tag>
)
},
{
label: "操作时间",
prop: "operatingTime",
minWidth: 180,
formatter: ({ operatingTime }) =>
dayjs(operatingTime).format("YYYY-MM-DD HH:mm:ss")
}
];
function handleSizeChange(val: number) {
console.log(`${val} items per page`);
}
function handleCurrentChange(val: number) {
console.log(`current page: ${val}`);
}
/** 当CheckBox选择项发生变化时会触发该事件 */
function handleSelectionChange(val) {
selectedNum.value = val.length;
// 重置表格高度
tableRef.value.setAdaptive();
}
/** 取消选择 */
function onSelectionCancel() {
selectedNum.value = 0;
// 用于多选表格,清空用户的选择
tableRef.value.getTableRef().clearSelection();
}
/** 批量删除 */
function onbatchDel() {
// 返回当前选中的行
const curSelected = tableRef.value.getTableRef().getSelectionRows();
// 接下来根据实际业务通过选中行的某项数据比如下面的id调用接口进行批量删除
message(`已删除序号为 ${getKeyList(curSelected, "id")} 的数据`, {
type: "success"
});
tableRef.value.getTableRef().clearSelection();
onSearch();
}
/** 清空日志 */
function clearAll() {
// 根据实际业务,调用接口删除所有日志数据
message("已删除所有日志数据", {
type: "success"
});
onSearch();
}
async function onSearch() {
loading.value = true;
const { data } = await getOperationLogsList(toRaw(form));
dataList.value = data.list;
pagination.total = data.total;
pagination.pageSize = data.pageSize;
pagination.currentPage = data.currentPage;
setTimeout(() => {
loading.value = false;
}, 500);
}
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
onMounted(() => {
onSearch();
});
return {
form,
loading,
columns,
dataList,
pagination,
selectedNum,
onSearch,
clearAll,
resetForm,
onbatchDel,
handleSizeChange,
onSelectionCancel,
handleCurrentChange,
handleSelectionChange
};
}

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import { useRole } from "./hook";
import { useRole } from "@/hooks/monitor/useRole";
import { getPickerShortcuts } from "../../utils";
import { PureTableBar } from "@/components/RePureTableBar";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import PureTable from "@pureadmin/table";
import Delete from "@iconify-icons/ep/delete";
import Refresh from "@iconify-icons/ep/refresh";
@ -15,130 +15,68 @@ defineOptions({
const formRef = ref();
const tableRef = ref();
const {
form,
loading,
columns,
dataList,
pagination,
selectedNum,
onSearch,
clearAll,
resetForm,
onbatchDel,
handleSizeChange,
onSelectionCancel,
handleCurrentChange,
handleSelectionChange
} = useRole(tableRef);
const { form, loading, columns, dataList, pagination, selectedNum, onSearch, clearAll, resetForm, onbatchDel, handleSizeChange, onSelectionCancel, handleCurrentChange, handleSelectionChange } =
useRole(tableRef);
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="所属模块" prop="module">
<el-input
v-model="form.module"
placeholder="请输入所属模块"
clearable
class="!w-[170px]"
/>
<el-input v-model="form.module" class="!w-[170px]" clearable placeholder="请输入所属模块" />
</el-form-item>
<el-form-item label="操作状态" prop="status">
<el-select
v-model="form.status"
placeholder="请选择"
clearable
class="!w-[150px]"
>
<el-select v-model="form.status" class="!w-[150px]" clearable placeholder="请选择">
<el-option label="成功" value="1" />
<el-option label="失败" value="0" />
</el-select>
</el-form-item>
<el-form-item label="操作时间" prop="operatingTime">
<el-date-picker
v-model="form.operatingTime"
:shortcuts="getPickerShortcuts()"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
/>
<el-date-picker v-model="form.operatingTime" :shortcuts="getPickerShortcuts()" end-placeholder="结束日期时间" range-separator="" start-placeholder="开始日期时间" type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button>
</el-form-item>
</el-form>
<PureTableBar
title="操作日志(仅演示,操作后不生效)"
:columns="columns"
@refresh="onSearch"
>
<PureTableBar :columns="columns" title="操作日志(仅演示,操作后不生效)" @refresh="onSearch">
<template #buttons>
<el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll">
<template #reference>
<el-button type="danger" :icon="useRenderIcon(Delete)">
清空日志
</el-button>
<el-button :icon="useRenderIcon(Delete)" type="danger"> 清空日志</el-button>
</template>
</el-popconfirm>
</template>
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div v-if="selectedNum > 0" v-motion-fade class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center">
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }}
</span>
<el-button type="primary" text @click="onSelectionCancel">
取消选择
</el-button>
<span class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" style="font-size: var(--el-font-size-base)"> 已选 {{ selectedNum }} </span>
<el-button text type="primary" @click="onSelectionCancel"> 取消选择</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<template #reference>
<el-button type="danger" text class="mr-1"> 批量删除 </el-button>
<el-button class="mr-1" text type="danger"> 批量删除</el-button>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
align-whole="center"
table-layout="auto"
:loading="loading"
:size="size"
adaptive
:adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:size="size"
adaptive
align-whole="center"
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
@ -148,7 +86,7 @@ const {
</div>
</template>
<style scoped lang="scss">
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}

View File

@ -1,4 +1,4 @@
<script setup lang="tsx">
<script lang="tsx" setup>
import { ref } from "vue";
import "vue-json-pretty/lib/styles.css";
import VueJsonPretty from "vue-json-pretty";
@ -82,15 +82,10 @@ const dataList = ref([
<template>
<div>
<el-scrollbar>
<PureDescriptions border :data="data" :columns="columns" :column="5" />
<PureDescriptions :column="5" :columns="columns" :data="data" border />
</el-scrollbar>
<el-tabs :modelValue="'responseBody'" type="border-card" class="mt-4">
<el-tab-pane
v-for="(item, index) in dataList"
:key="index"
:name="item.name"
:label="item.title"
>
<el-tabs :modelValue="'responseBody'" class="mt-4" type="border-card">
<el-tab-pane v-for="(item, index) in dataList" :key="index" :label="item.title" :name="item.name">
<el-scrollbar max-height="calc(100vh - 240px)">
<vue-json-pretty v-model:data="item.data" />
</el-scrollbar>

View File

@ -3,9 +3,9 @@ import Detail from "./detail.vue";
import { message } from "@/utils/message";
import { addDialog } from "@/components/ReDialog";
import type { PaginationProps } from "@pureadmin/table";
import { type Ref, reactive, ref, onMounted, toRaw } from "vue";
import { onMounted, reactive, ref, type Ref, toRaw } from "vue";
import { getKeyList, useCopyToClipboard } from "@pureadmin/utils";
import { getSystemLogsList, getSystemLogsDetail } from "@/api/system";
import { getSystemLogsDetail, getSystemLogsList } from "@/api/system";
import Info from "@iconify-icons/ri/question-line";
export function useRole(tableRef: Ref) {
@ -25,20 +25,20 @@ export function useRole(tableRef: Ref) {
background: true
});
// const getLevelType = (type, text = false) => {
// switch (type) {
// case 0:
// return text ? "debug" : "primary";
// case 1:
// return text ? "info" : "success";
// case 2:
// return text ? "warn" : "info";
// case 3:
// return text ? "error" : "warning";
// case 4:
// return text ? "fatal" : "danger";
// }
// };
const getLevelType = (type, text = false) => {
switch (type) {
case 0:
return text ? "debug" : "primary";
case 1:
return text ? "info" : "success";
case 2:
return text ? "warn" : "info";
case 3:
return text ? "error" : "warning";
case 4:
return text ? "fatal" : "danger";
}
};
const columns: TableColumnList = [
{
@ -98,26 +98,22 @@ export function useRole(tableRef: Ref) {
prop: "browser",
minWidth: 100
},
// {
// label: "级别",
// prop: "level",
// minWidth: 90,
// cellRenderer: ({ row, props }) => (
// <el-tag size={props.size} type={getLevelType(row.level)} effect="plain">
// {getLevelType(row.level, true)}
// </el-tag>
// )
// },
{
label: "级别",
prop: "level",
minWidth: 90,
cellRenderer: ({ row, props }) => (
<el-tag size={props.size} type={getLevelType(row.level)} effect="plain">
{getLevelType(row.level, true)}
</el-tag>
)
},
{
label: "请求耗时",
prop: "takesTime",
minWidth: 100,
cellRenderer: ({ row, props }) => (
<el-tag
size={props.size}
type={row.takesTime < 1000 ? "success" : "warning"}
effect="plain"
>
<el-tag size={props.size} type={row.takesTime < 1000 ? "success" : "warning"} effect="plain">
{row.takesTime} ms
</el-tag>
)
@ -126,8 +122,7 @@ export function useRole(tableRef: Ref) {
label: "请求时间",
prop: "requestTime",
minWidth: 180,
formatter: ({ requestTime }) =>
dayjs(requestTime).format("YYYY-MM-DD HH:mm:ss")
formatter: ({ requestTime }) => dayjs(requestTime).format("YYYY-MM-DD HH:mm:ss")
},
{
label: "操作",
@ -162,9 +157,7 @@ export function useRole(tableRef: Ref) {
function handleCellDblclick({ url }, { property }) {
if (property !== "url") return;
update(url);
copied.value
? message(`${url} 已拷贝`, { type: "success" })
: message("拷贝失败", { type: "warning" });
copied.value ? message(`${url} 已拷贝`, { type: "success" }) : message("拷贝失败", { type: "warning" });
}
/** 批量删除 */

View File

@ -1,10 +1,10 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import { useRole } from "./hook";
import { getPickerShortcuts } from "../../utils";
import { PureTableBar } from "@/components/RePureTableBar";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import PureTable from "@pureadmin/table";
import View from "@iconify-icons/ep/view";
import Delete from "@iconify-icons/ep/delete";
import Refresh from "@iconify-icons/ep/refresh";
@ -38,115 +38,63 @@ const {
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="所属模块" prop="module">
<el-input
v-model="form.module"
placeholder="请输入所属模块"
clearable
class="!w-[170px]"
/>
<el-input v-model="form.module" class="!w-[170px]" clearable placeholder="请输入所属模块" />
</el-form-item>
<el-form-item label="请求时间" prop="requestTime">
<el-date-picker
v-model="form.requestTime"
:shortcuts="getPickerShortcuts()"
type="datetimerange"
range-separator="至"
start-placeholder="开始日期时间"
end-placeholder="结束日期时间"
/>
<el-date-picker v-model="form.requestTime" :shortcuts="getPickerShortcuts()" end-placeholder="结束日期时间" range-separator="" start-placeholder="开始日期时间" type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button>
</el-form-item>
</el-form>
<PureTableBar
title="系统日志(仅演示,操作后不生效)"
:columns="columns"
@refresh="onSearch"
>
<PureTableBar :columns="columns" title="系统日志(仅演示,操作后不生效)" @refresh="onSearch">
<template #buttons>
<el-popconfirm title="确定要删除所有日志数据吗?" @confirm="clearAll">
<template #reference>
<el-button type="danger" :icon="useRenderIcon(Delete)">
清空日志
</el-button>
<el-button :icon="useRenderIcon(Delete)" type="danger"> 清空日志</el-button>
</template>
</el-popconfirm>
</template>
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div v-if="selectedNum > 0" v-motion-fade class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center">
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }}
</span>
<el-button type="primary" text @click="onSelectionCancel">
取消选择
</el-button>
<span class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" style="font-size: var(--el-font-size-base)"> 已选 {{ selectedNum }} </span>
<el-button text type="primary" @click="onSelectionCancel"> 取消选择</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<template #reference>
<el-button type="danger" text class="mr-1"> 批量删除 </el-button>
<el-button class="mr-1" text type="danger"> 批量删除</el-button>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
align-whole="center"
table-layout="auto"
:loading="loading"
:size="size"
adaptive
:adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
row-key="id"
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
@cell-dblclick="handleCellDblclick"
>
<template #operation="{ row }">
<el-button
class="reset-margin !outline-none"
link
type="primary"
:size="size"
:icon="useRenderIcon(View)"
@click="onDetail(row)"
>
详情
</el-button>
<el-button :icon="useRenderIcon(View)" :size="size" class="reset-margin !outline-none" link type="primary" @click="onDetail(row)"> 详情 </el-button>
</template>
</pure-table>
</template>
@ -154,7 +102,7 @@ const {
</div>
</template>
<style scoped lang="scss">
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import { useRole } from "./hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRole } from "@/hooks/monitor/useRole";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Plane from "@iconify-icons/ri/plane-line";
@ -12,93 +12,47 @@ defineOptions({
});
const formRef = ref();
const {
form,
loading,
columns,
dataList,
pagination,
onSearch,
resetForm,
handleOffline,
handleSizeChange,
handleCurrentChange,
handleSelectionChange
} = useRole();
const { form, loading, columns, dataList, pagination, onSearch, resetForm, handleOffline, handleSizeChange, handleCurrentChange, handleSelectionChange } = useRole();
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="用户名" prop="username">
<el-input
v-model="form.username"
placeholder="请输入用户名"
clearable
class="!w-[180px]"
/>
<el-input v-model="form.username" class="!w-[180px]" clearable placeholder="请输入用户名" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button>
</el-form-item>
</el-form>
<PureTableBar
title="在线用户(仅演示,操作后不生效)"
:columns="columns"
@refresh="onSearch"
>
<PureTableBar :columns="columns" title="在线用户(仅演示,操作后不生效)" @refresh="onSearch">
<template v-slot="{ size, dynamicColumns }">
<pure-table
align-whole="center"
showOverflowTooltip
table-layout="auto"
:loading="loading"
:size="size"
adaptive
:adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small'"
:size="size"
adaptive
align-whole="center"
showOverflowTooltip
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-popconfirm
:title="`是否强制下线${row.username}`"
@confirm="handleOffline(row)"
>
<el-popconfirm :title="`是否强制下线${row.username}`" @confirm="handleOffline(row)">
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Plane)"
>
强退
</el-button>
<el-button :icon="useRenderIcon(Plane)" :size="size" class="reset-margin" link type="primary"> 强退 </el-button>
</template>
</el-popconfirm>
</template>
@ -108,7 +62,7 @@ const {
</div>
</template>
<style scoped lang="scss">
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}

View File

@ -0,0 +1,150 @@
<template>
<TablePlusBar
:column="columns"
:data-list="dataList"
:form="form"
:handle-current-change="handleCurrentChange"
:handle-selection-change="handleSelectionChange"
:handle-size-change="handleSizeChange"
:loading="loading"
:onReFresh="onReFresh"
:pagination="pagination"
row-key="id"
tableTitle="系统测试(仅演示,操作后不生效)"
@changeColumn="handleChangeColumn"
@cell-dblclick="handleCellDblclick"
>
<template #tableForm>
<el-form-item label="用户名" prop="username">
<el-input v-model="form.username" class="!w-[150px]" clearable placeholder="请输入用户名" />
</el-form-item>
<el-form-item label="登录状态" prop="status">
<el-select v-model="form.status" class="!w-[150px]" clearable placeholder="请选择">
<el-option label="成功" value="1" />
<el-option label="失败" value="0" />
</el-select>
</el-form-item>
<el-form-item label="登录时间" prop="loginTime">
<el-date-picker v-model="form.loginTime" :shortcuts="getPickerShortcuts()" end-placeholder="结束日期时间" range-separator="" start-placeholder="开始日期时间" type="datetimerange" />
</el-form-item>
<el-form-item>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置</el-button>
</el-form-item>
</template>
<template #tableButtons>
<el-button type="danger">按钮</el-button>
</template>
<template #tableSelect>
<div v-if="selectedNum > 0" v-motion-fade class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center">
<div class="flex-auto">
<span class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]" style="font-size: var(--el-font-size-base)"> 已选 {{ selectedNum }} </span>
<el-button text type="primary" @click="onSelectionCancel"> 取消选择</el-button>
</div>
<el-popconfirm title="是否确认删除?" @confirm="onbatchDel">
<template #reference>
<el-button class="mr-1" text type="danger"> 批量删除</el-button>
</template>
</el-popconfirm>
</div>
</template>
<template #operation> 111</template>
</TablePlusBar>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref } from "vue";
import { PaginationProps } from "@pureadmin/table";
import TablePlusBar from "@/components/TableBar/src/TablePlusBar.vue";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Refresh from "@iconify-icons/ep/refresh";
import { getPickerShortcuts } from "@/views/monitor/utils";
import { delay } from "@pureadmin/utils";
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const loading = ref(true);
const form = reactive({});
const selectedNum = ref(0);
const pagination = reactive<PaginationProps>({
total: 0,
pageSize: 10,
currentPage: 1,
background: true
});
let columns = ref([
{ label: t("table.tableNumber"), prop: "id", minWidth: 60 },
{ label: "用户名", prop: "username", minWidth: 100 },
{ label: "登录 IP", prop: "ip", minWidth: 140 },
{ label: "登录地点", prop: "address", minWidth: 140 },
{ label: "操作系统", prop: "system", minWidth: 100 },
{ label: "浏览器类型", prop: "browser", minWidth: 100 },
{ label: "登录时间", prop: "loginTime", minWidth: 180 },
{ label: "操作", fixed: "right", slot: "operation" }
]);
const dataList = [
{
id: 1,
username: "admin",
ip: "163.108.28.226",
address: "中国河南省信阳市",
system: "macOS",
browser: "Chrome",
loginTime: "2024-05-12T07:33:12.080Z"
},
{
id: 2,
username: "common",
ip: "106.62.252.181",
address: "中国广东省深圳市",
system: "Windows",
browser: "Firefox",
loginTime: "2024-05-12T07:33:12.080Z"
}
];
const onSearch = () => {
console.log(form);
console.log("搜索。。。");
};
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
onSearch();
};
function handleSizeChange(val: number) {
console.log(`${val} items per page`);
}
function handleCurrentChange(val: number) {
console.log(`current page: ${val}`);
}
function handleSelectionChange(val) {
console.log("handleSelectionChange", val);
}
function onReFresh() {
loading.value = true;
delay(500).then(() => (loading.value = false));
}
function handleChangeColumn(value: Object[]) {
columns.value = value;
}
const onSelectionCancel = () => {};
const onbatchDel = () => {};
const handleCellDblclick = val => {
console.log(val);
};
onMounted(() => {
setTimeout(() => (loading.value = false), 1000);
});
</script>

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import ReCol from "@/components/ReCol";
import { formRules } from "./utils/rule";
import { FormProps } from "./utils/types";
import { usePublicHooks } from "../hooks";
import { usePublicHooks } from "@/hooks/system/usePublicHooks";
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
@ -31,18 +31,12 @@ defineExpose({ getRef });
</script>
<template>
<el-form
ref="ruleFormRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<el-form ref="ruleFormRef" :model="newFormInline" :rules="formRules" label-width="82px">
<el-row :gutter="30">
<re-col>
<el-form-item label="上级部门">
<el-cascader
v-model="newFormInline.parentId"
class="w-full"
:options="newFormInline.higherDeptOptions"
:props="{
value: 'id',
@ -50,6 +44,7 @@ defineExpose({ getRef });
emitPath: false,
checkStrictly: true
}"
class="w-full"
clearable
filterable
placeholder="请选择上级部门"
@ -62,76 +57,42 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="部门名称" prop="name">
<el-input
v-model="newFormInline.name"
clearable
placeholder="请输入部门名称"
/>
<el-input v-model="newFormInline.name" clearable placeholder="请输入部门名称" />
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="部门负责人">
<el-input
v-model="newFormInline.principal"
clearable
placeholder="请输入部门负责人"
/>
<el-input v-model="newFormInline.principal" clearable placeholder="请输入部门负责人" />
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="手机号" prop="phone">
<el-input
v-model="newFormInline.phone"
clearable
placeholder="请输入手机号"
/>
<el-input v-model="newFormInline.phone" clearable placeholder="请输入手机号" />
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="邮箱" prop="email">
<el-input
v-model="newFormInline.email"
clearable
placeholder="请输入邮箱"
/>
<el-input v-model="newFormInline.email" clearable placeholder="请输入邮箱" />
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="排序">
<el-input-number
v-model="newFormInline.sort"
class="!w-full"
:min="0"
:max="9999"
controls-position="right"
/>
<el-input-number v-model="newFormInline.sort" :max="9999" :min="0" class="!w-full" controls-position="right" />
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="部门状态">
<el-switch
v-model="newFormInline.status"
inline-prompt
:active-value="1"
:inactive-value="0"
active-text="启用"
inactive-text="停用"
:style="switchStyle"
/>
<el-switch v-model="newFormInline.status" :active-value="1" :inactive-value="0" :style="switchStyle" active-text="启用" inactive-text="停用" inline-prompt />
</el-form-item>
</re-col>
<re-col>
<el-form-item label="备注">
<el-input
v-model="newFormInline.remark"
placeholder="请输入备注信息"
type="textarea"
/>
<el-input v-model="newFormInline.remark" placeholder="请输入备注信息" type="textarea" />
</el-form-item>
</re-col>
</el-row>

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import { useDept } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete";
@ -15,131 +15,57 @@ defineOptions({
const formRef = ref();
const tableRef = ref();
const {
form,
loading,
columns,
dataList,
onSearch,
resetForm,
openDialog,
handleDelete,
handleSelectionChange
} = useDept();
const { form, loading, columns, dataList, onSearch, resetForm, openDialog, handleDelete, handleSelectionChange } = useDept();
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="部门名称:" prop="name">
<el-input
v-model="form.name"
placeholder="请输入部门名称"
clearable
class="!w-[180px]"
/>
<el-input v-model="form.name" class="!w-[180px]" clearable placeholder="请输入部门名称" />
</el-form-item>
<el-form-item label="状态:" prop="status">
<el-select
v-model="form.status"
placeholder="请选择状态"
clearable
class="!w-[180px]"
>
<el-option label="启用" :value="1" />
<el-option label="停用" :value="0" />
<el-select v-model="form.status" class="!w-[180px]" clearable placeholder="请选择状态">
<el-option :value="1" label="启用" />
<el-option :value="0" label="停用" />
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置 </el-button>
</el-form-item>
</el-form>
<PureTableBar
title="部门管理(仅演示,操作后不生效)"
:columns="columns"
:tableRef="tableRef?.getTableRef()"
@refresh="onSearch"
>
<PureTableBar :columns="columns" :tableRef="tableRef?.getTableRef()" title="部门管理(仅演示,操作后不生效)" @refresh="onSearch">
<template #buttons>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
>
新增部门
</el-button>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增部门 </el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
adaptive
:adaptiveConfig="{ offsetBottom: 45 }"
align-whole="center"
row-key="id"
showOverflowTooltip
table-layout="auto"
default-expand-all
:loading="loading"
:size="size"
:data="dataList"
:columns="dynamicColumns"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:size="size"
adaptive
align-whole="center"
default-expand-all
row-key="id"
showOverflowTooltip
table-layout="auto"
@selection-change="handleSelectionChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
修改
</el-button>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增', { parentId: row.id } as any)"
>
新增
</el-button>
<el-popconfirm
:title="`是否确认删除部门名称为${row.name}的这条数据`"
@confirm="handleDelete(row)"
>
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="openDialog('修改', row)"> 修改 </el-button>
<el-button :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="openDialog('新增', { parentId: row.id } as any)"> 新增 </el-button>
<el-popconfirm :title="`是否确认删除部门名称为${row.name}的这条数据`" @confirm="handleDelete(row)">
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
</template>
</el-popconfirm>
</template>

View File

@ -1,4 +1,4 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import ReCol from "@/components/ReCol";
import { formRules } from "./utils/rule";
@ -7,15 +7,7 @@ import { transformI18n } from "@/plugins/i18n";
import { IconSelect } from "@/components/ReIcon";
import Segmented from "@/components/ReSegmented";
import ReAnimateSelector from "@/components/ReAnimateSelector";
import {
menuTypeOptions,
showLinkOptions,
fixedTagOptions,
keepAliveOptions,
hiddenTagOptions,
showParentOptions,
frameLoadingOptions
} from "./utils/enums";
import { fixedTagOptions, frameLoadingOptions, hiddenTagOptions, keepAliveOptions, menuTypeOptions, showLinkOptions, showParentOptions } from "./utils/enums";
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
@ -55,19 +47,11 @@ defineExpose({ getRef });
</script>
<template>
<el-form
ref="ruleFormRef"
:model="newFormInline"
:rules="formRules"
label-width="82px"
>
<el-form ref="ruleFormRef" :model="newFormInline" :rules="formRules" label-width="82px">
<el-row :gutter="30">
<re-col>
<el-form-item label="菜单类型">
<Segmented
v-model="newFormInline.menuType"
:options="menuTypeOptions"
/>
<Segmented v-model="newFormInline.menuType" :options="menuTypeOptions" />
</el-form-item>
</re-col>
@ -75,7 +59,6 @@ defineExpose({ getRef });
<el-form-item label="上级菜单">
<el-cascader
v-model="newFormInline.parentId"
class="w-full"
:options="newFormInline.higherMenuOptions"
:props="{
value: 'id',
@ -83,6 +66,7 @@ defineExpose({ getRef });
emitPath: false,
checkStrictly: true
}"
class="w-full"
clearable
filterable
placeholder="请选择上级菜单"
@ -95,158 +79,80 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="菜单名称" prop="title">
<el-input
v-model="newFormInline.title"
clearable
placeholder="请输入菜单名称"
/>
<el-input v-model="newFormInline.title" clearable placeholder="请输入菜单名称" />
</el-form-item>
</re-col>
<re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24">
<re-col v-if="newFormInline.menuType !== 3" :sm="24" :value="12" :xs="24">
<el-form-item label="路由名称" prop="name">
<el-input
v-model="newFormInline.name"
clearable
placeholder="请输入路由名称"
/>
<el-input v-model="newFormInline.name" clearable placeholder="请输入路由名称" />
</el-form-item>
</re-col>
<re-col v-if="newFormInline.menuType !== 3" :value="12" :xs="24" :sm="24">
<re-col v-if="newFormInline.menuType !== 3" :sm="24" :value="12" :xs="24">
<el-form-item label="路由路径" prop="path">
<el-input
v-model="newFormInline.path"
clearable
placeholder="请输入路由路径"
/>
<el-input v-model="newFormInline.path" clearable placeholder="请输入路由路径" />
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType === 0"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType === 0" :sm="24" :value="12" :xs="24">
<el-form-item label="组件路径">
<el-input
v-model="newFormInline.component"
clearable
placeholder="请输入组件路径"
/>
<el-input v-model="newFormInline.component" clearable placeholder="请输入组件路径" />
</el-form-item>
</re-col>
<re-col :value="12" :xs="24" :sm="24">
<re-col :sm="24" :value="12" :xs="24">
<el-form-item label="菜单排序">
<el-input-number
v-model="newFormInline.rank"
class="!w-full"
:min="1"
:max="9999"
controls-position="right"
/>
<el-input-number v-model="newFormInline.rank" :max="9999" :min="1" class="!w-full" controls-position="right" />
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType === 0"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType === 0" :sm="24" :value="12" :xs="24">
<el-form-item label="路由重定向">
<el-input
v-model="newFormInline.redirect"
clearable
placeholder="请输入默认跳转地址"
/>
<el-input v-model="newFormInline.redirect" clearable placeholder="请输入默认跳转地址" />
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType !== 3"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType !== 3" :sm="24" :value="12" :xs="24">
<el-form-item label="菜单图标">
<IconSelect v-model="newFormInline.icon" class="w-full" />
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType !== 3"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType !== 3" :sm="24" :value="12" :xs="24">
<el-form-item label="右侧图标">
<el-input
v-model="newFormInline.extraIcon"
clearable
placeholder="菜单名称右侧的额外图标"
/>
<el-input v-model="newFormInline.extraIcon" clearable placeholder="菜单名称右侧的额外图标" />
</el-form-item>
</re-col>
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
<el-form-item label="进场动画">
<ReAnimateSelector
v-model="newFormInline.enterTransition"
placeholder="请选择页面进场加载动画"
/>
<ReAnimateSelector v-model="newFormInline.enterTransition" placeholder="请选择页面进场加载动画" />
</el-form-item>
</re-col>
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
<el-form-item label="离场动画">
<ReAnimateSelector
v-model="newFormInline.leaveTransition"
placeholder="请选择页面离场加载动画"
/>
<ReAnimateSelector v-model="newFormInline.leaveTransition" placeholder="请选择页面离场加载动画" />
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType === 0"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType === 0" :sm="24" :value="12" :xs="24">
<el-form-item label="菜单激活">
<el-input
v-model="newFormInline.activePath"
clearable
placeholder="请输入需要激活的菜单"
/>
<el-input v-model="newFormInline.activePath" clearable placeholder="请输入需要激活的菜单" />
</el-form-item>
</re-col>
<re-col v-if="newFormInline.menuType === 3" :value="12" :xs="24" :sm="24">
<re-col v-if="newFormInline.menuType === 3" :sm="24" :value="12" :xs="24">
<!-- 按钮级别权限设置 -->
<el-form-item label="权限标识" prop="auths">
<el-input
v-model="newFormInline.auths"
clearable
placeholder="请输入权限标识"
/>
<el-input v-model="newFormInline.auths" clearable placeholder="请输入权限标识" />
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType === 1"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType === 1" :sm="24" :value="12" :xs="24">
<!-- iframe -->
<el-form-item label="链接地址">
<el-input
v-model="newFormInline.frameSrc"
clearable
placeholder="请输入 iframe 链接地址"
/>
<el-input v-model="newFormInline.frameSrc" clearable placeholder="请输入 iframe 链接地址" />
</el-form-item>
</re-col>
<re-col v-if="newFormInline.menuType === 1" :value="12" :xs="24" :sm="24">
<re-col v-if="newFormInline.menuType === 1" :sm="24" :value="12" :xs="24">
<el-form-item label="加载动画">
<Segmented
:modelValue="newFormInline.frameLoading ? 0 : 1"
@ -260,12 +166,7 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType !== 3"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType !== 3" :sm="24" :value="12" :xs="24">
<el-form-item label="菜单">
<Segmented
:modelValue="newFormInline.showLink ? 0 : 1"
@ -278,12 +179,7 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<re-col
v-show="newFormInline.menuType !== 3"
:value="12"
:xs="24"
:sm="24"
>
<re-col v-show="newFormInline.menuType !== 3" :sm="24" :value="12" :xs="24">
<el-form-item label="父级菜单">
<Segmented
:modelValue="newFormInline.showParent ? 0 : 1"
@ -297,7 +193,7 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
<el-form-item label="缓存页面">
<Segmented
:modelValue="newFormInline.keepAlive ? 0 : 1"
@ -311,7 +207,7 @@ defineExpose({ getRef });
</el-form-item>
</re-col>
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
<el-form-item label="标签页">
<Segmented
:modelValue="newFormInline.hiddenTag ? 1 : 0"
@ -324,7 +220,7 @@ defineExpose({ getRef });
/>
</el-form-item>
</re-col>
<re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
<re-col v-show="newFormInline.menuType < 2" :sm="24" :value="12" :xs="24">
<el-form-item label="固定标签页">
<Segmented
:modelValue="newFormInline.fixedTag ? 0 : 1"

View File

@ -1,8 +1,8 @@
<script setup lang="ts">
<script lang="ts" setup>
import { ref } from "vue";
import { useMenu } from "./utils/hook";
import { transformI18n } from "@/plugins/i18n";
import { PureTableBar } from "@/components/RePureTableBar";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete";
@ -16,105 +16,47 @@ defineOptions({
const formRef = ref();
const tableRef = ref();
const {
form,
loading,
columns,
dataList,
onSearch,
resetForm,
openDialog,
handleDelete,
handleSelectionChange
} = useMenu();
const { form, loading, columns, dataList, onSearch, resetForm, openDialog, handleDelete, handleSelectionChange } = useMenu();
</script>
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="菜单名称:" prop="title">
<el-input
v-model="form.title"
placeholder="请输入菜单名称"
clearable
class="!w-[180px]"
/>
<el-input v-model="form.title" class="!w-[180px]" clearable placeholder="请输入菜单名称" />
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置 </el-button>
</el-form-item>
</el-form>
<PureTableBar
title="菜单管理(仅演示,操作后不生效)"
:columns="columns"
:isExpandAll="false"
:tableRef="tableRef?.getTableRef()"
@refresh="onSearch"
>
<PureTableBar :columns="columns" :isExpandAll="false" :tableRef="tableRef?.getTableRef()" title="菜单管理(仅演示,操作后不生效)" @refresh="onSearch">
<template #buttons>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
>
新增菜单
</el-button>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增菜单 </el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
adaptive
:adaptiveConfig="{ offsetBottom: 45 }"
align-whole="center"
row-key="id"
showOverflowTooltip
table-layout="auto"
:loading="loading"
:size="size"
:data="dataList"
:columns="dynamicColumns"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:size="size"
adaptive
align-whole="center"
row-key="id"
showOverflowTooltip
table-layout="auto"
@selection-change="handleSelectionChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
修改
</el-button>
<el-button
v-show="row.menuType !== 3"
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增', { parentId: row.id } as any)"
>
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="openDialog('修改', row)"> 修改 </el-button>
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="openDialog('新增', { parentId: row.id } as any)">
新增
</el-button>
<el-popconfirm
@ -122,15 +64,7 @@ const {
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
</template>
</el-popconfirm>
</template>

View File

@ -1,14 +1,9 @@
<script setup lang="ts">
<script lang="ts" setup>
import { useRole } from "./utils/hook";
import { ref, computed, nextTick, onMounted } from "vue";
import { PureTableBar } from "@/components/RePureTableBar";
import { computed, nextTick, onMounted, ref } from "vue";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import {
delay,
subBefore,
deviceDetection,
useResizeObserver
} from "@pureadmin/utils";
import { delay, deviceDetection, subBefore, useResizeObserver } from "@pureadmin/utils";
// import Database from "@iconify-icons/ri/database-2-line";
// import More from "@iconify-icons/ep/more-filled";
@ -82,9 +77,7 @@ onMounted(() => {
useResizeObserver(contentRef, async () => {
await nextTick();
delay(60).then(() => {
treeHeight.value = parseFloat(
subBefore(tableRef.value.getTableDoms().tableWrapper.style.height, "px")
);
treeHeight.value = parseFloat(subBefore(tableRef.value.getTableDoms().tableWrapper.style.height, "px"));
});
});
});
@ -92,134 +85,67 @@ onMounted(() => {
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto"
>
<el-form ref="formRef" :inline="true" :model="form" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto">
<el-form-item label="角色名称:" prop="name">
<el-input
v-model="form.name"
placeholder="请输入角色名称"
clearable
class="!w-[180px]"
/>
<el-input v-model="form.name" class="!w-[180px]" clearable placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="角色标识:" prop="code">
<el-input
v-model="form.code"
placeholder="请输入角色标识"
clearable
class="!w-[180px]"
/>
<el-input v-model="form.code" class="!w-[180px]" clearable placeholder="请输入角色标识" />
</el-form-item>
<el-form-item label="状态:" prop="status">
<el-select
v-model="form.status"
placeholder="请选择状态"
clearable
class="!w-[180px]"
>
<el-select v-model="form.status" class="!w-[180px]" clearable placeholder="请选择状态">
<el-option label="已启用" value="1" />
<el-option label="已停用" value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
搜索
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
重置
</el-button>
<el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch"> 搜索 </el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)"> 重置 </el-button>
</el-form-item>
</el-form>
<div
ref="contentRef"
:class="['flex', deviceDetection() ? 'flex-wrap' : '']"
>
<div ref="contentRef" :class="['flex', deviceDetection() ? 'flex-wrap' : '']">
<PureTableBar
:class="[isShow && !deviceDetection() ? '!w-[60vw]' : 'w-full']"
:columns="columns"
style="transition: width 220ms cubic-bezier(0.4, 0, 0.2, 1)"
title="角色管理(仅演示,操作后不生效)"
:columns="columns"
@refresh="onSearch"
>
<template #buttons>
<el-button
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog()"
>
新增角色
</el-button>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增角色 </el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
align-whole="center"
showOverflowTooltip
table-layout="auto"
:loading="loading"
:size="size"
adaptive
:row-style="rowStyle"
:adaptiveConfig="{ offsetBottom: 108 }"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:data="dataList"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
:loading="loading"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:row-style="rowStyle"
:size="size"
adaptive
align-whole="center"
showOverflowTooltip
table-layout="auto"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
修改
</el-button>
<el-popconfirm
:title="`是否确认删除角色名称为${row.name}的这条数据`"
@confirm="handleDelete(row)"
>
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary" @click="openDialog('修改', row)"> 修改 </el-button>
<el-popconfirm :title="`是否确认删除角色名称为${row.name}的这条数据`" @confirm="handleDelete(row)">
<template #reference>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Delete)"
>
删除
</el-button>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
</template>
</el-popconfirm>
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:icon="useRenderIcon(Menu)"
@click="handleMenu(row)"
>
权限
</el-button>
<el-button :icon="useRenderIcon(Menu)" :size="size" class="reset-margin" link type="primary" @click="handleMenu(row)"> 权限 </el-button>
<!-- <el-dropdown>
<el-button
class="ml-3 mt-[2px]"
@ -262,10 +188,7 @@ onMounted(() => {
</template>
</PureTableBar>
<div
v-if="isShow"
class="!min-w-[calc(100vw-60vw-268px)] mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto"
>
<div v-if="isShow" class="!min-w-[calc(100vw-60vw-268px)] mt-2 px-2 pb-2 bg-bg_color ml-2 overflow-auto">
<div class="flex justify-between w-full px-3 pt-5 pb-4">
<div class="flex">
<span :class="iconClass">
@ -273,10 +196,10 @@ onMounted(() => {
v-tippy="{
content: '关闭'
}"
class="dark:text-white"
width="18px"
height="18px"
:icon="Close"
class="dark:text-white"
height="18px"
width="18px"
@click="handleMenu"
/>
</span>
@ -285,10 +208,10 @@ onMounted(() => {
v-tippy="{
content: '保存菜单权限'
}"
class="dark:text-white"
width="18px"
height="18px"
:icon="Check"
class="dark:text-white"
height="18px"
width="18px"
@click="handleSave"
/>
</span>
@ -298,27 +221,13 @@ onMounted(() => {
{{ `${curRow?.name ? `${curRow.name}` : ""}` }}
</p>
</div>
<el-input
v-model="treeSearchValue"
placeholder="请输入菜单进行搜索"
class="mb-1"
clearable
@input="onQueryChanged"
/>
<el-input v-model="treeSearchValue" class="mb-1" clearable placeholder="请输入菜单进行搜索" @input="onQueryChanged" />
<div class="flex flex-wrap">
<el-checkbox v-model="isExpandAll" label="展开/折叠" />
<el-checkbox v-model="isSelectAll" label="全选/全不选" />
<el-checkbox v-model="isLinkage" label="父子联动" />
</div>
<el-tree-v2
ref="treeRef"
show-checkbox
:data="treeData"
:props="treeProps"
:height="treeHeight"
:check-strictly="!isLinkage"
:filter-method="filterMethod"
>
<el-tree-v2 ref="treeRef" :check-strictly="!isLinkage" :data="treeData" :filter-method="filterMethod" :height="treeHeight" :props="treeProps" show-checkbox>
<template #default="{ node }">
<span>{{ transformI18n(node.label) }}</span>
</template>
@ -328,7 +237,7 @@ onMounted(() => {
</div>
</template>
<style scoped lang="scss">
<style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) {
margin: 0;
}

View File

@ -2,7 +2,7 @@
import { ref } from "vue";
import tree from "./tree.vue";
import { useUser } from "./utils/hook";
import { PureTableBar } from "@/components/RePureTableBar";
import { PureTableBar } from "@/components/TableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Upload from "@iconify-icons/ri/upload-line";