页组件
parent
9b8af50aa9
commit
9d4b5956ad
|
|
@ -209,6 +209,10 @@ const displayData = computed(() => {
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
.drop-table {
|
||||||
|
width 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.clear-btn {
|
.clear-btn {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.page-wrapper {
|
||||||
|
height 100%
|
||||||
|
width 100%;
|
||||||
|
overflow hidden
|
||||||
|
padding 0 5px 5px 5px
|
||||||
|
contain: layout paint;
|
||||||
|
transform: translateZ(0);
|
||||||
|
box-sizing border-box
|
||||||
|
display grid
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,405 @@
|
||||||
|
/* @jsxImportSource vue */
|
||||||
|
import APage from '@/components/a-page/APage.vue'
|
||||||
|
import styles from '@/components/a-page/a-table-page/a-table-page.module.styl'
|
||||||
|
import {
|
||||||
|
ElButton,
|
||||||
|
ElDropdown,
|
||||||
|
ElDropdownItem,
|
||||||
|
ElDropdownMenu,
|
||||||
|
ElForm,
|
||||||
|
ElLoading,
|
||||||
|
ElPagination,
|
||||||
|
ElPopconfirm,
|
||||||
|
ElScrollbar,
|
||||||
|
ElTable,
|
||||||
|
ElTableColumn,
|
||||||
|
ElTooltip,
|
||||||
|
type TableColumnCtx,
|
||||||
|
type TableProps,
|
||||||
|
} from 'element-plus'
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
withDirectives,
|
||||||
|
withModifiers,
|
||||||
|
} from 'vue'
|
||||||
|
import type { R } from '@/common/utils/http-util.ts'
|
||||||
|
import {
|
||||||
|
elIcons,
|
||||||
|
type ElIconType,
|
||||||
|
} from '@/common/element/element.ts'
|
||||||
|
import type { DefaultRow } from 'element-plus/es/components/table/src/table/defaults'
|
||||||
|
import type { FormProps } from 'element-plus/es/components/form/src/form'
|
||||||
|
import Utils, { type ResetAble } from '@/common/utils'
|
||||||
|
import Strings from '@/common/utils/strings.ts'
|
||||||
|
import { saveFile } from '@/common/app'
|
||||||
|
import Colls from '@/common/utils/colls.ts'
|
||||||
|
import type { SetupContext } from '@vue/runtime-core'
|
||||||
|
|
||||||
|
interface ColumnScopeType<T> {
|
||||||
|
row: T,
|
||||||
|
column: TableColumnCtx,
|
||||||
|
$index: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableActionType<T> {
|
||||||
|
icon?: ElIconType
|
||||||
|
type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger'
|
||||||
|
label?: string | ((data: ColumnScopeType<T>) => string)
|
||||||
|
tooltip?: string | ((data: ColumnScopeType<T>) => string)
|
||||||
|
loading?: boolean
|
||||||
|
textBtn?: boolean
|
||||||
|
show?: (data: ColumnScopeType<T>) => boolean
|
||||||
|
action: (data: ColumnScopeType<T>) => Promise<boolean> | void
|
||||||
|
confirm?: {
|
||||||
|
title: string | ((data: ColumnScopeType<T>) => string)
|
||||||
|
width?: string
|
||||||
|
confirmButtonText?: string
|
||||||
|
cancelButtonText?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ActionColumnType<T> {
|
||||||
|
width?: string
|
||||||
|
tableActions: TableActionType<T>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolType {
|
||||||
|
icon?: ElIconType
|
||||||
|
type?: 'text' | 'default' | 'primary' | 'success' | 'warning' | 'info' | 'danger'
|
||||||
|
label?: string
|
||||||
|
action: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolBarType {
|
||||||
|
leftTools: ToolType[]
|
||||||
|
rightTools: Required<Omit<ToolType, 'type' | 'label'>>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TablePropsType<T extends DefaultRow, F extends object> = Omit<TableProps<T>, 'data' | 'headerRowClassName' | 'cellClassName' | 'context'> & {
|
||||||
|
treeLoad?: (param: F, row: T, expanded: any, resolve?: (data: T[]) => void) => void
|
||||||
|
actionColumn: ActionColumnType<T>
|
||||||
|
}
|
||||||
|
export type FormPropsType<P, T> = {
|
||||||
|
paging?: (param: P) => Promise<R<G.PageResult<T>>>
|
||||||
|
export?: (param: P) => Promise<R<{ content: Blob, filename: string }>>
|
||||||
|
defaultData: P
|
||||||
|
highForm?: Partial<FormProps> & { colCount?: number, vgap?: string, hgap?: string }
|
||||||
|
simpleForm?: Partial<FormProps>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PageLayoutType {
|
||||||
|
/* columns: string[]
|
||||||
|
rows: string[] */
|
||||||
|
dataListHeight: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ATablePageType<P extends object, T extends DefaultRow> {
|
||||||
|
pageLayout: PageLayoutType
|
||||||
|
searchForm: FormPropsType<P, T>
|
||||||
|
toolBar: ToolBarType
|
||||||
|
table: TablePropsType<T, P>
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSearchForm<P, T>(searchForm: Partial<FormPropsType<P, T>> = {}) {
|
||||||
|
if (searchForm.defaultData == null) {
|
||||||
|
searchForm.defaultData = {} as P
|
||||||
|
}
|
||||||
|
return searchForm
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function buildPageLayout(pageLayout: Partial<PageLayoutType> = {}) {
|
||||||
|
/* if (pageLayout.columns == null) {
|
||||||
|
pageLayout.columns = [ '1fr' ]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageLayout.rows == null) {
|
||||||
|
pageLayout.rows = [ '1fr', '9fr' ]
|
||||||
|
} */
|
||||||
|
if (pageLayout.dataListHeight == null) {
|
||||||
|
pageLayout.dataListHeight = '9fr'
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTable<P extends object, T extends DefaultRow>(table: Partial<TablePropsType<T, P>> = {}) {
|
||||||
|
if (table.actionColumn == null) {
|
||||||
|
table.actionColumn = {
|
||||||
|
tableActions: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (table.actionColumn.tableActions == null) {
|
||||||
|
table.actionColumn.tableActions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
return table
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildToolBar(toolBar: Partial<ToolBarType> = {}) {
|
||||||
|
if (toolBar.leftTools == null) toolBar.leftTools = []
|
||||||
|
if (toolBar.rightTools == null) toolBar.rightTools = []
|
||||||
|
|
||||||
|
return toolBar
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PropsTplType<P extends G.PageParam, T extends DefaultRow> {
|
||||||
|
pageLayout?: Partial<PageLayoutType>
|
||||||
|
searchForm?: Partial<FormPropsType<P, T>>
|
||||||
|
toolBar?: Partial<ToolBarType>
|
||||||
|
table?: Partial<TablePropsType<T, P>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildProps<P extends G.PageParam, T extends DefaultRow>({pageLayout, searchForm, toolBar, table}: PropsTplType<P, T>) {
|
||||||
|
return reactive({
|
||||||
|
pageLayout: buildPageLayout(pageLayout),
|
||||||
|
searchForm: buildSearchForm(searchForm),
|
||||||
|
toolBar: buildToolBar(toolBar),
|
||||||
|
table: buildTable(table),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Exposed extends Record<string, any> {
|
||||||
|
doSearch: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const component = defineComponent(
|
||||||
|
<P extends G.PageParam, T extends DefaultRow>(props: ATablePageType<P, T>, {slots, expose}: SetupContext) => {
|
||||||
|
const formData = Utils.resetAble(reactive<P>({
|
||||||
|
...(props.searchForm.defaultData),
|
||||||
|
current: 1,
|
||||||
|
size: 20,
|
||||||
|
})) as ResetAble<P>
|
||||||
|
const tableData = Utils.resetAble(reactive<T[]>([])) as ResetAble<T[]>
|
||||||
|
|
||||||
|
const totalCount = ref(0)
|
||||||
|
const loading = ref<boolean>(false)
|
||||||
|
const showHighForm = ref<boolean>(true)
|
||||||
|
|
||||||
|
const doSearch = () => {
|
||||||
|
loading.value = true
|
||||||
|
if (props.searchForm.paging != null) {
|
||||||
|
props.searchForm.paging(formData.$clone() as P)
|
||||||
|
.then(res => {
|
||||||
|
totalCount.value = res.data?.total ?? 0
|
||||||
|
const records = res.data?.records ?? ([] as T[])
|
||||||
|
tableData.$reset(records)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expose({doSearch})
|
||||||
|
const showHighFormHandle = () => {
|
||||||
|
showHighForm.value = !showHighForm.value
|
||||||
|
formData.$reset()
|
||||||
|
}
|
||||||
|
const doExport = () => {
|
||||||
|
if (props?.searchForm?.export == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = true
|
||||||
|
saveFile(props?.searchForm?.export(formData.$clone() as P))
|
||||||
|
.finally(() => {
|
||||||
|
loading.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const rowAction = (data: ColumnScopeType<T>, action: TableActionType<T>) => {
|
||||||
|
if (action.loading != null) {
|
||||||
|
action.loading = true
|
||||||
|
}
|
||||||
|
const result = action.action(data)
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result
|
||||||
|
.then(reload => {
|
||||||
|
if (reload) {
|
||||||
|
doSearch()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (action.loading != null) {
|
||||||
|
action.loading = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doReset() {
|
||||||
|
formData.$reset()
|
||||||
|
doSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const highFormCssParam = computed(() => {
|
||||||
|
return {
|
||||||
|
'--col-count': props.searchForm.highForm?.colCount ?? 3,
|
||||||
|
'--vgap': props.searchForm.highForm?.vgap ?? '0',
|
||||||
|
'--hgap': props.searchForm.highForm?.hgap ?? '0',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const pageCssParam = computed(() => {
|
||||||
|
return {
|
||||||
|
'--data-list-height': props.pageLayout.dataListHeight,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const actionColumnBtnRender = (scope: ColumnScopeType<T>, tableAction: TableActionType<T>) => {
|
||||||
|
const Btn = <ElButton
|
||||||
|
icon={tableAction.icon == null ? undefined : elIcons[tableAction.icon]}
|
||||||
|
loading={tableAction.loading}
|
||||||
|
type={tableAction.type}
|
||||||
|
class={tableAction.textBtn ? 'text-btn' : 'icon-btn'}
|
||||||
|
text>
|
||||||
|
{tableAction.label}
|
||||||
|
</ElButton>
|
||||||
|
const tooltipTxt = tableAction.tooltip == null ? '' : (typeof tableAction.tooltip === 'function' ? tableAction.tooltip(scope) : tableAction.tooltip)
|
||||||
|
if (Strings.isBlank(tooltipTxt)) {
|
||||||
|
return (<ElTooltip content={tooltipTxt} placement="top">
|
||||||
|
{{
|
||||||
|
default: () => Btn,
|
||||||
|
}}
|
||||||
|
</ElTooltip>)
|
||||||
|
}
|
||||||
|
return Btn
|
||||||
|
}
|
||||||
|
const actionColumnRender = () => {
|
||||||
|
const actionColumn = props.table?.actionColumn
|
||||||
|
return Colls.isEmpty(actionColumn?.tableActions) ? <></>
|
||||||
|
: (<ElTableColumn width={actionColumn?.width ?? '180'} fixed="right" label="操作">
|
||||||
|
<div class="action-btn">
|
||||||
|
{{
|
||||||
|
default: (scope: ColumnScopeType<T>) => (actionColumn?.tableActions!
|
||||||
|
.filter(it => (it.show == null ? true : it.show(scope)))
|
||||||
|
.map((tableAction, i) => (tableAction.confirm != null ?
|
||||||
|
(<ElPopconfirm
|
||||||
|
key={'action-btn-' + i}
|
||||||
|
cancel-button-text={tableAction.confirm.cancelButtonText ?? '否'}
|
||||||
|
confirm-button-text={tableAction.confirm.confirmButtonText ?? '是'}
|
||||||
|
title={typeof tableAction.confirm.title === 'function' ? tableAction.confirm.title(scope) : tableAction.confirm.title}
|
||||||
|
width={tableAction.confirm.width ?? '180'}
|
||||||
|
cancel-button-type="primary"
|
||||||
|
confirm-button-type="danger"
|
||||||
|
onConfirm={() => rowAction(scope, tableAction)}>
|
||||||
|
{{
|
||||||
|
reference: () => (actionColumnBtnRender(scope, tableAction)),
|
||||||
|
}}
|
||||||
|
</ElPopconfirm>) : actionColumnBtnRender(scope, tableAction)))
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</ElTableColumn>)
|
||||||
|
}
|
||||||
|
onMounted(doSearch)
|
||||||
|
return () => (<APage class={[ styles.tablePage, showHighForm.value ? '' : styles.folder ]} style={pageCssParam.value}>
|
||||||
|
{/*@ts-ignore*/}
|
||||||
|
<div class={styles.searchFormWrapper} style={highFormCssParam.value}>
|
||||||
|
<ElScrollbar>
|
||||||
|
{/*@ts-ignore*/}
|
||||||
|
<ElForm class={styles.searchForm} onSubmit={withModifiers(doSearch, [ 'prevent' ])} labelWidth={props.searchForm.highForm?.labelWidth ?? '90px'}>
|
||||||
|
{
|
||||||
|
slots.highFormItem?.(formData)
|
||||||
|
}
|
||||||
|
<button style="display: none" type="submit"/>
|
||||||
|
</ElForm>
|
||||||
|
</ElScrollbar>
|
||||||
|
</div>
|
||||||
|
<div class={styles.dataList}>
|
||||||
|
<div class={styles.toolBar}>
|
||||||
|
<div class={styles.toolBarLeft}>
|
||||||
|
{
|
||||||
|
props.toolBar?.leftTools?.map((tool, i) => (
|
||||||
|
<ElButton key={'tool-bar-left-' + i}
|
||||||
|
icon={Strings.isBlank(tool.icon) ? undefined : elIcons[tool.icon!]}
|
||||||
|
type={tool.type ?? 'primary'}
|
||||||
|
onClick={tool.action}>
|
||||||
|
{tool.label}
|
||||||
|
</ElButton>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class={styles.toolBarRight}>
|
||||||
|
{/*@ts-ignore*/}
|
||||||
|
<ElForm onSubmit={withModifiers(doSearch, [ 'prevent' ])}>
|
||||||
|
{
|
||||||
|
slots.simpleFormItem?.(formData)
|
||||||
|
}
|
||||||
|
<button style="display: none" type="submit"/>
|
||||||
|
</ElForm>
|
||||||
|
<ElTooltip content="搜索" placement="top">
|
||||||
|
<ElDropdown split-button onClick={doSearch} onCommand={(command: string) => {
|
||||||
|
if (command === 'reset') {
|
||||||
|
doReset()
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{{
|
||||||
|
default: () => (<ElButton icon={elIcons.Search} loading={loading.value} type="default"/>),
|
||||||
|
dropdown: () => (<ElDropdownMenu>
|
||||||
|
<ElDropdownItem icon={elIcons.Refresh} command="reset">重置并刷新</ElDropdownItem>
|
||||||
|
</ElDropdownMenu>),
|
||||||
|
}}
|
||||||
|
</ElDropdown>
|
||||||
|
</ElTooltip>
|
||||||
|
{
|
||||||
|
props.toolBar?.rightTools?.map((tool, i) => (
|
||||||
|
<ElButton key={'tool-bar-right-' + i}
|
||||||
|
icon={elIcons[tool.icon]}
|
||||||
|
onClick={tool.action}/>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<ElTooltip content="导出" placement="top">
|
||||||
|
<ElButton icon={elIcons.Download} loading={loading.value} type="default" onClick={doExport}/>
|
||||||
|
</ElTooltip>
|
||||||
|
<ElTooltip content={showHighForm.value ? '关闭高级搜索' : '打开高级搜索'} placement="top">
|
||||||
|
<ElButton class={showHighForm.value ? styles.filterBtnActive : ''} icon={elIcons.Filter} type="default" onClick={showHighFormHandle}/>
|
||||||
|
</ElTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
withDirectives(<ElTable
|
||||||
|
data={tableData}
|
||||||
|
empty-text={props.table?.emptyText ?? '暂无数据'}
|
||||||
|
row-key={props.table?.rowKey ?? 'id'}
|
||||||
|
ref="dataTable"
|
||||||
|
lazy={props.table?.treeLoad == null ? undefined : true}
|
||||||
|
load={props.table?.treeLoad ? ((row, expanded, resolve) => props.table?.treeLoad!(formData as P, row, expanded, resolve)) : undefined}
|
||||||
|
onExpand-change={props.table?.treeLoad ? ((row: any, expandedRows: any) => props.table?.treeLoad!(formData as P, row, expandedRows, undefined)) : undefined}
|
||||||
|
cell-class-name="table-cell"
|
||||||
|
header-row-class-name="table-header"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
slots.columns?.()
|
||||||
|
}
|
||||||
|
{
|
||||||
|
actionColumnRender()
|
||||||
|
}
|
||||||
|
</ElTable>, [ [ ElLoading.directive, loading.value ] ])
|
||||||
|
}
|
||||||
|
<ElPagination
|
||||||
|
current-page={(formData as G.PageParam).current}
|
||||||
|
page-size={(formData as G.PageParam).size}
|
||||||
|
onUpdate:current-page={(val) => (formData as G.PageParam).current = val}
|
||||||
|
onUpdate:page-size={(val) => (formData as G.PageParam).size = val}
|
||||||
|
hide-on-single-page={false}
|
||||||
|
page-sizes={[ 10, 20, 50, 100, 500 ]}
|
||||||
|
teleported={false}
|
||||||
|
total={totalCount.value}
|
||||||
|
background={true}
|
||||||
|
layout="total, prev, pager, next, sizes, jumper"
|
||||||
|
onChange={doSearch}/>
|
||||||
|
{
|
||||||
|
slots?.default?.()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</APage>)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'ATablePage',
|
||||||
|
props: [ 'pageLayout', 'searchForm', 'toolBar', 'table' ],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ATablePageInstance extends InstanceType<typeof component>, Exposed {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default component
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
interface ATablePageInstance extends InstanceType<typeof ATablePage>, ATablePageExposed {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
.table-page {
|
||||||
|
grid-template-columns 1fr
|
||||||
|
grid-template-rows: minmax(0, 1fr) minmax(0, var(--data-list-height));
|
||||||
|
grid-auto-rows: minmax(0, auto);
|
||||||
|
gap 10px
|
||||||
|
|
||||||
|
&.folder {
|
||||||
|
grid-template-rows: 0 minmax(0, 3fr);
|
||||||
|
gap: 0;
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
padding 0
|
||||||
|
border-width 0
|
||||||
|
height 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form-wrapper {
|
||||||
|
border: 1px solid #EAEBF1;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: white;
|
||||||
|
height 100%
|
||||||
|
width 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
& {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
max-height 168px
|
||||||
|
width 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
display grid
|
||||||
|
grid-template-columns repeat(var(--col-count), 1fr)
|
||||||
|
row-gap var(--vgap);
|
||||||
|
column-gap var(--hgap);
|
||||||
|
|
||||||
|
:global(.el-form-item) {
|
||||||
|
margin 0
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-form-item).form-action-btn {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
|
||||||
|
.el-form-item__content {
|
||||||
|
justify-content end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-list {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display grid
|
||||||
|
grid-template-columns 1fr
|
||||||
|
grid-template-rows 50px minmax(0, 1fr) 50px
|
||||||
|
border: 1px solid #EAEBF1;
|
||||||
|
padding: 15px 20px 20px 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background-color: white;
|
||||||
|
height 100%
|
||||||
|
width 100%;
|
||||||
|
gap 20px
|
||||||
|
|
||||||
|
.tool-bar {
|
||||||
|
display flex
|
||||||
|
justify-content space-between
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.tool-bar-left {
|
||||||
|
flex 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-bar-right {
|
||||||
|
flex 1
|
||||||
|
display flex
|
||||||
|
gap 10px
|
||||||
|
justify-content end
|
||||||
|
|
||||||
|
:global(.el-form) {
|
||||||
|
display flex
|
||||||
|
gap 10px
|
||||||
|
flex-shrink 0
|
||||||
|
|
||||||
|
.el-form-item {
|
||||||
|
margin: 0;
|
||||||
|
flex 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-button--default) {
|
||||||
|
width 32px
|
||||||
|
color #4D5875
|
||||||
|
background-color #F1F4F4
|
||||||
|
border-color #F1F4F4
|
||||||
|
margin 0
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color var(--main-color)
|
||||||
|
background-color #E6EAEB;
|
||||||
|
border-color #E6EAEB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:global(.el-dropdown) {
|
||||||
|
flex-shrink 0
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-button--default).filter-btn-active {
|
||||||
|
color var(--main-color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-table) {
|
||||||
|
width 100%
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
:global(.table-header) {
|
||||||
|
color #454C59
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color #EDF1F7
|
||||||
|
font-weight 500
|
||||||
|
position relative
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display flex
|
||||||
|
gap 5px
|
||||||
|
align-items center
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child) > div::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 1px;
|
||||||
|
width: 1px;
|
||||||
|
background-color: #D3D7DE;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
content: "";
|
||||||
|
height 50%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.table-cell) {
|
||||||
|
color #2F3540
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width 100%
|
||||||
|
display flex
|
||||||
|
flex-wrap wrap
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
.text-btn {
|
||||||
|
margin 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn {
|
||||||
|
margin 0
|
||||||
|
padding: 8px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
//#ebf0ff
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-button--default).icon-btn {
|
||||||
|
color oklch(72% .19 231.6)
|
||||||
|
background-color oklch(0.96 0.03 224.26)
|
||||||
|
border-color oklch(0.96 0.03 224.26)
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-button--danger).icon-btn {
|
||||||
|
color oklch(73% .15 25.3)
|
||||||
|
background-color oklch(0.96 0.02 22.09)
|
||||||
|
border-color oklch(0.96 0.02 22.09)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-pagination) {
|
||||||
|
justify-content center
|
||||||
|
|
||||||
|
:global(.btn-next),
|
||||||
|
:global(.btn-prev),
|
||||||
|
:global(.el-pager) li {
|
||||||
|
border-radius: 6px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<script generic="F extends object,TT extends DefaultRow" lang="ts" setup>
|
<script generic="F extends object,TT extends DefaultRow" lang="ts" setup>
|
||||||
import { elIcons } from '@/common/element/element.ts'
|
import { elIcons } from '@/common/element/element.ts'
|
||||||
import Page from '@/components/page/Page.vue'
|
import APage from '@/components/a-page/APage.vue'
|
||||||
import Utils, { type ResetAble } from '@/common/utils'
|
import Utils, { type ResetAble } from '@/common/utils'
|
||||||
import Colls from '@/common/utils/colls.ts'
|
import Colls from '@/common/utils/colls.ts'
|
||||||
import Strings from '@/common/utils/strings.ts'
|
import Strings from '@/common/utils/strings.ts'
|
||||||
|
|
@ -153,7 +153,7 @@ onMounted(doSearch)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page class="form-page">
|
<APage class="form-page">
|
||||||
<div v-show="showSearchForm" class="search-form">
|
<div v-show="showSearchForm" class="search-form">
|
||||||
<ElScrollbar>
|
<ElScrollbar>
|
||||||
<ElForm :class="{'border-form':formStyle.border}" v-bind="searchFormProps" @submit.prevent="doSearch">
|
<ElForm :class="{'border-form':formStyle.border}" v-bind="searchFormProps" @submit.prevent="doSearch">
|
||||||
|
|
@ -334,7 +334,7 @@ onMounted(doSearch)
|
||||||
@change="doSearch"/>
|
@change="doSearch"/>
|
||||||
</div>
|
</div>
|
||||||
<slot/>
|
<slot/>
|
||||||
</Page>
|
</APage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
export {}
|
export {}
|
||||||
declare global {
|
declare global {
|
||||||
const EffectScope: typeof import('vue').EffectScope
|
const EffectScope: typeof import('vue').EffectScope
|
||||||
|
const ElLoading: typeof import('element-plus/es').ElLoading
|
||||||
const ElMessage: typeof import('element-plus/es').ElMessage
|
const ElMessage: typeof import('element-plus/es').ElMessage
|
||||||
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
||||||
const computed: typeof import('vue').computed
|
const computed: typeof import('vue').computed
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<FormPage
|
<ATablePage
|
||||||
ref="formPage"
|
ref="tablePage"
|
||||||
:action-column="actionColumn"
|
v-bind="tablePageProps">
|
||||||
:left-tools="leftTools"
|
<template #highFormItem="formData">
|
||||||
:default-search-form="{createTimes:[undefined,undefined]}"
|
|
||||||
:paging="paging">
|
|
||||||
<template #searchFormItem="{ searchForm }">
|
|
||||||
<ElFormItem label="产品分类">
|
<ElFormItem label="产品分类">
|
||||||
<GoodsCategoryDropTable v-model="searchForm.goodsCategoryId"/>
|
<GoodsCategoryDropTable v-model="formData.goodsCategoryId"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="产品编码">
|
<ElFormItem label="产品编码">
|
||||||
<ElInput v-model="searchForm.sn" placeholder="产品编码"/>
|
<ElInput v-model="formData.sn" placeholder="产品编码"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="产品名称">
|
<ElFormItem label="产品名称">
|
||||||
<ElInput v-model="searchForm.goodsName" placeholder="产品名称"/>
|
<ElInput v-model="formData.goodsName" placeholder="产品名称"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="创建时间">
|
<ElFormItem label="创建时间">
|
||||||
<ADtPicker v-model="searchForm.createTimes" :change-handler="research"/>
|
<ADtPicker v-model="formData.createTimes" :change-handler="research"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</template>
|
</template>
|
||||||
<template #simpleSearchFormItem="{ searchForm }">
|
<template #simpleFormItem="formData">
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElInput v-model="searchForm.sn" placeholder="产品编码"/>
|
<ElInput v-model="formData.sn" placeholder="产品编码"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElInput v-model="searchForm.goodsName" placeholder="产品名称"/>
|
<ElInput v-model="formData.goodsName" placeholder="产品名称"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</template>
|
</template>
|
||||||
<template #columns>
|
<template #columns>
|
||||||
|
|
@ -46,26 +43,55 @@
|
||||||
<ElTableColumn label="创建时间" prop="createTime" width="160"/>
|
<ElTableColumn label="创建时间" prop="createTime" width="160"/>
|
||||||
</template>
|
</template>
|
||||||
<GoodsForm ref="goodsForm" :research="research"/>
|
<GoodsForm ref="goodsForm" :research="research"/>
|
||||||
</FormPage>
|
</ATablePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import GoodsApi from '@/pages/gds/goods/goods-api.ts'
|
import GoodsApi from '@/pages/gds/goods/goods-api.ts'
|
||||||
import GoodsForm from '@/pages/gds/goods/GoodsForm.vue'
|
import GoodsForm from '@/pages/gds/goods/GoodsForm.vue'
|
||||||
import AppApi from '@/common/app/app-api.ts'
|
import AppApi from '@/common/app/app-api.ts'
|
||||||
import FormPage from '@/components/page/FormPage.vue'
|
|
||||||
import type {
|
|
||||||
ActionColumnType,
|
|
||||||
ToolType,
|
|
||||||
} from '@/components/page/a-page-type.ts'
|
|
||||||
import type { ComponentExposed } from 'vue-component-type-helpers'
|
|
||||||
import ADtPicker from '@/components/a-dt-picker/ADtPicker.vue'
|
import ADtPicker from '@/components/a-dt-picker/ADtPicker.vue'
|
||||||
import GoodsCategoryDropTable from '@/pages/gds/goods-category/GoodsCategoryDropTable.vue'
|
import GoodsCategoryDropTable from '@/pages/gds/goods-category/GoodsCategoryDropTable.vue'
|
||||||
|
import ATablePage, {
|
||||||
|
type ATablePageInstance,
|
||||||
|
buildProps,
|
||||||
|
} from '@/components/a-page/a-table-page/ATablePage.tsx'
|
||||||
|
|
||||||
const goodsFormIns = useTemplateRef<InstanceType<typeof GoodsForm>>('goodsForm')
|
const goodsFormIns = useTemplateRef<InstanceType<typeof GoodsForm>>('goodsForm')
|
||||||
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage')
|
|
||||||
|
|
||||||
const actionColumn = reactive<ActionColumnType<GoodsTypes.SearchGoodsResult>>({
|
const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
|
||||||
|
|
||||||
|
function research() {
|
||||||
|
tablePageIns.value?.doSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const tablePageProps = buildProps({
|
||||||
|
searchForm: {
|
||||||
|
defaultData: {createTimes: [ undefined, undefined ]},
|
||||||
|
highForm: {
|
||||||
|
colCount: 4,
|
||||||
|
},
|
||||||
|
paging(param: GoodsTypes.SearchGoodsParam) {
|
||||||
|
return GoodsApi.paging({
|
||||||
|
...param,
|
||||||
|
createTimeStart: param.createTimes[0],
|
||||||
|
createTimeEnd: param.createTimes[1],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolBar: {
|
||||||
|
leftTools: [
|
||||||
|
{
|
||||||
|
icon: 'Plus',
|
||||||
|
label: '新建',
|
||||||
|
action() {
|
||||||
|
goodsFormIns.value?.open()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
actionColumn: {
|
||||||
tableActions: [
|
tableActions: [
|
||||||
{
|
{
|
||||||
tooltip: '编辑',
|
tooltip: '编辑',
|
||||||
|
|
@ -91,28 +117,10 @@ const actionColumn = reactive<ActionColumnType<GoodsTypes.SearchGoodsResult>>({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as ActionColumnType<GoodsTypes.SearchGoodsResult>)
|
|
||||||
const leftTools: ToolType[] = [
|
|
||||||
{
|
|
||||||
icon: 'Plus',
|
|
||||||
label: '新建',
|
|
||||||
action() {
|
|
||||||
goodsFormIns.value?.open()
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
})
|
||||||
|
|
||||||
function research() {
|
|
||||||
formPageIns.value?.doSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
function paging(param: GoodsTypes.SearchGoodsParam) {
|
|
||||||
return GoodsApi.paging({
|
|
||||||
...param,
|
|
||||||
createTimeStart: param.createTimes[0],
|
|
||||||
createTimeEnd: param.createTimes[1],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableGoodsHandler(enable: boolean, id: string) {
|
function enableGoodsHandler(enable: boolean, id: string) {
|
||||||
GoodsApi.enable({enable, id})
|
GoodsApi.enable({enable, id})
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,46 @@
|
||||||
<template>
|
<template>
|
||||||
<FormPage
|
<ATablePage
|
||||||
ref="formPage"
|
ref="tablePage"
|
||||||
:action-column="actionColumn"
|
v-bind="tablePageProps">
|
||||||
:form-style="{
|
<template #highFormItem="formData">
|
||||||
border: false,
|
|
||||||
colCount: 3,
|
|
||||||
vgap: '20px',
|
|
||||||
hgap: '20px',
|
|
||||||
}"
|
|
||||||
:paging="paging">
|
|
||||||
<template #searchFormItem="{searchForm}">
|
|
||||||
<ElFormItem label="站点">
|
<ElFormItem label="站点">
|
||||||
<ElInput v-model="searchForm.stationId" placeholder="站点"/>
|
<ElInput v-model="formData.stationId" placeholder="站点"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="订单编号">
|
<ElFormItem label="订单编号">
|
||||||
<ElInput v-model="searchForm.sn" placeholder="订单编号"/>
|
<ElInput v-model="formData.sn" placeholder="订单编号"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="项目名称">
|
<ElFormItem label="项目名称">
|
||||||
<ElInput v-model="searchForm.projectName" placeholder="项目名称"/>
|
<ElInput v-model="formData.projectName" placeholder="项目名称"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="客户姓名">
|
<ElFormItem label="客户姓名">
|
||||||
<ElInput v-model="searchForm.contacts" placeholder="客户姓名"/>
|
<ElInput v-model="formData.contacts" placeholder="客户姓名"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="联系方式">
|
<ElFormItem label="联系方式">
|
||||||
<ElInput v-model="searchForm.phone" placeholder="联系方式"/>
|
<ElInput v-model="formData.phone" placeholder="联系方式"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
|
|
||||||
<ElFormItem label="订单类型">
|
<ElFormItem label="订单类型">
|
||||||
<ElInput v-model="searchForm.orderCategory" placeholder="订单类型"/>
|
<ElInput v-model="formData.orderCategory" placeholder="订单类型"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<!-- <ElFormItem label="订单状态">
|
<!-- <ElFormItem label="订单状态">
|
||||||
<ElInput v-model="searchForm.orderStatus" placeholder="订单状态"/>
|
<ElInput v-model="formData.orderStatus" placeholder="订单状态"/>
|
||||||
</ElFormItem> -->
|
</ElFormItem> -->
|
||||||
<ElFormItem label="运输企业">
|
<ElFormItem label="运输企业">
|
||||||
<ElInput v-model="searchForm.transOrgName" placeholder="运输企业"/>
|
<ElInput v-model="formData.transOrgName" placeholder="运输企业"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="下单时间">
|
<ElFormItem label="下单时间">
|
||||||
<ADtPicker v-model="searchForm.orderTimes" :change-handler="research"/>
|
<ADtPicker v-model="formData.orderTimes" :change-handler="research"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="完结时间">
|
<ElFormItem label="完结时间">
|
||||||
<ADtPicker v-model="searchForm.finishTimes" :change-handler="research"/>
|
<ADtPicker v-model="formData.finishTimes" :change-handler="research"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</template>
|
</template>
|
||||||
<template #simpleSearchFormItem="{searchForm}">
|
<template #simpleFormItem="formData">
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElInput v-model="searchForm.sn" placeholder="订单编号"/>
|
<ElInput v-model="formData.sn" placeholder="订单编号"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem>
|
<ElFormItem>
|
||||||
<ElInput v-model="searchForm.orderCategory" placeholder="订单类型"/>
|
<ElInput v-model="formData.orderCategory" placeholder="订单类型"/>
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
</template>
|
</template>
|
||||||
<template #columns>
|
<template #columns>
|
||||||
|
|
@ -86,53 +79,49 @@
|
||||||
<!-- <ElTableColumn label="客户备注" prop="customerMemo"/> -->
|
<!-- <ElTableColumn label="客户备注" prop="customerMemo"/> -->
|
||||||
</template>
|
</template>
|
||||||
<OrderForm ref="orderForm" :research="research"/>
|
<OrderForm ref="orderForm" :research="research"/>
|
||||||
</FormPage>
|
</ATablePage>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import OrderApi from '@/pages/order/order-api.ts'
|
import OrderApi from '@/pages/order/order-api.ts'
|
||||||
import OrderForm from '@/pages/order/book/BookForm.vue'
|
import OrderForm from '@/pages/order/book/BookForm.vue'
|
||||||
import FormPage from '@/components/page/FormPage.vue'
|
|
||||||
import ADtPicker from '@/components/a-dt-picker/ADtPicker.vue'
|
import ADtPicker from '@/components/a-dt-picker/ADtPicker.vue'
|
||||||
import type { ComponentExposed } from 'vue-component-type-helpers'
|
import ATablePage, {
|
||||||
import type { ActionColumnType } from '@/components/page/a-page-type.ts'
|
type ATablePageInstance,
|
||||||
|
buildProps,
|
||||||
|
} from '@/components/a-page/a-table-page/ATablePage.tsx'
|
||||||
|
|
||||||
|
const tablePageIns = useTemplateRef<ATablePageInstance>('tablePage')
|
||||||
|
|
||||||
const actionColumn = reactive<ActionColumnType<OrderTypes.SearchOrderResult>>({
|
function research() {
|
||||||
|
tablePageIns.value?.doSearch()
|
||||||
|
}
|
||||||
|
|
||||||
|
const tablePageProps = buildProps({
|
||||||
|
table: {
|
||||||
|
actionColumn: {
|
||||||
tableActions: [
|
tableActions: [
|
||||||
{
|
{
|
||||||
tooltip: '详情',
|
tooltip: '详情',
|
||||||
icon: 'Postcard',
|
icon: 'Postcard',
|
||||||
action({row}) {
|
action() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/* {
|
],
|
||||||
icon: 'Delete',
|
|
||||||
loading: false,
|
|
||||||
type: 'danger',
|
|
||||||
tooltip: '删除',
|
|
||||||
confirm: {
|
|
||||||
title: '是否删除当前数据',
|
|
||||||
},
|
},
|
||||||
action({row}) {
|
},
|
||||||
OrgApi.del([ row.id! ])
|
searchForm: {
|
||||||
.then(() => {
|
paging(param: OrderTypes.SearchOrderParam) {
|
||||||
ElMessage.success('删除成功')
|
return OrderApi.paging(param).then(res => {
|
||||||
return true
|
res.data.records = []
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
res.data.records.push({})
|
||||||
|
}
|
||||||
|
return res
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}, */
|
},
|
||||||
],
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const formPageIns = useTemplateRef<ComponentExposed<typeof FormPage>>('formPage')
|
|
||||||
|
|
||||||
function research() {
|
|
||||||
formPageIns.value?.doSearch()
|
|
||||||
}
|
|
||||||
|
|
||||||
function paging(param: OrderTypes.SearchOrderParam) {
|
|
||||||
return OrderApi.paging(param)
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<Page>
|
<APage>
|
||||||
<div class="dict-page">
|
<div class="dict-page">
|
||||||
<DictCategory @search-dict="searchDict"/>
|
<DictCategory @search-dict="searchDict"/>
|
||||||
<ElDivider direction="vertical"/>
|
<ElDivider direction="vertical"/>
|
||||||
<DictItem ref="dictItem"/>
|
<DictItem ref="dictItem"/>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</APage>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import Page from '@/components/page/Page.vue'
|
import APage from '@/components/a-page/APage.vue'
|
||||||
import DictCategory from '@/pages/sys/dict/DictCategory.vue'
|
import DictCategory from '@/pages/sys/dict/DictCategory.vue'
|
||||||
import DictItem from '@/pages/sys/dict/DictItem.vue'
|
import DictItem from '@/pages/sys/dict/DictItem.vue'
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue