增加导航
This commit is contained in:
		
							parent
							
								
									89c09e5a6b
								
							
						
					
					
						commit
						a793a95da0
					
				| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
    <meta charset="UTF-8" />
 | 
			
		||||
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 | 
			
		||||
    <title>管理后台</title>
 | 
			
		||||
    <title> 食堂系统管理</title>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@
 | 
			
		|||
        "pinia-plugin-persistedstate": "^3.2.0",
 | 
			
		||||
        "terser": "^5.19.2",
 | 
			
		||||
        "vue": "^3.3.4",
 | 
			
		||||
        "vue-component-type-helpers": "^2.1.2",
 | 
			
		||||
        "vue-router": "4"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
| 
						 | 
				
			
			@ -2692,6 +2693,11 @@
 | 
			
		|||
        "@vue/shared": "3.3.4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/vue-component-type-helpers": {
 | 
			
		||||
      "version": "2.2.10",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.10.tgz",
 | 
			
		||||
      "integrity": "sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/vue-router": {
 | 
			
		||||
      "version": "4.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.2.4.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,6 +22,7 @@
 | 
			
		|||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "pinia": "^2.1.6",
 | 
			
		||||
    "pinia-plugin-persistedstate": "^3.2.0",
 | 
			
		||||
    "vue-component-type-helpers": "^2.1.2",
 | 
			
		||||
    "terser": "^5.19.2",
 | 
			
		||||
    "vue": "^3.3.4",
 | 
			
		||||
    "vue-router": "4"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,213 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <a-form
 | 
			
		||||
      ref="formProMaxRef"
 | 
			
		||||
      v-bind="props"
 | 
			
		||||
      :model="modelValue"
 | 
			
		||||
  >
 | 
			
		||||
    <a-row :gutter="props.gutter">
 | 
			
		||||
      <a-col
 | 
			
		||||
          v-for="(item,field) in props.formItemOptions as FormProMaxItemOptions<T>"
 | 
			
		||||
          :key="field"
 | 
			
		||||
          v-bind="getResponsive(item)"
 | 
			
		||||
      >
 | 
			
		||||
        <a-form-item
 | 
			
		||||
            :name="field"
 | 
			
		||||
            v-bind="item"
 | 
			
		||||
            :label="undefined"
 | 
			
		||||
        >
 | 
			
		||||
          <template v-slot:label>
 | 
			
		||||
            {{ item.label }}
 | 
			
		||||
            <template v-if="item.remarkRender">
 | 
			
		||||
              <a-popover :title="item.label" :content="item.remarkRender()">
 | 
			
		||||
                <QuestionCircleOutlined class="margin-left-xs" style="color: red"/>
 | 
			
		||||
              </a-popover>
 | 
			
		||||
            </template>
 | 
			
		||||
          </template>
 | 
			
		||||
          <!--     自定义组件     -->
 | 
			
		||||
          <!--     ant design vue 组件      -->
 | 
			
		||||
          <a-input
 | 
			
		||||
              v-if="item.type==='input'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <a-input-password
 | 
			
		||||
              v-else-if="item.type==='inputPassword'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <a-input-number
 | 
			
		||||
              v-else-if="item.type==='inputNumber'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
          />
 | 
			
		||||
          <a-textarea
 | 
			
		||||
              v-else-if="item.type==='inputTextArea'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <a-radio-group
 | 
			
		||||
              v-else-if="item.type==='radioGroup'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :options="item.options"
 | 
			
		||||
          />
 | 
			
		||||
          <a-checkbox-group
 | 
			
		||||
              v-else-if="item.type==='checkboxGroup'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :options="item.options"
 | 
			
		||||
          />
 | 
			
		||||
          <a-select
 | 
			
		||||
              v-else-if="item.type==='select'"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
              :options="item.options"
 | 
			
		||||
          />
 | 
			
		||||
          <a-tree-select
 | 
			
		||||
              v-else-if="item.type==='treeSelect'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
              :tree-data="item.options"
 | 
			
		||||
          />
 | 
			
		||||
          <a-cascader
 | 
			
		||||
              v-else-if="item.type ==='cascader'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
              :options="item.options"
 | 
			
		||||
          />
 | 
			
		||||
          <AdministrativeDivisionsTree
 | 
			
		||||
              v-else-if="item.type ==='administrativeDivisionsTree'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
           />
 | 
			
		||||
          <a-range-picker
 | 
			
		||||
              v-else-if="item.type ==='rangePicker'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="item.componentsProps?.placeholder ?? ['开始日期', '结束日期']"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <a-date-picker
 | 
			
		||||
              v-else-if="item.type ==='datePicker'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="item.componentsProps?.placeholder ?? '请选择日期'"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <a-time-range-picker
 | 
			
		||||
              v-else-if="item.type ==='timeRangePicker'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="item.componentsProps?.placeholder ?? ['开始时间', '结束时间']"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <a-time-picker
 | 
			
		||||
              v-else-if="item.type ==='timePicker'"
 | 
			
		||||
              style="width: 100%"
 | 
			
		||||
              v-model:value="modelValue[field]"
 | 
			
		||||
              v-bind="item.componentsProps"
 | 
			
		||||
              :placeholder="getPlaceholder(item)"
 | 
			
		||||
              :allowClear="item.componentsProps?.allowClear ?? true"
 | 
			
		||||
          />
 | 
			
		||||
          <template v-else-if="item.type==='custom'">
 | 
			
		||||
            <component :is="item.customRender"/>
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-form-item>
 | 
			
		||||
      </a-col>
 | 
			
		||||
    </a-row>
 | 
			
		||||
    <slot name="formOperation"></slot>
 | 
			
		||||
  </a-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts" generic="T extends Record<string,any>">
 | 
			
		||||
import {FormInstance} from "ant-design-vue";
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {FormExpose} from "ant-design-vue/es/form/Form";
 | 
			
		||||
import {QuestionCircleOutlined} from '@ant-design/icons-vue'
 | 
			
		||||
import {FormProMaxItemOptions, FormProMaxItemProps, FormProMaxProps} from "@/types/components/form/index.ts";
 | 
			
		||||
import AdministrativeDivisionsTree from "@/components/tree/AdministrativeDivisionsTree.vue";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const modelValue = defineModel<T>('value', {
 | 
			
		||||
  default: {}
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<FormProMaxProps<T>>(), {
 | 
			
		||||
  grid: () => {
 | 
			
		||||
    return {
 | 
			
		||||
      span: 24
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  gutter: 10,
 | 
			
		||||
  labelCol: () => {
 | 
			
		||||
    return {
 | 
			
		||||
      style: {
 | 
			
		||||
        width: '120px'
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  wrapperCol: () => {
 | 
			
		||||
    return {
 | 
			
		||||
      span: 18
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  labelAlign: "left",
 | 
			
		||||
  colon: undefined,
 | 
			
		||||
  disabled: undefined,
 | 
			
		||||
  hideRequiredMark: undefined,
 | 
			
		||||
  labelWrap: undefined,
 | 
			
		||||
  scrollToFirstError: undefined,
 | 
			
		||||
  validateOnRuleChange: undefined
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const formProMaxRef = ref<FormInstance>(null!)
 | 
			
		||||
 | 
			
		||||
const getResponsive = (item: FormProMaxItemProps): Grid => {
 | 
			
		||||
  //span优先级高于响应式设置
 | 
			
		||||
  if (item.grid) return item.grid.span ? {span: item.grid.span} : {...item.grid};
 | 
			
		||||
  return {...props.grid}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//优先级: 组件本身=》formItem=》label
 | 
			
		||||
const getPlaceholder = (item: FormProMaxItemProps) => item.componentsProps?.placeholder ?? item.placeholder ?? (item.type.includes('input') ? `请输入${item.label}` : `请选择${item.label}`)
 | 
			
		||||
 | 
			
		||||
defineExpose<FormExpose>({
 | 
			
		||||
  validate: (nameList, options) => formProMaxRef.value?.validate(nameList, options),
 | 
			
		||||
  resetFields: (name) => formProMaxRef.value?.resetFields(name),
 | 
			
		||||
  clearValidate: () => formProMaxRef.value?.clearValidate(),
 | 
			
		||||
  getFieldsValue: (nameList) => formProMaxRef.value?.getFieldsValue(nameList),
 | 
			
		||||
  scrollToField: (name, options) => formProMaxRef.value?.scrollToField(name, options),
 | 
			
		||||
  validateFields: (nameList, options) => formProMaxRef.value?.validateFields(nameList, options)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,223 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="table-pro-content">
 | 
			
		||||
    <div class="card padding" v-if="props.searchFormOptions">
 | 
			
		||||
      <FormProMax
 | 
			
		||||
          ref="searchFormRef"
 | 
			
		||||
          :form-item-options="props.searchFormOptions"
 | 
			
		||||
          v-model:value="searchParams"
 | 
			
		||||
          v-bind="props.searchFormProps"
 | 
			
		||||
      >
 | 
			
		||||
        <template v-slot:formOperation>
 | 
			
		||||
          <a-space class="margin-right flex-end">
 | 
			
		||||
            <a-button type="primary" @click="search">
 | 
			
		||||
              <search-outlined/>
 | 
			
		||||
              搜索
 | 
			
		||||
            </a-button>
 | 
			
		||||
            <a-button danger @click="resetFormAndTable">
 | 
			
		||||
              <rollback-outlined/>
 | 
			
		||||
              重置
 | 
			
		||||
            </a-button>
 | 
			
		||||
          </a-space>
 | 
			
		||||
        </template>
 | 
			
		||||
      </FormProMax>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="card padding margin-top-xs">
 | 
			
		||||
      <div class="flex-justify-between">
 | 
			
		||||
        <slot name="tableHeader" :selectKeys="selectKeys" :selectRows="selectRows"></slot>
 | 
			
		||||
        <div></div>
 | 
			
		||||
        <slot name="tableHeaderRight" :selectKeys="selectKeys" :selectRows="selectRows"></slot>
 | 
			
		||||
        <a-space>
 | 
			
		||||
          <template v-if="!props.searchFormOptions">
 | 
			
		||||
            <a-tooltip>
 | 
			
		||||
              <template #title>刷新数据</template>
 | 
			
		||||
              <a-button shape="circle" @click="requestGetTableData">
 | 
			
		||||
                <ReloadOutlined/>
 | 
			
		||||
              </a-button>
 | 
			
		||||
            </a-tooltip>
 | 
			
		||||
          </template>
 | 
			
		||||
          <template v-if="props.isPrinter">
 | 
			
		||||
            <a-tooltip>
 | 
			
		||||
              <template #title>打印数据</template>
 | 
			
		||||
              <a-button shape="circle">
 | 
			
		||||
                <PrinterOutlined/>
 | 
			
		||||
              </a-button>
 | 
			
		||||
            </a-tooltip>
 | 
			
		||||
          </template>
 | 
			
		||||
        </a-space>
 | 
			
		||||
      </div>
 | 
			
		||||
      <a-table
 | 
			
		||||
          class="margin-top"
 | 
			
		||||
          v-bind="props"
 | 
			
		||||
          :columns="tableColumns"
 | 
			
		||||
          :row-selection="props.isSelection ? props.selectionProps ? props.selectionProps : defaultSelectProps : null"
 | 
			
		||||
          :data-source="dataSource"
 | 
			
		||||
          :loading="loading"
 | 
			
		||||
          :pagination="false"
 | 
			
		||||
      >
 | 
			
		||||
        <template v-for="(_,key) in slots" v-slot:[key]="scope">
 | 
			
		||||
          <slot v-if="!includes(['tableHeader','tableHeaderRight'],key)" :name="key" v-bind="scope"></slot>
 | 
			
		||||
        </template>
 | 
			
		||||
      </a-table>
 | 
			
		||||
      <a-pagination
 | 
			
		||||
          v-if="props.isPagination"
 | 
			
		||||
          class="flex-end margin-top margin-right"
 | 
			
		||||
          v-model:current="pageParams.current"
 | 
			
		||||
          v-model:page-size="pageParams.size"
 | 
			
		||||
          :total="pageParams.total"
 | 
			
		||||
          v-bind="props.paginationProps"
 | 
			
		||||
          @change="handleCurrentChange"
 | 
			
		||||
          @showSizeChange="handleSizeChange"
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script
 | 
			
		||||
    setup
 | 
			
		||||
    lang="ts"
 | 
			
		||||
    generic="T extends BaseTableRowRecord = {},P extends { [key: string]: any } ={}"
 | 
			
		||||
>
 | 
			
		||||
import FormProMax from "@/components/form/FormProMax.vue";
 | 
			
		||||
import {PrinterOutlined, ReloadOutlined, RollbackOutlined, SearchOutlined} from "@ant-design/icons-vue";
 | 
			
		||||
import {computed, onMounted, Ref, ref} from "vue";
 | 
			
		||||
import {FormInstance} from "ant-design-vue";
 | 
			
		||||
import useTableProMax from "@/hooks/useTableProMax";
 | 
			
		||||
import {includes, isEmpty} from "lodash-es";
 | 
			
		||||
import {BaseTableRowRecord, TableProMaxProps, TableProMaxRowSelect, TableProMaxSlots} from "@/types/components/table";
 | 
			
		||||
 | 
			
		||||
const selectKeys = ref<string[]>([])
 | 
			
		||||
const selectRows = ref<T[]>([]) as Ref<T[]>
 | 
			
		||||
 | 
			
		||||
const defaultSelectProps: TableProMaxRowSelect<T> = {
 | 
			
		||||
  type: "checkbox",
 | 
			
		||||
  selectedRowKeys: selectKeys as any,
 | 
			
		||||
  preserveSelectedRowKeys: true,
 | 
			
		||||
  onSelect: (record, selected) => {
 | 
			
		||||
    if (selected) {
 | 
			
		||||
      selectKeys.value.push(record[props.rowKey] as string)
 | 
			
		||||
      selectRows.value.push(record)
 | 
			
		||||
    } else {
 | 
			
		||||
      selectKeys.value.splice(selectKeys.value.findIndex(x => x === record[props.rowKey]));
 | 
			
		||||
      selectRows.value.splice(selectRows.value.findIndex(r => r[props.rowKey] === record[props.rowKey]))
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  onChange: (selectedRowKeys, selectedRows) => {
 | 
			
		||||
    selectKeys.value = selectedRowKeys as string[];
 | 
			
		||||
    selectRows.value = selectedRows;
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const clearSelect = () => {
 | 
			
		||||
  selectKeys.value = [];
 | 
			
		||||
  selectRows.value = [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const props = withDefaults(defineProps<TableProMaxProps<T, P>>(), {
 | 
			
		||||
  needIndex: true,
 | 
			
		||||
  searchFormProps: () => {
 | 
			
		||||
    return {
 | 
			
		||||
      grid: {
 | 
			
		||||
        xs: 24,
 | 
			
		||||
        sm: 12,
 | 
			
		||||
        md: 8,
 | 
			
		||||
        lg: 6,
 | 
			
		||||
        xl: 4
 | 
			
		||||
      },
 | 
			
		||||
      labelCol: undefined,
 | 
			
		||||
      wrapperCol: undefined
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  rowKey: 'snowFlakeId',
 | 
			
		||||
  requestAuto: true,
 | 
			
		||||
  isPagination: true,
 | 
			
		||||
  isSelection: false,
 | 
			
		||||
  paginationProps: () => {
 | 
			
		||||
    return {
 | 
			
		||||
      showTotal: (total) => `共 ${total} 条`,
 | 
			
		||||
      showSizeChanger: true,
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  bordered: true,
 | 
			
		||||
  showSorterTooltip: undefined,
 | 
			
		||||
  showHeader: undefined,
 | 
			
		||||
  expandFixed: undefined,
 | 
			
		||||
  expandRowByClick: undefined,
 | 
			
		||||
  defaultExpandAllRows: undefined,
 | 
			
		||||
  showExpandColumn: undefined,
 | 
			
		||||
  sticky: undefined
 | 
			
		||||
})
 | 
			
		||||
const slots = defineSlots<TableProMaxSlots<T>>()
 | 
			
		||||
 | 
			
		||||
const tableColumns = computed(() => {
 | 
			
		||||
  let cols = props.columns;
 | 
			
		||||
  if (!isEmpty(cols) && props.needIndex) {
 | 
			
		||||
    if (!(cols?.[0].dataIndex === 'index')) {
 | 
			
		||||
      cols?.unshift({
 | 
			
		||||
        dataIndex: 'index',
 | 
			
		||||
        width: 80,
 | 
			
		||||
        title: '序号',
 | 
			
		||||
        customRender: ({index}) => index + 1
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return cols;
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 表单实例
 | 
			
		||||
 */
 | 
			
		||||
const searchFormRef = ref<FormInstance>() as Ref<FormInstance>
 | 
			
		||||
/**
 | 
			
		||||
 * 查询参数
 | 
			
		||||
 */
 | 
			
		||||
const searchParams = ref<P | Record<string, any>>(props.defaultSearchParams || {}) as Ref<P>
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  loading,
 | 
			
		||||
  dataSource,
 | 
			
		||||
  pageParams,
 | 
			
		||||
  search,
 | 
			
		||||
  requestGetTableData,
 | 
			
		||||
  handleSizeChange,
 | 
			
		||||
  handleCurrentChange,
 | 
			
		||||
  resetState
 | 
			
		||||
} = useTableProMax(props.requestApi,
 | 
			
		||||
    searchFormRef,
 | 
			
		||||
    searchParams,
 | 
			
		||||
    props.isPagination,
 | 
			
		||||
    props.dataCallback,
 | 
			
		||||
    props.requestError
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
onMounted(() => props.requestAuto && requestGetTableData(true))
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 重置表单并查询
 | 
			
		||||
 */
 | 
			
		||||
const resetFormAndTable = () => {
 | 
			
		||||
  searchFormRef.value?.resetFields()
 | 
			
		||||
  requestGetTableData()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
defineExpose({
 | 
			
		||||
  selectKeys,
 | 
			
		||||
  selectRows,
 | 
			
		||||
  requestGetTableData,
 | 
			
		||||
  clearSelect,
 | 
			
		||||
  resetTable: () => {
 | 
			
		||||
    searchFormRef.value?.resetFields()
 | 
			
		||||
    resetState();
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.card {
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  background-color: #ffffff;
 | 
			
		||||
  border: 1px solid #e4e7ed;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +55,7 @@ const addTab = (route) => {
 | 
			
		|||
  if (!exists) {
 | 
			
		||||
    tabsList.value.push({
 | 
			
		||||
      key: route.path,
 | 
			
		||||
      title: route.name || "新tab",
 | 
			
		||||
      title: route.name || "",
 | 
			
		||||
      closable: route.path !== "/home", //首页不能关闭
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,3 +46,86 @@ interface RouterVo {
 | 
			
		|||
  /** 路由元数据 **/
 | 
			
		||||
  meta: RouterMetaVo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare const __APP_ENV: ImportMetaEnv;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 全局返回
 | 
			
		||||
 */
 | 
			
		||||
interface JsonResult<T> {
 | 
			
		||||
  code: number;
 | 
			
		||||
  message: string;
 | 
			
		||||
  data?: T;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 选择
 | 
			
		||||
 */
 | 
			
		||||
class SelectNodeVo<T, E = Record<string, any>> {
 | 
			
		||||
  value: T;
 | 
			
		||||
  label: string;
 | 
			
		||||
  options?: SelectNodeVo<T>[]
 | 
			
		||||
  orderIndex?: number;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  extData?: E
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 树
 | 
			
		||||
 */
 | 
			
		||||
class TreeNodeVo<T, E = Record<string, any>> {
 | 
			
		||||
  value: T;
 | 
			
		||||
  parentValue: T;
 | 
			
		||||
  label: string;
 | 
			
		||||
  orderIndex?: number;
 | 
			
		||||
  children?: TreeNodeVo<T>[]
 | 
			
		||||
  extData?: E;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 栅格布局
 | 
			
		||||
 */
 | 
			
		||||
declare interface Grid {
 | 
			
		||||
  //栅格占据的列数
 | 
			
		||||
  span?: number;
 | 
			
		||||
  //栅格左侧的间隔格数
 | 
			
		||||
  offset?: number;
 | 
			
		||||
  //栅格向右移动格数
 | 
			
		||||
  push?: number;
 | 
			
		||||
  //栅格向左移动格数
 | 
			
		||||
  pull?: number;
 | 
			
		||||
  //<768px 响应式栅格数或者栅格属性对象
 | 
			
		||||
  xs?: number;
 | 
			
		||||
  //≥768px 响应式栅格数或者栅格属性对象
 | 
			
		||||
  sm?: number;
 | 
			
		||||
  //≥992px 响应式栅格数或者栅格属性对象
 | 
			
		||||
  md?: number;
 | 
			
		||||
  //≥1200px 响应式栅格数或者栅格属性对象
 | 
			
		||||
  lg?: number;
 | 
			
		||||
  //≥1920px 响应式栅格数或者栅格属性对象
 | 
			
		||||
  xl?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface BaseEnum<T> {
 | 
			
		||||
  value: T;
 | 
			
		||||
  label: string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface TypeEnum<T> {
 | 
			
		||||
  value: string;
 | 
			
		||||
  label: string
 | 
			
		||||
}
 | 
			
		||||
interface dataStatus {
 | 
			
		||||
  account: string;
 | 
			
		||||
  password: string;
 | 
			
		||||
  remark: string;
 | 
			
		||||
  checkStatus: {
 | 
			
		||||
    extData: {
 | 
			
		||||
      color: string;
 | 
			
		||||
    };
 | 
			
		||||
    label: string;
 | 
			
		||||
    value: number;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,134 @@
 | 
			
		|||
import {ref, Ref} from "vue";
 | 
			
		||||
import {Page, PageParams, PageResult} from "@/types/hooks/useTableProMax";
 | 
			
		||||
import {FormInstance} from "ant-design-vue";
 | 
			
		||||
import {BaseTableRowRecord, RequestApiType} from "@/types/components/table";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 *
 | 
			
		||||
 * @param api 查询方法
 | 
			
		||||
 * @param searchFormRef 表单校验
 | 
			
		||||
 * @param searchParams 查询的参数
 | 
			
		||||
 * @param isPageTable 是否分页
 | 
			
		||||
 * @param dataCallBack 查询到数据后的回调
 | 
			
		||||
 * @param requestError 查询出错回调
 | 
			
		||||
 */
 | 
			
		||||
export default <T extends BaseTableRowRecord, P extends Record<string, any> | PageParams<P>>(api: RequestApiType<T, P>,
 | 
			
		||||
                                                                                             searchFormRef: Ref<FormInstance>,
 | 
			
		||||
                                                                                             searchParams: Ref<P>,
 | 
			
		||||
                                                                                             isPageTable: boolean = true,
 | 
			
		||||
                                                                                             dataCallBack?: (data: T[]) => T[],
 | 
			
		||||
                                                                                             requestError?: (errorMsg: any) => void) => {
 | 
			
		||||
 | 
			
		||||
    const dataSource = ref<T[]>([]) as Ref<T[]>;
 | 
			
		||||
    const loading = ref<boolean>(false);
 | 
			
		||||
    const pageParams = ref<Page>({
 | 
			
		||||
        current: 1,
 | 
			
		||||
        size: 10,
 | 
			
		||||
        total: 0
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取表格数据
 | 
			
		||||
     */
 | 
			
		||||
    const requestGetTableData = async (isInit: boolean = false) => {
 | 
			
		||||
        try {
 | 
			
		||||
            //校验表单
 | 
			
		||||
            !isInit && await searchFormRef.value?.validate();
 | 
			
		||||
            //组装参数
 | 
			
		||||
            let totalSearchParams;
 | 
			
		||||
            if (isPageTable) {
 | 
			
		||||
                totalSearchParams = {
 | 
			
		||||
                    params: searchParams.value,
 | 
			
		||||
                    page: {
 | 
			
		||||
                        current: pageParams.value.current,
 | 
			
		||||
                        size: pageParams.value.size
 | 
			
		||||
                    }
 | 
			
		||||
                } as PageParams<P>;
 | 
			
		||||
            } else {
 | 
			
		||||
                totalSearchParams = searchParams.value
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            loading.value = true;
 | 
			
		||||
 | 
			
		||||
            const resp = await api(totalSearchParams as P);
 | 
			
		||||
            let tableData: T[];
 | 
			
		||||
 | 
			
		||||
            if (isPageTable) {
 | 
			
		||||
                const {current, records, size, total} = resp.data as PageResult<T>;
 | 
			
		||||
                isPageTable && updatePageParams({
 | 
			
		||||
                    current: parseInt(current),
 | 
			
		||||
                    size: parseInt(size),
 | 
			
		||||
                    total: parseInt(total)
 | 
			
		||||
                });
 | 
			
		||||
                tableData = records;
 | 
			
		||||
            } else {
 | 
			
		||||
                tableData = resp.data as T[]
 | 
			
		||||
            }
 | 
			
		||||
            dataCallBack && (tableData = dataCallBack(tableData));
 | 
			
		||||
            dataSource.value = tableData;
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            requestError && requestError(error);
 | 
			
		||||
        } finally {
 | 
			
		||||
            loading.value = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新分页信息
 | 
			
		||||
     */
 | 
			
		||||
    const updatePageParams = (ps: Page) => Object.assign(pageParams.value, ps)
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 重置表格状态 包括 dataSource loading pageParams
 | 
			
		||||
     */
 | 
			
		||||
    const resetState = () => {
 | 
			
		||||
        dataSource.value = [];
 | 
			
		||||
        loading.value = false;
 | 
			
		||||
        pageParams.value = {
 | 
			
		||||
            current: 1,
 | 
			
		||||
            size: 10,
 | 
			
		||||
            total: 0
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 表格数据查询 与 requestGetTableData 区别是会将页面重置为1
 | 
			
		||||
     * 如果只做刷新数据请用 requestGetTableData
 | 
			
		||||
     */
 | 
			
		||||
    const search = async () => {
 | 
			
		||||
        pageParams.value.current = 1;
 | 
			
		||||
        await requestGetTableData();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 每页条数改变
 | 
			
		||||
     * @param _
 | 
			
		||||
     * @param {Number} size 页显示数量
 | 
			
		||||
     */
 | 
			
		||||
    const handleSizeChange = async (_: number, size: number) => {
 | 
			
		||||
        pageParams.value.current = 1;
 | 
			
		||||
        pageParams.value.size = size;
 | 
			
		||||
        await requestGetTableData();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @description 当前页改变
 | 
			
		||||
     * @param current 当前页
 | 
			
		||||
     */
 | 
			
		||||
    const handleCurrentChange = async (current: number) => {
 | 
			
		||||
        pageParams.value.current = current;
 | 
			
		||||
        await requestGetTableData();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
        dataSource,
 | 
			
		||||
        loading,
 | 
			
		||||
        pageParams,
 | 
			
		||||
        requestGetTableData,
 | 
			
		||||
        search,
 | 
			
		||||
        handleSizeChange,
 | 
			
		||||
        handleCurrentChange,
 | 
			
		||||
        resetState
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
import {
 | 
			
		||||
    FormProps,
 | 
			
		||||
    RangePicker,
 | 
			
		||||
    Input,
 | 
			
		||||
    InputNumber,
 | 
			
		||||
    Textarea,
 | 
			
		||||
    InputPassword,
 | 
			
		||||
    RadioGroup,
 | 
			
		||||
    Select,
 | 
			
		||||
    TreeSelect,
 | 
			
		||||
    Cascader,
 | 
			
		||||
    CheckboxGroup,
 | 
			
		||||
    DatePicker,
 | 
			
		||||
    FormItem, TimeRangePicker, TimePicker,
 | 
			
		||||
} from "ant-design-vue";
 | 
			
		||||
import {Ref, UnwrapRef, VNode} from "vue";
 | 
			
		||||
import {ComponentProps} from "vue-component-type-helpers";
 | 
			
		||||
 | 
			
		||||
type FormProMaxItemType =
 | 
			
		||||
    | 'custom'
 | 
			
		||||
    | 'input'
 | 
			
		||||
    | 'inputPassword'
 | 
			
		||||
    | 'inputNumber'
 | 
			
		||||
    | 'inputTextArea'
 | 
			
		||||
    | 'radioGroup'
 | 
			
		||||
    | 'select'
 | 
			
		||||
    | 'selectIcon'
 | 
			
		||||
    | 'selectUser'
 | 
			
		||||
    | 'treeSelect'
 | 
			
		||||
    | 'cascader'
 | 
			
		||||
    | 'checkboxGroup'
 | 
			
		||||
    | 'datePicker'
 | 
			
		||||
    | 'rangePicker'
 | 
			
		||||
    | 'timeRangePicker'
 | 
			
		||||
    | 'timePicker'
 | 
			
		||||
    | 'administrativeDivisionsTree';
 | 
			
		||||
 | 
			
		||||
interface FormProMaxItemCommonProps extends ComponentProps<typeof FormItem> {
 | 
			
		||||
    label?: string,
 | 
			
		||||
    grid?: Grid,
 | 
			
		||||
    placeholder?: string,
 | 
			
		||||
    remarkRender?: () => VNode | string,
 | 
			
		||||
    customRender?: () => VNode;
 | 
			
		||||
    options?: (SelectNodeVo<unknown> | TreeNodeVo<unknown>) [] | Ref<(SelectNodeVo<unknown> | TreeNodeVo<unknown>)[]>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface FormProMaxItemProps<T extends FormProMaxItemType = any, C = any> extends FormProMaxItemCommonProps {
 | 
			
		||||
    type: T
 | 
			
		||||
    componentsProps?: C
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type FormProMaxItemOptions<T> = {
 | 
			
		||||
    [key in keyof T | string]:
 | 
			
		||||
    FormProMaxItemProps<'custom', ComponentProps<Record<string, any>>>
 | 
			
		||||
    | FormProMaxItemProps<'input', ComponentProps<typeof Input>>
 | 
			
		||||
    | FormProMaxItemProps<'inputPassword', ComponentProps<typeof InputPassword>>
 | 
			
		||||
    | FormProMaxItemProps<'inputNumber', ComponentProps<typeof InputNumber>>
 | 
			
		||||
    | FormProMaxItemProps<'inputTextArea', ComponentProps<typeof Textarea>>
 | 
			
		||||
    | FormProMaxItemProps<'radioGroup', ComponentProps<typeof RadioGroup>>
 | 
			
		||||
    | FormProMaxItemProps<'select', ComponentProps<typeof Select>>
 | 
			
		||||
    | FormProMaxItemProps<'treeSelect', ComponentProps<typeof TreeSelect>>
 | 
			
		||||
    | FormProMaxItemProps<'cascader', ComponentProps<typeof Cascader>>
 | 
			
		||||
    | FormProMaxItemProps<'checkboxGroup', ComponentProps<typeof CheckboxGroup>>
 | 
			
		||||
    | FormProMaxItemProps<'datePicker', ComponentProps<typeof DatePicker>>
 | 
			
		||||
    | FormProMaxItemProps<'rangePicker', ComponentProps<typeof RangePicker>>
 | 
			
		||||
    | FormProMaxItemProps<'timeRangePicker', ComponentProps<typeof TimeRangePicker>>
 | 
			
		||||
    | FormProMaxItemProps<'timePicker', ComponentProps<typeof TimePicker>>
 | 
			
		||||
    | FormProMaxItemProps<'administrativeDivisionsTree', ComponentProps<Record<string, any>>>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface FormProMaxProps<T = {}> extends FormProps {
 | 
			
		||||
    grid?: Grid
 | 
			
		||||
    gutter?: number;
 | 
			
		||||
    formItemOptions?: FormProMaxItemOptions<T> | Ref<FormProMaxItemOptions<T>> | UnwrapRef<FormProMaxItemOptions<T>>
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
import {PaginationProps, Table, TableProps} from "ant-design-vue";
 | 
			
		||||
import {TableRowSelection} from "ant-design-vue/lib/table/interface";
 | 
			
		||||
import {Ref, UnwrapRef} from "vue";
 | 
			
		||||
import {ColumnType} from "ant-design-vue/es/table/interface";
 | 
			
		||||
import {ComponentSlots} from "vue-component-type-helpers";
 | 
			
		||||
import {FormProMaxItemOptions, FormProMaxProps} from "@/types/components/form";
 | 
			
		||||
import {PageParams, PageResult} from "@/types/hooks/useTableProMax";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type TableProMaxColumnType<T extends BaseTableRowRecord> = Omit<ColumnType<T>, 'dataIndex'> & {
 | 
			
		||||
    dataIndex: keyof T | string | string[] | number | number[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type TableProMaxProps<
 | 
			
		||||
    T extends BaseTableRowRecord = {},
 | 
			
		||||
    P extends { [key: string]: any } = {}
 | 
			
		||||
> = Partial<Omit<TableProps<T>, "dataSource" | 'pagination' | 'loading' | 'rowKey' | 'columns'>> & {
 | 
			
		||||
    rowKey?: keyof T,
 | 
			
		||||
    columns?: TableProMaxColumnType<T>[],
 | 
			
		||||
    searchFormProps?: Omit<FormProMaxProps<P>, 'formItems'>
 | 
			
		||||
    searchFormOptions?: FormProMaxItemOptions<P> | Ref<FormProMaxItemOptions<P>> | UnwrapRef<FormProMaxItemOptions<P>>,
 | 
			
		||||
    defaultSearchParams?: { [key in keyof P | string]: any };
 | 
			
		||||
    requestAuto?: boolean,
 | 
			
		||||
    requestApi: RequestApiType<T, P>,
 | 
			
		||||
    requestError?: (errorMsg: any) => void,
 | 
			
		||||
    dataCallback?: (data: T[]) => T[],
 | 
			
		||||
    isPagination?: boolean,
 | 
			
		||||
    paginationProps?: TableProMaxPaginationProps,
 | 
			
		||||
    isSelection?: boolean,
 | 
			
		||||
    selectionProps?: TableProMaxRowSelect<T>,
 | 
			
		||||
    isPrinter?: boolean,
 | 
			
		||||
    needIndex?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type TableProMaxSlots<T> = ComponentSlots<typeof Table> & {
 | 
			
		||||
    tableHeader: (scope: { selectKeys: string[], selectRows: T[] }) => any,
 | 
			
		||||
    tableHeaderRight: (scope: { selectKeys: string[], selectRows: T[] }) => any,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type RequestApiType<T extends BaseTableRowRecord, P extends {
 | 
			
		||||
    [key: string]: any
 | 
			
		||||
} = {}> = (params: P | PageParams<P>) => Promise<JsonResult<T[] | PageResult<T>>>;
 | 
			
		||||
 | 
			
		||||
export type TableProMaxPaginationProps = Partial<Omit<PaginationProps, "current" | "pageSize" | "total">>;
 | 
			
		||||
 | 
			
		||||
export type TableProMaxRowSelect<T extends BaseTableRowRecord> = TableRowSelection<T>;
 | 
			
		||||
 | 
			
		||||
export interface BaseTableRowRecord {
 | 
			
		||||
    snowFlakeId?: string;
 | 
			
		||||
    createUserName?: string;
 | 
			
		||||
    createTime?: Date | string;
 | 
			
		||||
    updateUserName?: string;
 | 
			
		||||
    updateTime?: Date | string
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
/**
 | 
			
		||||
 * 分页对象
 | 
			
		||||
 */
 | 
			
		||||
export interface Page {
 | 
			
		||||
    current: number,
 | 
			
		||||
    size: number,
 | 
			
		||||
    total: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 分页参数
 | 
			
		||||
 */
 | 
			
		||||
export interface PageParams<T extends Record<string, any> = {}> {
 | 
			
		||||
    params: T & { [key: string]: any },
 | 
			
		||||
    page: Omit<Page, 'total'>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 分页结果
 | 
			
		||||
 */
 | 
			
		||||
export interface PageResult<T> {
 | 
			
		||||
    current: string,
 | 
			
		||||
    records: T[],
 | 
			
		||||
    size: string,
 | 
			
		||||
    total: string
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +63,7 @@
 | 
			
		|||
</template>
 | 
			
		||||
<script lang="tsx" setup>
 | 
			
		||||
import { PlusOutlined, ZoomInOutlined } from "@ant-design/icons-vue";
 | 
			
		||||
import { computed, reactive } from "vue";
 | 
			
		||||
import {computed, reactive, ref} from "vue";
 | 
			
		||||
 | 
			
		||||
type Key = string | number;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -96,7 +96,7 @@ const columns = [
 | 
			
		|||
    customRender: () => {
 | 
			
		||||
      return (
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-button type="primary" danger class="margin-right-sm">
 | 
			
		||||
          <a-button type="primary" danger class="margin-right-sm" >
 | 
			
		||||
            删除
 | 
			
		||||
          </a-button>
 | 
			
		||||
          <a-button class="margin-right-sm">查看</a-button>
 | 
			
		||||
| 
						 | 
				
			
			@ -107,14 +107,15 @@ const columns = [
 | 
			
		|||
  },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const dataSource = ref([])
 | 
			
		||||
const data: DataType[] = [];
 | 
			
		||||
for (let i = 0; i < 100; i++) {
 | 
			
		||||
  data.push({
 | 
			
		||||
  dataSource.value =  data.push({
 | 
			
		||||
    key: i,
 | 
			
		||||
    name: `小明 ${i}`,
 | 
			
		||||
    age: 32 + i,
 | 
			
		||||
    address: `湖南省长沙市岳麓区. ${i}`,
 | 
			
		||||
  });
 | 
			
		||||
  })as any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const state = reactive<{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,12 +1,167 @@
 | 
			
		|||
<template>
 | 
			
		||||
   <div>菜品</div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
  <div class="food">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <div class="foodIndex">
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-form name="horizontal_login" layout="inline" autocomplete="off" ref="formRef" :model="formState">
 | 
			
		||||
            <a-form-item label="菜品名称" name="name">
 | 
			
		||||
              <a-input placeholder="请输入菜品名称"></a-input>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="菜品分类标签" name="type">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.type" placeholder="请选择类别">
 | 
			
		||||
                <a-select-option value="0">主食</a-select-option>
 | 
			
		||||
                <a-select-option value="1">汤类</a-select-option>
 | 
			
		||||
                <a-select-option value="3">素食</a-select-option>
 | 
			
		||||
                <a-select-option value="4">荤菜</a-select-option>
 | 
			
		||||
                <a-select-option value="5">低卡</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="价格" name="inventory">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.inventory" placeholder="请选择库存状态">
 | 
			
		||||
                <a-select-option value="0">原价</a-select-option>
 | 
			
		||||
                <a-select-option value="1">会员价</a-select-option>
 | 
			
		||||
                <a-select-option value="2">套餐优惠价</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="份量选项" name="shelfLife">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.shelfLife" placeholder="请选择保质期">
 | 
			
		||||
                <a-select-option value="0">小份</a-select-option>
 | 
			
		||||
                <a-select-option value="1">中份</a-select-option>
 | 
			
		||||
                <a-select-option value="2">大份</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
          </a-form>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-button type="primary">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <ZoomInOutlined/>
 | 
			
		||||
            </template>
 | 
			
		||||
            搜索
 | 
			
		||||
          </a-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="foodItem">
 | 
			
		||||
      <a-table :dataSource="dataSource" :columns="columns" :scroll="{ x: 1500, y: 430 }" />
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="tsx" setup>
 | 
			
		||||
import {ZoomInOutlined} from "@ant-design/icons-vue";
 | 
			
		||||
import {reactive, ref, UnwrapRef} from "vue";
 | 
			
		||||
import {Dayjs} from 'dayjs';
 | 
			
		||||
 | 
			
		||||
interface FormState {
 | 
			
		||||
  name: string;
 | 
			
		||||
  type: string | undefined;
 | 
			
		||||
  inventory:string | undefined;
 | 
			
		||||
  shelfLife:string | undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const formState: UnwrapRef<FormState> = reactive({
 | 
			
		||||
  name: '',
 | 
			
		||||
  type: undefined,
 | 
			
		||||
  inventory:undefined,
 | 
			
		||||
  shelfLife:undefined,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dataSource = ref( [
 | 
			
		||||
  {
 | 
			
		||||
    key: '1',
 | 
			
		||||
    image:'',
 | 
			
		||||
    name: '辣椒炒肉',
 | 
			
		||||
    age: '素食',
 | 
			
		||||
    inventory:'16',
 | 
			
		||||
    shelfLife:'小份'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '2',
 | 
			
		||||
    image:'',
 | 
			
		||||
    name: '番茄炒鸡蛋',
 | 
			
		||||
    age:'素食',
 | 
			
		||||
    inventory:'10',
 | 
			
		||||
    shelfLife:'中份'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '3',
 | 
			
		||||
    image:'',
 | 
			
		||||
    name: '番茄牛腩',
 | 
			
		||||
    age:'荤菜',
 | 
			
		||||
    inventory:'28',
 | 
			
		||||
    shelfLife:'大份'
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
const columns =  [
 | 
			
		||||
  {
 | 
			
		||||
    title: '菜品图片',
 | 
			
		||||
    dataIndex: 'image',
 | 
			
		||||
    key: 'image',
 | 
			
		||||
    customRender:(value:any)=>{
 | 
			
		||||
       return <a-avatar src="https://www.antdv.com/assets/logo.1ef800a8.svg" />
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '菜品名称',
 | 
			
		||||
    dataIndex: 'name',
 | 
			
		||||
    key: 'name',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '类别',
 | 
			
		||||
    dataIndex: 'age',
 | 
			
		||||
    key: 'age',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '价格',
 | 
			
		||||
    dataIndex: 'inventory',
 | 
			
		||||
    key: 'inventory',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '份量选项',
 | 
			
		||||
    dataIndex: 'shelfLife',
 | 
			
		||||
    key: 'shelfLife',
 | 
			
		||||
    customRender: (text: any) => {
 | 
			
		||||
      return  <a-tag >{text.text}</a-tag>
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: " 操作",
 | 
			
		||||
    dataIndex: "operation",
 | 
			
		||||
    customRender:()=>{
 | 
			
		||||
      return (
 | 
			
		||||
          <div>
 | 
			
		||||
            <a-button type="primary" danger className="margin-right-sm" >
 | 
			
		||||
              删除
 | 
			
		||||
            </a-button>
 | 
			
		||||
            <a-button className="margin-right-sm">查看</a-button>
 | 
			
		||||
            <a-button type="primary">编辑</a-button>
 | 
			
		||||
          </div>
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.food {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .foodIndex {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
  .foodItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,22 +1,174 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="attr">
 | 
			
		||||
  <div class="food">
 | 
			
		||||
    <a-card :bordered="false" style="width: 100%">
 | 
			
		||||
      <p>属性管理</p>
 | 
			
		||||
      <div class="foodIndex">
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-form name="horizontal_login" layout="inline" autocomplete="off" ref="formRef" :model="formState">
 | 
			
		||||
            <a-form-item label="食材名称" name="name">
 | 
			
		||||
              <a-input placeholder="请输入食材名称"></a-input>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="类别" name="type">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.type" placeholder="请选择类别">
 | 
			
		||||
                <a-select-option value="0">肉类</a-select-option>
 | 
			
		||||
                <a-select-option value="1">蔬菜</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="库存状态" name="inventory">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.inventory" placeholder="请选择库存状态">
 | 
			
		||||
                <a-select-option value="0">充足</a-select-option>
 | 
			
		||||
                <a-select-option value="1">不足</a-select-option>
 | 
			
		||||
                <a-select-option value="2">急需补货</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="保质期" name="shelfLife">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.shelfLife" placeholder="请选择保质期">
 | 
			
		||||
                <a-select-option value="0">全部</a-select-option>
 | 
			
		||||
                <a-select-option value="1">3天内过期</a-select-option>
 | 
			
		||||
                <a-select-option value="2">7天内过期</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
            <a-form-item label="存储位置" name="storageLocation">
 | 
			
		||||
              <a-select style="width: 180px" v-model:value="formState.storageLocation" placeholder="请选择存储位置">
 | 
			
		||||
                <a-select-option value="0">冷藏</a-select-option>
 | 
			
		||||
                <a-select-option value="1">冷冻</a-select-option>
 | 
			
		||||
                <a-select-option value="2">常温仓库</a-select-option>
 | 
			
		||||
              </a-select>
 | 
			
		||||
            </a-form-item>
 | 
			
		||||
          </a-form>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <a-button type="primary">
 | 
			
		||||
            <template #icon>
 | 
			
		||||
              <ZoomInOutlined/>
 | 
			
		||||
            </template>
 | 
			
		||||
            搜索
 | 
			
		||||
          </a-button
 | 
			
		||||
          >
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
    </a-card>
 | 
			
		||||
 | 
			
		||||
    <a-card :bordered="false" class="attrItem">
 | 
			
		||||
      <p>我是表格页面</p>
 | 
			
		||||
    <a-card :bordered="false" class="foodItem">
 | 
			
		||||
      <a-table :dataSource="dataSource" :columns="columns" :scroll="{ x: 1500, y: 430 }" />
 | 
			
		||||
    </a-card>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script lang="ts" setup></script>
 | 
			
		||||
<script lang="tsx" setup>
 | 
			
		||||
import {ZoomInOutlined} from "@ant-design/icons-vue";
 | 
			
		||||
import {reactive, ref, UnwrapRef} from "vue";
 | 
			
		||||
import {Dayjs} from 'dayjs';
 | 
			
		||||
 | 
			
		||||
interface FormState {
 | 
			
		||||
  name: string;
 | 
			
		||||
  type: string | undefined;
 | 
			
		||||
  inventory:string | undefined;
 | 
			
		||||
  shelfLife:string | undefined;
 | 
			
		||||
  storageLocation:string | undefined;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const formState: UnwrapRef<FormState> = reactive({
 | 
			
		||||
  name: '',
 | 
			
		||||
  type: undefined,
 | 
			
		||||
  inventory:undefined,
 | 
			
		||||
  shelfLife:undefined,
 | 
			
		||||
  storageLocation:undefined
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const dataSource = ref( [
 | 
			
		||||
  {
 | 
			
		||||
    key: '1',
 | 
			
		||||
    name: '牛肉',
 | 
			
		||||
    age: '肉类',
 | 
			
		||||
    address: '200kg',
 | 
			
		||||
    time:'2025-4-25 14:35:25',
 | 
			
		||||
    inventory:'50kg',
 | 
			
		||||
    shelfLife:'3天内过期'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '2',
 | 
			
		||||
    name: '猪肉',
 | 
			
		||||
    age:'肉类',
 | 
			
		||||
    address: '400kg',
 | 
			
		||||
    time:'2025-4-25 14:35:25',
 | 
			
		||||
    inventory:'50kg',
 | 
			
		||||
    shelfLife:'正常'
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: '3',
 | 
			
		||||
    name: '胡萝卜',
 | 
			
		||||
    age:'蔬菜',
 | 
			
		||||
    address: '100kg',
 | 
			
		||||
    time:'2025-4-25 14:35:25',
 | 
			
		||||
    inventory:'20kg',
 | 
			
		||||
    shelfLife:'7天内过期'
 | 
			
		||||
  }
 | 
			
		||||
])
 | 
			
		||||
const columns =  [
 | 
			
		||||
  {
 | 
			
		||||
    title: '食品名称',
 | 
			
		||||
    dataIndex: 'name',
 | 
			
		||||
    key: 'name',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '类别',
 | 
			
		||||
    dataIndex: 'age',
 | 
			
		||||
    key: 'age',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '当前库存量',
 | 
			
		||||
    dataIndex: 'address',
 | 
			
		||||
    key: 'address',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '入库时间',
 | 
			
		||||
    dataIndex: 'time',
 | 
			
		||||
    key: 'time',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '最低库存阈值',
 | 
			
		||||
    dataIndex: 'inventory',
 | 
			
		||||
    key: 'inventory',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: '保质期',
 | 
			
		||||
    dataIndex: 'shelfLife',
 | 
			
		||||
    key: 'shelfLife',
 | 
			
		||||
    customRender: (text: any) => {
 | 
			
		||||
      return  <a-tag color={text.text === '3天内过期'?'orange':text.text === '7天内过期'?'red':'green'}>{text.text}</a-tag>
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: " 操作",
 | 
			
		||||
    dataIndex: "operation",
 | 
			
		||||
    customRender:()=>{
 | 
			
		||||
       return (
 | 
			
		||||
           <div>
 | 
			
		||||
             <a-button type="primary" danger className="margin-right-sm" >
 | 
			
		||||
               删除
 | 
			
		||||
             </a-button>
 | 
			
		||||
             <a-button className="margin-right-sm">查看</a-button>
 | 
			
		||||
             <a-button type="primary">编辑</a-button>
 | 
			
		||||
           </div>
 | 
			
		||||
       )
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.attr {
 | 
			
		||||
.food {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  .attrItem {
 | 
			
		||||
 | 
			
		||||
  .foodIndex {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .foodItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1361,6 +1361,11 @@ vite@^4.0.0, vite@^4.4.5:
 | 
			
		|||
  optionalDependencies:
 | 
			
		||||
    fsevents "~2.3.2"
 | 
			
		||||
 | 
			
		||||
vue-component-type-helpers@^2.1.2:
 | 
			
		||||
  version "2.2.10"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.10.tgz"
 | 
			
		||||
  integrity sha512-iDUO7uQK+Sab2tYuiP9D1oLujCWlhHELHMgV/cB13cuGbG4qwkLHvtfWb6FzvxrIOPDnU0oHsz2MlQjhYDeaHA==
 | 
			
		||||
 | 
			
		||||
vue-demi@>=0.14.5:
 | 
			
		||||
  version "0.14.5"
 | 
			
		||||
  resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.5.tgz"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue