docs: align stack with java 8 and native miniapp

This commit is contained in:
王鹏
2026-07-07 14:56:26 +08:00
parent e853b8f7d8
commit 95685efa82
2 changed files with 533 additions and 124 deletions

View File

@@ -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. **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 │ └── src
├── miniapp ├── miniapp
│ ├── package.json │ ├── package.json
── src ── project.config.json
│ └── miniprogram
│ ├── app.js
│ ├── app.json
│ ├── app.wxss
│ ├── api
│ ├── components
│ ├── pages
│ └── utils
├── rider-h5 ├── rider-h5
│ ├── package.json │ ├── package.json
│ ├── vite.config.ts │ ├── vite.config.ts
@@ -79,13 +87,13 @@ Boundary rules:
- `backend/src/main/java/com/linhelp/common` contains cross-cutting code only. - `backend/src/main/java/com/linhelp/common` contains cross-cutting code only.
- Each business package owns its entity, mapper, service, controller, DTOs, and tests. - 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`. - 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. - Use table names `app_user`, `goods_order`, and `sys_role` instead of reserved or ambiguous names.
## Shared Domain Vocabulary ## 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 ```text
GoodsOrderStatus: PENDING_CONFIRM, PREPARING, PENDING_DELIVERY, PENDING_PICKUP, DELIVERING, COMPLETED, CANCELED 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/package.json`
- Create: `admin-web/vite.config.ts` - Create: `admin-web/vite.config.ts`
- Create: `miniapp/package.json` - 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/package.json`
- Create: `rider-h5/vite.config.ts` - Create: `rider-h5/vite.config.ts`
- Create: `deploy/docker-compose.dev.yml` - Create: `deploy/docker-compose.dev.yml`
@@ -118,7 +131,7 @@ DeliveryMethod: IMMEDIATE, SCHEDULED, SELF_PICKUP
- [ ] **Step 1: Create backend Spring Boot skeleton** - [ ] **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 ```xml
<project xmlns="http://maven.apache.org/POM/4.0.0" <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> <version>0.1.0-SNAPSHOT</version>
<name>linhelp-backend</name> <name>linhelp-backend</name>
<properties> <properties>
<java.version>17</java.version> <java.version>1.8</java.version>
<spring-boot.version>3.3.2</spring-boot.version> <maven.compiler.source>1.8</maven.compiler.source>
<mybatis-plus.version>3.5.7</mybatis-plus.version> <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> <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> <minio.version>8.5.11</minio.version>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -163,30 +179,26 @@ Add `backend/pom.xml` with Java 17, Spring Boot 3, MyBatis Plus, Sa-Token, MySQL
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>cn.dev33</groupId> <groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId> <artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token.version}</version> <version>${sa-token.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope> <scope>runtime</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.flywaydb</groupId> <groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId> <artifactId>flyway-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.github.xiaoymin</groupId> <groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId> <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
<version>${knife4j.version}</version> <version>${knife4j.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@@ -202,6 +214,15 @@ Add `backend/pom.xml` with Java 17, Spring Boot 3, MyBatis Plus, Sa-Token, MySQL
</dependencies> </dependencies>
<build> <build>
<plugins> <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> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
@@ -290,10 +311,9 @@ spring:
url: jdbc:mysql://localhost:3306/linhelp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai url: jdbc:mysql://localhost:3306/linhelp?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: linhelp username: linhelp
password: linhelp password: linhelp
data: redis:
redis: host: localhost
host: localhost port: 6379
port: 6379
flyway: flyway:
enabled: true enabled: true
locations: classpath:db/migration 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 ```json
{ {
"name": "linhelp-miniapp", "name": "linhelp-miniapp",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev:mp-weixin": "uni -p mp-weixin", "test": "jest --runInBand",
"build": "uni build -p mp-weixin", "ci:preview": "miniprogram-ci preview --pp ./project.config.json --robot 1"
"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"
}, },
"devDependencies": { "devDependencies": {
"@dcloudio/types": "^3.4.8", "jest": "^29.7.0",
"@dcloudio/uni-cli-shared": "^3.0.0", "miniprogram-ci": "^2.0.0"
"@dcloudio/vite-plugin-uni": "^3.0.0",
"typescript": "^5.5.3",
"vite": "^5.3.3",
"vitest": "^2.0.2"
} }
} }
``` ```
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`. Create `rider-h5/package.json`.
```json ```json
@@ -482,22 +614,59 @@ Expected: FAIL because enum classes do not exist.
- [ ] **Step 3: Create common API wrappers** - [ ] **Step 3: Create common API wrappers**
Create `ApiResponse.java`. Create `ApiResponse.java`. Use a normal Java class because the backend targets Java 8.
```java ```java
package com.linhelp.common.api; 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) { 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() { 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) { 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; 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 ```java
package com.linhelp.common.enums; package com.linhelp.common.enums;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -532,16 +750,21 @@ public enum GoodsOrderStatus {
COMPLETED, COMPLETED,
CANCELED; CANCELED;
private static final Map<GoodsOrderStatus, Set<GoodsOrderStatus>> TRANSITIONS = Map.of( private static final Map<GoodsOrderStatus, Set<GoodsOrderStatus>> TRANSITIONS;
PENDING_CONFIRM, Set.of(PREPARING, CANCELED),
PREPARING, Set.of(PENDING_DELIVERY, PENDING_PICKUP, CANCELED), static {
PENDING_DELIVERY, Set.of(DELIVERING, CANCELED), Map<GoodsOrderStatus, Set<GoodsOrderStatus>> transitions = new EnumMap<GoodsOrderStatus, Set<GoodsOrderStatus>>(GoodsOrderStatus.class);
PENDING_PICKUP, Set.of(COMPLETED, CANCELED), transitions.put(PENDING_CONFIRM, EnumSet.of(PREPARING, CANCELED));
DELIVERING, Set.of(COMPLETED, 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) { 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(); 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: Create request/response DTOs:
```java ```java
public record LoginRequest(String username, String password) {} public class LoginRequest {
public record LoginResponse(String token, Long userId, Long tenantId, Long communityId, String roleCode) {} 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: Create `AuthController` endpoints:
@@ -1270,7 +1562,7 @@ void cannotCreateGoodsOrderWhenSkuStockIsInsufficient() {
addressId, addressId,
DeliveryMethod.IMMEDIATE, DeliveryMethod.IMMEDIATE,
null, 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 ### Task 10: Resident Miniapp MVP
**Files:** **Files:**
- Create: `miniapp/src/main.ts` - Create: `miniapp/miniprogram/api/http.js`
- Create: `miniapp/src/App.vue` - Create: `miniapp/miniprogram/api/homeApi.js`
- Create: `miniapp/src/pages.json` - Create: `miniapp/miniprogram/api/productApi.js`
- Create: `miniapp/src/api/*.ts` - Create: `miniapp/miniprogram/api/orderApi.js`
- Create: `miniapp/src/pages/home/index.vue` - Create: `miniapp/miniprogram/api/expressApi.js`
- Create: `miniapp/src/pages/products/index.vue` - Create: `miniapp/miniprogram/api/groupBuyApi.js`
- Create: `miniapp/src/pages/products/detail.vue` - Create: `miniapp/miniprogram/api/secondGoodsApi.js`
- Create: `miniapp/src/pages/orders/index.vue` - Create: `miniapp/miniprogram/api/noticeApi.js`
- Create: `miniapp/src/pages/orders/detail.vue` - Create: `miniapp/miniprogram/api/addressApi.js`
- Create: `miniapp/src/pages/express/create.vue` - Create: `miniapp/miniprogram/pages/home/index.js`
- Create: `miniapp/src/pages/group-buy/index.vue` - Create: `miniapp/miniprogram/pages/home/index.json`
- Create: `miniapp/src/pages/group-buy/detail.vue` - Create: `miniapp/miniprogram/pages/home/index.wxml`
- Create: `miniapp/src/pages/second-hand/index.vue` - Create: `miniapp/miniprogram/pages/home/index.wxss`
- Create: `miniapp/src/pages/second-hand/create.vue` - Create: native page file sets under `miniapp/miniprogram/pages/products`, `orders`, `express`, `group-buy`, `second-hand`, `notices`, `profile`, and `address`
- Create: `miniapp/src/pages/notices/index.vue` - Test: `miniapp/miniprogram/api/expressApi.test.js`
- Create: `miniapp/src/pages/profile/index.vue`
- Create: `miniapp/src/pages/address/index.vue`
- Test: `miniapp/src/api/expressApi.test.ts`
- [ ] **Step 1: Create express fee UI helper test** - [ ] **Step 1: Create express fee UI helper test**
```ts ```js
import { describe, expect, it } from 'vitest' const { calculateExpressFeeCent } = require('./expressApi')
import { calculateExpressFeeCent } from './expressApi'
describe('calculateExpressFeeCent', () => { describe('calculateExpressFeeCent', () => {
it('matches backend express fee rule', () => { test('matches backend express fee rule', () => {
expect(calculateExpressFeeCent(1)).toBe(300) expect(calculateExpressFeeCent(1)).toBe(300)
expect(calculateExpressFeeCent(2)).toBe(400) expect(calculateExpressFeeCent(2)).toBe(400)
expect(calculateExpressFeeCent(4)).toBe(600) 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: 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: Minimum flows:
@@ -1858,7 +2256,7 @@ Minimum flows:
- Manage addresses and default address. - Manage addresses and default address.
- View order lists by type and detail pages. - View order lists by type and detail pages.
- [ ] **Step 4: Verify** - [ ] **Step 6: Verify**
Run: Run:
@@ -1866,12 +2264,11 @@ Run:
cd miniapp cd miniapp
npm install npm install
npm test 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 ```powershell
git add miniapp git add miniapp
@@ -2032,7 +2429,6 @@ npm test
npm run build npm run build
cd ..\miniapp cd ..\miniapp
npm test npm test
npm run build
cd ..\rider-h5 cd ..\rider-h5
npm test npm test
npm run build npm run build
@@ -2043,7 +2439,8 @@ Expected:
- Backend tests pass. - Backend tests pass.
- All frontend 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. - `git status --short` only shows intended changes before final commit.
- [ ] **Step 5: 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: The MVP is ready for one-community trial operation when:
- Backend full test suite passes. - 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. - Manual checklist is completed once against local seed data.
- Admin can move a goods order from submission to completion through dispatch. - 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. - Admin can move an express pickup order from submission to completion through dispatch.

View File

@@ -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。
- 配送员端 H5Vue 3、TypeScript、Vite。
因后端指定 Java 8一期不采用 Spring Boot 3Spring 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 预留,一期只维护当前小区。 社区管理用于 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 展示任务。 - 配送员 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 分析。 一期数据看板只做基础运营统计,不做复杂 BI 分析。
## 7. 配送员 H5 PRD ## 8. 配送员 H5 PRD
配送员 H5 面向手机浏览器使用,重点是简单、稳定、少操作。 配送员 H5 面向手机浏览器使用,重点是简单、稳定、少操作。
@@ -577,9 +588,9 @@
- 用户拒收。 - 用户拒收。
- 其他。 - 其他。
## 8. 核心业务流程 ## 9. 核心业务流程
### 8.1 商品订单流程 ### 9.1 商品订单流程
1. 用户浏览商品。 1. 用户浏览商品。
2. 用户选择规格、数量和配送方式。 2. 用户选择规格、数量和配送方式。
@@ -592,7 +603,7 @@
9. 配送员送达并上传照片。 9. 配送员送达并上传照片。
10. 用户确认完成或后台确认完成。 10. 用户确认完成或后台确认完成。
### 8.2 快递代取流程 ### 9.2 快递代取流程
1. 用户填写快递代取信息。 1. 用户填写快递代取信息。
2. 系统根据件数计算预估费用。 2. 系统根据件数计算预估费用。
@@ -604,7 +615,7 @@
8. 配送员送达并上传照片。 8. 配送员送达并上传照片。
9. 用户确认完成或后台确认完成。 9. 用户确认完成或后台确认完成。
### 8.3 团购流程 ### 9.3 团购流程
1. 后台发布团购活动。 1. 后台发布团购活动。
2. 用户浏览团购商品。 2. 用户浏览团购商品。
@@ -615,7 +626,7 @@
7. 后台安排配送或自提。 7. 后台安排配送或自提。
8. 用户收货后完成订单。 8. 用户收货后完成订单。
### 8.4 二手闲置流程 ### 9.4 二手闲置流程
1. 用户发布闲置信息。 1. 用户发布闲置信息。
2. 内容进入待审核。 2. 内容进入待审核。
@@ -625,7 +636,7 @@
6. 双方线下完成交易。 6. 双方线下完成交易。
7. 发布者可标记已成交或下架。 7. 发布者可标记已成交或下架。
## 9. 数据设计概要 ## 10. 数据设计概要
一期数据结构需要支持单小区上线,并预留 SaaS 扩展字段。 一期数据结构需要支持单小区上线,并预留 SaaS 扩展字段。
@@ -674,9 +685,9 @@
- payment_record收款记录。 - payment_record收款记录。
- refund_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 扩展字段。 - 数据模型预留 tenant_id、community_id、merchant_id 等 SaaS 扩展字段。
## 13. 后续扩展方向 ## 14. 后续扩展方向
二期可考虑: 二期可考虑:
@@ -771,7 +782,7 @@
- 物业缴费。 - 物业缴费。
- 数据分析看板。 - 数据分析看板。
## 14. 待用户确认 ## 15. 待用户确认
本 PRD 已确定以下方向: 本 PRD 已确定以下方向: