# 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. 选择套餐所包含的菜品动画效果

2. 移除套餐所选菜品

3. 移动端适配效果

### ②可视化图表+自适应
可视化图表+自适应指令,实现移动端适配效果

### ③小程序部分页面截图
|
|
|
| :----------------------------------------------------------: | :----------------------------------------------------------: |
|
|
|
|
|
|
|
|
|
## 三.通用组件+通用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)
> **智能提示+严格规范**
>
> |
|
|
> | :----------------------------------------------------------: | :----------------------------------------------------------: |
> |  |
|
>
> 智能语法提示的本质是借助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)

2. 元素尺寸监听指令(防抖处理):[点击跳转源文件位置](./training-vue/src/directive/resize.ts)

## 四.快速启动本地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 启动成功!
```
启动效果如图
> 此脚本需要确保你的电脑安装有Redis,并且指定正确的安装目录,否则启动失败!!