943 lines
29 KiB
Vue
943 lines
29 KiB
Vue
|
|
<template>
|
|||
|
|
<page-title :title="getTitle"/>
|
|||
|
|
|
|||
|
|
<div class="ns-table-wrapper">
|
|||
|
|
<div class="ns-table" :class="{ 'ns-table-no-search': !(formConfig?.schemas.length > 0) }">
|
|||
|
|
<a-spin :spinning="tableState.loading">
|
|||
|
|
<a-row type="flex" class="ns-table-main">
|
|||
|
|
<a-col flex="180px">
|
|||
|
|
<ns-table-header
|
|||
|
|
:headerActions="headerActions"
|
|||
|
|
:searchData="formModel"
|
|||
|
|
:tableTitle="tableTitle"
|
|||
|
|
:data="tableState.selectedRows">
|
|||
|
|
<template #header="data">
|
|||
|
|
<slot name="header" v-bind="data || {}"></slot>
|
|||
|
|
</template>
|
|||
|
|
</ns-table-header>
|
|||
|
|
<Skeleton :loading="loadingTree" active>
|
|||
|
|
<div class="type-tree-wrapper">
|
|||
|
|
<div class="tree-top-header">
|
|||
|
|
<div class="tree-node-content-wrapper" :class="{'selected': allSelected}" @click="onAllSelect">全部类别</div>
|
|||
|
|
<ns-button type="link" @click="onContextMenuClick(null, 'add')" v-if="authMap['add']">
|
|||
|
|
<PlusCircleOutlined/>
|
|||
|
|
添加类别
|
|||
|
|
</ns-button>
|
|||
|
|
</div>
|
|||
|
|
<a-tree v-model:expandedKeys="expandedKeys" :tree-data="treeData" v-model:selectedKeys="selectedKeys"
|
|||
|
|
@expand="onExpand" @select="onTreeSelect">
|
|||
|
|
<template #title="{ key: treeKey, title, dataRef }">
|
|||
|
|
<a-dropdown :trigger="['contextmenu']">
|
|||
|
|
<span style="min-width: 80px; display: block">{{ title }}</span>
|
|||
|
|
<template #overlay>
|
|||
|
|
<a-menu @click="({ key: menuKey }) => onContextMenuClick(treeKey, menuKey, dataRef)">
|
|||
|
|
<a-menu-item key="edit" v-if="authMap['edit']">
|
|||
|
|
<EditOutlined/>
|
|||
|
|
编辑
|
|||
|
|
</a-menu-item>
|
|||
|
|
<a-menu-item key="add" v-if="authMap['add']">
|
|||
|
|
<PlusCircleOutlined/>
|
|||
|
|
添加
|
|||
|
|
</a-menu-item>
|
|||
|
|
<a-menu-item key="remove" v-if="authMap['remove']">
|
|||
|
|
<DeleteOutlined/>
|
|||
|
|
删除
|
|||
|
|
</a-menu-item>
|
|||
|
|
</a-menu>
|
|||
|
|
</template>
|
|||
|
|
</a-dropdown>
|
|||
|
|
</template>
|
|||
|
|
</a-tree>
|
|||
|
|
</div>
|
|||
|
|
</Skeleton>
|
|||
|
|
</a-col>
|
|||
|
|
<a-col flex="auto">
|
|||
|
|
<!-- {{ formConfig }} -->
|
|||
|
|
<div class="ns-table-search">
|
|||
|
|
<ns-form
|
|||
|
|
ref="formElRef"
|
|||
|
|
class="ns-table-form"
|
|||
|
|
:showAction="true"
|
|||
|
|
v-bind="formConfig"
|
|||
|
|
formLayout="flex"
|
|||
|
|
:expand="expand"
|
|||
|
|
:showExpand="showExpand"
|
|||
|
|
:model="formModel"
|
|||
|
|
@finish="formFinish"/>
|
|||
|
|
</div>
|
|||
|
|
<Skeleton :loading="loadingTable" active>
|
|||
|
|
<div class="section0">
|
|||
|
|
<div class="section-content" v-if="tableData && tableData.length>0">
|
|||
|
|
<template v-for="(item) in tableData">
|
|||
|
|
<div class="item card-item" @click="onCLickCard(item)">
|
|||
|
|
<header class="item-header">
|
|||
|
|
<div class="info-card-header">
|
|||
|
|
<div class="card-title">{{ item['alias'] }}</div>
|
|||
|
|
</div>
|
|||
|
|
</header>
|
|||
|
|
<div class="info-item-block">
|
|||
|
|
<div class="card-icon"><img :src="imgSrc + item['iconID']" alt=""></div>
|
|||
|
|
<div class="card-description-wrapper">
|
|||
|
|
<div class="card-description"><span>{{ item['desc'] }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<footer class="item-footer">
|
|||
|
|
<div class="card-item-footer">
|
|||
|
|
<div class="card-footer-left">
|
|||
|
|
{{ item['typeClassAlias'] }}
|
|||
|
|
</div>
|
|||
|
|
<div class="card-footer-right">
|
|||
|
|
<div class="card-status">
|
|||
|
|
<span v-if="item['shelfState']"> <i class="iconfont iconGreen font-12"></i> 已上架
|
|||
|
|
</span>
|
|||
|
|
<span v-if="!item['shelfState']"><i class="iconfont iconRed font-12"></i> 待上架
|
|||
|
|
</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</footer>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
</div>
|
|||
|
|
<div class="section-content" v-if="tableData && tableData.length === 0 || !tableData">
|
|||
|
|
<div class="ns-table-content"> <a-empty /> </div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="pagination" v-if="tableData && tableData.length === 0 || tableData!=null">
|
|||
|
|
<a-pagination
|
|||
|
|
v-bind="getPagination"
|
|||
|
|
show-quick-jumper
|
|||
|
|
show-size-changer
|
|||
|
|
@change=" (current,pageSize) => onChange({current,pageSize})"
|
|||
|
|
size="small"
|
|||
|
|
:show-total="(total, range) => `显示 ${range[0]} 到 ${range[1]} 条数据,共计${total}条`"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</Skeleton>
|
|||
|
|
</a-col>
|
|||
|
|
</a-row>
|
|||
|
|
</a-spin>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<a-modal v-model:visible="modalVisible" :title="modalOperation.title" @ok="handleOk" @cancel="cancel" v-if="modalVisible">
|
|||
|
|
<div class="add-form" id="app" v-if="typeSchema.length">
|
|||
|
|
<ns-form
|
|||
|
|
ref="configRef"
|
|||
|
|
formLayout="ns-vertical-form"
|
|||
|
|
:schemas="typeSchema"
|
|||
|
|
:model="typeData"
|
|||
|
|
/>
|
|||
|
|
</div>
|
|||
|
|
</a-modal>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script lang="ts">
|
|||
|
|
import {computed, defineComponent, reactive, ref, unref, watch} from "vue";
|
|||
|
|
import {Skeleton, Pagination} from "ant-design-vue";
|
|||
|
|
import {debounce, cloneDeep, isArray, get} from "lodash-es";
|
|||
|
|
import {tableProps} from "../../../lib/component/table/props";
|
|||
|
|
import {http, PropTypes, stringUtil} from "../../../lib/util";
|
|||
|
|
import {useRoute} from "vue-router";
|
|||
|
|
import {PlusCircleOutlined, EditOutlined, DeleteOutlined, ExclamationCircleOutlined} from "@ant-design/icons-vue";
|
|||
|
|
import {NsMessage, NsModal} from "../../../lib/component";
|
|||
|
|
import {RequestParams} from "/nerv-lib/component/table/table";
|
|||
|
|
import {AxiosRequestConfig} from "axios";
|
|||
|
|
import {useApi, useParams} from "/nerv-lib/use";
|
|||
|
|
import {useTableRefresh} from "/nerv-lib/component/table/use-table-refresh";
|
|||
|
|
import {useTableSession} from "/nerv-lib/component/table/use-table-session";
|
|||
|
|
import {APP} from "/@/router/index.ts";
|
|||
|
|
import {authorizationService} from "/nerv-lib/paas";
|
|||
|
|
|
|||
|
|
export default defineComponent({
|
|||
|
|
name: 'TreeAndTableList',
|
|||
|
|
components: {
|
|||
|
|
Skeleton,
|
|||
|
|
PlusCircleOutlined,
|
|||
|
|
EditOutlined,
|
|||
|
|
DeleteOutlined,
|
|||
|
|
'a-pagination': Pagination,
|
|||
|
|
},
|
|||
|
|
props: {
|
|||
|
|
...tableProps,
|
|||
|
|
title: PropTypes.string,
|
|||
|
|
tableTitle: PropTypes.func,
|
|||
|
|
showTitle: {
|
|||
|
|
type: Boolean,
|
|||
|
|
default: true,
|
|||
|
|
},
|
|||
|
|
resultField: PropTypes.string
|
|||
|
|
},
|
|||
|
|
setup: (props, {attrs, emit}) => {
|
|||
|
|
const nsTableRef = ref();
|
|||
|
|
const route = useRoute();
|
|||
|
|
const formModel = ref({});
|
|||
|
|
// 搜索form参数
|
|||
|
|
const formParamsRef = ref({});
|
|||
|
|
// table参数
|
|||
|
|
const tableState = reactive({
|
|||
|
|
selectedRowKeys: [],
|
|||
|
|
selectedRows: [],
|
|||
|
|
loading: false,
|
|||
|
|
loadError: false,
|
|||
|
|
loadErrorMessage: '',
|
|||
|
|
});
|
|||
|
|
const expandedKeys = ref()
|
|||
|
|
const { delayFetch } = useTableRefresh({ props, reload });
|
|||
|
|
const defaultPageRef = ref(0);
|
|||
|
|
const { getParams } = useParams();
|
|||
|
|
const treeParamsRef = ref({});
|
|||
|
|
const dataRef = ref([]);
|
|||
|
|
const orderRef = ref({});
|
|||
|
|
const { setTableSession } = useTableSession(formModel.value, formParamsRef, defaultPageRef);
|
|||
|
|
const tableData = ref<Recordable[]>([]);
|
|||
|
|
let allSelected = ref(true);
|
|||
|
|
const selectedKeys = ref([]);
|
|||
|
|
let loadingTable = ref<boolean>(false);
|
|||
|
|
let loadingTree = ref<boolean>(false);
|
|||
|
|
const imgSrc = ref(`/api/${APP}/objs/Images/`)
|
|||
|
|
let typeFieldErrors = ref({});
|
|||
|
|
// 缓存
|
|||
|
|
function initTableSession() {
|
|||
|
|
const { fullPath } = route;
|
|||
|
|
const tableSession = JSON.parse(sessionStorage[fullPath] || '{}');
|
|||
|
|
if (!props.enableTableSession) return;
|
|||
|
|
if (tableSession['formModel']) {
|
|||
|
|
Object.assign(formModel.value, tableSession['formModel']);
|
|||
|
|
let code = formModel.value['typeClassCode'];
|
|||
|
|
if(code){
|
|||
|
|
treeParamsRef.value['typeClassCode'] = code;
|
|||
|
|
selectedKeys.value =[ code];
|
|||
|
|
allSelected.value = false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
initTableSession();
|
|||
|
|
//权限部分
|
|||
|
|
let authMap = ref({});
|
|||
|
|
const resourceTypeClassOp= ['add', 'edit', 'remove'];
|
|||
|
|
resourceTypeClassOp.forEach( key => {
|
|||
|
|
authMap.value[key]= authorizationService().checkPermission('resourceRepo', 'resourceTypeClass', key);
|
|||
|
|
})
|
|||
|
|
// 页面标题
|
|||
|
|
const getTitle = computed(() => {
|
|||
|
|
const {title} = props;
|
|||
|
|
if (title) return title;
|
|||
|
|
const {
|
|||
|
|
params: {pageTitle},
|
|||
|
|
} = route;
|
|||
|
|
if (pageTitle) return pageTitle;
|
|||
|
|
});
|
|||
|
|
// 搜索参数
|
|||
|
|
const formFinish = debounce((data) => {
|
|||
|
|
formParamsRef.value = data;
|
|||
|
|
fetch({
|
|||
|
|
page: 1,
|
|||
|
|
});
|
|||
|
|
}, 300);
|
|||
|
|
const tableChangeEvent = (pagination: Props, filters: [], sorter?: any) => {
|
|||
|
|
// console.log('params', pagination, filters, sorter);
|
|||
|
|
if (sorter?.field) {
|
|||
|
|
if (sorter.order) {
|
|||
|
|
orderRef.value = {
|
|||
|
|
[props.paramsOrderField]: stringUtil.toLine(
|
|||
|
|
`${sorter.field} ${sorter.order.replace('end', '')}`,
|
|||
|
|
),
|
|||
|
|
};
|
|||
|
|
} else {
|
|||
|
|
orderRef.value = { [props.paramsOrderField]: '' }; //覆盖默认params
|
|||
|
|
}
|
|||
|
|
fetch({
|
|||
|
|
page: pagination?.current || getPagination.value?.current || 1,
|
|||
|
|
pageSize: pagination?.pageSize,
|
|||
|
|
});
|
|||
|
|
} else if (pagination?.current) {
|
|||
|
|
fetch({
|
|||
|
|
page: pagination?.current,
|
|||
|
|
pageSize: pagination.pageSize,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
// pagination
|
|||
|
|
const getPagination: Recordable | Boolean = computed(() => {
|
|||
|
|
const { pagination } = props;
|
|||
|
|
if (pagination) {
|
|||
|
|
const current = get(dataRef.value, props.pageField);
|
|||
|
|
return {
|
|||
|
|
showQuickJumper: true,
|
|||
|
|
showLessItems: true,
|
|||
|
|
showSizeChanger: true,
|
|||
|
|
showTotal: (total: number, range: Array<number>) =>
|
|||
|
|
`显示第${range[0]}到${range[1]}条记录 ,共 ${total} 条记录`,
|
|||
|
|
...(pagination as Props),
|
|||
|
|
total: get(dataRef.value, props.totalField),
|
|||
|
|
current: (current >= 0 ? current : 0) + props.pageFieldOffset, // 后端0 开始
|
|||
|
|
pageSize: get(dataRef.value, props.sizeField),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const getTableBindValues = computed(() => {
|
|||
|
|
const { params, dynamicParams } = props;
|
|||
|
|
return {
|
|||
|
|
...attrs,
|
|||
|
|
...props,
|
|||
|
|
params: dynamicParams
|
|||
|
|
? getParams({ ...route.params, ...route.query }, dynamicParams, params)
|
|||
|
|
: params || {},
|
|||
|
|
pagination: getPagination.value,
|
|||
|
|
onChange: tableChangeEvent,
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
watch(
|
|||
|
|
() => getTableBindValues.value.api,
|
|||
|
|
() => {
|
|||
|
|
fetch();
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
immediate: true,
|
|||
|
|
},
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
function fetch(requestParams: RequestParams = {}, clearDelay = true) {
|
|||
|
|
clearDelay && delayFetch();
|
|||
|
|
const { api, pagination } = props;
|
|||
|
|
const { page, pageSize } = requestParams;
|
|||
|
|
if (api) {
|
|||
|
|
let pageParams: Recordable = {};
|
|||
|
|
|
|||
|
|
if (pagination !== false) {
|
|||
|
|
pageParams = {
|
|||
|
|
[props.paramsPageField]: page ? page - props.pageFieldOffset : defaultPageRef.value, // 后端0 开始
|
|||
|
|
[props.paramsPageSizeField]:
|
|||
|
|
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
|||
|
|
};
|
|||
|
|
} else {
|
|||
|
|
pageParams = {
|
|||
|
|
[props.paramsPageField]: defaultPageRef.value, // 后端0 开始
|
|||
|
|
[props.paramsPageSizeField]:
|
|||
|
|
pageSize || getPagination.value?.pageSize || props.defaultPageSize,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
const httpParams = {
|
|||
|
|
...getTableBindValues.value.params,
|
|||
|
|
...pageParams,
|
|||
|
|
...formParamsRef.value,
|
|||
|
|
...treeParamsRef.value,
|
|||
|
|
...orderRef.value,
|
|||
|
|
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
setTableSession(pageParams[props.paramsPageField]);
|
|||
|
|
|
|||
|
|
clearDelay && setLoading(true);
|
|||
|
|
|
|||
|
|
const requestConfig: AxiosRequestConfig = { method: 'get' };
|
|||
|
|
const { httpRequest } = useApi();
|
|||
|
|
httpRequest({
|
|||
|
|
api,
|
|||
|
|
params: httpParams,
|
|||
|
|
pathParams: { ...route.params, ...route.query },
|
|||
|
|
requestConfig,
|
|||
|
|
}).then((res: any) => {
|
|||
|
|
|
|||
|
|
tableState.loadError = false;
|
|||
|
|
tableState.loadErrorMessage = '';
|
|||
|
|
dataRef.value = res;
|
|||
|
|
tableData.value = get(unref(dataRef), props.listField);
|
|||
|
|
emit('update:dataSource', tableData.value);
|
|||
|
|
clearDelay && setLoading(false);
|
|||
|
|
loadingTable.value = false;
|
|||
|
|
})
|
|||
|
|
.catch((error: any) => {
|
|||
|
|
const { response, code, message } = error || {};
|
|||
|
|
let errMessage = response?.data?.msg;
|
|||
|
|
const err: string = error?.toString?.() ?? '';
|
|||
|
|
if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
|
|||
|
|
errMessage = '接口请求超时,请刷新页面重试!';
|
|||
|
|
}
|
|||
|
|
if (err?.includes('Network Error')) {
|
|||
|
|
errMessage = '网络异常,请检查您的网络连接是否正常!';
|
|||
|
|
}
|
|||
|
|
tableState.loadError = true;
|
|||
|
|
tableState.loadErrorMessage = errMessage;
|
|||
|
|
clearDelay && setLoading(false);
|
|||
|
|
loadingTable.value = false;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function setLoading(loading: boolean) {
|
|||
|
|
tableState.loading = loading;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// 右侧树形结构
|
|||
|
|
const treeData = ref( []);
|
|||
|
|
// 树形结构添加
|
|||
|
|
let typeSchema = ref([]);
|
|||
|
|
const getTypeSchema = (mode) => {
|
|||
|
|
let schema = [
|
|||
|
|
{
|
|||
|
|
field: 'alias',
|
|||
|
|
component: 'NsInput',
|
|||
|
|
label: '类别名称',
|
|||
|
|
rules: [
|
|||
|
|
{
|
|||
|
|
required: true,
|
|||
|
|
trigger: 'change',
|
|||
|
|
validator: async (rule, value) => {
|
|||
|
|
if (!value) {
|
|||
|
|
return Promise.reject('类别名称不能为空');
|
|||
|
|
}
|
|||
|
|
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,50}$/.test(value)) {
|
|||
|
|
return Promise.reject(
|
|||
|
|
'只能包括大小写字母、中文、数字和短横线(-)、下划线(_)。必须以字母、中文、数字开头。长度必须在1–50字符之间。'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
trigger: 'change',
|
|||
|
|
validator: async (rule, value) => {
|
|||
|
|
if (Object.keys(typeFieldErrors.value).indexOf('alias') !== -1) {
|
|||
|
|
const errorInfo = typeFieldErrors.value['alias'];
|
|||
|
|
delete typeFieldErrors.value['alias'];
|
|||
|
|
return Promise.reject(errorInfo);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
];
|
|||
|
|
if (mode === 'add') {
|
|||
|
|
schema.push(
|
|||
|
|
{
|
|||
|
|
field: 'code',
|
|||
|
|
component: 'NsInput',
|
|||
|
|
label: '类别代码',
|
|||
|
|
rules: [
|
|||
|
|
{
|
|||
|
|
required: true,
|
|||
|
|
trigger: 'change',
|
|||
|
|
validator: async (rule, value) => {
|
|||
|
|
if (!value) {
|
|||
|
|
return Promise.reject('类别代码不能为空');
|
|||
|
|
}
|
|||
|
|
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,50}$/.test(value)) {
|
|||
|
|
return Promise.reject(
|
|||
|
|
'只能包括大小写字母、中文、数字和短横线(-)、下划线(_)。必须以字母、中文、数字开头。长度必须在1–50字符之间。'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
trigger: 'change',
|
|||
|
|
validator: async (rule, value) => {
|
|||
|
|
if (Object.keys(typeFieldErrors.value).indexOf('code') !== -1) {
|
|||
|
|
const errorInfo = typeFieldErrors.value['code'];
|
|||
|
|
delete typeFieldErrors.value['code'];
|
|||
|
|
return Promise.reject(errorInfo);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
})
|
|||
|
|
} else if (mode === 'edit') {
|
|||
|
|
schema.push(
|
|||
|
|
{
|
|||
|
|
field: 'code',
|
|||
|
|
component: 'NsInputText',
|
|||
|
|
label: '类别代码',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
return schema || [];
|
|||
|
|
}
|
|||
|
|
const typeData = ref({});
|
|||
|
|
let modalVisible = ref(false);
|
|||
|
|
let modalOperation = ref({
|
|||
|
|
title: '添加类别',
|
|||
|
|
key: 'add'
|
|||
|
|
});
|
|||
|
|
// 搜索
|
|||
|
|
const formElRef = ref(null);
|
|||
|
|
const getFormConfig = computed(() => {
|
|||
|
|
const formConfig = cloneDeep(props.formConfig);
|
|||
|
|
if (formConfig) {
|
|||
|
|
formConfig.showAction = false;
|
|||
|
|
if (!isArray(formConfig.schemas)) {
|
|||
|
|
formConfig.schemas = [];
|
|||
|
|
}
|
|||
|
|
if (formConfig.keySearch !== false) {
|
|||
|
|
formConfig.schemas.push({
|
|||
|
|
field: 'alias',
|
|||
|
|
label: '关键字',
|
|||
|
|
component: 'NsInputSearch',
|
|||
|
|
rules: [
|
|||
|
|
{
|
|||
|
|
message: '请输入关键字',
|
|||
|
|
trigger: 'change',
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
componentProps: {
|
|||
|
|
maxlength: 50,
|
|||
|
|
onSearch: () => {
|
|||
|
|
unref(formElRef)?.triggerSubmit()
|
|||
|
|
// unref(nsTableRef)?.formElRef?.triggerSubmit();
|
|||
|
|
},
|
|||
|
|
onKeydown: (event) => {
|
|||
|
|
//fix 单个input回车会提交表单 造成重复提交
|
|||
|
|
if (event.key === 'Enter' || event.code === 'Enter') {
|
|||
|
|
event.preventDefault();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
placeholder: '请输入关键字',
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return formConfig;
|
|||
|
|
});
|
|||
|
|
// 卡片列表分页信息
|
|||
|
|
function onTreeSelect(selectedKeys: never[],
|
|||
|
|
e: {
|
|||
|
|
selected: boolean;
|
|||
|
|
selectedNodes: { props: { dataRef: any } }[];
|
|||
|
|
node: any;
|
|||
|
|
event: any;
|
|||
|
|
})
|
|||
|
|
{
|
|||
|
|
allSelected.value = false;
|
|||
|
|
const dataRef = e.selectedNodes[0];
|
|||
|
|
treeParamsRef.value = getParams(dataRef, {typeClassCode: 'code'});
|
|||
|
|
unref(formElRef)?.triggerSubmit();
|
|||
|
|
let key = 'typeClassCode';
|
|||
|
|
if (e.selected) {
|
|||
|
|
formModel.value[key] = treeParamsRef.value[key];
|
|||
|
|
} else {
|
|||
|
|
updateFormModel();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
function reload(clearDelay = true) {
|
|||
|
|
const pagination = unref(getPagination);
|
|||
|
|
fetch(
|
|||
|
|
{
|
|||
|
|
page: pagination === false ? 1 : pagination.current,
|
|||
|
|
},
|
|||
|
|
clearDelay,
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
const updateFormModel = function (value?:any) {
|
|||
|
|
const key = 'typeClassCode';
|
|||
|
|
const model: object = formModel.value;
|
|||
|
|
if (value) {
|
|||
|
|
if (Object.keys(model).includes(key) && model[key] === value) {
|
|||
|
|
delete model[key];
|
|||
|
|
}
|
|||
|
|
} else if (Object.keys(model).includes(key)){
|
|||
|
|
delete model[key];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return {
|
|||
|
|
tableState,
|
|||
|
|
getTitle,
|
|||
|
|
treeData,
|
|||
|
|
expandedKeys,
|
|||
|
|
formModel,
|
|||
|
|
formFinish,
|
|||
|
|
tableData,
|
|||
|
|
typeSchema,
|
|||
|
|
typeData,
|
|||
|
|
modalVisible,
|
|||
|
|
modalOperation,
|
|||
|
|
typeFieldErrors,
|
|||
|
|
getTypeSchema,
|
|||
|
|
formConfig: getFormConfig.value,
|
|||
|
|
nsTableRef,
|
|||
|
|
formElRef,
|
|||
|
|
getParams,
|
|||
|
|
onTreeSelect,
|
|||
|
|
|
|||
|
|
onChange: tableChangeEvent,
|
|||
|
|
allSelected,
|
|||
|
|
selectedKeys,
|
|||
|
|
treeParamsRef,
|
|||
|
|
loadingTable,
|
|||
|
|
loadingTree,
|
|||
|
|
imgSrc,
|
|||
|
|
getTableBindValues,
|
|||
|
|
getPagination,
|
|||
|
|
authMap,
|
|||
|
|
updateFormModel,
|
|||
|
|
setTableSession
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
mounted() {
|
|||
|
|
this.getDataTree()
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
onCLickCard(item) {
|
|||
|
|
this.$router.push(item['code'] + '/detail' );
|
|||
|
|
},
|
|||
|
|
// 转换树结构数据
|
|||
|
|
transferTree(data) {
|
|||
|
|
if( isArray(data) && data.length > 0) {
|
|||
|
|
data.forEach(it=>{
|
|||
|
|
it['title'] = it['alias'];
|
|||
|
|
it['key']=it['code'];
|
|||
|
|
if(it['children'] ) {
|
|||
|
|
this.transferTree(it['children']);
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
} else {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
getDataTree() {
|
|||
|
|
http.get(`/api/${APP}/objs/ResourceTypeClasses`).then(res => {
|
|||
|
|
this.transferTree(res)
|
|||
|
|
this.treeData = res;
|
|||
|
|
|
|||
|
|
this.loadingTree = false;
|
|||
|
|
}).catch( err => {
|
|||
|
|
this.loadingTree = false;
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
onAllSelect() {
|
|||
|
|
this.allSelected = !this.allSelected;
|
|||
|
|
this.selectedKeys = [];
|
|||
|
|
this.treeParamsRef = null;
|
|||
|
|
this.updateFormModel();
|
|||
|
|
this.formElRef.triggerSubmit();
|
|||
|
|
},
|
|||
|
|
// onExpand(keys) {
|
|||
|
|
// this.expandedKeys = keys;
|
|||
|
|
// },
|
|||
|
|
onContextMenuClick(treeKey, menuKey, data) {
|
|||
|
|
this.modalOperation.key = menuKey
|
|||
|
|
if (menuKey === 'add') {
|
|||
|
|
this.modalOperation.title = '添加类别';
|
|||
|
|
this.modalVisible = true;
|
|||
|
|
this.typeData['parentID'] = data?.id || null;
|
|||
|
|
this.typeSchema = this.getTypeSchema('add')
|
|||
|
|
}
|
|||
|
|
if (menuKey === 'edit') {
|
|||
|
|
this.modalOperation.title = '编辑类别';
|
|||
|
|
this.modalVisible = true;
|
|||
|
|
this.typeData = {
|
|||
|
|
alias: data['title'],
|
|||
|
|
code: data['code'],
|
|||
|
|
id: data['id'],
|
|||
|
|
parentID: data['parentID']
|
|||
|
|
}
|
|||
|
|
this.typeSchema = [
|
|||
|
|
{
|
|||
|
|
field: 'alias',
|
|||
|
|
component: 'NsInput',
|
|||
|
|
label: '类别名称',
|
|||
|
|
rules: [
|
|||
|
|
{
|
|||
|
|
required: true,
|
|||
|
|
trigger: 'change',
|
|||
|
|
validator: async (rule, value) => {
|
|||
|
|
if (!value) {
|
|||
|
|
return Promise.reject('类别名称不能为空');
|
|||
|
|
}
|
|||
|
|
if (!/^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9-_\u4e00-\u9fa5]{0,50}$/.test(value)) {
|
|||
|
|
return Promise.reject(
|
|||
|
|
'只能包括大小写字母、中文、数字和短横线(-)、下划线(_)。必须以字母、中文、数字开头。长度必须在1–50字符之间。'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
field: 'code',
|
|||
|
|
component: 'NsInputText',
|
|||
|
|
label: '类别代码'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
field: 'parentID',
|
|||
|
|
label: '类别',
|
|||
|
|
component: 'NsSelectTreeApi',
|
|||
|
|
componentProps: {
|
|||
|
|
showCheckedStrategy: 'TreeSelect.SHOW_ALL',
|
|||
|
|
selectType: 'tree',
|
|||
|
|
api: `/api/${APP}/objs/ResourceTypeClasses`,
|
|||
|
|
labelField: 'alias',
|
|||
|
|
valueField: 'id',
|
|||
|
|
resultField: '',
|
|||
|
|
immediate: true,
|
|||
|
|
placeholder: '请选择类型',
|
|||
|
|
allowClear: true,
|
|||
|
|
unSelectable: {
|
|||
|
|
level: -1,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
if (menuKey === 'remove') {
|
|||
|
|
const thisObj = this;
|
|||
|
|
NsModal.confirm(
|
|||
|
|
{
|
|||
|
|
title: '警告',
|
|||
|
|
content: `确定要删除该项吗?\n删除后不可恢复,请谨慎操作!`,
|
|||
|
|
okText: '确认',
|
|||
|
|
cancelText: '取消',
|
|||
|
|
onOk() {
|
|||
|
|
http.delete(`/api/${APP}/objs/ResourceTypeClasses`, {id: data['id']}).then(() => {
|
|||
|
|
NsMessage.success('删除成功');
|
|||
|
|
if (data['code']) {
|
|||
|
|
thisObj.updateFormModel( data['code']);
|
|||
|
|
thisObj.setTableSession(thisObj.getPagination.current);
|
|||
|
|
}
|
|||
|
|
thisObj.getDataTree();
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
handleArgumentError(data) {
|
|||
|
|
const error = data?.response?.data;
|
|||
|
|
if(error && error['errType'] === 'ArgumentError') {
|
|||
|
|
this.typeFieldErrors = error['fieldErrors'];
|
|||
|
|
this.$refs.configRef.triggerSubmit();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
addType() {
|
|||
|
|
http.post(`/api/${APP}/objs/ResourceTypeClasses`, this.typeData).then((res) => {
|
|||
|
|
NsMessage.success('操作成功');
|
|||
|
|
this.getDataTree();
|
|||
|
|
this.typeData = {};
|
|||
|
|
this.modalVisible = false;
|
|||
|
|
}).catch((e)=>{
|
|||
|
|
this.handleArgumentError(e)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
editType() {
|
|||
|
|
if (Object.keys(this.typeData).includes('parentID')) {
|
|||
|
|
this.typeData['parentID'] = 0;
|
|||
|
|
}
|
|||
|
|
http.put(`/api/${APP}/objs/ResourceTypeClasses`, this.typeData).then((res) => {
|
|||
|
|
NsMessage.success('操作成功');
|
|||
|
|
this.getDataTree();
|
|||
|
|
this.typeData = {};
|
|||
|
|
this.modalVisible = false;
|
|||
|
|
}).catch((e)=>{
|
|||
|
|
this.handleArgumentError(e)
|
|||
|
|
})
|
|||
|
|
},
|
|||
|
|
removeType() {
|
|||
|
|
console.log(this.typeData)
|
|||
|
|
},
|
|||
|
|
handleOk() {
|
|||
|
|
if (['add'].includes(this.modalOperation.key)) {
|
|||
|
|
this.$refs.configRef.triggerSubmit().then(() => {
|
|||
|
|
this.addType();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (['edit'].includes(this.modalOperation.key)) {
|
|||
|
|
this.$refs.configRef.triggerSubmit().then(() => {
|
|||
|
|
this.editType();
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (['remove'].includes(this.modalOperation.key)) {
|
|||
|
|
this.removeType();
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
cancel() {
|
|||
|
|
this.typeData = {};
|
|||
|
|
this.modalVisible = false;
|
|||
|
|
this.typeFieldErrors = {};
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="less" scoped>
|
|||
|
|
.ns-table-main {
|
|||
|
|
flex-wrap: nowrap;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.ns-table-header) {
|
|||
|
|
min-width: unset;
|
|||
|
|
.ant-btn {
|
|||
|
|
margin-left: 0 !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.ns-table-wrapper {
|
|||
|
|
margin: 0 24px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.ns-table-search .ns-form-item label) {
|
|||
|
|
display: none;
|
|||
|
|
}
|
|||
|
|
.type-tree-wrapper {
|
|||
|
|
color: rgba(0, 0, 0, 0.65);
|
|||
|
|
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
|||
|
|
height: 800px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
|
|||
|
|
.tree-top-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
cursor: pointer;
|
|||
|
|
margin-bottom: 4px;
|
|||
|
|
.tree-node-content-wrapper {
|
|||
|
|
padding: 0 4px;
|
|||
|
|
line-height: 30px;
|
|||
|
|
&.selected {
|
|||
|
|
background-color: rgba(0, 172, 255, 0.1);
|
|||
|
|
color: #00acff;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
:deep(.ant-tree ) {
|
|||
|
|
color: rgba(0, 0, 0, 0.65);
|
|||
|
|
|
|||
|
|
.ant-tree-node-content-wrapper.ant-tree-node-selected {
|
|||
|
|
background-color: rgba(0, 172, 255, 0.1);
|
|||
|
|
color: #00acff
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 卡片列表
|
|||
|
|
.section0 {
|
|||
|
|
height: 100%;
|
|||
|
|
max-height: 600px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
.iconRed {
|
|||
|
|
color: #EB5757;
|
|||
|
|
}
|
|||
|
|
.iconGreen {
|
|||
|
|
color: #0DCB70;
|
|||
|
|
}
|
|||
|
|
.font-12 {
|
|||
|
|
font-size: 12px;
|
|||
|
|
}
|
|||
|
|
margin: 0 6px 0 24px;
|
|||
|
|
|
|||
|
|
.section-content {
|
|||
|
|
.item {
|
|||
|
|
width: 24%;
|
|||
|
|
//border: 1px solid #dedede;
|
|||
|
|
margin-right: 8px;
|
|||
|
|
display: inline-block;
|
|||
|
|
margin-bottom: 10px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
//transition: box-shadow 400ms;
|
|||
|
|
//box-shadow: none;s
|
|||
|
|
|
|||
|
|
transition-property: box-shadow, border;
|
|||
|
|
transition-duration: 400ms, 400ms;
|
|||
|
|
|
|||
|
|
|
|||
|
|
border: 1px solid #F1F3F5;
|
|||
|
|
box-shadow: 0 0 16px rgba(0, 0, 0, 0.09);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
|
|||
|
|
&:hover {
|
|||
|
|
//box-shadow: 0 2px 12px rgba(0, 172, 255, .7);
|
|||
|
|
border: 1px solid #6DCFFF;
|
|||
|
|
box-shadow: 0 0 16px rgba(0, 172, 255, 0.3);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.item-header {
|
|||
|
|
background-color: inherit;
|
|||
|
|
display: flex;
|
|||
|
|
height: auto;
|
|||
|
|
padding: 10px 14px 0 14px;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
//border-bottom: .05rem solid #dedede;
|
|||
|
|
.info-card-header {
|
|||
|
|
flex: 1;
|
|||
|
|
height: 32px;
|
|||
|
|
max-height: 32px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
.card-title {
|
|||
|
|
font-size: 16px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
overflow-x: hidden;
|
|||
|
|
overflow-y: hidden;
|
|||
|
|
text-overflow: ellipsis
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.info-item-block {
|
|||
|
|
height: 90px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
align-items: center;
|
|||
|
|
//border-bottom: .05rem solid #dedede;
|
|||
|
|
padding: 4px 14px;
|
|||
|
|
display: flex;
|
|||
|
|
|
|||
|
|
.card-icon {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
text-align: center;
|
|||
|
|
//width: 54px;
|
|||
|
|
max-height: 88px;
|
|||
|
|
img {
|
|||
|
|
width: 64px;
|
|||
|
|
max-height: 88px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.card-description-wrapper {
|
|||
|
|
display: grid;
|
|||
|
|
height: 88px;
|
|||
|
|
width: 100%;
|
|||
|
|
align-items: center;
|
|||
|
|
overflow: hidden;
|
|||
|
|
.card-description {
|
|||
|
|
max-height: 88px;
|
|||
|
|
padding-left: 6px;
|
|||
|
|
|
|||
|
|
span {
|
|||
|
|
line-height: 20px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.item-footer {
|
|||
|
|
padding: 10px 14px;
|
|||
|
|
background-color: #fafbfe;
|
|||
|
|
border-bottom-left-radius: 6px;
|
|||
|
|
border-bottom-right-radius: 6px;
|
|||
|
|
|
|||
|
|
.card-item-footer {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
width: 100%;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
|
|||
|
|
.card-footer-left {
|
|||
|
|
line-height: 22px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
.pagination {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: flex-end;
|
|||
|
|
padding: 6px;
|
|||
|
|
}
|
|||
|
|
// 无数据
|
|||
|
|
.ant-empty-description {
|
|||
|
|
color: #bfbfbf;
|
|||
|
|
}
|
|||
|
|
</style>
|