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,10 +311,9 @@ spring:
|
||||
url: jdbc:mysql://localhost:3306/linhelp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
|
||||
username: linhelp
|
||||
password: linhelp
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
redis:
|
||||
host: localhost
|
||||
port: 6379
|
||||
flyway:
|
||||
enabled: true
|
||||
locations: classpath:db/migration
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user