Merge branch 'main' into gitee
|
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 392 B |
|
Before Width: | Height: | Size: 161 B After Width: | Height: | Size: 161 B |
|
Before Width: | Height: | Size: 235 B After Width: | Height: | Size: 235 B |
|
Before Width: | Height: | Size: 840 B After Width: | Height: | Size: 840 B |
@@ -7,7 +7,7 @@ defineOptions({
|
||||
name: "ReAnimateSelector"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请选择动画"
|
||||
@@ -81,7 +81,7 @@ function onMouseleave() {
|
||||
<el-select
|
||||
clearable
|
||||
filterable
|
||||
:placeholder="props.placeholder"
|
||||
:placeholder="placeholder"
|
||||
popper-class="pure-animate-popper"
|
||||
:model-value="inputValue"
|
||||
:filter-method="filterMethod"
|
||||
|
||||
@@ -7,7 +7,7 @@ defineOptions({
|
||||
name: "ReCropperPreview"
|
||||
});
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
imgSrc: String
|
||||
});
|
||||
|
||||
@@ -44,7 +44,7 @@ defineExpose({ hidePopover });
|
||||
<div class="w-[18vw]">
|
||||
<ReCropper
|
||||
ref="refCropper"
|
||||
:src="props.imgSrc"
|
||||
:src="imgSrc"
|
||||
circled
|
||||
@cropper="onCropper"
|
||||
@readied="showPopover = true"
|
||||
|
||||
@@ -29,9 +29,11 @@ const addDialog = (options: DialogOptions) => {
|
||||
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
|
||||
dialogStore.value[index].visible = false;
|
||||
options.closeCallBack && options.closeCallBack({ options, index, args });
|
||||
|
||||
const closeDelay = options?.closeDelay ?? 200;
|
||||
useTimeoutFn(() => {
|
||||
dialogStore.value.splice(index, 1);
|
||||
}, 200);
|
||||
}, closeDelay);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import VueJsonPretty from "vue-json-pretty";
|
||||
import "vue-json-pretty/lib/styles.css";
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
graphData: Object
|
||||
});
|
||||
</script>
|
||||
@@ -12,6 +12,6 @@ const props = defineProps({
|
||||
:path="'res'"
|
||||
:deep="3"
|
||||
:showLength="true"
|
||||
:data="props.graphData"
|
||||
:data="graphData"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -35,7 +35,7 @@ const nodeDragNode = item => {
|
||||
<!-- 左侧bpmn元素选择器 -->
|
||||
<div class="node-panel">
|
||||
<div
|
||||
v-for="item in props.nodeList"
|
||||
v-for="item in nodeList"
|
||||
:key="item.text"
|
||||
class="node-item dark:text-bg_color"
|
||||
@mousedown="nodeDragNode(item)"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
|
||||
|
||||
/**
|
||||
* 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标
|
||||
* @see 点击查看文档图标篇 {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/}
|
||||
* @see 点击查看文档图标篇 {@link https://pure-admin.github.io/pure-admin-doc/pages/icon/}
|
||||
* @param icon 必传 图标
|
||||
* @param attrs 可选 iconType 属性
|
||||
* @returns Component
|
||||
|
||||
@@ -18,11 +18,11 @@ import {
|
||||
getKeyList
|
||||
} from "@pureadmin/utils";
|
||||
|
||||
import DragIcon from "./svg/drag.svg?component";
|
||||
import ExpandIcon from "./svg/expand.svg?component";
|
||||
import RefreshIcon from "./svg/refresh.svg?component";
|
||||
import SettingIcon from "./svg/settings.svg?component";
|
||||
import CollapseIcon from "./svg/collapse.svg?component";
|
||||
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 = {
|
||||
/** 头部最左边的标题 */
|
||||
|
||||
@@ -502,7 +502,7 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :ref="'wrap' + props.classOption['key']">
|
||||
<div :ref="'wrap' + classOption['key']">
|
||||
<div
|
||||
v-if="navigation"
|
||||
:style="leftSwitch"
|
||||
@@ -520,7 +520,7 @@ defineExpose({
|
||||
<slot name="right-switch" />
|
||||
</div>
|
||||
<div
|
||||
:ref="'realBox' + props.classOption['key']"
|
||||
:ref="'realBox' + classOption['key']"
|
||||
:style="pos"
|
||||
@mouseenter="enter"
|
||||
@mouseleave="leave"
|
||||
@@ -529,7 +529,7 @@ defineExpose({
|
||||
@touchend="touchEnd"
|
||||
@mousewheel.passive="wheel"
|
||||
>
|
||||
<div :ref="'slotList' + props.classOption['key']" :style="float">
|
||||
<div :ref="'slotList' + classOption['key']" :style="float">
|
||||
<slot />
|
||||
</div>
|
||||
<div :style="float" v-html="copyHtml" />
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface OptionsType {
|
||||
label?: string | (() => VNode | Component);
|
||||
/**
|
||||
* @description 图标,采用平台内置的 `useRenderIcon` 函数渲染
|
||||
* @see {@link 用法参考 https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
* @see {@link 用法参考 https://pure-admin.github.io/pure-admin-doc/pages/icon/#%E9%80%9A%E7%94%A8%E5%9B%BE%E6%A0%87-userendericon-hooks }
|
||||
*/
|
||||
icon?: string | Component;
|
||||
/** 图标属性、样式配置 */
|
||||
|
||||
5
src/components/ReVxeTableBar/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import vxeTableBar from "./src/bar";
|
||||
import { withInstall } from "@pureadmin/utils";
|
||||
|
||||
/** 配合 `vxe-table` 实现快速便捷的表格操作 */
|
||||
export const VxeTableBar = withInstall(vxeTableBar);
|
||||
362
src/components/ReVxeTableBar/src/bar.tsx
Normal file
@@ -0,0 +1,362 @@
|
||||
import Sortable from "sortablejs";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { delay, cloneDeep, getKeyList } from "@pureadmin/utils";
|
||||
import {
|
||||
type PropType,
|
||||
ref,
|
||||
unref,
|
||||
computed,
|
||||
nextTick,
|
||||
defineComponent,
|
||||
getCurrentInstance
|
||||
} 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import Footer from "./footer/index.vue";
|
||||
import LayFrame from "../lay-frame/index.vue";
|
||||
import LayFooter from "../lay-footer/index.vue";
|
||||
import { useGlobal, isNumber } from "@pureadmin/utils";
|
||||
import KeepAliveFrame from "./keepAliveFrame/index.vue";
|
||||
import backTop from "@/assets/svg/back_top.svg?component";
|
||||
import BackTopIcon from "@/assets/svg/back_top.svg?component";
|
||||
import { h, computed, Transition, defineComponent } from "vue";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
|
||||
@@ -99,15 +99,15 @@ const transitionMain = defineComponent({
|
||||
|
||||
<template>
|
||||
<section
|
||||
:class="[props.fixedHeader ? 'app-main' : 'app-main-nofixed-header']"
|
||||
:class="[fixedHeader ? 'app-main' : 'app-main-nofixed-header']"
|
||||
:style="getSectionStyle"
|
||||
>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<KeepAliveFrame :currComp="Component" :currRoute="route">
|
||||
<LayFrame :currComp="Component" :currRoute="route">
|
||||
<template #default="{ Comp, fullPath, frameInfo }">
|
||||
<el-scrollbar
|
||||
v-if="props.fixedHeader"
|
||||
v-if="fixedHeader"
|
||||
:wrap-style="{
|
||||
display: 'flex',
|
||||
'flex-wrap': 'wrap',
|
||||
@@ -126,7 +126,7 @@ const transitionMain = defineComponent({
|
||||
:title="t('buttons.pureBackTop')"
|
||||
target=".app-main .el-scrollbar__wrap"
|
||||
>
|
||||
<backTop />
|
||||
<BackTopIcon />
|
||||
</el-backtop>
|
||||
<div class="grow">
|
||||
<transitionMain :route="route">
|
||||
@@ -150,7 +150,7 @@ const transitionMain = defineComponent({
|
||||
/>
|
||||
</transitionMain>
|
||||
</div>
|
||||
<Footer v-if="!hideFooter" />
|
||||
<LayFooter v-if="!hideFooter" />
|
||||
</el-scrollbar>
|
||||
<div v-else class="grow">
|
||||
<transitionMain :route="route">
|
||||
@@ -175,12 +175,12 @@ const transitionMain = defineComponent({
|
||||
</transitionMain>
|
||||
</div>
|
||||
</template>
|
||||
</KeepAliveFrame>
|
||||
</LayFrame>
|
||||
</template>
|
||||
</router-view>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<Footer v-if="!hideFooter && !props.fixedHeader" />
|
||||
<LayFooter v-if="!hideFooter && !fixedHeader" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { getConfig } from "@/config";
|
||||
import { useMultiFrame } from "@/layout/hooks/useMultiFrame";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { type Component, shallowRef, watch, computed } from "vue";
|
||||
import { type RouteRecordRaw, RouteLocationNormalizedLoaded } from "vue-router";
|
||||
import { useMultiFrame } from "@/layout/components/keepAliveFrame/useMultiFrame";
|
||||
|
||||
const props = defineProps<{
|
||||
currRoute: RouteLocationNormalizedLoaded;
|
||||
@@ -20,7 +20,7 @@ const keep = computed(() => {
|
||||
!!props.currRoute.meta?.frameSrc
|
||||
);
|
||||
});
|
||||
// 避免重新渲染 frameView
|
||||
// 避免重新渲染 LayFrame
|
||||
const normalComp = computed(() => !keep.value && props.currComp);
|
||||
|
||||
watch(useMultiTagsStoreHook().multiTags, (tags: any) => {
|
||||
@@ -65,7 +65,7 @@ watch(
|
||||
</script>
|
||||
<template>
|
||||
<template v-for="[fullPath, Comp] in compList" :key="fullPath">
|
||||
<div v-show="fullPath === props.currRoute.fullPath" class="w-full h-full">
|
||||
<div v-show="fullPath === currRoute.fullPath" class="w-full h-full">
|
||||
<slot
|
||||
:fullPath="fullPath"
|
||||
:Comp="Comp"
|
||||
@@ -74,6 +74,6 @@ watch(
|
||||
</div>
|
||||
</template>
|
||||
<div v-show="!keep" class="w-full h-full">
|
||||
<slot :Comp="normalComp" :fullPath="props.currRoute.fullPath" frameInfo />
|
||||
<slot :Comp="normalComp" :fullPath="currRoute.fullPath" frameInfo />
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import Search from "./search/index.vue";
|
||||
import Notice from "./notice/index.vue";
|
||||
import mixNav from "./sidebar/mixNav.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import FullScreen from "./sidebar/fullScreen.vue";
|
||||
import Breadcrumb from "./sidebar/breadCrumb.vue";
|
||||
import topCollapse from "./sidebar/topCollapse.vue";
|
||||
import { useTranslationLang } from "../hooks/useTranslationLang";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import LaySearch from "../lay-search/index.vue";
|
||||
import LayNotice from "../lay-notice/index.vue";
|
||||
import LayNavMix from "../lay-sidebar/NavMix.vue";
|
||||
import { useTranslationLang } from "@/layout/hooks/useTranslationLang";
|
||||
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
|
||||
import LaySidebarBreadCrumb from "../lay-sidebar/components/SidebarBreadCrumb.vue";
|
||||
import LaySidebarTopCollapse from "../lay-sidebar/components/SidebarTopCollapse.vue";
|
||||
|
||||
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
|
||||
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
|
||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
@@ -33,26 +34,26 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
|
||||
|
||||
<template>
|
||||
<div class="navbar bg-[#fff] shadow-sm shadow-[rgba(0,21,41,0.08)]">
|
||||
<topCollapse
|
||||
<LaySidebarTopCollapse
|
||||
v-if="device === 'mobile'"
|
||||
class="hamburger-container"
|
||||
:is-active="pureApp.sidebar.opened"
|
||||
@toggleClick="toggleSideBar"
|
||||
/>
|
||||
|
||||
<Breadcrumb
|
||||
<LaySidebarBreadCrumb
|
||||
v-if="layout !== 'mix' && device !== 'mobile'"
|
||||
class="breadcrumb-container"
|
||||
/>
|
||||
|
||||
<mixNav v-if="layout === 'mix'" />
|
||||
<LayNavMix v-if="layout === 'mix'" />
|
||||
|
||||
<div v-if="layout === 'vertical'" class="vertical-header-right">
|
||||
<!-- 菜单搜索 -->
|
||||
<Search id="header-search" />
|
||||
<LaySearch id="header-search" />
|
||||
<!-- 国际化 -->
|
||||
<el-dropdown id="header-translation" trigger="click">
|
||||
<globalization
|
||||
<GlobalizationIcon
|
||||
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
|
||||
/>
|
||||
<template #dropdown>
|
||||
@@ -83,9 +84,9 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 全屏 -->
|
||||
<FullScreen id="full-screen" />
|
||||
<LaySidebarFullScreen id="full-screen" />
|
||||
<!-- 消息通知 -->
|
||||
<Notice id="header-notice" />
|
||||
<LayNotice id="header-notice" />
|
||||
<!-- 退出登录 -->
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link navbar-bg-hover select-none">
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ListItem } from "./data";
|
||||
import { ListItem } from "../data";
|
||||
import { ref, PropType, nextTick } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { deviceDetection } from "@pureadmin/utils";
|
||||
@@ -52,9 +52,9 @@ function hoverDescription(event, description) {
|
||||
class="notice-container border-b-[1px] border-solid border-[#f0f0f0] dark:border-[#303030]"
|
||||
>
|
||||
<el-avatar
|
||||
v-if="props.noticeItem.avatar"
|
||||
v-if="noticeItem.avatar"
|
||||
:size="30"
|
||||
:src="props.noticeItem.avatar"
|
||||
:src="noticeItem.avatar"
|
||||
class="notice-container-avatar"
|
||||
/>
|
||||
<div class="notice-container-text">
|
||||
@@ -63,7 +63,7 @@ function hoverDescription(event, description) {
|
||||
popper-class="notice-title-popper"
|
||||
:effect="tooltipEffect"
|
||||
:disabled="!titleTooltip"
|
||||
:content="props.noticeItem.title"
|
||||
:content="noticeItem.title"
|
||||
placement="top-start"
|
||||
:enterable="!isMobile"
|
||||
>
|
||||
@@ -72,16 +72,16 @@ function hoverDescription(event, description) {
|
||||
class="notice-title-content"
|
||||
@mouseover="hoverTitle"
|
||||
>
|
||||
{{ props.noticeItem.title }}
|
||||
{{ noticeItem.title }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<el-tag
|
||||
v-if="props.noticeItem?.extra"
|
||||
:type="props.noticeItem?.status"
|
||||
v-if="noticeItem?.extra"
|
||||
:type="noticeItem?.status"
|
||||
size="small"
|
||||
class="notice-title-extra"
|
||||
>
|
||||
{{ props.noticeItem?.extra }}
|
||||
{{ noticeItem?.extra }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
@@ -89,19 +89,19 @@ function hoverDescription(event, description) {
|
||||
popper-class="notice-title-popper"
|
||||
:effect="tooltipEffect"
|
||||
:disabled="!descriptionTooltip"
|
||||
:content="props.noticeItem.description"
|
||||
:content="noticeItem.description"
|
||||
placement="top-start"
|
||||
>
|
||||
<div
|
||||
ref="descriptionRef"
|
||||
class="notice-text-description"
|
||||
@mouseover="hoverDescription($event, props.noticeItem.description)"
|
||||
@mouseover="hoverDescription($event, noticeItem.description)"
|
||||
>
|
||||
{{ props.noticeItem.description }}
|
||||
{{ noticeItem.description }}
|
||||
</div>
|
||||
</el-tooltip>
|
||||
<div class="notice-text-datetime text-[#00000073] dark:text-white">
|
||||
{{ props.noticeItem.datetime }}
|
||||
{{ noticeItem.datetime }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
24
src/layout/components/lay-notice/components/NoticeList.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { ListItem } from "../data";
|
||||
import NoticeItem from "./NoticeItem.vue";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
|
||||
defineProps({
|
||||
list: {
|
||||
type: Array as PropType<Array<ListItem>>,
|
||||
default: () => []
|
||||
},
|
||||
emptyText: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="list.length">
|
||||
<NoticeItem v-for="(item, index) in list" :key="index" :noticeItem="item" />
|
||||
</div>
|
||||
<el-empty v-else :description="transformI18n(emptyText)" />
|
||||
</template>
|
||||
99
src/layout/components/lay-notice/data.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
|
||||
export interface ListItem {
|
||||
avatar: string;
|
||||
title: string;
|
||||
datetime: string;
|
||||
type: string;
|
||||
description: string;
|
||||
status?: "primary" | "success" | "warning" | "info" | "danger";
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
name: string;
|
||||
list: ListItem[];
|
||||
emptyText: string;
|
||||
}
|
||||
|
||||
export const noticesData: TabItem[] = [
|
||||
{
|
||||
key: "1",
|
||||
name: $t("status.pureNotify"),
|
||||
list: [],
|
||||
emptyText: $t("status.pureNoNotify")
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
name: $t("status.pureMessage"),
|
||||
list: [
|
||||
{
|
||||
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile1.svg",
|
||||
title: "小铭 评论了你",
|
||||
description: "诚在于心,信在于行,诚信在于心行合一。",
|
||||
datetime: "今天",
|
||||
type: "2"
|
||||
},
|
||||
{
|
||||
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile2.svg",
|
||||
title: "李白 回复了你",
|
||||
description: "长风破浪会有时,直挂云帆济沧海。",
|
||||
datetime: "昨天",
|
||||
type: "2"
|
||||
},
|
||||
{
|
||||
avatar: "https://xiaoxian521.github.io/hyperlink/svg/smile5.svg",
|
||||
title: "标题",
|
||||
description:
|
||||
"请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
|
||||
datetime: "时间",
|
||||
type: "2"
|
||||
}
|
||||
],
|
||||
emptyText: $t("status.pureNoMessage")
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
name: $t("status.pureTodo"),
|
||||
list: [
|
||||
{
|
||||
avatar: "",
|
||||
title: "第三方紧急代码变更",
|
||||
description:
|
||||
"小林提交于 2024-05-10,需在 2024-05-11 前完成代码变更任务",
|
||||
datetime: "",
|
||||
extra: "马上到期",
|
||||
status: "danger",
|
||||
type: "3"
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "版本发布",
|
||||
description: "指派小铭于 2024-06-18 前完成更新并发布",
|
||||
datetime: "",
|
||||
extra: "已耗时 8 天",
|
||||
status: "warning",
|
||||
type: "3"
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "新功能开发",
|
||||
description: "开发多租户管理",
|
||||
datetime: "",
|
||||
extra: "进行中",
|
||||
type: "3"
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "任务名称",
|
||||
description: "任务需要在 2030-10-30 10:00 前启动",
|
||||
datetime: "",
|
||||
extra: "未开始",
|
||||
status: "info",
|
||||
type: "3"
|
||||
}
|
||||
],
|
||||
emptyText: $t("status.pureNoTodo")
|
||||
}
|
||||
];
|
||||
@@ -1,24 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { ref, computed } from "vue";
|
||||
import { noticesData } from "./data";
|
||||
import NoticeList from "./noticeList.vue";
|
||||
import Bell from "@iconify-icons/ep/bell";
|
||||
import NoticeList from "./components/NoticeList.vue";
|
||||
import BellIcon from "@iconify-icons/ep/bell";
|
||||
|
||||
const { t } = useI18n();
|
||||
const noticesNum = ref(0);
|
||||
const notices = ref(noticesData);
|
||||
const activeKey = ref(noticesData[0].key);
|
||||
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})` : "")
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dropdown trigger="click" placement="bottom-end">
|
||||
<span class="dropdown-badge navbar-bg-hover select-none">
|
||||
<el-badge :value="noticesNum" :max="99">
|
||||
<span
|
||||
:class="[
|
||||
'dropdown-badge',
|
||||
'navbar-bg-hover',
|
||||
'select-none',
|
||||
Number(noticesNum) !== 0 && 'mr-[10px]'
|
||||
]"
|
||||
>
|
||||
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
|
||||
<span class="header-notice-icon">
|
||||
<IconifyIconOffline :icon="Bell" />
|
||||
<IconifyIconOffline :icon="BellIcon" />
|
||||
</span>
|
||||
</el-badge>
|
||||
</span>
|
||||
@@ -37,13 +49,10 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
/>
|
||||
<span v-else>
|
||||
<template v-for="item in notices" :key="item.key">
|
||||
<el-tab-pane
|
||||
:label="`${item.name}(${item.list.length})`"
|
||||
:name="`${item.key}`"
|
||||
>
|
||||
<el-tab-pane :label="getLabel(item)" :name="`${item.key}`">
|
||||
<el-scrollbar max-height="330px">
|
||||
<div class="noticeList-container">
|
||||
<NoticeList :list="item.list" />
|
||||
<NoticeList :list="item.list" :emptyText="item.emptyText" />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
@@ -62,7 +71,6 @@ notices.value.map(v => (noticesNum.value += v.list.length));
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 48px;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
.header-notice-icon {
|
||||
@@ -4,7 +4,7 @@ import { emitter } from "@/utils/mitt";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from "vue";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
import Close from "@iconify-icons/ep/close";
|
||||
import CloseIcon from "@iconify-icons/ep/close";
|
||||
|
||||
const target = ref(null);
|
||||
const show = ref<Boolean>(false);
|
||||
@@ -68,7 +68,7 @@ onBeforeUnmount(() => {
|
||||
class="dark:text-white"
|
||||
width="18px"
|
||||
height="18px"
|
||||
:icon="Close"
|
||||
:icon="CloseIcon"
|
||||
@click="show = !show"
|
||||
/>
|
||||
</span>
|
||||
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import mdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
|
||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
import MdiKeyboardEsc from "@/assets/svg/keyboard_esc.svg?component";
|
||||
import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
import ArrowUpLine from "@iconify-icons/ri/arrow-up-line";
|
||||
import ArrowDownLine from "@iconify-icons/ri/arrow-down-line";
|
||||
|
||||
@@ -17,7 +17,7 @@ const { device } = useNav();
|
||||
<template>
|
||||
<div class="search-footer text-[#333] dark:text-white">
|
||||
<span class="search-footer-item">
|
||||
<enterOutlined class="icon" />
|
||||
<EnterOutlined class="icon" />
|
||||
{{ t("buttons.pureConfirm") }}
|
||||
</span>
|
||||
<span class="search-footer-item">
|
||||
@@ -26,14 +26,11 @@ const { device } = useNav();
|
||||
{{ t("buttons.pureSwitch") }}
|
||||
</span>
|
||||
<span class="search-footer-item">
|
||||
<mdiKeyboardEsc class="icon" />
|
||||
<MdiKeyboardEsc class="icon" />
|
||||
{{ t("buttons.pureClose") }}
|
||||
</span>
|
||||
<p
|
||||
v-if="device !== 'mobile' && props.total > 0"
|
||||
class="search-footer-total"
|
||||
>
|
||||
{{ `${t("search.pureTotal")} ${props.total}` }}
|
||||
<p v-if="device !== 'mobile' && total > 0" class="search-footer-total">
|
||||
{{ `${t("search.pureTotal")} ${total}` }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -2,8 +2,8 @@
|
||||
import type { optionsItem } from "../types";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import Star from "@iconify-icons/ep/star";
|
||||
import Close from "@iconify-icons/ep/close";
|
||||
import StarIcon from "@iconify-icons/ep/star";
|
||||
import CloseIcon from "@iconify-icons/ep/close";
|
||||
|
||||
interface Props {
|
||||
item: optionsItem;
|
||||
@@ -33,12 +33,12 @@ function handleDelete(item) {
|
||||
</span>
|
||||
<IconifyIconOffline
|
||||
v-show="item.type === 'history'"
|
||||
:icon="Star"
|
||||
:icon="StarIcon"
|
||||
class="w-[18px] h-[18px] mr-2 hover:text-[#d7d5d4]"
|
||||
@click.stop="handleCollect(item)"
|
||||
/>
|
||||
<IconifyIconOffline
|
||||
:icon="Close"
|
||||
:icon="CloseIcon"
|
||||
class="w-[18px] h-[18px] hover:text-[#d7d5d4] cursor-pointer"
|
||||
@click.stop="handleDelete(item)"
|
||||
/>
|
||||
@@ -13,7 +13,7 @@ import { ref, computed, shallowRef, watch } from "vue";
|
||||
import { useDebounceFn, onKeyStroke } from "@vueuse/core";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { cloneDeep, isAllEmpty, storageLocal } from "@pureadmin/utils";
|
||||
import Search from "@iconify-icons/ri/search-line";
|
||||
import SearchIcon from "@iconify-icons/ri/search-line";
|
||||
|
||||
interface Props {
|
||||
/** 弹窗显隐 */
|
||||
@@ -298,7 +298,7 @@ onKeyStroke("ArrowDown", handleDown);
|
||||
>
|
||||
<template #prefix>
|
||||
<IconifyIconOffline
|
||||
:icon="Search"
|
||||
:icon="SearchIcon"
|
||||
class="text-primary w-[24px] h-[24px]"
|
||||
/>
|
||||
</template>
|
||||
@@ -5,7 +5,7 @@ import { useResizeObserver } from "@pureadmin/utils";
|
||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { ref, computed, getCurrentInstance, onMounted } from "vue";
|
||||
import enterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
import EnterOutlined from "@/assets/svg/enter_outlined.svg?component";
|
||||
|
||||
interface Emits {
|
||||
(e: "update:value", val: string): void;
|
||||
@@ -84,7 +84,7 @@ defineExpose({ handleScroll });
|
||||
<span class="result-item-title">
|
||||
{{ transformI18n(item.meta?.title) }}
|
||||
</span>
|
||||
<enterOutlined />
|
||||
<EnterOutlined />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { SearchModal } from "./components";
|
||||
import { useBoolean } from "../../hooks/useBoolean";
|
||||
import SearchModal from "./components/SearchModal.vue";
|
||||
|
||||
const { bool: show, toggle } = useBoolean();
|
||||
function handleSearch() {
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
onBeforeMount
|
||||
} from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import panel from "../panel/index.vue";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import LayPanel from "../lay-panel/index.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { useAppStoreHook } from "@/store/modules/app";
|
||||
import { toggleTheme } from "@pureadmin/theme/dist/browser-utils";
|
||||
@@ -23,9 +23,9 @@ import { useDark, useGlobal, debounce, isNumber } from "@pureadmin/utils";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import LeftArrow from "@iconify-icons/ri/arrow-left-s-line";
|
||||
import RightArrow from "@iconify-icons/ri/arrow-right-s-line";
|
||||
import dayIcon from "@/assets/svg/day.svg?component";
|
||||
import darkIcon from "@/assets/svg/dark.svg?component";
|
||||
import systemIcon from "@/assets/svg/system.svg?component";
|
||||
import DayIcon from "@/assets/svg/day.svg?component";
|
||||
import DarkIcon from "@/assets/svg/dark.svg?component";
|
||||
import SystemIcon from "@/assets/svg/system.svg?component";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { device } = useNav();
|
||||
@@ -199,21 +199,21 @@ const themeOptions = computed<Array<OptionsType>>(() => {
|
||||
return [
|
||||
{
|
||||
label: t("panel.pureOverallStyleLight"),
|
||||
icon: dayIcon,
|
||||
icon: DayIcon,
|
||||
theme: "light",
|
||||
tip: t("panel.pureOverallStyleLightTip"),
|
||||
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
|
||||
},
|
||||
{
|
||||
label: t("panel.pureOverallStyleDark"),
|
||||
icon: darkIcon,
|
||||
icon: DarkIcon,
|
||||
theme: "dark",
|
||||
tip: t("panel.pureOverallStyleDarkTip"),
|
||||
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
|
||||
},
|
||||
{
|
||||
label: t("panel.pureOverallStyleSystem"),
|
||||
icon: systemIcon,
|
||||
icon: SystemIcon,
|
||||
theme: "system",
|
||||
tip: t("panel.pureOverallStyleSystemTip"),
|
||||
iconAttrs: { fill: isDark.value ? "#fff" : "#000" }
|
||||
@@ -313,7 +313,7 @@ onUnmounted(() => removeMatchMedia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<panel>
|
||||
<LayPanel>
|
||||
<div class="p-5">
|
||||
<p :class="pClass">{{ t("panel.pureOverallStyle") }}</p>
|
||||
<Segmented
|
||||
@@ -517,7 +517,7 @@ onUnmounted(() => removeMatchMedia);
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</panel>
|
||||
</LayPanel>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -1,14 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import FullScreen from "./fullScreen.vue";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import { isAllEmpty } from "@pureadmin/utils";
|
||||
import { ref, nextTick, computed } from "vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import LaySearch from "../lay-search/index.vue";
|
||||
import LayNotice from "../lay-notice/index.vue";
|
||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
|
||||
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
|
||||
|
||||
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
|
||||
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
|
||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
@@ -21,11 +22,11 @@ const { t, route, locale, translationCh, translationEn } =
|
||||
const {
|
||||
title,
|
||||
logout,
|
||||
backTopMenu,
|
||||
onPanel,
|
||||
getLogo,
|
||||
username,
|
||||
userAvatar,
|
||||
backTopMenu,
|
||||
avatarsStyle,
|
||||
toAccountSettings,
|
||||
getDropdownItemStyle,
|
||||
@@ -58,7 +59,7 @@ nextTick(() => {
|
||||
class="horizontal-header-menu"
|
||||
:default-active="defaultActive"
|
||||
>
|
||||
<sidebar-item
|
||||
<LaySidebarItem
|
||||
v-for="route in usePermissionStoreHook().wholeMenus"
|
||||
:key="route.path"
|
||||
:item="route"
|
||||
@@ -67,10 +68,10 @@ nextTick(() => {
|
||||
</el-menu>
|
||||
<div class="horizontal-header-right">
|
||||
<!-- 菜单搜索 -->
|
||||
<Search id="header-search" />
|
||||
<LaySearch id="header-search" />
|
||||
<!-- 国际化 -->
|
||||
<el-dropdown id="header-translation" trigger="click">
|
||||
<globalization
|
||||
<GlobalizationIcon
|
||||
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
|
||||
/>
|
||||
<template #dropdown>
|
||||
@@ -99,9 +100,9 @@ nextTick(() => {
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 全屏 -->
|
||||
<FullScreen id="full-screen" />
|
||||
<LaySidebarFullScreen id="full-screen" />
|
||||
<!-- 消息通知 -->
|
||||
<Notice id="header-notice" />
|
||||
<LayNotice id="header-notice" />
|
||||
<!-- 退出登录 -->
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link navbar-bg-hover">
|
||||
@@ -1,17 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import Search from "../search/index.vue";
|
||||
import Notice from "../notice/index.vue";
|
||||
import FullScreen from "./fullScreen.vue";
|
||||
import { isAllEmpty } from "@pureadmin/utils";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import LaySearch from "../lay-search/index.vue";
|
||||
import LayNotice from "../lay-notice/index.vue";
|
||||
import { ref, toRaw, watch, onMounted, nextTick } from "vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import { getParentPaths, findRouteByPath } from "@/router/utils";
|
||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import globalization from "@/assets/svg/globalization.svg?component";
|
||||
import LaySidebarExtraIcon from "../lay-sidebar/components/SidebarExtraIcon.vue";
|
||||
import LaySidebarFullScreen from "../lay-sidebar/components/SidebarFullScreen.vue";
|
||||
|
||||
import GlobalizationIcon from "@/assets/svg/globalization.svg?component";
|
||||
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
|
||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
|
||||
import Setting from "@iconify-icons/ri/settings-3-line";
|
||||
@@ -93,17 +94,17 @@ watch(
|
||||
<span class="select-none">
|
||||
{{ transformI18n(route.meta.title) }}
|
||||
</span>
|
||||
<extraIcon :extraIcon="route.meta.extraIcon" />
|
||||
<LaySidebarExtraIcon :extraIcon="route.meta.extraIcon" />
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<div class="horizontal-header-right">
|
||||
<!-- 菜单搜索 -->
|
||||
<Search id="header-search" />
|
||||
<LaySearch id="header-search" />
|
||||
<!-- 国际化 -->
|
||||
<el-dropdown id="header-translation" trigger="click">
|
||||
<globalization
|
||||
<GlobalizationIcon
|
||||
class="navbar-bg-hover w-[40px] h-[48px] p-[11px] cursor-pointer outline-none"
|
||||
/>
|
||||
<template #dropdown>
|
||||
@@ -132,9 +133,9 @@ watch(
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<!-- 全屏 -->
|
||||
<FullScreen id="full-screen" />
|
||||
<LaySidebarFullScreen id="full-screen" />
|
||||
<!-- 消息通知 -->
|
||||
<Notice id="header-notice" />
|
||||
<LayNotice id="header-notice" />
|
||||
<!-- 退出登录 -->
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link navbar-bg-hover select-none">
|
||||
@@ -1,16 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import Logo from "./logo.vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { emitter } from "@/utils/mitt";
|
||||
import SidebarItem from "./sidebarItem.vue";
|
||||
import LeftCollapse from "./leftCollapse.vue";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import CenterCollapse from "./centerCollapse.vue";
|
||||
import { responsiveStorageNameSpace } from "@/config";
|
||||
import { storageLocal, isAllEmpty } from "@pureadmin/utils";
|
||||
import { findRouteByPath, getParentPaths } from "@/router/utils";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import LaySidebarLogo from "../lay-sidebar/components/SidebarLogo.vue";
|
||||
import LaySidebarItem from "../lay-sidebar/components/SidebarItem.vue";
|
||||
import LaySidebarLeftCollapse from "../lay-sidebar/components/SidebarLeftCollapse.vue";
|
||||
import LaySidebarCenterCollapse from "../lay-sidebar/components/SidebarCenterCollapse.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const isShow = ref(false);
|
||||
@@ -93,7 +93,7 @@ onBeforeUnmount(() => {
|
||||
@mouseenter.prevent="isShow = true"
|
||||
@mouseleave.prevent="isShow = false"
|
||||
>
|
||||
<Logo v-if="showLogo" :collapse="isCollapse" />
|
||||
<LaySidebarLogo v-if="showLogo" :collapse="isCollapse" />
|
||||
<el-scrollbar
|
||||
wrap-class="scrollbar-wrapper"
|
||||
:class="[device === 'mobile' ? 'mobile' : 'pc']"
|
||||
@@ -109,7 +109,7 @@ onBeforeUnmount(() => {
|
||||
:popper-effect="tooltipEffect"
|
||||
:default-active="defaultActive"
|
||||
>
|
||||
<sidebar-item
|
||||
<LaySidebarItem
|
||||
v-for="routes in menuData"
|
||||
:key="routes.path"
|
||||
:item="routes"
|
||||
@@ -118,12 +118,12 @@ onBeforeUnmount(() => {
|
||||
/>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
<CenterCollapse
|
||||
<LaySidebarCenterCollapse
|
||||
v-if="device !== 'mobile' && (isShow || isCollapse)"
|
||||
:is-active="pureApp.sidebar.opened"
|
||||
@toggleClick="toggleSideBar"
|
||||
/>
|
||||
<LeftCollapse
|
||||
<LaySidebarLeftCollapse
|
||||
v-if="device !== 'mobile'"
|
||||
:is-active="pureApp.sidebar.opened"
|
||||
@toggleClick="toggleSideBar"
|
||||
@@ -36,7 +36,7 @@ const toggleClick = () => {
|
||||
<template>
|
||||
<div
|
||||
v-tippy="{
|
||||
content: props.isActive
|
||||
content: isActive
|
||||
? t('buttons.pureClickCollapse')
|
||||
: t('buttons.pureClickExpand'),
|
||||
theme: tooltipEffect,
|
||||
@@ -49,7 +49,7 @@ const toggleClick = () => {
|
||||
<IconifyIconOffline
|
||||
:icon="ArrowLeft"
|
||||
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
|
||||
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
|
||||
:style="{ transform: isActive ? 'none' : 'rotateY(180deg)' }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -11,9 +11,9 @@ const props = defineProps({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.extraIcon" class="flex justify-center items-center">
|
||||
<div v-if="extraIcon" class="flex justify-center items-center">
|
||||
<component
|
||||
:is="useRenderIcon(toRaw(props.extraIcon))"
|
||||
:is="useRenderIcon(toRaw(extraIcon))"
|
||||
class="w-[30px] h-[30px]"
|
||||
/>
|
||||
</div>
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import path from "path";
|
||||
import { getConfig } from "@/config";
|
||||
import LinkItem from "./linkItem.vue";
|
||||
import { menuType } from "../../types";
|
||||
import extraIcon from "./extraIcon.vue";
|
||||
import { menuType } from "@/layout/types";
|
||||
import { ReText } from "@/components/ReText";
|
||||
import { useNav } from "@/layout/hooks/useNav";
|
||||
import { transformI18n } from "@/plugins/i18n";
|
||||
import SidebarLinkItem from "./SidebarLinkItem.vue";
|
||||
import SidebarExtraIcon from "./SidebarExtraIcon.vue";
|
||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||
import {
|
||||
type PropType,
|
||||
@@ -106,9 +106,9 @@ function resolvePath(routePath) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<link-item
|
||||
<SidebarLinkItem
|
||||
v-if="
|
||||
hasOneShowingChild(props.item.children, props.item) &&
|
||||
hasOneShowingChild(item.children, item) &&
|
||||
(!onlyOneChild.children || onlyOneChild.noShowingChildren)
|
||||
"
|
||||
:to="item"
|
||||
@@ -120,7 +120,7 @@ function resolvePath(routePath) {
|
||||
v-bind="attrs"
|
||||
>
|
||||
<div
|
||||
v-if="toRaw(props.item.meta.icon)"
|
||||
v-if="toRaw(item.meta.icon)"
|
||||
class="sub-menu-icon"
|
||||
:style="getSubMenuIconStyle"
|
||||
>
|
||||
@@ -128,21 +128,21 @@ function resolvePath(routePath) {
|
||||
:is="
|
||||
useRenderIcon(
|
||||
toRaw(onlyOneChild.meta.icon) ||
|
||||
(props.item.meta && toRaw(props.item.meta.icon))
|
||||
(item.meta && toRaw(item.meta.icon))
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<el-text
|
||||
v-if="
|
||||
(!props.item?.meta.icon &&
|
||||
(!item?.meta.icon &&
|
||||
isCollapse &&
|
||||
layout === 'vertical' &&
|
||||
props.item?.pathList?.length === 1) ||
|
||||
item?.pathList?.length === 1) ||
|
||||
(!onlyOneChild.meta.icon &&
|
||||
isCollapse &&
|
||||
layout === 'mix' &&
|
||||
props.item?.pathList?.length === 2)
|
||||
item?.pathList?.length === 2)
|
||||
"
|
||||
truncated
|
||||
class="!w-full !px-4 !text-inherit"
|
||||
@@ -161,37 +161,35 @@ function resolvePath(routePath) {
|
||||
>
|
||||
{{ transformI18n(onlyOneChild.meta.title) }}
|
||||
</ReText>
|
||||
<extraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
|
||||
<SidebarExtraIcon :extraIcon="onlyOneChild.meta.extraIcon" />
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
</link-item>
|
||||
</SidebarLinkItem>
|
||||
<el-sub-menu
|
||||
v-else
|
||||
ref="subMenu"
|
||||
teleported
|
||||
:index="resolvePath(props.item.path)"
|
||||
:index="resolvePath(item.path)"
|
||||
v-bind="expandCloseIcon"
|
||||
>
|
||||
<template #title>
|
||||
<div
|
||||
v-if="toRaw(props.item.meta.icon)"
|
||||
v-if="toRaw(item.meta.icon)"
|
||||
:style="getSubMenuIconStyle"
|
||||
class="sub-menu-icon"
|
||||
>
|
||||
<component
|
||||
:is="useRenderIcon(props.item.meta && toRaw(props.item.meta.icon))"
|
||||
/>
|
||||
<component :is="useRenderIcon(item.meta && toRaw(item.meta.icon))" />
|
||||
</div>
|
||||
<ReText
|
||||
v-if="
|
||||
layout === 'mix' && toRaw(props.item.meta.icon)
|
||||
? !isCollapse || props.item?.pathList?.length !== 2
|
||||
layout === 'mix' && toRaw(item.meta.icon)
|
||||
? !isCollapse || item?.pathList?.length !== 2
|
||||
: !(
|
||||
layout === 'vertical' &&
|
||||
isCollapse &&
|
||||
toRaw(props.item.meta.icon) &&
|
||||
props.item.parentId === null
|
||||
toRaw(item.meta.icon) &&
|
||||
item.parentId === null
|
||||
)
|
||||
"
|
||||
:tippyProps="{
|
||||
@@ -204,17 +202,17 @@ function resolvePath(routePath) {
|
||||
'!px-4':
|
||||
layout !== 'horizontal' &&
|
||||
isCollapse &&
|
||||
!toRaw(props.item.meta.icon) &&
|
||||
props.item.parentId === null
|
||||
!toRaw(item.meta.icon) &&
|
||||
item.parentId === null
|
||||
}"
|
||||
>
|
||||
{{ transformI18n(props.item.meta.title) }}
|
||||
{{ transformI18n(item.meta.title) }}
|
||||
</ReText>
|
||||
<extraIcon v-if="!isCollapse" :extraIcon="props.item.meta.extraIcon" />
|
||||
<SidebarExtraIcon v-if="!isCollapse" :extraIcon="item.meta.extraIcon" />
|
||||
</template>
|
||||
|
||||
<sidebar-item
|
||||
v-for="child in props.item.children"
|
||||
v-for="child in item.children"
|
||||
:key="child.path"
|
||||
:is-nest="true"
|
||||
:item="child"
|
||||
@@ -46,7 +46,7 @@ const toggleClick = () => {
|
||||
<div class="left-collapse">
|
||||
<IconifyIconOffline
|
||||
v-tippy="{
|
||||
content: props.isActive
|
||||
content: isActive
|
||||
? t('buttons.pureClickCollapse')
|
||||
: t('buttons.pureClickExpand'),
|
||||
theme: tooltipEffect,
|
||||
@@ -55,7 +55,7 @@ const toggleClick = () => {
|
||||
}"
|
||||
:icon="MenuFold"
|
||||
:class="[iconClass, themeColor === 'light' ? '' : 'text-primary']"
|
||||
:style="{ transform: props.isActive ? 'none' : 'rotateY(180deg)' }"
|
||||
:style="{ transform: isActive ? 'none' : 'rotateY(180deg)' }"
|
||||
@click="toggleClick"
|
||||
/>
|
||||
</div>
|
||||
@@ -3,10 +3,6 @@ import { computed } from "vue";
|
||||
import { isUrl } from "@pureadmin/utils";
|
||||
import { menuType } from "@/layout/types";
|
||||
|
||||
defineOptions({
|
||||
name: "LinkItem"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
to: menuType;
|
||||
}>();
|
||||
@@ -10,11 +10,11 @@ const { title, getLogo } = useNav();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="sidebar-logo-container" :class="{ collapses: props.collapse }">
|
||||
<div class="sidebar-logo-container" :class="{ collapses: collapse }">
|
||||
<transition name="sidebarLogoFade">
|
||||
<router-link
|
||||
v-if="props.collapse"
|
||||
key="props.collapse"
|
||||
v-if="collapse"
|
||||
key="collapse"
|
||||
:title="title"
|
||||
class="sidebar-logo-link"
|
||||
:to="getTopMenu()?.path ?? '/'"
|
||||
@@ -26,14 +26,12 @@ const toggleClick = () => {
|
||||
<div
|
||||
class="px-3 mr-1 navbar-bg-hover"
|
||||
:title="
|
||||
props.isActive
|
||||
? t('buttons.pureClickCollapse')
|
||||
: t('buttons.pureClickExpand')
|
||||
isActive ? t('buttons.pureClickCollapse') : t('buttons.pureClickExpand')
|
||||
"
|
||||
@click="toggleClick"
|
||||
>
|
||||
<IconifyIconOffline
|
||||
:icon="props.isActive ? MenuFold : MenuUnfold"
|
||||
:icon="isActive ? MenuFold : MenuUnfold"
|
||||
class="inline-block align-middle hover:text-primary dark:hover:!text-white"
|
||||
/>
|
||||
</div>
|
||||
@@ -1,146 +0,0 @@
|
||||
export interface ListItem {
|
||||
avatar: string;
|
||||
title: string;
|
||||
datetime: string;
|
||||
type: string;
|
||||
description: string;
|
||||
status?: "primary" | "success" | "warning" | "info" | "danger";
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
export interface TabItem {
|
||||
key: string;
|
||||
name: string;
|
||||
list: ListItem[];
|
||||
}
|
||||
|
||||
export const noticesData: TabItem[] = [
|
||||
{
|
||||
key: "1",
|
||||
name: "通知",
|
||||
list: [
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png",
|
||||
title: "你收到了 12 份新周报",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png",
|
||||
title: "你推荐的 前端高手 已通过第三轮面试",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png",
|
||||
title: "这种模板可以区分多种通知类型",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
|
||||
title:
|
||||
"展示标题内容超过一行后的处理方式,如果内容超过1行将自动截断并支持tooltip显示完整标题。",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
|
||||
title: "左侧图标用于区分不同的类型",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png",
|
||||
title: "左侧图标用于区分不同的类型",
|
||||
datetime: "一年前",
|
||||
description: "",
|
||||
type: "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
name: "消息",
|
||||
list: [
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
|
||||
title: "李白 评论了你",
|
||||
description: "长风破浪会有时,直挂云帆济沧海",
|
||||
datetime: "一年前",
|
||||
type: "2"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
|
||||
title: "李白 回复了你",
|
||||
description: "行路难,行路难,多歧路,今安在。",
|
||||
datetime: "一年前",
|
||||
type: "2"
|
||||
},
|
||||
{
|
||||
avatar:
|
||||
"https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg",
|
||||
title: "标题",
|
||||
description:
|
||||
"请将鼠标移动到此处,以便测试超长的消息在此处将如何处理。本例中设置的描述最大行数为2,超过2行的描述内容将被省略并且可以通过tooltip查看完整内容",
|
||||
datetime: "一年前",
|
||||
type: "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
name: "待办",
|
||||
list: [
|
||||
{
|
||||
avatar: "",
|
||||
title: "任务名称",
|
||||
description: "任务需要在 2022-11-16 20:00 前启动",
|
||||
datetime: "",
|
||||
extra: "未开始",
|
||||
status: "info",
|
||||
type: "3"
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "第三方紧急代码变更",
|
||||
description:
|
||||
"一拳提交于 2022-11-16,需在 2022-11-18 前完成代码变更任务",
|
||||
datetime: "",
|
||||
extra: "马上到期",
|
||||
status: "danger",
|
||||
type: "3"
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "信息安全考试",
|
||||
description: "指派小仙于 2022-12-12 前完成更新并发布",
|
||||
datetime: "",
|
||||
extra: "已耗时 8 天",
|
||||
status: "warning",
|
||||
type: "3"
|
||||
},
|
||||
{
|
||||
avatar: "",
|
||||
title: "vue-pure-admin 版本发布",
|
||||
description: "vue-pure-admin 版本发布",
|
||||
datetime: "",
|
||||
extra: "进行中",
|
||||
type: "3"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -1,26 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from "vue";
|
||||
import { ListItem } from "./data";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import NoticeItem from "./noticeItem.vue";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array as PropType<Array<ListItem>>,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.list.length">
|
||||
<NoticeItem
|
||||
v-for="(item, index) in props.list"
|
||||
:key="index"
|
||||
:noticeItem="item"
|
||||
/>
|
||||
</div>
|
||||
<el-empty v-else :description="t('status.pureNoMessage')" />
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
import SearchModal from "./SearchModal.vue";
|
||||
|
||||
export { SearchModal };
|
||||
@@ -4,7 +4,7 @@ import { useRoute } from "vue-router";
|
||||
import { ref, unref, watch, onMounted, nextTick } from "vue";
|
||||
|
||||
defineOptions({
|
||||
name: "FrameView"
|
||||
name: "LayFrame"
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -24,13 +24,13 @@ import {
|
||||
useResizeObserver
|
||||
} from "@pureadmin/utils";
|
||||
|
||||
import navbar from "./components/navbar.vue";
|
||||
import tag from "./components/tag/index.vue";
|
||||
import appMain from "./components/appMain.vue";
|
||||
import setting from "./components/setting/index.vue";
|
||||
import Vertical from "./components/sidebar/vertical.vue";
|
||||
import Horizontal from "./components/sidebar/horizontal.vue";
|
||||
import backTop from "@/assets/svg/back_top.svg?component";
|
||||
import LayTag from "./components/lay-tag/index.vue";
|
||||
import LayNavbar from "./components/lay-navbar/index.vue";
|
||||
import LayContent from "./components/lay-content/index.vue";
|
||||
import LaySetting from "./components/lay-setting/index.vue";
|
||||
import NavVertical from "./components/lay-sidebar/NavVertical.vue";
|
||||
import NavHorizontal from "./components/lay-sidebar/NavHorizontal.vue";
|
||||
import BackTopIcon from "@/assets/svg/back_top.svg?component";
|
||||
|
||||
const { t } = useI18n();
|
||||
const appWrapperRef = ref();
|
||||
@@ -126,7 +126,8 @@ onBeforeMount(() => {
|
||||
useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
|
||||
});
|
||||
|
||||
const layoutHeader = defineComponent({
|
||||
const LayHeader = defineComponent({
|
||||
name: "LayHeader",
|
||||
render() {
|
||||
return h(
|
||||
"div",
|
||||
@@ -144,12 +145,12 @@ const layoutHeader = defineComponent({
|
||||
default: () => [
|
||||
!pureSetting.hiddenSideBar &&
|
||||
(layout.value.includes("vertical") || layout.value.includes("mix"))
|
||||
? h(navbar)
|
||||
? h(LayNavbar)
|
||||
: null,
|
||||
!pureSetting.hiddenSideBar && layout.value.includes("horizontal")
|
||||
? h(Horizontal)
|
||||
? h(NavHorizontal)
|
||||
: null,
|
||||
h(tag)
|
||||
h(LayTag)
|
||||
]
|
||||
}
|
||||
);
|
||||
@@ -168,7 +169,7 @@ const layoutHeader = defineComponent({
|
||||
class="app-mask"
|
||||
@click="useAppStoreHook().toggleSideBar()"
|
||||
/>
|
||||
<Vertical
|
||||
<NavVertical
|
||||
v-show="
|
||||
!pureSetting.hiddenSideBar &&
|
||||
(layout.includes('vertical') || layout.includes('mix'))
|
||||
@@ -181,24 +182,24 @@ const layoutHeader = defineComponent({
|
||||
]"
|
||||
>
|
||||
<div v-if="set.fixedHeader">
|
||||
<layout-header />
|
||||
<LayHeader />
|
||||
<!-- 主体内容 -->
|
||||
<app-main :fixed-header="set.fixedHeader" />
|
||||
<LayContent :fixed-header="set.fixedHeader" />
|
||||
</div>
|
||||
<el-scrollbar v-else>
|
||||
<el-backtop
|
||||
:title="t('buttons.pureBackTop')"
|
||||
target=".main-container .el-scrollbar__wrap"
|
||||
>
|
||||
<backTop />
|
||||
<BackTopIcon />
|
||||
</el-backtop>
|
||||
<layout-header />
|
||||
<LayHeader />
|
||||
<!-- 主体内容 -->
|
||||
<app-main :fixed-header="set.fixedHeader" />
|
||||
<LayContent :fixed-header="set.fixedHeader" />
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
<!-- 系统设置 -->
|
||||
<setting />
|
||||
<LaySetting />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -20,11 +20,12 @@ import {
|
||||
// 可选组件
|
||||
Icon,
|
||||
Column,
|
||||
Grid,
|
||||
Pager,
|
||||
Select,
|
||||
// Colgroup,
|
||||
// Grid,
|
||||
// Tooltip,
|
||||
// Toolbar,
|
||||
// Pager,
|
||||
// Form,
|
||||
// FormItem,
|
||||
// FormGather,
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
// RadioButton,
|
||||
// Switch,
|
||||
// Input,
|
||||
// Select,
|
||||
// Optgroup,
|
||||
// Option,
|
||||
// Textarea,
|
||||
@@ -76,11 +76,12 @@ export function useVxeTable(app: App) {
|
||||
// 可选组件
|
||||
.use(Icon)
|
||||
.use(Column)
|
||||
.use(Grid)
|
||||
.use(Pager)
|
||||
.use(Select)
|
||||
// .use(Colgroup)
|
||||
// .use(Grid)
|
||||
// .use(Tooltip)
|
||||
// .use(Toolbar)
|
||||
// .use(Pager)
|
||||
// .use(Form)
|
||||
// .use(FormItem)
|
||||
// .use(FormGather)
|
||||
@@ -91,7 +92,6 @@ export function useVxeTable(app: App) {
|
||||
// .use(RadioButton)
|
||||
// .use(Switch)
|
||||
// .use(Input)
|
||||
// .use(Select)
|
||||
// .use(Optgroup)
|
||||
// .use(Option)
|
||||
// .use(Textarea)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { board } from "@/router/enums";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
const IFrame = () => import("@/layout/frame.vue");
|
||||
|
||||
export default {
|
||||
path: "/board",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { formdesign } from "@/router/enums";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
const IFrame = () => import("@/layout/frame.vue");
|
||||
|
||||
export default {
|
||||
path: "/form-design",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { mind } from "@/router/enums";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
const IFrame = () => import("@/layout/frame.vue");
|
||||
|
||||
export default {
|
||||
path: "/mind-map",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ppt } from "@/router/enums";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
const IFrame = () => import("@/layout/frame.vue");
|
||||
|
||||
export default {
|
||||
path: "/ppt",
|
||||
|
||||
@@ -22,7 +22,7 @@ import { userKey, type DataInfo } from "@/utils/auth";
|
||||
import { type menuType, routerArrays } from "@/layout/types";
|
||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
|
||||
import { usePermissionStoreHook } from "@/store/modules/permission";
|
||||
const IFrame = () => import("@/layout/frameView.vue");
|
||||
const IFrame = () => import("@/layout/frame.vue");
|
||||
// https://cn.vitejs.dev/guide/features.html#glob-import
|
||||
const modulesRoutes = import.meta.glob("/src/views/**/*.{vue,tsx}");
|
||||
|
||||
|
||||
@@ -82,10 +82,7 @@ export function useColumns() {
|
||||
className: "pure-version",
|
||||
cellRenderer: () => {
|
||||
return (
|
||||
<a
|
||||
href="https://yiming_chang.gitee.io/pure-admin-doc"
|
||||
target="_blank"
|
||||
>
|
||||
<a href="https://pure-admin.github.io/pure-admin-doc" target="_blank">
|
||||
<span style="color: var(--el-color-primary)">文档链接</span>
|
||||
</a>
|
||||
);
|
||||
@@ -97,10 +94,7 @@ export function useColumns() {
|
||||
className: "pure-version",
|
||||
cellRenderer: () => {
|
||||
return (
|
||||
<a
|
||||
href="https://yiming_chang.gitee.io/vue-pure-admin"
|
||||
target="_blank"
|
||||
>
|
||||
<a href="https://pure-admin.github.io/vue-pure-admin" target="_blank">
|
||||
<span style="color: var(--el-color-primary)">预览链接</span>
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -8,8 +8,8 @@ import Preferences from "./components/Preferences.vue";
|
||||
import SecurityLog from "./components/SecurityLog.vue";
|
||||
import { useGlobal, deviceDetection } from "@pureadmin/utils";
|
||||
import AccountManagement from "./components/AccountManagement.vue";
|
||||
import TopCollapse from "@/layout/components/sidebar/topCollapse.vue";
|
||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||
import LaySidebarTopCollapse from "@/layout/components/lay-sidebar/components/SidebarTopCollapse.vue";
|
||||
|
||||
import leftLine from "@iconify-icons/ri/arrow-left-s-line";
|
||||
import ProfileIcon from "@iconify-icons/ri/user-3-line";
|
||||
@@ -115,7 +115,7 @@ getMine().then(res => {
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<TopCollapse
|
||||
<LaySidebarTopCollapse
|
||||
v-if="deviceDetection()"
|
||||
class="px-0"
|
||||
:is-active="isOpen"
|
||||
|
||||
@@ -23,7 +23,7 @@ const { isDisabled, text } = useVerifyCode();
|
||||
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(valid => {
|
||||
if (valid) {
|
||||
// 模拟登录请求,需根据实际开发进行修改
|
||||
setTimeout(() => {
|
||||
@@ -34,7 +34,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
}, 2000);
|
||||
} else {
|
||||
loading.value = false;
|
||||
return fields;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ const repeatPasswordRule = [
|
||||
const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(valid => {
|
||||
if (valid) {
|
||||
if (checked.value) {
|
||||
// 模拟请求,需根据实际开发进行修改
|
||||
@@ -63,7 +63,6 @@ const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
} else {
|
||||
loading.value = false;
|
||||
return fields;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ const repeatPasswordRule = [
|
||||
const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
loading.value = true;
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(valid => {
|
||||
if (valid) {
|
||||
// 模拟请求,需根据实际开发进行修改
|
||||
setTimeout(() => {
|
||||
@@ -53,7 +53,6 @@ const onUpdate = async (formEl: FormInstance | undefined) => {
|
||||
}, 2000);
|
||||
} else {
|
||||
loading.value = false;
|
||||
return fields;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ const ruleForm = reactive({
|
||||
|
||||
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(valid => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
useUserStoreHook()
|
||||
@@ -86,8 +86,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
} else {
|
||||
return fields;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -82,12 +82,7 @@ const dataList = ref([
|
||||
<template>
|
||||
<div>
|
||||
<el-scrollbar>
|
||||
<PureDescriptions
|
||||
border
|
||||
:data="props.data"
|
||||
:columns="columns"
|
||||
:column="5"
|
||||
/>
|
||||
<PureDescriptions border :data="data" :columns="columns" :column="5" />
|
||||
</el-scrollbar>
|
||||
<el-tabs :modelValue="'responseBody'" type="border-card" class="mt-4">
|
||||
<el-tab-pane
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
| `name` | 路由名称(必须唯一并且和当前路由`component`字段对应的页面里用`defineOptions`包起来的`name`保持一致) |
|
||||
| `path` | 路由路径 |
|
||||
| `component` | 组件路径(传`component`组件路径,那么`path`可以随便写,如果不传,`component`组件路径会跟`path`保持一致) |
|
||||
| `rank` | 菜单排序(平台规定只有`home`路由的`rank`才能为`0`,所以后端在返回`rank`的时候需要从非`0`开始 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E8%8F%9C%E5%8D%95%E6%8E%92%E5%BA%8F-rank)) |
|
||||
| `rank` | 菜单排序(平台规定只有`home`路由的`rank`才能为`0`,所以后端在返回`rank`的时候需要从非`0`开始 [点击查看更多](https://pure-admin.github.io/pure-admin-doc/pages/routerMenu/#%E8%8F%9C%E5%8D%95%E6%8E%92%E5%BA%8F-rank)) |
|
||||
| `redirect` | 路由重定向 |
|
||||
| `icon` | 菜单图标 |
|
||||
| `extraIcon` | 右侧图标 |
|
||||
@@ -22,5 +22,5 @@
|
||||
| `hiddenTag` | 标签页(当前菜单名称或自定义信息禁止添加到标签页) |
|
||||
| `fixedTag` | 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭) |
|
||||
| `showLink` | 菜单(是否显示该菜单) |
|
||||
| `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) |
|
||||
| `showParent` | 父级菜单(是否显示父级菜单 [点击查看更多](https://pure-admin.github.io/pure-admin-doc/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) |
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Tree {
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
defineProps({
|
||||
treeLoading: Boolean,
|
||||
treeData: Array
|
||||
});
|
||||
@@ -95,7 +95,7 @@ defineExpose({ onTreeReset });
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-loading="props.treeLoading"
|
||||
v-loading="treeLoading"
|
||||
class="h-full bg-bg_color overflow-auto"
|
||||
:style="{ minHeight: `calc(100vh - 141px)` }"
|
||||
>
|
||||
@@ -153,7 +153,7 @@ defineExpose({ onTreeReset });
|
||||
<el-divider />
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
:data="props.treeData"
|
||||
:data="treeData"
|
||||
node-key="id"
|
||||
size="small"
|
||||
:props="defaultProps"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { tableDataMore } from "./data";
|
||||
|
||||
const props = withDefaults(
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
height?: string;
|
||||
}>(),
|
||||
@@ -62,12 +62,12 @@ function handleClick(row) {
|
||||
<template>
|
||||
<pure-table
|
||||
:data="
|
||||
props.height
|
||||
height
|
||||
? tableDataMore.concat(tableDataMore).concat(tableDataMore)
|
||||
: tableDataMore
|
||||
"
|
||||
:columns="columns"
|
||||
:height="props.height"
|
||||
:height="height"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<el-button link type="primary" size="small" @click="handleClick(row)">
|
||||
|
||||
@@ -30,7 +30,7 @@ const columns: TableColumnList = [
|
||||
<template #video>
|
||||
<video width="180" height="180" controls>
|
||||
<source
|
||||
src="https://yiming_chang.gitee.io/pure-admin-doc/video/url.mov"
|
||||
src="https://pure-admin.github.io/pure-admin-doc/video/url.mov"
|
||||
type="video/mp4"
|
||||
/>
|
||||
</video>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import List from "./list.vue";
|
||||
import TreeList from "./treeList.vue";
|
||||
import PageList from "./pageList.vue";
|
||||
|
||||
const rendContent = (val: string) =>
|
||||
`代码位置:src/views/table/virtual/${val}.vue`;
|
||||
@@ -8,13 +9,19 @@ export const list = [
|
||||
{
|
||||
key: "list",
|
||||
content: rendContent("list"),
|
||||
title: "虚拟列表",
|
||||
title: "虚拟表格",
|
||||
component: List
|
||||
},
|
||||
{
|
||||
key: "treeList",
|
||||
content: rendContent("treeList"),
|
||||
title: "虚拟树",
|
||||
title: "虚拟树形表格",
|
||||
component: TreeList
|
||||
},
|
||||
{
|
||||
key: "pageList",
|
||||
content: rendContent("pageList"),
|
||||
title: "分页表格",
|
||||
component: PageList
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { VxeTableBar } from "@/components/ReVxeTableBar";
|
||||
|
||||
const vxeTableRef = ref();
|
||||
const loading = ref(true);
|
||||
const tableData = ref([]);
|
||||
|
||||
setTimeout(() => {
|
||||
const columns = [
|
||||
{ type: "seq", field: "seq", title: "序号", width: 100 },
|
||||
{ field: "name", title: "名称", sortable: true },
|
||||
{ field: "role", title: "角色" },
|
||||
{ field: "sex", title: "性别" }
|
||||
];
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
// 模拟数据
|
||||
const mockList = [];
|
||||
for (let index = 0; index < 500; index++) {
|
||||
@@ -15,21 +26,33 @@ setTimeout(() => {
|
||||
});
|
||||
}
|
||||
tableData.value = mockList;
|
||||
}, 100);
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
onSearch();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<vxe-table
|
||||
border
|
||||
show-overflow
|
||||
height="500"
|
||||
:row-config="{ isHover: true }"
|
||||
:data="tableData"
|
||||
:scroll-y="{ enabled: true }"
|
||||
<VxeTableBar
|
||||
:vxeTableRef="vxeTableRef"
|
||||
:columns="columns"
|
||||
title="虚拟表格"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<vxe-column type="seq" title="序号" width="100" />
|
||||
<vxe-column field="name" title="名称" sortable />
|
||||
<vxe-column field="role" title="角色" />
|
||||
<vxe-column field="sex" title="性别" />
|
||||
</vxe-table>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<vxe-grid
|
||||
ref="vxeTableRef"
|
||||
v-loading="loading"
|
||||
show-overflow
|
||||
height="500"
|
||||
:size="size"
|
||||
:row-config="{ isHover: true }"
|
||||
:scroll-y="{ enabled: true }"
|
||||
:columns="dynamicColumns"
|
||||
:data="tableData"
|
||||
/>
|
||||
</template>
|
||||
</VxeTableBar>
|
||||
</template>
|
||||
|
||||
86
src/views/table/virtual/pageList.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from "vue";
|
||||
import { VxeTableBar } from "@/components/ReVxeTableBar";
|
||||
|
||||
const vxeTableRef = ref();
|
||||
const loading = ref(true);
|
||||
const tableData = ref([]);
|
||||
|
||||
const getTableHeight = computed(() => {
|
||||
return (size: string) => {
|
||||
switch (size) {
|
||||
case "medium":
|
||||
return 531;
|
||||
case "small":
|
||||
return 482;
|
||||
case "mini":
|
||||
return 433;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const pagerConfig = reactive({
|
||||
total: 0,
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
pageSizes: [5, 10, 15, 20]
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ type: "seq", field: "seq", title: "序号", width: 100 },
|
||||
{ field: "name", title: "名称", sortable: true },
|
||||
{ field: "role", title: "角色" },
|
||||
{ field: "sex", title: "性别" }
|
||||
];
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
// 模拟数据
|
||||
const mockList = [];
|
||||
for (let index = 0; index < 10; index++) {
|
||||
mockList.push({
|
||||
id: index,
|
||||
name: "Test" + index,
|
||||
role: "Developer",
|
||||
sex: "男"
|
||||
});
|
||||
}
|
||||
pagerConfig.total = 20;
|
||||
tableData.value = mockList;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const handlePageChange = ({ currentPage, pageSize }) => {
|
||||
pagerConfig.currentPage = currentPage;
|
||||
pagerConfig.pageSize = pageSize;
|
||||
onSearch();
|
||||
};
|
||||
|
||||
onSearch();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VxeTableBar
|
||||
:vxeTableRef="vxeTableRef"
|
||||
:columns="columns"
|
||||
title="分页表格"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<vxe-grid
|
||||
ref="vxeTableRef"
|
||||
v-loading="loading"
|
||||
show-overflow
|
||||
:height="getTableHeight(size)"
|
||||
:size="size"
|
||||
:column-config="{ resizable: true }"
|
||||
:columns="dynamicColumns"
|
||||
:pagerConfig="pagerConfig"
|
||||
:data="tableData"
|
||||
@page-change="handlePageChange"
|
||||
/>
|
||||
</template>
|
||||
</VxeTableBar>
|
||||
</template>
|
||||
@@ -1,56 +1,51 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import treeList from "./tree.json";
|
||||
import { VxeTableBar } from "@/components/ReVxeTableBar";
|
||||
|
||||
const tableRef = ref();
|
||||
const vxeTableRef = ref();
|
||||
|
||||
const loading = ref(false);
|
||||
const loading = ref(true);
|
||||
const tableData = ref([]);
|
||||
|
||||
const loadList = () => {
|
||||
const columns = [
|
||||
{ type: "seq", field: "seq", title: "序号", width: 200, treeNode: true },
|
||||
{ field: "id", title: "Id" },
|
||||
{ field: "name", title: "地点" }
|
||||
];
|
||||
|
||||
async function onSearch() {
|
||||
loading.value = true;
|
||||
tableData.value = treeList;
|
||||
setTimeout(() => {
|
||||
tableData.value = treeList;
|
||||
loading.value = false;
|
||||
}, 200);
|
||||
};
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const expandAllEvent = () => {
|
||||
const $table = tableRef.value;
|
||||
if ($table) {
|
||||
$table.setAllTreeExpand(true);
|
||||
}
|
||||
};
|
||||
|
||||
const claseExpandEvent = () => {
|
||||
const $table = tableRef.value;
|
||||
if ($table) {
|
||||
$table.clearTreeExpand();
|
||||
}
|
||||
};
|
||||
|
||||
loadList();
|
||||
onSearch();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="mb-4">
|
||||
<el-button @click="expandAllEvent">展开所有</el-button>
|
||||
<el-button @click="claseExpandEvent">收起所有</el-button>
|
||||
</div>
|
||||
|
||||
<vxe-table
|
||||
ref="tableRef"
|
||||
show-overflow
|
||||
height="500"
|
||||
:loading="loading"
|
||||
:tree-config="{ transform: true }"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:data="tableData"
|
||||
>
|
||||
<vxe-column type="seq" title="序号" width="200" tree-node />
|
||||
<vxe-column field="id" title="Id" />
|
||||
<vxe-column field="name" title="地点" />
|
||||
</vxe-table>
|
||||
</div>
|
||||
<VxeTableBar
|
||||
tree
|
||||
title="虚拟树形表格"
|
||||
:isExpandAll="false"
|
||||
:vxeTableRef="vxeTableRef"
|
||||
:columns="columns"
|
||||
@refresh="onSearch"
|
||||
>
|
||||
<template v-slot="{ size, dynamicColumns }">
|
||||
<vxe-grid
|
||||
ref="vxeTableRef"
|
||||
v-loading="loading"
|
||||
show-overflow
|
||||
height="500"
|
||||
:size="size"
|
||||
:tree-config="{ transform: true }"
|
||||
:scroll-y="{ enabled: true, gt: 20 }"
|
||||
:columns="dynamicColumns"
|
||||
:data="tableData"
|
||||
/>
|
||||
</template>
|
||||
</VxeTableBar>
|
||||
</template>
|
||||
|
||||