This commit is contained in:
@@ -3,7 +3,7 @@ name: 构建并部署前端到生产环境
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- iteration/august
|
||||
- dev
|
||||
- test
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
- name: 设置镜像标签
|
||||
id: tag
|
||||
run: |
|
||||
if [ "${{ github.ref }}" = "refs/heads/develop" ]; then
|
||||
if [ "${{ github.ref }}" = "refs/heads/iteration/august" ]; then
|
||||
echo "tag=latest" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.ref }}" = "refs/heads/dev" ]; then
|
||||
echo "tag=dev" >> $GITHUB_OUTPUT
|
||||
@@ -51,8 +51,8 @@ jobs:
|
||||
docker push ${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }}
|
||||
docker push ${{ env.IMAGE_NAME }}:${{ github.sha }}
|
||||
|
||||
- name: 部署到本地(仅 develop 分支)
|
||||
if: github.ref == 'refs/heads/develop'
|
||||
- name: 部署到本地(仅 iteration/august 分支)
|
||||
if: github.ref == 'refs/heads/iteration/august'
|
||||
run: |
|
||||
mkdir -p ${{ env.DEPLOY_DIR }}
|
||||
|
||||
|
||||
21
openspec/changes/add-agent-distribution-code/design.md
Normal file
21
openspec/changes/add-agent-distribution-code/design.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## Context
|
||||
|
||||
The Uni-app frontend already obtains the authenticated user through `GET /api/auth/me`. That user includes `user_type` and `shop_id`; `GET /api/admin/shops/{shop_id}` returns the shop's `distribution_code`. The home page currently has an agent-only “提现管理” shortcut, and `src/static/icons/分销码.png` is present as an untracked user-supplied asset.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
- Goals: expose a role-restricted shortcut after withdrawal management and show the current shop's distribution code as a scannable QR code.
|
||||
- Non-Goals: change backend contracts, generate or rotate distribution codes, or expose another shop's code.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Use the current authenticated user's `shop_id` from `/api/auth/me`; do not hardcode a shop ID or accept one from the page URL.
|
||||
- Fetch the shop record through a typed shop-detail API method and encode the returned `distribution_code` value itself in the QR code.
|
||||
- Generate/render the QR code locally with a Uni-app-compatible solution. Do not send distribution codes to a remote QR-generation service.
|
||||
- Add a dedicated page, use the supplied icon for the home shortcut, and re-check the current user type on the page so direct navigation does not bypass the agent-only UI.
|
||||
- Show a clear unavailable/error state when the account has no `shop_id`, the shop response has no distribution code, or the request fails.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- QR rendering APIs differ across Uni-app targets. Select a local renderer compatible with the project's supported H5 and mini-program builds and verify it in the target runtime.
|
||||
- The supplied icon is currently untracked; implementation must preserve and use it without replacing or deleting it.
|
||||
16
openspec/changes/add-agent-distribution-code/proposal.md
Normal file
16
openspec/changes/add-agent-distribution-code/proposal.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Change: Add agent distribution code viewing
|
||||
|
||||
## Why
|
||||
|
||||
Agent accounts need a quick way to share their shop's distribution code. The authenticated user response already provides `shop_id`, and the shop detail endpoint returns `distribution_code`, but the agent home page does not expose or display it.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add a “查看我的分销码” shortcut immediately after “提现管理” on the agent home page, visible only when `user_type` is `3`.
|
||||
- Add a distribution-code page that uses the current authenticated user's `shop_id` to fetch `/api/admin/shops/{shop_id}` and renders the returned `distribution_code` as a QR code.
|
||||
- Reuse the existing `src/static/icons/分销码.png` shortcut icon.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected specs: `agent-distribution-code` (new capability)
|
||||
- Affected code: `src/pages/agent-system/home/index.vue`, `src/pages/agent-system/distribution-code/index.vue`, `src/pages.json`, and the shop API module; a Uni-app-compatible local QR renderer may require a dependency.
|
||||
@@ -0,0 +1,23 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Agent users can view their shop distribution code
|
||||
|
||||
The authenticated home page SHALL show a “查看我的分销码” shortcut immediately after “提现管理” only when the current user's `user_type` is `3`. The shortcut SHALL use `src/static/icons/分销码.png`. Opening it SHALL use the current user's `shop_id` to retrieve that shop's `distribution_code` from `/api/admin/shops/{shop_id}` and display a QR code encoding that exact value. The code SHALL be generated locally and SHALL NOT be sent to a third-party QR service.
|
||||
|
||||
#### Scenario: Agent sees and opens the shortcut
|
||||
- **GIVEN** `/api/auth/me` returns a user with `user_type` `3` and a valid `shop_id`
|
||||
- **WHEN** the home page displays its quick actions
|
||||
- **THEN** “查看我的分销码” appears immediately after “提现管理” with the supplied distribution-code icon
|
||||
- **AND** opening it displays the current shop's distribution code as a QR code
|
||||
|
||||
#### Scenario: Non-agent does not see the shortcut
|
||||
- **GIVEN** `/api/auth/me` returns a user whose `user_type` is not `3`
|
||||
- **WHEN** the home page displays its quick actions
|
||||
- **THEN** the distribution-code shortcut is not shown
|
||||
- **AND** direct navigation to the distribution-code page does not reveal a code
|
||||
|
||||
#### Scenario: Shop or distribution code is unavailable
|
||||
- **GIVEN** an agent opens the distribution-code page
|
||||
- **WHEN** the current account has no `shop_id`, the shop request fails, or the shop has no `distribution_code`
|
||||
- **THEN** no QR code is rendered
|
||||
- **AND** the page explains that the distribution code is currently unavailable
|
||||
12
openspec/changes/add-agent-distribution-code/tasks.md
Normal file
12
openspec/changes/add-agent-distribution-code/tasks.md
Normal file
@@ -0,0 +1,12 @@
|
||||
## 1. Proposal Review
|
||||
- [x] 1.1 Review and approve this proposal before implementation
|
||||
|
||||
## 2. Implementation
|
||||
- [x] 2.1 Add a typed shop-detail API method for `/api/admin/shops/{shop_id}` and its `distribution_code` field
|
||||
- [x] 2.2 Add a distribution-code page that checks the current user type, fetches the current shop, and renders the code locally as a QR
|
||||
- [x] 2.3 Register the page and add the agent-only home shortcut immediately after withdrawal management using the supplied icon
|
||||
- [x] 2.4 Handle missing shop/code and request failure states
|
||||
|
||||
## 3. Verification
|
||||
- [x] 3.1 Run targeted ESLint and type-check; record unrelated existing type-check failures
|
||||
- [x] 3.2 Verify role gating, `shop_id` lookup, and QR content mapping, and build H5 and WeChat mini-program targets
|
||||
@@ -91,6 +91,7 @@
|
||||
"destr": "^2.0.5",
|
||||
"pinia": "2.2.4",
|
||||
"pinia-plugin-persistedstate": "4.1.3",
|
||||
"qrcode-generator": "^2.0.4",
|
||||
"uview-plus": "^3.7.13",
|
||||
"vue": "3.4.21",
|
||||
"vue-i18n": "9.1.9",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -74,6 +74,9 @@ importers:
|
||||
pinia-plugin-persistedstate:
|
||||
specifier: 4.1.3
|
||||
version: 4.1.3(pinia@2.2.4(typescript@5.8.3)(vue@3.4.21(typescript@5.8.3)))
|
||||
qrcode-generator:
|
||||
specifier: ^2.0.4
|
||||
version: 2.0.4
|
||||
uview-plus:
|
||||
specifier: ^3.7.13
|
||||
version: 3.7.13
|
||||
@@ -7304,6 +7307,9 @@ packages:
|
||||
resolution: {integrity: sha512-kXuQdQTB6oN3KhI6V4acnBSZx8D2I4xzZvn9+wFLLFCoBNQY/sFnCW6c43OL7pOQ2HvGV4lnWIXNmgfp7cTWhQ==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
qrcode-generator@2.0.4:
|
||||
resolution: {integrity: sha512-mZSiP6RnbHl4xL2Ap5HfkjLnmxfKcPWpWe/c+5XxCuetEenqmNFf1FH/ftXPCtFG5/TDobjsjz6sSNL0Sr8Z9g==}
|
||||
|
||||
qrcode-reader@1.0.4:
|
||||
resolution: {integrity: sha512-rRjALGNh9zVqvweg1j5OKIQKNsw3bLC+7qwlnead5K/9cb1cEIAGkwikt/09U0K+2IDWGD9CC6SP7tHAjUeqvQ==}
|
||||
|
||||
@@ -18535,6 +18541,8 @@ snapshots:
|
||||
dependencies:
|
||||
hookified: 1.15.0
|
||||
|
||||
qrcode-generator@2.0.4: {}
|
||||
|
||||
qrcode-reader@1.0.4: {}
|
||||
|
||||
qrcode-terminal@0.12.0: {}
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
import { get } from '@/utils/request'
|
||||
import { get } from '@/utils/request';
|
||||
|
||||
export interface ShopCascadeParams {
|
||||
parent_id?: number
|
||||
shop_name?: string
|
||||
parent_id?: number;
|
||||
shop_name?: string;
|
||||
}
|
||||
|
||||
export interface ShopCascadeItem {
|
||||
id: number
|
||||
shop_name: string
|
||||
has_children: boolean
|
||||
id: number;
|
||||
shop_name: string;
|
||||
has_children: boolean;
|
||||
}
|
||||
|
||||
export interface ShopDetail {
|
||||
id: number;
|
||||
shop_name: string;
|
||||
shop_code: string;
|
||||
distribution_code?: string | null;
|
||||
}
|
||||
|
||||
export function getShopCascade(params?: ShopCascadeParams) {
|
||||
return get<ShopCascadeItem[]>('/api/admin/shops/cascade', { params })
|
||||
return get<ShopCascadeItem[]>('/api/admin/shops/cascade', { params });
|
||||
}
|
||||
|
||||
export function getShopDetail(shopId: number) {
|
||||
return get<ShopDetail>(`/api/admin/shops/${shopId}`);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,14 @@
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/agent-system/distribution-code/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的分销码",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"navigationBarTextStyle": "black"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/agent-system/asset-wallet/index",
|
||||
"style": {
|
||||
|
||||
136
src/pages/agent-system/distribution-code/index.vue
Normal file
136
src/pages/agent-system/distribution-code/index.vue
Normal file
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view class="bg-page min-h-screen px-4 pt-6 pb-safe">
|
||||
<view class="rounded-16px bg-white px-5 py-6">
|
||||
<text class="mb-6 block text-center text-18px text-[#212121] font-700">
|
||||
我的分销码
|
||||
</text>
|
||||
|
||||
<view v-if="loading && !distributionCode" class="min-h-64 center">
|
||||
<text class="text-14px text-[#999]">
|
||||
正在加载分销码...
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="errorMessage" class="min-h-64 center flex-col">
|
||||
<text class="text-center text-14px text-[#7a8590]">
|
||||
{{ errorMessage }}
|
||||
</text>
|
||||
<button class="bg-brand mt-4 rounded-8px px-6 py-2 text-14px text-white" @click="loadDistributionCode">
|
||||
重试
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<view v-else-if="distributionCode" class="flex flex-col items-center">
|
||||
<view class="rounded-12px bg-white p-2 shadow-sm">
|
||||
<canvas
|
||||
id="distribution-code-qr"
|
||||
canvas-id="distribution-code-qr"
|
||||
class="qr-canvas"
|
||||
style="width: 240px; height: 240px;"
|
||||
/>
|
||||
</view>
|
||||
<text class="mt-4 text-13px text-[#7a8590]">
|
||||
扫描二维码获取分销信息
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import qrcodeGenerator from 'qrcode-generator';
|
||||
import { nextTick, onMounted, ref } from 'vue';
|
||||
import { getUserInfo } from '@/api/auth';
|
||||
import { getShopDetail } from '@/api/shop';
|
||||
|
||||
const QR_CANVAS_ID = 'distribution-code-qr';
|
||||
const QR_CANVAS_SIZE = 240;
|
||||
const QR_QUIET_ZONE = 4;
|
||||
|
||||
const loading = ref(true);
|
||||
const distributionCode = ref('');
|
||||
const errorMessage = ref('');
|
||||
|
||||
function drawQrCode(value: string) {
|
||||
const qrCode = qrcodeGenerator(0, 'M');
|
||||
qrCode.addData(value);
|
||||
qrCode.make();
|
||||
|
||||
const moduleCount = qrCode.getModuleCount();
|
||||
const moduleSize = QR_CANVAS_SIZE / (moduleCount + QR_QUIET_ZONE * 2);
|
||||
const context = uni.createCanvasContext(QR_CANVAS_ID);
|
||||
|
||||
context.setFillStyle('#ffffff');
|
||||
context.fillRect(0, 0, QR_CANVAS_SIZE, QR_CANVAS_SIZE);
|
||||
context.setFillStyle('#1F2937');
|
||||
|
||||
for (let row = 0; row < moduleCount; row += 1) {
|
||||
for (let column = 0; column < moduleCount; column += 1) {
|
||||
if (qrCode.isDark(row, column)) {
|
||||
context.fillRect(
|
||||
(column + QR_QUIET_ZONE) * moduleSize,
|
||||
(row + QR_QUIET_ZONE) * moduleSize,
|
||||
moduleSize + 0.25,
|
||||
moduleSize + 0.25,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.draw(false);
|
||||
}
|
||||
|
||||
async function loadDistributionCode() {
|
||||
loading.value = true;
|
||||
distributionCode.value = '';
|
||||
errorMessage.value = '';
|
||||
|
||||
try {
|
||||
const user = await getUserInfo();
|
||||
if (user.user_type !== 3) {
|
||||
errorMessage.value = '当前账号无权查看分销码';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!user.shop_id) {
|
||||
errorMessage.value = '未获取到当前店铺信息,暂无法查看分销码';
|
||||
return;
|
||||
}
|
||||
|
||||
const shop = await getShopDetail(user.shop_id);
|
||||
const code = shop.distribution_code?.trim();
|
||||
if (!code) {
|
||||
errorMessage.value = '当前店铺暂无可用的分销码';
|
||||
return;
|
||||
}
|
||||
|
||||
distributionCode.value = code;
|
||||
await nextTick();
|
||||
drawQrCode(code);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('加载分销码失败:', error);
|
||||
distributionCode.value = '';
|
||||
errorMessage.value = '分销码加载失败,请稍后重试';
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadDistributionCode();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qr-canvas {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -142,6 +142,14 @@
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 代理端: 查看我的分销码 -->
|
||||
<view v-if="userInfo?.user_type === 3" class="aspect-square flex flex-col items-center justify-center px-1 py-3" @click="navigateTo('/pages/agent-system/distribution-code/index')">
|
||||
<image src="@/static/icons/分销码.png" class="mb-1.5 h-8 w-8 rounded-8px" mode="aspectFit" />
|
||||
<text class="text-12px text-[#212121] font-500">
|
||||
查看我的分销码
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
<view class="aspect-square flex flex-col items-center justify-center px-1 py-3" @click="navigateTo('/pages/agent-system/change-password/index')">
|
||||
<image src="@/static/icons/修改密码.png" class="mb-1.5 h-8 w-8 rounded-8px" mode="aspectFit" />
|
||||
|
||||
BIN
src/static/icons/分销码.png
Normal file
BIN
src/static/icons/分销码.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
Reference in New Issue
Block a user