新架构

This commit is contained in:
TimSpan 2025-04-28 14:15:37 +08:00
parent 2177aaf1e7
commit 4ca7e4d8ed
16 changed files with 2 additions and 3092 deletions

View File

@ -1,5 +1,4 @@
<!DOCTYPE html>
<<<<<<< HEAD
<html lang="">
<head>
@ -14,18 +13,4 @@
<script type="module" src="/src/main.ts"></script>
</body>
</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
</html>

15
package-lock.json generated
View File

@ -16,7 +16,6 @@
"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",
@ -26,15 +25,6 @@
"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",
@ -4937,11 +4927,6 @@
}
}
},
"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",

View File

@ -20,7 +20,6 @@
"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",
@ -30,15 +29,6 @@
"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",

View File

@ -1,213 +0,0 @@
<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>

View File

@ -1,223 +0,0 @@
<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>

View File

@ -1,96 +0,0 @@
<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>

131
src/global.d.ts vendored
View File

@ -1,131 +0,0 @@
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;
};
}

View File

@ -1,134 +0,0 @@
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
};
}

View File

@ -12,21 +12,10 @@ 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 &&

View File

@ -1,75 +0,0 @@
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>>
}

View File

@ -1,55 +0,0 @@
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
}

View File

@ -1,26 +0,0 @@
/**
*
*/
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
}

View File

@ -1,301 +0,0 @@
<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>

View File

@ -1,178 +0,0 @@
<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>

View File

@ -1,180 +1,11 @@
<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 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
</style>

1438
yarn.lock

File diff suppressed because it is too large Load Diff