SchoolDash Alpha冲刺 团队代码规范、任务与计划
课程与作业信息
- 所属课程:软件工程实践
- 作业要求来源:第五次作业——Alpha冲刺
- 本篇目标:阐述团队代码规范、本次冲刺任务及计划,对应课程目标7
(1)SchoolDash 代码规范
1. 概述
本文档定义了 SchoolDash 项目的代码规范,包括前端、后端和数据库的编码标准,以确保代码的一致性、可读性和可维护性。
2. 通用规范
2.1 命名规范
变量和函数命名
- 使用驼峰命名法(camelCase)
- 变量名使用有意义的名词或形容词
- 函数名使用动词或动词短语
- 布尔值变量以
is、has、can等开头
// 正确示例
const userName = 'john';
const isUserActive = true;
const getUserById = (id) => { /* ... */ };
const hasPermission = user.role === 'admin';
// 错误示例
const u_name = 'john';
const activeuser = true;
const get_user = (id) => { /* ... */ };
常量命名
- 使用全大写字母和下划线分隔
// 正确示例
const API_BASE_URL = 'https://api.example.com';
const MAX_RETRY_COUNT = 3;
// 错误示例
const apiBaseUrl = 'https://api.example.com';
const maxRetryCount = 3;
类名和组件名
- 使用帕斯卡命名法(PascalCase)
// 正确示例
class UserService { /* ... */ }
const UserProfile = { /* ... */ };
// 错误示例
class userService { /* ... */ }
const userProfile = { /* ... */ };
文件命名
- 使用小写字母和连字符分隔
- 组件文件名与组件名保持一致(使用帕斯卡命名法)
// 正确示例
user-service.js
user-profile.vue
GoodsManage.vue
// 错误示例
userService.js
userprofile.vue
goodsmanage.vue
2.2 注释规范
文件头注释
每个文件应包含文件头注释,说明文件用途、作者和创建日期
/**
* 用户服务模块
* 提供用户相关的业务逻辑处理
*
* @author 开发团队
* @since 2023-01-01
*/
函数注释
使用 JSDoc 格式注释函数,说明参数、返回值和功能
/**
* 根据用户ID获取用户信息
* @param {number} userId - 用户ID
* @param {boolean} includeProfile - 是否包含详细资料
* @returns {Promise<Object>} 用户信息对象
* @throws {Error} 当用户不存在时抛出错误
*/
const getUserById = async (userId, includeProfile = false) => {
// 实现代码
};
行内注释
对复杂逻辑或关键步骤添加简短注释
// 验证用户权限
if (!hasPermission(user, 'admin')) {
throw new Error('无权限访问此资源');
}
// 计算订单总价(包含税费)
const totalPrice = calculateTotalPrice(orderItems) * (1 + taxRate);
2.3 代码格式化
缩进
使用 2 个空格进行缩进,不使用制表符
// 正确示例
if (condition) {
doSomething();
if (anotherCondition) {
doAnotherThing();
}
}
// 错误示例
if (condition) {
doSomething();
if (anotherCondition) {
doAnotherThing();
}
}
行长度
每行代码不超过 100 个字符
空行
在逻辑块之间使用空行分隔
函数之间使用 1-2 个空行分隔
const getUserList = async () => {
// 实现代码
};
const createUser = async (userData) => {
// 实现代码
};
3. 前端代码规范
3.1 Vue.js 组件规范
组件结构
按照以下顺序组织组件内容:
<template>
<!-- 模板内容 -->
</template>
<script>
// 导入语句
import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
// 组件定义
export default {
name: 'ComponentName',
// 组件选项
props: {
// 属性定义
},
emits: [
// 事件定义
],
setup(props, { emit }) {
// 响应式数据
const data = ref('');
// 计算属性
const computedData = computed(() => {
// 计算逻辑
});
// 方法
const method = () => {
// 方法实现
};
// 生命周期钩子
onMounted(() => {
// 初始化逻辑
});
// 返回模板中需要使用的数据和方法
return {
data,
computedData,
method
};
}
};
</script>
<style scoped>
/* 组件样式 */
</style>
组件命名
- 组件名使用帕斯卡命名法
- 组件名应该是有意义的,描述组件的功能
- 避免使用 HTML 保留字
// 正确示例
UserProfile
GoodsManage
OrderList
// 错误示例
userprofile
goodsmanage
orderlist
Div
Span
Props 定义
- Props 应该详细定义类型、默认值和验证
- Props 命名使用驼峰命名法
// 正确示例
props: {
userId: {
type: Number,
required: true
},
userName: {
type: String,
default: ''
},
isActive: {
type: Boolean,
default: false
},
permissions: {
type: Array,
default: () => []
}
}
事件命名
- 事件名使用 kebab-case(短横线分隔)
- 事件名应该描述动作
// 正确示例
emit('user-selected', userId);
emit('order-updated', orderId);
emit('form-submitted', formData);
// 错误示例
emit('userSelected', userId);
emit('orderUpdated', orderId);
emit('formSubmitted', formData);
3.2 模板规范
HTML 结构
- 使用语义化 HTML5 标签
- 合理嵌套标签,避免过深嵌套
- 使用缩进保持结构清晰
<template>
<div class="user-profile">
<header class="profile-header">
<h2>{{ userName }}</h2>
<p class="user-role">{{ userRole }}</p>
</header>
<main class="profile-content">
<section class="user-info">
<h3>基本信息</h3>
<ul>
<li v-for="info in userInfo" :key="info.id">
{{ info.label }}: {{ info.value }}
</li>
</ul>
</section>
<section class="user-actions">
<button @click="editProfile">编辑资料</button>
<button @click="changePassword">修改密码</button>
</section>
</main>
</div>
</template>
条件渲染
使用 v-if、v-else-if、v-else 进行条件渲染
<template>
<div>
<div v-if="isLoading">加载中...</div>
<div v-else-if="hasError">加载失败</div>
<div v-else>
<!-- 正常内容 -->
</div>
</div>
</template>
列表渲染
使用 v-for 进行列表渲染,始终提供唯一的 key
<template>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
3.3 样式规范
CSS 类命名
使用 BEM(Block Element Modifier)命名法
/* 块(Block) */
.user-profile { }
/* 元素(Element) */
.user-profile__header { }
.user-profile__content { }
/* 修饰符(Modifier) */
.user-profile--active { }
.user-profile__header--highlighted { }
样式作用域
使用 scoped 属性限制样式作用域
<style scoped>
/* 只作用于当前组件 */
.component-style {
/* 样式定义 */
}
</style>
响应式设计
使用媒体查询实现响应式布局
.container {
width: 100%;
padding: 0 15px;
}
@media (min-width: 768px) {
.container {
max-width: 750px;
margin: 0 auto;
}
}
@media (min-width: 992px) {
.container {
max-width: 970px;
}
}
3.4 状态管理
使用 Pinia 或 Vuex
- 集中管理应用状态
- 按功能模块组织状态
// store/modules/user.js
export const useUserStore = defineStore('user', {
state: () => ({
currentUser: null,
isLoggedIn: false,
permissions: []
}),
getters: {
isAdmin: (state) => state.currentUser?.role === 'admin',
hasPermission: (state) => (permission) => {
return state.permissions.includes(permission);
}
},
actions: {
async login(credentials) {
// 登录逻辑
},
async logout() {
// 登出逻辑
}
}
});
4. 后端代码规范
4.1 Node.js/Express 规范
项目结构
按照功能模块组织代码
backend/
├── config/ # 配置文件
├── middleware/ # 中间件
├── models/ # 数据模型
├── routes/ # 路由定义
├── services/ # 业务逻辑服务
├── utils/ # 工具函数
├── controllers/ # 控制器(可选)
├── app.js # 应用入口
└── package.json # 依赖配置
模块导入
使用 ES6 模块导入语法
// 正确示例
import express from 'express';
import cors from 'cors';
import { sequelize } from './config/db.js';
import authRoutes from './routes/authRoutes.js';
// 错误示例
const express = require('express');
const cors = require('cors');
const { sequelize } = require('./config/db');
const authRoutes = require('./routes/authRoutes');
路由定义
- 使用 Express Router 组织路由
- 路由处理器应该是异步函数
- 使用中间件处理通用逻辑
// routes/userRoutes.js
import express from 'express';
import { authenticateToken } from '../middleware/auth.js';
import { getUserById, createUser, updateUser } from '../controllers/userController.js';
const router = express.Router();
// 路由定义
router.get('/users/:id', authenticateToken, getUserById);
router.post('/users', createUser);
router.put('/users/:id', authenticateToken, updateUser);
export default router;
控制器
- 控制器只负责处理请求和响应
- 业务逻辑应该放在服务层
// controllers/userController.js
import { userService } from '../services/userService.js';
export const getUserById = async (req, res) => {
try {
const { id } = req.params;
const user = await userService.getUserById(id);
if (!user) {
return res.status(404).json({
code: 404,
msg: '用户不存在'
});
}
res.json({
code: 200,
msg: '获取成功',
data: user
});
} catch (error) {
console.error('获取用户失败:', error);
res.status(500).json({
code: 500,
msg: '服务器内部错误'
});
}
};
错误处理
- 使用统一的错误处理中间件
- 自定义错误类型
// middleware/errorHandler.js
export const errorHandler = (err, req, res, next) => {
console.error('错误详情:', err);
// 自定义错误
if (err.name === 'ValidationError') {
return res.status(400).json({
code: 400,
msg: '参数验证失败',
details: err.message
});
}
// 数据库错误
if (err.name === 'SequelizeError') {
return res.status(500).json({
code: 500,
msg: '数据库操作失败'
});
}
// 默认错误
res.status(500).json({
code: 500,
msg: '服务器内部错误'
});
};
// 在 app.js 中使用
app.use(errorHandler);
4.2 数据库模型规范
Sequelize 模型定义
- 使用类定义模型
- 明确定义字段类型和约束
- 定义模型关联关系
// models/User.js
import { DataTypes } from 'sequelize';
import { sequelize } from '../config/db.js';
export const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [3, 50]
}
},
password: {
type: DataTypes.STRING(255),
allowNull: false,
validate: {
len: [6, 255]
}
},
email: {
type: DataTypes.STRING(100),
allowNull: true,
validate: {
isEmail: true
}
},
role: {
type: DataTypes.ENUM('user', 'admin', 'rider'),
allowNull: false,
defaultValue: 'user'
},
isActive: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
},
updatedAt: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: DataTypes.NOW
}
}, {
tableName: 'users',
timestamps: true
});
模型关联
- 在模型文件中定义关联关系
- 使用
belongsTo、hasMany、belongsToMany等方法
// models/User.js
import { Order } from './Order.js';
import { Address } from './Address.js';
// 用户有多个订单
User.hasMany(Order, {
foreignKey: 'userId',
as: 'orders'
});
// 用户有多个地址
User.hasMany(Address, {
foreignKey: 'userId',
as: 'addresses'
});
// models/Order.js
import { User } from './User.js';
import { OrderItem } from './OrderItem.js';
// 订单属于一个用户
Order.belongsTo(User, {
foreignKey: 'userId',
as: 'user'
});
// 订单有多个订单项
Order.hasMany(OrderItem, {
foreignKey: 'orderId',
as: 'items'
});
4.3 API 响应格式
统一响应格式
所有 API 响应应遵循统一格式
// 成功响应
{
"code": 200,
"msg": "操作成功",
"data": {
// 实际数据
}
}
// 错误响应
{
"code": 400,
"msg": "参数错误",
"details": "具体错误信息"
}
// 分页响应
{
"code": 200,
"msg": "获取成功",
"data": {
"list": [
// 数据列表
],
"pagination": {
"current": 1,
"pageSize": 10,
"total": 100
}
}
}
状态码使用
- 200: 成功
- 201: 创建成功
- 400: 请求参数错误
- 401: 未授权
- 403: 禁止访问
- 404: 资源不存在
- 500: 服务器内部错误
4.4 中间件规范
认证中间件
- 验证 JWT 令牌
- 设置用户信息到请求对象
// middleware/auth.js
import jwt from 'jsonwebtoken';
export const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({
code: 401,
msg: '访问令牌缺失'
});
}
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({
code: 403,
msg: '访问令牌无效'
});
}
req.user = user;
next();
});
};
权限中间件
- 检查用户是否有特定权限
// middleware/permission.js
export const requirePermission = (permission) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
code: 401,
msg: '未登录'
});
}
if (!req.user.permissions.includes(permission)) {
return res.status(403).json({
code: 403,
msg: '权限不足'
});
}
next();
};
};
// 使用示例
router.post('/admin/goods', authenticateToken, requirePermission('goods:create'), createGoods);
5. 数据库规范
5.1 命名规范
表名
- 使用小写字母和下划线分隔
- 使用复数形式
- 表名应该是有意义的,描述表的用途
-- 正确示例
users
orders
order_items
user_addresses
-- 错误示例
User
Order
orderItems
useraddress
字段名
- 使用小写字母和下划线分隔
- 字段名应该是有意义的,描述字段的用途
- 避免使用数据库保留字
-- 正确示例
user_id
created_at
is_active
first_name
-- 错误示例
userId
createdAt
active
firstName
索引名
- 使用
idx_前缀 - 包含表名和字段名
-- 正确示例
idx_users_email
idx_orders_user_id
idx_order_items_order_id
-- 错误示例
users_email_index
order_user_idx
order_items_order_id_index
5.2 字段定义
主键
- 使用
id作为主键名 - 使用自增整数或 UUID
-- 正确示例
id INT PRIMARY KEY AUTO_INCREMENT
-- 或
id VARCHAR(36) PRIMARY KEY
-- 错误示例
user_id INT PRIMARY KEY AUTO_INCREMENT
uuid VARCHAR(36) PRIMARY KEY
外键
- 使用
表名_id格式 - 明确定义外键约束
-- 正确示例
user_id INT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
-- 错误示例
userId INT NOT NULL,
user INT NOT NULL
时间戳字段
- 使用
created_at和updated_at - 使用
TIMESTAMP或DATETIME类型 - 设置默认值和自动更新
-- 正确示例
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
-- 错误示例
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP
updateTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
布尔字段
- 使用
is_前缀 - 使用
TINYINT(1)或BOOLEAN类型 - 设置默认值
-- 正确示例
is_active TINYINT(1) DEFAULT 1
is_deleted TINYINT(1) DEFAULT 0
-- 错误示例
active TINYINT(1) DEFAULT 1
deleted TINYINT(1) DEFAULT 0
5.3 表设计原则
第一范式(1NF)
- 字段不可再分
- 每个字段都是原子性的
-- 正确示例
users(
id INT PRIMARY KEY,
first_name VARCHAR(50),
last_name VARCHAR(50),
email VARCHAR(100)
)
-- 错误示例
users(
id INT PRIMARY KEY,
name VARCHAR(100), -- 包含名和姓,不符合1NF
email VARCHAR(100)
)
第二范式(2NF)
- 满足第一范式
- 非主键字段完全依赖于主键
-- 正确示例
orders(
id INT PRIMARY KEY,
user_id INT NOT NULL,
order_date DATE NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
)
order_items(
id INT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
)
-- 错误示例
orders(
id INT PRIMARY KEY,
user_id INT NOT NULL,
order_date DATE NOT NULL,
product_id INT NOT NULL,
product_name VARCHAR(100) NOT NULL, -- 依赖于product_id,不完全依赖于order_id
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL
)
第三范式(3NF)
- 满足第二范式
- 非主键字段不传递依赖于主键
-- 正确示例
users(
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
city_id INT NOT NULL,
FOREIGN KEY (city_id) REFERENCES cities(id)
)
cities(
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
province VARCHAR(50) NOT NULL
)
-- 错误示例
users(
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
city_name VARCHAR(100) NOT NULL, -- 传递依赖于city_id
city_province VARCHAR(50) NOT NULL -- 传递依赖于city_id
)
5.4 索引规范
主键索引
- 主键自动创建索引
- 不需要手动创建
唯一索引
- 为需要唯一性的字段创建唯一索引
-- 正确示例
CREATE UNIQUE INDEX idx_users_email ON users(email);
CREATE UNIQUE INDEX idx_users_username ON users(username);
普通索引
- 为经常用于查询条件的字段创建索引
- 为外键字段创建索引
-- 正确示例
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_order_date ON orders(order_date);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);
复合索引
- 为经常一起查询的多个字段创建复合索引
- 将选择性高的字段放在前面
-- 正确示例
CREATE INDEX idx_orders_user_date ON orders(user_id, order_date);
CREATE INDEX idx_order_items_order_product ON order_items(order_id, product_id);
5.5 SQL 查询规范
查询格式化
- 使用缩进和换行保持可读性
- 关键字大写,字段名和表名小写
-- 正确示例
SELECT
u.id,
u.username,
u.email,
o.id AS order_id,
o.order_date,
o.total_amount
FROM
users u
INNER JOIN
orders o ON u.id = o.user_id
WHERE
u.is_active = 1
AND o.order_date >= '2023-01-01'
ORDER BY
o.order_date DESC
LIMIT 10;
-- 错误示例
select u.id, u.username, u.email, o.id as order_id, o.order_date, o.total_amount
from users u inner join orders o on u.id = o.user_id
where u.is_active = 1 and o.order_date >= '2023-01-01'
order by o.order_date desc limit 10;
避免 SELECT *
- 明确指定需要的字段
- 减少数据传输量
-- 正确示例
SELECT id, username, email FROM users WHERE id = 1;
-- 错误示例
SELECT * FROM users WHERE id = 1;
使用参数化查询
- 防止 SQL 注入
- 提高查询性能
// 正确示例
const getUserById = async (id) => {
const [rows] = await sequelize.query(
'SELECT id, username, email FROM users WHERE id = ?',
{
replacements: [id],
type: QueryTypes.SELECT
}
);
return rows[0];
};
// 错误示例
const getUserById = async (id) => {
const [rows] = await sequelize.query(
`SELECT id, username, email FROM users WHERE id = ${id}`,
{ type: QueryTypes.SELECT }
);
return rows[0];
};
6. 代码审查清单
6.1 前端代码审查
- 组件命名是否符合规范
- Props 和事件定义是否完整
- 是否有内存泄漏风险(事件监听器、定时器等)
- 是否有未处理的 Promise 异常
- 样式是否使用 scoped 或 BEM 命名
- 是否有重复代码可以提取为公共组件或函数
- 是否有性能优化空间(懒加载、防抖、节流等)
6.2 后端代码审查
- API 响应格式是否统一
- 错误处理是否完善
- 是否有安全漏洞(SQL 注入、XSS 等)
- 数据验证是否充分
- 是否有未处理的异步错误
- 数据库查询是否优化
- 敏感信息是否正确处理(密码加密、令牌验证等)
6.3 数据库代码审查
- 表设计是否符合范式
- 索引使用是否合理
- 外键约束是否正确定义
- 查询语句是否优化
- 是否有性能瓶颈
- 数据类型选择是否合适
总结
遵循本代码规范可以提高代码质量、可读性和可维护性,减少开发过程中的沟通成本,提高团队协作效率。所有开发人员都应该熟悉并遵守这些规范,代码审查时也应以此规范为标准。
代码规范不是一成不变的,随着项目的发展和团队经验的积累,可以适当调整和完善规范内容。任何修改都应该经过团队讨论,并及时更新文档。
(2) 本次冲刺任务
本次Alpha冲刺为期10天,聚焦SchoolDash系统的核心功能完善。项目是一个校园配送管理系统,包括用户端(商品浏览/购物车/订单)、骑手端(订单接收/配送)和管理端(商品/订单/用户管理)。基于前期开发,已移除图片功能,使用JWT多角色认证,支持MySQL/SQLite切换。
1.主要任务
主要任务分解为以下Issue:
-
Issue #1: 环境优化与认证完善 :切换数据库配置,实现多角色JWT Token管理。
-
Issue #2: 用户注册登录模块:前后端集成,包含骑手/管理员角色。
-
Issue #3: 商品与分类模块 :后端路由(categoryRoutes.js/homeRoutes.js),前端页面(GoodsList.vue/GoodsDetail.vue)。
-
Issue #4: 购物车与订单模块 :购物车管理(cartRoutes.js/Cart.vue),订单创建/查看(orderRoutes.js/OrderList.vue)。
-
Issue #5: 骑手与管理端功能 :骑手订单处理(riderRoutes.js/RiderDashboard.vue),管理端CRUD(adminRoutes.js/AdminGoodsManage.vue)。
-
Issue #6: 集成测试与优化:端到端测试,性能调优,缓存问题修复。
2.成员分工
| 成员 | 贡献度 | 成员分工 |
|---|---|---|
| Shi Haokun | 8.3% | 协调项目进度,分配任务,促进跨模块沟通,组织阶段讨论,与教师联络接收提交成果,确保质量控制,并负责课堂展示。 |
| Ou Yang/Shi Haozhen/Zheng Dingnan/Wang Junqi | 33.2% | 负责用户端和管理端的全页面开发,涵盖首页、产品分类/详情页(包括加入购物车)、登录/注册、购物车、个人中心(订单查询)、结账/订单确认/支付结果页面,以及产品/订单/用户管理后台页面。确保界面可用且操作流程连贯。 |
| GUO ZIKAI/Li Yan/Sun Xuhang/Mo Xinyan | 33.2% | 相关的数据支持和业务逻辑方面,一人负责设计数据库表(如用户、产品、订单等)并实现对数据的添加、删除、修改和查询操作。另外两人分别负责用户系统(登录/注册验证、权限管理)和产品管理、购物车(产品添加、删除、修改)以及订单流程(创建、状态更新)。一人负责前端和后端接口的开发及集成测试,以确保数据交互的顺畅进行。 |
| Zhou Jinlin/Hu Yichen/Lin Xie | 25.3% | 验证每个模块的功能(如登录、下单、支付等),检查界面的一致性和数据的准确性,协助进行故障排查,并确保系统的可用性。 |
3. 冲刺计划
-
时间安排:每日站会(15min,讨论进度/阻塞),每2天回顾并发布冲刺随笔(含燃尽图、运行效果、提交记录)。冲刺末尾总结。
-
工具与实践:用GitHub管理Issue/Commit(分支规范:feature/xxx),PM2部署后端,Vite构建前端。自动化测试用Jest/Vitest。
-
风险与应对:潜在风险如API路径不匹配或缓存问题(参考开发问题记录.md),应对通过每日审查与Pair Programming。目标:完成Alpha版本,覆盖用户/骑手/管理员完整流程。
-
预期输出:可运行项目(链接:https://github.com/team/schooldash),包含所有模块运行截图/GIF。
团队将以敏捷方式推进,确保高质量交付。冲刺启动前,所有成员确认规范熟悉度。
SchoolDash团队,Alpha冲刺准备就绪!

被折叠的 条评论
为什么被折叠?



