This commit is contained in:
		
						commit
						2177aaf1e7
					
				
							
								
								
									
										17
									
								
								index.html
								
								
								
								
							
							
						
						
									
										17
									
								
								index.html
								
								
								
								
							| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<<<<<<< HEAD
 | 
			
		||||
<html lang="">
 | 
			
		||||
 | 
			
		||||
<head>
 | 
			
		||||
| 
						 | 
				
			
			@ -13,4 +14,18 @@
 | 
			
		|||
  <script type="module" src="/src/main.ts"></script>
 | 
			
		||||
</body>
 | 
			
		||||
 | 
			
		||||
</html>
 | 
			
		||||
</html>
 | 
			
		||||
=======
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <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>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body>
 | 
			
		||||
    <div id="app"></div>
 | 
			
		||||
    <script type="module" src="/src/main.ts"></script>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
>>>>>>> 8716e35d55875b7e8377cae748005c95a6fe60c2
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@
 | 
			
		|||
        "js-base64": "^3.7.7",
 | 
			
		||||
        "jsencrypt": "^3.3.2",
 | 
			
		||||
        "lodash-es": "^4.17.21",
 | 
			
		||||
<<<<<<< HEAD
 | 
			
		||||
        "naive-ui": "^2.41.0",
 | 
			
		||||
        "pinia": "^3.0.1",
 | 
			
		||||
        "pinia-plugin-persistedstate": "^4.2.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -25,6 +26,15 @@
 | 
			
		|||
        "unplugin-vue-components": "^28.4.1",
 | 
			
		||||
        "vue": "^3.5.13",
 | 
			
		||||
        "vue-router": "^4.5.0"
 | 
			
		||||
=======
 | 
			
		||||
        "nprogress": "^0.2.0",
 | 
			
		||||
        "pinia": "^2.1.6",
 | 
			
		||||
        "pinia-plugin-persistedstate": "^3.2.0",
 | 
			
		||||
        "terser": "^5.19.2",
 | 
			
		||||
        "vue": "^3.3.4",
 | 
			
		||||
        "vue-component-type-helpers": "^2.1.2",
 | 
			
		||||
        "vue-router": "4"
 | 
			
		||||
>>>>>>> 8716e35d55875b7e8377cae748005c95a6fe60c2
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@tsconfig/node22": "^22.0.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -4927,6 +4937,11 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "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.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								package.json
								
								
								
								
							
							
						
						
									
										10
									
								
								package.json
								
								
								
								
							| 
						 | 
				
			
			@ -20,6 +20,7 @@
 | 
			
		|||
    "js-base64": "^3.7.7",
 | 
			
		||||
    "jsencrypt": "^3.3.2",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
<<<<<<< HEAD
 | 
			
		||||
    "pinia": "^3.0.1",
 | 
			
		||||
    "pinia-plugin-persistedstate": "^4.2.0",
 | 
			
		||||
    "sass": "^1.85.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +30,15 @@
 | 
			
		|||
    "vue": "^3.5.13",
 | 
			
		||||
    "vue-router": "^4.5.0",
 | 
			
		||||
    "naive-ui": "^2.41.0"
 | 
			
		||||
=======
 | 
			
		||||
    "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"
 | 
			
		||||
>>>>>>> 8716e35d55875b7e8377cae748005c95a6fe60c2
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@tsconfig/node22": "^22.0.0",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <a-tabs
 | 
			
		||||
    type="editable-card"
 | 
			
		||||
    v-model:activeKey="activeKey"
 | 
			
		||||
    @edit="handleTabEdit"
 | 
			
		||||
    @tabClick="handleTabClick"
 | 
			
		||||
    hide-add
 | 
			
		||||
  >
 | 
			
		||||
    <a-tab-pane
 | 
			
		||||
      v-for="tab in tabsList"
 | 
			
		||||
      :key="tab.key"
 | 
			
		||||
      :tab="tab.title"
 | 
			
		||||
      :closable="tab.closable"
 | 
			
		||||
    >
 | 
			
		||||
    </a-tab-pane>
 | 
			
		||||
  </a-tabs>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import { ref, watch, onMounted } from "vue";
 | 
			
		||||
import { useRouter, useRoute } from "vue-router";
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
 | 
			
		||||
// 标签页列表
 | 
			
		||||
const tabsList = ref([
 | 
			
		||||
  {
 | 
			
		||||
    key: "/home",
 | 
			
		||||
    title: "首页",
 | 
			
		||||
    closable: false,
 | 
			
		||||
  },
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
// 当前激活的标签页
 | 
			
		||||
const activeKey = ref(route.path);
 | 
			
		||||
 | 
			
		||||
// 初始化处理
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  addTab(route);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 监听路由变化
 | 
			
		||||
watch(
 | 
			
		||||
  () => route.path,
 | 
			
		||||
  (newPath) => {
 | 
			
		||||
    activeKey.value = newPath;
 | 
			
		||||
    addTab(route);
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 添加标签页
 | 
			
		||||
const addTab = (route) => {
 | 
			
		||||
  const exists = tabsList.value.some((tab) => tab.key === route.path);
 | 
			
		||||
  if (!exists) {
 | 
			
		||||
    tabsList.value.push({
 | 
			
		||||
      key: route.path,
 | 
			
		||||
      title: route.name || "",
 | 
			
		||||
      closable: route.path !== "/home", //首页不能关闭
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 处理标签页点击
 | 
			
		||||
const handleTabClick = (key) => {
 | 
			
		||||
  router.push(key);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 处理标签页编辑(关闭)
 | 
			
		||||
const handleTabEdit = (targetKey, action) => {
 | 
			
		||||
  if (action === "remove") {
 | 
			
		||||
    removeTab(targetKey);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 移除标签页
 | 
			
		||||
const removeTab = (targetKey) => {
 | 
			
		||||
  if (targetKey === "/home") return; // 阻止关闭首页
 | 
			
		||||
 | 
			
		||||
  const tabs = tabsList.value;
 | 
			
		||||
  let newActiveKey = activeKey.value;
 | 
			
		||||
 | 
			
		||||
  if (targetKey === activeKey.value) {
 | 
			
		||||
    tabs.forEach((tab, index) => {
 | 
			
		||||
      if (tab.key === targetKey) {
 | 
			
		||||
        const nextTab = tabs[index + 1] || tabs[index - 1];
 | 
			
		||||
        newActiveKey = nextTab?.key || "/home";
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  tabsList.value = tabs.filter((tab) => tab.key !== targetKey);
 | 
			
		||||
  activeKey.value = newActiveKey;
 | 
			
		||||
  router.push(newActiveKey);
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
declare const __APP_ENV__: ImportMetaEnv;
 | 
			
		||||
 | 
			
		||||
interface Window {
 | 
			
		||||
  _AMapSecurityConfig: {
 | 
			
		||||
    securityJsCode: string;
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface tokenInfo {
 | 
			
		||||
  isLogin: boolean;
 | 
			
		||||
  loginDevice: string;
 | 
			
		||||
  loginId: string;
 | 
			
		||||
  loginType: string;
 | 
			
		||||
  sessionTimeout: string;
 | 
			
		||||
  tag: string;
 | 
			
		||||
  tokenActiveTimeout: string;
 | 
			
		||||
  tokenName: string;
 | 
			
		||||
  tokenSessionTimeout: string;
 | 
			
		||||
  tokenTimeout: string;
 | 
			
		||||
  tokenValue: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SelectNodeVo<T, E = Record<string, any>> {
 | 
			
		||||
  value: T;
 | 
			
		||||
  label: string;
 | 
			
		||||
  options?: SelectNodeVo<T>[];
 | 
			
		||||
  orderIndex?: number;
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  extData?: E;
 | 
			
		||||
}
 | 
			
		||||
interface RouterVo {
 | 
			
		||||
  /** id **/
 | 
			
		||||
  snowFlakeId: string;
 | 
			
		||||
  /** 父级id **/
 | 
			
		||||
  parentId: string;
 | 
			
		||||
  /** 子项 **/
 | 
			
		||||
  children: unsupported;
 | 
			
		||||
  /** 名称 **/
 | 
			
		||||
  name: string;
 | 
			
		||||
  /** 菜单类型 **/
 | 
			
		||||
  type: SelectNodeVo<"dir" | "menu" | "btn">;
 | 
			
		||||
  /** 路径 **/
 | 
			
		||||
  path: string;
 | 
			
		||||
  /** 重定向地址 **/
 | 
			
		||||
  redirect: string;
 | 
			
		||||
  /** 路由元数据 **/
 | 
			
		||||
  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
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -12,10 +12,21 @@ const router = createRouter({
 | 
			
		|||
  routes: [...constantRoutes, ...errorRouter],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
<<<<<<< HEAD
 | 
			
		||||
router.beforeEach(async (to, from, next) => {
 | 
			
		||||
  loadingBar.start();
 | 
			
		||||
  const permissionStore = usePermissionStore();
 | 
			
		||||
  //判断是不是访问登录页
 | 
			
		||||
=======
 | 
			
		||||
/**
 | 
			
		||||
 * 前置路由守卫
 | 
			
		||||
 */
 | 
			
		||||
router.beforeEach((to: any, from: any, next: any) => {
 | 
			
		||||
  //动态设置标题
 | 
			
		||||
  const title: string = "智慧食堂系统";
 | 
			
		||||
  document.title = to.meta.name ? `${to.meta.name} - ${title}` : title;
 | 
			
		||||
  //todo 鉴权操作...
 | 
			
		||||
>>>>>>> 8716e35d55875b7e8377cae748005c95a6fe60c2
 | 
			
		||||
  const userStore = useUserStore();
 | 
			
		||||
  // if (
 | 
			
		||||
  //   to.path.toLocaleLowerCase() === LOGIN_ROUTER.path &&
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,301 @@
 | 
			
		|||
<template>
 | 
			
		||||
 | 
			
		||||
  <div class="home" >
 | 
			
		||||
    <div style="width: 74%">
 | 
			
		||||
       <div class="homeItem">
 | 
			
		||||
         <blockquote  class="homeItemIndex">
 | 
			
		||||
           系统公告
 | 
			
		||||
           <span class="homeSpan">2024-11-25</span>
 | 
			
		||||
         </blockquote>
 | 
			
		||||
         <span>【系统菜单调整通知】</span>
 | 
			
		||||
         <span>为了更方便功能查找使用,从新编排了菜单位置,基础信息相关的页面都在顶部”基础信息“下</span>
 | 
			
		||||
       </div>
 | 
			
		||||
      <div class="home2Item">
 | 
			
		||||
        <blockquote class="homeItem2Index">
 | 
			
		||||
          <div class="header-wrapper">
 | 
			
		||||
            <span>公司</span>
 | 
			
		||||
            <div class="fold-button" @click="toggleFold">
 | 
			
		||||
              <span>{{ isFold ? '展开' : '收起' }}</span>
 | 
			
		||||
              <caret-right-outlined :style="{ transform: isFold ? 'rotate(90deg)' : 'rotate(265deg)' }" />
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </blockquote>
 | 
			
		||||
        <!-- 折叠内容区域 -->
 | 
			
		||||
        <div :style="{height:isFold?'420px':'600px'}">
 | 
			
		||||
          <div class="fold-content">
 | 
			
		||||
            <!-- 这里放具体内容 -->
 | 
			
		||||
            <div class="fold-content_item">
 | 
			
		||||
<!--              1-->
 | 
			
		||||
               <div class="fold-content_item_index">
 | 
			
		||||
                  <div class="fold-content_item_index_top">
 | 
			
		||||
                    <div class="content">某某央厨</div>
 | 
			
		||||
                    <span>屏幕</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="intermediate" v-for="item in List" :key="item.key">
 | 
			
		||||
                    <span style="width: 50px">{{item.name}}</span>
 | 
			
		||||
                    <span style="width: 50px">{{item.digital}}</span>
 | 
			
		||||
                    <span style="width: 50px">{{item.mechanism}}</span>
 | 
			
		||||
                    <span style="width: 50px">{{item.digitalKey}}</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <hr/>
 | 
			
		||||
                  <div style="display: flex;align-items: center;justify-content: space-between">
 | 
			
		||||
                     <div style="display: flex;">
 | 
			
		||||
                       <div class="risk">低风险</div>
 | 
			
		||||
                       <div style="font-weight: bold;color: #009688 !important;margin-left: 10px;line-height: 18px">100分</div>
 | 
			
		||||
                     </div>
 | 
			
		||||
                    <div style="display: flex">
 | 
			
		||||
                      <div>排名</div>
 | 
			
		||||
                      <div style="margin-left: 10px">1</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
               </div>
 | 
			
		||||
<!--           2-->
 | 
			
		||||
              <div class="fold-content_item_index">
 | 
			
		||||
                <div class="fold-content_item_index_top">
 | 
			
		||||
                  <div class="content">某某央厨</div>
 | 
			
		||||
                  <span>屏幕</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="intermediate" v-for="item in List" :key="item.key">
 | 
			
		||||
                  <span style="width: 50px">{{item.name}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digital}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.mechanism}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digitalKey}}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <hr/>
 | 
			
		||||
                <div style="display: flex;align-items: center;justify-content: space-between">
 | 
			
		||||
                  <div style="display: flex;">
 | 
			
		||||
                    <div class="risk">低风险</div>
 | 
			
		||||
                    <div style="font-weight: bold;color: #009688 !important;margin-left: 10px;line-height: 18px">100分</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style="display: flex">
 | 
			
		||||
                    <div>排名</div>
 | 
			
		||||
                    <div style="margin-left: 10px">1</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
<!--              3-->
 | 
			
		||||
              <div class="fold-content_item_index">
 | 
			
		||||
                <div class="fold-content_item_index_top">
 | 
			
		||||
                  <div class="content">某某央厨</div>
 | 
			
		||||
                  <span>屏幕</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="intermediate" v-for="item in List" :key="item.key">
 | 
			
		||||
                  <span style="width: 50px">{{item.name}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digital}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.mechanism}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digitalKey}}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <hr/>
 | 
			
		||||
                <div style="display: flex;align-items: center;justify-content: space-between">
 | 
			
		||||
                  <div style="display: flex;">
 | 
			
		||||
                    <div class="risk">低风险</div>
 | 
			
		||||
                    <div style="font-weight: bold;color: #009688 !important;margin-left: 10px;line-height: 18px">100分</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style="display: flex">
 | 
			
		||||
                    <div>排名</div>
 | 
			
		||||
                    <div style="margin-left: 10px">1</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
<!--              4-->
 | 
			
		||||
              <div class="fold-content_item_index">
 | 
			
		||||
                <div class="fold-content_item_index_top">
 | 
			
		||||
                  <div class="content">某某央厨</div>
 | 
			
		||||
                  <span>屏幕</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="intermediate" v-for="item in List" :key="item.key">
 | 
			
		||||
                  <span style="width: 50px">{{item.name}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digital}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.mechanism}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digitalKey}}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <hr/>
 | 
			
		||||
                <div style="display: flex;align-items: center;justify-content: space-between">
 | 
			
		||||
                  <div style="display: flex;">
 | 
			
		||||
                    <div class="risk">低风险</div>
 | 
			
		||||
                    <div style="font-weight: bold;color: #009688 !important;margin-left: 10px;line-height: 18px">100分</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style="display: flex">
 | 
			
		||||
                    <div>排名</div>
 | 
			
		||||
                    <div style="margin-left: 10px">1</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
<!--              5-->
 | 
			
		||||
              <div class="fold-content_item_index">
 | 
			
		||||
                <div class="fold-content_item_index_top">
 | 
			
		||||
                  <div class="content">某某央厨</div>
 | 
			
		||||
                  <span>屏幕</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="intermediate" v-for="item in List" :key="item.key">
 | 
			
		||||
                  <span style="width: 50px">{{item.name}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digital}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.mechanism}}</span>
 | 
			
		||||
                  <span style="width: 50px">{{item.digitalKey}}</span>
 | 
			
		||||
                </div>
 | 
			
		||||
                <hr/>
 | 
			
		||||
                <div style="display: flex;align-items: center;justify-content: space-between">
 | 
			
		||||
                  <div style="display: flex;">
 | 
			
		||||
                    <div class="risk">低风险</div>
 | 
			
		||||
                    <div style="font-weight: bold;color: #009688 !important;margin-left: 10px;line-height: 18px">100分</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div style="display: flex">
 | 
			
		||||
                    <div>排名</div>
 | 
			
		||||
                    <div style="margin-left: 10px">1</div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div style="width: 25%"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { CaretRightOutlined } from '@ant-design/icons-vue';
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {CollapseProps} from "ant-design-vue";
 | 
			
		||||
 | 
			
		||||
const isFold  = ref(true)
 | 
			
		||||
const toggleFold = ()=>{
 | 
			
		||||
  isFold.value = !isFold.value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const List = ref([
 | 
			
		||||
  {key:1,name:'环境监测',digital:'0 / 0', mechanism:'留样柜',digitalKey:'1 / 1'},
 | 
			
		||||
  {key:2,name:'晨检机',digital:'1 / 1',mechanism:'点评机',digitalKey:'0 / 0'},
 | 
			
		||||
  {key:3,name:'资质',digital:'19',mechanism:'健康证',digitalKey:'94'}
 | 
			
		||||
])
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.home{
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
 | 
			
		||||
  .homeItem{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background: #ffffff;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
    .homeItemIndex{
 | 
			
		||||
      margin-bottom: 10px;
 | 
			
		||||
      line-height: 1.6;
 | 
			
		||||
      border-left: 5px solid #5FB878;
 | 
			
		||||
      border-radius: 0 2px 2px 0;
 | 
			
		||||
      padding-left: 10px;
 | 
			
		||||
      .homeSpan{
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        padding: 0 6px;
 | 
			
		||||
        font-size: 12px;
 | 
			
		||||
        text-align: center;
 | 
			
		||||
        background-color: #FF5722;
 | 
			
		||||
        color: #fff;
 | 
			
		||||
        border-radius: 2px
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  .home2Item{
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    background: #ffffff;
 | 
			
		||||
    margin-bottom: 10px;
 | 
			
		||||
    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .05);
 | 
			
		||||
    padding: 10px 15px;
 | 
			
		||||
    .header-wrapper {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .fold-button {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      gap: 8px;
 | 
			
		||||
      color: #1890ff;
 | 
			
		||||
      transition: all 0.3s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .fold-button:hover {
 | 
			
		||||
      opacity: 0.8;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .fold-button i {
 | 
			
		||||
      transition: transform 0.3s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .fold-content {
 | 
			
		||||
      border-top: 1px solid #f0f0f0;
 | 
			
		||||
      .fold-content_item{
 | 
			
		||||
         margin-top: 15px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        justify-content: space-between;
 | 
			
		||||
        flex-wrap: wrap;
 | 
			
		||||
        .fold-content_item_index{
 | 
			
		||||
          padding: 10px;
 | 
			
		||||
          min-height: 90px;
 | 
			
		||||
          border-radius: 5px;
 | 
			
		||||
          border-color: #eee;
 | 
			
		||||
          border-width: 1px;
 | 
			
		||||
          border-style: solid;
 | 
			
		||||
          background-color: #fff;
 | 
			
		||||
          color: #666;
 | 
			
		||||
          box-shadow: 1px 1px 4px rgb(0 0 0 / 8%);
 | 
			
		||||
          width: 24%;
 | 
			
		||||
          margin-right: 5px;
 | 
			
		||||
          margin-bottom: 15px;
 | 
			
		||||
           .fold-content_item_index_top{
 | 
			
		||||
              display: flex;
 | 
			
		||||
              justify-content: space-between;
 | 
			
		||||
             align-items: center;
 | 
			
		||||
             .content{
 | 
			
		||||
               font-weight: bold;
 | 
			
		||||
               font-size: 16px !important;
 | 
			
		||||
             }
 | 
			
		||||
           }
 | 
			
		||||
          .intermediate{
 | 
			
		||||
             padding: 5px 7px;
 | 
			
		||||
             margin-top: 5px;
 | 
			
		||||
             font-size: 12px;
 | 
			
		||||
             display: flex;
 | 
			
		||||
             justify-content: space-between;
 | 
			
		||||
          }
 | 
			
		||||
          hr{
 | 
			
		||||
            line-height: 0;
 | 
			
		||||
            margin: 10px 0;
 | 
			
		||||
            padding: 0;
 | 
			
		||||
            border: none !important;
 | 
			
		||||
            border-bottom: 1px solid #eee !important;
 | 
			
		||||
            clear: both;
 | 
			
		||||
            background: 0 0
 | 
			
		||||
          }
 | 
			
		||||
          .risk{
 | 
			
		||||
            height: 18px;
 | 
			
		||||
            line-height: 18px;
 | 
			
		||||
            display: inline-block;
 | 
			
		||||
            padding: 0 6px;
 | 
			
		||||
            font-size: 12px;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            background-color: #FF5722;
 | 
			
		||||
            color: #fff;
 | 
			
		||||
            border-radius: 2px;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    .homeItem2Index{
 | 
			
		||||
      margin-bottom: 10px;
 | 
			
		||||
      border-left: 5px solid #5FB878;
 | 
			
		||||
      border-radius: 0 2px 2px 0;
 | 
			
		||||
      padding-left: 10px;
 | 
			
		||||
      height: 18px;
 | 
			
		||||
      line-height: 18px
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,178 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <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>
 | 
			
		||||
            </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="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;
 | 
			
		||||
  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>
 | 
			
		||||
.food {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .foodIndex {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .foodItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,180 @@
 | 
			
		|||
<template>
 | 
			
		||||
<<<<<<< HEAD:src/views/system/role/index.vue
 | 
			
		||||
  <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>
 | 
			
		||||
>>>>>>> 8716e35d55875b7e8377cae748005c95a6fe60c2:src/views/product/dishes/index.vue
 | 
			
		||||
 | 
			
		||||
    </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>
 | 
			
		||||
 | 
			
		||||
<<<<<<< HEAD:src/views/system/role/index.vue
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
</style>
 | 
			
		||||
=======
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.food {
 | 
			
		||||
  height: calc(100vh - 130px);
 | 
			
		||||
  // background: #fff;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .foodIndex {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .foodItem {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: calc(100vh - 100px);
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
>>>>>>> 8716e35d55875b7e8377cae748005c95a6fe60c2:src/views/product/dishes/index.vue
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue