docs: align stack with java 8 and native miniapp
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
**Architecture:** Use a backend-first implementation: define schema, status machines, API contracts, and tests before building the three clients. Keep the first release as a modular monolith with tenant/community fields on core tables, so later multi-community SaaS work can expand without rewriting order, delivery, and permission models.
|
||||
|
||||
**Tech Stack:** Spring Boot 3, Java 17, MyBatis Plus, Sa-Token, MySQL 8, Redis, Flyway, Knife4j/OpenAPI, MinIO-compatible object storage, Vue 3, TypeScript, Vite, Element Plus, uni-app/Vue 3 for WeChat miniapp, Vue 3 mobile H5 for riders.
|
||||
**Tech Stack:** Spring Boot 2.7.18, Java 8, MyBatis Plus, Sa-Token, MySQL 8, Redis, Flyway, Knife4j/OpenAPI, MinIO-compatible object storage, Vue 3, TypeScript, Vite, Element Plus for admin web, WeChat native mini program with WXML/WXSS/JavaScript, Vue 3 mobile H5 for riders.
|
||||
|
||||
---
|
||||
|
||||
@@ -62,7 +62,15 @@ D:\codes\LinHelp
|
||||
│ └── src
|
||||
├── miniapp
|
||||
│ ├── package.json
|
||||
│ └── src
|
||||
│ ├── project.config.json
|
||||
│ └── miniprogram
|
||||
│ ├── app.js
|
||||
│ ├── app.json
|
||||
│ ├── app.wxss
|
||||
│ ├── api
|
||||
│ ├── components
|
||||
│ ├── pages
|
||||
│ └── utils
|
||||
├── rider-h5
|
||||
│ ├── package.json
|
||||
│ ├── vite.config.ts
|
||||
@@ -79,13 +87,13 @@ Boundary rules:
|
||||
|
||||
- `backend/src/main/java/com/linhelp/common` contains cross-cutting code only.
|
||||
- Each business package owns its entity, mapper, service, controller, DTOs, and tests.
|
||||
- Frontend clients use typed API modules under `src/api`, and pages call APIs through those modules instead of inline `fetch` calls.
|
||||
- Admin web and rider H5 use API modules under `src/api`; the native mini program uses request modules under `miniprogram/api` and a shared `miniprogram/utils/request.js` wrapper instead of inline `wx.request` calls.
|
||||
- All tables that carry tenant-owned business data include `tenant_id` and `community_id`.
|
||||
- Use table names `app_user`, `goods_order`, and `sys_role` instead of reserved or ambiguous names.
|
||||
|
||||
## Shared Domain Vocabulary
|
||||
|
||||
Use these enum codes in backend and frontend:
|
||||
Use these enum codes in backend, admin web, rider H5, and miniapp constants:
|
||||
|
||||
```text
|
||||
GoodsOrderStatus: PENDING_CONFIRM, PREPARING, PENDING_DELIVERY, PENDING_PICKUP, DELIVERING, COMPLETED, CANCELED
|
||||
@@ -110,6 +118,11 @@ DeliveryMethod: IMMEDIATE, SCHEDULED, SELF_PICKUP
|
||||
- Create: `admin-web/package.json`
|
||||
- Create: `admin-web/vite.config.ts`
|
||||
- Create: `miniapp/package.json`
|
||||
- Create: `miniapp/project.config.json`
|
||||
- Create: `miniapp/miniprogram/app.js`
|
||||
- Create: `miniapp/miniprogram/app.json`
|
||||
- Create: `miniapp/miniprogram/app.wxss`
|
||||
- Create: `miniapp/miniprogram/utils/request.js`
|
||||
- Create: `rider-h5/package.json`
|
||||
- Create: `rider-h5/vite.config.ts`
|
||||
- Create: `deploy/docker-compose.dev.yml`
|
||||
@@ -118,7 +131,7 @@ DeliveryMethod: IMMEDIATE, SCHEDULED, SELF_PICKUP
|
||||
|
||||
- [ ] **Step 1: Create backend Spring Boot skeleton**
|
||||
|
||||
Add `backend/pom.xml` with Java 17, Spring Boot 3, MyBatis Plus, Sa-Token, MySQL, Redis, Flyway, Knife4j, MinIO, validation, and test dependencies.
|
||||
Add `backend/pom.xml` with Java 8, Spring Boot 2.7.18, MyBatis Plus, Sa-Token, MySQL, Redis, Flyway, Knife4j, MinIO, validation, and test dependencies. Spring Boot 3 is not used because it requires Java 17+.
|
||||
|
||||
```xml
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
@@ -130,11 +143,14 @@ Add `backend/pom.xml` with Java 17, Spring Boot 3, MyBatis Plus, Sa-Token, MySQL
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<name>linhelp-backend</name>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-boot.version>3.3.2</spring-boot.version>
|
||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||
<java.version>1.8</java.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-boot.version>2.7.18</spring-boot.version>
|
||||
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||
<sa-token.version>1.38.0</sa-token.version>
|
||||
<knife4j.version>4.5.0</knife4j.version>
|
||||
<knife4j.version>4.4.0</knife4j.version>
|
||||
<minio.version>8.5.11</minio.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
@@ -163,30 +179,26 @@ Add `backend/pom.xml` with Java 17, Spring Boot 3, MyBatis Plus, Sa-Token, MySQL
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
|
||||
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
|
||||
<version>${knife4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@@ -202,6 +214,15 @@ Add `backend/pom.xml` with Java 17, Spring Boot 3, MyBatis Plus, Sa-Token, MySQL
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
@@ -290,7 +311,6 @@ spring:
|
||||
url: jdbc:mysql://localhost:3306/linhelp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
username: linhelp
|
||||
password: linhelp
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
@@ -345,37 +365,149 @@ Create `admin-web/package.json`.
|
||||
}
|
||||
```
|
||||
|
||||
Create `miniapp/package.json`.
|
||||
Create `miniapp/package.json` for native mini program helper tests and optional WeChat CI commands. Do not add uni-app, Vue, or Vite dependencies to the miniapp.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "linhelp-miniapp",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev:mp-weixin": "uni -p mp-weixin",
|
||||
"build": "uni build -p mp-weixin",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dcloudio/uni-app": "^3.0.0",
|
||||
"@dcloudio/uni-mp-weixin": "^3.0.0",
|
||||
"axios": "^1.7.2",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.4.31"
|
||||
"test": "jest --runInBand",
|
||||
"ci:preview": "miniprogram-ci preview --pp ./project.config.json --robot 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dcloudio/types": "^3.4.8",
|
||||
"@dcloudio/uni-cli-shared": "^3.0.0",
|
||||
"@dcloudio/vite-plugin-uni": "^3.0.0",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.3",
|
||||
"vitest": "^2.0.2"
|
||||
"jest": "^29.7.0",
|
||||
"miniprogram-ci": "^2.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create `miniapp/project.config.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"appid": "touristappid",
|
||||
"projectname": "linhelp-miniapp",
|
||||
"miniprogramRoot": "miniprogram/",
|
||||
"setting": {
|
||||
"urlCheck": false,
|
||||
"es6": true,
|
||||
"enhance": true,
|
||||
"postcss": true,
|
||||
"minified": true
|
||||
},
|
||||
"compileType": "miniprogram"
|
||||
}
|
||||
```
|
||||
|
||||
Create `miniapp/miniprogram/app.js`.
|
||||
|
||||
```js
|
||||
App({
|
||||
globalData: {
|
||||
apiBaseUrl: 'http://localhost:8080',
|
||||
token: ''
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Create `miniapp/miniprogram/app.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"pages": [
|
||||
"pages/home/index",
|
||||
"pages/products/index",
|
||||
"pages/products/detail",
|
||||
"pages/orders/index",
|
||||
"pages/orders/detail",
|
||||
"pages/express/create",
|
||||
"pages/group-buy/index",
|
||||
"pages/group-buy/detail",
|
||||
"pages/second-hand/index",
|
||||
"pages/second-hand/create",
|
||||
"pages/notices/index",
|
||||
"pages/profile/index",
|
||||
"pages/address/index"
|
||||
],
|
||||
"window": {
|
||||
"navigationBarTitleText": "邻小帮",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#f6f7f9"
|
||||
},
|
||||
"tabBar": {
|
||||
"color": "#6b7280",
|
||||
"selectedColor": "#0f766e",
|
||||
"backgroundColor": "#ffffff",
|
||||
"list": [
|
||||
{
|
||||
"pagePath": "pages/home/index",
|
||||
"text": "首页"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/group-buy/index",
|
||||
"text": "团购"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/second-hand/index",
|
||||
"text": "闲置"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/index",
|
||||
"text": "我的"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create `miniapp/miniprogram/app.wxss`.
|
||||
|
||||
```css
|
||||
page {
|
||||
background: #f6f7f9;
|
||||
color: #111827;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 24rpx;
|
||||
}
|
||||
```
|
||||
|
||||
Create `miniapp/miniprogram/utils/request.js`.
|
||||
|
||||
```js
|
||||
function request(options) {
|
||||
const app = getApp()
|
||||
const token = app.globalData.token
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + options.url,
|
||||
method: options.method || 'GET',
|
||||
data: options.data || {},
|
||||
header: Object.assign({
|
||||
Authorization: token
|
||||
}, options.header || {}),
|
||||
success(res) {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && res.data && res.data.code === 0) {
|
||||
resolve(res.data.data)
|
||||
return
|
||||
}
|
||||
reject(new Error((res.data && res.data.message) || '请求失败'))
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
request
|
||||
}
|
||||
```
|
||||
|
||||
Create `rider-h5/package.json`.
|
||||
|
||||
```json
|
||||
@@ -482,22 +614,59 @@ Expected: FAIL because enum classes do not exist.
|
||||
|
||||
- [ ] **Step 3: Create common API wrappers**
|
||||
|
||||
Create `ApiResponse.java`.
|
||||
Create `ApiResponse.java`. Use a normal Java class because the backend targets Java 8.
|
||||
|
||||
```java
|
||||
package com.linhelp.common.api;
|
||||
|
||||
public record ApiResponse<T>(int code, String message, T data) {
|
||||
public class ApiResponse<T> {
|
||||
private int code;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
public ApiResponse() {
|
||||
}
|
||||
|
||||
public ApiResponse(int code, String message, T data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static <T> ApiResponse<T> ok(T data) {
|
||||
return new ApiResponse<>(0, "ok", data);
|
||||
return new ApiResponse<T>(0, "ok", data);
|
||||
}
|
||||
|
||||
public static ApiResponse<Void> ok() {
|
||||
return new ApiResponse<>(0, "ok", null);
|
||||
return new ApiResponse<Void>(0, "ok", null);
|
||||
}
|
||||
|
||||
public static ApiResponse<Void> fail(int code, String message) {
|
||||
return new ApiResponse<>(code, message, null);
|
||||
return new ApiResponse<Void>(code, message, null);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -509,7 +678,53 @@ package com.linhelp.common.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record PageResponse<T>(long total, long pageNo, long pageSize, List<T> records) {
|
||||
public class PageResponse<T> {
|
||||
private long total;
|
||||
private long pageNo;
|
||||
private long pageSize;
|
||||
private List<T> records;
|
||||
|
||||
public PageResponse() {
|
||||
}
|
||||
|
||||
public PageResponse(long total, long pageNo, long pageSize, List<T> records) {
|
||||
this.total = total;
|
||||
this.pageNo = pageNo;
|
||||
this.pageSize = pageSize;
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
public long getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public void setTotal(long total) {
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
public long getPageNo() {
|
||||
return pageNo;
|
||||
}
|
||||
|
||||
public void setPageNo(long pageNo) {
|
||||
this.pageNo = pageNo;
|
||||
}
|
||||
|
||||
public long getPageSize() {
|
||||
return pageSize;
|
||||
}
|
||||
|
||||
public void setPageSize(long pageSize) {
|
||||
this.pageSize = pageSize;
|
||||
}
|
||||
|
||||
public List<T> getRecords() {
|
||||
return records;
|
||||
}
|
||||
|
||||
public void setRecords(List<T> records) {
|
||||
this.records = records;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -520,6 +735,9 @@ Create `GoodsOrderStatus.java`.
|
||||
```java
|
||||
package com.linhelp.common.enums;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -532,16 +750,21 @@ public enum GoodsOrderStatus {
|
||||
COMPLETED,
|
||||
CANCELED;
|
||||
|
||||
private static final Map<GoodsOrderStatus, Set<GoodsOrderStatus>> TRANSITIONS = Map.of(
|
||||
PENDING_CONFIRM, Set.of(PREPARING, CANCELED),
|
||||
PREPARING, Set.of(PENDING_DELIVERY, PENDING_PICKUP, CANCELED),
|
||||
PENDING_DELIVERY, Set.of(DELIVERING, CANCELED),
|
||||
PENDING_PICKUP, Set.of(COMPLETED, CANCELED),
|
||||
DELIVERING, Set.of(COMPLETED, CANCELED)
|
||||
);
|
||||
private static final Map<GoodsOrderStatus, Set<GoodsOrderStatus>> TRANSITIONS;
|
||||
|
||||
static {
|
||||
Map<GoodsOrderStatus, Set<GoodsOrderStatus>> transitions = new EnumMap<GoodsOrderStatus, Set<GoodsOrderStatus>>(GoodsOrderStatus.class);
|
||||
transitions.put(PENDING_CONFIRM, EnumSet.of(PREPARING, CANCELED));
|
||||
transitions.put(PREPARING, EnumSet.of(PENDING_DELIVERY, PENDING_PICKUP, CANCELED));
|
||||
transitions.put(PENDING_DELIVERY, EnumSet.of(DELIVERING, CANCELED));
|
||||
transitions.put(PENDING_PICKUP, EnumSet.of(COMPLETED, CANCELED));
|
||||
transitions.put(DELIVERING, EnumSet.of(COMPLETED, CANCELED));
|
||||
TRANSITIONS = Collections.unmodifiableMap(transitions);
|
||||
}
|
||||
|
||||
public boolean canMoveTo(GoodsOrderStatus target) {
|
||||
return TRANSITIONS.getOrDefault(this, Set.of()).contains(target);
|
||||
Set<GoodsOrderStatus> targets = TRANSITIONS.get(this);
|
||||
return targets != null && targets.contains(target);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1106,7 +1329,22 @@ public final class TenantContext {
|
||||
SCOPE.remove();
|
||||
}
|
||||
|
||||
private record Scope(Long tenantId, Long communityId) {
|
||||
private static class Scope {
|
||||
private final Long tenantId;
|
||||
private final Long communityId;
|
||||
|
||||
private Scope(Long tenantId, Long communityId) {
|
||||
this.tenantId = tenantId;
|
||||
this.communityId = communityId;
|
||||
}
|
||||
|
||||
private Long tenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
private Long communityId() {
|
||||
return communityId;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1118,8 +1356,62 @@ Use Sa-Token for session management. First implementation supports backend accou
|
||||
Create request/response DTOs:
|
||||
|
||||
```java
|
||||
public record LoginRequest(String username, String password) {}
|
||||
public record LoginResponse(String token, Long userId, Long tenantId, Long communityId, String roleCode) {}
|
||||
public class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private Long userId;
|
||||
private Long tenantId;
|
||||
private Long communityId;
|
||||
private String roleCode;
|
||||
|
||||
public LoginResponse(String token, Long userId, Long tenantId, Long communityId, String roleCode) {
|
||||
this.token = token;
|
||||
this.userId = userId;
|
||||
this.tenantId = tenantId;
|
||||
this.communityId = communityId;
|
||||
this.roleCode = roleCode;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public Long getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public Long getCommunityId() {
|
||||
return communityId;
|
||||
}
|
||||
|
||||
public String getRoleCode() {
|
||||
return roleCode;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create `AuthController` endpoints:
|
||||
@@ -1270,7 +1562,7 @@ void cannotCreateGoodsOrderWhenSkuStockIsInsufficient() {
|
||||
addressId,
|
||||
DeliveryMethod.IMMEDIATE,
|
||||
null,
|
||||
List.of(new CreateGoodsOrderItemRequest(productId, skuId, 99)),
|
||||
Collections.singletonList(new CreateGoodsOrderItemRequest(productId, skuId, 99)),
|
||||
"请尽快送达"
|
||||
);
|
||||
|
||||
@@ -1789,33 +2081,29 @@ git commit -m "feat: add admin web MVP"
|
||||
### Task 10: Resident Miniapp MVP
|
||||
|
||||
**Files:**
|
||||
- Create: `miniapp/src/main.ts`
|
||||
- Create: `miniapp/src/App.vue`
|
||||
- Create: `miniapp/src/pages.json`
|
||||
- Create: `miniapp/src/api/*.ts`
|
||||
- Create: `miniapp/src/pages/home/index.vue`
|
||||
- Create: `miniapp/src/pages/products/index.vue`
|
||||
- Create: `miniapp/src/pages/products/detail.vue`
|
||||
- Create: `miniapp/src/pages/orders/index.vue`
|
||||
- Create: `miniapp/src/pages/orders/detail.vue`
|
||||
- Create: `miniapp/src/pages/express/create.vue`
|
||||
- Create: `miniapp/src/pages/group-buy/index.vue`
|
||||
- Create: `miniapp/src/pages/group-buy/detail.vue`
|
||||
- Create: `miniapp/src/pages/second-hand/index.vue`
|
||||
- Create: `miniapp/src/pages/second-hand/create.vue`
|
||||
- Create: `miniapp/src/pages/notices/index.vue`
|
||||
- Create: `miniapp/src/pages/profile/index.vue`
|
||||
- Create: `miniapp/src/pages/address/index.vue`
|
||||
- Test: `miniapp/src/api/expressApi.test.ts`
|
||||
- Create: `miniapp/miniprogram/api/http.js`
|
||||
- Create: `miniapp/miniprogram/api/homeApi.js`
|
||||
- Create: `miniapp/miniprogram/api/productApi.js`
|
||||
- Create: `miniapp/miniprogram/api/orderApi.js`
|
||||
- Create: `miniapp/miniprogram/api/expressApi.js`
|
||||
- Create: `miniapp/miniprogram/api/groupBuyApi.js`
|
||||
- Create: `miniapp/miniprogram/api/secondGoodsApi.js`
|
||||
- Create: `miniapp/miniprogram/api/noticeApi.js`
|
||||
- Create: `miniapp/miniprogram/api/addressApi.js`
|
||||
- Create: `miniapp/miniprogram/pages/home/index.js`
|
||||
- Create: `miniapp/miniprogram/pages/home/index.json`
|
||||
- Create: `miniapp/miniprogram/pages/home/index.wxml`
|
||||
- Create: `miniapp/miniprogram/pages/home/index.wxss`
|
||||
- Create: native page file sets under `miniapp/miniprogram/pages/products`, `orders`, `express`, `group-buy`, `second-hand`, `notices`, `profile`, and `address`
|
||||
- Test: `miniapp/miniprogram/api/expressApi.test.js`
|
||||
|
||||
- [ ] **Step 1: Create express fee UI helper test**
|
||||
|
||||
```ts
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { calculateExpressFeeCent } from './expressApi'
|
||||
```js
|
||||
const { calculateExpressFeeCent } = require('./expressApi')
|
||||
|
||||
describe('calculateExpressFeeCent', () => {
|
||||
it('matches backend express fee rule', () => {
|
||||
test('matches backend express fee rule', () => {
|
||||
expect(calculateExpressFeeCent(1)).toBe(300)
|
||||
expect(calculateExpressFeeCent(2)).toBe(400)
|
||||
expect(calculateExpressFeeCent(4)).toBe(600)
|
||||
@@ -1823,7 +2111,44 @@ describe('calculateExpressFeeCent', () => {
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Implement miniapp navigation**
|
||||
- [ ] **Step 2: Implement native miniapp API modules**
|
||||
|
||||
Create `miniapp/miniprogram/api/expressApi.js`.
|
||||
|
||||
```js
|
||||
const { request } = require('../utils/request')
|
||||
|
||||
function calculateExpressFeeCent(packageCount) {
|
||||
if (packageCount <= 0) {
|
||||
throw new Error('件数必须大于0')
|
||||
}
|
||||
return 300 + Math.max(0, packageCount - 1) * 100
|
||||
}
|
||||
|
||||
function createExpressOrder(data) {
|
||||
return request({
|
||||
url: '/api/mini/express-orders',
|
||||
method: 'POST',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
function listExpressOrders() {
|
||||
return request({
|
||||
url: '/api/mini/express-orders'
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
calculateExpressFeeCent,
|
||||
createExpressOrder,
|
||||
listExpressOrders
|
||||
}
|
||||
```
|
||||
|
||||
Create matching CommonJS modules for products, orders, group buy, secondhand, notices, and addresses. Each module exports named functions and calls `request()` from `miniprogram/utils/request.js`.
|
||||
|
||||
- [ ] **Step 3: Implement miniapp navigation**
|
||||
|
||||
Tabs:
|
||||
|
||||
@@ -1845,7 +2170,80 @@ Home shortcuts:
|
||||
我的订单
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Implement resident flows**
|
||||
- [ ] **Step 4: Implement native miniapp page pattern**
|
||||
|
||||
Use this structure for every page:
|
||||
|
||||
```text
|
||||
index.js page state, lifecycle methods, event handlers
|
||||
index.json navigation title and page-level component config
|
||||
index.wxml native miniapp markup
|
||||
index.wxss page styles
|
||||
```
|
||||
|
||||
Example `miniapp/miniprogram/pages/express/create.js`:
|
||||
|
||||
```js
|
||||
const { calculateExpressFeeCent, createExpressOrder } = require('../../api/expressApi')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
form: {
|
||||
expressCompany: '',
|
||||
pickupCode: '',
|
||||
pickupAddress: '',
|
||||
addressId: null,
|
||||
phone: '',
|
||||
packageCount: 1,
|
||||
remark: ''
|
||||
},
|
||||
feeCent: 300,
|
||||
submitting: false
|
||||
},
|
||||
|
||||
onPackageCountChange(event) {
|
||||
const packageCount = Number(event.detail.value || 1)
|
||||
this.setData({
|
||||
'form.packageCount': packageCount,
|
||||
feeCent: calculateExpressFeeCent(packageCount)
|
||||
})
|
||||
},
|
||||
|
||||
submit() {
|
||||
if (this.data.submitting) {
|
||||
return
|
||||
}
|
||||
this.setData({ submitting: true })
|
||||
createExpressOrder(this.data.form)
|
||||
.then(() => {
|
||||
wx.showToast({ title: '已提交' })
|
||||
wx.navigateBack()
|
||||
})
|
||||
.catch((error) => {
|
||||
wx.showToast({ title: error.message, icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ submitting: false })
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Example `miniapp/miniprogram/pages/express/create.wxml`:
|
||||
|
||||
```xml
|
||||
<view class="page">
|
||||
<input placeholder="快递公司" data-field="expressCompany" />
|
||||
<input placeholder="取件码" data-field="pickupCode" />
|
||||
<input placeholder="取件地址" data-field="pickupAddress" />
|
||||
<input placeholder="手机号" data-field="phone" />
|
||||
<input type="number" value="{{form.packageCount}}" bindinput="onPackageCountChange" />
|
||||
<view>预估费用:{{feeCent / 100}}元</view>
|
||||
<button loading="{{submitting}}" bindtap="submit">提交代取</button>
|
||||
</view>
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Implement resident flows**
|
||||
|
||||
Minimum flows:
|
||||
|
||||
@@ -1858,7 +2256,7 @@ Minimum flows:
|
||||
- Manage addresses and default address.
|
||||
- View order lists by type and detail pages.
|
||||
|
||||
- [ ] **Step 4: Verify**
|
||||
- [ ] **Step 6: Verify**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -1866,12 +2264,11 @@ Run:
|
||||
cd miniapp
|
||||
npm install
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
Expected: API helper tests pass and `npm run build` produces the WeChat miniapp output under `miniapp/dist/build/mp-weixin`.
|
||||
Expected: Jest helper tests pass. Open `miniapp/project.config.json` in WeChat DevTools and click Compile; expected result is that the native mini program compiles with no missing page, JSON, WXML, WXSS, or API module errors.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add miniapp
|
||||
@@ -2032,7 +2429,6 @@ npm test
|
||||
npm run build
|
||||
cd ..\miniapp
|
||||
npm test
|
||||
npm run build
|
||||
cd ..\rider-h5
|
||||
npm test
|
||||
npm run build
|
||||
@@ -2043,7 +2439,8 @@ Expected:
|
||||
|
||||
- Backend tests pass.
|
||||
- All frontend tests pass.
|
||||
- All builds succeed.
|
||||
- Admin web and rider H5 builds succeed.
|
||||
- Native miniapp helper tests pass, and `miniapp/project.config.json` compiles successfully in WeChat DevTools.
|
||||
- `git status --short` only shows intended changes before final commit.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
@@ -2073,7 +2470,8 @@ git commit -m "docs: add release runbook and seed data"
|
||||
The MVP is ready for one-community trial operation when:
|
||||
|
||||
- Backend full test suite passes.
|
||||
- Admin web, miniapp, and rider H5 builds pass.
|
||||
- Admin web and rider H5 builds pass.
|
||||
- Native miniapp compiles successfully in WeChat DevTools.
|
||||
- Manual checklist is completed once against local seed data.
|
||||
- Admin can move a goods order from submission to completion through dispatch.
|
||||
- Admin can move an express pickup order from submission to completion through dispatch.
|
||||
|
||||
@@ -105,9 +105,20 @@
|
||||
|
||||
一期配送方式采用后台派单。管理员将商品订单、快递代取订单或团购配送任务分配给配送员。
|
||||
|
||||
## 5. 小程序端 PRD
|
||||
## 5. 技术选型
|
||||
|
||||
### 5.1 首页
|
||||
一期技术选型确认如下:
|
||||
|
||||
- 后端:Java 8、Spring Boot 2.7.x、MyBatis Plus、Sa-Token、MySQL、Redis、Flyway、Knife4j、MinIO。
|
||||
- 小程序端:微信原生小程序,使用 WXML、WXSS、JavaScript 和 `wx.request` 封装请求。
|
||||
- 后台管理端:Vue 3、TypeScript、Vite、Element Plus。
|
||||
- 配送员端 H5:Vue 3、TypeScript、Vite。
|
||||
|
||||
因后端指定 Java 8,一期不采用 Spring Boot 3;Spring Boot 3 要求 Java 17 及以上。
|
||||
|
||||
## 6. 小程序端 PRD
|
||||
|
||||
### 6.1 首页
|
||||
|
||||
首页目标是让居民快速进入高频服务,并看到当前社区的运营内容。
|
||||
|
||||
@@ -137,7 +148,7 @@
|
||||
- 无热门商品时显示简短空状态。
|
||||
- 无公告时显示“暂无公告”。
|
||||
|
||||
### 5.2 商品预定
|
||||
### 6.2 商品预定
|
||||
|
||||
商品预定是一期核心交易模块,服务社区便利店、水果店、生鲜店等本地商户。
|
||||
|
||||
@@ -187,7 +198,7 @@
|
||||
- 自提订单不进入配送员派单流程。
|
||||
- 后台可取消异常订单,并记录取消原因。
|
||||
|
||||
### 5.3 快递代取
|
||||
### 6.3 快递代取
|
||||
|
||||
快递代取是高频服务模块,目标是降低居民取件成本,并为平台带来稳定服务费。
|
||||
|
||||
@@ -225,7 +236,7 @@
|
||||
- 配送员送达时可上传照片。
|
||||
- 若取件码错误或快递不存在,配送员可标记异常。
|
||||
|
||||
### 5.4 今日团购
|
||||
### 6.4 今日团购
|
||||
|
||||
团购用于社区集中采购和统一配送,一期做轻量预定,不做复杂成团机制。
|
||||
|
||||
@@ -263,7 +274,7 @@
|
||||
- 后台可按团购活动汇总订单。
|
||||
- 团购订单可选择配送或自提。
|
||||
|
||||
### 5.5 二手闲置
|
||||
### 6.5 二手闲置
|
||||
|
||||
二手闲置用于提升社区活跃度,一期只做信息发布和联系,不做在线交易。
|
||||
|
||||
@@ -301,7 +312,7 @@
|
||||
- 已下架。
|
||||
- 已成交。
|
||||
|
||||
### 5.6 社区公告
|
||||
### 6.6 社区公告
|
||||
|
||||
社区公告用于物业通知、停水停电、活动和运营通知。
|
||||
|
||||
@@ -321,7 +332,7 @@
|
||||
- 服务说明。
|
||||
- 其他。
|
||||
|
||||
### 5.7 我的
|
||||
### 6.7 我的
|
||||
|
||||
我的页面用于承载个人信息和用户资产。
|
||||
|
||||
@@ -337,9 +348,9 @@
|
||||
- 联系客服。
|
||||
- 服务协议和隐私政策入口。
|
||||
|
||||
## 6. 后台管理 PRD
|
||||
## 7. 后台管理 PRD
|
||||
|
||||
### 6.1 登录与权限
|
||||
### 7.1 登录与权限
|
||||
|
||||
后台支持账号密码登录。一期角色包括:
|
||||
|
||||
@@ -355,7 +366,7 @@
|
||||
- 商家只管理自己店铺的商品和订单。
|
||||
- 配送员只查看分配给自己的配送任务。
|
||||
|
||||
### 6.2 社区管理
|
||||
### 7.2 社区管理
|
||||
|
||||
社区管理用于 SaaS 预留,一期只维护当前小区。
|
||||
|
||||
@@ -376,7 +387,7 @@
|
||||
- 二手闲置。
|
||||
- 社区公告。
|
||||
|
||||
### 6.3 商家管理
|
||||
### 7.3 商家管理
|
||||
|
||||
字段包括:
|
||||
|
||||
@@ -395,7 +406,7 @@
|
||||
- 药店。
|
||||
- 其他。
|
||||
|
||||
### 6.4 商品管理
|
||||
### 7.4 商品管理
|
||||
|
||||
功能包括:
|
||||
|
||||
@@ -419,7 +430,7 @@
|
||||
- 单位。
|
||||
- 状态。
|
||||
|
||||
### 6.5 订单管理
|
||||
### 7.5 订单管理
|
||||
|
||||
订单管理包含商品订单、快递代取订单和团购订单。
|
||||
|
||||
@@ -449,7 +460,7 @@
|
||||
- 配送中订单取消需填写原因。
|
||||
- 已完成订单不可取消,只能做售后备注。
|
||||
|
||||
### 6.6 配送管理
|
||||
### 7.6 配送管理
|
||||
|
||||
功能包括:
|
||||
|
||||
@@ -468,7 +479,7 @@
|
||||
- 系统生成配送任务。
|
||||
- 配送员 H5 展示任务。
|
||||
|
||||
### 6.7 团购管理
|
||||
### 7.7 团购管理
|
||||
|
||||
功能包括:
|
||||
|
||||
@@ -487,7 +498,7 @@
|
||||
- 已完成。
|
||||
- 已取消。
|
||||
|
||||
### 6.8 二手审核
|
||||
### 7.8 二手审核
|
||||
|
||||
功能包括:
|
||||
|
||||
@@ -504,7 +515,7 @@
|
||||
- 联系方式是否异常。
|
||||
- 是否包含违法或高风险商品。
|
||||
|
||||
### 6.9 公告管理
|
||||
### 7.9 公告管理
|
||||
|
||||
功能包括:
|
||||
|
||||
@@ -521,7 +532,7 @@
|
||||
- 已发布。
|
||||
- 已下线。
|
||||
|
||||
### 6.10 数据概览
|
||||
### 7.10 数据概览
|
||||
|
||||
首页看板展示:
|
||||
|
||||
@@ -536,7 +547,7 @@
|
||||
|
||||
一期数据看板只做基础运营统计,不做复杂 BI 分析。
|
||||
|
||||
## 7. 配送员 H5 PRD
|
||||
## 8. 配送员 H5 PRD
|
||||
|
||||
配送员 H5 面向手机浏览器使用,重点是简单、稳定、少操作。
|
||||
|
||||
@@ -577,9 +588,9 @@
|
||||
- 用户拒收。
|
||||
- 其他。
|
||||
|
||||
## 8. 核心业务流程
|
||||
## 9. 核心业务流程
|
||||
|
||||
### 8.1 商品订单流程
|
||||
### 9.1 商品订单流程
|
||||
|
||||
1. 用户浏览商品。
|
||||
2. 用户选择规格、数量和配送方式。
|
||||
@@ -592,7 +603,7 @@
|
||||
9. 配送员送达并上传照片。
|
||||
10. 用户确认完成或后台确认完成。
|
||||
|
||||
### 8.2 快递代取流程
|
||||
### 9.2 快递代取流程
|
||||
|
||||
1. 用户填写快递代取信息。
|
||||
2. 系统根据件数计算预估费用。
|
||||
@@ -604,7 +615,7 @@
|
||||
8. 配送员送达并上传照片。
|
||||
9. 用户确认完成或后台确认完成。
|
||||
|
||||
### 8.3 团购流程
|
||||
### 9.3 团购流程
|
||||
|
||||
1. 后台发布团购活动。
|
||||
2. 用户浏览团购商品。
|
||||
@@ -615,7 +626,7 @@
|
||||
7. 后台安排配送或自提。
|
||||
8. 用户收货后完成订单。
|
||||
|
||||
### 8.4 二手闲置流程
|
||||
### 9.4 二手闲置流程
|
||||
|
||||
1. 用户发布闲置信息。
|
||||
2. 内容进入待审核。
|
||||
@@ -625,7 +636,7 @@
|
||||
6. 双方线下完成交易。
|
||||
7. 发布者可标记已成交或下架。
|
||||
|
||||
## 9. 数据设计概要
|
||||
## 10. 数据设计概要
|
||||
|
||||
一期数据结构需要支持单小区上线,并预留 SaaS 扩展字段。
|
||||
|
||||
@@ -674,9 +685,9 @@
|
||||
- payment_record:收款记录。
|
||||
- refund_record:退款记录。
|
||||
|
||||
## 10. 状态机设计
|
||||
## 11. 状态机设计
|
||||
|
||||
### 10.1 商品订单状态
|
||||
### 11.1 商品订单状态
|
||||
|
||||
- 待确认。
|
||||
- 待备货。
|
||||
@@ -686,7 +697,7 @@
|
||||
- 已完成。
|
||||
- 已取消。
|
||||
|
||||
### 10.2 快递代取状态
|
||||
### 11.2 快递代取状态
|
||||
|
||||
- 待确认。
|
||||
- 待取件。
|
||||
@@ -696,7 +707,7 @@
|
||||
- 已完成。
|
||||
- 已取消。
|
||||
|
||||
### 10.3 团购订单状态
|
||||
### 11.3 团购订单状态
|
||||
|
||||
- 待确认。
|
||||
- 已确认。
|
||||
@@ -706,7 +717,7 @@
|
||||
- 已完成。
|
||||
- 已取消。
|
||||
|
||||
### 10.4 闲置状态
|
||||
### 11.4 闲置状态
|
||||
|
||||
- 待审核。
|
||||
- 已发布。
|
||||
@@ -714,7 +725,7 @@
|
||||
- 已下架。
|
||||
- 已成交。
|
||||
|
||||
## 11. 异常与风控
|
||||
## 12. 异常与风控
|
||||
|
||||
订单异常:
|
||||
|
||||
@@ -739,7 +750,7 @@
|
||||
- 后台可下架违规内容。
|
||||
- 用户联系方式只在详情页展示。
|
||||
|
||||
## 12. 验收标准
|
||||
## 13. 验收标准
|
||||
|
||||
一期上线需满足:
|
||||
|
||||
@@ -754,7 +765,7 @@
|
||||
- 系统可支持一个小区真实试运营。
|
||||
- 数据模型预留 tenant_id、community_id、merchant_id 等 SaaS 扩展字段。
|
||||
|
||||
## 13. 后续扩展方向
|
||||
## 14. 后续扩展方向
|
||||
|
||||
二期可考虑:
|
||||
|
||||
@@ -771,7 +782,7 @@
|
||||
- 物业缴费。
|
||||
- 数据分析看板。
|
||||
|
||||
## 14. 待用户确认
|
||||
## 15. 待用户确认
|
||||
|
||||
本 PRD 已确定以下方向:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user