Compare commits

..

101 Commits

Author SHA1 Message Date
08dd44cc1d Merge branch 'test'
All checks were successful
CI Build & Deploy / build-and-deploy-dev (push) Has been skipped
CI Build & Deploy / build-and-deploy-prod (push) Successful in 1m49s
# Conflicts:
#	.gitea/workflows/ci.yaml
2025-11-05 11:52:58 +08:00
58985b9a6b ci(other): ci脚本换源
All checks were successful
CI Build & Deploy / build-and-deploy-dev (push) Successful in 1m50s
CI Build & Deploy / build-and-deploy-prod (push) Has been skipped
2025-11-05 11:51:43 +08:00
4b21481de9 ci(other): ci update 2025-10-31 18:10:03 +08:00
1c63b0cb4c refactor(custom): 地图服务商更换 2025-10-31 18:10:03 +08:00
5e14e93a21 refactor(custom): 地图Key变更 2025-10-31 18:10:03 +08:00
ee659da4c1 ci(other): ci update 2025-10-31 18:09:32 +08:00
c2b0e49f7b feat(custom): 多API版本
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-23 18:31:11 +08:00
e791fef430 refactor(custom): 地图服务商更换 2025-09-23 18:31:10 +08:00
3b9cee3a65 refactor(custom): 地图Key变更 2025-09-23 18:31:10 +08:00
b4dfb62193 feat(custom): 多API版本
All checks were successful
continuous-integration/drone/push Build is passing
2025-09-23 18:29:52 +08:00
af76eda747 refactor(custom): 地图服务商更换 2025-09-23 18:29:52 +08:00
55d59131a6 refactor(custom): 地图Key变更 2025-09-23 18:28:44 +08:00
68e02ecc84 refactor(custom): 地图服务商更换
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-16 18:02:55 +08:00
e4df9adce8 refactor(custom): 地图服务商更换
All checks were successful
continuous-integration/drone/push Build is passing
2025-06-16 18:01:57 +08:00
646ba7165f refactor(custom): 地图Key变更
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-29 15:57:46 +08:00
a6e570d936 refactor(custom): 地图Key变更
All checks were successful
continuous-integration/drone/push Build is passing
2025-05-29 15:57:35 +08:00
6c69a1b296 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-18 19:49:16 +08:00
8a885d9c5e Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-14 22:11:19 +08:00
614923b395 feat(custom): 现金部分
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-14 22:10:45 +08:00
36612722e1 mod(custom): \!
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-05 04:21:55 +08:00
64d1923336 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-12 01:43:43 +08:00
ec3d7e20e5 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-09 16:07:57 +08:00
5041b83386 feat(custom): \!
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-09 16:07:44 +08:00
c5f299d4aa Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-06 21:39:30 +08:00
13d6bdd5f0 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-06 21:10:44 +08:00
fe15b87b42 feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-06 21:10:22 +08:00
59de06f8e8 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-05 20:30:36 +08:00
57ff4e3cb1 feat(custom): \!
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-05 20:29:06 +08:00
dc70eb3000 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-05 16:42:24 +08:00
25c836c008 feat(custom): \!
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-05 16:38:54 +08:00
6756f80cd6 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-03 22:22:13 +08:00
0fe04dd0c3 feat(custom): !
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-03 22:21:16 +08:00
0c47b09064 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-05 18:10:50 +08:00
c97f3c655f Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-05 18:10:42 +08:00
fd715b51a8 feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-05 18:10:10 +08:00
630dad47dc Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-05 05:35:10 +08:00
c0d46d3b7b Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-05 05:35:00 +08:00
829f86908f feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-05 05:34:46 +08:00
b87ae167bb Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-01 21:43:42 +08:00
736675608a mod(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-08-01 21:43:10 +08:00
ca60223245 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-31 21:04:43 +08:00
1306e2acf6 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-29 15:12:03 +08:00
3782abcd2e feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-29 15:11:50 +08:00
537e155050 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-28 10:47:13 +08:00
11386f9864 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-28 10:47:07 +08:00
3f11741c33 feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-28 10:46:54 +08:00
627d618b65 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-22 18:23:19 +08:00
592fb2ede7 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-22 18:23:11 +08:00
2f11eae9d2 mod(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-07-22 18:22:51 +08:00
85b592adec Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 16:08:28 +08:00
f868be28a3 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 16:08:18 +08:00
10ed1bb6d6 feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-20 16:08:04 +08:00
8751b78d8d Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-17 18:49:08 +08:00
346930fac9 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-17 18:48:56 +08:00
df796d6a1d fix(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-17 18:48:33 +08:00
e7e050d107 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-17 18:16:09 +08:00
0744da45f6 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-17 18:16:01 +08:00
f899201e4a fix(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-17 18:15:49 +08:00
981fff22de fix(custom): 2024-05-17 18:15:35 +08:00
8f8e024cfe Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-15 17:31:37 +08:00
db9d37d370 feat(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-15 17:31:19 +08:00
1f963a84f7 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-12 18:55:29 +08:00
aba5d6d6c6 feat(custom): 新增活动订单核销
All checks were successful
continuous-integration/drone/push Build is passing
2024-04-12 18:54:54 +08:00
79f34be500 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-20 16:37:56 +08:00
e139fed2a6 feat(custom): 新增若干功能
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-20 16:35:15 +08:00
c563bf8cd6 Merge branch 'test'
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-15 16:09:37 +08:00
760c843060 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-09 18:18:27 +08:00
71f72ff03d feat(custom): 增加后结统计时间筛选
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-09 18:17:51 +08:00
af20de8776 Merge branch 'dev' into test
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-08 20:55:00 +08:00
dd49ee9cd6 ci(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-08 20:54:49 +08:00
3a246511e1 ci(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-08 19:15:47 +08:00
7d7ac808f8 ci(custom):
All checks were successful
continuous-integration/drone/push Build is passing
2024-03-08 19:06:17 +08:00
a53552943a Merge branch 'dev' into test
Some checks failed
continuous-integration/drone Build is failing
2024-03-08 18:59:17 +08:00
eb1f6ff400 ci(custom): update .drone.yml 2024-03-08 18:58:53 +08:00
2faf99c2ff ci(custom): add CICD 2024-03-08 18:58:06 +08:00
c8974937ff Merge branch 'dev' into test 2024-03-07 18:58:37 +08:00
011284c5dc Merge branch 'dev' into test 2024-03-05 21:20:39 +08:00
a0f1d29597 Merge branch 'dev' into test 2024-03-04 21:20:31 +08:00
528c405f63 Merge branch 'test' 2024-01-27 14:13:47 +08:00
74a4aa3ab2 Merge branch 'dev' into test 2024-01-25 22:48:31 +08:00
fca2672393 Merge branch 'dev' into test 2024-01-22 22:25:25 +08:00
aa3220186e Merge branch 'test' 2023-12-25 17:05:52 +08:00
28a6b326e4 Merge branch 'dev' into test 2023-12-17 21:49:22 +08:00
936fbca87e Merge branch 'test' 2023-12-15 20:48:25 +08:00
c9b590fcb2 Merge branch 'dev' into test 2023-12-15 20:48:04 +08:00
462047d560 Merge branch 'test' 2023-12-13 13:21:21 +08:00
40d27d2032 Merge branch 'dev' into test 2023-12-13 13:21:05 +08:00
5cf8469009 Merge branch 'test' 2023-12-03 22:24:39 +08:00
99336f5304 Merge branch 'dev' into test 2023-12-03 22:24:19 +08:00
66589f52b3 Merge branch 'test' 2023-12-03 21:37:54 +08:00
fd5bc032a6 Merge branch 'dev' into test 2023-12-03 21:37:21 +08:00
7d1cc49a6d mod(custom): merge branch 'test' 2023-11-29 15:38:06 +08:00
8fbe2c84b7 Merge branch 'dev' into test 2023-11-21 19:13:33 +08:00
871988a9b3 Merge branch 'dev' into test 2023-11-21 18:35:57 +08:00
9c95725969 Merge branch 'dev' into 'test'
fix(custom): 订单列表统计小数点后两位不显示

See merge request xinling/jdt-mer!6
2023-10-20 21:07:17 +08:00
8bf7cc8a31 Merge branch 'test' into 'main'
Merge branch 'dev' into 'test'

See merge request xinling/jdt-mer!5
2023-10-10 18:06:39 +08:00
1b2a0d7bc6 Revert "Merge branch 'dev' into 'main'"
This reverts merge request !3
2023-10-10 18:05:21 +08:00
f6fa59a1c3 Merge branch 'dev' into 'test'
fix(custom): 隐藏添加/编辑商品的log打印

See merge request xinling/jdt-mer!4
2023-10-10 18:04:27 +08:00
58deecbf36 Merge branch 'dev' into 'main'
fix(custom): 隐藏添加/编辑商品的log打印

See merge request xinling/jdt-mer!3
2023-10-10 18:03:41 +08:00
0a4169d750 Merge branch 'test' into 'main'
fix(custom): 修复Upload组件上传成功但未取到值的问题

See merge request xinling/jdt-mer!2
2023-10-10 16:03:15 +08:00
db9a56dade Merge branch 'dev' into 'test'
fix(custom): 修复Upload组件上传成功但未取到值的问题

See merge request xinling/jdt-mer!1
2023-10-10 16:00:25 +08:00
59 changed files with 8533 additions and 5576 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.dockerignore Normal file
View File

@@ -0,0 +1 @@
node_modules

View File

@@ -8,9 +8,8 @@ VITE_USE_MOCK=false
VITE_USE_PROXY=true
# base api
VITE_BASE_API='/store'
VITE_BASE_API='/api'
VITE_BASE_API_1='/api1'
VITE_ADMIN_API='/admin'
# 是否启用监控
VITE_SENTRY=false
VITE_ADMIN_API_1='/admin1'

View File

@@ -6,14 +6,13 @@ VITE_USE_MOCK=false
# base api
VITE_BASE_API='//www.wanzhuanyongcheng.cn/store'
VITE_BASE_API_1='//api.gxwzwh.com/store'
VITE_ADMIN_API='//www.wanzhuanyongcheng.cn'
VITE_ADMIN_API_1='//api.gxwzwh.com'
# 是否启用压缩
VITE_USE_COMPRESS=true
# 压缩类型
VITE_COMPRESS_TYPE=gzip
# 是否启用监控
VITE_SENTRY=true

View File

@@ -1,5 +0,0 @@
# DO NOT commit this file to your repository!
# The SENTRY_AUTH_TOKEN variable is picked up by the Sentry Build Plugin.
# It's used for authentication when uploading source maps.
# You can also set this env variable in your own `.env` files and remove this file.
SENTRY_AUTH_TOKEN="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"

View File

@@ -13,6 +13,3 @@ VITE_USE_COMPRESS=true
# 压缩类型
VITE_COMPRESS_TYPE=gzip
# 是否启用监控
VITE_SENTRY=false

162
.gitea/workflows/ci.yaml Normal file
View File

@@ -0,0 +1,162 @@
name: CI Build & Deploy
on:
push:
branches:
- test
- main
jobs:
build-and-deploy-dev:
if: gitea.ref_name == 'test'
runs-on: gitea_act_runner
container:
image: node:24-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install deps
run: |
npm config set registry https://registry.npmmirror.com/
pnpm install
- name: Build (test)
run: pnpm build:test
- name: Pack artifacts
run: |
rm -rf dist.tar
tar -zcvf dist.tar ./dist ./default.conf ./Dockerfile
- name: Upload artifacts to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST_DEV }}
username: ${{ secrets.USER_DEV }}
password: ${{ secrets.PWD_DEV }}
port: 22
source: 'dist.tar'
target: '/www/builder'
strip_components: 0
- name: Deploy over SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST_DEV }}
username: ${{ secrets.USER_DEV }}
password: ${{ secrets.PWD_DEV }}
port: 22
script: |
set -e
cd /www/builder
rm -rf jdt-mer-dev
mkdir -p jdt-mer-dev
tar -xzvf dist.tar -C /www/builder/jdt-mer-dev
rm -rf dist.tar
cd jdt-mer-dev
docker build -t jdt-mer-dev .
docker stop jdt-mer-dev || true
docker rm jdt-mer-dev || true
docker run -d -p 8083:80 --restart=always --name jdt-mer-dev jdt-mer-dev
cd ..
rm -rf jdt-mer-dev
- name: Notify WeCom (Dev)
if: always()
env:
WEBHOOK_KEY: ${{ secrets.QYWX_WEBHOOK_KEY }}
STATUS: ${{ job.status }}
REPO: ${{ gitea.repository }}
RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}
BRANCH: ${{ gitea.ref_name }}
COMMIT: ${{ gitea.sha }}
ACTOR: ${{ gitea.actor }}
run: |
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories || true
apk add --no-cache curl jq
EMOJI=$( [ "$STATUS" = "success" ] && echo "✅" || echo "❌" )
MSG="$(printf "%s**%s** (Run #%s)\n>**构建结果**: %s\n>**构建详情**: [点击查看](%s)\n>**代码分支**: %s\n>**提交标识**: %s\n>**提交发起**: %s\n" "$EMOJI" "$REPO" "${{ gitea.run_number }}" "$STATUS" "$RUN_URL" "$BRANCH" "$COMMIT" "$ACTOR")"
curl -sS -H 'Content-Type: application/json' -d "{\"msgtype\":\"markdown\",\"markdown\":{\"content\":\"$MSG\"}}" "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WEBHOOK_KEY}"
build-and-deploy-prod:
if: gitea.ref_name == 'main'
runs-on: gitea_act_runner
container:
image: node:24-alpine
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 8
- name: Install deps
run: |
npm config set registry https://registry.npmmirror.com/
pnpm install
- name: Build (prod)
run: pnpm build:prod
- name: Pack artifacts
run: |
rm -rf dist.tar
tar -zcvf dist.tar ./dist ./default.conf ./Dockerfile
- name: Upload artifacts to server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST_PROD }}
username: ${{ secrets.USER_PROD }}
password: ${{ secrets.PWD_PROD }}
port: 22
source: 'dist.tar'
target: '/www/builder'
strip_components: 0
- name: Deploy over SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST_PROD }}
username: ${{ secrets.USER_PROD }}
password: ${{ secrets.PWD_PROD }}
port: 22
script: |
set -e
cd /www/builder
rm -rf jdt-mer-prod
mkdir -p jdt-mer-prod
tar -xzvf dist.tar -C /www/builder/jdt-mer-prod
rm -rf dist.tar
cd jdt-mer-prod
docker build -t jdt-mer-prod .
docker stop jdt-mer-prod || true
docker rm jdt-mer-prod || true
docker run -d -p 8083:80 --restart=always --name jdt-mer-prod jdt-mer-prod
cd ..
rm -rf jdt-mer-prod
- name: Notify WeCom (Prod)
if: always()
env:
WEBHOOK_KEY: ${{ secrets.QYWX_WEBHOOK_KEY }}
STATUS: ${{ job.status }}
REPO: ${{ gitea.repository }}
RUN_URL: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}
BRANCH: ${{ gitea.ref_name }}
COMMIT: ${{ gitea.sha }}
ACTOR: ${{ gitea.actor }}
run: |
sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories || true
apk add --no-cache curl jq
EMOJI=$( [ "$STATUS" = "success" ] && echo "✅" || echo "❌" )
MSG="$(printf "%s**%s** (Run #%s)\n>**构建结果**: %s\n>**构建详情**: [点击查看](%s)\n>**代码分支**: %s\n>**提交标识**: %s\n>**提交发起**: %s\n" "$EMOJI" "$REPO" "${{ gitea.run_number }}" "$STATUS" "$RUN_URL" "$BRANCH" "$COMMIT" "$ACTOR")"
curl -sS -H 'Content-Type: application/json' -d "{\"msgtype\":\"markdown\",\"markdown\":{\"content\":\"$MSG\"}}" "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${WEBHOOK_KEY}"

5
.idea/.gitignore generated vendored
View File

@@ -1,5 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

View File

@@ -1,65 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<option name="LINE_SEPARATOR" value="&#10;" />
<HTMLCodeStyleSettings>
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
<option name="HTML_QUOTE_STYLE" value="Single" />
<option name="HTML_ENFORCE_QUOTES" value="true" />
</HTMLCodeStyleSettings>
<JSCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</JSCodeStyleSettings>
<TypeScriptCodeStyleSettings version="0">
<option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" />
<option name="FORCE_SEMICOLON_STYLE" value="true" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="USE_DOUBLE_QUOTES" value="false" />
<option name="FORCE_QUOTE_STYlE" value="true" />
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
<option name="SPACES_WITHIN_IMPORTS" value="true" />
</TypeScriptCodeStyleSettings>
<VueCodeStyleSettings>
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
</VueCodeStyleSettings>
<codeStyleSettings language="HTML">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="Vue">
<option name="SOFT_MARGINS" value="100" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<option name="fix-on-save" value="true" />
</component>
</project>

12
.idea/mer.iml generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated
View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mer.iml" filepath="$PROJECT_DIR$/.idea/mer.iml" />
</modules>
</component>
</project>

6
.idea/prettier.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PrettierConfiguration">
<option name="myConfigurationMode" value="AUTOMATIC" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Ronnie Zhang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
build/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -2,37 +2,36 @@ export const OUTPUT_DIR = 'dist'
export const PROXY_CONFIG = {
/**
* @desc 替换匹配值
* @请求路径 http://localhost:3100/api/user
* @转发路径 http://localhost:8080/user
* @desc 主接口代理
* @请求路径 http://localhost:3100/api/login
* @转发路径 http://localhost:3000/api/login
*/
'/store': {
'/api': {
target: 'https://test.wanzhuanyongcheng.cn',
changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp('^/api'), ''),
rewrite: (path) => path.replace(/^\/api/, '/store'),
},
/**
* @desc 备用接口代理
* @请求路径 http://localhost:3100/api1/login
* @转发路径 http://localhost:3001/api/login
*/
'/api1': {
target: 'https://api.gxwzwh.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api1/, '/store'),
},
/**
* @desc null
*/
'/admin': {
target: 'https://test.wanzhuanyongcheng.cn',
changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp('^/admin'), ''),
rewrite: (path) => path.replace(/^\/admin/, ''),
},
'/admin1': {
target: 'https://api.gxwzwh.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/admin1/, ''),
},
/**
* @desc 不替换匹配值
* @请求路径 http://localhost:3100/api/v2/user
* @转发路径 http://localhost:8080/api/v2/user
*/
// '/api/v2': {
// target: 'http://localhost:8080',
// changeOrigin: true,
// },
/**
* @desc 替换部分匹配值
* @请求路径 http://localhost:3100/api/v3/user
* @转发路径 http://localhost:8080/user
*/
// '/api/v3': {
// target: 'http://localhost:8080',
// changeOrigin: true,
// rewrite: (path) => path.replace(new RegExp('^/api'), ''),
// },
}

View File

@@ -14,7 +14,6 @@ import viteCompression from 'vite-plugin-compression'
import { configHtmlPlugin } from './html'
import { configMockPlugin } from './mock'
import unplugin from './unplugin'
import { sentryVitePlugin } from '@sentry/vite-plugin'
export function createVitePlugins(viteEnv, isBuild) {
const plugins = [vue(), ...unplugin, configHtmlPlugin(viteEnv, isBuild), Unocss()]
@@ -36,21 +35,5 @@ export function createVitePlugins(viteEnv, isBuild) {
})
)
}
if (viteEnv.VITE_SENTRY) {
plugins.push(
sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: 'sentry',
project: 'jdt-mer',
url: 'https://w.huakk.top',
sourcemaps: {
ignore: ['node_modules'],
filesToDeleteAfterUpload: ['dist/**/*.js.map'],
},
})
)
}
return plugins
}

View File

@@ -5,7 +5,6 @@ import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import { FileSystemIconLoader } from 'unplugin-icons/loaders'
import IconsResolver from 'unplugin-icons/resolver'
import mkcert from 'vite-plugin-mkcert'
import { sentryVitePlugin } from '@sentry/vite-plugin'
/**
* * unplugin-icons插件自动引入iconify图标
@@ -46,11 +45,4 @@ export default [
customDomId: '__CUSTOM_SVG_ICON__',
}),
mkcert(),
sentryVitePlugin({
authToken:
'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',
org: 'sentry',
project: 'jdt-mer',
url: 'https://w.huakk.top',
}),
]

View File

@@ -1,40 +0,0 @@
import { resolveToken } from '../utils'
const token = {
admin: 'admin',
editor: 'editor',
}
export default [
{
url: '/api/auth/login',
method: 'post',
response: ({ body }) => {
if (['admin', 'editor'].includes(body?.name)) {
return {
code: 0,
data: {
token: token[body.name],
},
}
} else {
return {
code: -1,
message: '没有此用户',
}
}
},
},
{
url: '/api/auth/refreshToken',
method: 'post',
response: ({ headers }) => {
return {
code: 0,
data: {
token: resolveToken(headers?.authorization),
},
}
},
},
]

View File

@@ -1,5 +0,0 @@
import auth from './auth'
import user from './user'
import post from './post'
export default [...auth, ...user, ...post]

View File

@@ -1,138 +0,0 @@
const posts = [
{
title: '使用纯css优雅配置移动端rem布局',
author: '大脸怪',
category: 'Css',
description: '通常配置rem布局会使用js进行处理比如750的设计稿会这样...',
content: '通常配置rem布局会使用js进行处理比如750的设计稿会这样',
isRecommend: true,
isPublish: true,
createDate: '2021-11-04T04:03:36.000Z',
updateDate: '2021-11-04T04:03:36.000Z',
},
{
title: 'Vue2&Vue3项目风格指南',
author: 'Ronnie',
category: 'Vue',
description: '总结的Vue2和Vue3的项目风格',
content: '### 1. 命名风格\n\n> 文件夹如果是由多个单词组成,应该始终是横线连接 ',
isRecommend: true,
isPublish: true,
createDate: '2021-10-25T08:57:47.000Z',
updateDate: '2022-02-28T04:02:39.000Z',
},
{
title: '如何优雅的给图片添加水印',
author: '大脸怪',
category: 'JavaScript',
description: '优雅的给图片添加水印',
content: '我之前写过一篇文章记录了一次上传图片的优化史',
isRecommend: true,
isPublish: true,
createDate: '2021-06-24T18:46:19.000Z',
updateDate: '2021-09-23T07:51:22.000Z',
},
{
title: '前端缓存的理解',
author: '大脸怪',
category: 'Http',
description: '谈谈前端缓存的理解',
content:
'> 背景\n\n公司有个vue-cli3移动端web项目发版更新后发现部分用户手机在钉钉内置浏览器打开出现了缓存',
isRecommend: true,
isPublish: true,
createDate: '2021-06-10T18:51:19.000Z',
updateDate: '2021-09-17T09:33:24.000Z',
},
{
title: 'Promise的五个静态方法',
author: '大脸怪',
category: 'JavaScript',
description: '简单介绍下在 Promise 类中有5 种静态方法及它们的使用场景',
content:
'## 1. Promise.all\n\n并行执行多个 promise并等待所有 promise 都准备就绪。再对它们进行处理。',
isRecommend: true,
isPublish: true,
createDate: '2021-02-22T22:37:06.000Z',
updateDate: '2021-09-17T09:33:24.000Z',
},
]
export default [
{
url: '/api/posts',
method: 'get',
response: (data = {}) => {
const { title, pageNo, pageSize } = data.query
let pageData = []
let total = 60
const filterData = posts.filter(
(item) => item.title.includes(title) || (!title && title !== 0)
)
if (filterData.length) {
if (pageSize) {
while (pageData.length < pageSize) {
pageData.push(filterData[Math.round(Math.random() * (filterData.length - 1))])
}
} else {
pageData = filterData
}
pageData = pageData.map((item, index) => ({
id: pageSize * (pageNo - 1) + index + 1,
...item,
}))
} else {
total = 0
}
return {
code: 0,
message: 'ok',
data: {
pageData,
total,
pageNo,
pageSize,
},
}
},
},
{
url: '/api/post',
method: 'post',
response: ({ body }) => {
return {
code: 0,
message: 'ok',
data: body,
}
},
},
{
url: '/api/post/:id',
method: 'put',
response: ({ query, body }) => {
return {
code: 0,
message: 'ok',
data: {
id: query.id,
body,
},
}
},
},
{
url: '/api/post/:id',
method: 'delete',
response: ({ query }) => {
return {
code: 0,
message: 'ok',
data: {
id: query.id,
},
}
},
},
]

View File

@@ -1,39 +0,0 @@
import { resolveToken } from '../utils'
const users = {
admin: {
id: 1,
name: '大脸怪(admin)',
avatar: 'https://assets.qszone.com/images/avatar.jpg',
email: 'Ronnie@123.com',
role: ['admin'],
},
editor: {
id: 2,
name: '大脸怪(editor)',
avatar: 'https://assets.qszone.com/images/avatar.jpg',
email: 'Ronnie@123.com',
role: ['editor'],
},
guest: {
id: 3,
name: '访客(guest)',
avatar: 'https://assets.qszone.com/images/avatar.jpg',
role: [],
},
}
export default [
{
url: '/api/user',
method: 'get',
response: ({ headers }) => {
const token = resolveToken(headers?.authorization)
return {
code: 0,
data: {
...(users[token] || users.guest),
},
}
},
},
]

View File

@@ -1,6 +0,0 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
import api from './api'
export function setupProdMockServer() {
createProdMockServer(api)
}

View File

@@ -1,12 +0,0 @@
export function resolveToken(authorization) {
/**
* * jwt token
* * Bearer + token
* ! 认证方案: Bearer
*/
const reqTokenSplit = authorization.split(' ')
if (reqTokenSplit.length === 2) {
return reqTokenSplit[1]
}
return ''
}

View File

@@ -2,7 +2,7 @@
"name": "vue-naive-admin",
"version": "1.0.0",
"scripts": {
"build": "vite build",
"build:prod": "vite build",
"build:test": "vite build --mode test",
"cz": "cz",
"dev": "vite",
@@ -32,54 +32,53 @@
]
},
"dependencies": {
"@sentry/vite-plugin": "^2.10.2",
"@sentry/vue": "^7.84.0",
"@unocss/eslint-config": "^0.55.7",
"@vueuse/core": "^10.6.1",
"@vueuse/core": "^10.11.1",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "5.1.12",
"axios": "^1.6.2",
"dayjs": "^1.11.10",
"echarts": "^5.5.0",
"axios": "^1.13.1",
"dayjs": "^1.11.19",
"echarts": "^5.6.0",
"lodash-es": "^4.17.21",
"md-editor-v3": "^4.9.0",
"md-editor-v3": "^4.21.3",
"mockjs": "^1.1.0",
"pinia": "^2.1.7",
"vite": "^4.5.0",
"pinia": "^2.3.1",
"vite": "^4.5.14",
"vue": "3.3.4",
"vue-echarts": "^6.6.9",
"vue-router": "^4.2.5",
"vue-echarts": "^6.7.3",
"vue-router": "^4.6.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@commitlint/cli": "^17.8.1",
"@commitlint/config-conventional": "^17.8.1",
"@iconify/json": "^2.2.150",
"@iconify/vue": "^4.1.1",
"@iconify/json": "^2.2.402",
"@iconify/vue": "^4.3.0",
"@unocss/preset-rem-to-px": "^0.55.7",
"@vitejs/plugin-vue": "^4.5.1",
"@vue/compiler-sfc": "^3.3.9",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/compiler-sfc": "^3.5.22",
"@zclzone/eslint-config": "^0.0.4",
"chalk": "^5.3.0",
"commitizen": "^4.3.0",
"chalk": "^5.6.2",
"commitizen": "^4.3.1",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^7.0.0",
"dotenv": "^16.3.1",
"cz-customizable": "^7.5.1",
"dotenv": "^16.6.1",
"esno": "^0.17.0",
"fs-extra": "^11.2.0",
"fs-extra": "^11.3.2",
"husky": "^8.0.3",
"lint-staged": "^14.0.1",
"naive-ui": "^2.35.0",
"rollup-plugin-visualizer": "^5.9.3",
"sass": "^1.69.5",
"naive-ui": "^2.43.1",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.93.2",
"unocss": "0.55.3",
"unplugin-auto-import": "^0.16.7",
"unplugin-icons": "^0.16.6",
"unplugin-vue-components": "^0.25.2",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.0",
"vite-plugin-mkcert": "^1.17.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-mkcert": "^1.17.9",
"vite-plugin-mock": "2.9.6",
"vite-plugin-svg-icons": "^2.0.1"
}
},
"packageManager": "pnpm@9.1.4+sha512.9df9cf27c91715646c7d675d1c9c8e41f6fce88246f1318c1aa6a1ed1aeb3c4f032fcdf4ba63cc69c4fe6d634279176b5358727d8f2cc1e65b65f43ce2f8bfb0"
}

11727
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,527 @@
<template>
<div class="tianditu-picker">
<!-- 顶部搜索框 -->
<div class="search-header">
<n-input-group>
<n-auto-complete
v-model:value="keyword"
placeholder="搜索地点"
clearable
@select="selectFromSearch"
@input:value="handleInput"
/>
<n-button type="primary" @click="search">搜索位置</n-button>
</n-input-group>
</div>
<!-- 地图区域 -->
<div id="mapDiv" class="map-container"></div>
<!-- 底部位置信息和搜索结果 -->
<div class="location-footer">
<!-- 统一的结果列表 -->
<div class="results-section">
<div class="results-header">位置选择</div>
<div class="results-list">
<!-- 我的位置项 -->
<div
class="result-item current-location-item"
:class="{ selected: !showSearchResults }"
@click="relocateToCurrentPosition"
>
<div class="location-icon">📍</div>
<div class="result-content">
<div class="result-name">我的位置</div>
<div class="result-address">
{{ currentLocation?.address || '请点击获取当前位置' }}
</div>
</div>
</div>
<!-- 搜索结果项 -->
<div
v-for="(result, index) in searchResults"
:key="`search-${index}`"
class="result-item search-result-item"
@click="selectSearchResult(result)"
>
<div class="location-icon">📍</div>
<div class="result-content">
<div class="result-name">{{ result.name }}</div>
<div class="result-address">{{ result.address }}</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<n-space justify="space-between">
<n-button type="tertiary" @click="getCurrentLocation">定位当前位置</n-button>
<n-space>
<n-button @click="$emit('cancel')">取消</n-button>
<n-button type="primary" @click="confirmLocation">确认位置</n-button>
</n-space>
</n-space>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import axios from 'axios'
const emit = defineEmits(['confirm', 'cancel'])
const keyword = ref('')
const selectedLocation = ref(null)
const currentLocation = ref(null)
const searchResults = ref([])
const showSearchResults = ref(false)
let map = null
let marker = null
const TDT_KEY = '42db4f3dfd1a18d31e73ee90aa2ce054'
// 初始化地图
const initMap = () => {
if (typeof T === 'undefined') {
console.error('天地图API未加载完成')
return
}
map = new window.T.Map('mapDiv')
map.centerAndZoom(new window.T.LngLat(116.40969, 39.89945), 12)
// 地图点击事件
map.addEventListener('click', function (e) {
const lnglat = e.lnglat
addMarker(lnglat)
getAddress(lnglat)
// 点击地图时隐藏搜索结果
showSearchResults.value = false
})
// 获取当前位置
getCurrentLocation()
}
// 添加标记
const addMarker = (lnglat) => {
if (marker) {
map.removeOverLay(marker)
}
// 创建自定义图标
const icon = new window.T.Icon({
iconUrl:
'data:image/svg+xml;base64,' +
btoa(`
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="40" viewBox="0 0 32 40">
<path fill="#ff4757" d="M16 0C7.2 0 0 7.2 0 16c0 8.8 16 24 16 24s16-15.2 16-24C32 7.2 24.8 0 16 0zm0 22c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z"/>
<circle fill="white" cx="16" cy="16" r="4"/>
</svg>
`),
iconSize: new window.T.Point(32, 40),
iconAnchor: new window.T.Point(16, 40),
})
marker = new window.T.Marker(lnglat, { icon })
map.addOverLay(marker)
map.panTo(lnglat)
selectedLocation.value = {
lat: lnglat.lat,
lng: lnglat.lng,
address: '正在获取地址...',
}
}
// 地理编码
const getAddress = (lnglat) => {
const geocoder = new window.T.Geocoder()
geocoder.getLocation(lnglat, function (result) {
const address = result.getAddress()
if (selectedLocation.value) {
selectedLocation.value.address = address
}
})
}
// 获取当前位置
const getCurrentLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude
const lng = position.coords.longitude
const lnglat = new window.T.LngLat(lng, lat)
map.centerAndZoom(lnglat, 15)
addMarker(lnglat)
// 获取地址信息,专门为当前位置设置
const geocoder = new window.T.Geocoder()
geocoder.getLocation(lnglat, function (result) {
const address = result.getAddress()
// 同时更新当前位置和选中位置
currentLocation.value = {
lat: lat,
lng: lng,
address: address,
}
if (selectedLocation.value) {
selectedLocation.value.address = address
}
})
},
(error) => {
console.error('获取位置失败:', error)
$message.warning('无法获取当前位置,请手动选择')
}
)
} else {
$message.warning('浏览器不支持定位功能')
}
}
// 使用axios调用天地图HTTP API搜索
const searchWithAPI = async (query) => {
try {
console.log('搜索关键词:', query)
// 构建搜索参数
const postStr = {
keyWord: query,
level: 12,
mapBound: '116.02524,39.83833,116.65592,39.99185',
queryType: 1,
start: 0,
count: 10,
}
const response = await axios.get('https://api.tianditu.gov.cn/v2/search', {
params: {
postStr: JSON.stringify(postStr),
type: 'query',
tk: TDT_KEY,
},
timeout: 10000, // 10秒超时
})
console.log('搜索响应:', response.data)
if (
response.data.status.infocode === 1000 &&
response.data.pois &&
response.data.pois.length > 0
) {
return response.data.pois.map((poi) => ({
name: poi.name,
address: poi.address,
lonlat: poi.lonlat,
adminName: poi.adminName,
}))
}
return []
} catch (error) {
console.error('搜索失败:', error)
// 更详细的错误处理
if (error.code === 'ECONNABORTED') {
$message.error('请求超时,请重试')
} else if (error.response) {
$message.error(`搜索失败: ${error.response.status}`)
} else if (error.request) {
$message.error('网络请求失败,请检查网络连接')
} else {
$message.error('搜索出错,请重试')
}
return []
}
}
// 搜索功能
const search = async () => {
if (!keyword.value || !map) return
try {
const results = await searchWithAPI(keyword.value)
if (results.length > 0) {
searchResults.value = results
showSearchResults.value = true
console.log('搜索结果:', results)
} else {
$message.warning('未找到相关位置')
searchResults.value = []
showSearchResults.value = false
}
} catch (error) {
console.error('搜索出错:', error)
$message.error('搜索失败,请重试')
}
}
// 选择搜索结果
const selectSearchResult = (result) => {
if (!result.lonlat) return
const [lng, lat] = result.lonlat.split(',').map(Number)
const lnglat = new window.T.LngLat(lng, lat)
addMarker(lnglat)
// 只更新选中位置,不影响当前位置
if (selectedLocation.value) {
selectedLocation.value.address = `${result.name} - ${result.address}`
}
showSearchResults.value = false
}
// 处理搜索输入
const handleInput = (value) => {
if (value && value.length > 1) {
// 延迟搜索,避免频繁请求
clearTimeout(handleInput.timer)
handleInput.timer = setTimeout(() => {
search()
}, 500)
} else {
searchResults.value = []
showSearchResults.value = false
}
}
// 从搜索建议中选择
const selectFromSearch = (value, option) => {
if (option && option.data) {
selectSearchResult(option.data)
}
}
// 确认选择的位置
const confirmLocation = () => {
if (selectedLocation.value) {
emit('confirm', {
latlng: {
lat: selectedLocation.value.lat,
lng: selectedLocation.value.lng,
},
address: selectedLocation.value.address || '',
})
} else {
$message.warning('请先选择位置')
}
}
// 重新定位到当前位置
const relocateToCurrentPosition = () => {
if (currentLocation.value) {
// 如果已有当前位置信息,直接使用
const lnglat = new window.T.LngLat(currentLocation.value.lng, currentLocation.value.lat)
addMarker(lnglat)
if (selectedLocation.value) {
selectedLocation.value.address = currentLocation.value.address
}
} else {
// 重新获取当前位置
getCurrentLocation()
}
showSearchResults.value = false
}
// 加载天地图API
const loadTiandituScript = () => {
return new Promise((resolve, reject) => {
if (typeof T !== 'undefined') {
resolve()
return
}
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = `https://api.tianditu.gov.cn/api?v=4.0&tk=${TDT_KEY}`
script.onload = resolve
script.onerror = reject
document.head.appendChild(script)
})
}
onMounted(async () => {
try {
await loadTiandituScript()
await nextTick()
initMap()
} catch (error) {
console.error('加载天地图API失败:', error)
$message.error('地图加载失败,请检查网络连接')
}
})
onUnmounted(() => {
if (map) {
map = null
marker = null
}
if (handleInput.timer) {
clearTimeout(handleInput.timer)
}
})
</script>
<style scoped lang="scss">
.tianditu-picker {
display: flex;
flex-direction: column;
height: 100%;
background-color: #f5f5f5;
.search-header {
padding: 12px 0;
background-color: #fff;
border-bottom: 1px solid #eee;
z-index: 1000;
}
.map-container {
flex: 1;
width: 100%;
min-height: 400px;
}
.location-footer {
background-color: #fff;
border-top: 1px solid #eee;
max-height: 300px;
overflow-y: auto;
.results-section {
.results-header {
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
color: #333;
background-color: #f5f5f5;
border-bottom: 1px solid #eee;
}
.results-list {
max-height: 180px;
overflow-y: auto;
.result-item {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
cursor: pointer;
transition: background-color 0.2s;
&:hover {
background-color: #f5f5f5;
}
&:last-child {
border-bottom: none;
}
.location-icon {
font-size: 18px;
margin-right: 12px;
flex-shrink: 0;
}
.result-content {
flex: 1;
.result-name {
font-size: 14px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.result-address {
font-size: 12px;
color: #666;
line-height: 1.4;
}
}
}
.current-location-item {
background-color: #f8f9fa;
}
.search-result-item {
background-color: #fff;
}
}
}
.action-buttons {
padding: 12px 16px;
border-top: 1px solid #f0f0f0;
background-color: #fff;
}
}
}
// 深色模式适配
.dark {
.tianditu-picker {
background-color: #1f1f1f;
.search-header {
background-color: #2a2a2a;
border-bottom-color: #3a3a3a;
}
.location-footer {
background-color: #2a2a2a;
border-top-color: #3a3a3a;
.results-section {
.results-header {
background-color: #333;
color: #fff;
border-bottom-color: #3a3a3a;
}
.results-list {
.result-item {
border-bottom-color: #3a3a3a;
&:hover {
background-color: #333;
}
.result-name {
color: #fff;
}
.result-address {
color: #ccc;
}
}
.current-location-item {
background-color: #333;
}
.search-result-item {
background-color: #333;
}
}
}
.action-buttons {
background-color: #2a2a2a;
border-top-color: #3a3a3a;
}
}
}
}
</style>

View File

@@ -68,7 +68,7 @@ const tabs = [
const count = ref(tabs.map((item) => item.messages).flat().length)
watch(activeTab, (v) => {
if (count === 0) return
if (count.value === 0) return
const tabIndex = tabs.findIndex((item) => item.name === v)
count.value -= tabs[tabIndex].messages.length
if (count.value < 0) count.value = 0

View File

@@ -9,10 +9,14 @@ import { setupRouter } from '@/router'
import { setupStore } from '@/store'
import App from './App.vue'
import { setupNaiveDiscreteApi } from './utils'
import { initApiEndpoint } from '@/utils/api-config'
async function setupApp() {
const app = createApp(App)
// 初始化接口配置
initApiEndpoint()
setupStore(app)
setupNaiveDiscreteApi()

View File

@@ -1,4 +1,5 @@
import { getToken, refreshAccessToken, isNullOrWhitespace } from '@/utils'
import { addDynamicRoutes } from '@/router'
const WHITE_LIST = ['/login', '/404']
export function createPermissionGuard(router) {
@@ -17,6 +18,20 @@ export function createPermissionGuard(router) {
/** 有token的情况 */
if (to.path === '/login') return { path: '/' }
// 确保动态路由已加载
if (token && !router.hasRoute('Dashboard')) {
try {
await addDynamicRoutes()
// 如果当前路径不存在,重定向到工作台
if (to.path !== '/' && !router.hasRoute(to.name)) {
return { path: '/workbench' }
}
} catch (error) {
console.error('动态路由加载失败:', error)
return { path: '/login' }
}
}
refreshAccessToken()
return true
})

View File

@@ -3,7 +3,6 @@ import { setupRouterGuard } from './guard'
import { basicRoutes, EMPTY_ROUTE, NOT_FOUND_ROUTE } from './routes'
import { getToken, isNullOrWhitespace } from '@/utils'
import { useUserStore, usePermissionStore } from '@/store'
import * as Sentry from '@sentry/vue'
const isHash = false
export const router = createRouter({
@@ -15,22 +14,6 @@ export const router = createRouter({
export async function setupRouter(app) {
await addDynamicRoutes()
setupRouterGuard(router)
if (import.meta.env.VITE_SENTRY === 'true') {
Sentry.init({
app,
dsn: 'https://aa4308fc56a9d107786b8dbcd2ae56e8@w.huakk.top/13',
integrations: [
new Sentry.BrowserTracing({
tracePropagationTargets: ['localhost', /^https:\/\/w\.huakk\.top\/api/],
routingInstrumentation: Sentry.vueRouterInstrumentation(router),
}),
new Sentry.Replay(),
],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
app.use(router)
}
@@ -59,13 +42,45 @@ export async function addDynamicRoutes() {
const permissionStore = usePermissionStore()
!userStore.userId && (await userStore.getUserInfo())
const accessRoutes = permissionStore.generateRoutes(userStore.role)
// 确保路由按正确顺序添加
accessRoutes.forEach((route) => {
!router.hasRoute(route.name) && router.addRoute(route)
if (!router.hasRoute(route.name)) {
router.addRoute(route)
}
})
router.hasRoute(EMPTY_ROUTE.name) && router.removeRoute(EMPTY_ROUTE.name)
// 移除空路由添加404路由
if (router.hasRoute(EMPTY_ROUTE.name)) {
router.removeRoute(EMPTY_ROUTE.name)
}
router.addRoute(NOT_FOUND_ROUTE)
// 确保根路径重定向到工作台
if (!router.hasRoute('Dashboard')) {
const workbenchRoute = {
name: 'Dashboard',
path: '/',
component: () => import('@/layout/index.vue'),
redirect: '/workbench',
children: [
{
name: 'Workbench',
path: 'workbench',
component: () => import('@/views/workbench/index.vue'),
meta: {
title: '工作台',
icon: 'mdi:index',
order: 0,
},
},
],
}
router.addRoute(workbenchRoute)
}
} catch (error) {
console.error(error)
throw error
}
}

View File

@@ -18,10 +18,7 @@ export const useUserStore = defineStore('user', {
return this.userInfo?.name
},
avatar() {
return (
this.userInfo?.avatar ||
'https://pic3.58cdn.com.cn/nowater/webim/big/n_v21bc7874294754e63a22b80febac9cf51.jpg'
)
return this.userInfo?.avatar || 'https://v2.xxapi.cn/api/head?return=302'
},
role() {
return this.userInfo?.role || []

65
src/utils/api-config.js Normal file
View File

@@ -0,0 +1,65 @@
// 判断是否为开发环境
const isDev = import.meta.env.DEV
// API接口线路配置管理
export const API_ENDPOINTS = {
primary: {
name: '主要线路',
baseURL: import.meta.env.VITE_BASE_API,
base_admin_url: import.meta.env.VITE_ADMIN_API,
timeout: 10000,
},
backup1: {
name: '备用线路',
baseURL: import.meta.env.VITE_BASE_API_1,
base_admin_url: import.meta.env.VITE_ADMIN_API_1,
timeout: 10000,
},
}
// 调试信息
console.log('=== 多接口配置信息 ===')
console.log('当前环境:', isDev ? '开发环境' : '生产环境')
console.log('API配置:', API_ENDPOINTS)
console.log('环境变量 VITE_BASE_API:', import.meta.env.VITE_BASE_API)
console.log('环境变量 VITE_BASE_API_1:', import.meta.env.VITE_BASE_API_1)
console.log('环境变量 VITE_BASE_ADMIN:', import.meta.env.VITE_ADMIN_API)
console.log('环境变量 VITE_BASE_ADMIN_1:', import.meta.env.VITE_ADMIN_API_1)
console.log('是否使用代理:', import.meta.env.VITE_USE_PROXY)
// 当前使用的接口线路
let currentEndpoint = 'primary'
// 获取当前接口配置
export function getCurrentEndpoint() {
return {
key: currentEndpoint,
...API_ENDPOINTS[currentEndpoint],
}
}
// 设置当前接口
export function setCurrentEndpoint(endpoint) {
if (API_ENDPOINTS[endpoint]) {
currentEndpoint = endpoint
localStorage.setItem('api_endpoint', endpoint)
return true
}
return false
}
// 获取所有可用接口
export function getAvailableEndpoints() {
return Object.entries(API_ENDPOINTS).map(([key, config]) => ({
key,
...config,
}))
}
// 初始化时从本地存储恢复接口选择
export function initApiEndpoint() {
const savedEndpoint = localStorage.getItem('api_endpoint')
if (savedEndpoint && API_ENDPOINTS[savedEndpoint]) {
currentEndpoint = savedEndpoint
}
}

View File

@@ -1,5 +1,6 @@
import axios from 'axios'
import { resReject, resResolve, reqReject, reqResolve } from './interceptors'
import { getCurrentEndpoint, getAvailableEndpoints } from '../api-config'
export function createAxios(options = {}) {
const defaultOptions = {
@@ -14,6 +15,114 @@ export function createAxios(options = {}) {
return service
}
export const request = createAxios({
baseURL: import.meta.env.VITE_BASE_API,
})
// 检测是否为admin类接口
function isAdminEndpoint(url) {
console.log('url', url)
// 检查URL是否包含admin相关路径
return url.includes('/admin')
}
// 根据URL类型选择对应的baseURL
function getBaseURLByUrlType(endpoint, url) {
console.log('endpoint', endpoint)
console.log('url', url)
if (isAdminEndpoint(url)) {
return endpoint.base_admin_url || endpoint.baseURL
}
return endpoint.baseURL
}
// 创建支持多接口的请求实例
export function createMultiEndpointRequest() {
let instances = null
// 延迟创建axios实例
function ensureInstances() {
if (!instances) {
instances = {}
const endpoints = getAvailableEndpoints()
console.log('创建axios实例接口列表:', endpoints)
endpoints.forEach((endpoint) => {
console.log(`创建实例 ${endpoint.key}:`, endpoint.baseURL)
instances[endpoint.key] = createAxios({
baseURL: isAdminEndpoint(endpoint.baseURL) ? endpoint.base_admin_url : endpoint.baseURL,
timeout: endpoint.timeout,
})
})
}
return instances
}
return {
// 使用当前选中的接口发送请求
async request(config) {
const currentEndpoint = getCurrentEndpoint()
const url = config.url || ''
// 根据URL类型选择baseURL
const baseURL = getBaseURLByUrlType(currentEndpoint, url)
console.log('当前接口配置:', currentEndpoint)
console.log('请求URL:', url)
console.log('是否为admin接口:', isAdminEndpoint(url))
console.log('选择的baseURL:', baseURL)
// 创建临时axios实例
const instance = createAxios({
baseURL: baseURL,
timeout: currentEndpoint.timeout,
})
console.log('请求配置:', config)
return await instance(config)
},
// 获取当前接口实例
getCurrentInstance() {
const instances = ensureInstances()
const currentEndpoint = getCurrentEndpoint()
return instances[currentEndpoint.key]
},
// 获取所有接口实例
getAllInstances() {
return ensureInstances()
},
}
}
// export const request = createAxios({
// baseURL: import.meta.env.VITE_BASE_API,
// })
// 支持多接口的请求实例
export const multiRequest = createMultiEndpointRequest()
// 创建自动适配多接口的request实例
const multiEndpointInstance = createMultiEndpointRequest()
// 创建支持axios方法的request实例
export const request = {
// 基础请求方法
request: (config) => multiEndpointInstance.request(config),
// 自动适配axios方法
get: (url, config = {}) => multiEndpointInstance.request({ method: 'get', url, ...config }),
post: (url, data, config = {}) =>
multiEndpointInstance.request({ method: 'post', url, data, ...config }),
put: (url, data, config = {}) =>
multiEndpointInstance.request({ method: 'put', url, data, ...config }),
delete: (url, config = {}) => multiEndpointInstance.request({ method: 'delete', url, ...config }),
patch: (url, data, config = {}) =>
multiEndpointInstance.request({ method: 'patch', url, data, ...config }),
head: (url, config = {}) => multiEndpointInstance.request({ method: 'head', url, ...config }),
options: (url, config = {}) =>
multiEndpointInstance.request({ method: 'options', url, ...config }),
// 获取当前接口实例用于直接访问axios实例
getCurrentInstance: () => multiEndpointInstance.getCurrentInstance(),
getAllInstances: () => multiEndpointInstance.getAllInstances(),
}

View File

@@ -2,13 +2,11 @@ import { getToken } from '@/utils'
import { resolveResError } from './helpers'
export function reqResolve(config) {
if (config.url.includes('/admin')) {
config.url = config.url.replace(new RegExp('^/admin'), '')
console.log(config)
config.baseURL = import.meta.env.VITE_ADMIN_API
} else {
config.baseURL = import.meta.env.VITE_BASE_API
}
// if (config.url.includes('/admin')) {
// config.url = config.url.replace(new RegExp('^/admin'), '')
// console.log(config)
// config.baseURL = import.meta.env.VITE_ADMIN_API
// }
// 处理不需要token的请求
if (config.noNeedToken) {
return config

View File

@@ -58,6 +58,17 @@
/>
</div>
</n-col>
<n-col :span="24">
<div mt-10 flex items-center>
<span w-100>投注时间</span>
<n-date-picker
v-model:formatted-value="queryData.time1"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
clearable
/>
</div>
</n-col>
<n-col :span="24">
<div mt-10>
<n-button type="primary" @click="get_list">搜索</n-button>
@@ -86,10 +97,10 @@ const songs = ref([
label: '未使用',
value: 1,
},
{
label: '已使用',
value: 2,
},
// {
// label: '已使用',
// value: 2,
// },
{
label: '赢',
value: 3,
@@ -109,6 +120,7 @@ const queryData = ref({
hall_id: null,
status: null,
time: null,
time1: null,
})
const options = ref([])
@@ -199,6 +211,8 @@ const get_list = async () => {
pageSize: pagination.value.pageSize,
StartTime: queryData.value.time ? queryData.value.time[0] : '',
EndTime: queryData.value.time ? queryData.value.time[1] : '',
UseStartTime: queryData.value.time1 ? queryData.value.time1[0] : '',
UseEndTime: queryData.value.time1 ? queryData.value.time1[1] : '',
}
Reflect.deleteProperty(obj, 'time')
const res = await api.getData(obj)

View File

@@ -7,7 +7,7 @@ export default {
redirect: '/game_jl',
meta: {
title: '游戏统计',
icon: 'mdi:home',
icon: 'mdi:access-point',
order: 100,
},
children: [
@@ -17,7 +17,7 @@ export default {
component: () => import('./jl/index.vue'),
meta: {
title: '豆子记录',
icon: 'mdi:home',
icon: 'mdi:access-point-check',
},
},
],

View File

@@ -32,12 +32,12 @@
<!-- ]"-->
<!-- />-->
<!-- </n-form-item-gi>-->
<n-form-item-gi :span="12" label="现金价格:" path="number">
<n-form-item-gi :span="12" label="商品价格:" path="number">
<n-input-number v-model:value="model.number" :min="0" placeholder="输入现金价格" />
</n-form-item-gi>
<n-form-item-gi :span="12" label="积分价格:" path="exchange">
<n-input-number v-model:value="model.exchange" :min="0" placeholder="输入积分价格" />
</n-form-item-gi>
<!-- <n-form-item-gi :span="12" label="积分价格:" path="exchange">-->
<!-- <n-input-number v-model:value="model.exchange" :min="0" placeholder="输入积分价格" />-->
<!-- </n-form-item-gi>-->
<n-form-item-gi :span="12" label="商品库存:" path="stock">
<n-input-number v-model:value="model.stock" :min="0" placeholder="输入商品库存" />
</n-form-item-gi>
@@ -118,12 +118,12 @@ const rules = {
message: '请输入商品价格',
trigger: 'blur',
},
exchange: {
required: true,
type: 'number',
message: '请输入市场价格',
trigger: 'blur',
},
// exchange: {
// required: true,
// type: 'number',
// message: '请输入市场价格',
// trigger: 'blur',
// },
stock: {
required: true,
type: 'number',
@@ -147,10 +147,10 @@ const classList = ref([])
const getClassList = async () => {
isShowSpin.value = true
const res = await api.getGoodClass()
classList.value = res.data.data
classList.value = res.data.data.filter((item) => item.status !== 2)
if (row && type.value === 'edit') {
console.log(row)
// console.log(row)
type.value = 'edit'
model.value = {
...row,

View File

@@ -1,6 +1,25 @@
<template>
<CommonPage show-footer :title="$route.title">
<n-button type="primary" @click="addGood(1)">添加商品</n-button>
<n-grid class="mb-10" x-gap="12">
<n-gi span="12" mt-10 flex items-center>
<span w-100>筛选条件:</span>
<n-input-group>
<n-select
v-model:value="queryParams.selectKey"
:style="{ width: '20%' }"
:options="selectOptions"
placeholder="请选择"
/>
<n-input v-model:value="queryParams.word" :style="{ width: '30%' }" />
</n-input-group>
</n-gi>
<n-gi span="24" mt-10 flex items-center>
<n-button type="primary" @click="getList">查询</n-button>
<n-button ml-10 @click="clear">重置</n-button>
<n-button ml-10 type="primary" @click="addGood(1)">添加商品</n-button>
</n-gi>
</n-grid>
<n-data-table
:loading="loading"
:columns="columns"
@@ -21,6 +40,18 @@ import { useGoodsStore } from '@/store/modules/goods'
const loading = ref(false)
const selectOptions = ref([
{
label: '商品名称',
value: 0,
},
])
const queryParams = ref({
selectKey: 0,
word: '',
})
const columns = ref([
{
title: 'ID',
@@ -160,6 +191,7 @@ const getList = async () => {
const res = await api.getGoods({
pageNum: pagination.value.page,
pageSize: pagination.value.pageSize,
name: queryParams.value.word,
})
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
@@ -182,6 +214,11 @@ const toEdit = (item) => {
setRow(item)
addGood()
}
const clear = () => {
queryParams.value.word = ''
getList()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,5 +1,13 @@
import { request } from '@/utils'
import { request, multiRequest } from '@/utils'
export default {
login: (data) => request.post('/login', data, { noNeedToken: true }),
// 使用多接口的登录方法
loginWithMultiEndpoint: (data) =>
multiRequest.request({
method: 'post',
url: '/login',
data,
noNeedToken: true,
}),
}

View File

@@ -14,7 +14,17 @@
<img src="@/assets/images/logo.png" height="50" class="mr-10" />
{{ title }}
</h5>
<div mt-30>
<!-- 接口线路选择 -->
<div mt-20>
<n-select
v-model:value="selectedEndpoint"
:options="endpointOptions"
placeholder="选择接口线路"
size="large"
@update:value="handleEndpointChange"
/>
</div>
<div mt-10>
<n-input
v-model:value="loginInfo.name"
autofocus
@@ -23,7 +33,7 @@
:maxlength="20"
/>
</div>
<div mt-30>
<div mt-10>
<n-input
v-model:value="loginInfo.password"
class="h-50 items-center pl-10 text-16"
@@ -68,6 +78,12 @@ import bgImg from '@/assets/images/login_bg.webp'
import api from './api'
import { addDynamicRoutes } from '@/router'
import { useUserStore } from '@/store'
import {
getAvailableEndpoints,
setCurrentEndpoint,
getCurrentEndpoint,
initApiEndpoint,
} from '@/utils/api-config'
const userStore = useUserStore()
@@ -89,6 +105,7 @@ const easyLogin = async () => {
console.log(query)
$message.success('登录成功')
setToken(query.tk)
setCurrentEndpoint(query.api)
window.localStorage.setItem('type', query.type)
await addDynamicRoutes()
if (query.redirect) {
@@ -113,6 +130,39 @@ function initLoginInfo() {
const isRemember = useStorage('isRemember', false)
const loading = ref(false)
// 接口线路相关
const selectedEndpoint = ref('primary')
const endpointOptions = ref([])
// 初始化接口配置
initApiEndpoint()
const currentEndpoint = getCurrentEndpoint()
selectedEndpoint.value = currentEndpoint.key
// 加载接口选项
function loadEndpointOptions() {
const endpoints = getAvailableEndpoints()
endpointOptions.value = endpoints.map((endpoint) => ({
label: endpoint.name,
value: endpoint.key,
}))
}
// 处理接口切换
function handleEndpointChange(value) {
if (setCurrentEndpoint(value)) {
selectedEndpoint.value = value
$message.success(`已切换到${endpointOptions.value.find((opt) => opt.value === value)?.label}`)
} else {
$message.error('接口切换失败')
}
}
// 初始化
onMounted(() => {
loadEndpointOptions()
})
async function handleLogin() {
const { name, password } = loginInfo.value
if (!name || !password) {
@@ -122,7 +172,14 @@ async function handleLogin() {
try {
loading.value = true
$message.loading('正在验证...')
const res = await api.login({ phone: name, password: password.toString() })
console.log('开始登录请求...')
console.log('登录数据:', { phone: name, password: password.toString() })
console.log('当前选中接口:', selectedEndpoint.value)
const res = await api.loginWithMultiEndpoint({ phone: name, password: password.toString() })
console.log('登录响应:', res)
$message.success('登录成功')
setToken(res.data.token)
window.localStorage.setItem('type', res.data.type)
@@ -140,7 +197,7 @@ async function handleLogin() {
router.push('/')
}
} catch (error) {
console.error(error)
console.error('登录请求失败:', error)
$message.removeMessage()
}
loading.value = false

View File

@@ -71,6 +71,7 @@
<n-col :span="24">
<div mt-10>
<n-button type="primary" @click="getList">搜索</n-button>
<n-button type="primary" ml-10 @click="exportTable">导出表格</n-button>
<n-button ml-10 @click="clear">重置</n-button>
</div>
</n-col>
@@ -82,6 +83,7 @@
:data="data"
:pagination="pagination"
:bordered="false"
:scroll-x="1800"
remote
/>
</CommonPage>
@@ -89,6 +91,7 @@
<script setup>
import api from './api'
import * as XLSX from 'xlsx'
const loading = ref(false)
@@ -101,15 +104,15 @@ const queryData = ref({
const songs = ref([
{
value: 0,
value: 1,
label: '未付款',
},
{
value: 1,
value: 2,
label: '已付款',
},
{
value: 2,
value: 3,
label: '挂帐中',
},
])
@@ -134,10 +137,20 @@ const selectOptions = ref([
])
const columns = ref([
{
title: '桌号',
align: 'center',
key: 'seat',
},
{
title: '备注',
align: 'center',
key: 'notes',
},
{
title: '订单号',
align: 'center',
key: 'oid',
key: 'jl_oid',
},
{
title: '用户',
@@ -242,6 +255,8 @@ const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
showSizePicker: true,
pageSizes: [10, 20, 50, 100],
onChange: (page) => {
pagination.value.page = page
getList()
@@ -306,6 +321,16 @@ const clear = () => {
}
getList()
}
const exportTable = () => {
// 将数据转换为工作簿
const worksheet = XLSX.utils.json_to_sheet(data.value)
const workbook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
// 生成Excel文件并触发下载
XLSX.writeFile(workbook, 'table.xlsx')
}
</script>
<style lang="scss" scoped>

View File

@@ -2,4 +2,6 @@ import { request } from '@/utils'
export default {
getOrder: (data) => request.post('/order', data),
// 核销订单
verifyOrder: (data) => request.post('/order/verify', data),
}

View File

@@ -20,7 +20,12 @@
</n-card>
<n-card ml-10 w-200 rounded-5>
<n-statistic label="总佣金" tabular-nums>
<n-number-animation :from="0" :to="cardData.commission" />
<n-number-animation :from="0" :to="cardData.commission" :precision="2" />
</n-statistic>
</n-card>
<n-card ml-10 w-200 rounded-5>
<n-statistic label="现金部分" tabular-nums>
<n-number-animation :from="0" :to="cardData.discount_number" :precision="2" />
</n-statistic>
</n-card>
</div>
@@ -39,6 +44,10 @@
label: '积分',
value: 2,
},
{
label: '小猪积分',
value: 3,
},
]"
:key="song.value"
:value="song.value"
@@ -88,6 +97,7 @@
<n-col :span="24">
<div mt-10>
<n-button type="primary" @click="getList">搜索</n-button>
<n-button type="primary" ml-10 @click="exportTable">导出表格</n-button>
<n-button ml-10 @click="clear">重置</n-button>
</div>
</n-col>
@@ -99,6 +109,7 @@
:data="data"
:pagination="pagination"
:bordered="false"
:scroll-x="2000"
remote
/>
</CommonPage>
@@ -106,6 +117,8 @@
<script setup>
import api from './api'
import { NButton, NPopconfirm } from 'naive-ui'
import * as XLSX from 'xlsx'
const loading = ref(false)
@@ -164,11 +177,15 @@ const columns = ref([
title: '订单号',
align: 'center',
key: 'oid',
width: 200,
fixed: 'left',
},
{
title: '用户',
align: 'center',
slot: 'user',
width: 100,
fixed: 'left',
render: (row) => {
return [
h(
@@ -224,22 +241,26 @@ const columns = ref([
key: 'count',
},
{
title: '订单总价',
title: '订单金额',
align: 'center',
slot: 'number',
render: (row) => h('span', row.pay_type === 1 ? `${row.price}元` : `${row.exchange}积分`),
render: (row) => h('span', `${row.price}元`),
},
{
title: '抵扣后价格(元)',
key: 'discount_price',
align: 'center',
},
{
title: '积分抵扣',
key: 'exchange',
align: 'center',
},
{
title: '支付方式',
align: 'center',
slot: 'pay_type',
render: (row) => h('span', row.pay_type === 1 ? '微信' : '积分'),
},
{
title: '订单总价',
align: 'center',
slot: 'number',
render: (row) => h('span', row.pay_type === 1 ? `${row.price}元` : `${row.exchange}积分`),
render: (row) => h('span', row.pay_type === 1 ? '微信' : '平台抵扣'),
},
{
title: '订单状态',
@@ -301,14 +322,47 @@ const columns = ref([
align: 'center',
key: 'cancel_time',
},
// {
// title: '操作',
// align: 'center',
// slot: 'action',
// render() {
// // console.log(row)
// },
// },
{
title: '操作',
align: 'center',
slot: 'action',
fixed: 'right',
render(row) {
const el = []
if (row.status === 1) {
el.push(
h(
NPopconfirm,
{
onPositiveClick: async () => {
await api.verifyOrder({
oids: [row.oid],
})
getList()
},
onNegativeClick: () => $message.info('已取消'),
},
{
default: () => `确定核销此${row.oid}订单吗?`,
trigger: () =>
h(
NButton,
{
type: 'primary',
size: 'small',
text: true,
},
{
default: () => '核销',
}
),
}
)
)
}
return el
},
},
])
const data = ref([])
@@ -325,6 +379,8 @@ const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
showSizePicker: true,
pageSizes: [10, 20, 50, 100],
onChange: (page) => {
pagination.value.page = page
getList()
@@ -375,6 +431,7 @@ const getList = async () => {
cardData.value.count = res.data.total
cardData.value.commission = res.data.total_commission
cardData.value.pulse = res.data.total_pulse
cardData.value.discount_number = res.data.discount_number
} catch (error) {
$message.error(error.msg)
throw error
@@ -392,6 +449,16 @@ const clear = () => {
}
getList()
}
const exportTable = () => {
// 将数据转换为工作簿
const worksheet = XLSX.utils.json_to_sheet(data.value)
const workbook = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
// 生成Excel文件并触发下载
XLSX.writeFile(workbook, 'table.xlsx')
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,5 +1,25 @@
<template>
<CommonPage show-footer :title="$route.title">
<n-grid mb-10 flex items-center x-gap="10" :y-gap="8" :cols="3">
<n-gi>
<!-- {{ queryData }} -->
<div flex items-center>
<span w-120>时间筛选</span>
<n-date-picker
v-model:formatted-value="queryData.time"
value-format="yyyy-MM-dd HH:mm:ss"
type="datetimerange"
clearable
/>
</div>
</n-gi>
<n-gi>
<div>
<n-button type="primary" @click="get_data">搜索</n-button>
<n-button ml-10 @click="clear">重置</n-button>
</div>
</n-gi>
</n-grid>
<n-card mb-5 rounded-5 title="营业额汇总">
<n-grid x-gap="10" :y-gap="8" :cols="4">
<n-gi>
@@ -67,6 +87,10 @@ const cardData = ref({})
const loading = ref(false)
const queryData = ref({
time: null,
})
const option = ref({
tooltip: {
trigger: 'axis',
@@ -91,6 +115,7 @@ const option = ref({
{
data: [],
type: 'bar',
barWidth: '10%',
name: '金额(元)',
label: {
show: true,
@@ -119,7 +144,11 @@ const get_data = async () => {
loading.value = true
option.value.xAxis.data = []
option.value.series[0].data = []
const res = await api.getData()
const query_data = {
StartTime: queryData.value.time === null ? '' : queryData.value.time[0] || '',
EndTime: queryData.value.time === null ? '' : queryData.value.time[1] || '',
}
const res = await api.getData(query_data)
cardData.value = res.data
for (let i = 0; i < res.data.data.length; i++) {
option.value.xAxis.data.push(res.data.data[i].Name)
@@ -128,6 +157,11 @@ const get_data = async () => {
console.log(cardData.value)
loading.value = false
}
const clear = async () => {
queryData.value.time = null
get_data()
}
</script>
<style lang="scss" scoped>

View File

@@ -4,12 +4,12 @@
<n-col :span="24">
<div flex>
<n-card w-500>
<n-statistic label="订单流水(积分" tabular-nums>
<n-statistic label="订单流水(" tabular-nums>
<n-number-animation :from="0" :to="cardData.total" />
</n-statistic>
</n-card>
<n-card ml-10 w-500>
<n-statistic label="订单服务费(积分" tabular-nums>
<n-statistic label="订单服务费(" tabular-nums>
<n-number-animation :from="0" :to="cardData.service" />
</n-statistic>
</n-card>
@@ -139,13 +139,13 @@ const getList = async () => {
key: 'oid',
align: 'center',
},
// {
// title: '用户名称',
// key: 'user_name',
// align: 'center',
// },
{
title: '用户名称',
key: 'user_name',
align: 'center',
},
{
title: '上次留存积分',
title: '上次留存余额',
key: 'balance',
align: 'center',
},
@@ -155,13 +155,13 @@ const getList = async () => {
align: 'center',
},
{
title: '获取积分',
title: '获取余额',
key: 'number',
align: 'center',
},
{
title: '时间',
key: 'record_time',
key: 'add_time',
align: 'center',
},
]
@@ -174,18 +174,18 @@ const getList = async () => {
align: 'center',
},
{
title: '上次留存积分',
title: '上次留存余额',
key: 'balance',
align: 'center',
},
{
title: '扣除积分',
title: '扣除余额',
key: 'record_number',
align: 'center',
},
{
title: '时间',
key: 'record_time',
key: 'add_time',
align: 'center',
},
]
@@ -198,18 +198,18 @@ const getList = async () => {
align: 'center',
},
{
title: '上次留存积分',
title: '上次留存余额',
key: 'balance',
align: 'center',
},
{
title: '获取积分',
title: '获取余额',
key: 'record_number',
align: 'center',
},
{
title: '时间',
key: 'record_time',
key: 'add_time',
align: 'center',
},
]

View File

@@ -1,12 +1,12 @@
const Layout = () => import('@/layout/index.vue')
export default {
name: '积分管理',
name: '余额管理',
path: '/settlement',
component: Layout,
redirect: 'jf_list',
meta: {
title: '积分管理',
title: '余额管理',
icon: 'mdi:account-multiple',
order: 10,
},
@@ -16,7 +16,7 @@ export default {
path: 'jf_list',
component: () => import('./jf_list/index.vue'),
meta: {
title: '积分明细',
title: '余额明细',
icon: 'mdi:account-multiple',
order: 10,
},
@@ -26,10 +26,20 @@ export default {
path: 'tx_list',
component: () => import('./tx_list/index.vue'),
meta: {
title: '积分提现',
title: '余额提现',
icon: 'mdi:account-multiple',
order: 10,
},
},
// {
// name: 'Ylist',
// path: 'y_list',
// component: () => import('./y_list/index.vue'),
// meta: {
// title: '后结提现',
// icon: 'mdi:account-multiple',
// order: 10,
// },
// },
],
}

View File

@@ -1,9 +1,26 @@
<template>
<!-- <div> -->
<CommonPage show-footer :title="$route.title">
<div w-1200 flex items-center justify-between>
<div w-1200 flex items-center>
<!-- <n-card class="w-300">-->
<!-- <n-statistic label="可提现积分">-->
<!-- <n-number-animation-->
<!-- ref="numberAnimationInstRef"-->
<!-- :precision="2"-->
<!-- :from="0"-->
<!-- :to="userInfo.integral"-->
<!-- />-->
<!-- </n-statistic>-->
<!-- </n-card>-->
<!-- <div w-100 text-center text-25>/</div>-->
<!-- <n-card class="w-300">-->
<!-- <n-statistic label="兑换比例">-->
<!-- <n-number-animation ref="numberAnimationInstRef" :precision="2" :from="0" :to="100" />-->
<!-- </n-statistic>-->
<!-- </n-card>-->
<!-- <div w-100 text-center text-25>=</div>-->
<n-card class="w-300">
<n-statistic label="可提现积分">
<n-statistic label="CNY">
<n-number-animation
ref="numberAnimationInstRef"
:precision="2"
@@ -12,25 +29,13 @@
/>
</n-statistic>
</n-card>
<div w-100 text-center text-25>/</div>
<n-card class="w-300">
<n-statistic label="兑换比例">
<n-number-animation ref="numberAnimationInstRef" :precision="2" :from="0" :to="100" />
</n-statistic>
</n-card>
<div w-100 text-center text-25>=</div>
<n-card class="w-300">
<n-statistic label="CNY">
<n-number-animation
ref="numberAnimationInstRef"
:precision="2"
:from="0"
:to="userInfo.integral / 100"
/>
</n-statistic>
</n-card>
<div ml-10 w-300 flex flex-col items-center justify-center>
<n-input-number v-model:value="formData.integral" clearable placeholder="请输入提现积分" />
<n-input-number
v-model:value="formData.integral"
clearable
w-full
placeholder="请输入提现余额"
/>
<n-button mt-10 w-full type="primary" @click="ok">立即提现</n-button>
</div>
</div>
@@ -66,7 +71,7 @@ const columns = ref([
align: 'center',
},
{
title: '上次留存积分',
title: '上次留存余额',
key: 'balance',
align: 'center',
},

View File

@@ -0,0 +1,7 @@
import { request } from '@/utils'
export default {
getList: (data) => request.post('/amount/withdraw', data),
// 申请提现
apply: (data) => request.post('/amount/withdraw/set', data),
}

View File

@@ -0,0 +1,243 @@
<template>
<!-- <div> -->
<CommonPage show-footer :title="$route.title">
<div w-1200 flex items-center justify-between>
<n-card class="w-300">
<n-statistic label="可提现余额">
<n-number-animation
ref="numberAnimationInstRef"
:precision="2"
:from="0"
:to="userInfo.integral"
/>
</n-statistic>
</n-card>
<div w-100 text-center text-25>/</div>
<n-card class="w-300">
<n-statistic label="兑换比例">
<n-number-animation ref="numberAnimationInstRef" :precision="2" :from="0" :to="100" />
</n-statistic>
</n-card>
<div w-100 text-center text-25>=</div>
<n-card class="w-300">
<n-statistic label="CNY">
<n-number-animation
ref="numberAnimationInstRef"
:precision="2"
:from="0"
:to="userInfo.integral / 100"
/>
</n-statistic>
</n-card>
<div ml-10 w-300 flex flex-col items-center justify-center>
<n-input-number v-model:value="formData.integral" clearable placeholder="请输入提现积分" />
<n-button mt-10 w-full type="primary" @click="ok">立即提现</n-button>
</div>
</div>
<n-data-table
class="mt-10"
:loading="loading"
:columns="columns"
:data="data"
:pagination="pagination"
:bordered="false"
remote
/>
</CommonPage>
<!-- </div> -->
</template>
<script setup>
import api from './api'
import comm from '@/api'
import { NTag, NImage } from 'naive-ui'
const loading = ref(false)
const columns = ref([
{
title: 'ID',
key: 'ID',
align: 'center',
},
{
title: '提现金额',
key: 'integral',
align: 'center',
},
{
title: '上次留存余额',
key: 'balance',
align: 'center',
},
{
title: '服务费',
key: 'commission',
align: 'center',
},
{
title: '实际到账',
key: 'number',
align: 'center',
},
{
title: '剩余余额',
key: 'residue',
align: 'center',
},
{
title: '手续费比例',
slot: 'commission_number',
align: 'center',
render: (row) => {
return h(
'span',
{},
{
default: () => `${row.commission_number}%`,
}
)
},
},
{
title: '手续费类型',
key: 'commission_type',
align: 'center',
render: (row) => {
return h(
NTag,
{
type: row.commission_type === 1 ? 'success' : 'warning',
},
{
default: () => (row.commission_type === 1 ? '百分比' : '固定值'),
}
)
},
},
{
title: '银行名称',
key: 'bank',
align: 'center',
},
{
title: '银行卡号',
key: 'bank_card',
align: 'center',
},
{
title: '账户名称',
key: 'bank_name',
align: 'center',
},
{
title: '法人',
key: 'bank_user',
align: 'center',
},
{
title: '提现状态',
slot: 'status',
align: 'center',
render: (row) => {
return h(
NTag,
{
type: row.status === 1 ? 'success' : row.status === 2 ? 'error' : 'warning',
},
{
default: () => (row.status === 1 ? '已审核' : row.status === 2 ? '已拒绝' : '待审核'),
}
)
},
},
{
title: '提现时间',
slot: 'add_time',
align: 'center',
render: (row) => {
return h(
'span',
{},
{
default: () => row.add_time,
}
)
},
},
{
title: '打款截图',
slot: 'img',
render: (row) => {
return h(NImage, {
src: row.status_img,
width: '50',
})
},
},
])
const data = ref([])
const formData = ref({
integral: null,
})
const pagination = ref({
page: 1,
pageSize: 10,
itemCount: 0,
onChange: (page) => {
pagination.value.page = page
getList()
},
onUpdatePageSize: (pageSize) => {
pagination.value.pageSize = pageSize
pagination.value.page = 1
getList()
},
})
onMounted(() => {
getList()
getData()
})
const userInfo = ref({})
const getData = async () => {
const res = await comm.getMerchantInfo()
userInfo.value = res.data.data
}
const getList = async () => {
loading.value = true
try {
const res = await api.getList({
pageNum: pagination.value.page,
pageSize: pagination.value.pageSize,
})
data.value = res.data.data || []
pagination.value.itemCount = res.data.total
} catch (error) {
$message.error(error.msg)
}
loading.value = false
}
const ok = async () => {
// if (formData.value.integral < 1000) return $message.warning('提现积分不能小于10000')
const res = await api.apply({
number: formData.value.integral,
})
$message.success(res.msg)
clear()
}
const clear = () => {
formData.value.integral = null
getList()
getData()
}
</script>
<style lang="scss" scoped></style>

View File

@@ -1,6 +1,7 @@
<script setup>
import api from '../api'
import Upload from '@/components/Upload.vue'
import TiandituPicker from '@/components/TiandituPicker.vue'
onMounted(() => {
getInfo()
@@ -114,6 +115,13 @@ window.addEventListener('message', (res) => {
showModal.value = false
})
const confirm = (data) => {
model.value.lt = `${data.latlng.lat},${data.latlng.lng}`
model.value.lat = data.latlng.lat
model.value.lon = data.latlng.lng
showModal.value = false
}
const submit = () => {
formRef.value?.validate(async (errors) => {
if (!errors) {
@@ -214,19 +222,14 @@ const submit = () => {
<!-- h5地图 -->
<n-modal v-if="showModal" v-model:show="showModal">
<n-card
style="width: 600px; height: 600px"
style="width: 600px; height: auto"
title="查找地图位置"
:bordered="false"
size="huge"
role="dialog"
aria-modal="true"
>
<iframe
src="https://apis.map.qq.com/tools/locpicker?type=1&key=S3GBZ-WR26O-IXNW2-SXBOD-LZXV6-WAFNO&referer=myapp"
width="100%"
height="100%"
frameborder="0"
></iframe>
<TiandituPicker @confirm="confirm" @cancel="showModal = false" />
</n-card>
</n-modal>
</CommonPage>

View File

@@ -9,18 +9,18 @@
</n-statistic>
</n-card> -->
<n-card ml-10 w-200 rounded-5>
<n-statistic label="总豆子" tabular-nums>
<n-statistic label="用户豆子(留存)" tabular-nums>
<n-number-animation :from="0" :to="cardData.pulse" />
</n-statistic>
</n-card>
<n-card ml-10 w-200 rounded-5>
<n-statistic label="用户赢(积分)" tabular-nums>
<n-statistic label="用户积分(留存)" tabular-nums>
<n-number-animation :from="0" :to="cardData.win" />
</n-statistic>
</n-card>
</div>
</n-col>
<n-col :span="24" mt-10>
<!-- <n-col :span="24" mt-10>
<div>
<span>筛选状态</span>
<n-radio-group v-model:value="queryData.status">
@@ -32,7 +32,7 @@
/>
</n-radio-group>
</div>
</n-col>
</n-col> -->
<n-col :span="24">
<div mt-10 flex items-center>
<div w-100>关键字搜索</div>
@@ -162,12 +162,8 @@
<script setup>
import api from './api'
import {
// NDropdown,
NButton,
NEllipsis,
} from 'naive-ui'
// import TheIcon from '@/components/icon/TheIcon.vue'
import { NDropdown, NButton, NEllipsis } from 'naive-ui'
import TheIcon from '@/components/icon/TheIcon.vue'
const loading = ref(false)
@@ -184,24 +180,24 @@ const queryData = ref({
word: '',
})
const songs = ref([
{
value: 1,
label: '未使用',
},
{
value: 3,
label: '用户赢',
},
{
value: 4,
label: '用户输',
},
{
value: 5,
label: '已过期',
},
])
// const songs = ref([
// {
// value: 1,
// label: '未使用',
// },
// {
// value: 3,
// label: '用户赢',
// },
// {
// value: 4,
// label: '用户输',
// },
// {
// value: 5,
// label: '已过期',
// },
// ])
const selectOptions = ref([
{
@@ -252,65 +248,65 @@ const columns = ref([
sorter: true,
sortOrder: false,
},
{
title: '赢积分',
align: 'center',
key: 'win',
sorter: true,
sortOrder: false,
},
{
title: '用户豆子',
align: 'center',
key: 'pulse',
sorter: true,
sortOrder: false,
},
// {
// title: '操作',
// title: '赢积分',
// align: 'center',
// slot: 'action',
// render(row) {
// return [
// h(
// NDropdown,
// {
// trigger: 'click',
// options: [
// {
// label: '用户详情',
// key: 1,
// },
// ],
// onSelect: (key) => {
// switch (key) {
// case 1:
// openDrawer(row)
// break
// }
// },
// },
// {
// default: () =>
// h(
// NButton,
// {
// text: true,
// iconPlacement: 'right',
// },
// {
// default: () => '更多',
// icon: () =>
// h(TheIcon, {
// icon: 'ant-design:down-outlined',
// }),
// }
// ),
// }
// ),
// ]
// },
// key: 'win',
// sorter: true,
// sortOrder: false,
// },
// {
// title: '用户豆子',
// align: 'center',
// key: 'pulse',
// sorter: true,
// sortOrder: false,
// },
{
title: '操作',
align: 'center',
slot: 'action',
render(row) {
return [
h(
NDropdown,
{
trigger: 'click',
options: [
{
label: '用户详情',
key: 1,
},
],
onSelect: (key) => {
switch (key) {
case 1:
openDrawer(row)
break
}
},
},
{
default: () =>
h(
NButton,
{
text: true,
iconPlacement: 'right',
},
{
default: () => '更多',
icon: () =>
h(TheIcon, {
icon: 'ant-design:down-outlined',
}),
}
),
}
),
]
},
},
])
const data = ref([])

View File

@@ -12,7 +12,7 @@ export default {
component: () => import('./index.vue'),
meta: {
title: '工作台',
icon: 'mdi:home',
icon: 'mdi:index',
order: 0,
},
},

View File

@@ -20,6 +20,14 @@ export default defineConfig(({ command, mode }) => {
VITE_SENTRY,
} = viteEnv
// 调试代理配置
console.log('=== Vite代理配置调试 ===')
console.log('VITE_USE_PROXY:', VITE_USE_PROXY)
console.log('VITE_BASE_API:', VITE_BASE_API)
console.log('VITE_ADMIN_API:', VITE_ADMIN_API)
console.log('PROXY_CONFIG:', PROXY_CONFIG)
console.log('所有环境变量:', viteEnv)
return {
base: VITE_PUBLIC_PATH || '/',
resolve: {
@@ -34,12 +42,12 @@ export default defineConfig(({ command, mode }) => {
https: false,
port: VITE_PORT,
open: false,
proxy: VITE_USE_PROXY
? {
[VITE_BASE_API]: PROXY_CONFIG[VITE_BASE_API],
[VITE_ADMIN_API]: PROXY_CONFIG[VITE_ADMIN_API],
}
: undefined,
proxy: {
'/api1': PROXY_CONFIG['/api1'],
'/api': PROXY_CONFIG['/api'],
'/admin1': PROXY_CONFIG['/admin1'],
'/admin': PROXY_CONFIG['/admin'],
},
},
build: {
target: 'es2015',