# training-project **Repository Path**: youarefortunate/training-project ## Basic Information - **Project Name**: training-project - **Description**: SpringBoot+Vue3+TS+Uniapp - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2024-09-10 - **Last Updated**: 2026-01-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 一.项目部署 关于本项目前后端部署教程,请参阅 [项目部署](./training-deploy/README.md)。 ## 二.项目效果演示 ### ①新增套餐效果预览 1. 选择套餐所包含的菜品动画效果 ![](./preview/%E6%96%B0%E5%A2%9E%E8%8F%9C%E5%93%81%E9%A2%84%E8%A7%88.gif) 2. 移除套餐所选菜品 ![](./preview/移除所选套餐菜品.gif) 3. 移动端适配效果 ![](./preview/套餐移动端适配效果.gif) ### ②可视化图表+自适应 可视化图表+自适应指令,实现移动端适配效果 ![](preview/%E5%8F%AF%E8%A7%86%E5%8C%96%E5%9B%BE%E8%A1%A8%E8%87%AA%E9%80%82%E5%BA%94.gif) ### ③小程序部分页面截图 | image-20250709020718507 | image-20250709020636813 | | :----------------------------------------------------------: | :----------------------------------------------------------: | | image-20250709020820242 | image-20250709020845320 | | image-20250709021410502 | image-20250709021303096 | | image-20250709021450737 | image-20250709021530276 | ## 三.通用组件+通用hooks+通用指令 ### ①通用组件(部分,具备类型提示功能,包括ElementPlus组件类型) 1. 表单组件:[点击跳转vue文件](./training-vue/src/components/Base/Form/BaseForm.vue),[类型声明文件](./training-vue/src/typings/form.d.ts) ```ts const fields = ref>([ { label: "套餐状态:", prop: "status", style: { width: "100%" }, component: markRaw(ElementPlus.ElRadioGroup), childComponent: markRaw(ElementPlus.ElRadioButton), props: { disabled: true, clearable: true, placeholder: "请选择套餐状态", tip: "默认套餐处于停用状态,首次新增无法修改,避免移动端直接上架", }, childProps: { options: [ { label: "停用", value: 0 }, { label: "启用", value: 1 }, ], }, }, { label: "套餐菜品:", prop: "setmealDishes", style: { width: "100%" }, props: { otherSlot: true, tip: "该套餐包含有哪些菜品,至少选择两个菜品才能组成一个套餐!", }, isManualValidate: true, rules: [ { required: true, validator: (_, __, callback) => { if (dishTableData.data?.length <= 1) { callback(new Error("至少选择两道菜品")); return; } callback(); }, trigger: ["change"], }, ], }, ]); ``` 2. 表格组件:[点击跳转vue文件](./training-vue/src/components/Base/Table/BaseTable.vue),[类型声明文件](./training-vue/src/typings/table.d.ts) 3. Echarts组件:[点击跳转vue文件](./training-vue/src/components/Base/Echarts/BaseEcharts.vue) > **智能提示+严格规范** > > | image-20250709022409449 | image-20250709022505458 | > | :----------------------------------------------------------: | :----------------------------------------------------------: | > | ![image-20250709022659572](preview/image-20250709022659572.png) | image-20250709023023611 | > > 智能语法提示的本质是借助vue和TS的内置工具类型`ExtractPropTypes["$props"]>`实现的,提取`defineProps`中定义有的属性 ### ②通用hooks 1. useEcharts(部分):[点击跳转源文件位置](./training-vue/src/hooks/useEcharts.ts) ```ts export const useEcharts = ( elRef: Ref, options: EChartsCoreOption ) => { const isInitialized = ref(false); const charts = shallowRef(); const setOptions = (options: EChartsCoreOption) => { if (isEmpty(options) || !charts.value) return; charts.value.setOption(options); }; const initCharts = (theme?: string, otherOptions?: EChartsCoreOption) => { const el = unref(elRef); if (!el || !unref(el) || isInitialized.value) return; charts.value = echarts.init(el, theme); if (otherOptions?.color) { options.color = otherOptions?.color; } isInitialized.value = true; setOptions(options); }; const resize = (options?: echarts.ResizeOpts) => charts.value?.resize?.(options); ``` 2. 通用websocket(部分):[点击跳转源文件位置](./training-vue/src/utils/websocket.ts),useSocket:[点击跳转源文件位置](./training-vue/src/hooks/useSocket.ts) ```ts export default class Socket { private onOpen(event: Event) { this.reconnectAttempts = 0; this.startHeartbeat(); this.emit("open", event); } private onClose(event: CloseEvent) { this.stopHeartbeat(); this.emit("close", event); // 重连 if ( this.opts?.maxReconnectAttempts !== 0 && this.reconnectAttempts! < this.opts?.maxReconnectAttempts! ) { setTimeout(() => { this.reconnectAttempts!++; this.initSocket(); }, this.opts?.reconnectInterval || 5000); } } // 开始心跳检测 private startHeartbeat() { if (!this.opts?.heartbeatInterval) return; this.heartbeatInterval = window.setInterval(() => { if (this.ws?.readyState === WebSocket.OPEN) { this.ws.send("ping"); } }, this.opts?.heartbeatInterval); } // 停止心跳检测 private stopHeartbeat() { if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = undefined; } } on( event: K, callback: (data: SocketEventMap[K]) => void ) { if (!this.listeners[event]) { this.listeners[event] = []; } this.listeners?.[event]?.push(callback); } // 在事件监听器中触发一个指定的事件 private emit(event: K, data: SocketEventMap[K]) { const callbacks = this.listeners[event]; callbacks?.forEach((callback) => callback(data)); } } ``` ### ③通用指令 1. 元素动画延迟指令:[点击跳转源文件位置](./training-vue/src/directive/stagger.ts) ![](preview/%E5%85%83%E7%B4%A0%E5%8A%A8%E7%94%BB%E5%BB%B6%E8%BF%9F%E6%8C%87%E4%BB%A4%E6%95%88%E6%9E%9C.gif) 2. 元素尺寸监听指令(防抖处理):[点击跳转源文件位置](./training-vue/src/directive/resize.ts) ![](preview/%E5%85%83%E7%B4%A0%E5%B0%BA%E5%AF%B8%E7%9B%91%E5%90%AC%E6%8C%87%E4%BB%A4.gif) ## 四.快速启动本地Redis ```bash @echo off :: 设置UTF-8编码支持中文 chcp 65001 > nul set DEFAULT_PATH=D:\Redis set PASSWORD=liang122010 echo 尝试关闭已存在的Redis服务... cd /d %DEFAULT_PATH% redis-cli.exe -h 127.0.0.1 -p 6379 -a %PASSWORD% shutdown > nul 2>&1 echo 等待3s中... timeout /t 3 /nobreak > nul echo 启动Redis服务端... start cmd /k "cd /d %DEFAULT_PATH% && redis-server.exe redis.windows.conf" timeout /t 2 /nobreak > nul echo 正在启动Redis客户端... start cmd /k "cd /d %DEFAULT_PATH% && redis-cli.exe -h 127.0.0.1 -p 6379 -a %PASSWORD%" echo 启动成功! ``` 启动效果如图 image-20250708192329914 > 此脚本需要确保你的电脑安装有Redis,并且指定正确的安装目录,否则启动失败!!