mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-11 00:32:27 +08:00
fix Bug or add docs
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
</AppProvider>
|
||||
</NConfigProvider>
|
||||
|
||||
<transition v-if="isLock && $route.name != 'login'" name="slide-up">
|
||||
<transition v-if="isLock && $route.name !== 'login'" name="slide-up">
|
||||
<LockScreen />
|
||||
</transition>
|
||||
</template>
|
||||
@@ -34,9 +34,7 @@
|
||||
const designStore = useDesignSettingStore();
|
||||
const isLock = computed(() => useLockscreen.isLock);
|
||||
const lockTime = computed(() => useLockscreen.lockTime);
|
||||
/**
|
||||
* @type import('naive-ui').GlobalThemeOverrides
|
||||
*/
|
||||
|
||||
const getThemeOverrides = computed(() => {
|
||||
return {
|
||||
common: {
|
||||
@@ -89,9 +87,7 @@
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import 'styles/global.less';
|
||||
@import 'styles/common.less';
|
||||
@import 'styles/override.less';
|
||||
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
|
||||
@@ -14,7 +14,7 @@ export function adminMenus() {
|
||||
* 获取tree菜单列表
|
||||
* @param params
|
||||
*/
|
||||
export function getMenuList(params) {
|
||||
export function getMenuList(params?) {
|
||||
return http.request({
|
||||
url: '/menu/list',
|
||||
method: 'GET',
|
||||
|
||||
@@ -33,7 +33,7 @@ export function login(params) {
|
||||
params,
|
||||
},
|
||||
{
|
||||
isTransformRequestResult: false,
|
||||
isTransformResponse: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export function changePassword(params, uid) {
|
||||
params,
|
||||
},
|
||||
{
|
||||
isTransformRequestResult: false,
|
||||
isTransformResponse: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,41 @@
|
||||
<template>
|
||||
<n-dialog-provider>
|
||||
<DialogContent />
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<MessageContent />
|
||||
<slot slot="default"></slot>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
<n-loading-bar-provider>
|
||||
<LoadingContent />
|
||||
<n-dialog-provider>
|
||||
<DialogContent />
|
||||
<n-notification-provider>
|
||||
<n-message-provider>
|
||||
<MessageContent />
|
||||
<slot slot="default"></slot>
|
||||
</n-message-provider>
|
||||
</n-notification-provider>
|
||||
</n-dialog-provider>
|
||||
</n-loading-bar-provider>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
NDialogProvider,
|
||||
NNotificationProvider,
|
||||
NMessageProvider,
|
||||
NLoadingBarProvider,
|
||||
} from 'naive-ui';
|
||||
import { LoadingContent } from '@/components/LoadingContent';
|
||||
import { MessageContent } from '@/components/MessageContent';
|
||||
import { DialogContent } from '@/components/DialogContent';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Application',
|
||||
components: { MessageContent, DialogContent },
|
||||
components: {
|
||||
NDialogProvider,
|
||||
NNotificationProvider,
|
||||
NMessageProvider,
|
||||
NLoadingBarProvider,
|
||||
LoadingContent,
|
||||
MessageContent,
|
||||
DialogContent,
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
},
|
||||
|
||||
3
src/components/LoadingContent/index.ts
Normal file
3
src/components/LoadingContent/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import LoadingContent from './index.vue';
|
||||
|
||||
export { LoadingContent };
|
||||
12
src/components/LoadingContent/index.vue
Normal file
12
src/components/LoadingContent/index.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template></template>
|
||||
<script lang="ts">
|
||||
import { useLoadingBar } from 'naive-ui';
|
||||
|
||||
export default {
|
||||
name: 'LoadingContent',
|
||||
setup() {
|
||||
//挂载在 window 方便与在js中使用
|
||||
window['$loading'] = useLoadingBar();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -1,95 +0,0 @@
|
||||
BasicTable 重封装组件说明
|
||||
====
|
||||
|
||||
封装说明
|
||||
----
|
||||
|
||||
> 基础的使用方式与 API 与 [官方版(data-table)](https://www.naiveui.com/zh-CN/os-theme/components/data-table#tree) 本一致,在其基础上,封装了加载数据的方法。
|
||||
>
|
||||
> 你无需在你是用表格的页面进行分页逻辑处理,仅需向 BasicTable 组件传递绑定 `:api="Promise"` 对象即可
|
||||
>
|
||||
> 例子1
|
||||
----
|
||||
(基础使用)
|
||||
|
||||
```vue
|
||||
|
||||
<template>
|
||||
<BasicTable
|
||||
title="表格列表"
|
||||
:columns="columns"
|
||||
:api="loadDataTable"
|
||||
:row-key="row => row.id"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
>
|
||||
<template #toolbar>
|
||||
<n-button type="primary">添加会员</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { BasicTable } from '@/components/Table'
|
||||
import { getTableList } from '@/api/table/list'
|
||||
const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
auth: ['amdin'], // 同时根据权限控制是否显示
|
||||
ifShow: (row) => {
|
||||
return true; // 根据业务控制是否显示
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '日期',
|
||||
key: 'date'
|
||||
},
|
||||
]
|
||||
export default defineComponent({
|
||||
components: { BasicTable },
|
||||
setup() {
|
||||
const loadDataTable = async (params) => {
|
||||
const data = await getTableList(params);
|
||||
return data
|
||||
}
|
||||
return {
|
||||
columns,
|
||||
loadDataTable
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
API
|
||||
----
|
||||
BasicTable 在 NaiveUi 的 data-table 上进行了一层封装,支持了一些预设,并且封装了一些行为。这里只列出与 data-table 不同的 api。
|
||||
|
||||
> request:Promise 参考上面例子写法
|
||||
> ref:可绑定ref 调用组件内部方法(data-table本身的方法和参数)
|
||||
|
||||
Methods
|
||||
----
|
||||
> reload:actionRef.value.reload()
|
||||
|
||||
> 其余方法,请打印查看
|
||||
|
||||
Slots
|
||||
----
|
||||
> 名称:tableTitle | 表格顶部左侧区域
|
||||
> 名称:toolbar | 表格顶部右侧区域
|
||||
|
||||
|
||||
更新时间
|
||||
----
|
||||
|
||||
该文档最后更新于: 2021-07-12 PM 10:13
|
||||
@@ -59,6 +59,7 @@
|
||||
</div>
|
||||
<div class="s-table">
|
||||
<n-data-table
|
||||
ref="tableElRef"
|
||||
v-bind="getBindValues"
|
||||
:pagination="pagination"
|
||||
@update:page="updatePage"
|
||||
@@ -73,7 +74,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { NDataTable } from 'naive-ui';
|
||||
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs } from 'vue';
|
||||
import {
|
||||
ref,
|
||||
defineComponent,
|
||||
reactive,
|
||||
unref,
|
||||
toRaw,
|
||||
computed,
|
||||
toRefs,
|
||||
onMounted,
|
||||
nextTick,
|
||||
} from 'vue';
|
||||
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||
import { createTableContext } from './hooks/useTableContext';
|
||||
|
||||
@@ -88,6 +99,10 @@
|
||||
|
||||
import { BasicTableProps } from './types/table';
|
||||
|
||||
import { getViewportOffset } from '@/utils/domUtils';
|
||||
import { useWindowSizeFn } from '@/hooks/event/useWindowSizeFn';
|
||||
import { isBoolean } from '@/utils/is';
|
||||
|
||||
const densityOptions = [
|
||||
{
|
||||
type: 'menu',
|
||||
@@ -117,9 +132,20 @@
|
||||
...NDataTable.props, // 这里继承原 UI 组件的 props
|
||||
...basicProps,
|
||||
},
|
||||
emits: ['fetch-success', 'fetch-error', 'update:checked-row-keys'],
|
||||
emits: [
|
||||
'fetch-success',
|
||||
'fetch-error',
|
||||
'update:checked-row-keys',
|
||||
'edit-end',
|
||||
'edit-cancel',
|
||||
'edit-row-end',
|
||||
'edit-change',
|
||||
],
|
||||
setup(props, { emit }) {
|
||||
const deviceHeight = ref(150);
|
||||
const tableElRef = ref<ComponentRef>(null);
|
||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||
let paginationEl: HTMLElement | null;
|
||||
|
||||
const tableData = ref<Recordable[]>([]);
|
||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||
@@ -173,20 +199,14 @@
|
||||
emit('update:checked-row-keys', rowKeys);
|
||||
}
|
||||
|
||||
//重置 Columns
|
||||
const resetColumns = () => {
|
||||
columns.map((item) => {
|
||||
item.isShow = true;
|
||||
});
|
||||
};
|
||||
|
||||
//获取表格大小
|
||||
const getTableSize = computed(() => state.tableSize);
|
||||
|
||||
//组装表格信息
|
||||
const getBindValues = computed(() => {
|
||||
const tableData = unref(getDataSourceRef);
|
||||
let propsData = {
|
||||
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
||||
return {
|
||||
...unref(getProps),
|
||||
loading: unref(getLoading),
|
||||
columns: toRaw(unref(getPageColumns)),
|
||||
@@ -194,8 +214,8 @@
|
||||
data: tableData,
|
||||
size: unref(getTableSize),
|
||||
remote: true,
|
||||
'max-height': maxHeight,
|
||||
};
|
||||
return propsData;
|
||||
});
|
||||
|
||||
//获取分页信息
|
||||
@@ -205,7 +225,7 @@
|
||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||
}
|
||||
|
||||
const tableAction: TableActionType = {
|
||||
const tableAction = {
|
||||
reload,
|
||||
setColumns,
|
||||
setLoading,
|
||||
@@ -216,14 +236,54 @@
|
||||
setCacheColumnsField,
|
||||
emit,
|
||||
getSize: () => {
|
||||
return unref(getBindValues).size as SizeType;
|
||||
return unref(getBindValues).size;
|
||||
},
|
||||
};
|
||||
|
||||
const getCanResize = computed(() => {
|
||||
const { canResize } = unref(getProps);
|
||||
return canResize;
|
||||
});
|
||||
|
||||
async function computeTableHeight() {
|
||||
const table = unref(tableElRef);
|
||||
if (!table) return;
|
||||
if (!unref(getCanResize)) return;
|
||||
const tableEl: any = table?.$el;
|
||||
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
||||
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||||
const headerH = 64;
|
||||
let paginationH = 2;
|
||||
let marginH = 24;
|
||||
if (!isBoolean(pagination)) {
|
||||
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
||||
if (paginationEl) {
|
||||
const offsetHeight = paginationEl.offsetHeight;
|
||||
paginationH += offsetHeight || 0;
|
||||
} else {
|
||||
paginationH += 28;
|
||||
}
|
||||
}
|
||||
let height =
|
||||
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
||||
const maxHeight = props.maxHeight;
|
||||
height = maxHeight && maxHeight < height ? maxHeight : height;
|
||||
deviceHeight.value = height;
|
||||
}
|
||||
|
||||
useWindowSizeFn(computeTableHeight, 280);
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
computeTableHeight();
|
||||
});
|
||||
});
|
||||
|
||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
tableElRef,
|
||||
getBindValues,
|
||||
densityOptions,
|
||||
reload,
|
||||
@@ -232,7 +292,6 @@
|
||||
updatePageSize,
|
||||
updateCheckedRowKeys,
|
||||
pagination,
|
||||
resetColumns,
|
||||
tableAction,
|
||||
};
|
||||
},
|
||||
|
||||
41
src/components/Table/src/componentMap.ts
Normal file
41
src/components/Table/src/componentMap.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import type { Component } from 'vue';
|
||||
import {
|
||||
NInput,
|
||||
NSelect,
|
||||
NCheckbox,
|
||||
NInputNumber,
|
||||
NSwitch,
|
||||
NDatePicker,
|
||||
NTimePicker,
|
||||
} from 'naive-ui';
|
||||
import type { ComponentType } from './types/componentType';
|
||||
|
||||
export enum EventEnum {
|
||||
NInput = 'on-input',
|
||||
NInputNumber = 'on-input',
|
||||
NSelect = 'on-update:value',
|
||||
NSwitch = 'on-update:value',
|
||||
NCheckbox = 'on-update:value',
|
||||
NDatePicker = 'on-update:value',
|
||||
NTimePicker = 'on-update:value',
|
||||
}
|
||||
|
||||
const componentMap = new Map<ComponentType, Component>();
|
||||
|
||||
componentMap.set('NInput', NInput);
|
||||
componentMap.set('NInputNumber', NInputNumber);
|
||||
componentMap.set('NSelect', NSelect);
|
||||
componentMap.set('NSwitch', NSwitch);
|
||||
componentMap.set('NCheckbox', NCheckbox);
|
||||
componentMap.set('NDatePicker', NDatePicker);
|
||||
componentMap.set('NTimePicker', NTimePicker);
|
||||
|
||||
export function add(compName: ComponentType, component: Component) {
|
||||
componentMap.set(compName, component);
|
||||
}
|
||||
|
||||
export function del(compName: ComponentType) {
|
||||
componentMap.delete(compName);
|
||||
}
|
||||
|
||||
export { componentMap };
|
||||
@@ -41,6 +41,7 @@
|
||||
actions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
default: null,
|
||||
required: true,
|
||||
},
|
||||
dropDownActions: {
|
||||
type: Array as PropType<ActionItem[]>,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { FunctionalComponent, defineComponent } from 'vue';
|
||||
import type { ComponentType } from '../../types/componentType';
|
||||
import { componentMap } from '@/components/Table/src/componentMap';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { NPopover } from 'naive-ui';
|
||||
|
||||
export interface ComponentProps {
|
||||
component: ComponentType;
|
||||
rule: boolean;
|
||||
popoverVisible: boolean;
|
||||
ruleMessage: string;
|
||||
}
|
||||
|
||||
export const CellComponent: FunctionalComponent = (
|
||||
{ component = 'NInput', rule = true, ruleMessage, popoverVisible }: ComponentProps,
|
||||
{ attrs }
|
||||
) => {
|
||||
const Comp = componentMap.get(component) as typeof defineComponent;
|
||||
|
||||
const DefaultComp = h(Comp, attrs);
|
||||
if (!rule) {
|
||||
return DefaultComp;
|
||||
}
|
||||
return h(
|
||||
NPopover,
|
||||
{ 'display-directive': 'show', show: !!popoverVisible, manual: 'manual' },
|
||||
{
|
||||
trigger: () => DefaultComp,
|
||||
default: () =>
|
||||
h(
|
||||
'span',
|
||||
{
|
||||
style: {
|
||||
color: 'red',
|
||||
width: '90px',
|
||||
display: 'inline-block',
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => ruleMessage,
|
||||
}
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
402
src/components/Table/src/components/editable/EditableCell.vue
Normal file
402
src/components/Table/src/components/editable/EditableCell.vue
Normal file
@@ -0,0 +1,402 @@
|
||||
<template>
|
||||
<div class="editable-cell">
|
||||
<div v-show="!isEdit" class="editable-cell-content" @click="handleEdit">
|
||||
{{ getValues }}
|
||||
<n-icon class="edit-icon" v-if="!column.editRow">
|
||||
<FormOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
|
||||
<CellComponent
|
||||
v-bind="getComponentProps"
|
||||
:component="getComponent"
|
||||
:style="getWrapperStyle"
|
||||
:popoverVisible="getRuleVisible"
|
||||
:ruleMessage="ruleMessage"
|
||||
:rule="getRule"
|
||||
:class="getWrapperClass"
|
||||
ref="elRef"
|
||||
@options-change="handleOptionsChange"
|
||||
@pressEnter="handleEnter"
|
||||
/>
|
||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||
<n-icon class="cursor-pointer mx-2">
|
||||
<CheckOutlined @click="handleSubmit" />
|
||||
</n-icon>
|
||||
<n-icon class="cursor-pointer mx-2">
|
||||
<CloseOutlined @click="handleCancel" />
|
||||
</n-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import type { CSSProperties, PropType } from 'vue';
|
||||
import type { BasicColumn } from '../../types/table';
|
||||
import type { EditRecordRow } from './index';
|
||||
|
||||
import { defineComponent, ref, unref, nextTick, computed, watchEffect, toRaw } from 'vue';
|
||||
import { FormOutlined, CloseOutlined, CheckOutlined } from '@vicons/antd';
|
||||
import { CellComponent } from './CellComponent';
|
||||
|
||||
import { useTableContext } from '../../hooks/useTableContext';
|
||||
|
||||
import clickOutside from '@/directives/clickOutside';
|
||||
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { isString, isBoolean, isFunction, isNumber, isArray } from '@/utils/is';
|
||||
import { createPlaceholderMessage } from './helper';
|
||||
import { set, omit } from 'lodash-es';
|
||||
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditableCell',
|
||||
components: { FormOutlined, CloseOutlined, CheckOutlined, CellComponent },
|
||||
directives: {
|
||||
clickOutside,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number, Boolean, Object] as PropType<string | number | boolean | Recordable>,
|
||||
default: '',
|
||||
},
|
||||
record: {
|
||||
type: Object as PropType<EditRecordRow>,
|
||||
},
|
||||
column: {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: () => ({}),
|
||||
},
|
||||
index: propTypes.number,
|
||||
},
|
||||
setup(props) {
|
||||
const table = useTableContext();
|
||||
const isEdit = ref(false);
|
||||
const elRef = ref();
|
||||
const ruleVisible = ref(false);
|
||||
const ruleMessage = ref('');
|
||||
const optionsRef = ref<LabelValueOptions>([]);
|
||||
const currentValueRef = ref<any>(props.value);
|
||||
const defaultValueRef = ref<any>(props.value);
|
||||
|
||||
// const { prefixCls } = useDesign('editable-cell');
|
||||
|
||||
const getComponent = computed(() => props.column?.editComponent || 'NInput');
|
||||
const getRule = computed(() => props.column?.editRule);
|
||||
|
||||
const getRuleVisible = computed(() => {
|
||||
return unref(ruleMessage) && unref(ruleVisible);
|
||||
});
|
||||
|
||||
const getIsCheckComp = computed(() => {
|
||||
const component = unref(getComponent);
|
||||
return ['NCheckbox', 'NSwitch'].includes(component);
|
||||
});
|
||||
|
||||
const getComponentProps = computed(() => {
|
||||
const compProps = props.column?.editComponentProps ?? {};
|
||||
const editComponent = props.column?.editComponent ?? null;
|
||||
const component = unref(getComponent);
|
||||
const apiSelectProps: Recordable = {};
|
||||
|
||||
const isCheckValue = unref(getIsCheckComp);
|
||||
|
||||
const valueField = isCheckValue ? 'checked' : 'value';
|
||||
const val = unref(currentValueRef);
|
||||
|
||||
let value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||
|
||||
if (component === 'NDatePicker') {
|
||||
value = dayjs(value).valueOf();
|
||||
}
|
||||
|
||||
const onEvent: any = editComponent ? EventEnum[editComponent] : undefined;
|
||||
|
||||
return {
|
||||
placeholder: createPlaceholderMessage(unref(getComponent)),
|
||||
...apiSelectProps,
|
||||
...omit(compProps, 'onChange'),
|
||||
[onEvent]: handleChange,
|
||||
[valueField]: value,
|
||||
};
|
||||
});
|
||||
|
||||
const getValues = computed(() => {
|
||||
const { editComponentProps, editValueMap } = props.column;
|
||||
|
||||
const value = unref(currentValueRef);
|
||||
|
||||
if (editValueMap && isFunction(editValueMap)) {
|
||||
return editValueMap(value);
|
||||
}
|
||||
|
||||
const component = unref(getComponent);
|
||||
if (!component.includes('NSelect')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const options: LabelValueOptions = editComponentProps?.options ?? (unref(optionsRef) || []);
|
||||
const option = options.find((item) => `${item.value}` === `${value}`);
|
||||
|
||||
return option?.label ?? value;
|
||||
});
|
||||
|
||||
const getWrapperStyle = computed((): CSSProperties => {
|
||||
// if (unref(getIsCheckComp) || unref(getRowEditable)) {
|
||||
// return {};
|
||||
// }
|
||||
return {
|
||||
width: 'calc(100% - 48px)',
|
||||
};
|
||||
});
|
||||
|
||||
const getWrapperClass = computed(() => {
|
||||
const { align = 'center' } = props.column;
|
||||
return `edit-cell-align-${align}`;
|
||||
});
|
||||
|
||||
const getRowEditable = computed(() => {
|
||||
const { editable } = props.record || {};
|
||||
return !!editable;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
defaultValueRef.value = props.value;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
const { editable } = props.column;
|
||||
if (isBoolean(editable) || isBoolean(unref(getRowEditable))) {
|
||||
isEdit.value = !!editable || unref(getRowEditable);
|
||||
}
|
||||
});
|
||||
|
||||
function handleEdit() {
|
||||
if (unref(getRowEditable) || unref(props.column?.editRow)) return;
|
||||
ruleMessage.value = '';
|
||||
isEdit.value = true;
|
||||
nextTick(() => {
|
||||
const el = unref(elRef);
|
||||
el?.focus?.();
|
||||
});
|
||||
}
|
||||
|
||||
async function handleChange(e: any) {
|
||||
const component = unref(getComponent);
|
||||
if (!e) {
|
||||
currentValueRef.value = e;
|
||||
} else if (e?.target && Reflect.has(e.target, 'value')) {
|
||||
currentValueRef.value = (e as ChangeEvent).target.value;
|
||||
} else if (component === 'NCheckbox') {
|
||||
currentValueRef.value = (e as ChangeEvent).target.checked;
|
||||
} else if (isString(e) || isBoolean(e) || isNumber(e)) {
|
||||
currentValueRef.value = e;
|
||||
}
|
||||
|
||||
//TODO 这里组件参数格式,和dayjs格式不一致
|
||||
if (component === 'NDatePicker') {
|
||||
let format = (props.column.editComponentProps?.format)
|
||||
.replace(/yyyy/g, 'YYYY')
|
||||
.replace(/dd/g, 'DD');
|
||||
currentValueRef.value = dayjs(currentValueRef.value).format(format);
|
||||
}
|
||||
|
||||
const onChange = props.column?.editComponentProps?.onChange;
|
||||
if (onChange && isFunction(onChange)) onChange(...arguments);
|
||||
|
||||
table.emit?.('edit-change', {
|
||||
column: props.column,
|
||||
value: unref(currentValueRef),
|
||||
record: toRaw(props.record),
|
||||
});
|
||||
await handleSubmiRule();
|
||||
}
|
||||
|
||||
async function handleSubmiRule() {
|
||||
const { column, record } = props;
|
||||
const { editRule } = column;
|
||||
const currentValue = unref(currentValueRef);
|
||||
|
||||
if (editRule) {
|
||||
if (isBoolean(editRule) && !currentValue && !isNumber(currentValue)) {
|
||||
ruleVisible.value = true;
|
||||
const component = unref(getComponent);
|
||||
ruleMessage.value = createPlaceholderMessage(component);
|
||||
return false;
|
||||
}
|
||||
if (isFunction(editRule)) {
|
||||
const res = await editRule(currentValue, record as Recordable);
|
||||
if (!!res) {
|
||||
ruleMessage.value = res;
|
||||
ruleVisible.value = true;
|
||||
return false;
|
||||
} else {
|
||||
ruleMessage.value = '';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ruleMessage.value = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSubmit(needEmit = true, valid = true) {
|
||||
if (valid) {
|
||||
const isPass = await handleSubmiRule();
|
||||
if (!isPass) return false;
|
||||
}
|
||||
|
||||
const { column, index, record } = props;
|
||||
if (!record) return false;
|
||||
const { key } = column;
|
||||
const value = unref(currentValueRef);
|
||||
if (!key) return;
|
||||
|
||||
const dataKey = key as string;
|
||||
|
||||
set(record, dataKey, value);
|
||||
//const record = await table.updateTableData(index, dataKey, value);
|
||||
needEmit && table.emit?.('edit-end', { record, index, key, value });
|
||||
isEdit.value = false;
|
||||
}
|
||||
|
||||
async function handleEnter() {
|
||||
if (props.column?.editRow) {
|
||||
return;
|
||||
}
|
||||
await handleSubmit();
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
isEdit.value = false;
|
||||
currentValueRef.value = defaultValueRef.value;
|
||||
const { column, index, record } = props;
|
||||
const { key } = column;
|
||||
ruleVisible.value = true;
|
||||
ruleMessage.value = '';
|
||||
table.emit?.('edit-cancel', {
|
||||
record,
|
||||
index,
|
||||
key: key,
|
||||
value: unref(currentValueRef),
|
||||
});
|
||||
}
|
||||
|
||||
function onClickOutside() {
|
||||
if (props.column?.editable || unref(getRowEditable)) {
|
||||
return;
|
||||
}
|
||||
const component = unref(getComponent);
|
||||
|
||||
if (component.includes('NInput')) {
|
||||
handleCancel();
|
||||
}
|
||||
}
|
||||
|
||||
// only ApiSelect
|
||||
function handleOptionsChange(options: LabelValueOptions) {
|
||||
optionsRef.value = options;
|
||||
}
|
||||
|
||||
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
||||
if (props.record) {
|
||||
/* eslint-disable */
|
||||
isArray(props.record[cbs])
|
||||
? props.record[cbs]?.push(handle)
|
||||
: (props.record[cbs] = [handle]);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.record) {
|
||||
initCbs('submitCbs', handleSubmit);
|
||||
initCbs('validCbs', handleSubmiRule);
|
||||
initCbs('cancelCbs', handleCancel);
|
||||
|
||||
if (props.column.key) {
|
||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||
props.record.editValueRefs[props.column.key] = currentValueRef;
|
||||
}
|
||||
/* eslint-disable */
|
||||
props.record.onCancelEdit = () => {
|
||||
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
||||
};
|
||||
/* eslint-disable */
|
||||
props.record.onSubmitEdit = async() => {
|
||||
if (isArray(props.record?.submitCbs)) {
|
||||
const validFns = (props.record?.validCbs || []).map((fn) => fn());
|
||||
|
||||
const res = await Promise.all(validFns);
|
||||
|
||||
const pass = res.every((item) => !!item);
|
||||
|
||||
if (!pass) return;
|
||||
const submitFns = props.record?.submitCbs || [];
|
||||
submitFns.forEach((fn) => fn(false, false));
|
||||
table.emit?.('edit-row-end');
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isEdit,
|
||||
handleEdit,
|
||||
currentValueRef,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
handleCancel,
|
||||
elRef,
|
||||
getComponent,
|
||||
getRule,
|
||||
onClickOutside,
|
||||
ruleMessage,
|
||||
getRuleVisible,
|
||||
getComponentProps,
|
||||
handleOptionsChange,
|
||||
getWrapperStyle,
|
||||
getWrapperClass,
|
||||
getRowEditable,
|
||||
getValues,
|
||||
handleEnter,
|
||||
// getSize,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.editable-cell {
|
||||
&-content {
|
||||
position: relative;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.edit-icon {
|
||||
font-size: 14px;
|
||||
//position: absolute;
|
||||
//top: 4px;
|
||||
//right: 0;
|
||||
display: none;
|
||||
width: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.edit-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
15
src/components/Table/src/components/editable/helper.ts
Normal file
15
src/components/Table/src/components/editable/helper.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { ComponentType } from '../../types/componentType';
|
||||
|
||||
/**
|
||||
* @description: 生成placeholder
|
||||
*/
|
||||
export function createPlaceholderMessage(component: ComponentType) {
|
||||
if (component === 'NInput') return '请输入';
|
||||
if (
|
||||
['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes(
|
||||
component
|
||||
)
|
||||
)
|
||||
return '请选择';
|
||||
return '';
|
||||
}
|
||||
49
src/components/Table/src/components/editable/index.ts
Normal file
49
src/components/Table/src/components/editable/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { BasicColumn } from '@/components/Table/src/types/table';
|
||||
import { h, Ref } from 'vue';
|
||||
|
||||
import EditableCell from './EditableCell.vue';
|
||||
|
||||
export function renderEditCell(column: BasicColumn) {
|
||||
return (record, index) => {
|
||||
const _key = column.key;
|
||||
const value = record[_key];
|
||||
record.onEdit = async (edit: boolean, submit = false) => {
|
||||
if (!submit) {
|
||||
record.editable = edit;
|
||||
}
|
||||
|
||||
if (!edit && submit) {
|
||||
const res = await record.onSubmitEdit?.();
|
||||
if (res) {
|
||||
record.editable = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// cancel
|
||||
if (!edit && !submit) {
|
||||
record.onCancelEdit?.();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return h(EditableCell, {
|
||||
value,
|
||||
record,
|
||||
column,
|
||||
index,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export type EditRecordRow<T = Recordable> = Partial<
|
||||
{
|
||||
onEdit: (editable: boolean, submit?: boolean) => Promise<boolean>;
|
||||
editable: boolean;
|
||||
onCancel: Fn;
|
||||
onSubmit: Fn;
|
||||
submitCbs: Fn[];
|
||||
cancelCbs: Fn[];
|
||||
validCbs: Fn[];
|
||||
editValueRefs: Recordable<Ref>;
|
||||
} & T
|
||||
>;
|
||||
@@ -158,7 +158,7 @@
|
||||
table.getColumns().forEach((item) => {
|
||||
newRet.push({ ...item });
|
||||
});
|
||||
return newRet;
|
||||
return newRet.filter((item) => item.key != 'action' && item.title != '操作');
|
||||
}
|
||||
|
||||
//重置
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue';
|
||||
import { ref, Ref, ComputedRef, unref, computed, watch, toRaw, h } from 'vue';
|
||||
import type { BasicColumn, BasicTableProps } from '../types/table';
|
||||
import { isEqual, cloneDeep } from 'lodash-es';
|
||||
import { isArray, isString, isBoolean, isFunction } from '@/utils/is';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { ActionItem } from '@/components/Table';
|
||||
import { renderEditCell } from '../components/editable';
|
||||
import { NTooltip, NIcon } from 'naive-ui';
|
||||
import { FormOutlined } from '@vicons/antd';
|
||||
|
||||
export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
||||
const columnsRef = ref(unref(propsRef).columns) as unknown as Ref<BasicColumn[]>;
|
||||
@@ -33,13 +36,48 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
||||
return isIfShow;
|
||||
}
|
||||
|
||||
const renderTooltip = (trigger, content) => {
|
||||
return h(NTooltip, null, {
|
||||
trigger: () => trigger,
|
||||
default: () => content,
|
||||
});
|
||||
};
|
||||
|
||||
const getPageColumns = computed(() => {
|
||||
const pageColumns = unref(getColumnsRef);
|
||||
const columns = cloneDeep(pageColumns);
|
||||
return columns.filter((column) => {
|
||||
// @ts-ignore
|
||||
return hasPermission(column.auth) && isIfShow(column);
|
||||
});
|
||||
return columns
|
||||
.filter((column) => {
|
||||
// @ts-ignore
|
||||
return hasPermission(column.auth) && isIfShow(column);
|
||||
})
|
||||
.map((column) => {
|
||||
const { edit, editRow } = column;
|
||||
if (edit) {
|
||||
column.render = renderEditCell(column);
|
||||
if (edit) {
|
||||
const title: any = column.title;
|
||||
column.title = () => {
|
||||
return renderTooltip(
|
||||
h('span', {}, [
|
||||
h('span', { style: { 'margin-right': '5px' } }, title),
|
||||
h(
|
||||
NIcon,
|
||||
{
|
||||
size: 14,
|
||||
},
|
||||
{
|
||||
default: () => h(FormOutlined),
|
||||
}
|
||||
),
|
||||
]),
|
||||
'该列可编辑'
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
return column;
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import { BasicColumn } from './types/table';
|
||||
|
||||
export const basicProps = {
|
||||
@@ -16,7 +17,7 @@ export const basicProps = {
|
||||
},
|
||||
tableData: {
|
||||
type: [Object],
|
||||
default: () => {},
|
||||
default: () => [],
|
||||
},
|
||||
columns: {
|
||||
type: [Array] as PropType<BasicColumn[]>,
|
||||
@@ -36,6 +37,7 @@ export const basicProps = {
|
||||
type: [Object, Boolean],
|
||||
default: () => {},
|
||||
},
|
||||
//废弃
|
||||
showPagination: {
|
||||
type: [String, Boolean],
|
||||
default: 'auto',
|
||||
@@ -44,4 +46,6 @@ export const basicProps = {
|
||||
type: Object as PropType<BasicColumn>,
|
||||
default: null,
|
||||
},
|
||||
canResize: propTypes.bool.def(true),
|
||||
resizeHeightOffset: propTypes.number.def(0),
|
||||
};
|
||||
|
||||
8
src/components/Table/src/types/componentType.ts
Normal file
8
src/components/Table/src/types/componentType.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export type ComponentType =
|
||||
| 'NInput'
|
||||
| 'NInputNumber'
|
||||
| 'NSelect'
|
||||
| 'NCheckbox'
|
||||
| 'NSwitch'
|
||||
| 'NDatePicker'
|
||||
| 'NTimePicker';
|
||||
@@ -1,6 +1,20 @@
|
||||
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
||||
|
||||
export type BasicColumn = TableBaseColumn;
|
||||
import { ComponentType } from './componentType';
|
||||
export interface BasicColumn extends TableBaseColumn {
|
||||
//编辑表格
|
||||
edit?: boolean;
|
||||
editRow?: boolean;
|
||||
editable?: boolean;
|
||||
editComponent?: ComponentType;
|
||||
editComponentProps?: Recordable;
|
||||
editRule?: boolean | ((text: string, record: Recordable) => Promise<string>);
|
||||
editValueMap?: (value: any) => string;
|
||||
onEditRow?: () => void;
|
||||
// 权限编码控制是否显示
|
||||
auth?: RoleEnum | RoleEnum[] | string | string[];
|
||||
// 业务控制是否显示
|
||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||
}
|
||||
|
||||
export interface TableActionType {
|
||||
reload: (opt) => Promise<void>;
|
||||
@@ -16,4 +30,6 @@ export interface BasicTableProps {
|
||||
pagination: object;
|
||||
showPagination: boolean;
|
||||
actionColumn: any[];
|
||||
canResize: boolean;
|
||||
resizeHeightOffset: number;
|
||||
}
|
||||
|
||||
86
src/directives/clickOutside.ts
Normal file
86
src/directives/clickOutside.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { on } from '@/utils/domUtils';
|
||||
import { isServer } from '@/utils/is';
|
||||
import type { ComponentPublicInstance, DirectiveBinding, ObjectDirective } from 'vue';
|
||||
|
||||
type DocumentHandler = <T extends MouseEvent>(mouseup: T, mousedown: T) => void;
|
||||
|
||||
type FlushList = Map<
|
||||
HTMLElement,
|
||||
{
|
||||
documentHandler: DocumentHandler;
|
||||
bindingFn: (...args: unknown[]) => unknown;
|
||||
}
|
||||
>;
|
||||
|
||||
const nodeList: FlushList = new Map();
|
||||
|
||||
let startClick: MouseEvent;
|
||||
|
||||
if (!isServer) {
|
||||
on(document, 'mousedown', (e: MouseEvent) => (startClick = e));
|
||||
on(document, 'mouseup', (e: MouseEvent) => {
|
||||
for (const { documentHandler } of nodeList.values()) {
|
||||
documentHandler(e, startClick);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createDocumentHandler(el: HTMLElement, binding: DirectiveBinding): DocumentHandler {
|
||||
let excludes: HTMLElement[] = [];
|
||||
if (Array.isArray(binding.arg)) {
|
||||
excludes = binding.arg;
|
||||
} else {
|
||||
// due to current implementation on binding type is wrong the type casting is necessary here
|
||||
excludes.push(binding.arg as unknown as HTMLElement);
|
||||
}
|
||||
return function (mouseup, mousedown) {
|
||||
const popperRef = (
|
||||
binding.instance as ComponentPublicInstance<{
|
||||
popperRef: Nullable<HTMLElement>;
|
||||
}>
|
||||
).popperRef;
|
||||
const mouseUpTarget = mouseup.target as Node;
|
||||
const mouseDownTarget = mousedown.target as Node;
|
||||
const isBound = !binding || !binding.instance;
|
||||
const isTargetExists = !mouseUpTarget || !mouseDownTarget;
|
||||
const isContainedByEl = el.contains(mouseUpTarget) || el.contains(mouseDownTarget);
|
||||
const isSelf = el === mouseUpTarget;
|
||||
|
||||
const isTargetExcluded =
|
||||
(excludes.length && excludes.some((item) => item?.contains(mouseUpTarget))) ||
|
||||
(excludes.length && excludes.includes(mouseDownTarget as HTMLElement));
|
||||
const isContainedByPopper =
|
||||
popperRef && (popperRef.contains(mouseUpTarget) || popperRef.contains(mouseDownTarget));
|
||||
if (
|
||||
isBound ||
|
||||
isTargetExists ||
|
||||
isContainedByEl ||
|
||||
isSelf ||
|
||||
isTargetExcluded ||
|
||||
isContainedByPopper
|
||||
) {
|
||||
return;
|
||||
}
|
||||
binding.value();
|
||||
};
|
||||
}
|
||||
|
||||
const ClickOutside: ObjectDirective = {
|
||||
beforeMount(el, binding) {
|
||||
nodeList.set(el, {
|
||||
documentHandler: createDocumentHandler(el, binding),
|
||||
bindingFn: binding.value,
|
||||
});
|
||||
},
|
||||
updated(el, binding) {
|
||||
nodeList.set(el, {
|
||||
documentHandler: createDocumentHandler(el, binding),
|
||||
bindingFn: binding.value,
|
||||
});
|
||||
},
|
||||
unmounted(el) {
|
||||
nodeList.delete(el);
|
||||
},
|
||||
};
|
||||
|
||||
export default ClickOutside;
|
||||
36
src/hooks/event/useWindowSizeFn.ts
Normal file
36
src/hooks/event/useWindowSizeFn.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { tryOnMounted, tryOnUnmounted } from '@vueuse/core';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
interface WindowSizeOptions {
|
||||
once?: boolean;
|
||||
immediate?: boolean;
|
||||
listenerOptions?: AddEventListenerOptions | boolean;
|
||||
}
|
||||
|
||||
export function useWindowSizeFn<T>(fn: Fn<T>, wait = 150, options?: WindowSizeOptions) {
|
||||
let handler = () => {
|
||||
fn();
|
||||
};
|
||||
const handleSize = useDebounceFn(handler, wait);
|
||||
handler = handleSize;
|
||||
|
||||
const start = () => {
|
||||
if (options && options.immediate) {
|
||||
handler();
|
||||
}
|
||||
window.addEventListener('resize', handler);
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
window.removeEventListener('resize', handler);
|
||||
};
|
||||
|
||||
tryOnMounted(() => {
|
||||
start();
|
||||
});
|
||||
|
||||
tryOnUnmounted(() => {
|
||||
stop();
|
||||
});
|
||||
return [start, stop];
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<style lang="less" scoped>
|
||||
.page-footer {
|
||||
margin: 48px 0 24px 0;
|
||||
//margin: 28px 0 24px 0;
|
||||
padding: 0 16px;
|
||||
text-align: center;
|
||||
|
||||
|
||||
@@ -7,7 +7,18 @@
|
||||
<div class="drawer-setting-item justify-center dark-switch">
|
||||
<n-tooltip placement="bottom">
|
||||
<template #trigger>
|
||||
<n-switch v-model:value="designStore.darkTheme" />
|
||||
<n-switch v-model:value="designStore.darkTheme" class="dark-theme-switch">
|
||||
<template #checked>
|
||||
<n-icon size="14" color="#ffd93b">
|
||||
<SunnySharp />
|
||||
</n-icon>
|
||||
</template>
|
||||
<template #unchecked>
|
||||
<n-icon size="14" color="#ffd93b">
|
||||
<Moon />
|
||||
</n-icon>
|
||||
</template>
|
||||
</n-switch>
|
||||
</template>
|
||||
<span>深色主题</span>
|
||||
</n-tooltip>
|
||||
@@ -146,13 +157,13 @@
|
||||
<n-switch v-model:value="settingStore.multiTabsSetting.show" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="drawer-setting-item">
|
||||
<div class="drawer-setting-item-title"> 显示页脚 </div>
|
||||
<div class="drawer-setting-item-action">
|
||||
<n-switch v-model:value="settingStore.showFooter" />
|
||||
</div>
|
||||
</div>
|
||||
<!--1.15废弃,没啥用,占用操作空间-->
|
||||
<!-- <div class="drawer-setting-item">-->
|
||||
<!-- <div class="drawer-setting-item-title"> 显示页脚 </div>-->
|
||||
<!-- <div class="drawer-setting-item-action">-->
|
||||
<!-- <n-switch v-model:value="settingStore.showFooter" />-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="drawer-setting-item">
|
||||
<n-alert type="warning" :showIcon="false">
|
||||
@@ -169,11 +180,12 @@
|
||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||
import { CheckOutlined } from '@vicons/antd';
|
||||
import { Moon, SunnySharp } from '@vicons/ionicons5';
|
||||
import { darkTheme } from 'naive-ui';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ProjectSetting',
|
||||
components: { CheckOutlined },
|
||||
components: { CheckOutlined, Moon, SunnySharp },
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
@@ -300,8 +312,7 @@
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dark-switch .n-switch--active {
|
||||
.dark-switch .n-switch {
|
||||
::v-deep(.n-switch__rail) {
|
||||
background-color: #000e1c;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<div class="layout-header-trigger layout-header-trigger-min">
|
||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
||||
<div class="avatar">
|
||||
<n-avatar>
|
||||
<n-avatar round>
|
||||
{{ username }}
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:collapsed="collapsed"
|
||||
:collapsed-width="64"
|
||||
:collapsed-icon-size="20"
|
||||
:indent="28"
|
||||
:expanded-keys="openKeys"
|
||||
v-model:value="selectedKeys"
|
||||
@update:value="clickMenuItem"
|
||||
@@ -14,7 +15,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, computed, watch, toRefs } from 'vue';
|
||||
import { defineComponent, reactive, computed, watch, toRefs, unref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
||||
import { generatorMenu } from '@/utils/index';
|
||||
@@ -44,7 +45,7 @@
|
||||
// 获取当前打开的子菜单
|
||||
const matched = currentRoute.matched;
|
||||
|
||||
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : [];
|
||||
const getOpenKeys = matched && matched.length ? matched.map((item) => item.name) : [];
|
||||
|
||||
const state = reactive({
|
||||
openKeys: getOpenKeys,
|
||||
@@ -73,8 +74,7 @@
|
||||
() => currentRoute.fullPath,
|
||||
() => {
|
||||
const matched = currentRoute.matched;
|
||||
const getOpenKeys = matched && matched.length ? [matched[0]?.name] : [];
|
||||
state.openKeys = getOpenKeys;
|
||||
state.openKeys = matched.map((item) => item.name);
|
||||
state.selectedKeys = currentRoute.name;
|
||||
}
|
||||
);
|
||||
@@ -90,10 +90,22 @@
|
||||
|
||||
//展开菜单
|
||||
function menuExpanded(openKeys: string[]) {
|
||||
console.log(openKeys);
|
||||
if (!openKeys) return;
|
||||
const latestOpenKey = openKeys.pop();
|
||||
state.openKeys = latestOpenKey ? [latestOpenKey] : [];
|
||||
const latestOpenKey = openKeys.find((key) => state.openKeys.indexOf(key) === -1);
|
||||
const isExistChildren = findChildrenLen(latestOpenKey as string);
|
||||
state.openKeys = isExistChildren ? (latestOpenKey ? [latestOpenKey] : []) : openKeys;
|
||||
}
|
||||
|
||||
//查找是否存在子路由
|
||||
function findChildrenLen(key: string) {
|
||||
if (!key) return false;
|
||||
const subRouteChildren: string[] = [];
|
||||
for (const { children, key } of unref(menus)) {
|
||||
if (children && children.length > 0) {
|
||||
subRouteChildren.push(key as string);
|
||||
}
|
||||
}
|
||||
return subRouteChildren.includes(key);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -46,10 +46,10 @@
|
||||
<MainView />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NLayoutFooter v-if="getShowFooter">
|
||||
<PageFooter />
|
||||
</NLayoutFooter>
|
||||
<!--1.15废弃,没啥用,占用操作空间-->
|
||||
<!-- <NLayoutFooter v-if="getShowFooter">-->
|
||||
<!-- <PageFooter />-->
|
||||
<!-- </NLayoutFooter>-->
|
||||
</NLayoutContent>
|
||||
</NLayout>
|
||||
</NLayout>
|
||||
|
||||
3
src/layout/parentLayout.vue
Normal file
3
src/layout/parentLayout.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
14
src/main.ts
14
src/main.ts
@@ -5,9 +5,12 @@ import router, { setupRouter } from './router';
|
||||
import { setupStore } from '@/store';
|
||||
import MakeitCaptcha from 'makeit-captcha';
|
||||
import 'makeit-captcha/dist/captcha.min.css';
|
||||
import { setupNaive, setupDirectives, setupGlobalMethods, setupCustomComponents } from '@/plugins';
|
||||
import { setupNaive, setupDirectives } from '@/plugins';
|
||||
import { AppProvider } from '@/components/Application';
|
||||
|
||||
async function bootstrap() {
|
||||
const appProvider = createApp(AppProvider);
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(MakeitCaptcha);
|
||||
@@ -15,18 +18,21 @@ async function bootstrap() {
|
||||
// 注册全局常用的 naive-ui 组件
|
||||
setupNaive(app);
|
||||
|
||||
// 注册全局自定义组件,如:<svg-icon />
|
||||
setupCustomComponents(app);
|
||||
// 注册全局自定义组件
|
||||
//setupCustomComponents();
|
||||
|
||||
// 注册全局自定义指令,如:v-permission权限指令
|
||||
setupDirectives(app);
|
||||
|
||||
// 注册全局方法,如:app.config.globalProperties.$message = message
|
||||
setupGlobalMethods(app);
|
||||
//setupGlobalMethods(app);
|
||||
|
||||
// 挂载状态管理
|
||||
setupStore(app);
|
||||
|
||||
//优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件
|
||||
appProvider.mount('#appProvider', true);
|
||||
|
||||
// 挂载路由
|
||||
await setupRouter(app);
|
||||
|
||||
|
||||
@@ -3,3 +3,5 @@ export const RedirectName = 'Redirect';
|
||||
export const ErrorPage = () => import('@/views/exception/404.vue');
|
||||
|
||||
export const Layout = () => import('@/layout/index.vue');
|
||||
|
||||
export const ParentLayout = () => import('@/layout/parentLayout.vue');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
import { Layout } from '@/router/constant';
|
||||
import { Layout, ParentLayout } from '@/router/constant';
|
||||
import { WalletOutlined } from '@vicons/antd';
|
||||
import { renderIcon } from '@/utils/index';
|
||||
|
||||
@@ -20,8 +20,8 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/comp',
|
||||
name: routeName,
|
||||
redirect: '/comp/console',
|
||||
component: Layout,
|
||||
redirect: '/comp/table',
|
||||
meta: {
|
||||
title: '组件示例',
|
||||
icon: renderIcon(WalletOutlined),
|
||||
@@ -31,10 +31,37 @@ const routes: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: 'table',
|
||||
name: `${routeName}_table`,
|
||||
redirect: '/comp/table/basic',
|
||||
component: ParentLayout,
|
||||
meta: {
|
||||
title: '表格',
|
||||
},
|
||||
component: () => import('@/views/comp/table/list.vue'),
|
||||
children: [
|
||||
{
|
||||
path: 'basic',
|
||||
name: `${routeName}_table_basic`,
|
||||
meta: {
|
||||
title: '基础表格',
|
||||
},
|
||||
component: () => import('@/views/comp/table/basic.vue'),
|
||||
},
|
||||
{
|
||||
path: 'editCell',
|
||||
name: `${routeName}_table_editCell`,
|
||||
meta: {
|
||||
title: '单元格编辑',
|
||||
},
|
||||
component: () => import('@/views/comp/table/editCell.vue'),
|
||||
},
|
||||
{
|
||||
path: 'editRow',
|
||||
name: `${routeName}_table_editRow`,
|
||||
meta: {
|
||||
title: '整行编辑',
|
||||
},
|
||||
component: () => import('@/views/comp/table/editRow.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'upload',
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { isNavigationFailure, Router } from 'vue-router';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
|
||||
import NProgress from 'nprogress'; // progress bar
|
||||
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
||||
import { storage } from '@/utils/Storage';
|
||||
import { PageEnum } from '@/enums/pageEnum';
|
||||
|
||||
NProgress.configure({ showSpinner: false }); // NProgress Configuration
|
||||
|
||||
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
||||
|
||||
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
||||
@@ -15,8 +12,9 @@ const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
||||
export function createRouterGuards(router: Router) {
|
||||
const userStore = useUserStoreWidthOut();
|
||||
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
||||
const Loading = window['$loading'] || null;
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start();
|
||||
Loading && Loading.start();
|
||||
if (from.path === LOGIN_PATH && to.name === 'errorPage') {
|
||||
next(PageEnum.BASE_HOME);
|
||||
return;
|
||||
@@ -70,7 +68,7 @@ export function createRouterGuards(router: Router) {
|
||||
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
|
||||
asyncRouteStore.setDynamicAddedRoute(true);
|
||||
next(nextData);
|
||||
NProgress.done();
|
||||
Loading && Loading.finish();
|
||||
});
|
||||
|
||||
router.afterEach((to, _, failure) => {
|
||||
@@ -93,7 +91,7 @@ export function createRouterGuards(router: Router) {
|
||||
}
|
||||
}
|
||||
asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
|
||||
NProgress.done(); // finish progress bar
|
||||
Loading && Loading.finish();
|
||||
});
|
||||
|
||||
router.onError((error) => {
|
||||
|
||||
@@ -10,6 +10,14 @@ export const appThemeList: string[] = [
|
||||
'#0096c7',
|
||||
'#9c27b0',
|
||||
'#ff9800',
|
||||
'#FF3D68',
|
||||
'#00C1D4',
|
||||
'#71EFA3',
|
||||
'#171010',
|
||||
'#78DEC7',
|
||||
'#1768AC',
|
||||
'#FB9300',
|
||||
'#FC5404',
|
||||
];
|
||||
|
||||
const setting = {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
@primaryColor: #2d8cf0;
|
||||
@primaryColorHover: 57 a3f3;
|
||||
@header-height: 64px;
|
||||
@footer-height: 70px;
|
||||
@@ -1,5 +0,0 @@
|
||||
body {
|
||||
.ant-message {
|
||||
z-index: 999999;
|
||||
}
|
||||
}
|
||||
165
src/utils/domUtils.ts
Normal file
165
src/utils/domUtils.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import { upperFirst } from 'lodash-es';
|
||||
|
||||
export interface ViewportOffsetResult {
|
||||
left: number;
|
||||
top: number;
|
||||
right: number;
|
||||
bottom: number;
|
||||
rightIncludeBody: number;
|
||||
bottomIncludeBody: number;
|
||||
}
|
||||
|
||||
export function getBoundingClientRect(element: Element): DOMRect | number {
|
||||
if (!element || !element.getBoundingClientRect) {
|
||||
return 0;
|
||||
}
|
||||
return element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
function trim(string: string) {
|
||||
return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '');
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function hasClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return false;
|
||||
if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.');
|
||||
if (el.classList) {
|
||||
return el.classList.contains(cls);
|
||||
} else {
|
||||
return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function addClass(el: Element, cls: string) {
|
||||
if (!el) return;
|
||||
let curClass = el.className;
|
||||
const classes = (cls || '').split(' ');
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i];
|
||||
if (!clsName) continue;
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.add(clsName);
|
||||
} else if (!hasClass(el, clsName)) {
|
||||
curClass += ' ' + clsName;
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = curClass;
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function removeClass(el: Element, cls: string) {
|
||||
if (!el || !cls) return;
|
||||
const classes = cls.split(' ');
|
||||
let curClass = ' ' + el.className + ' ';
|
||||
|
||||
for (let i = 0, j = classes.length; i < j; i++) {
|
||||
const clsName = classes[i];
|
||||
if (!clsName) continue;
|
||||
|
||||
if (el.classList) {
|
||||
el.classList.remove(clsName);
|
||||
} else if (hasClass(el, clsName)) {
|
||||
curClass = curClass.replace(' ' + clsName + ' ', ' ');
|
||||
}
|
||||
}
|
||||
if (!el.classList) {
|
||||
el.className = trim(curClass);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the left and top offset of the current element
|
||||
* left: the distance between the leftmost element and the left side of the document
|
||||
* top: the distance from the top of the element to the top of the document
|
||||
* right: the distance from the far right of the element to the right of the document
|
||||
* bottom: the distance from the bottom of the element to the bottom of the document
|
||||
* rightIncludeBody: the distance between the leftmost element and the right side of the document
|
||||
* bottomIncludeBody: the distance from the bottom of the element to the bottom of the document
|
||||
*
|
||||
* @description:
|
||||
*/
|
||||
export function getViewportOffset(element: Element): ViewportOffsetResult {
|
||||
const doc = document.documentElement;
|
||||
|
||||
const docScrollLeft = doc.scrollLeft;
|
||||
const docScrollTop = doc.scrollTop;
|
||||
const docClientLeft = doc.clientLeft;
|
||||
const docClientTop = doc.clientTop;
|
||||
|
||||
const pageXOffset = window.pageXOffset;
|
||||
const pageYOffset = window.pageYOffset;
|
||||
|
||||
const box = getBoundingClientRect(element);
|
||||
|
||||
const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect;
|
||||
|
||||
const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0);
|
||||
const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0);
|
||||
const offsetLeft = retLeft + pageXOffset;
|
||||
const offsetTop = rectTop + pageYOffset;
|
||||
|
||||
const left = offsetLeft - scrollLeft;
|
||||
const top = offsetTop - scrollTop;
|
||||
|
||||
const clientWidth = window.document.documentElement.clientWidth;
|
||||
const clientHeight = window.document.documentElement.clientHeight;
|
||||
return {
|
||||
left: left,
|
||||
top: top,
|
||||
right: clientWidth - rectWidth - left,
|
||||
bottom: clientHeight - rectHeight - top,
|
||||
rightIncludeBody: clientWidth - left,
|
||||
bottomIncludeBody: clientHeight - top,
|
||||
};
|
||||
}
|
||||
|
||||
export function hackCss(attr: string, value: string) {
|
||||
const prefix: string[] = ['webkit', 'Moz', 'ms', 'OT'];
|
||||
|
||||
const styleObj: any = {};
|
||||
prefix.forEach((item) => {
|
||||
styleObj[`${item}${upperFirst(attr)}`] = value;
|
||||
});
|
||||
return {
|
||||
...styleObj,
|
||||
[attr]: value,
|
||||
};
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function on(
|
||||
element: Element | HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: EventListenerOrEventListenerObject
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.addEventListener(event, handler, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function off(
|
||||
element: Element | HTMLElement | Document | Window,
|
||||
event: string,
|
||||
handler: Fn
|
||||
): void {
|
||||
if (element && event && handler) {
|
||||
element.removeEventListener(event, handler, false);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
export function once(el: HTMLElement, event: string, fn: EventListener): void {
|
||||
const listener = function (this: any, ...args: unknown[]) {
|
||||
if (fn) {
|
||||
fn.apply(this, args);
|
||||
}
|
||||
off(el, event, listener);
|
||||
};
|
||||
on(el, event, listener);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
export function checkStatus(status: number, msg: string): void {
|
||||
const message = window['$message'];
|
||||
export function checkStatus(status: number, msg: string, message: any): void {
|
||||
switch (status) {
|
||||
case 400:
|
||||
message.error(`${msg}`);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
|
||||
|
||||
import { VAxios } from './Axios';
|
||||
import { AxiosTransform } from './axiosTransform';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
@@ -18,8 +17,6 @@ import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
// @ts-ignore
|
||||
const { $message: Message, $dialog: Modal } = window;
|
||||
|
||||
import router from '@/router';
|
||||
import { storage } from '@/utils/Storage';
|
||||
@@ -32,18 +29,35 @@ const transform: AxiosTransform = {
|
||||
* @description: 处理请求数据
|
||||
*/
|
||||
transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
|
||||
const { $message: Message, $dialog: Modal } = window;
|
||||
const {
|
||||
isTransformRequestResult,
|
||||
isShowMessage = true,
|
||||
isShowErrorMessage,
|
||||
isShowSuccessMessage,
|
||||
successMessageText,
|
||||
errorMessageText,
|
||||
isTransformResponse,
|
||||
isReturnNativeResponse,
|
||||
} = options;
|
||||
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return res;
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformResponse) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
const reject = Promise.reject;
|
||||
|
||||
const { data } = res;
|
||||
|
||||
if (!data) {
|
||||
// return '[HTTP] Request has no return value';
|
||||
return reject(data);
|
||||
}
|
||||
// 这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
|
||||
const { code, result, message } = data;
|
||||
// 请求成功
|
||||
@@ -58,19 +72,14 @@ const transform: AxiosTransform = {
|
||||
Message.error(message || errorMessageText || '操作失败!');
|
||||
} else if (!hasSuccess && options.errorMessageMode === 'modal') {
|
||||
// errorMessageMode=‘custom-modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||
Modal.confirm({ title: '错误提示', content: message });
|
||||
Modal.info({
|
||||
title: '提示',
|
||||
content: message,
|
||||
positiveText: '确定',
|
||||
onPositiveClick: () => {},
|
||||
});
|
||||
}
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformRequestResult) {
|
||||
return res.data;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
// return '[HTTP] Request has no return value';
|
||||
return reject(data);
|
||||
}
|
||||
|
||||
// 接口请求成功,直接返回结果
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
@@ -94,19 +103,21 @@ const transform: AxiosTransform = {
|
||||
if (router.currentRoute.value.name == 'login') return;
|
||||
// 到登录页
|
||||
const timeoutMsg = '登录超时,请重新登录!';
|
||||
Modal.destroyAll();
|
||||
Modal.warning({
|
||||
title: '提示',
|
||||
content: '登录身份已失效,请重新登录!',
|
||||
onOk: () => {
|
||||
content: '登录身份已失效,请重新登录!',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
storage.clear();
|
||||
router.replace({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: router.currentRoute.value.fullPath,
|
||||
},
|
||||
});
|
||||
storage.clear();
|
||||
},
|
||||
onNegativeClick: () => {},
|
||||
});
|
||||
return reject(new Error(timeoutMsg));
|
||||
}
|
||||
@@ -175,9 +186,11 @@ const transform: AxiosTransform = {
|
||||
* @description: 响应错误处理
|
||||
*/
|
||||
responseInterceptorsCatch: (error: any) => {
|
||||
const { $message: Message, $dialog: Modal } = window;
|
||||
const { response, code, message } = error || {};
|
||||
// TODO 此处要根据后端接口返回格式修改
|
||||
const msg: string =
|
||||
response && response.data && response.data.error ? response.data.error.message : '';
|
||||
response && response.data && response.data.message ? response.data.message : '';
|
||||
const err: string = error.toString();
|
||||
try {
|
||||
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
||||
@@ -185,9 +198,11 @@ const transform: AxiosTransform = {
|
||||
return;
|
||||
}
|
||||
if (err && err.includes('Network Error')) {
|
||||
Modal.confirm({
|
||||
Modal.info({
|
||||
title: '网络异常',
|
||||
content: '请检查您的网络连接是否正常!',
|
||||
positiveText: '确定',
|
||||
onPositiveClick: () => {},
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -197,7 +212,7 @@ const transform: AxiosTransform = {
|
||||
// 请求是否被取消
|
||||
const isCancel = axios.isCancel(error);
|
||||
if (!isCancel) {
|
||||
checkStatus(error.response && error.response.status, msg);
|
||||
checkStatus(error.response && error.response.status, msg, Message);
|
||||
} else {
|
||||
console.warn(error, '请求被取消!');
|
||||
}
|
||||
@@ -206,20 +221,20 @@ const transform: AxiosTransform = {
|
||||
};
|
||||
|
||||
const Axios = new VAxios({
|
||||
timeout: 15 * 1000,
|
||||
// 基础接口地址
|
||||
// baseURL: globSetting.apiUrl,
|
||||
// 接口可能会有通用的地址部分,可以统一抽取出来
|
||||
// prefixUrl: prefix,
|
||||
headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
|
||||
timeout: 10 * 1000,
|
||||
// 接口前缀
|
||||
prefixUrl: urlPrefix,
|
||||
headers: { 'Content-Type': ContentTypeEnum.JSON },
|
||||
// 数据处理方式
|
||||
transform,
|
||||
// 配置项,下面的选项都可以在独立的接口请求中覆盖
|
||||
requestOptions: {
|
||||
// 默认将prefix 添加到url
|
||||
joinPrefix: true,
|
||||
// 是否返回原生响应头 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformRequestResult: true,
|
||||
isTransformResponse: true,
|
||||
// post请求的时候添加参数到url
|
||||
joinParamsToUrl: false,
|
||||
// 格式化提交参数时间
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface RequestOptions {
|
||||
// 格式化请求参数时间
|
||||
formatDate?: boolean;
|
||||
// 是否处理请求结果
|
||||
isTransformRequestResult?: boolean;
|
||||
isTransformResponse?: boolean;
|
||||
// 是否显示提示信息
|
||||
isShowMessage?: boolean;
|
||||
// 是否解析成JSON
|
||||
@@ -34,6 +34,10 @@ export interface RequestOptions {
|
||||
errorMessageMode?: 'none' | 'modal';
|
||||
// 是否添加时间戳
|
||||
joinTime?: boolean;
|
||||
// 不进行任何处理,直接返回
|
||||
isTransformResponse?: boolean;
|
||||
// 是否返回原生响应头
|
||||
isReturnNativeResponse?: boolean;
|
||||
}
|
||||
|
||||
export interface Result<T = any> {
|
||||
|
||||
75
src/views/comp/table/CellColumns.ts
Normal file
75
src/views/comp/table/CellColumns.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '编码',
|
||||
key: 'no',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
editComponent: 'NInput',
|
||||
// 默认必填校验
|
||||
editRule: true,
|
||||
edit: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
key: 'avatar',
|
||||
render(row) {
|
||||
return h(NAvatar, {
|
||||
size: 48,
|
||||
src: row.avatar,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
editComponent: 'NSelect',
|
||||
editComponentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '广东省',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '浙江省',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
edit: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '开始日期',
|
||||
key: 'beginTime',
|
||||
edit: true,
|
||||
width: 250,
|
||||
editComponent: 'NDatePicker',
|
||||
editComponentProps: {
|
||||
type: 'datetime',
|
||||
format: 'yyyy-MM-dd HH:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '结束日期',
|
||||
key: 'endTime',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: '停留时间',
|
||||
key: 'time',
|
||||
},
|
||||
];
|
||||
121
src/views/comp/table/basic.vue
Normal file
121
src/views/comp/table/basic.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
title="表格列表"
|
||||
titleTooltip="这是一个提示"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
>
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, ref, h } from 'vue';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { getTableList } from '@/api/table/list';
|
||||
import { columns } from './basicColumns';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicTable },
|
||||
setup() {
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const actionRef = ref();
|
||||
const state = reactive({
|
||||
params: {
|
||||
pageSize: 5,
|
||||
name: 'xiaoMa',
|
||||
},
|
||||
actionColumn: {
|
||||
width: 150,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: createActions(record),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function createActions(record) {
|
||||
return [
|
||||
{
|
||||
label: '删除',
|
||||
icon: 'ic:outline-delete-outline',
|
||||
onClick: handleDelete.bind(null, record),
|
||||
// 根据业务控制是否显示 isShow 和 auth 是并且关系
|
||||
ifShow: () => {
|
||||
return true;
|
||||
},
|
||||
// 根据权限控制是否显示: 有权限,会显示,支持多个
|
||||
auth: ['basic_list'],
|
||||
},
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
ifShow: () => {
|
||||
return true;
|
||||
},
|
||||
auth: ['basic_list'],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const loadDataTable = async (params) => {
|
||||
const data = await getTableList(params);
|
||||
return data;
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function handleDelete(record) {
|
||||
console.log(record);
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: `您想删除${record.name}`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
message.success('删除成功');
|
||||
},
|
||||
onNegativeClick: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
function handleEdit(record) {
|
||||
console.log(record);
|
||||
message.success('您点击了编辑按钮');
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
columns,
|
||||
actionRef,
|
||||
loadDataTable,
|
||||
onCheckedRow,
|
||||
reloadTable,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
66
src/views/comp/table/basicColumns.ts
Normal file
66
src/views/comp/table/basicColumns.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar, NTag } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '编码',
|
||||
key: 'no',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
key: 'avatar',
|
||||
render(row) {
|
||||
return h(NAvatar, {
|
||||
size: 48,
|
||||
src: row.avatar,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '开始日期',
|
||||
key: 'beginTime',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '结束日期',
|
||||
key: 'endTime',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
render(row) {
|
||||
return h(
|
||||
NTag,
|
||||
{
|
||||
type: row.status ? 'success' : 'error',
|
||||
},
|
||||
{
|
||||
default: () => (row.status ? '启用' : '禁用'),
|
||||
}
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: '停留时间',
|
||||
key: 'time',
|
||||
},
|
||||
];
|
||||
@@ -1,67 +0,0 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar, NButton } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
key: 'avatar',
|
||||
render(row) {
|
||||
return h(NAvatar, {
|
||||
size: 48,
|
||||
src: row.avatar,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
},
|
||||
{
|
||||
title: '开始日期',
|
||||
key: 'beginTime',
|
||||
},
|
||||
{
|
||||
title: '结束日期',
|
||||
key: 'endTime',
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 150,
|
||||
//简单写一下例子,不建议这么写,过段时间,这里封二次封装
|
||||
render() {
|
||||
return [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
type: 'error',
|
||||
style: 'margin-right:10px',
|
||||
onClick: () => {},
|
||||
},
|
||||
{ default: () => '删除' }
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
size: 'small',
|
||||
onClick: () => {},
|
||||
},
|
||||
{ default: () => '编辑' }
|
||||
),
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
131
src/views/comp/table/editCell.vue
Normal file
131
src/views/comp/table/editCell.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
title="表格列表"
|
||||
titleTooltip="这是一个提示"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@edit-end="editEnd"
|
||||
@edit-change="onEditChange"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
>
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, ref, h } from 'vue';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { getTableList } from '@/api/table/list';
|
||||
import { columns } from './CellColumns';
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicTable },
|
||||
setup() {
|
||||
const actionRef = ref();
|
||||
const currentEditKeyRef = ref('');
|
||||
const state = reactive({
|
||||
params: {
|
||||
pageSize: 5,
|
||||
name: 'xiaoMa',
|
||||
},
|
||||
actionColumn: {
|
||||
width: 150,
|
||||
title: '操作',
|
||||
dataIndex: 'action',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: createActions(record),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function handleEdit(record) {
|
||||
currentEditKeyRef.value = record.key;
|
||||
record.onEdit?.(true);
|
||||
}
|
||||
|
||||
function handleCancel(record: EditRecordRow) {
|
||||
currentEditKeyRef.value = '';
|
||||
record.onEdit?.(false, false);
|
||||
}
|
||||
|
||||
function onEditChange({ column, value, record }) {
|
||||
if (column.dataIndex === 'id') {
|
||||
record.editValueRefs.name4.value = `${value}`;
|
||||
}
|
||||
console.log(column, value, record);
|
||||
}
|
||||
|
||||
async function handleSave(record: EditRecordRow) {
|
||||
const pass = await record.onEdit?.(false, true);
|
||||
if (pass) {
|
||||
currentEditKeyRef.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function createActions(record) {
|
||||
if (!record.editable) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
label: '保存',
|
||||
onClick: handleSave.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '取消',
|
||||
onClick: handleCancel.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const loadDataTable = async (params) => {
|
||||
const data = await getTableList(params);
|
||||
return data;
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
console.log(actionRef.value);
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function editEnd({ record, index, key, value }) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
columns,
|
||||
actionRef,
|
||||
loadDataTable,
|
||||
onCheckedRow,
|
||||
reloadTable,
|
||||
editEnd,
|
||||
onEditChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
131
src/views/comp/table/editRow.vue
Normal file
131
src/views/comp/table/editRow.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
title="表格列表"
|
||||
titleTooltip="这是一个提示"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
:actionColumn="actionColumn"
|
||||
@edit-end="editEnd"
|
||||
@edit-change="onEditChange"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
>
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, ref, h } from 'vue';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { getTableList } from '@/api/table/list';
|
||||
import { columns } from './rowColumns';
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicTable },
|
||||
setup() {
|
||||
const actionRef = ref();
|
||||
const currentEditKeyRef = ref('');
|
||||
const state = reactive({
|
||||
params: {
|
||||
pageSize: 5,
|
||||
name: 'xiaoMa',
|
||||
},
|
||||
actionColumn: {
|
||||
width: 150,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
align: 'center',
|
||||
render(record) {
|
||||
return h(TableAction, {
|
||||
style: 'button',
|
||||
actions: createActions(record),
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function handleEdit(record) {
|
||||
currentEditKeyRef.value = record.key;
|
||||
record.onEdit?.(true);
|
||||
}
|
||||
|
||||
function handleCancel(record: EditRecordRow) {
|
||||
currentEditKeyRef.value = '';
|
||||
record.onEdit?.(false, false);
|
||||
}
|
||||
|
||||
function onEditChange({ column, value, record }) {
|
||||
if (column.dataIndex === 'id') {
|
||||
record.editValueRefs.name4.value = `${value}`;
|
||||
}
|
||||
console.log(column, value, record);
|
||||
}
|
||||
|
||||
async function handleSave(record: EditRecordRow) {
|
||||
const pass = await record.onEdit?.(false, true);
|
||||
if (pass) {
|
||||
currentEditKeyRef.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
function createActions(record) {
|
||||
if (!record.editable) {
|
||||
return [
|
||||
{
|
||||
label: '编辑',
|
||||
onClick: handleEdit.bind(null, record),
|
||||
},
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
{
|
||||
label: '保存',
|
||||
onClick: handleSave.bind(null, record),
|
||||
},
|
||||
{
|
||||
label: '取消',
|
||||
onClick: handleCancel.bind(null, record),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
const loadDataTable = async (params) => {
|
||||
const data = await getTableList(params);
|
||||
return data;
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
console.log(actionRef.value);
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
function editEnd({ record, index, key, value }) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
columns,
|
||||
actionRef,
|
||||
loadDataTable,
|
||||
onCheckedRow,
|
||||
reloadTable,
|
||||
editEnd,
|
||||
onEditChange,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicTable
|
||||
title="表格列表"
|
||||
titleTooltip="这是一个提示"
|
||||
:columns="columns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.id"
|
||||
ref="actionRef"
|
||||
@update:checked-row-keys="onCheckedRow"
|
||||
>
|
||||
<template #toolbar>
|
||||
<n-button type="primary" @click="reloadTable">刷新数据</n-button>
|
||||
</template>
|
||||
</BasicTable>
|
||||
</n-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { getTableList } from '@/api/table/list';
|
||||
import { columns } from './columns';
|
||||
|
||||
export default defineComponent({
|
||||
components: { BasicTable },
|
||||
setup() {
|
||||
const actionRef = ref();
|
||||
const state = reactive({
|
||||
params: {
|
||||
pageSize: 5,
|
||||
name: 'xiaoMa',
|
||||
},
|
||||
});
|
||||
const loadDataTable = async (params) => {
|
||||
const data = await getTableList(params);
|
||||
return data;
|
||||
};
|
||||
|
||||
function onCheckedRow(rowKeys) {
|
||||
console.log(rowKeys);
|
||||
}
|
||||
|
||||
function reloadTable() {
|
||||
console.log(actionRef.value);
|
||||
actionRef.value.reload();
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
columns,
|
||||
actionRef,
|
||||
loadDataTable,
|
||||
onCheckedRow,
|
||||
reloadTable,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
89
src/views/comp/table/rowColumns.ts
Normal file
89
src/views/comp/table/rowColumns.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { h } from 'vue';
|
||||
import { NAvatar } from 'naive-ui';
|
||||
|
||||
export const columns = [
|
||||
{
|
||||
title: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '编码',
|
||||
key: 'no',
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
key: 'name',
|
||||
editComponent: 'NInput',
|
||||
editRow: true,
|
||||
// 默认必填校验
|
||||
editRule: true,
|
||||
edit: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
key: 'avatar',
|
||||
render(row) {
|
||||
return h(NAvatar, {
|
||||
size: 48,
|
||||
src: row.avatar,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '地址',
|
||||
key: 'address',
|
||||
editRow: true,
|
||||
editComponent: 'NSelect',
|
||||
editComponentProps: {
|
||||
options: [
|
||||
{
|
||||
label: '广东省',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
label: '浙江省',
|
||||
value: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
edit: true,
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '开始日期',
|
||||
key: 'beginTime',
|
||||
editRow: true,
|
||||
edit: true,
|
||||
width: 250,
|
||||
editComponent: 'NDatePicker',
|
||||
editComponentProps: {
|
||||
type: 'datetime',
|
||||
format: 'yyyy-MM-dd HH:mm:ss',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '结束日期',
|
||||
key: 'endTime',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
editRow: true,
|
||||
edit: true,
|
||||
width: 100,
|
||||
editComponent: 'NSwitch',
|
||||
editValueMap: (value) => {
|
||||
return value ? '启用' : '禁用';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: '停留时间',
|
||||
key: 'time',
|
||||
},
|
||||
];
|
||||
@@ -219,7 +219,7 @@
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.view-account {
|
||||
background-image: url('@/assets/images/login.svg');
|
||||
background-image: url('../../assets/images/login.svg');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50%;
|
||||
background-size: 100%;
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
页面数据为 Mock 示例数据,非真实数据。
|
||||
</n-card>
|
||||
</div>
|
||||
|
||||
<n-grid class="mt-4" cols="1 s:1 m:1 l:3 xl:3 2xl:3" responsive="screen" :x-gap="12">
|
||||
<n-gi span="1">
|
||||
<n-card :segmented="{ content: 'hard' }" :bordered="false" size="small">
|
||||
@@ -35,7 +34,6 @@
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
|
||||
<div class="w-full menu">
|
||||
<n-input type="input" v-model:value="pattern" placeholder="输入菜单名称搜索">
|
||||
<template #suffix>
|
||||
@@ -61,6 +59,7 @@
|
||||
:expandedKeys="expandedKeys"
|
||||
style="max-height: 650px; overflow: hidden"
|
||||
@update:selected-keys="selectedTree"
|
||||
@update:expanded-keys="onExpandedKeys"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
@@ -77,7 +76,7 @@
|
||||
<span>编辑菜单{{ treeItemTitle ? `:${treeItemTitle}` : '' }}</span>
|
||||
</n-space>
|
||||
</template>
|
||||
<n-alert type="info" closable> 从菜单列表选择一项后,进行编辑 </n-alert>
|
||||
<n-alert type="info" closable> 从菜单列表选择一项后,进行编辑</n-alert>
|
||||
<n-form
|
||||
:model="formParams"
|
||||
:rules="rules"
|
||||
@@ -122,17 +121,15 @@
|
||||
</n-card>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<CreateDrawer ref="createDrawerRef" :title="drawerTitle" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, unref, reactive, toRefs, onMounted, computed } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { DownOutlined, AlignLeftOutlined, SearchOutlined, FormOutlined } from '@vicons/antd';
|
||||
import { getMenuList } from '@/api/system/menu';
|
||||
import { getTreeItem } from '@/utils/index';
|
||||
import { getTreeItem } from '@/utils';
|
||||
import CreateDrawer from './CreateDrawer.vue';
|
||||
|
||||
const rules = {
|
||||
@@ -156,35 +153,49 @@
|
||||
const message = useMessage();
|
||||
|
||||
const isAddSon = computed(() => {
|
||||
return state.treeItemKey.length ? false : true;
|
||||
return !state.treeItemKey.length;
|
||||
});
|
||||
|
||||
const addMenuOptions = ref([
|
||||
{
|
||||
label: '添加顶级菜单',
|
||||
key: 'home',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '添加子菜单',
|
||||
key: 'son',
|
||||
disabled: isAddSon,
|
||||
},
|
||||
]);
|
||||
|
||||
const treeItemKey: string[] = reactive([]);
|
||||
|
||||
const expandedKeys: string[] = reactive([]);
|
||||
|
||||
const treeData: string[] = reactive([]);
|
||||
|
||||
const state = reactive({
|
||||
addMenuOptions: [
|
||||
{
|
||||
label: '添加顶级菜单',
|
||||
key: 'home',
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '添加子菜单',
|
||||
key: 'son',
|
||||
disabled: isAddSon,
|
||||
},
|
||||
],
|
||||
loading: true,
|
||||
subLoading: false,
|
||||
isEditMenu: false,
|
||||
treeData: [],
|
||||
treeItemKey: [],
|
||||
treeItemTitle: '',
|
||||
expandedKeys: [],
|
||||
formParams: {},
|
||||
formParams: {
|
||||
type: 1,
|
||||
label: '',
|
||||
subtitle: '',
|
||||
path: '',
|
||||
auth: '',
|
||||
openType: 1,
|
||||
},
|
||||
pattern: '',
|
||||
drawerTitle: '',
|
||||
treeItemKey,
|
||||
expandedKeys,
|
||||
treeData,
|
||||
});
|
||||
|
||||
function selectAddMenu(key) {
|
||||
function selectAddMenu(key: string) {
|
||||
state.drawerTitle = key === 'home' ? '添加顶栏菜单' : `添加子菜单:${state.treeItemTitle}`;
|
||||
openCreateDrawer();
|
||||
}
|
||||
@@ -194,7 +205,7 @@
|
||||
openDrawer();
|
||||
}
|
||||
|
||||
function selectedTree(keys) {
|
||||
function selectedTree(keys: string[]) {
|
||||
if (keys.length) {
|
||||
const treeItem = getTreeItem(unref(state.treeData), keys[0]);
|
||||
state.treeItemKey = keys;
|
||||
@@ -214,7 +225,7 @@
|
||||
}
|
||||
|
||||
function formSubmit() {
|
||||
formRef.value.validate((errors) => {
|
||||
formRef.value.validate((errors: boolean) => {
|
||||
if (!errors) {
|
||||
message.error('抱歉,您没有该权限');
|
||||
} else {
|
||||
@@ -227,21 +238,10 @@
|
||||
if (state.expandedKeys.length) {
|
||||
state.expandedKeys = [];
|
||||
} else {
|
||||
state.expandedKeys = state.treeData.map((item) => item.key);
|
||||
state.expandedKeys = state.treeData.map((item: any) => item.key);
|
||||
}
|
||||
}
|
||||
|
||||
function onExpandedKeys() {
|
||||
// let key = keys[0]
|
||||
// console.log(state.expandedKeys)
|
||||
// if(state.expandedKeys.includes(key)){
|
||||
// state.expandedKeys.splice(state.expandedKeys.findIndex(item => item === key), 1)
|
||||
// console.log(state.expandedKeys)
|
||||
// }else{
|
||||
// state.expandedKeys.push(key)
|
||||
// }
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const treeMenuList = await getMenuList();
|
||||
state.expandedKeys = treeMenuList.list.map((item) => item.key);
|
||||
@@ -249,8 +249,13 @@
|
||||
state.loading = false;
|
||||
});
|
||||
|
||||
function onExpandedKeys(keys: string[]) {
|
||||
state.expandedKeys = keys;
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
addMenuOptions,
|
||||
createDrawerRef,
|
||||
formRef,
|
||||
rules,
|
||||
|
||||
Reference in New Issue
Block a user