Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dd08421b6 | ||
|
|
a45594a4bc | ||
|
|
d0127ca07c | ||
|
|
b342b24aa5 | ||
|
|
76343f5838 | ||
|
|
37aace8a87 | ||
|
|
be3744a0d1 | ||
|
|
babaf53237 | ||
|
|
81019adff4 | ||
|
|
1108e1b321 | ||
|
|
f41aa192fb | ||
|
|
2c175b2324 | ||
|
|
b8be5859b1 | ||
|
|
58b2edc97b | ||
|
|
200a6bf219 | ||
|
|
f70644e497 | ||
|
|
5cd6d4788a | ||
|
|
4810ffee11 | ||
|
|
03fc133850 | ||
|
|
e17c4132bc | ||
|
|
5c51db1dfe | ||
|
|
58b14c385c | ||
|
|
56036d6c00 | ||
|
|
daef6594b6 | ||
|
|
76943141f7 | ||
|
|
7d54be028a | ||
|
|
f5b2bf5adc | ||
|
|
f9994a5ce8 | ||
|
|
8cdf58310e | ||
|
|
75c49e89c2 | ||
|
|
8405471afd | ||
|
|
1e0b38451c | ||
|
|
b84823610c | ||
|
|
332ddbe0e8 | ||
|
|
9cfbf48ce9 |
@@ -0,0 +1,170 @@
|
|||||||
|
name: Playwright Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
mysql-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: /tmp/.docker_cache
|
||||||
|
key: ${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('**/Dockerfile*', '**/requirements.txt') }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Create temporary Dockerfile with Python ${{ matrix.python-version }}
|
||||||
|
run: |
|
||||||
|
# Save original Dockerfile.my
|
||||||
|
cp ruoyi-fastapi-backend/Dockerfile.my ruoyi-fastapi-backend/Dockerfile.my.bak
|
||||||
|
# Create temporary Dockerfile with target Python version
|
||||||
|
sed "s/FROM python:3.10/FROM python:${{ matrix.python-version }}/g" ruoyi-fastapi-backend/Dockerfile.my.bak > ruoyi-fastapi-backend/Dockerfile.my
|
||||||
|
|
||||||
|
- name: Start services with docker-compose
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
docker compose -f docker-compose.test.my.yml up -d --build
|
||||||
|
|
||||||
|
- name: Wait for services to be ready
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
# Wait for backend to be running
|
||||||
|
timeout 180 bash -c 'until docker compose -f docker-compose.test.my.yml ps ruoyi-backend-my | grep -q "Up"; do sleep 5; done'
|
||||||
|
# Wait for frontend to be running
|
||||||
|
timeout 120 bash -c 'until docker compose -f docker-compose.test.my.yml ps ruoyi-frontend | grep -q "Up"; do sleep 5; done'
|
||||||
|
# Additional wait for services to be fully ready and listening on ports
|
||||||
|
sleep 30
|
||||||
|
# Check that backend is actually responding on the API endpoint
|
||||||
|
echo "Checking if backend service is ready..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||||
|
echo "Backend service is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for backend service to be ready... ($i/30)"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
# Final check
|
||||||
|
if ! curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||||
|
echo "Backend service failed to start properly. Checking logs..."
|
||||||
|
docker logs ruoyi-backend-my-test
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
pip install -r requirements.txt
|
||||||
|
playwright install
|
||||||
|
pytest -v
|
||||||
|
|
||||||
|
- name: Stop services
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
docker compose -f docker-compose.test.my.yml down
|
||||||
|
|
||||||
|
- name: Restore original Dockerfile.my
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
# Restore original Dockerfile.my after test
|
||||||
|
mv ruoyi-fastapi-backend/Dockerfile.my.bak ruoyi-fastapi-backend/Dockerfile.my
|
||||||
|
|
||||||
|
pg-test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: /tmp/.docker_cache
|
||||||
|
key: ${{ runner.os }}-docker-${{ matrix.python-version }}-${{ hashFiles('**/Dockerfile*', '**/requirements.txt') }}
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Create temporary Dockerfile with Python ${{ matrix.python-version }}
|
||||||
|
run: |
|
||||||
|
# Save original Dockerfile.my
|
||||||
|
cp ruoyi-fastapi-backend/Dockerfile.my ruoyi-fastapi-backend/Dockerfile.my.bak
|
||||||
|
# Create temporary Dockerfile with target Python version
|
||||||
|
sed "s/FROM python:3.10/FROM python:${{ matrix.python-version }}/g" ruoyi-fastapi-backend/Dockerfile.my.bak > ruoyi-fastapi-backend/Dockerfile.my
|
||||||
|
|
||||||
|
- name: Start services with docker-compose
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
docker compose -f docker-compose.test.pg.yml up -d --build
|
||||||
|
|
||||||
|
- name: Wait for services to be ready
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
# Wait for backend to be running
|
||||||
|
timeout 180 bash -c 'until docker compose -f docker-compose.test.pg.yml ps ruoyi-backend-pg | grep -q "Up"; do sleep 5; done'
|
||||||
|
# Wait for frontend to be running
|
||||||
|
timeout 120 bash -c 'until docker compose -f docker-compose.test.pg.yml ps ruoyi-frontend | grep -q "Up"; do sleep 5; done'
|
||||||
|
# Additional wait for services to be fully ready and listening on ports
|
||||||
|
sleep 30
|
||||||
|
# Check that backend is actually responding on the API endpoint
|
||||||
|
echo "Checking if backend service is ready..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||||
|
echo "Backend service is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for backend service to be ready... ($i/30)"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
# Final check
|
||||||
|
if ! curl -f http://localhost:9099/captchaImage > /dev/null 2>&1; then
|
||||||
|
echo "Backend service failed to start properly. Checking logs..."
|
||||||
|
docker logs ruoyi-backend-pg-test
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
pip install -r requirements.txt
|
||||||
|
playwright install
|
||||||
|
pytest -v
|
||||||
|
|
||||||
|
- name: Stop services
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
cd ruoyi-fastapi-test
|
||||||
|
docker compose -f docker-compose.test.pg.yml down
|
||||||
|
|
||||||
|
- name: Restore original Dockerfile.my
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
# Restore original Dockerfile.my after test
|
||||||
|
mv ruoyi-fastapi-backend/Dockerfile.my.bak ruoyi-fastapi-backend/Dockerfile.my
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
name: Ruff Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint-format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install ruff
|
||||||
|
|
||||||
|
- name: Run linting
|
||||||
|
run: |
|
||||||
|
ruff check ruoyi-fastapi-backend
|
||||||
|
ruff check ruoyi-fastapi-test
|
||||||
|
|
||||||
|
- name: Run format check
|
||||||
|
run: |
|
||||||
|
ruff format ruoyi-fastapi-backend --check
|
||||||
|
ruff format ruoyi-fastapi-test --check
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
name: Tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ master ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ master ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-format:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v5
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
python-version: "3.9"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install ruff
|
|
||||||
|
|
||||||
- name: Run linting
|
|
||||||
run: |
|
|
||||||
ruff check ruoyi-fastapi-backend
|
|
||||||
|
|
||||||
- name: Run format check
|
|
||||||
run: |
|
|
||||||
ruff format ruoyi-fastapi-backend --check
|
|
||||||
@@ -143,3 +143,12 @@ cython_debug/
|
|||||||
# VSCode
|
# VSCode
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
# AI Editor
|
||||||
|
.agent/
|
||||||
|
.claude/
|
||||||
|
.codebuddy/
|
||||||
|
.codex/
|
||||||
|
.cursor/
|
||||||
|
.opencode/
|
||||||
|
.qoder/
|
||||||
|
.trae/
|
||||||
|
|||||||
@@ -1,10 +1,63 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
## RuoYi-Vue3-FastAPI v1.9.0
|
||||||
|
|
||||||
|
### 项目依赖
|
||||||
|
|
||||||
|
前后端依赖均有升级,请升级依赖或重新创建环境。
|
||||||
|
|
||||||
|
### 新增功能
|
||||||
|
|
||||||
|
1.新增AI管理模块 ([#69](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/69))。
|
||||||
|
2.新增移动端模块 ([#73](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/73))。
|
||||||
|
3.新增多worker运行支持 ([#76](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/76))。
|
||||||
|
4.应用新增演示模式 ([#78](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/78))。
|
||||||
|
|
||||||
|
### BUG修复
|
||||||
|
|
||||||
|
1.修复代码生成controller模板删除接口query_db参数异常的问题 ([#63](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/63))。
|
||||||
|
2.修复登录接口response_model声明错误 ([#71](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/71))。
|
||||||
|
3.修复无法直接通过后端地址访问API文档的问题 ([#74](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/74))。
|
||||||
|
4.修复create_app重复执行的问题 ([#84](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/84))。
|
||||||
|
|
||||||
|
### 代码重构
|
||||||
|
|
||||||
|
1.移除对python3.9的支持 ([#67](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/67))。
|
||||||
|
|
||||||
|
### 代码优化
|
||||||
|
|
||||||
|
1.优化alembic处理表模型逻辑,避免无关表影响 ([#68](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/68))。
|
||||||
|
2.优化代码生成后端模板 ([#72](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/72))。
|
||||||
|
3.自动注册路由出错时抛出异常以便于调试 ([#79](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/79))。
|
||||||
|
4.优化部分页面字段tooltip说明 (#80)。
|
||||||
|
5.优化项目启动速度 ([#82](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/82))。
|
||||||
|
6.优化暗黑模式切换效果 ([#83](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/83))。
|
||||||
|
7.优化热重载模式或单worker下scheduler的任务状态同步机制 ([#85](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/85))。
|
||||||
|
8.优化防重提交间隔时间可自定义 ([#87](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/87))。
|
||||||
|
9.优化验证码计算结果为非负数 ([#88](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/88))。
|
||||||
|
10.优化ci测试稳定性 ([#90](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/90))。
|
||||||
|
|
||||||
|
## RuoYi-Vue3-FastAPI v1.8.1
|
||||||
|
|
||||||
|
### 新增功能
|
||||||
|
|
||||||
|
1.新增E2E测试 ([#57](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/57))。
|
||||||
|
|
||||||
|
### BUG修复
|
||||||
|
|
||||||
|
1.修复DictTag组件渲染异常的问题 ([#59](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/59))。
|
||||||
|
|
||||||
|
### 代码优化
|
||||||
|
|
||||||
|
1.优化数据权限依赖 ([#55](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/55))。
|
||||||
|
2.动态导入定时任务函数,移除eval ([#56](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/56))。
|
||||||
|
3.优化pg版本的docker compose配置文件 ([#61](https://github.com/insistence/RuoYi-Vue3-FastAPI/pull/61))。
|
||||||
|
|
||||||
## RuoYi-Vue3-FastAPI v1.8.0
|
## RuoYi-Vue3-FastAPI v1.8.0
|
||||||
|
|
||||||
### 项目依赖
|
### 项目依赖
|
||||||
|
|
||||||
## 后端
|
#### 后端
|
||||||
|
|
||||||
1.后端依赖升级到最新版本,请升级依赖或重新创建环境。
|
1.后端依赖升级到最新版本,请升级依赖或重新创建环境。
|
||||||
|
|
||||||
@@ -21,8 +74,8 @@
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复单账号登录模式下强退功能失效的问题 #52。
|
1.修复单账号登录模式下强退功能失效的问题 [#52](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/52)。
|
||||||
2.确保ApschedulerJobs字段类型与apscheduler默认创建的表字段类型一致 #53。
|
2.确保ApschedulerJobs字段类型与apscheduler默认创建的表字段类型一致 [#53](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/53)。
|
||||||
3.修复磁盘存在异常时服务监控无法正常运行的问题。
|
3.修复磁盘存在异常时服务监控无法正常运行的问题。
|
||||||
4.移除代码生成表业务表外键,修复无法删除的问题。
|
4.移除代码生成表业务表外键,修复无法删除的问题。
|
||||||
5.修复固定头部时出现的导航栏偏移问题。
|
5.修复固定头部时出现的导航栏偏移问题。
|
||||||
@@ -62,7 +115,7 @@
|
|||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复代码生成controller模板编辑接口异常生成字段的问题。
|
1.修复代码生成controller模板编辑接口异常生成字段的问题。
|
||||||
2.移除passlib直接使用bcrypt修复密码校验异常的问题 #48 #49。
|
2.移除passlib直接使用bcrypt修复密码校验异常的问题 [#48](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/48) [#49](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/49)。
|
||||||
|
|
||||||
### 代码优化
|
### 代码优化
|
||||||
|
|
||||||
@@ -99,7 +152,7 @@
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复logout接口未按照app_same_time_login配置项动态判断的问题 #IBZZ1S。
|
1.修复logout接口未按照app_same_time_login配置项动态判断的问题 [#IBZZ1S](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/IBZZ1S)。
|
||||||
2.修复上传组件被多次引用拖动仅对第一个有效的问题。
|
2.修复上传组件被多次引用拖动仅对第一个有效的问题。
|
||||||
|
|
||||||
### 代码优化
|
### 代码优化
|
||||||
@@ -128,18 +181,18 @@
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复日志管理时间查询报错 #27。
|
1.修复日志管理时间查询报错 [#27](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/27)。
|
||||||
2.修复定时任务状态暂停时执行单次任务会触发cron表达式的问题 #31。
|
2.修复定时任务状态暂停时执行单次任务会触发cron表达式的问题 [#31](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/31)。
|
||||||
3.修复修改字典类型时获取dict_code异常的问题。
|
3.修复修改字典类型时获取dict_code异常的问题。
|
||||||
4.修复修改字典类型时字典数据更新时间异常的问题。
|
4.修复修改字典类型时字典数据更新时间异常的问题。
|
||||||
5.修复代码生成模板时间查询问题 #28。
|
5.修复代码生成模板时间查询问题 [#28](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/28)。
|
||||||
6.修复用户导出缺失部门名称的问题。
|
6.修复用户导出缺失部门名称的问题。
|
||||||
|
|
||||||
### 代码优化
|
### 代码优化
|
||||||
|
|
||||||
1.优化代码生成新增和编辑字段显示和渲染。
|
1.优化代码生成新增和编辑字段显示和渲染。
|
||||||
2.pagination更换成flex布局。
|
2.pagination更换成flex布局。
|
||||||
3.优化代码生成vue模板 #23。
|
3.优化代码生成vue模板 [#23](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/23)。
|
||||||
|
|
||||||
## RuoYi-Vue3-FastAPI v1.6.1
|
## RuoYi-Vue3-FastAPI v1.6.1
|
||||||
|
|
||||||
@@ -182,7 +235,7 @@ pip install sqlglot[rs]==26.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|||||||
1.修复默认关闭Tags-Views时,内链页面打不开。
|
1.修复默认关闭Tags-Views时,内链页面打不开。
|
||||||
2.修复删除当前登录用户拦截失效的问题。
|
2.修复删除当前登录用户拦截失效的问题。
|
||||||
3.修复定时任务目标字符串规则校验不全的问题。
|
3.修复定时任务目标字符串规则校验不全的问题。
|
||||||
4.修复执行单次任务时会覆盖已启用任务的问题 #IBEKD2。
|
4.修复执行单次任务时会覆盖已启用任务的问题 [#IBEKD2](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/IBEKD2)。
|
||||||
5.修复个人中心特殊字符密码修改失败问题。
|
5.修复个人中心特殊字符密码修改失败问题。
|
||||||
|
|
||||||
### 代码优化
|
### 代码优化
|
||||||
@@ -215,7 +268,7 @@ pip install sqlglot[rs]==26.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复DictTag组件控制台抛异常的问题 #IAYSVZ。
|
1.修复DictTag组件控制台抛异常的问题 [#IAYSVZ](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/IAYSVZ)。
|
||||||
2.修复登录日志导出文件名称错误的问题。
|
2.修复登录日志导出文件名称错误的问题。
|
||||||
|
|
||||||
### 代码回滚
|
### 代码回滚
|
||||||
@@ -278,7 +331,7 @@ pip install fastapi[all]==0.115.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复分页函数计算has_next错误的问题 #10。
|
1.修复分页函数计算has_next错误的问题 [#10](https://github.com/insistence/RuoYi-Vue3-FastAPI/issues/10)。
|
||||||
2.修复定时任务监听函数中事件没有job_id报错的问题。
|
2.修复定时任务监听函数中事件没有job_id报错的问题。
|
||||||
|
|
||||||
### 代码优化
|
### 代码优化
|
||||||
@@ -412,8 +465,8 @@ pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复编辑定时任务时更新的信息未同步至scheduler的问题 #I9EK56。
|
1.修复编辑定时任务时更新的信息未同步至scheduler的问题 [#I9EK56](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/I9EK56)。
|
||||||
2.修复编辑角色数据权限时后端异常的问题 #I9ENQN。
|
2.修复编辑角色数据权限时后端异常的问题 [#I9ENQN](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/I9ENQN)。
|
||||||
3.修复菜单配置路由参数不生效的问题。
|
3.修复菜单配置路由参数不生效的问题。
|
||||||
4.修复获取路由信息时菜单排序不生效的问题。
|
4.修复获取路由信息时菜单排序不生效的问题。
|
||||||
5.修复添加菜单时是否外链和是否缓存回显异常的问题。
|
5.修复添加菜单时是否外链和是否缓存回显异常的问题。
|
||||||
@@ -428,8 +481,8 @@ pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复token本身过期时退出登录接口异常的问题 #I9CBWT。
|
1.修复token本身过期时退出登录接口异常的问题 [#I9CBWT](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/I9CBWT)。
|
||||||
2.修复系统版本号或浏览器版本号无法获取时登录异常的问题 #I9CYNM。
|
2.修复系统版本号或浏览器版本号无法获取时登录异常的问题 [#I9CYNM](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/I9CYNM)。
|
||||||
|
|
||||||
## RuoYi-Vue3-FastAPI v1.0.3
|
## RuoYi-Vue3-FastAPI v1.0.3
|
||||||
|
|
||||||
@@ -439,8 +492,8 @@ pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。
|
|||||||
|
|
||||||
### BUG修复
|
### BUG修复
|
||||||
|
|
||||||
1.修复外链菜单无法打开的问题 #I95KBK。
|
1.修复外链菜单无法打开的问题 [#I95KBK](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/I95KBK)。
|
||||||
2.修复添加和编辑菜单页面中是否缓存和是否外链字段回显异常的问题 #I95KBK。
|
2.修复添加和编辑菜单页面中是否缓存和是否外链字段回显异常的问题 [#I95KBK](https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/issues/I95KBK)。
|
||||||
|
|
||||||
## RuoYi-Vue3-FastAPI v1.0.2
|
## RuoYi-Vue3-FastAPI v1.0.2
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,34 @@
|
|||||||
<p align="center">
|
<h1 align="center">
|
||||||
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
|
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
|
||||||
</p>
|
</h1>
|
||||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI v1.8.0</h1>
|
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi-Vue3-FastAPI</h1>
|
||||||
<h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4>
|
<h4 align="center">基于RuoYi-Vue3+FastAPI前后端分离的快速开发框架</h4>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers"><img src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark"></a>
|
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/stargazers">
|
||||||
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social"></a>
|
<img alt="Gitee" src="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/badge/star.svg?theme=dark">
|
||||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI"><img src="https://img.shields.io/badge/RuoYiVue3FastAPI-v1.8.0-brightgreen.svg"></a>
|
</a>
|
||||||
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
|
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI">
|
||||||
<img src="https://img.shields.io/badge/node-≥18-blue">
|
<img alt="Github" src="https://img.shields.io/github/stars/insistence/RuoYi-Vue3-FastAPI?style=social">
|
||||||
<img src="https://img.shields.io/badge/python-≥3.9-blue">
|
</a>
|
||||||
<img src="https://img.shields.io/badge/MySQL-≥5.7-blue">
|
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22%22Playwright+Tests%22%22">
|
||||||
|
<img alt="Playwright Tests" src="https://github.com/insistence/RuoYi-Vue3-FastAPI/workflows/Playwright Tests/badge.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/insistence/RuoYi-Vue3-FastAPI/actions?query=branch%3Amaster+event%3Apush+workflow%3A%22%22Ruff+Check%22%22">
|
||||||
|
<img alt="Ruff Check" src="https://github.com/insistence/RuoYi-Vue3-FastAPI/workflows/Ruff Check/badge.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI">
|
||||||
|
<img alt="project version" src="https://img.shields.io/badge/version-1.9.0-brightgreen.svg">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/astral-sh/ruff">
|
||||||
|
<img alt="Ruff" src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json">
|
||||||
|
</a>
|
||||||
|
<a href="https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/LICENSE">
|
||||||
|
<img alt="LICENSE" src="https://img.shields.io/github/license/mashape/apistatus.svg">
|
||||||
|
</a>
|
||||||
|
<img alt="node version" src="https://img.shields.io/badge/node-≥18-blue">
|
||||||
|
<img alt="python version" src="https://img.shields.io/badge/python-≥3.10-blue">
|
||||||
|
<img alt="mysql version" src="https://img.shields.io/badge/MySQL-≥5.7-blue">
|
||||||
|
<img alt="redis version" src="https://img.shields.io/badge/redis-≥6.2-blue">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## 平台简介
|
## 平台简介
|
||||||
@@ -18,28 +36,29 @@
|
|||||||
RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||||
|
|
||||||
* 前端采用Vue3、Element Plus,基于<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>前端项目修改。
|
* 前端采用Vue3、Element Plus,基于<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>前端项目修改。
|
||||||
|
* 移动端采用uni-app、Vue3、Vite,内置tailwindcss,基于<u>[RuoYi-App](https://github.com/yangzongzhuan/RuoYi-App)</u>项目修改。
|
||||||
* 后端采用FastAPI、sqlalchemy、MySQL(PostgreSQL)、Redis、OAuth2 & Jwt。
|
* 后端采用FastAPI、sqlalchemy、MySQL(PostgreSQL)、Redis、OAuth2 & Jwt。
|
||||||
* 权限认证使用OAuth2 & Jwt,支持多终端认证系统。
|
* 权限认证使用OAuth2 & Jwt,支持多终端认证系统。
|
||||||
* 支持加载动态权限菜单,多方式轻松权限控制。
|
* 支持加载动态权限菜单,多方式轻松权限控制。
|
||||||
* Vue2版本:
|
* Vue2版本:
|
||||||
- Gitte仓库地址:https://gitee.com/insistence2022/RuoYi-Vue-FastAPI
|
* Gitte仓库地址:<https://gitee.com/insistence2022/RuoYi-Vue-FastAPI>
|
||||||
- GitHub仓库地址:https://github.com/insistence/RuoYi-Vue-FastAPI
|
* GitHub仓库地址:<https://github.com/insistence/RuoYi-Vue-FastAPI>
|
||||||
* 纯Python版本:
|
* 纯Python版本:
|
||||||
- Gitte仓库地址:https://gitee.com/insistence2022/dash-fastapi-admin
|
* Gitte仓库地址:<https://gitee.com/insistence2022/dash-fastapi-admin>
|
||||||
- GitHub仓库地址:https://github.com/insistence/Dash-FastAPI-Admin
|
* GitHub仓库地址:<https://github.com/insistence/Dash-FastAPI-Admin>
|
||||||
* 特别鸣谢:<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>
|
* 特别鸣谢:<u>[RuoYi-Vue3](https://github.com/yangzongzhuan/RuoYi-Vue3)</u>、<u>[RuoYi-App](https://github.com/yangzongzhuan/RuoYi-App)</u>
|
||||||
|
|
||||||
## 内置功能
|
## 内置功能
|
||||||
|
|
||||||
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
2. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
2. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
|
||||||
3. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
3. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||||
4. 部门管理:配置系统组织机构(公司、部门、小组)。
|
4. 部门管理:配置系统组织机构(公司、部门、小组)。
|
||||||
5. 岗位管理:配置系统用户所属担任职务。
|
5. 岗位管理:配置系统用户所属担任职务。
|
||||||
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||||
7. 参数管理:对系统动态配置常用参数。
|
7. 参数管理:对系统动态配置常用参数。
|
||||||
8. 通知公告:系统通知公告信息发布维护。
|
8. 通知公告:系统通知公告信息发布维护。
|
||||||
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||||
10. 登录日志:系统登录日志记录查询包含登录异常。
|
10. 登录日志:系统登录日志记录查询包含登录异常。
|
||||||
11. 在线用户:当前系统中活跃用户状态监控。
|
11. 在线用户:当前系统中活跃用户状态监控。
|
||||||
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||||
@@ -48,59 +67,125 @@ RuoYi-Vue3-FastAPI是一套全部开源的快速开发平台,毫无保留给
|
|||||||
15. 在线构建器:拖动表单元素生成相应的HTML代码。
|
15. 在线构建器:拖动表单元素生成相应的HTML代码。
|
||||||
16. 系统接口:根据业务代码自动生成相关的api接口文档。
|
16. 系统接口:根据业务代码自动生成相关的api接口文档。
|
||||||
17. 代码生成:配置数据库表信息一键生成前后端代码(python、sql、vue、js),支持下载。
|
17. 代码生成:配置数据库表信息一键生成前后端代码(python、sql、vue、js),支持下载。
|
||||||
|
18. AI管理:提供AI模型管理和AI对话功能。
|
||||||
|
|
||||||
## 演示图
|
## 演示图
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/login.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dashboard.png"/></td>
|
<img alt="login" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/login.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="dashboard" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/dashboard.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/user.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/role.png"/></td>
|
<img alt="user" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/user.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="role" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/role.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/menu.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dept.png"/></td>
|
<img alt="menu" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/menu.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="dept" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/dept.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/post.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/dict.png"/></td>
|
<img alt=""post src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/post.png">
|
||||||
</tr>
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="dict" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/dict.png">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/config.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/notice.png"/></td>
|
<img alt="config" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/config.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="notice" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/notice.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/operLog.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/loginLog.png"/></td>
|
<img alt="operLog" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/operLog.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="loginLog" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/loginLog.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/online.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/job.png"/></td>
|
<img alt="online" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/online.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="job" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/job.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/server.png"/></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cache.png"/></td>
|
<img alt="server" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/server.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="cache" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/cache.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/cacheList.png"></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/form.png"></td>
|
<img alt="cacheList" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/cacheList.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="form" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/form.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/api.png"></td>
|
<td>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/gen.png"/></td>
|
<img alt="api" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/api.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="gen" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/gen.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/profile.png"/></td>
|
<td>
|
||||||
|
<img alt="aiModel" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/aiModel.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="aiChat" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/aiChat.png">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img alt="profile" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/web/profile.png">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img alt="applogin" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/app/login.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="appWorkbench" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/app/workbench.png">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="appProfile" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/vue3/app/profile.png">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
- *账号:admin*
|
|
||||||
- *密码:admin123*
|
* *账号:admin*
|
||||||
- 演示地址:<a href="https://vfadmin.insistence.tech">vfadmin管理系统<a>
|
* *密码:admin123*
|
||||||
|
* 演示地址:<a href="https://vfadmin.insistence.tech">vfadmin管理系统<a>
|
||||||
|
|
||||||
## 项目开发及发布相关
|
## 项目开发及发布相关
|
||||||
|
|
||||||
@@ -115,6 +200,7 @@ cd RuoYi-Vue3-FastAPI
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 前端
|
#### 前端
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进入前端目录
|
# 进入前端目录
|
||||||
cd ruoyi-fastapi-frontend
|
cd ruoyi-fastapi-frontend
|
||||||
@@ -129,7 +215,27 @@ npm install --registry=https://registry.npmmirror.com
|
|||||||
npm run dev 或 yarn dev
|
npm run dev 或 yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 移动端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 进入移动端目录
|
||||||
|
cd ruoyi-fastapi-app
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
npm install -g pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 启动 H5
|
||||||
|
pnpm dev:h5
|
||||||
|
|
||||||
|
# 启动微信小程序
|
||||||
|
pnpm dev:mp-weixin
|
||||||
|
```
|
||||||
|
|
||||||
|
移动端详细文档请参考:[ruoyi-fastapi-app/README.md](./ruoyi-fastapi-app/README.md)
|
||||||
|
|
||||||
#### 后端
|
#### 后端
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 进入后端目录
|
# 进入后端目录
|
||||||
cd ruoyi-fastapi-backend
|
cd ruoyi-fastapi-backend
|
||||||
@@ -151,6 +257,7 @@ python3 app.py --env=dev
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 访问
|
#### 访问
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 默认账号密码
|
# 默认账号密码
|
||||||
账号:admin
|
账号:admin
|
||||||
@@ -163,6 +270,7 @@ python3 app.py --env=dev
|
|||||||
### 发布
|
### 发布
|
||||||
|
|
||||||
#### 前端
|
#### 前端
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 构建测试环境
|
# 构建测试环境
|
||||||
npm run build:stage 或 yarn build:stage
|
npm run build:stage 或 yarn build:stage
|
||||||
@@ -172,6 +280,7 @@ npm run build:prod 或 yarn build:prod
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 后端
|
#### 后端
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 配置环境
|
# 配置环境
|
||||||
在.env.prod文件中配置生产环境的数据库和redis
|
在.env.prod文件中配置生产环境的数据库和redis
|
||||||
@@ -180,14 +289,37 @@ npm run build:prod 或 yarn build:prod
|
|||||||
python3 app.py --env=prod
|
python3 app.py --env=prod
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Docker Compose部署方式
|
||||||
|
|
||||||
|
> ⚠️ **警告:** 默认未做数据持久化配置,请注意数据备份或自行配置持久化
|
||||||
|
|
||||||
|
#### MySQL版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.my.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PostgreSQL版本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.pg.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
## 交流与赞助
|
## 交流与赞助
|
||||||
|
|
||||||
如果有对本项目及FastAPI感兴趣的朋友,欢迎加入知识星球一起交流学习,让我们一起变得更强。如果你觉得这个项目帮助到了你,你可以请作者喝杯咖啡表示鼓励☕。扫描下面微信二维码添加微信备注VF-Admin即可进群。
|
如果有对本项目及FastAPI感兴趣的朋友,欢迎加入知识星球一起交流学习,让我们一起变得更强。如果你觉得这个项目帮助到了你,你可以请作者喝杯咖啡表示鼓励☕。扫描下面微信二维码添加微信备注VF-Admin即可进群。
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img alt="zsxq" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/zsxq.jpg"></td>
|
<td>
|
||||||
<td><img alt="zanzhu" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/zanzhu.jpg"></td>
|
<img alt="zsxq" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/common/zsxq.jpg">
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<img alt="zanzhu" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/common/zanzhu.jpg">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><img alt="wxcode" src="https://gitee.com/insistence2022/RuoYi-Vue-FastAPI/raw/master/demo-pictures/wxcode.jpg"></td>
|
<td>
|
||||||
|
<img alt="wxcode" src="https://gitee.com/insistence2022/ruoyi-fastapi-pictures/raw/master/common/wxcode.jpg">
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "19099:9099"
|
- "19099:9099"
|
||||||
depends_on:
|
depends_on:
|
||||||
- ruoyi-pg
|
ruoyi-pg:
|
||||||
- ruoyi-redis
|
condition: service_healthy
|
||||||
|
ruoyi-redis:
|
||||||
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- ruoyi-network
|
- ruoyi-network
|
||||||
|
|
||||||
@@ -58,6 +60,11 @@ services:
|
|||||||
- "16379:6379"
|
- "16379:6379"
|
||||||
networks:
|
networks:
|
||||||
- ruoyi-network
|
- ruoyi-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 30
|
||||||
|
|
||||||
# 网络配置
|
# 网络配置
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# EditorConfig is awesome: https://EditorConfig.org
|
||||||
|
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules/
|
||||||
|
unpackage/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.project
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw*
|
||||||
|
# 要提交 .env 来确保 jit v2 的开发 watch mode
|
||||||
|
!.env
|
||||||
|
|
||||||
|
src/ignore
|
||||||
|
pnpm-lock.yaml
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 设置国内镜像地址
|
||||||
|
registry=http://registry.npmmirror.com/
|
||||||
|
# registry=http://registry.npmjs.com/
|
||||||
|
# 这个是给 pnpm 用的
|
||||||
|
shamefully-hoist=true
|
||||||
|
|
||||||
|
ignore-engines=true
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# RuoYi-FastAPI-App
|
||||||
|
|
||||||
|
基于 `uni-app` 的 `vite` + `vue3` + `tailwindcss` 开发。
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- ⚡️ [Vue 3](https://github.com/vuejs/core), [Vite](https://github.com/vitejs/vite), [pnpm](https://pnpm.io/) - 快 & 稳定
|
||||||
|
|
||||||
|
- 🎨 [TailwindCSS](https://tailwindcss.com/) - 世界上最流行,生态最好的原子化CSS框架
|
||||||
|
|
||||||
|
- 😃 [集成 Iconify](https://github.com/egoist/tailwindcss-icons) - [icones.js.org](https://icones.js.org/) 中的所有图标都为你所用
|
||||||
|
|
||||||
|
- 📥 [API 自动加载](https://github.com/antfu/unplugin-auto-import) - 直接使用 Composition API 无需引入
|
||||||
|
|
||||||
|
- 🧬 [uni-app 条件编译样式](https://tw.icebreaker.top/docs/quick-start/uni-app-css-macro) - 帮助你在多端更灵活的使用 `TailwindCSS`
|
||||||
|
|
||||||
|
- 🦾 [TypeScript](https://www.typescriptlang.org/) & [ESLint](https://eslint.org/) & [Stylelint](https://stylelint.io/) - 样式,类型,统一的校验与格式化规则,保证你的代码风格和质量
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> 推荐使用 `"node": "^20.19.0 || >=22.12.0"` 的 Node.js 版本进行开发!
|
||||||
|
>
|
||||||
|
> 另外谨慎升级 `package.json` 中锁定的 `pinia`/`vue`/`@vue/*` 相关包的版本,新版本可能 `uni-app` 没有兼容,造成一些奇怪的 bug
|
||||||
|
|
||||||
|
### vscode
|
||||||
|
|
||||||
|
使用 `vscode` 的开发者,请先安装 [Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) 智能提示与感应插件
|
||||||
|
|
||||||
|
其他 IDE 请参考: <https://tw.icebreaker.top/docs/quick-start/intelliSense>
|
||||||
|
|
||||||
|
### 更换 Appid
|
||||||
|
|
||||||
|
把 `src/manifest.json` 中的 `appid`, 更换为你自己的 `appid`, 比如 `uni-app` / `mp-weixin` 平台。
|
||||||
|
|
||||||
|
## 升级依赖
|
||||||
|
|
||||||
|
- `pnpm up:pkg` 升级除了 `uni-app` 相关的其他依赖
|
||||||
|
- `pnpm up:uniapp` 升级 `uni-app` 相关的依赖
|
||||||
|
|
||||||
|
推荐先使用 `pnpm up:pkg` 升级, 再使用 `pnpm up:uniapp` 进行升级,因为 `pnpm up:uniapp` 很有可能会进行版本的降级已达到和 `uni-app` 版本匹配的效果
|
||||||
|
|
||||||
|
## 切换镜像源
|
||||||
|
|
||||||
|
默认情况下,走的是淘宝镜像源 : `registry.npmmirror.com`
|
||||||
|
|
||||||
|
假如你需要修改镜像源,请修改目录下的 `.npmrc` 文件,然后重新进行 `pnpm i` 安装包即可
|
||||||
|
|
||||||
|
## 包管理器
|
||||||
|
|
||||||
|
本项目默认使用 `pnpm@10` 进行管理,当然你也可以切换到其他包管理器,比如 `yarn`, `npm`
|
||||||
|
|
||||||
|
你只需要把 `pnpm-lock.yaml` 删掉,然后把 `package.json` 中的 `packageManager` 字段去除或者换成你具体的包管理器版本,然后重新安装即可
|
||||||
|
|
||||||
|
### weapp-ide-cli
|
||||||
|
|
||||||
|
本项目已经集成 `weapp-ide-cli` 可以通过 `cli` 对 `ide` 进行额外操作
|
||||||
|
|
||||||
|
- `pnpm open:dev` 打开微信开发者工具,引入 `dist/dev/mp-weixin`
|
||||||
|
- `pnpm open:build` 打开微信开发者工具,引入 `dist/build/mp-weixin`
|
||||||
|
|
||||||
|
[详细信息](https://www.npmjs.com/package/weapp-ide-cli)
|
||||||
|
|
||||||
|
## tailwindcss 生态
|
||||||
|
|
||||||
|
详见:<https://github.com/aniftyco/awesome-tailwindcss>
|
||||||
|
|
||||||
|
你可以在这里找到许多现成的UI,组件模板。
|
||||||
|
|
||||||
|
## 单位转换
|
||||||
|
|
||||||
|
- `rem` -> `rpx` (默认开启, 见 `vite.config.ts` 中 `uvtw` 插件的 `rem2rpx` 选项)
|
||||||
|
- `px` -> `rpx` (默认不开启,可在 `postcss.config.ts` 中引入 `postcss-pxtransform` 开启配置)
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- 升级 `uni-app` 依赖的方式为 `npx @dcloudio/uvm` 后,选择对应的 `Package Manager` 即可。而升级其他包的方式,可以使用 `pnpm up -Li`,这个是 `pnpm` 自带的方式。
|
||||||
|
- 使用 `vscode` 记得安装官方插件 `stylelint`,`tailwindcss`, 已在 `.vscode/extensions.json` 中设置推荐
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<script>
|
||||||
|
var coverSupport =
|
||||||
|
'CSS' in window &&
|
||||||
|
typeof CSS.supports === 'function' &&
|
||||||
|
(CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
|
||||||
|
document.write(
|
||||||
|
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
|
||||||
|
(coverSupport ? ', viewport-fit=cover' : '') +
|
||||||
|
'" />',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title></title>
|
||||||
|
<!--preload-links-->
|
||||||
|
<!--app-context-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"><!--app-html--></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# netlify 部署文件
|
||||||
|
# 用于部署此项目的 h5 产物,可删除
|
||||||
|
[build]
|
||||||
|
publish = "dist/build/h5"
|
||||||
|
command = "pnpm run build:h5"
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
NODE_VERSION = "20"
|
||||||
|
|
||||||
|
[[redirects]]
|
||||||
|
from = "/*"
|
||||||
|
to = "/index.html"
|
||||||
|
status = 200
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"name": "ruoyi-fastapi-app",
|
||||||
|
"version": "1.9.0",
|
||||||
|
"packageManager": "pnpm@10.28.1",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "uni -p mp-weixin",
|
||||||
|
"build": "npm run build:mp-weixin",
|
||||||
|
"dev:app": "uni -p app",
|
||||||
|
"dev:custom": "uni -p",
|
||||||
|
"dev:h5": "uni --host",
|
||||||
|
"dev:h5:ssr": "uni --ssr",
|
||||||
|
"dev:mp-alipay": "uni -p mp-alipay",
|
||||||
|
"dev:mp-baidu": "uni -p mp-baidu",
|
||||||
|
"dev:mp-kuaishou": "uni -p mp-kuaishou",
|
||||||
|
"dev:mp-lark": "uni -p mp-lark",
|
||||||
|
"dev:mp-qq": "uni -p mp-qq",
|
||||||
|
"dev:mp-toutiao": "uni -p mp-toutiao",
|
||||||
|
"dev:mp-weixin": "uni -p mp-weixin",
|
||||||
|
"dev:quickapp-webview": "uni -p quickapp-webview",
|
||||||
|
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
|
||||||
|
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
|
||||||
|
"build:app": "uni build -p app",
|
||||||
|
"build:custom": "uni build -p",
|
||||||
|
"build:h5": "uni build",
|
||||||
|
"build:h5:ssr": "uni build --ssr",
|
||||||
|
"build:mp-alipay": "uni build -p mp-alipay",
|
||||||
|
"build:mp-baidu": "uni build -p mp-baidu",
|
||||||
|
"build:mp-kuaishou": "uni build -p mp-kuaishou",
|
||||||
|
"build:mp-lark": "uni build -p mp-lark",
|
||||||
|
"build:mp-qq": "uni build -p mp-qq",
|
||||||
|
"build:mp-toutiao": "uni build -p mp-toutiao",
|
||||||
|
"build:mp-weixin": "uni build -p mp-weixin",
|
||||||
|
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||||
|
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||||
|
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
|
||||||
|
"open:dev": "weapp open -p dist/dev/mp-weixin",
|
||||||
|
"open:build": "weapp open -p dist/build/mp-weixin",
|
||||||
|
"weapp:login": "weapp login",
|
||||||
|
"upload:dev": "weapp upload -p dist/dev/mp-weixin -v 1.0.0 -d \"dev version\"",
|
||||||
|
"upload:build": "weapp upload -p dist/build/mp-weixin -v 1.0.0 -d \"release version\"",
|
||||||
|
"postinstall": "weapp-tw patch",
|
||||||
|
"up:pkg": "pnpm up -rLi \"!@dcloudio/*\"",
|
||||||
|
"up:uniapp": "pnpx @dcloudio/uvm@latest"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dcloudio/uni-app": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-app-harmony": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-app-plus": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-components": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-h5": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-alipay": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-baidu": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-harmony": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-jd": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-kuaishou": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-lark": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-qq": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-toutiao": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-weixin": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-mp-xhs": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-quickapp-webview": "3.0.0-4080720251210001",
|
||||||
|
"@vue/shared": "3.4.21",
|
||||||
|
"@weapp-tailwindcss/merge-v3": "^0.1.5",
|
||||||
|
"pinia": "2.2.4",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.1.9",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@dcloudio/types": "^3.4.8",
|
||||||
|
"@dcloudio/uni-automator": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-cli-shared": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/uni-stacktracey": "3.0.0-4080720251210001",
|
||||||
|
"@dcloudio/vite-plugin-uni": "3.0.0-4080720251210001",
|
||||||
|
"@egoist/tailwindcss-icons": "^1.9.2",
|
||||||
|
"@icebreakers/stylelint-config": "^1.2.5",
|
||||||
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
|
"@iconify-json/svg-spinners": "^1.2.4",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@vue/runtime-core": "^3.5.27",
|
||||||
|
"autoprefixer": "^10.4.24",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"sass": "^1.97.3",
|
||||||
|
"tailwindcss": "^3.4.19",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"unplugin-auto-import": "^20.3.0",
|
||||||
|
"vite": "5.4.21",
|
||||||
|
"weapp-ide-cli": "^5.0.1",
|
||||||
|
"weapp-tailwindcss": "^4.9.8"
|
||||||
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"weapp-tailwindcss",
|
||||||
|
"@weapp-tailwindcss/merge-v3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import process from "node:process";
|
||||||
|
|
||||||
|
const isH5 = process.env.UNI_PLATFORM === "h5";
|
||||||
|
const isApp = process.env.UNI_PLATFORM === "app";
|
||||||
|
const WeappTailwindcssDisabled = isH5 || isApp;
|
||||||
|
const isMp = !isH5 && !isApp;
|
||||||
|
|
||||||
|
export { isApp, isH5, isMp, WeappTailwindcssDisabled };
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import type { AcceptedPlugin } from "postcss";
|
||||||
|
import autoprefixer from "autoprefixer";
|
||||||
|
import tailwindcss from "tailwindcss";
|
||||||
|
import cssMacro from "weapp-tailwindcss/css-macro/postcss";
|
||||||
|
|
||||||
|
const plugins: AcceptedPlugin[] = [tailwindcss(), autoprefixer()];
|
||||||
|
|
||||||
|
// 可以使用 postcss-pxtransform 来进行 px 转 rpx 的功能
|
||||||
|
// 详见: https://tw.icebreaker.top/docs/quick-start/css-unit-transform#px-%E8%BD%AC-rpx
|
||||||
|
|
||||||
|
plugins.push(cssMacro);
|
||||||
|
|
||||||
|
export default plugins;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<script setup>
|
||||||
|
import config from "./config";
|
||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
import { useConfigStore } from "@/store";
|
||||||
|
import { getCurrentInstance } from "vue";
|
||||||
|
import { onLaunch } from "@dcloudio/uni-app";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
onLaunch(() => {
|
||||||
|
initApp();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化应用
|
||||||
|
function initApp() {
|
||||||
|
// 初始化应用配置
|
||||||
|
initConfig();
|
||||||
|
// 检查用户登录状态
|
||||||
|
//#ifdef H5
|
||||||
|
checkLogin();
|
||||||
|
//#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
function initConfig() {
|
||||||
|
useConfigStore().setConfig(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkLogin() {
|
||||||
|
if (!getToken()) {
|
||||||
|
proxy.$tab.reLaunch("/pages/login");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Global Reset for UniApp/Mobile */
|
||||||
|
page,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* Prevent default bounce effect on iOS if needed, or just handle overflow */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Global box-sizing for consistency with Tailwind preflight */
|
||||||
|
page,
|
||||||
|
view,
|
||||||
|
scroll-view,
|
||||||
|
image,
|
||||||
|
text,
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
label,
|
||||||
|
navigator {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修复 H5 端 uni.showToast 图标在引入 Tailwind 后可能偏左的问题 */
|
||||||
|
/* #ifdef H5 */
|
||||||
|
uni-toast img,
|
||||||
|
uni-toast svg {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
/* #endif */
|
||||||
|
|
||||||
|
/* Hide scrollbar for Chrome/Safari/Webkit */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
width: 0 !important;
|
||||||
|
height: 0 !important;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// 登录方法
|
||||||
|
export function login(username, password, code, uuid) {
|
||||||
|
const data = {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
code,
|
||||||
|
uuid,
|
||||||
|
};
|
||||||
|
return request({
|
||||||
|
url: "/login",
|
||||||
|
header: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
isToken: false,
|
||||||
|
},
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册方法
|
||||||
|
export function register(data) {
|
||||||
|
return request({
|
||||||
|
url: "/register",
|
||||||
|
headers: {
|
||||||
|
isToken: false,
|
||||||
|
},
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户详细信息
|
||||||
|
export function getInfo() {
|
||||||
|
return request({
|
||||||
|
url: "/getInfo",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 退出方法
|
||||||
|
export function logout() {
|
||||||
|
return request({
|
||||||
|
url: "/logout",
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取验证码
|
||||||
|
export function getCodeImg() {
|
||||||
|
return request({
|
||||||
|
url: "/captchaImage",
|
||||||
|
headers: {
|
||||||
|
isToken: false,
|
||||||
|
},
|
||||||
|
method: "get",
|
||||||
|
timeout: 20000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// 查询字典数据列表
|
||||||
|
export function listData(query) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/data/list",
|
||||||
|
method: "get",
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询字典数据详细
|
||||||
|
export function getData(dictCode) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/data/" + dictCode,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据字典类型查询字典数据信息
|
||||||
|
export function getDicts(dictType) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/data/type/" + dictType,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增字典数据
|
||||||
|
export function addData(data) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/data",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改字典数据
|
||||||
|
export function updateData(data) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/data",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除字典数据
|
||||||
|
export function delData(dictCode) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/data/" + dictCode,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// 查询字典类型列表
|
||||||
|
export function listType(query) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type/list",
|
||||||
|
method: "get",
|
||||||
|
params: query,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询字典类型详细
|
||||||
|
export function getType(dictId) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type/" + dictId,
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增字典类型
|
||||||
|
export function addType(data) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type",
|
||||||
|
method: "post",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改字典类型
|
||||||
|
export function updateType(data) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除字典类型
|
||||||
|
export function delType(dictId) {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type/" + dictId,
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新字典缓存
|
||||||
|
export function refreshCache() {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type/refreshCache",
|
||||||
|
method: "delete",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字典选择框列表
|
||||||
|
export function optionselect() {
|
||||||
|
return request({
|
||||||
|
url: "/system/dict/type/optionselect",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import upload from "@/utils/upload";
|
||||||
|
import request from "@/utils/request";
|
||||||
|
|
||||||
|
// 用户密码重置
|
||||||
|
export function updateUserPwd(oldPassword, newPassword) {
|
||||||
|
const data = {
|
||||||
|
oldPassword,
|
||||||
|
newPassword,
|
||||||
|
};
|
||||||
|
return request({
|
||||||
|
url: "/system/user/profile/updatePwd",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户个人信息
|
||||||
|
export function getUserProfile() {
|
||||||
|
return request({
|
||||||
|
url: "/system/user/profile",
|
||||||
|
method: "get",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改用户个人信息
|
||||||
|
export function updateUserProfile(data) {
|
||||||
|
return request({
|
||||||
|
url: "/system/user/profile",
|
||||||
|
method: "put",
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户头像上传
|
||||||
|
export function uploadAvatar(data) {
|
||||||
|
return upload({
|
||||||
|
url: "/system/user/profile/avatar",
|
||||||
|
name: data.name,
|
||||||
|
filePath: data.filePath,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
// biome-ignore lint: disable
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
const EffectScope: typeof import('vue').EffectScope
|
||||||
|
const acceptHMRUpdate: typeof import('pinia').acceptHMRUpdate
|
||||||
|
const computed: typeof import('vue').computed
|
||||||
|
const createApp: typeof import('vue').createApp
|
||||||
|
const createPinia: typeof import('pinia').createPinia
|
||||||
|
const customRef: typeof import('vue').customRef
|
||||||
|
const defineAsyncComponent: typeof import('vue').defineAsyncComponent
|
||||||
|
const defineComponent: typeof import('vue').defineComponent
|
||||||
|
const defineStore: typeof import('pinia').defineStore
|
||||||
|
const effectScope: typeof import('vue').effectScope
|
||||||
|
const getActivePinia: typeof import('pinia').getActivePinia
|
||||||
|
const getCurrentInstance: typeof import('vue').getCurrentInstance
|
||||||
|
const getCurrentScope: typeof import('vue').getCurrentScope
|
||||||
|
const getCurrentWatcher: typeof import('vue').getCurrentWatcher
|
||||||
|
const h: typeof import('vue').h
|
||||||
|
const inject: typeof import('vue').inject
|
||||||
|
const isProxy: typeof import('vue').isProxy
|
||||||
|
const isReactive: typeof import('vue').isReactive
|
||||||
|
const isReadonly: typeof import('vue').isReadonly
|
||||||
|
const isRef: typeof import('vue').isRef
|
||||||
|
const isShallow: typeof import('vue').isShallow
|
||||||
|
const mapActions: typeof import('pinia').mapActions
|
||||||
|
const mapGetters: typeof import('pinia').mapGetters
|
||||||
|
const mapState: typeof import('pinia').mapState
|
||||||
|
const mapStores: typeof import('pinia').mapStores
|
||||||
|
const mapWritableState: typeof import('pinia').mapWritableState
|
||||||
|
const markRaw: typeof import('vue').markRaw
|
||||||
|
const nextTick: typeof import('vue').nextTick
|
||||||
|
const onActivated: typeof import('vue').onActivated
|
||||||
|
const onAddToFavorites: typeof import('@dcloudio/uni-app').onAddToFavorites
|
||||||
|
const onBackPress: typeof import('@dcloudio/uni-app').onBackPress
|
||||||
|
const onBeforeMount: typeof import('vue').onBeforeMount
|
||||||
|
const onBeforeUnmount: typeof import('vue').onBeforeUnmount
|
||||||
|
const onBeforeUpdate: typeof import('vue').onBeforeUpdate
|
||||||
|
const onDeactivated: typeof import('vue').onDeactivated
|
||||||
|
const onError: typeof import('@dcloudio/uni-app').onError
|
||||||
|
const onErrorCaptured: typeof import('vue').onErrorCaptured
|
||||||
|
const onHide: typeof import('@dcloudio/uni-app').onHide
|
||||||
|
const onLaunch: typeof import('@dcloudio/uni-app').onLaunch
|
||||||
|
const onLoad: typeof import('@dcloudio/uni-app').onLoad
|
||||||
|
const onMounted: typeof import('vue').onMounted
|
||||||
|
const onNavigationBarButtonTap: typeof import('@dcloudio/uni-app').onNavigationBarButtonTap
|
||||||
|
const onNavigationBarSearchInputChanged: typeof import('@dcloudio/uni-app').onNavigationBarSearchInputChanged
|
||||||
|
const onNavigationBarSearchInputClicked: typeof import('@dcloudio/uni-app').onNavigationBarSearchInputClicked
|
||||||
|
const onNavigationBarSearchInputConfirmed: typeof import('@dcloudio/uni-app').onNavigationBarSearchInputConfirmed
|
||||||
|
const onNavigationBarSearchInputFocusChanged: typeof import('@dcloudio/uni-app').onNavigationBarSearchInputFocusChanged
|
||||||
|
const onPageNotFound: typeof import('@dcloudio/uni-app').onPageNotFound
|
||||||
|
const onPageScroll: typeof import('@dcloudio/uni-app').onPageScroll
|
||||||
|
const onPullDownRefresh: typeof import('@dcloudio/uni-app').onPullDownRefresh
|
||||||
|
const onReachBottom: typeof import('@dcloudio/uni-app').onReachBottom
|
||||||
|
const onReady: typeof import('@dcloudio/uni-app').onReady
|
||||||
|
const onRenderTracked: typeof import('vue').onRenderTracked
|
||||||
|
const onRenderTriggered: typeof import('vue').onRenderTriggered
|
||||||
|
const onResize: typeof import('@dcloudio/uni-app').onResize
|
||||||
|
const onScopeDispose: typeof import('vue').onScopeDispose
|
||||||
|
const onServerPrefetch: typeof import('vue').onServerPrefetch
|
||||||
|
const onShareAppMessage: typeof import('@dcloudio/uni-app').onShareAppMessage
|
||||||
|
const onShareTimeline: typeof import('@dcloudio/uni-app').onShareTimeline
|
||||||
|
const onShow: typeof import('@dcloudio/uni-app').onShow
|
||||||
|
const onTabItemTap: typeof import('@dcloudio/uni-app').onTabItemTap
|
||||||
|
const onThemeChange: typeof import('@dcloudio/uni-app').onThemeChange
|
||||||
|
const onUnhandledRejection: typeof import('@dcloudio/uni-app').onUnhandledRejection
|
||||||
|
const onUnload: typeof import('@dcloudio/uni-app').onUnload
|
||||||
|
const onUnmounted: typeof import('vue').onUnmounted
|
||||||
|
const onUpdated: typeof import('vue').onUpdated
|
||||||
|
const onWatcherCleanup: typeof import('vue').onWatcherCleanup
|
||||||
|
const provide: typeof import('vue').provide
|
||||||
|
const reactive: typeof import('vue').reactive
|
||||||
|
const readonly: typeof import('vue').readonly
|
||||||
|
const ref: typeof import('vue').ref
|
||||||
|
const resolveComponent: typeof import('vue').resolveComponent
|
||||||
|
const setActivePinia: typeof import('pinia').setActivePinia
|
||||||
|
const setMapStoreSuffix: typeof import('pinia').setMapStoreSuffix
|
||||||
|
const shallowReactive: typeof import('vue').shallowReactive
|
||||||
|
const shallowReadonly: typeof import('vue').shallowReadonly
|
||||||
|
const shallowRef: typeof import('vue').shallowRef
|
||||||
|
const storeToRefs: typeof import('pinia').storeToRefs
|
||||||
|
const toRaw: typeof import('vue').toRaw
|
||||||
|
const toRef: typeof import('vue').toRef
|
||||||
|
const toRefs: typeof import('vue').toRefs
|
||||||
|
const toValue: typeof import('vue').toValue
|
||||||
|
const triggerRef: typeof import('vue').triggerRef
|
||||||
|
const unref: typeof import('vue').unref
|
||||||
|
const useAttrs: typeof import('vue').useAttrs
|
||||||
|
const useCssModule: typeof import('vue').useCssModule
|
||||||
|
const useCssVars: typeof import('vue').useCssVars
|
||||||
|
const useId: typeof import('vue').useId
|
||||||
|
const useModel: typeof import('vue').useModel
|
||||||
|
const useSlots: typeof import('vue').useSlots
|
||||||
|
const useTemplateRef: typeof import('vue').useTemplateRef
|
||||||
|
const watch: typeof import('vue').watch
|
||||||
|
const watchEffect: typeof import('vue').watchEffect
|
||||||
|
const watchPostEffect: typeof import('vue').watchPostEffect
|
||||||
|
const watchSyncEffect: typeof import('vue').watchSyncEffect
|
||||||
|
}
|
||||||
|
// for type re-export
|
||||||
|
declare global {
|
||||||
|
// @ts-ignore
|
||||||
|
export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
|
||||||
|
import('vue')
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// 应用全局配置
|
||||||
|
export default {
|
||||||
|
baseUrl: "http://localhost:9099",
|
||||||
|
// 应用信息
|
||||||
|
appInfo: {
|
||||||
|
// 应用名称
|
||||||
|
name: "RuoYi-FastAPI-APP",
|
||||||
|
// 应用版本
|
||||||
|
version: "1.9.0",
|
||||||
|
// 应用logo
|
||||||
|
logo: "/static/logo.png",
|
||||||
|
// 官方网站
|
||||||
|
site_url: "https://vfadmin.insistence.tech",
|
||||||
|
// 政策协议
|
||||||
|
agreements: [
|
||||||
|
{
|
||||||
|
title: "隐私政策",
|
||||||
|
url: "https://ruoyi.vip/protocol.html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "用户服务协议",
|
||||||
|
url: "https://ruoyi.vip/protocol.html",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module "*.vue" {
|
||||||
|
import type { DefineComponent } from "vue";
|
||||||
|
|
||||||
|
const component: DefineComponent<object, object, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import { createSSRApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import store from "./store"; // store
|
||||||
|
import { install } from "./plugins"; // plugins
|
||||||
|
import "./permission"; // permission
|
||||||
|
import { useDict } from "@/utils/dict";
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = createSSRApp(App);
|
||||||
|
app.use(store);
|
||||||
|
app.config.globalProperties.useDict = useDict;
|
||||||
|
install(app);
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"name": "RuoYi-FastAPI移动端",
|
||||||
|
"appid": "__UNI__25A9D80",
|
||||||
|
"description": "",
|
||||||
|
"versionName": "1.9.0",
|
||||||
|
"versionCode": "100",
|
||||||
|
"transformPx": false,
|
||||||
|
"app-plus": {
|
||||||
|
"usingComponents": true,
|
||||||
|
"nvueCompiler": "uni-app",
|
||||||
|
"splashscreen": {
|
||||||
|
"alwaysShowBeforeRender": true,
|
||||||
|
"waiting": true,
|
||||||
|
"autoclose": true,
|
||||||
|
"delay": 0
|
||||||
|
},
|
||||||
|
"modules": {},
|
||||||
|
"distribute": {
|
||||||
|
"android": {
|
||||||
|
"permissions": [
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||||
|
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||||
|
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ios": {
|
||||||
|
"dSYMs": false
|
||||||
|
},
|
||||||
|
"sdkConfigs": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"quickapp": {},
|
||||||
|
"mp-weixin": {
|
||||||
|
"appid": "wxccd7e2a0911b3397",
|
||||||
|
"setting": {
|
||||||
|
"urlCheck": false,
|
||||||
|
"es6": false,
|
||||||
|
"minified": true,
|
||||||
|
"postcss": true
|
||||||
|
},
|
||||||
|
"optimization": {
|
||||||
|
"subPackages": true
|
||||||
|
},
|
||||||
|
"usingComponents": true
|
||||||
|
},
|
||||||
|
"vueVersion": "3",
|
||||||
|
"h5": {
|
||||||
|
"devServer": {
|
||||||
|
"port": 9090,
|
||||||
|
"https": false
|
||||||
|
},
|
||||||
|
"title": "RuoYi-FastAPI-APP",
|
||||||
|
"router": {
|
||||||
|
"mode": "hash",
|
||||||
|
"base": "./"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"path": "pages/login",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "登录"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/register",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "注册"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "RuoYi-FastAPI移动端",
|
||||||
|
"navigationStyle": "custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/work/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "工作台"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "我的"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/avatar/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改头像"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/info/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "个人信息"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/info/edit",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "编辑资料"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/pwd/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "修改密码"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/setting/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "应用设置"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/help/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "常见问题"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/mine/about/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "关于我们"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/common/agreement/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "用户协议"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/common/privacy/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "隐私协议"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/common/webview/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "浏览网页"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "pages/common/textview/index",
|
||||||
|
"style": {
|
||||||
|
"navigationBarTitleText": "浏览文本"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tabBar": {
|
||||||
|
"color": "#000000",
|
||||||
|
"selectedColor": "#000000",
|
||||||
|
"borderStyle": "white",
|
||||||
|
"backgroundColor": "#ffffff",
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/index",
|
||||||
|
"iconPath": "static/images/tabbar/home.png",
|
||||||
|
"selectedIconPath": "static/images/tabbar/home_.png",
|
||||||
|
"text": "首页"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/work/index",
|
||||||
|
"iconPath": "static/images/tabbar/work.png",
|
||||||
|
"selectedIconPath": "static/images/tabbar/work_.png",
|
||||||
|
"text": "工作台"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/mine/index",
|
||||||
|
"iconPath": "static/images/tabbar/mine.png",
|
||||||
|
"selectedIconPath": "static/images/tabbar/mine_.png",
|
||||||
|
"text": "我的"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"globalStyle": {
|
||||||
|
"navigationBarTextStyle": "black",
|
||||||
|
"navigationBarTitleText": "RuoYi",
|
||||||
|
"navigationBarBackgroundColor": "#FFFFFF"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<view class="min-h-full bg-white px-5 py-8">
|
||||||
|
<view class="mb-6 text-center">
|
||||||
|
<text class="text-xl font-bold text-gray-900">用户协议</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="space-y-6 text-justify text-sm leading-relaxed text-gray-600">
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>1. 服务条款的确认和接纳</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
>欢迎使用RuoYi-FastAPI移动端框架(以下简称“本产品”)。本产品的服务所有权和运作权归RuoYi-FastAPI所有。用户在使用本产品前,请仔细阅读本协议。当您点击“同意”或实际使用本产品服务时,即视为您已阅读并完全同意本协议的所有条款。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>2. 用户账号与安全</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
>您在注册账号时,应提供真实、准确、最新和完整的个人资料。您有责任维护账号和密码的安全,并对以您账号进行的所有活动承担法律责任。如发现账号被非法使用,请立即通知我们。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800">3. 用户行为规范</text>
|
||||||
|
<text
|
||||||
|
>用户在使用本产品过程中,必须遵守国家法律法规,不得利用本产品从事违法违规活动,包括但不限于:发布虚假信息、侵犯他人知识产权、传播淫秽色情信息、进行网络攻击等。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>4. 服务内容的变更、中断或终止</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
>鉴于网络服务的特殊性,我们有权随时变更、中断或终止部分或全部网络服务。对于因服务器维护、网络故障等原因导致的服务中断,我们将尽量提前公告,但不对因此产生的损失承担责任。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800">5. 知识产权声明</text>
|
||||||
|
<text
|
||||||
|
>本产品包含的所有内容(包括但不限于文字、图片、音频、视频、软件代码等)的知识产权归RuoYi-FastAPI或相关权利人所有。未经授权,任何人不得擅自使用、复制、修改或传播。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800">6. 免责声明</text>
|
||||||
|
<text
|
||||||
|
>本产品按“现状”提供,不包含任何明示或暗示的保证。我们不对因不可抗力、黑客攻击、系统不稳定等原因导致的服务中断或数据丢失承担责任。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="mt-10 text-center text-xs text-gray-400">
|
||||||
|
<text>更新日期:2024年01月01日</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup></script>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<view class="min-h-full bg-white px-5 py-8">
|
||||||
|
<view class="mb-6 text-center">
|
||||||
|
<text class="text-xl font-bold text-gray-900">隐私协议</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="space-y-6 text-justify text-sm leading-relaxed text-gray-600">
|
||||||
|
<view>
|
||||||
|
<text
|
||||||
|
>RuoYi-FastAPI非常重视您的隐私保护。本隐私政策旨在向您说明我们如何收集、使用、存储和保护您的个人信息。请您在使用本产品前仔细阅读本政策。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>1. 我们如何收集和使用您的个人信息</text
|
||||||
|
>
|
||||||
|
<text>为了向您提供更好的服务,我们可能会收集您的以下信息:</text>
|
||||||
|
<text class="mt-1 block pl-2"
|
||||||
|
>•
|
||||||
|
账号信息:当您注册或登录时,我们需要收集您的用户名、密码、手机号码等信息。</text
|
||||||
|
>
|
||||||
|
<text class="mt-1 block pl-2"
|
||||||
|
>•
|
||||||
|
设备信息:为了保障账户安全和优化服务,我们会收集您的设备型号、操作系统版本、唯一设备标识符等信息。</text
|
||||||
|
>
|
||||||
|
<text class="mt-1 block pl-2"
|
||||||
|
>•
|
||||||
|
日志信息:当您使用本产品时,我们会自动收集您的操作日志、IP地址、访问时间等信息。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>2. 我们如何共享、转让、公开披露您的个人信息</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
>我们不会向任何第三方共享、转让或公开披露您的个人信息,但以下情况除外:</text
|
||||||
|
>
|
||||||
|
<text class="mt-1 block pl-2">• 获得您的明确授权或同意;</text>
|
||||||
|
<text class="mt-1 block pl-2">• 根据法律法规或司法机关的要求;</text>
|
||||||
|
<text class="mt-1 block pl-2"
|
||||||
|
>• 为了维护国家安全、公共安全或重大公共利益。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>3. 我们如何保护您的个人信息</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
>我们采取了多种安全措施来保护您的个人信息,包括数据加密、访问控制、安全审计等。我们会尽力防止您的信息被未经授权的访问、篡改或泄露。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800">4. 您的权利</text>
|
||||||
|
<text
|
||||||
|
>您有权访问、更正或删除您的个人信息。您也可以随时注销账号。如您需要行使这些权利,请通过“联系我们”章节中的方式与我们联系。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800"
|
||||||
|
>5. 本政策如何更新</text
|
||||||
|
>
|
||||||
|
<text
|
||||||
|
>随着法律法规的变化或业务的发展,我们可能会不时更新本隐私政策。更新后的政策将在本产品中发布,并在生效前通过适当方式通知您。</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view>
|
||||||
|
<text class="mb-2 block font-bold text-gray-800">6. 联系我们</text>
|
||||||
|
<text
|
||||||
|
>如果您对本隐私政策有任何疑问或建议,请通过以下方式联系我们:</text
|
||||||
|
>
|
||||||
|
<text class="mt-1 block">邮箱:xxx@xxx.com</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="mt-10 text-center text-xs text-gray-400">
|
||||||
|
<text>更新日期:2024年01月01日</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup></script>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<view class="h-full overflow-y-auto p-4">
|
||||||
|
<view class="bg-white rounded-xl shadow-sm p-4">
|
||||||
|
<view
|
||||||
|
class="text-lg font-bold text-gray-900 mb-3 border-b border-gray-100 pb-2"
|
||||||
|
>
|
||||||
|
{{ title }}
|
||||||
|
</view>
|
||||||
|
<text class="block text-sm text-gray-600 leading-relaxed">
|
||||||
|
{{ content }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
|
||||||
|
const title = ref("");
|
||||||
|
const content = ref("");
|
||||||
|
|
||||||
|
onLoad((options) => {
|
||||||
|
title.value = options.title;
|
||||||
|
content.value = options.content;
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: options.title,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="params.url">
|
||||||
|
<web-view :webview-styles="webviewStyles" :src="`${params.url}`"></web-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
|
||||||
|
const params = ref({});
|
||||||
|
const webviewStyles = ref({
|
||||||
|
progress: {
|
||||||
|
color: "#FF3333",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
src: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
onLoad((event) => {
|
||||||
|
params.value = event;
|
||||||
|
if (event.title) {
|
||||||
|
uni.setNavigationBarTitle({
|
||||||
|
title: event.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col bg-gray-50">
|
||||||
|
<!-- Header Area -->
|
||||||
|
<view class="bg-white pb-6 rounded-b-3xl shadow-sm z-50">
|
||||||
|
<!-- Title -->
|
||||||
|
<view class="pt-12 pb-4 px-4 flex justify-center items-center">
|
||||||
|
<text class="text-lg font-bold text-gray-800">RuoYi-FastAPI移动端</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Search & Notification -->
|
||||||
|
<view class="px-4 pb-2">
|
||||||
|
<view class="flex items-center space-x-3">
|
||||||
|
<!-- Search Input -->
|
||||||
|
<view class="flex-1 relative">
|
||||||
|
<view
|
||||||
|
class="absolute left-3 top-0 bottom-0 flex items-center text-gray-400"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-magnify text-lg"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索..."
|
||||||
|
placeholder-class="text-gray-400"
|
||||||
|
class="w-full h-10 rounded-full bg-gray-100 pl-10 pr-4 text-sm text-gray-800"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<!-- Notification -->
|
||||||
|
<view
|
||||||
|
class="relative flex h-10 w-10 items-center justify-center rounded-full bg-gray-50 text-gray-600 active:scale-95 transition-transform border border-gray-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-bell-outline text-xl"></view>
|
||||||
|
<view
|
||||||
|
class="absolute top-2.5 right-2.5 h-2 w-2 rounded-full bg-red-500 border border-white"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view
|
||||||
|
scroll-y
|
||||||
|
class="flex-1 overflow-hidden"
|
||||||
|
:show-scrollbar="false"
|
||||||
|
>
|
||||||
|
<view class="p-4 space-y-5 pb-24">
|
||||||
|
<!-- Hero Card -->
|
||||||
|
<view
|
||||||
|
class="relative overflow-hidden rounded-3xl bg-gradient-to-r from-indigo-500 to-purple-600 p-6 text-white shadow-xl shadow-indigo-200"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="absolute -right-10 -top-10 h-32 w-32 rounded-full bg-white/20 blur-2xl"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="absolute -bottom-10 -left-10 h-32 w-32 rounded-full bg-white/20 blur-2xl"
|
||||||
|
></view>
|
||||||
|
|
||||||
|
<view class="relative z-10">
|
||||||
|
<view class="mb-4 flex items-center justify-between">
|
||||||
|
<view
|
||||||
|
class="rounded-lg bg-white/20 px-3 py-1 text-xs font-medium backdrop-blur-sm"
|
||||||
|
>系统运行正常</view
|
||||||
|
>
|
||||||
|
<view class="text-xs opacity-80">刚刚更新</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex items-end justify-between">
|
||||||
|
<view>
|
||||||
|
<view class="text-3xl font-bold">85%</view>
|
||||||
|
<view class="text-sm opacity-90">工作效率</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="h-10 w-24 overflow-hidden rounded-lg bg-white/10 p-1"
|
||||||
|
>
|
||||||
|
<view class="h-full w-full flex items-end justify-between px-1">
|
||||||
|
<view class="w-1 bg-white/50 h-2 rounded-full"></view>
|
||||||
|
<view class="w-1 bg-white/70 h-4 rounded-full"></view>
|
||||||
|
<view class="w-1 bg-white/40 h-3 rounded-full"></view>
|
||||||
|
<view class="w-1 bg-white/90 h-6 rounded-full"></view>
|
||||||
|
<view class="w-1 bg-white/60 h-4 rounded-full"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Stats Grid -->
|
||||||
|
<view class="grid grid-cols-2 gap-4">
|
||||||
|
<view
|
||||||
|
class="rounded-2xl bg-white p-4 shadow-sm border border-gray-100 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view class="text-xs text-gray-500 mb-1">新通知</view>
|
||||||
|
<view class="text-2xl font-bold text-gray-800">12</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-full bg-blue-50 text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-bell text-xl"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="rounded-2xl bg-white p-4 shadow-sm border border-gray-100 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<view>
|
||||||
|
<view class="text-xs text-gray-500 mb-1">待办任务</view>
|
||||||
|
<view class="text-2xl font-bold text-gray-800">5</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-full bg-purple-50 text-purple-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-calendar text-xl"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Quick Functions -->
|
||||||
|
<view>
|
||||||
|
<view class="mb-4 flex items-center justify-between">
|
||||||
|
<view class="text-base font-bold text-gray-800">快捷入口</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="grid grid-cols-4 gap-2 rounded-2xl bg-white p-4 shadow-sm"
|
||||||
|
>
|
||||||
|
<view class="flex flex-col items-center space-y-2">
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-2xl bg-orange-50 text-orange-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-file-document-edit text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<view class="text-xs font-medium text-gray-600">申请</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex flex-col items-center space-y-2">
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-2xl bg-green-50 text-green-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-newspaper text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<view class="text-xs font-medium text-gray-600">新闻</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex flex-col items-center space-y-2">
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-2xl bg-indigo-50 text-indigo-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-chart-bar text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<view class="text-xs font-medium text-gray-600">统计</view>
|
||||||
|
</view>
|
||||||
|
<view class="flex flex-col items-center space-y-2">
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-2xl bg-gray-50 text-gray-400"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-calendar text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<view class="text-xs font-medium text-gray-600">计划</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Recent Apps -->
|
||||||
|
<view>
|
||||||
|
<view class="mb-4 flex items-center justify-between">
|
||||||
|
<view class="text-base font-bold text-gray-800">最近更新</view>
|
||||||
|
<view class="text-xs text-blue-500">查看全部</view>
|
||||||
|
</view>
|
||||||
|
<view class="space-y-3">
|
||||||
|
<view
|
||||||
|
class="flex items-center rounded-2xl bg-white p-4 shadow-sm border border-gray-50"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-xl bg-blue-50 text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-clipboard-text-outline text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<view class="ml-4 flex-1">
|
||||||
|
<view class="font-bold text-gray-800">请假申请</view>
|
||||||
|
<view class="text-xs text-gray-500">等待经理审批</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="rounded-full bg-yellow-50 px-3 py-1 text-xs font-medium text-yellow-600"
|
||||||
|
>待处理</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex items-center rounded-2xl bg-white p-4 shadow-sm border border-gray-50"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-12 items-center justify-center rounded-xl bg-green-50 text-green-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-newspaper text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<view class="ml-4 flex-1">
|
||||||
|
<view class="font-bold text-gray-800">周报</view>
|
||||||
|
<view class="text-xs text-gray-500">提交成功</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="rounded-full bg-green-50 px-3 py-1 text-xs font-medium text-green-600"
|
||||||
|
>已完成</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const globalConfig = getApp().globalData.config;
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="flex h-full flex-col items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 px-6 pb-20 overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- Logo Section -->
|
||||||
|
<view class="mb-6 flex flex-col items-center">
|
||||||
|
<view
|
||||||
|
class="mb-4 flex size-16 items-center justify-center rounded-2xl bg-white shadow-lg"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="size-10"
|
||||||
|
:src="globalConfig.appInfo.logo"
|
||||||
|
mode="widthFix"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<text class="text-xl font-bold tracking-wide text-gray-800"
|
||||||
|
>RuoYi-FastAPI移动端登录</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Form Section -->
|
||||||
|
<view class="w-full rounded-3xl bg-white/80 p-6 shadow-xl backdrop-blur-md">
|
||||||
|
<!-- Username -->
|
||||||
|
<view class="group relative mb-5">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="loginForm.username"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
maxlength="30"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<view class="group relative mb-5">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-lock text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="loginForm.password"
|
||||||
|
type="password"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
maxlength="20"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Captcha -->
|
||||||
|
<view
|
||||||
|
class="mb-8 flex items-center justify-between"
|
||||||
|
v-if="captchaEnabled"
|
||||||
|
>
|
||||||
|
<view class="group relative mr-3 flex-1">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-security text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="loginForm.code"
|
||||||
|
type="number"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
placeholder="验证码"
|
||||||
|
maxlength="4"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="h-12 w-28 overflow-hidden rounded-xl bg-gray-100 shadow-sm transition-opacity active:opacity-80"
|
||||||
|
@click="getCode"
|
||||||
|
>
|
||||||
|
<image :src="codeUrl" class="size-full object-cover"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Login Button -->
|
||||||
|
<button
|
||||||
|
@click="handleLogin"
|
||||||
|
class="flex h-12 w-full items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-600 text-base font-semibold text-white shadow-lg shadow-blue-500/30 transition-transform active:scale-95"
|
||||||
|
>
|
||||||
|
登 录
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Footer Links -->
|
||||||
|
<view class="mt-6 flex flex-col items-center space-y-3">
|
||||||
|
<view class="flex items-center text-sm text-gray-500" v-if="register">
|
||||||
|
<text>没有账号?</text>
|
||||||
|
<text
|
||||||
|
@click="handleUserRegister"
|
||||||
|
class="ml-1 font-medium text-blue-600 active:opacity-70"
|
||||||
|
>立即注册</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="flex flex-wrap items-center justify-center text-xs text-gray-400"
|
||||||
|
>
|
||||||
|
<text>登录即代表同意</text>
|
||||||
|
<text
|
||||||
|
@click="handleUserAgrement"
|
||||||
|
class="mx-1 text-blue-500 active:opacity-70"
|
||||||
|
>《用户协议》</text
|
||||||
|
>
|
||||||
|
<text>和</text>
|
||||||
|
<text
|
||||||
|
@click="handlePrivacy"
|
||||||
|
class="mx-1 text-blue-500 active:opacity-70"
|
||||||
|
>《隐私协议》</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, getCurrentInstance } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
import { getCodeImg } from "@/api/login";
|
||||||
|
import { useConfigStore, useUserStore } from "@/store";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const globalConfig = useConfigStore().config;
|
||||||
|
const codeUrl = ref("");
|
||||||
|
// 验证码开关
|
||||||
|
const captchaEnabled = ref(true);
|
||||||
|
// 用户注册开关
|
||||||
|
const register = ref(false);
|
||||||
|
const loginForm = ref({
|
||||||
|
username: "admin",
|
||||||
|
password: "admin123",
|
||||||
|
code: "",
|
||||||
|
uuid: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用户注册
|
||||||
|
function handleUserRegister() {
|
||||||
|
proxy.$tab.redirectTo(`/pages/register`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 隐私协议
|
||||||
|
function handlePrivacy() {
|
||||||
|
proxy.$tab.navigateTo(`/pages/common/privacy/index`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户协议
|
||||||
|
function handleUserAgrement() {
|
||||||
|
proxy.$tab.navigateTo(`/pages/common/agreement/index`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取图形验证码
|
||||||
|
function getCode() {
|
||||||
|
getCodeImg().then((res) => {
|
||||||
|
captchaEnabled.value =
|
||||||
|
res.captchaEnabled === undefined ? true : res.captchaEnabled;
|
||||||
|
if (captchaEnabled.value) {
|
||||||
|
codeUrl.value = "data:image/gif;base64," + res.img;
|
||||||
|
loginForm.value.uuid = res.uuid;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录方法
|
||||||
|
async function handleLogin() {
|
||||||
|
if (loginForm.value.username === "") {
|
||||||
|
proxy.$modal.msgError("请输入账号");
|
||||||
|
} else if (loginForm.value.password === "") {
|
||||||
|
proxy.$modal.msgError("请输入密码");
|
||||||
|
} else if (loginForm.value.code === "" && captchaEnabled.value) {
|
||||||
|
proxy.$modal.msgError("请输入验证码");
|
||||||
|
} else {
|
||||||
|
proxy.$modal.loading("登录中,请耐心等待...");
|
||||||
|
pwdLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 密码登录
|
||||||
|
async function pwdLogin() {
|
||||||
|
useUserStore()
|
||||||
|
.login(loginForm.value)
|
||||||
|
.then(() => {
|
||||||
|
proxy.$modal.closeLoading();
|
||||||
|
loginSuccess();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (captchaEnabled.value) {
|
||||||
|
getCode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录成功后,处理函数
|
||||||
|
function loginSuccess(result) {
|
||||||
|
// 设置用户信息
|
||||||
|
useUserStore()
|
||||||
|
.getInfo()
|
||||||
|
.then((res) => {
|
||||||
|
proxy.$tab.reLaunch("/pages/index");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
//#ifdef H5
|
||||||
|
if (getToken()) {
|
||||||
|
proxy.$tab.reLaunch("/pages/index");
|
||||||
|
}
|
||||||
|
//#endif
|
||||||
|
});
|
||||||
|
|
||||||
|
getCode();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
page {
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col bg-gray-50 pb-10">
|
||||||
|
<!-- Header -->
|
||||||
|
<view class="flex flex-col items-center py-10">
|
||||||
|
<image
|
||||||
|
class="size-20 rounded-xl shadow-md"
|
||||||
|
src="/static/logo200.png"
|
||||||
|
mode="widthFix"
|
||||||
|
/>
|
||||||
|
<text class="mt-4 text-xl font-bold text-gray-800"
|
||||||
|
>RuoYi-FastAPI移动端</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Info List -->
|
||||||
|
<view class="bg-white">
|
||||||
|
<!-- Version -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4"
|
||||||
|
>
|
||||||
|
<text class="text-base text-gray-800">版本信息</text>
|
||||||
|
<text class="text-sm text-gray-500">v{{ version }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4"
|
||||||
|
>
|
||||||
|
<text class="text-base text-gray-800">官方邮箱</text>
|
||||||
|
<text class="text-sm text-gray-500">xxx@xxx.com</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Hotline -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4"
|
||||||
|
>
|
||||||
|
<text class="text-base text-gray-800">服务热线</text>
|
||||||
|
<text class="text-sm text-gray-500">400-999-9999</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Website -->
|
||||||
|
<view class="flex items-center justify-between px-5 py-4">
|
||||||
|
<text class="text-base text-gray-800">公司网站</text>
|
||||||
|
<text class="text-sm text-blue-500 underline active:opacity-70">{{
|
||||||
|
url
|
||||||
|
}}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Copyright -->
|
||||||
|
<view class="mt-10 text-center text-xs text-gray-400">
|
||||||
|
Copyright © 2026 insistence.tech All Rights Reserved.
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useConfigStore } from "@/store";
|
||||||
|
|
||||||
|
const url = useConfigStore().config.appInfo.site_url;
|
||||||
|
const version = useConfigStore().config.appInfo.version;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,678 @@
|
|||||||
|
<template>
|
||||||
|
<view class="h-full bg-black">
|
||||||
|
<view class="relative h-full w-full">
|
||||||
|
<view class="min-h-[750rpx] w-full">
|
||||||
|
<view
|
||||||
|
v-if="isShowImg"
|
||||||
|
class="relative box-border overflow-hidden"
|
||||||
|
:style="
|
||||||
|
'width:' +
|
||||||
|
cropperInitW +
|
||||||
|
'px;height:' +
|
||||||
|
cropperInitH +
|
||||||
|
'px;background:#000'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="relative"
|
||||||
|
:style="
|
||||||
|
'width:' +
|
||||||
|
cropperW +
|
||||||
|
'px;height:' +
|
||||||
|
cropperH +
|
||||||
|
'px;left:' +
|
||||||
|
cropperL +
|
||||||
|
'px;top:' +
|
||||||
|
cropperT +
|
||||||
|
'px'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
:src="imageSrc"
|
||||||
|
:style="
|
||||||
|
'width:' +
|
||||||
|
cropperW +
|
||||||
|
'px;height:' +
|
||||||
|
cropperH +
|
||||||
|
'px;display:block;margin:0 auto;'
|
||||||
|
"
|
||||||
|
></image>
|
||||||
|
<view
|
||||||
|
class="absolute z-10 bg-white/30"
|
||||||
|
@touchstart.stop="contentStartMove"
|
||||||
|
@touchmove.stop="contentMoveing"
|
||||||
|
@touchend.stop="contentTouchEnd"
|
||||||
|
:style="
|
||||||
|
'left:' +
|
||||||
|
cutL +
|
||||||
|
'px;top:' +
|
||||||
|
cutT +
|
||||||
|
'px;right:' +
|
||||||
|
cutR +
|
||||||
|
'px;bottom:' +
|
||||||
|
cutB +
|
||||||
|
'px'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="relative block h-full w-full overflow-visible outline outline-1 outline-[rgba(102,153,255,0.75)]"
|
||||||
|
>
|
||||||
|
<view class="uni-cropper-dashed-h"></view>
|
||||||
|
<view class="uni-cropper-dashed-v"></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-line-t"
|
||||||
|
data-drag="top"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-line-r"
|
||||||
|
data-drag="right"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-line-b"
|
||||||
|
data-drag="bottom"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-line-l"
|
||||||
|
data-drag="left"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-t"
|
||||||
|
data-drag="top"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-tr"
|
||||||
|
data-drag="topTight"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-r"
|
||||||
|
data-drag="right"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-rb"
|
||||||
|
data-drag="rightBottom"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-b"
|
||||||
|
data-drag="bottom"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-bl"
|
||||||
|
data-drag="bottomLeft"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-l"
|
||||||
|
data-drag="left"
|
||||||
|
@touchstart.stop="dragStart"
|
||||||
|
@touchmove.stop="dragMove"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="uni-cropper-point point-lt"
|
||||||
|
data-drag="leftTop"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="fixed bottom-8 left-0 w-full px-10">
|
||||||
|
<button
|
||||||
|
class="mt-4 w-full rounded-full bg-gradient-to-r from-blue-500 to-blue-600 text-white shadow-lg transition-all active:scale-95"
|
||||||
|
@click="getImage"
|
||||||
|
>
|
||||||
|
选择头像
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="mt-4 w-full rounded-full bg-gradient-to-r from-red-500 to-red-600 text-white shadow-lg transition-all active:scale-95"
|
||||||
|
@click="getImageInfo"
|
||||||
|
>
|
||||||
|
提交
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
<canvas
|
||||||
|
canvas-id="myCanvas"
|
||||||
|
:style="
|
||||||
|
'position:absolute;border: 1px solid red; width:' +
|
||||||
|
imageW +
|
||||||
|
'px;height:' +
|
||||||
|
imageH +
|
||||||
|
'px;top:-9999px;left:-9999px;'
|
||||||
|
"
|
||||||
|
></canvas>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import config from "@/config";
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import { uploadAvatar } from "@/api/system/user";
|
||||||
|
|
||||||
|
const baseUrl = config.baseUrl;
|
||||||
|
let sysInfo = uni.getSystemInfoSync();
|
||||||
|
let SCREEN_WIDTH = sysInfo.screenWidth;
|
||||||
|
let PAGE_X, // 手按下的x位置
|
||||||
|
PAGE_Y, // 手按下y的位置
|
||||||
|
PR = sysInfo.pixelRatio, // dpi
|
||||||
|
T_PAGE_X, // 手移动的时候x的位置
|
||||||
|
T_PAGE_Y, // 手移动的时候Y的位置
|
||||||
|
CUT_L, // 初始化拖拽元素的left值
|
||||||
|
CUT_T, // 初始化拖拽元素的top值
|
||||||
|
CUT_R, // 初始化拖拽元素的
|
||||||
|
CUT_B, // 初始化拖拽元素的
|
||||||
|
CUT_W, // 初始化拖拽元素的宽度
|
||||||
|
CUT_H, // 初始化拖拽元素的高度
|
||||||
|
IMG_RATIO, // 图片比例
|
||||||
|
IMG_REAL_W, // 图片实际的宽度
|
||||||
|
IMG_REAL_H, // 图片实际的高度
|
||||||
|
DRAFG_MOVE_RATIO = 1, //移动时候的比例,
|
||||||
|
INIT_DRAG_POSITION = 100, // 初始化屏幕宽度和裁剪区域的宽度之差,用于设置初始化裁剪的宽度
|
||||||
|
DRAW_IMAGE_W = sysInfo.screenWidth; // 设置生成的图片宽度
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 页面的初始数据
|
||||||
|
*/
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
imageSrc: useUserStore().avatar,
|
||||||
|
isShowImg: false,
|
||||||
|
// 初始化的宽高
|
||||||
|
cropperInitW: SCREEN_WIDTH,
|
||||||
|
cropperInitH: SCREEN_WIDTH,
|
||||||
|
// 动态的宽高
|
||||||
|
cropperW: SCREEN_WIDTH,
|
||||||
|
cropperH: SCREEN_WIDTH,
|
||||||
|
// 动态的left top值
|
||||||
|
cropperL: 0,
|
||||||
|
cropperT: 0,
|
||||||
|
|
||||||
|
transL: 0,
|
||||||
|
transT: 0,
|
||||||
|
|
||||||
|
// 图片缩放值
|
||||||
|
scaleP: 0,
|
||||||
|
imageW: 0,
|
||||||
|
imageH: 0,
|
||||||
|
|
||||||
|
// 裁剪框 宽高
|
||||||
|
cutL: 0,
|
||||||
|
cutT: 0,
|
||||||
|
cutB: SCREEN_WIDTH,
|
||||||
|
cutR: "100%",
|
||||||
|
qualityWidth: DRAW_IMAGE_W,
|
||||||
|
innerAspectRadio: DRAFG_MOVE_RATIO,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 生命周期函数--监听页面初次渲染完成
|
||||||
|
*/
|
||||||
|
onReady: function () {
|
||||||
|
this.loadImage();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setData: function (obj) {
|
||||||
|
let that = this;
|
||||||
|
Object.keys(obj).forEach(function (key) {
|
||||||
|
that.$set(that.$data, key, obj[key]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getImage: function () {
|
||||||
|
var _this = this;
|
||||||
|
uni.chooseImage({
|
||||||
|
success: function (res) {
|
||||||
|
_this.setData({
|
||||||
|
imageSrc: res.tempFilePaths[0],
|
||||||
|
});
|
||||||
|
_this.loadImage();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
loadImage: function () {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
uni.getImageInfo({
|
||||||
|
src: _this.imageSrc,
|
||||||
|
success: function success(res) {
|
||||||
|
IMG_RATIO = 1 / 1;
|
||||||
|
if (IMG_RATIO >= 1) {
|
||||||
|
IMG_REAL_W = SCREEN_WIDTH;
|
||||||
|
IMG_REAL_H = SCREEN_WIDTH / IMG_RATIO;
|
||||||
|
} else {
|
||||||
|
IMG_REAL_W = SCREEN_WIDTH * IMG_RATIO;
|
||||||
|
IMG_REAL_H = SCREEN_WIDTH;
|
||||||
|
}
|
||||||
|
let minRange = IMG_REAL_W > IMG_REAL_H ? IMG_REAL_W : IMG_REAL_H;
|
||||||
|
INIT_DRAG_POSITION =
|
||||||
|
minRange > INIT_DRAG_POSITION ? INIT_DRAG_POSITION : minRange;
|
||||||
|
// 根据图片的宽高显示不同的效果 保证图片可以正常显示
|
||||||
|
if (IMG_RATIO >= 1) {
|
||||||
|
let cutT = Math.ceil(
|
||||||
|
(SCREEN_WIDTH / IMG_RATIO -
|
||||||
|
(SCREEN_WIDTH / IMG_RATIO - INIT_DRAG_POSITION)) /
|
||||||
|
2,
|
||||||
|
);
|
||||||
|
let cutB = cutT;
|
||||||
|
let cutL = Math.ceil(
|
||||||
|
(SCREEN_WIDTH - SCREEN_WIDTH + INIT_DRAG_POSITION) / 2,
|
||||||
|
);
|
||||||
|
let cutR = cutL;
|
||||||
|
_this.setData({
|
||||||
|
cropperW: SCREEN_WIDTH,
|
||||||
|
cropperH: SCREEN_WIDTH / IMG_RATIO,
|
||||||
|
// 初始化left right
|
||||||
|
cropperL: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
|
||||||
|
cropperT: Math.ceil(
|
||||||
|
(SCREEN_WIDTH - SCREEN_WIDTH / IMG_RATIO) / 2,
|
||||||
|
),
|
||||||
|
cutL: cutL,
|
||||||
|
cutT: cutT,
|
||||||
|
cutR: cutR,
|
||||||
|
cutB: cutB,
|
||||||
|
// 图片缩放值
|
||||||
|
imageW: IMG_REAL_W,
|
||||||
|
imageH: IMG_REAL_H,
|
||||||
|
scaleP: IMG_REAL_W / SCREEN_WIDTH,
|
||||||
|
qualityWidth: DRAW_IMAGE_W,
|
||||||
|
innerAspectRadio: IMG_RATIO,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let cutL = Math.ceil(
|
||||||
|
(SCREEN_WIDTH * IMG_RATIO - SCREEN_WIDTH * IMG_RATIO) / 2,
|
||||||
|
);
|
||||||
|
let cutR = cutL;
|
||||||
|
let cutT = Math.ceil((SCREEN_WIDTH - INIT_DRAG_POSITION) / 2);
|
||||||
|
let cutB = cutT;
|
||||||
|
_this.setData({
|
||||||
|
cropperW: SCREEN_WIDTH * IMG_RATIO,
|
||||||
|
cropperH: SCREEN_WIDTH,
|
||||||
|
// 初始化left right
|
||||||
|
cropperL: Math.ceil(
|
||||||
|
(SCREEN_WIDTH - SCREEN_WIDTH * IMG_RATIO) / 2,
|
||||||
|
),
|
||||||
|
cropperT: Math.ceil((SCREEN_WIDTH - SCREEN_WIDTH) / 2),
|
||||||
|
|
||||||
|
cutL: cutL,
|
||||||
|
cutT: cutT,
|
||||||
|
cutR: cutR,
|
||||||
|
cutB: cutB,
|
||||||
|
// 图片缩放值
|
||||||
|
imageW: IMG_REAL_W,
|
||||||
|
imageH: IMG_REAL_H,
|
||||||
|
scaleP: IMG_REAL_W / SCREEN_WIDTH,
|
||||||
|
qualityWidth: DRAW_IMAGE_W,
|
||||||
|
innerAspectRadio: IMG_RATIO,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_this.setData({
|
||||||
|
isShowImg: true,
|
||||||
|
});
|
||||||
|
uni.hideLoading();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 拖动时候触发的touchStart事件
|
||||||
|
contentStartMove(e) {
|
||||||
|
PAGE_X = e.touches[0].pageX;
|
||||||
|
PAGE_Y = e.touches[0].pageY;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 拖动时候触发的touchMove事件
|
||||||
|
contentMoveing(e) {
|
||||||
|
var _this = this;
|
||||||
|
var dragLengthX = (PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||||
|
var dragLengthY = (PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||||
|
// 左移
|
||||||
|
if (dragLengthX > 0) {
|
||||||
|
if (this.cutL - dragLengthX < 0) dragLengthX = this.cutL;
|
||||||
|
} else {
|
||||||
|
if (this.cutR + dragLengthX < 0) dragLengthX = -this.cutR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dragLengthY > 0) {
|
||||||
|
if (this.cutT - dragLengthY < 0) dragLengthY = this.cutT;
|
||||||
|
} else {
|
||||||
|
if (this.cutB + dragLengthY < 0) dragLengthY = -this.cutB;
|
||||||
|
}
|
||||||
|
this.setData({
|
||||||
|
cutL: this.cutL - dragLengthX,
|
||||||
|
cutT: this.cutT - dragLengthY,
|
||||||
|
cutR: this.cutR + dragLengthX,
|
||||||
|
cutB: this.cutB + dragLengthY,
|
||||||
|
});
|
||||||
|
|
||||||
|
PAGE_X = e.touches[0].pageX;
|
||||||
|
PAGE_Y = e.touches[0].pageY;
|
||||||
|
},
|
||||||
|
|
||||||
|
contentTouchEnd() {},
|
||||||
|
|
||||||
|
// 获取图片
|
||||||
|
getImageInfo() {
|
||||||
|
var _this = this;
|
||||||
|
uni.showLoading({
|
||||||
|
title: "图片生成中...",
|
||||||
|
});
|
||||||
|
// 将图片写入画布
|
||||||
|
const ctx = uni.createCanvasContext("myCanvas");
|
||||||
|
ctx.drawImage(_this.imageSrc, 0, 0, IMG_REAL_W, IMG_REAL_H);
|
||||||
|
ctx.draw(true, () => {
|
||||||
|
// 获取画布要裁剪的位置和宽度 均为百分比 * 画布中图片的宽度 保证了在微信小程序中裁剪的图片模糊 位置不对的问题 canvasT = (_this.cutT / _this.cropperH) * (_this.imageH / pixelRatio)
|
||||||
|
var canvasW =
|
||||||
|
((_this.cropperW - _this.cutL - _this.cutR) / _this.cropperW) *
|
||||||
|
IMG_REAL_W;
|
||||||
|
var canvasH =
|
||||||
|
((_this.cropperH - _this.cutT - _this.cutB) / _this.cropperH) *
|
||||||
|
IMG_REAL_H;
|
||||||
|
var canvasL = (_this.cutL / _this.cropperW) * IMG_REAL_W;
|
||||||
|
var canvasT = (_this.cutT / _this.cropperH) * IMG_REAL_H;
|
||||||
|
uni.canvasToTempFilePath({
|
||||||
|
x: canvasL,
|
||||||
|
y: canvasT,
|
||||||
|
width: canvasW,
|
||||||
|
height: canvasH,
|
||||||
|
destWidth: canvasW,
|
||||||
|
destHeight: canvasH,
|
||||||
|
quality: 0.5,
|
||||||
|
canvasId: "myCanvas",
|
||||||
|
success: function (res) {
|
||||||
|
uni.hideLoading();
|
||||||
|
let data = { name: "avatarfile", filePath: res.tempFilePath };
|
||||||
|
uploadAvatar(data).then((response) => {
|
||||||
|
useUserStore().SET_AVATAR(baseUrl + response.imgUrl);
|
||||||
|
uni.showToast({ title: "修改成功", icon: "success" });
|
||||||
|
uni.navigateBack();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 设置大小的时候触发的touchStart事件
|
||||||
|
dragStart(e) {
|
||||||
|
T_PAGE_X = e.touches[0].pageX;
|
||||||
|
T_PAGE_Y = e.touches[0].pageY;
|
||||||
|
CUT_L = this.cutL;
|
||||||
|
CUT_R = this.cutR;
|
||||||
|
CUT_B = this.cutB;
|
||||||
|
CUT_T = this.cutT;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 设置大小的时候触发的touchMove事件
|
||||||
|
dragMove(e) {
|
||||||
|
var _this = this;
|
||||||
|
var dragType = e.target.dataset.drag;
|
||||||
|
switch (dragType) {
|
||||||
|
case "right":
|
||||||
|
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||||
|
if (CUT_R + dragLength < 0) dragLength = -CUT_R;
|
||||||
|
this.setData({
|
||||||
|
cutR: CUT_R + dragLength,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "left":
|
||||||
|
var dragLength = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||||
|
if (CUT_L - dragLength < 0) dragLength = CUT_L;
|
||||||
|
if (CUT_L - dragLength > this.cropperW - this.cutR)
|
||||||
|
dragLength = CUT_L - (this.cropperW - this.cutR);
|
||||||
|
this.setData({
|
||||||
|
cutL: CUT_L - dragLength,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "top":
|
||||||
|
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||||
|
if (CUT_T - dragLength < 0) dragLength = CUT_T;
|
||||||
|
if (CUT_T - dragLength > this.cropperH - this.cutB)
|
||||||
|
dragLength = CUT_T - (this.cropperH - this.cutB);
|
||||||
|
this.setData({
|
||||||
|
cutT: CUT_T - dragLength,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "bottom":
|
||||||
|
var dragLength = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||||
|
if (CUT_B + dragLength < 0) dragLength = -CUT_B;
|
||||||
|
this.setData({
|
||||||
|
cutB: CUT_B + dragLength,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "rightBottom":
|
||||||
|
var dragLengthX = (T_PAGE_X - e.touches[0].pageX) * DRAFG_MOVE_RATIO;
|
||||||
|
var dragLengthY = (T_PAGE_Y - e.touches[0].pageY) * DRAFG_MOVE_RATIO;
|
||||||
|
|
||||||
|
if (CUT_B + dragLengthY < 0) dragLengthY = -CUT_B;
|
||||||
|
if (CUT_R + dragLengthX < 0) dragLengthX = -CUT_R;
|
||||||
|
let cutB = CUT_B + dragLengthY;
|
||||||
|
let cutR = CUT_R + dragLengthX;
|
||||||
|
|
||||||
|
this.setData({
|
||||||
|
cutB: cutB,
|
||||||
|
cutR: cutR,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 横向虚线 */
|
||||||
|
.uni-cropper-dashed-h {
|
||||||
|
position: absolute;
|
||||||
|
top: 33.33333333%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 33.33333333%;
|
||||||
|
border-top: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||||
|
border-bottom: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 纵向虚线 */
|
||||||
|
.uni-cropper-dashed-v {
|
||||||
|
position: absolute;
|
||||||
|
left: 33.33333333%;
|
||||||
|
top: 0;
|
||||||
|
width: 33.33333333%;
|
||||||
|
height: 100%;
|
||||||
|
border-left: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||||
|
border-right: 1rpx dashed rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 四个方向的线 为了之后的拖动事件*/
|
||||||
|
.uni-cropper-line-t {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #69f;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 1rpx;
|
||||||
|
opacity: 0.1;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-t::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0rpx;
|
||||||
|
width: 100%;
|
||||||
|
-webkit-transform: translate3d(0, -50%, 0);
|
||||||
|
transform: translate3d(0, -50%, 0);
|
||||||
|
bottom: 0;
|
||||||
|
height: 41rpx;
|
||||||
|
background: transparent;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-r {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
background-color: #69f;
|
||||||
|
top: 0;
|
||||||
|
right: 0rpx;
|
||||||
|
width: 1rpx;
|
||||||
|
opacity: 0.1;
|
||||||
|
height: 100%;
|
||||||
|
cursor: e-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-r::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: 41rpx;
|
||||||
|
-webkit-transform: translate3d(-50%, 0, 0);
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: transparent;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-b {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #69f;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 1rpx;
|
||||||
|
opacity: 0.1;
|
||||||
|
cursor: s-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-b::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0rpx;
|
||||||
|
width: 100%;
|
||||||
|
-webkit-transform: translate3d(0, -50%, 0);
|
||||||
|
transform: translate3d(0, -50%, 0);
|
||||||
|
bottom: 0;
|
||||||
|
height: 41rpx;
|
||||||
|
background: transparent;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-l {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
background-color: #69f;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 1rpx;
|
||||||
|
opacity: 0.1;
|
||||||
|
height: 100%;
|
||||||
|
cursor: w-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-line-l::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: 41rpx;
|
||||||
|
-webkit-transform: translate3d(-50%, 0, 0);
|
||||||
|
transform: translate3d(-50%, 0, 0);
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: transparent;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uni-cropper-point {
|
||||||
|
width: 5rpx;
|
||||||
|
height: 5rpx;
|
||||||
|
background-color: #69f;
|
||||||
|
opacity: 0.75;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-t {
|
||||||
|
top: -3rpx;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-tr {
|
||||||
|
top: -3rpx;
|
||||||
|
left: 100%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-r {
|
||||||
|
top: 50%;
|
||||||
|
left: 100%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
margin-top: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-rb {
|
||||||
|
left: 100%;
|
||||||
|
top: 100%;
|
||||||
|
-webkit-transform: translate3d(-50%, -50%, 0);
|
||||||
|
transform: translate3d(-50%, -50%, 0);
|
||||||
|
cursor: n-resize;
|
||||||
|
width: 36rpx;
|
||||||
|
height: 36rpx;
|
||||||
|
background-color: #69f;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1112;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-b {
|
||||||
|
left: 50%;
|
||||||
|
top: 100%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
margin-top: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-bl {
|
||||||
|
left: 0%;
|
||||||
|
top: 100%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
margin-top: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-l {
|
||||||
|
left: 0%;
|
||||||
|
top: 50%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
margin-top: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.point-lt {
|
||||||
|
left: 0%;
|
||||||
|
top: 0%;
|
||||||
|
margin-left: -3rpx;
|
||||||
|
margin-top: -3rpx;
|
||||||
|
cursor: n-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col overflow-y-auto bg-gray-50 p-4 pb-24">
|
||||||
|
<view v-for="(item, findex) in list" :key="findex" class="mb-6">
|
||||||
|
<view
|
||||||
|
class="mb-3 ml-2 flex items-center text-base font-bold text-gray-800"
|
||||||
|
>
|
||||||
|
<view :class="['mr-2 text-lg text-blue-500', item.icon]"></view
|
||||||
|
>{{ item.title }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="overflow-hidden rounded-xl bg-white shadow-sm ring-1 ring-gray-100"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(child, zindex) in item.childList"
|
||||||
|
:key="zindex"
|
||||||
|
class="relative"
|
||||||
|
hover-class="bg-gray-50"
|
||||||
|
@click="handleText(child)"
|
||||||
|
>
|
||||||
|
<view class="p-4 text-sm text-gray-600">{{ child.title }}</view>
|
||||||
|
<view
|
||||||
|
class="h-px w-full bg-gray-100"
|
||||||
|
v-if="zindex !== item.childList.length - 1"
|
||||||
|
></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
const list = ref([
|
||||||
|
{
|
||||||
|
icon: "i-mdi-github",
|
||||||
|
title: "RuoYi-FastAPI问题",
|
||||||
|
childList: [
|
||||||
|
{
|
||||||
|
title: "RuoYi-FastAPI开源吗?",
|
||||||
|
content: "开源",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RuoYi-FastAPI可以商用吗?",
|
||||||
|
content: "可以",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RuoYi-FastAPI官网地址多少?",
|
||||||
|
content: "https://vfadmin.insistence.tech",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RuoYi-FastAPI文档地址多少?",
|
||||||
|
content: "https://vfadmin.insistence.tech",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "i-mdi-help-circle-outline",
|
||||||
|
title: "其他问题",
|
||||||
|
childList: [
|
||||||
|
{
|
||||||
|
title: "如何退出登录?",
|
||||||
|
content: "请点击[我的] - [应用设置] - [退出登录]即可退出登录",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "如何修改用户头像?",
|
||||||
|
content: "请点击[我的] - [选择头像] - [点击提交]即可更换用户头像",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "如何修改登录密码?",
|
||||||
|
content: "请点击[我的] - [应用设置] - [修改密码]即可修改登录密码",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
function handleText(item) {
|
||||||
|
proxy.$tab.navigateTo(
|
||||||
|
`/pages/common/textview/index?title=${item.title}&content=${item.content}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col bg-gray-50 overflow-hidden">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<view
|
||||||
|
class="relative overflow-hidden bg-gradient-to-r from-blue-500 to-indigo-600 pb-20 pt-16 text-white shadow-lg"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="absolute -right-10 -top-10 size-64 rounded-full bg-white/10 blur-3xl"
|
||||||
|
></view>
|
||||||
|
<view
|
||||||
|
class="absolute -bottom-10 -left-10 size-40 rounded-full bg-white/10 blur-2xl"
|
||||||
|
></view>
|
||||||
|
|
||||||
|
<view class="relative z-10 flex items-center justify-between px-6">
|
||||||
|
<view class="flex items-center space-x-4">
|
||||||
|
<!-- Avatar -->
|
||||||
|
<view
|
||||||
|
class="relative overflow-hidden rounded-full border-4 border-white/30 bg-white/20 shadow-xl transition-transform active:scale-95"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
v-if="avatar"
|
||||||
|
@click="handleToAvatar"
|
||||||
|
:src="avatar"
|
||||||
|
class="size-20 object-cover"
|
||||||
|
/>
|
||||||
|
<view
|
||||||
|
v-else
|
||||||
|
class="flex size-20 items-center justify-center bg-white text-gray-400"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-account text-5xl"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- User Info -->
|
||||||
|
<view class="flex flex-col">
|
||||||
|
<template v-if="name">
|
||||||
|
<view
|
||||||
|
class="text-xl font-bold tracking-wide"
|
||||||
|
@click="handleToInfo"
|
||||||
|
>
|
||||||
|
{{ name }}
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="mt-1 flex items-center text-sm text-blue-100"
|
||||||
|
@click="handleToInfo"
|
||||||
|
>
|
||||||
|
<text>查看个人信息</text>
|
||||||
|
<view class="i-mdi-chevron-right ml-1 text-xs"></view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<view v-else class="text-xl font-bold" @click="handleToLogin">
|
||||||
|
点击登录
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Content Section -->
|
||||||
|
<view class="relative z-20 -mt-12 flex-1 px-4 overflow-y-auto">
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
<view
|
||||||
|
class="mb-4 flex items-center justify-between rounded-2xl bg-white p-4 shadow-lg shadow-gray-200/50"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex flex-1 flex-col items-center justify-center space-y-2 active:opacity-70"
|
||||||
|
@click="handleJiaoLiuQun"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-12 items-center justify-center rounded-full bg-pink-50 text-pink-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-account-group text-2xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-xs font-medium text-gray-600">交流群</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex flex-1 flex-col items-center justify-center space-y-2 active:opacity-70"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-12 items-center justify-center rounded-full bg-blue-50 text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-face-agent text-2xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-xs font-medium text-gray-600">在线客服</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex flex-1 flex-col items-center justify-center space-y-2 active:opacity-70"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-12 items-center justify-center rounded-full bg-purple-50 text-purple-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-forum text-2xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-xs font-medium text-gray-600">反馈社区</text>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="flex flex-1 flex-col items-center justify-center space-y-2 active:opacity-70"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-12 items-center justify-center rounded-full bg-green-50 text-green-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-thumb-up text-2xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-xs font-medium text-gray-600">点赞我们</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Menu List -->
|
||||||
|
<view
|
||||||
|
class="overflow-hidden rounded-2xl bg-white shadow-lg shadow-gray-200/50"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="group flex items-center justify-between border-b border-gray-100 p-4 transition-colors active:bg-gray-50"
|
||||||
|
@click="handleToEditInfo"
|
||||||
|
>
|
||||||
|
<view class="flex items-center space-x-3">
|
||||||
|
<view class="i-mdi-account-edit text-xl text-blue-500"></view>
|
||||||
|
<text class="text-base text-gray-700">编辑资料</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="group flex items-center justify-between border-b border-gray-100 p-4 transition-colors active:bg-gray-50"
|
||||||
|
@click="handleHelp"
|
||||||
|
>
|
||||||
|
<view class="flex items-center space-x-3">
|
||||||
|
<view class="i-mdi-help-circle text-xl text-orange-500"></view>
|
||||||
|
<text class="text-base text-gray-700">常见问题</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="group flex items-center justify-between border-b border-gray-100 p-4 transition-colors active:bg-gray-50"
|
||||||
|
@click="handleAbout"
|
||||||
|
>
|
||||||
|
<view class="flex items-center space-x-3">
|
||||||
|
<view class="i-mdi-heart-outline text-xl text-red-500"></view>
|
||||||
|
<text class="text-base text-gray-700">关于我们</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="group flex items-center justify-between p-4 transition-colors active:bg-gray-50"
|
||||||
|
@click="handleToSetting"
|
||||||
|
>
|
||||||
|
<view class="flex items-center space-x-3">
|
||||||
|
<view class="i-mdi-cog-outline text-xl text-gray-500"></view>
|
||||||
|
<text class="text-base text-gray-700">应用设置</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import { computed, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
const name = computed(() => userStore.name);
|
||||||
|
const avatar = computed(() => userStore.avatar);
|
||||||
|
const windowHeight = computed(() => uni.getSystemInfoSync().windowHeight - 50);
|
||||||
|
|
||||||
|
function handleToInfo() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/info/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToEditInfo() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/info/edit");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToSetting() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/setting/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToLogin() {
|
||||||
|
proxy.$tab.reLaunch("/pages/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToAvatar() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/avatar/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHelp() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/help/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleAbout() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/about/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleJiaoLiuQun() {
|
||||||
|
proxy.$modal.showToast("模块建设中~");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBuilding() {
|
||||||
|
proxy.$modal.showToast("模块建设中~");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col overflow-y-auto bg-white p-4">
|
||||||
|
<view class="space-y-5">
|
||||||
|
<!-- Nickname -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700"
|
||||||
|
>用户昵称</text
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="user.nickName"
|
||||||
|
class="h-11 w-full rounded-xl bg-gray-50 px-4 text-sm text-gray-800 outline-none ring-1 ring-gray-200 transition-all focus:bg-white focus:ring-2 focus:ring-blue-500"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入昵称"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Phone -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700"
|
||||||
|
>手机号码</text
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="user.phonenumber"
|
||||||
|
class="h-11 w-full rounded-xl bg-gray-50 px-4 text-sm text-gray-800 outline-none ring-1 ring-gray-200 transition-all focus:bg-white focus:ring-2 focus:ring-blue-500"
|
||||||
|
type="number"
|
||||||
|
placeholder="请输入手机号码"
|
||||||
|
maxlength="11"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700">邮箱</text>
|
||||||
|
<input
|
||||||
|
v-model="user.email"
|
||||||
|
class="h-11 w-full rounded-xl bg-gray-50 px-4 text-sm text-gray-800 outline-none ring-1 ring-gray-200 transition-all focus:bg-white focus:ring-2 focus:ring-blue-500"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入邮箱"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Sex -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700">性别</text>
|
||||||
|
<view class="flex space-x-4">
|
||||||
|
<view
|
||||||
|
v-for="item in sexs"
|
||||||
|
:key="item.value"
|
||||||
|
@click="user.sex = item.value"
|
||||||
|
class="flex flex-1 items-center justify-center rounded-xl border py-2.5 transition-all active:scale-95"
|
||||||
|
:class="
|
||||||
|
user.sex === item.value
|
||||||
|
? 'bg-blue-50 border-blue-500 text-blue-600'
|
||||||
|
: 'bg-gray-50 border-transparent text-gray-500'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<text class="text-sm font-medium">{{ item.text }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<view class="pt-6">
|
||||||
|
<button
|
||||||
|
@click="submit"
|
||||||
|
class="flex h-12 w-full items-center justify-center rounded-xl bg-blue-500 text-base font-semibold text-white shadow-lg shadow-blue-500/30 transition-all active:scale-95 active:bg-blue-600"
|
||||||
|
>
|
||||||
|
提 交
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getUserProfile, updateUserProfile } from "@/api/system/user";
|
||||||
|
import { ref, getCurrentInstance } from "vue";
|
||||||
|
import { onLoad } from "@dcloudio/uni-app";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const user = ref({
|
||||||
|
nickName: "",
|
||||||
|
phonenumber: "",
|
||||||
|
email: "",
|
||||||
|
sex: "",
|
||||||
|
});
|
||||||
|
const sexs = [
|
||||||
|
{
|
||||||
|
text: "男",
|
||||||
|
value: "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "女",
|
||||||
|
value: "1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function getUser() {
|
||||||
|
getUserProfile().then((response) => {
|
||||||
|
const { nickName, phonenumber, email, sex } = response.data;
|
||||||
|
user.value = { nickName, phonenumber, email, sex };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
if (!user.value.nickName) {
|
||||||
|
proxy.$modal.msgError("用户昵称不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.value.phonenumber) {
|
||||||
|
proxy.$modal.msgError("手机号码不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^1[3|4|5|6|7|8|9][0-9]\d{8}$/.test(user.value.phonenumber)) {
|
||||||
|
proxy.$modal.msgError("请输入正确的手机号码");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.value.email) {
|
||||||
|
proxy.$modal.msgError("邮箱地址不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(user.value.email)) {
|
||||||
|
proxy.$modal.msgError("请输入正确的邮箱地址");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserProfile(user.value).then((response) => {
|
||||||
|
proxy.$modal.msgSuccess("修改成功");
|
||||||
|
setTimeout(() => {
|
||||||
|
proxy.$tab.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoad(() => {
|
||||||
|
getUser();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col overflow-y-auto bg-gray-50 pt-4 pb-10">
|
||||||
|
<view class="overflow-hidden bg-white shadow-sm">
|
||||||
|
<!-- Nickname -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view
|
||||||
|
class="flex size-9 items-center justify-center rounded-full bg-blue-50 text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="ml-3 text-base font-medium text-gray-800">昵称</text>
|
||||||
|
</view>
|
||||||
|
<text class="text-sm text-gray-500">{{ user.nickName }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Phone -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view
|
||||||
|
class="flex size-9 items-center justify-center rounded-full bg-green-50 text-green-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-phone text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="ml-3 text-base font-medium text-gray-800">手机号码</text>
|
||||||
|
</view>
|
||||||
|
<text class="text-sm text-gray-500">{{ user.phonenumber }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view
|
||||||
|
class="flex size-9 items-center justify-center rounded-full bg-indigo-50 text-indigo-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-email text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="ml-3 text-base font-medium text-gray-800">邮箱</text>
|
||||||
|
</view>
|
||||||
|
<text class="text-sm text-gray-500">{{ user.email }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Post -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view
|
||||||
|
class="flex size-9 items-center justify-center rounded-full bg-orange-50 text-orange-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-shield-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="ml-3 text-base font-medium text-gray-800">岗位</text>
|
||||||
|
</view>
|
||||||
|
<text class="text-sm text-gray-500">{{ postGroup }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Role -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view
|
||||||
|
class="flex size-9 items-center justify-center rounded-full bg-purple-50 text-purple-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-badge-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="ml-3 text-base font-medium text-gray-800">角色</text>
|
||||||
|
</view>
|
||||||
|
<text class="text-sm text-gray-500">{{ roleGroup }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Create Time -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between px-5 py-4 active:bg-gray-50"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view
|
||||||
|
class="flex size-9 items-center justify-center rounded-full bg-pink-50 text-pink-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-calendar text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="ml-3 text-base font-medium text-gray-800">创建日期</text>
|
||||||
|
</view>
|
||||||
|
<text class="text-sm text-gray-500">{{ user.createTime }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getUserProfile } from "@/api/system/user";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const user = ref({});
|
||||||
|
const roleGroup = ref("");
|
||||||
|
const postGroup = ref("");
|
||||||
|
|
||||||
|
function getUser() {
|
||||||
|
getUserProfile().then((response) => {
|
||||||
|
user.value = response.data;
|
||||||
|
roleGroup.value = response.roleGroup;
|
||||||
|
postGroup.value = response.postGroup;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getUser();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<template>
|
||||||
|
<view class="min-h-screen bg-white p-4">
|
||||||
|
<view class="space-y-5">
|
||||||
|
<!-- Old Password -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700">旧密码</text>
|
||||||
|
<input
|
||||||
|
v-model="user.oldPassword"
|
||||||
|
class="h-11 w-full rounded-xl bg-gray-50 px-4 text-sm text-gray-800 outline-none ring-1 ring-gray-200 transition-all focus:bg-white focus:ring-2 focus:ring-blue-500"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入旧密码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<!-- New Password -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700">新密码</text>
|
||||||
|
<input
|
||||||
|
v-model="user.newPassword"
|
||||||
|
class="h-11 w-full rounded-xl bg-gray-50 px-4 text-sm text-gray-800 outline-none ring-1 ring-gray-200 transition-all focus:bg-white focus:ring-2 focus:ring-blue-500"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入新密码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<view class="group relative">
|
||||||
|
<text class="mb-2 block text-sm font-medium text-gray-700"
|
||||||
|
>确认密码</text
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="user.confirmPassword"
|
||||||
|
class="h-11 w-full rounded-xl bg-gray-50 px-4 text-sm text-gray-800 outline-none ring-1 ring-gray-200 transition-all focus:bg-white focus:ring-2 focus:ring-blue-500"
|
||||||
|
type="password"
|
||||||
|
placeholder="请确认新密码"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<view class="pt-6">
|
||||||
|
<button
|
||||||
|
@click="submit"
|
||||||
|
class="flex h-12 w-full items-center justify-center rounded-xl bg-blue-500 text-base font-semibold text-white shadow-lg shadow-blue-500/30 transition-all active:scale-95 active:bg-blue-600"
|
||||||
|
>
|
||||||
|
提 交
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { updateUserPwd } from "@/api/system/user";
|
||||||
|
import { ref, reactive, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const user = reactive({
|
||||||
|
oldPassword: "",
|
||||||
|
newPassword: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
function submit() {
|
||||||
|
if (!user.oldPassword) {
|
||||||
|
proxy.$modal.msgError("旧密码不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.newPassword) {
|
||||||
|
proxy.$modal.msgError("新密码不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.newPassword.length < 6 || user.newPassword.length > 20) {
|
||||||
|
proxy.$modal.msgError("长度在 6 到 20 个字符");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!user.confirmPassword) {
|
||||||
|
proxy.$modal.msgError("确认密码不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (user.newPassword !== user.confirmPassword) {
|
||||||
|
proxy.$modal.msgError("两次输入的密码不一致");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUserPwd(user.oldPassword, user.newPassword).then((response) => {
|
||||||
|
proxy.$modal.msgSuccess("修改成功");
|
||||||
|
setTimeout(() => {
|
||||||
|
proxy.$tab.navigateBack();
|
||||||
|
}, 1500);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col overflow-y-auto bg-gray-50 pt-4 pb-10">
|
||||||
|
<view class="bg-white">
|
||||||
|
<!-- Change Password -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
@click="handleToPwd"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="i-mdi-lock text-xl text-gray-600 mr-3"></view>
|
||||||
|
<text class="text-base text-gray-800">修改密码</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-base text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Check Update -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between border-b border-gray-100 px-5 py-4 active:bg-gray-50"
|
||||||
|
@click="handleToUpgrade"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="i-mdi-refresh text-xl text-gray-600 mr-3"></view>
|
||||||
|
<text class="text-base text-gray-800">检查更新</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-base text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Clean Cache -->
|
||||||
|
<view
|
||||||
|
class="flex items-center justify-between px-5 py-4 active:bg-gray-50"
|
||||||
|
@click="handleCleanTmp"
|
||||||
|
>
|
||||||
|
<view class="flex items-center">
|
||||||
|
<view class="i-mdi-delete text-xl text-gray-600 mr-3"></view>
|
||||||
|
<text class="text-base text-gray-800">清理缓存</text>
|
||||||
|
</view>
|
||||||
|
<view class="i-mdi-chevron-right text-base text-gray-400"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Logout -->
|
||||||
|
<view class="mt-8 px-4">
|
||||||
|
<view
|
||||||
|
class="flex h-12 w-full items-center justify-center rounded-xl bg-red-50 text-base font-semibold text-red-600 transition-colors active:bg-red-100"
|
||||||
|
@click="handleLogout"
|
||||||
|
>退出登录</view
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import { getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
|
function handleToPwd() {
|
||||||
|
proxy.$tab.navigateTo("/pages/mine/pwd/index");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToUpgrade() {
|
||||||
|
proxy.$modal.showToast("模块建设中~");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCleanTmp() {
|
||||||
|
proxy.$modal.showToast("模块建设中~");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogout() {
|
||||||
|
proxy.$modal.confirm("确定注销并退出系统吗?").then(() => {
|
||||||
|
useUserStore()
|
||||||
|
.logOut()
|
||||||
|
.then(() => {})
|
||||||
|
.finally(() => {
|
||||||
|
proxy.$tab.reLaunch("/pages/index");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
page {
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
<template>
|
||||||
|
<view
|
||||||
|
class="flex h-full flex-col items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 px-6 pb-20 overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- Logo Section -->
|
||||||
|
<view class="mb-6 flex flex-col items-center">
|
||||||
|
<view
|
||||||
|
class="mb-4 flex size-16 items-center justify-center rounded-2xl bg-white shadow-lg"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="size-10"
|
||||||
|
:src="globalConfig.appInfo.logo"
|
||||||
|
mode="widthFix"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<text class="text-xl font-bold tracking-wide text-gray-800"
|
||||||
|
>RuoYi-FastAPI移动端注册</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Form Section -->
|
||||||
|
<view class="w-full rounded-3xl bg-white/80 p-6 shadow-xl backdrop-blur-md">
|
||||||
|
<!-- Username -->
|
||||||
|
<view class="group relative mb-5">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="registerForm.username"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入账号"
|
||||||
|
maxlength="30"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Password -->
|
||||||
|
<view class="group relative mb-5">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-lock text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="registerForm.password"
|
||||||
|
type="password"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
maxlength="20"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Confirm Password -->
|
||||||
|
<view class="group relative mb-5">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-lock text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="registerForm.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
placeholder="请输入重复密码"
|
||||||
|
maxlength="20"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Captcha -->
|
||||||
|
<view
|
||||||
|
class="mb-8 flex items-center justify-between"
|
||||||
|
v-if="captchaEnabled"
|
||||||
|
>
|
||||||
|
<view class="group relative mr-3 flex-1">
|
||||||
|
<view
|
||||||
|
class="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 transition-colors group-focus-within:text-blue-500"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-security text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<input
|
||||||
|
v-model="registerForm.code"
|
||||||
|
type="number"
|
||||||
|
class="h-12 w-full rounded-xl bg-gray-50 pl-12 pr-4 text-sm text-gray-700 outline-none transition-all focus:bg-white focus:ring-2 focus:ring-blue-400"
|
||||||
|
placeholder="验证码"
|
||||||
|
maxlength="4"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="h-12 w-28 overflow-hidden rounded-xl bg-gray-100 shadow-sm transition-opacity active:opacity-80"
|
||||||
|
@click="getCode"
|
||||||
|
>
|
||||||
|
<image :src="codeUrl" class="size-full object-cover"></image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- Register Button -->
|
||||||
|
<button
|
||||||
|
@click="handleRegister"
|
||||||
|
class="flex h-12 w-full items-center justify-center rounded-xl bg-gradient-to-r from-blue-500 to-indigo-600 text-base font-semibold text-white shadow-lg shadow-blue-500/30 transition-transform active:scale-95"
|
||||||
|
>
|
||||||
|
注 册
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Footer Links -->
|
||||||
|
<view class="mt-6 flex flex-col items-center space-y-3">
|
||||||
|
<view class="flex items-center text-sm text-gray-500">
|
||||||
|
<text
|
||||||
|
@click="handleUserLogin"
|
||||||
|
class="ml-1 font-medium text-blue-600 active:opacity-70"
|
||||||
|
>使用已有账号登录</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { getCodeImg, register } from "@/api/login";
|
||||||
|
import { ref, getCurrentInstance } from "vue";
|
||||||
|
import { useConfigStore } from "@/store";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const globalConfig = useConfigStore().config;
|
||||||
|
const codeUrl = ref("");
|
||||||
|
// 验证码开关
|
||||||
|
const captchaEnabled = ref(true);
|
||||||
|
const registerForm = ref({
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
code: "",
|
||||||
|
uuid: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 用户登录
|
||||||
|
function handleUserLogin() {
|
||||||
|
proxy.$tab.navigateTo(`/pages/login`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取图形验证码
|
||||||
|
function getCode() {
|
||||||
|
getCodeImg().then((res) => {
|
||||||
|
captchaEnabled.value =
|
||||||
|
res.captchaEnabled === undefined ? true : res.captchaEnabled;
|
||||||
|
if (captchaEnabled.value) {
|
||||||
|
codeUrl.value = "data:image/gif;base64," + res.img;
|
||||||
|
registerForm.value.uuid = res.uuid;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注册方法
|
||||||
|
async function handleRegister() {
|
||||||
|
if (registerForm.value.username === "") {
|
||||||
|
proxy.$modal.msgError("请输入您的账号");
|
||||||
|
} else if (registerForm.value.password === "") {
|
||||||
|
proxy.$modal.msgError("请输入您的密码");
|
||||||
|
} else if (registerForm.value.confirmPassword === "") {
|
||||||
|
proxy.$modal.msgError("请再次输入您的密码");
|
||||||
|
} else if (
|
||||||
|
registerForm.value.password !== registerForm.value.confirmPassword
|
||||||
|
) {
|
||||||
|
proxy.$modal.msgError("两次输入的密码不一致");
|
||||||
|
} else if (registerForm.value.code === "" && captchaEnabled.value) {
|
||||||
|
proxy.$modal.msgError("请输入验证码");
|
||||||
|
} else {
|
||||||
|
proxy.$modal.loading("注册中,请耐心等待...");
|
||||||
|
userRegister();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户注册
|
||||||
|
async function userRegister() {
|
||||||
|
register(registerForm.value)
|
||||||
|
.then((res) => {
|
||||||
|
proxy.$modal.closeLoading();
|
||||||
|
uni.showModal({
|
||||||
|
title: "系统提示",
|
||||||
|
content:
|
||||||
|
"恭喜你,您的账号 " + registerForm.value.username + " 注册成功!",
|
||||||
|
success: function (res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.redirectTo({ url: `/pages/login` });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (captchaEnabled.value) {
|
||||||
|
getCode();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCode();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
page {
|
||||||
|
background-color: #ffffff;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<view class="flex h-full flex-col bg-gray-50 overflow-hidden">
|
||||||
|
<scroll-view scroll-y class="flex-1" :show-scrollbar="false">
|
||||||
|
<view class="p-4 pb-24 space-y-6">
|
||||||
|
<!-- Banner -->
|
||||||
|
<view
|
||||||
|
class="relative h-40 w-full overflow-hidden rounded-2xl shadow-lg shadow-indigo-100"
|
||||||
|
>
|
||||||
|
<swiper
|
||||||
|
class="h-40 w-full"
|
||||||
|
:current="swiperDotIndex"
|
||||||
|
@change="changeSwiper"
|
||||||
|
autoplay
|
||||||
|
interval="3000"
|
||||||
|
circular
|
||||||
|
>
|
||||||
|
<swiper-item v-for="(item, index) in data" :key="index">
|
||||||
|
<view class="h-full w-full" @click="clickBannerItem(item)">
|
||||||
|
<image
|
||||||
|
:src="item.image"
|
||||||
|
mode="scaleToFill"
|
||||||
|
class="block h-full w-full"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</swiper-item>
|
||||||
|
</swiper>
|
||||||
|
<!-- Custom Dots -->
|
||||||
|
<view
|
||||||
|
class="absolute bottom-3 right-0 left-0 flex justify-center space-x-2 pointer-events-none"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
v-for="(item, index) in data"
|
||||||
|
:key="index"
|
||||||
|
class="h-1.5 rounded-full transition-all duration-300"
|
||||||
|
:class="current === index ? 'bg-white w-4' : 'bg-white/50 w-1.5'"
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- AI Tools Section -->
|
||||||
|
<view>
|
||||||
|
<view class="mb-4 flex items-center space-x-2">
|
||||||
|
<view class="h-4 w-1 rounded-full bg-indigo-500"></view>
|
||||||
|
<text class="text-base font-bold text-gray-800">AI 生产力</text>
|
||||||
|
</view>
|
||||||
|
<view class="grid grid-cols-2 gap-4">
|
||||||
|
<view
|
||||||
|
class="group relative overflow-hidden rounded-2xl bg-white p-5 shadow-sm transition-all active:scale-95"
|
||||||
|
@click="handleToAiChat"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="absolute -right-4 -top-4 size-20 rounded-full bg-indigo-50 opacity-50"
|
||||||
|
></view>
|
||||||
|
<view class="relative z-10 flex flex-col">
|
||||||
|
<view
|
||||||
|
class="mb-3 flex size-10 items-center justify-center rounded-xl bg-indigo-500 text-white shadow-md shadow-indigo-200"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-chat text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="font-bold text-gray-800">智能对话</text>
|
||||||
|
<text class="mt-1 text-xs text-gray-500">智能助手</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="group relative overflow-hidden rounded-2xl bg-white p-5 shadow-sm transition-all active:scale-95"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="absolute -right-4 -top-4 size-20 rounded-full bg-purple-50 opacity-50"
|
||||||
|
></view>
|
||||||
|
<view class="relative z-10 flex flex-col">
|
||||||
|
<view
|
||||||
|
class="mb-3 flex size-10 items-center justify-center rounded-xl bg-purple-500 text-white shadow-md shadow-purple-200"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-image-multiple text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="font-bold text-gray-800">图像生成</text>
|
||||||
|
<text class="mt-1 text-xs text-gray-500">创意工坊</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- System Management Section -->
|
||||||
|
<view>
|
||||||
|
<view class="mb-4 flex items-center space-x-2">
|
||||||
|
<view class="h-4 w-1 rounded-full bg-blue-500"></view>
|
||||||
|
<text class="text-base font-bold text-gray-800">系统管理</text>
|
||||||
|
</view>
|
||||||
|
<view class="rounded-2xl bg-white p-5 shadow-sm">
|
||||||
|
<view class="flex flex-wrap justify-between gap-y-4">
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleToUserList"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-blue-50 text-blue-500 transition-colors group-active:bg-blue-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>用户管理</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-orange-50 text-orange-500 transition-colors group-active:bg-orange-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-badge-account text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>角色管理</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-green-50 text-green-500 transition-colors group-active:bg-green-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-office-building text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>部门管理</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-red-50 text-red-500 transition-colors group-active:bg-red-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-cog text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>配置管理</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-cyan-50 text-cyan-500 transition-colors group-active:bg-cyan-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-book-open-page-variant text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>字典管理</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-yellow-50 text-yellow-500 transition-colors group-active:bg-yellow-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-bullhorn text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>通知公告</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-pink-50 text-pink-500 transition-colors group-active:bg-pink-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-file-document-outline text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600"
|
||||||
|
>日志管理</text
|
||||||
|
>
|
||||||
|
</view>
|
||||||
|
<view
|
||||||
|
class="w-[22%] flex flex-col items-center space-y-1.5 active:opacity-60"
|
||||||
|
@click="handleBuilding"
|
||||||
|
>
|
||||||
|
<view
|
||||||
|
class="flex size-10 items-center justify-center rounded-xl bg-gray-50 text-gray-500 transition-colors group-active:bg-gray-100"
|
||||||
|
>
|
||||||
|
<view class="i-mdi-dots-horizontal text-xl"></view>
|
||||||
|
</view>
|
||||||
|
<text class="text-[11px] font-medium text-gray-600">更多</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, getCurrentInstance } from "vue";
|
||||||
|
|
||||||
|
const { proxy } = getCurrentInstance();
|
||||||
|
const current = ref(0);
|
||||||
|
const swiperDotIndex = ref(0);
|
||||||
|
const data = ref([
|
||||||
|
{ image: "/static/images/banner/banner01.jpg" },
|
||||||
|
{ image: "/static/images/banner/banner02.jpg" },
|
||||||
|
{ image: "/static/images/banner/banner03.jpg" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
function clickBannerItem(item) {
|
||||||
|
console.info(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeSwiper(e) {
|
||||||
|
current.value = e.detail.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToAiChat() {
|
||||||
|
handleBuilding();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToUserList() {
|
||||||
|
handleBuilding();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleBuilding() {
|
||||||
|
proxy.$modal.msg("模块建设中~");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
|
||||||
|
// 登录页面
|
||||||
|
const loginPage = "/pages/login";
|
||||||
|
|
||||||
|
// 页面白名单
|
||||||
|
const whiteList = [
|
||||||
|
"/pages/login",
|
||||||
|
"/pages/register",
|
||||||
|
"/pages/common/webview/index",
|
||||||
|
"/pages/common/agreement/index",
|
||||||
|
"/pages/common/privacy/index",
|
||||||
|
];
|
||||||
|
|
||||||
|
// 检查地址白名单
|
||||||
|
function checkWhite(url) {
|
||||||
|
const path = url.split("?")[0];
|
||||||
|
return whiteList.indexOf(path) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面跳转验证拦截器
|
||||||
|
let list = ["navigateTo", "redirectTo", "reLaunch", "switchTab"];
|
||||||
|
list.forEach((item) => {
|
||||||
|
uni.addInterceptor(item, {
|
||||||
|
invoke(to) {
|
||||||
|
if (getToken()) {
|
||||||
|
if (to.url === loginPage) {
|
||||||
|
uni.reLaunch({ url: "/" });
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if (checkWhite(to.url)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
uni.reLaunch({ url: loginPage });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail(err) {
|
||||||
|
console.log(err);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useUserStore } from "@/store";
|
||||||
|
|
||||||
|
function authPermission(permission) {
|
||||||
|
const all_permission = "*:*:*";
|
||||||
|
const permissions = useUserStore().permissions;
|
||||||
|
if (permission && permission.length > 0) {
|
||||||
|
return permissions.some((v) => {
|
||||||
|
return all_permission === v || v === permission;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function authRole(role) {
|
||||||
|
const super_admin = "admin";
|
||||||
|
const roles = useUserStore().roles;
|
||||||
|
if (role && role.length > 0) {
|
||||||
|
return roles.some((v) => {
|
||||||
|
return super_admin === v || v === role;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// 验证用户是否具备某权限
|
||||||
|
hasPermi(permission) {
|
||||||
|
return authPermission(permission);
|
||||||
|
},
|
||||||
|
// 验证用户是否含有指定权限,只需包含其中一个
|
||||||
|
hasPermiOr(permissions) {
|
||||||
|
return permissions.some((item) => {
|
||||||
|
return authPermission(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 验证用户是否含有指定权限,必须全部拥有
|
||||||
|
hasPermiAnd(permissions) {
|
||||||
|
return permissions.every((item) => {
|
||||||
|
return authPermission(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 验证用户是否具备某角色
|
||||||
|
hasRole(role) {
|
||||||
|
return authRole(role);
|
||||||
|
},
|
||||||
|
// 验证用户是否含有指定角色,只需包含其中一个
|
||||||
|
hasRoleOr(roles) {
|
||||||
|
return roles.some((item) => {
|
||||||
|
return authRole(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 验证用户是否含有指定角色,必须全部拥有
|
||||||
|
hasRoleAnd(roles) {
|
||||||
|
return roles.every((item) => {
|
||||||
|
return authRole(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import tab from "./tab";
|
||||||
|
import auth from "./auth";
|
||||||
|
import modal from "./modal";
|
||||||
|
|
||||||
|
export function install(app) {
|
||||||
|
// 页签操作
|
||||||
|
app.config.globalProperties.$tab = tab;
|
||||||
|
// 认证对象
|
||||||
|
app.config.globalProperties.$auth = auth;
|
||||||
|
// 模态框对象
|
||||||
|
app.config.globalProperties.$modal = modal;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
export default {
|
||||||
|
// 消息提示
|
||||||
|
msg(content) {
|
||||||
|
uni.showToast({
|
||||||
|
title: content,
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 错误消息
|
||||||
|
msgError(content) {
|
||||||
|
uni.showToast({
|
||||||
|
title: content,
|
||||||
|
icon: "error",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 成功消息
|
||||||
|
msgSuccess(content) {
|
||||||
|
uni.showToast({
|
||||||
|
title: content,
|
||||||
|
icon: "success",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 隐藏消息
|
||||||
|
hideMsg(content) {
|
||||||
|
uni.hideToast();
|
||||||
|
},
|
||||||
|
// 弹出提示
|
||||||
|
alert(content, title) {
|
||||||
|
uni.showModal({
|
||||||
|
title: title || "系统提示",
|
||||||
|
content: content,
|
||||||
|
showCancel: false,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 确认窗体
|
||||||
|
confirm(content, title) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.showModal({
|
||||||
|
title: title || "系统提示",
|
||||||
|
content: content,
|
||||||
|
cancelText: "取消",
|
||||||
|
confirmText: "确定",
|
||||||
|
success: function (res) {
|
||||||
|
if (res.confirm) {
|
||||||
|
resolve(res.confirm);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 提示信息
|
||||||
|
showToast(option) {
|
||||||
|
if (typeof option === "object") {
|
||||||
|
uni.showToast(option);
|
||||||
|
} else {
|
||||||
|
uni.showToast({
|
||||||
|
title: option,
|
||||||
|
icon: "none",
|
||||||
|
duration: 2500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 打开遮罩层
|
||||||
|
loading(content) {
|
||||||
|
uni.showLoading({
|
||||||
|
title: content,
|
||||||
|
icon: "none",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 关闭遮罩层
|
||||||
|
closeLoading() {
|
||||||
|
try {
|
||||||
|
uni.hideLoading();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
// 关闭所有页面,打开到应用内的某个页面
|
||||||
|
reLaunch(url) {
|
||||||
|
return uni.reLaunch({
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 跳转到tabBar页面,并关闭其他所有非tabBar页面
|
||||||
|
switchTab(url) {
|
||||||
|
return uni.switchTab({
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 关闭当前页面,跳转到应用内的某个页面
|
||||||
|
redirectTo(url) {
|
||||||
|
return uni.redirectTo({
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 保留当前页面,跳转到应用内的某个页面
|
||||||
|
navigateTo(url) {
|
||||||
|
return uni.navigateTo({
|
||||||
|
url: url,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 关闭当前页面,返回上一页面或多级页面
|
||||||
|
navigateBack() {
|
||||||
|
return uni.navigateBack();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
import { createPinia } from "pinia";
|
||||||
|
import { useUserStore } from "./modules/user";
|
||||||
|
import { useConfigStore } from "./modules/config";
|
||||||
|
|
||||||
|
const pinia = createPinia();
|
||||||
|
|
||||||
|
export default pinia;
|
||||||
|
|
||||||
|
export { useUserStore, useConfigStore };
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
export const useConfigStore = defineStore("config", () => {
|
||||||
|
const config = ref();
|
||||||
|
const setConfig = (val) => {
|
||||||
|
config.value = val;
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
config,
|
||||||
|
setConfig,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
const useDictStore = defineStore("dict", {
|
||||||
|
state: () => ({
|
||||||
|
dict: new Array(),
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
// 获取字典
|
||||||
|
getDict(_key) {
|
||||||
|
if (_key == null && _key == "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < this.dict.length; i++) {
|
||||||
|
if (this.dict[i].key == _key) {
|
||||||
|
return this.dict[i].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 设置字典
|
||||||
|
setDict(_key, value) {
|
||||||
|
if (_key !== null && _key !== "") {
|
||||||
|
this.dict.push({
|
||||||
|
key: _key,
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 删除字典
|
||||||
|
removeDict(_key) {
|
||||||
|
var bln = false;
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < this.dict.length; i++) {
|
||||||
|
if (this.dict[i].key == _key) {
|
||||||
|
this.dict.splice(i, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
bln = false;
|
||||||
|
}
|
||||||
|
return bln;
|
||||||
|
},
|
||||||
|
// 清空字典
|
||||||
|
cleanDict() {
|
||||||
|
this.dict = new Array();
|
||||||
|
},
|
||||||
|
// 初始字典
|
||||||
|
initDict() {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default useDictStore;
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
import { ref } from "vue";
|
||||||
|
import config from "@/config";
|
||||||
|
import storage from "@/utils/storage";
|
||||||
|
import constant from "@/utils/constant";
|
||||||
|
import { isHttp, isEmpty } from "@/utils/validate";
|
||||||
|
import { getInfo, login, logout } from "@/api/login";
|
||||||
|
import { getToken, removeToken, setToken } from "@/utils/auth";
|
||||||
|
import defAva from "@/static/images/profile.jpg";
|
||||||
|
|
||||||
|
const baseUrl = config.baseUrl;
|
||||||
|
|
||||||
|
export const useUserStore = defineStore("user", () => {
|
||||||
|
const token = ref(getToken());
|
||||||
|
const id = ref(storage.get(constant.id));
|
||||||
|
const name = ref(storage.get(constant.name));
|
||||||
|
const avatar = ref(storage.get(constant.avatar));
|
||||||
|
const roles = ref(storage.get(constant.roles));
|
||||||
|
const permissions = ref(storage.get(constant.permissions));
|
||||||
|
|
||||||
|
const SET_TOKEN = (val) => {
|
||||||
|
token.value = val;
|
||||||
|
};
|
||||||
|
const SET_ID = (val) => {
|
||||||
|
id.value = val;
|
||||||
|
storage.set(constant.id, val);
|
||||||
|
};
|
||||||
|
const SET_NAME = (val) => {
|
||||||
|
name.value = val;
|
||||||
|
storage.set(constant.name, val);
|
||||||
|
};
|
||||||
|
const SET_AVATAR = (val) => {
|
||||||
|
avatar.value = val;
|
||||||
|
storage.set(constant.avatar, val);
|
||||||
|
};
|
||||||
|
const SET_ROLES = (val) => {
|
||||||
|
roles.value = val;
|
||||||
|
storage.set(constant.roles, val);
|
||||||
|
};
|
||||||
|
const SET_PERMISSIONS = (val) => {
|
||||||
|
permissions.value = val;
|
||||||
|
storage.set(constant.permissions, val);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
const loginAction = (userInfo) => {
|
||||||
|
const username = userInfo.username.trim();
|
||||||
|
const password = userInfo.password;
|
||||||
|
const code = userInfo.code;
|
||||||
|
const uuid = userInfo.uuid;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
login(username, password, code, uuid)
|
||||||
|
.then((res) => {
|
||||||
|
setToken(res.token);
|
||||||
|
SET_TOKEN(res.token);
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
const getInfoAction = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
getInfo()
|
||||||
|
.then((res) => {
|
||||||
|
const user = res.user;
|
||||||
|
let avatar = user.avatar || "";
|
||||||
|
if (!isHttp(avatar)) {
|
||||||
|
avatar = isEmpty(avatar) ? defAva : baseUrl + avatar;
|
||||||
|
}
|
||||||
|
const userid =
|
||||||
|
isEmpty(user) || isEmpty(user.userId) ? "" : user.userId;
|
||||||
|
const username =
|
||||||
|
isEmpty(user) || isEmpty(user.userName) ? "" : user.userName;
|
||||||
|
if (res.roles && res.roles.length > 0) {
|
||||||
|
SET_ROLES(res.roles);
|
||||||
|
SET_PERMISSIONS(res.permissions);
|
||||||
|
} else {
|
||||||
|
SET_ROLES(["ROLE_DEFAULT"]);
|
||||||
|
}
|
||||||
|
SET_ID(userid);
|
||||||
|
SET_NAME(username);
|
||||||
|
SET_AVATAR(avatar);
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 退出系统
|
||||||
|
const logOutAction = () => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
logout(token.value)
|
||||||
|
.then(() => {
|
||||||
|
SET_TOKEN("");
|
||||||
|
SET_ROLES([]);
|
||||||
|
SET_PERMISSIONS([]);
|
||||||
|
removeToken();
|
||||||
|
storage.clean();
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
avatar,
|
||||||
|
roles,
|
||||||
|
permissions,
|
||||||
|
SET_AVATAR,
|
||||||
|
login: loginAction,
|
||||||
|
getInfo: getInfoAction,
|
||||||
|
logOut: logOutAction,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"light": {
|
||||||
|
"navBgColor": "#f6f6f6",
|
||||||
|
"navTxtStyle": "black"
|
||||||
|
},
|
||||||
|
"dark": {
|
||||||
|
"navBgColor": "#191919",
|
||||||
|
"navTxtStyle": "white"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
const TokenKey = "App-Token";
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return uni.getStorageSync(TokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setToken(token) {
|
||||||
|
return uni.setStorageSync(TokenKey, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
return uni.removeStorageSync(TokenKey);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* 显示消息提示框
|
||||||
|
* @param content 提示的标题
|
||||||
|
*/
|
||||||
|
export function toast(content) {
|
||||||
|
uni.showToast({
|
||||||
|
icon: "none",
|
||||||
|
title: content,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示模态弹窗
|
||||||
|
* @param content 提示的标题
|
||||||
|
*/
|
||||||
|
export function showConfirm(content) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.showModal({
|
||||||
|
title: "提示",
|
||||||
|
content: content,
|
||||||
|
cancelText: "取消",
|
||||||
|
confirmText: "确定",
|
||||||
|
success: function (res) {
|
||||||
|
resolve(res);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数处理
|
||||||
|
* @param params 参数
|
||||||
|
*/
|
||||||
|
export function tansParams(params) {
|
||||||
|
let result = "";
|
||||||
|
for (const propName of Object.keys(params)) {
|
||||||
|
const value = params[propName];
|
||||||
|
var part = encodeURIComponent(propName) + "=";
|
||||||
|
if (value !== null && value !== "" && typeof value !== "undefined") {
|
||||||
|
if (typeof value === "object") {
|
||||||
|
for (const key of Object.keys(value)) {
|
||||||
|
if (
|
||||||
|
value[key] !== null &&
|
||||||
|
value[key] !== "" &&
|
||||||
|
typeof value[key] !== "undefined"
|
||||||
|
) {
|
||||||
|
let params = propName + "[" + key + "]";
|
||||||
|
var subPart = encodeURIComponent(params) + "=";
|
||||||
|
result += subPart + encodeURIComponent(value[key]) + "&";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += part + encodeURIComponent(value) + "&";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
const constant = {
|
||||||
|
avatar: "user_avatar",
|
||||||
|
id: "user_id",
|
||||||
|
name: "user_name",
|
||||||
|
roles: "user_roles",
|
||||||
|
permissions: "user_permissions",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default constant;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import useDictStore from "@/store/modules/dict";
|
||||||
|
import { getDicts } from "@/api/system/dict/data";
|
||||||
|
import { ref, toRefs } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字典数据
|
||||||
|
*/
|
||||||
|
export function useDict(...args) {
|
||||||
|
const res = ref({});
|
||||||
|
return (() => {
|
||||||
|
args.forEach((dictType, index) => {
|
||||||
|
res.value[dictType] = [];
|
||||||
|
const dicts = useDictStore().getDict(dictType);
|
||||||
|
if (dicts) {
|
||||||
|
res.value[dictType] = dicts;
|
||||||
|
} else {
|
||||||
|
getDicts(dictType).then((resp) => {
|
||||||
|
res.value[dictType] = resp.data.map((p) => ({
|
||||||
|
label: p.dictLabel,
|
||||||
|
value: p.dictValue,
|
||||||
|
elTagType: p.listClass,
|
||||||
|
elTagClass: p.cssClass,
|
||||||
|
}));
|
||||||
|
useDictStore().setDict(dictType, res.value[dictType]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return toRefs(res.value);
|
||||||
|
})();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
401: "认证失败,无法访问系统资源",
|
||||||
|
403: "当前操作没有权限",
|
||||||
|
404: "访问资源不存在",
|
||||||
|
default: "系统未知错误,请反馈给管理员",
|
||||||
|
};
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import store from "@/store";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符权限校验
|
||||||
|
* @param {Array} value 校验值
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function checkPermi(value) {
|
||||||
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
|
const permissions = store.getters && store.getters.permissions;
|
||||||
|
const permissionDatas = value;
|
||||||
|
const all_permission = "*:*:*";
|
||||||
|
|
||||||
|
const hasPermission = permissions.some((permission) => {
|
||||||
|
return (
|
||||||
|
all_permission === permission || permissionDatas.includes(permission)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasPermission) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
`need roles! Like checkPermi="['system:user:add','system:user:edit']"`,
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色权限校验
|
||||||
|
* @param {Array} value 校验值
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function checkRole(value) {
|
||||||
|
if (value && value instanceof Array && value.length > 0) {
|
||||||
|
const roles = store.getters && store.getters.roles;
|
||||||
|
const permissionRoles = value;
|
||||||
|
const super_admin = "admin";
|
||||||
|
|
||||||
|
const hasRole = roles.some((role) => {
|
||||||
|
return super_admin === role || permissionRoles.includes(role);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!hasRole) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
console.error(`need roles! Like checkRole="['admin','editor']"`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import config from "@/config";
|
||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
import errorCode from "@/utils/errorCode";
|
||||||
|
import { useUserStore } from "@/store/modules/user";
|
||||||
|
import { toast, showConfirm, tansParams } from "@/utils/common";
|
||||||
|
|
||||||
|
let timeout = 10000;
|
||||||
|
const baseUrl = config.baseUrl;
|
||||||
|
|
||||||
|
const request = (config) => {
|
||||||
|
// 是否需要设置 token
|
||||||
|
const isToken = (config.headers || {}).isToken === false;
|
||||||
|
config.header = config.header || {};
|
||||||
|
if (getToken() && !isToken) {
|
||||||
|
config.header["Authorization"] = "Bearer " + getToken();
|
||||||
|
}
|
||||||
|
// get请求映射params参数
|
||||||
|
if (config.params) {
|
||||||
|
let url = config.url + "?" + tansParams(config.params);
|
||||||
|
url = url.slice(0, -1);
|
||||||
|
config.url = url;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni
|
||||||
|
.request({
|
||||||
|
method: config.method || "get",
|
||||||
|
timeout: config.timeout || timeout,
|
||||||
|
url: config.baseUrl || baseUrl + config.url,
|
||||||
|
data: config.data,
|
||||||
|
header: config.header,
|
||||||
|
dataType: "json",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const res = response;
|
||||||
|
const code = res.data.code || 200;
|
||||||
|
const msg = errorCode[code] || res.data.msg || errorCode["default"];
|
||||||
|
if (code === 401) {
|
||||||
|
showConfirm(
|
||||||
|
"登录状态已过期,您可以继续留在该页面,或者重新登录?",
|
||||||
|
).then((res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
useUserStore()
|
||||||
|
.logOut()
|
||||||
|
.then((res) => {
|
||||||
|
uni.reLaunch({ url: "/pages/login" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reject("无效的会话,或者会话已过期,请重新登录。");
|
||||||
|
} else if (code === 500) {
|
||||||
|
toast(msg);
|
||||||
|
reject("500");
|
||||||
|
} else if (code !== 200) {
|
||||||
|
toast(msg);
|
||||||
|
reject(code);
|
||||||
|
}
|
||||||
|
resolve(res.data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
let { message } = error;
|
||||||
|
if (message === "Network Error") {
|
||||||
|
message = "后端接口连接异常";
|
||||||
|
} else if (message.includes("timeout")) {
|
||||||
|
message = "系统接口请求超时";
|
||||||
|
} else if (message.includes("Request failed with status code")) {
|
||||||
|
message = "系统接口" + message.substr(message.length - 3) + "异常";
|
||||||
|
}
|
||||||
|
toast(message);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default request;
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import constant from "./constant";
|
||||||
|
|
||||||
|
// 存储变量名
|
||||||
|
let storageKey = "storage_data";
|
||||||
|
|
||||||
|
// 存储节点变量名
|
||||||
|
let storageNodeKeys = [
|
||||||
|
constant.avatar,
|
||||||
|
constant.id,
|
||||||
|
constant.name,
|
||||||
|
constant.roles,
|
||||||
|
constant.permissions,
|
||||||
|
];
|
||||||
|
|
||||||
|
const storage = {
|
||||||
|
set: function (key, value) {
|
||||||
|
if (storageNodeKeys.indexOf(key) != -1) {
|
||||||
|
let tmp = uni.getStorageSync(storageKey);
|
||||||
|
tmp = tmp ? tmp : {};
|
||||||
|
tmp[key] = value;
|
||||||
|
uni.setStorageSync(storageKey, tmp);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get: function (key) {
|
||||||
|
let storageData = uni.getStorageSync(storageKey) || {};
|
||||||
|
return storageData[key] || "";
|
||||||
|
},
|
||||||
|
remove: function (key) {
|
||||||
|
let storageData = uni.getStorageSync(storageKey) || {};
|
||||||
|
delete storageData[key];
|
||||||
|
uni.setStorageSync(storageKey, storageData);
|
||||||
|
},
|
||||||
|
clean: function () {
|
||||||
|
uni.removeStorageSync(storageKey);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default storage;
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import config from "@/config";
|
||||||
|
import { getToken } from "@/utils/auth";
|
||||||
|
import errorCode from "@/utils/errorCode";
|
||||||
|
import { toast, showConfirm, tansParams } from "@/utils/common";
|
||||||
|
|
||||||
|
let timeout = 10000;
|
||||||
|
const baseUrl = config.baseUrl;
|
||||||
|
|
||||||
|
export default function upload(config) {
|
||||||
|
// 是否需要设置 token
|
||||||
|
const isToken = (config.headers || {}).isToken === false;
|
||||||
|
config.header = config.header || {};
|
||||||
|
if (getToken() && !isToken) {
|
||||||
|
config.header["Authorization"] = "Bearer " + getToken();
|
||||||
|
}
|
||||||
|
// get请求映射params参数
|
||||||
|
if (config.params) {
|
||||||
|
let url = config.url + "?" + tansParams(config.params);
|
||||||
|
url = url.slice(0, -1);
|
||||||
|
config.url = url;
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
uni.uploadFile({
|
||||||
|
timeout: config.timeout || timeout,
|
||||||
|
url: baseUrl + config.url,
|
||||||
|
filePath: config.filePath,
|
||||||
|
name: config.name || "file",
|
||||||
|
header: config.header,
|
||||||
|
formData: config.formData,
|
||||||
|
success: (res) => {
|
||||||
|
let result = JSON.parse(res.data);
|
||||||
|
const code = result.code || 200;
|
||||||
|
const msg = errorCode[code] || result.msg || errorCode["default"];
|
||||||
|
if (code === 200) {
|
||||||
|
resolve(result);
|
||||||
|
} else if (code == 401) {
|
||||||
|
showConfirm(
|
||||||
|
"登录状态已过期,您可以继续留在该页面,或者重新登录?",
|
||||||
|
).then((res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
useUserStore()
|
||||||
|
.logOut()
|
||||||
|
.then((res) => {
|
||||||
|
uni.reLaunch({ url: "/pages/login/login" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reject("无效的会话,或者会话已过期,请重新登录。");
|
||||||
|
} else if (code === 500) {
|
||||||
|
toast(msg);
|
||||||
|
reject("500");
|
||||||
|
} else if (code !== 200) {
|
||||||
|
toast(msg);
|
||||||
|
reject(code);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (error) => {
|
||||||
|
let { message } = error;
|
||||||
|
if (message == "Network Error") {
|
||||||
|
message = "后端接口连接异常";
|
||||||
|
} else if (message.includes("timeout")) {
|
||||||
|
message = "系统接口请求超时";
|
||||||
|
} else if (message.includes("Request failed with status code")) {
|
||||||
|
message = "系统接口" + message.substr(message.length - 3) + "异常";
|
||||||
|
}
|
||||||
|
toast(message);
|
||||||
|
reject(error);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/**
|
||||||
|
* 路径匹配器
|
||||||
|
* @param {string} pattern
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isPathMatch(pattern, path) {
|
||||||
|
const regexPattern = pattern
|
||||||
|
.replace(/\//g, "\\/")
|
||||||
|
.replace(/\*\*/g, ".*")
|
||||||
|
.replace(/\*/g, "[^\\/]*");
|
||||||
|
const regex = new RegExp(`^${regexPattern}$`);
|
||||||
|
return regex.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断value字符串是否为空
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isEmpty(value) {
|
||||||
|
if (
|
||||||
|
value == null ||
|
||||||
|
value == "" ||
|
||||||
|
value == undefined ||
|
||||||
|
value == "undefined"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断url是否是http或https
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isHttp(url) {
|
||||||
|
return url.indexOf("http://") !== -1 || url.indexOf("https://") !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断path是否为外链
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isExternal(path) {
|
||||||
|
return /^(https?:|mailto:|tel:)/.test(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validUsername(str) {
|
||||||
|
const valid_map = ["admin", "editor"];
|
||||||
|
return valid_map.indexOf(str.trim()) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validURL(url) {
|
||||||
|
const reg =
|
||||||
|
/^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/;
|
||||||
|
return reg.test(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validLowerCase(str) {
|
||||||
|
const reg = /^[a-z]+$/;
|
||||||
|
return reg.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validUpperCase(str) {
|
||||||
|
const reg = /^[A-Z]+$/;
|
||||||
|
return reg.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validAlphabets(str) {
|
||||||
|
const reg = /^[A-Za-z]+$/;
|
||||||
|
return reg.test(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} email
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function validEmail(email) {
|
||||||
|
const reg =
|
||||||
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
return reg.test(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} str
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isString(str) {
|
||||||
|
return typeof str === "string" || str instanceof String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} arg
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
export function isArray(arg) {
|
||||||
|
if (typeof Array.isArray === "undefined") {
|
||||||
|
return Object.prototype.toString.call(arg) === "[object Array]";
|
||||||
|
}
|
||||||
|
return Array.isArray(arg);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { icebreaker } from "@icebreakers/stylelint-config";
|
||||||
|
|
||||||
|
export default icebreaker();
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
import { getIconCollections, iconsPlugin } from "@egoist/tailwindcss-icons";
|
||||||
|
import cssMacro from "weapp-tailwindcss/css-macro";
|
||||||
|
import { isMp } from "./platform";
|
||||||
|
|
||||||
|
export default <Config>{
|
||||||
|
content: ["./index.html", "./src/**/*.{html,js,ts,jsx,tsx,vue}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
// colors: {
|
||||||
|
// // 你可以在这里进行颜色的扩展
|
||||||
|
// primary: {
|
||||||
|
// 'DEFAULT': 'var(--color-primary, #0089FF)',
|
||||||
|
// 'light-3': 'var(--color-primary-light-3, rgb(85, 199, 255))',
|
||||||
|
// 'light-5': 'var(--color-primary-light-5, rgb(130, 217, 255))',
|
||||||
|
// 'light-7': 'var(--color-primary-light-7, rgb(175, 235, 255))',
|
||||||
|
// 'light-9': 'var(--color-primary-light-9, rgb(219, 252, 255))',
|
||||||
|
// 'dark-2': 'var(--color-primary-dark-2, rgb(0, 135, 204))',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// https://tw.icebreaker.top/docs/quick-start/uni-app-css-macro
|
||||||
|
plugins: [
|
||||||
|
cssMacro({
|
||||||
|
variantsMap: {
|
||||||
|
wx: "MP-WEIXIN",
|
||||||
|
"-wx": {
|
||||||
|
value: "MP-WEIXIN",
|
||||||
|
negative: true,
|
||||||
|
},
|
||||||
|
// 定义多个条件判断
|
||||||
|
// mv: {
|
||||||
|
// value: 'H5 || MP-WEIXIN'
|
||||||
|
// },
|
||||||
|
// '-mv': {
|
||||||
|
// value: 'H5 || MP-WEIXIN',
|
||||||
|
// negative: true
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
iconsPlugin({
|
||||||
|
// 在这里可以选择你要使用的 icon, 更多详见:
|
||||||
|
// https://icon-sets.iconify.design/
|
||||||
|
collections: getIconCollections(["svg-spinners", "mdi"]),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
corePlugins: {
|
||||||
|
// 小程序去使用 h5 的 preflight 和响应式 container 没有意义
|
||||||
|
preflight: !isMp,
|
||||||
|
container: !isMp,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"outDir": "true",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"types": ["@dcloudio/types"],
|
||||||
|
"allowJs": true,
|
||||||
|
"strict": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import uni from "@dcloudio/vite-plugin-uni";
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { UnifiedViteWeappTailwindcssPlugin } from "weapp-tailwindcss/vite";
|
||||||
|
import { WeappTailwindcssDisabled } from "./platform";
|
||||||
|
import postcssPlugins from "./postcss.config";
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(async () => {
|
||||||
|
// 新版本的 unplugin-auto-import 改成了只有 esm 格式的产物,而 uni-app 目前必须 cjs 格式
|
||||||
|
// 所以需要改成动态 import 的写法来进行引入
|
||||||
|
// 详见 https://github.com/sonofmagic/uni-app-vite-vue3-tailwind-vscode-template/issues/29
|
||||||
|
const { default: AutoImport } = await import("unplugin-auto-import/vite");
|
||||||
|
return {
|
||||||
|
// uvtw 一定要放在 uni 后面
|
||||||
|
plugins: [
|
||||||
|
uni(),
|
||||||
|
UnifiedViteWeappTailwindcssPlugin({
|
||||||
|
rem2rpx: true,
|
||||||
|
disabled: WeappTailwindcssDisabled,
|
||||||
|
}),
|
||||||
|
AutoImport({
|
||||||
|
imports: ["vue", "uni-app", "pinia"],
|
||||||
|
dts: "./src/auto-imports.d.ts",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
// 内联 postcss 注册 tailwindcss
|
||||||
|
css: {
|
||||||
|
postcss: {
|
||||||
|
plugins: postcssPlugins,
|
||||||
|
},
|
||||||
|
// https://vitejs.dev/config/shared-options.html#css-preprocessoroptions
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
silenceDeprecations: ["legacy-js-api"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -10,13 +10,21 @@ APP_HOST = '0.0.0.0'
|
|||||||
# 应用端口
|
# 应用端口
|
||||||
APP_PORT = 9099
|
APP_PORT = 9099
|
||||||
# 应用版本
|
# 应用版本
|
||||||
APP_VERSION= '1.8.0'
|
APP_VERSION= '1.9.0'
|
||||||
# 应用是否开启热重载
|
# 应用是否开启热重载
|
||||||
APP_RELOAD = true
|
APP_RELOAD = true
|
||||||
|
# 应用工作进程数
|
||||||
|
APP_WORKERS = 1
|
||||||
# 应用是否开启IP归属区域查询
|
# 应用是否开启IP归属区域查询
|
||||||
APP_IP_LOCATION_QUERY = true
|
APP_IP_LOCATION_QUERY = true
|
||||||
# 应用是否允许账号同时登录
|
# 应用是否允许账号同时登录
|
||||||
APP_SAME_TIME_LOGIN = true
|
APP_SAME_TIME_LOGIN = true
|
||||||
|
# 应用是否为演示模式
|
||||||
|
APP_DEMO_MODE = false
|
||||||
|
# 应用是否禁用Swagger文档
|
||||||
|
APP_DISABLE_SWAGGER = false
|
||||||
|
# 应用是否禁用ReDoc文档
|
||||||
|
APP_DISABLE_REDOC = false
|
||||||
|
|
||||||
# -------- Jwt配置 --------
|
# -------- Jwt配置 --------
|
||||||
# Jwt秘钥
|
# Jwt秘钥
|
||||||
@@ -63,4 +71,50 @@ REDIS_USERNAME = ''
|
|||||||
# Redis密码
|
# Redis密码
|
||||||
REDIS_PASSWORD = ''
|
REDIS_PASSWORD = ''
|
||||||
# Redis数据库
|
# Redis数据库
|
||||||
REDIS_DATABASE = 2
|
REDIS_DATABASE = 2
|
||||||
|
|
||||||
|
# -------- 日志配置 --------
|
||||||
|
# Redis Stream Key
|
||||||
|
LOG_STREAM_KEY = 'log:stream'
|
||||||
|
# Redis Stream 消费组名称
|
||||||
|
LOG_STREAM_GROUP = 'log_aggregator'
|
||||||
|
# Redis Stream 消费者名称前缀
|
||||||
|
LOG_STREAM_CONSUMER_PREFIX = 'worker'
|
||||||
|
# 每次读取的最大消息数量
|
||||||
|
LOG_STREAM_BATCH_SIZE = 100
|
||||||
|
# 阻塞读取等待时间(毫秒)
|
||||||
|
LOG_STREAM_BLOCK_MS = 2000
|
||||||
|
# Stream 最大长度(近似裁剪)
|
||||||
|
LOG_STREAM_MAXLEN = 100000
|
||||||
|
# Pending 回收最小空闲时间(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_IDLE_MS = 60000
|
||||||
|
# Pending 回收检查间隔(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_INTERVAL_MS = 5000
|
||||||
|
# 每次回收的最大消息数量
|
||||||
|
LOG_STREAM_CLAIM_BATCH_SIZE = 100
|
||||||
|
# 去重 Key 过期时间(秒)
|
||||||
|
LOG_STREAM_DEDUP_TTL = 3600
|
||||||
|
# 去重 Key 前缀
|
||||||
|
LOG_STREAM_DEDUP_PREFIX = 'log:dedup'
|
||||||
|
# stdout 输出是否为 JSON
|
||||||
|
LOGURU_JSON = false
|
||||||
|
# Loguru 最低输出级别
|
||||||
|
LOGURU_LEVEL = 'INFO'
|
||||||
|
# 是否输出到 stdout
|
||||||
|
LOGURU_STDOUT = true
|
||||||
|
# 是否启用文件日志
|
||||||
|
LOG_FILE_ENABLED = true
|
||||||
|
# 文件日志根目录
|
||||||
|
LOG_FILE_BASE_DIR = 'logs'
|
||||||
|
# 文件滚动策略
|
||||||
|
LOGURU_ROTATION = '50MB'
|
||||||
|
# 文件保留策略
|
||||||
|
LOGURU_RETENTION = '30 days'
|
||||||
|
# 文件压缩格式
|
||||||
|
LOGURU_COMPRESSION = 'zip'
|
||||||
|
# 实例标识(用于区分实例)
|
||||||
|
LOG_INSTANCE_ID = 'dev'
|
||||||
|
# 服务名称(用于统一标识服务)
|
||||||
|
LOG_SERVICE_NAME = 'ruoyi-fastapi-backend'
|
||||||
|
# Worker 标识(auto 自动生成)
|
||||||
|
LOG_WORKER_ID = 'auto'
|
||||||
|
|||||||
@@ -10,13 +10,21 @@ APP_HOST = '0.0.0.0'
|
|||||||
# 应用端口
|
# 应用端口
|
||||||
APP_PORT = 9099
|
APP_PORT = 9099
|
||||||
# 应用版本
|
# 应用版本
|
||||||
APP_VERSION= '1.8.0'
|
APP_VERSION= '1.9.0'
|
||||||
# 应用是否开启热重载
|
# 应用是否开启热重载
|
||||||
APP_RELOAD = false
|
APP_RELOAD = false
|
||||||
|
# 应用工作进程数
|
||||||
|
APP_WORKERS = 1
|
||||||
# 应用是否开启IP归属区域查询
|
# 应用是否开启IP归属区域查询
|
||||||
APP_IP_LOCATION_QUERY = true
|
APP_IP_LOCATION_QUERY = true
|
||||||
# 应用是否允许账号同时登录
|
# 应用是否允许账号同时登录
|
||||||
APP_SAME_TIME_LOGIN = true
|
APP_SAME_TIME_LOGIN = true
|
||||||
|
# 应用是否为演示模式
|
||||||
|
APP_DEMO_MODE = false
|
||||||
|
# 应用是否禁用Swagger文档
|
||||||
|
APP_DISABLE_SWAGGER = true
|
||||||
|
# 应用是否禁用ReDoc文档
|
||||||
|
APP_DISABLE_REDOC = true
|
||||||
|
|
||||||
# -------- Jwt配置 --------
|
# -------- Jwt配置 --------
|
||||||
# Jwt秘钥
|
# Jwt秘钥
|
||||||
@@ -63,4 +71,50 @@ REDIS_USERNAME = ''
|
|||||||
# Redis密码
|
# Redis密码
|
||||||
REDIS_PASSWORD = ''
|
REDIS_PASSWORD = ''
|
||||||
# Redis数据库
|
# Redis数据库
|
||||||
REDIS_DATABASE = 2
|
REDIS_DATABASE = 2
|
||||||
|
|
||||||
|
# -------- 日志配置 --------
|
||||||
|
# Redis Stream Key
|
||||||
|
LOG_STREAM_KEY = 'log:stream'
|
||||||
|
# Redis Stream 消费组名称
|
||||||
|
LOG_STREAM_GROUP = 'log_aggregator'
|
||||||
|
# Redis Stream 消费者名称前缀
|
||||||
|
LOG_STREAM_CONSUMER_PREFIX = 'worker'
|
||||||
|
# 每次读取的最大消息数量
|
||||||
|
LOG_STREAM_BATCH_SIZE = 100
|
||||||
|
# 阻塞读取等待时间(毫秒)
|
||||||
|
LOG_STREAM_BLOCK_MS = 2000
|
||||||
|
# Stream 最大长度(近似裁剪)
|
||||||
|
LOG_STREAM_MAXLEN = 100000
|
||||||
|
# Pending 回收最小空闲时间(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_IDLE_MS = 60000
|
||||||
|
# Pending 回收检查间隔(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_INTERVAL_MS = 5000
|
||||||
|
# 每次回收的最大消息数量
|
||||||
|
LOG_STREAM_CLAIM_BATCH_SIZE = 100
|
||||||
|
# 去重 Key 过期时间(秒)
|
||||||
|
LOG_STREAM_DEDUP_TTL = 3600
|
||||||
|
# 去重 Key 前缀
|
||||||
|
LOG_STREAM_DEDUP_PREFIX = 'log:dedup'
|
||||||
|
# stdout 输出是否为 JSON
|
||||||
|
LOGURU_JSON = false
|
||||||
|
# Loguru 最低输出级别
|
||||||
|
LOGURU_LEVEL = 'INFO'
|
||||||
|
# 是否输出到 stdout
|
||||||
|
LOGURU_STDOUT = true
|
||||||
|
# 是否启用文件日志
|
||||||
|
LOG_FILE_ENABLED = true
|
||||||
|
# 文件日志根目录
|
||||||
|
LOG_FILE_BASE_DIR = 'logs'
|
||||||
|
# 文件滚动策略
|
||||||
|
LOGURU_ROTATION = '50MB'
|
||||||
|
# 文件保留策略
|
||||||
|
LOGURU_RETENTION = '30 days'
|
||||||
|
# 文件压缩格式
|
||||||
|
LOGURU_COMPRESSION = 'zip'
|
||||||
|
# 实例标识(用于区分实例)
|
||||||
|
LOG_INSTANCE_ID = 'dockermy'
|
||||||
|
# 服务名称(用于统一标识服务)
|
||||||
|
LOG_SERVICE_NAME = 'ruoyi-fastapi-backend'
|
||||||
|
# Worker 标识(auto 自动生成)
|
||||||
|
LOG_WORKER_ID = 'auto'
|
||||||
|
|||||||
@@ -10,13 +10,21 @@ APP_HOST = '0.0.0.0'
|
|||||||
# 应用端口
|
# 应用端口
|
||||||
APP_PORT = 9099
|
APP_PORT = 9099
|
||||||
# 应用版本
|
# 应用版本
|
||||||
APP_VERSION= '1.8.0'
|
APP_VERSION= '1.9.0'
|
||||||
# 应用是否开启热重载
|
# 应用是否开启热重载
|
||||||
APP_RELOAD = false
|
APP_RELOAD = false
|
||||||
|
# 应用工作进程数
|
||||||
|
APP_WORKERS = 1
|
||||||
# 应用是否开启IP归属区域查询
|
# 应用是否开启IP归属区域查询
|
||||||
APP_IP_LOCATION_QUERY = true
|
APP_IP_LOCATION_QUERY = true
|
||||||
|
# 应用是否为演示模式
|
||||||
|
APP_DEMO_MODE = false
|
||||||
# 应用是否允许账号同时登录
|
# 应用是否允许账号同时登录
|
||||||
APP_SAME_TIME_LOGIN = true
|
APP_SAME_TIME_LOGIN = true
|
||||||
|
# 应用是否禁用Swagger文档
|
||||||
|
APP_DISABLE_SWAGGER = true
|
||||||
|
# 应用是否禁用ReDoc文档
|
||||||
|
APP_DISABLE_REDOC = true
|
||||||
|
|
||||||
# -------- Jwt配置 --------
|
# -------- Jwt配置 --------
|
||||||
# Jwt秘钥
|
# Jwt秘钥
|
||||||
@@ -63,4 +71,50 @@ REDIS_USERNAME = ''
|
|||||||
# Redis密码
|
# Redis密码
|
||||||
REDIS_PASSWORD = ''
|
REDIS_PASSWORD = ''
|
||||||
# Redis数据库
|
# Redis数据库
|
||||||
REDIS_DATABASE = 2
|
REDIS_DATABASE = 2
|
||||||
|
|
||||||
|
# -------- 日志配置 --------
|
||||||
|
# Redis Stream Key
|
||||||
|
LOG_STREAM_KEY = 'log:stream'
|
||||||
|
# Redis Stream 消费组名称
|
||||||
|
LOG_STREAM_GROUP = 'log_aggregator'
|
||||||
|
# Redis Stream 消费者名称前缀
|
||||||
|
LOG_STREAM_CONSUMER_PREFIX = 'worker'
|
||||||
|
# 每次读取的最大消息数量
|
||||||
|
LOG_STREAM_BATCH_SIZE = 100
|
||||||
|
# 阻塞读取等待时间(毫秒)
|
||||||
|
LOG_STREAM_BLOCK_MS = 2000
|
||||||
|
# Stream 最大长度(近似裁剪)
|
||||||
|
LOG_STREAM_MAXLEN = 100000
|
||||||
|
# Pending 回收最小空闲时间(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_IDLE_MS = 60000
|
||||||
|
# Pending 回收检查间隔(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_INTERVAL_MS = 5000
|
||||||
|
# 每次回收的最大消息数量
|
||||||
|
LOG_STREAM_CLAIM_BATCH_SIZE = 100
|
||||||
|
# 去重 Key 过期时间(秒)
|
||||||
|
LOG_STREAM_DEDUP_TTL = 3600
|
||||||
|
# 去重 Key 前缀
|
||||||
|
LOG_STREAM_DEDUP_PREFIX = 'log:dedup'
|
||||||
|
# stdout 输出是否为 JSON
|
||||||
|
LOGURU_JSON = false
|
||||||
|
# Loguru 最低输出级别
|
||||||
|
LOGURU_LEVEL = 'INFO'
|
||||||
|
# 是否输出到 stdout
|
||||||
|
LOGURU_STDOUT = true
|
||||||
|
# 是否启用文件日志
|
||||||
|
LOG_FILE_ENABLED = true
|
||||||
|
# 文件日志根目录
|
||||||
|
LOG_FILE_BASE_DIR = 'logs'
|
||||||
|
# 文件滚动策略
|
||||||
|
LOGURU_ROTATION = '50MB'
|
||||||
|
# 文件保留策略
|
||||||
|
LOGURU_RETENTION = '30 days'
|
||||||
|
# 文件压缩格式
|
||||||
|
LOGURU_COMPRESSION = 'zip'
|
||||||
|
# 实例标识(用于区分实例)
|
||||||
|
LOG_INSTANCE_ID = 'dockerpg'
|
||||||
|
# 服务名称(用于统一标识服务)
|
||||||
|
LOG_SERVICE_NAME = 'ruoyi-fastapi-backend'
|
||||||
|
# Worker 标识(auto 自动生成)
|
||||||
|
LOG_WORKER_ID = 'auto'
|
||||||
|
|||||||
@@ -10,13 +10,21 @@ APP_HOST = '0.0.0.0'
|
|||||||
# 应用端口
|
# 应用端口
|
||||||
APP_PORT = 9099
|
APP_PORT = 9099
|
||||||
# 应用版本
|
# 应用版本
|
||||||
APP_VERSION= '1.8.0'
|
APP_VERSION= '1.9.0'
|
||||||
# 应用是否开启热重载
|
# 应用是否开启热重载
|
||||||
APP_RELOAD = false
|
APP_RELOAD = false
|
||||||
|
# 应用工作进程数
|
||||||
|
APP_WORKERS = 1
|
||||||
# 应用是否开启IP归属区域查询
|
# 应用是否开启IP归属区域查询
|
||||||
APP_IP_LOCATION_QUERY = true
|
APP_IP_LOCATION_QUERY = true
|
||||||
# 应用是否允许账号同时登录
|
# 应用是否允许账号同时登录
|
||||||
APP_SAME_TIME_LOGIN = true
|
APP_SAME_TIME_LOGIN = true
|
||||||
|
# 应用是否为演示模式
|
||||||
|
APP_DEMO_MODE = false
|
||||||
|
# 应用是否禁用Swagger文档
|
||||||
|
APP_DISABLE_SWAGGER = true
|
||||||
|
# 应用是否禁用ReDoc文档
|
||||||
|
APP_DISABLE_REDOC = true
|
||||||
|
|
||||||
# -------- Jwt配置 --------
|
# -------- Jwt配置 --------
|
||||||
# Jwt秘钥
|
# Jwt秘钥
|
||||||
@@ -63,4 +71,50 @@ REDIS_USERNAME = ''
|
|||||||
# Redis密码
|
# Redis密码
|
||||||
REDIS_PASSWORD = ''
|
REDIS_PASSWORD = ''
|
||||||
# Redis数据库
|
# Redis数据库
|
||||||
REDIS_DATABASE = 2
|
REDIS_DATABASE = 2
|
||||||
|
|
||||||
|
# -------- 日志配置 --------
|
||||||
|
# Redis Stream Key
|
||||||
|
LOG_STREAM_KEY = 'log:stream'
|
||||||
|
# Redis Stream 消费组名称
|
||||||
|
LOG_STREAM_GROUP = 'log_aggregator'
|
||||||
|
# Redis Stream 消费者名称前缀
|
||||||
|
LOG_STREAM_CONSUMER_PREFIX = 'worker'
|
||||||
|
# 每次读取的最大消息数量
|
||||||
|
LOG_STREAM_BATCH_SIZE = 100
|
||||||
|
# 阻塞读取等待时间(毫秒)
|
||||||
|
LOG_STREAM_BLOCK_MS = 2000
|
||||||
|
# Stream 最大长度(近似裁剪)
|
||||||
|
LOG_STREAM_MAXLEN = 100000
|
||||||
|
# Pending 回收最小空闲时间(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_IDLE_MS = 60000
|
||||||
|
# Pending 回收检查间隔(毫秒)
|
||||||
|
LOG_STREAM_CLAIM_INTERVAL_MS = 5000
|
||||||
|
# 每次回收的最大消息数量
|
||||||
|
LOG_STREAM_CLAIM_BATCH_SIZE = 100
|
||||||
|
# 去重 Key 过期时间(秒)
|
||||||
|
LOG_STREAM_DEDUP_TTL = 3600
|
||||||
|
# 去重 Key 前缀
|
||||||
|
LOG_STREAM_DEDUP_PREFIX = 'log:dedup'
|
||||||
|
# stdout 输出是否为 JSON
|
||||||
|
LOGURU_JSON = false
|
||||||
|
# Loguru 最低输出级别
|
||||||
|
LOGURU_LEVEL = 'INFO'
|
||||||
|
# 是否输出到 stdout
|
||||||
|
LOGURU_STDOUT = true
|
||||||
|
# 是否启用文件日志
|
||||||
|
LOG_FILE_ENABLED = true
|
||||||
|
# 文件日志根目录
|
||||||
|
LOG_FILE_BASE_DIR = 'logs'
|
||||||
|
# 文件滚动策略
|
||||||
|
LOGURU_ROTATION = '50MB'
|
||||||
|
# 文件保留策略
|
||||||
|
LOGURU_RETENTION = '30 days'
|
||||||
|
# 文件压缩格式
|
||||||
|
LOGURU_COMPRESSION = 'zip'
|
||||||
|
# 实例标识(用于区分实例)
|
||||||
|
LOG_INSTANCE_ID = 'prod'
|
||||||
|
# 服务名称(用于统一标识服务)
|
||||||
|
LOG_SERVICE_NAME = 'ruoyi-fastapi-backend'
|
||||||
|
# Worker 标识(auto 自动生成)
|
||||||
|
LOG_WORKER_ID = 'auto'
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable, MutableMapping
|
||||||
from logging.config import fileConfig
|
from logging.config import fileConfig
|
||||||
from typing import Optional, Union
|
from typing import Literal
|
||||||
|
|
||||||
from alembic import context
|
from alembic import context
|
||||||
from alembic.migration import MigrationContext
|
from alembic.migration import MigrationContext
|
||||||
@@ -65,9 +65,18 @@ def run_migrations_offline() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def do_run_migrations(connection: Connection) -> None:
|
def do_run_migrations(connection: Connection) -> None:
|
||||||
|
def include_name(
|
||||||
|
name: str | None,
|
||||||
|
type_: Literal['schema', 'table', 'column', 'index', 'unique_constraint', 'foreign_key_constraint'],
|
||||||
|
parent_names: MutableMapping[Literal['schema_name', 'table_name', 'schema_qualified_table_name'], str | None],
|
||||||
|
) -> bool:
|
||||||
|
if type_ == 'table':
|
||||||
|
return name in target_metadata.tables
|
||||||
|
return True
|
||||||
|
|
||||||
def process_revision_directives(
|
def process_revision_directives(
|
||||||
context: MigrationContext,
|
context: MigrationContext,
|
||||||
revision: Union[str, Iterable[Optional[str]], Iterable[str]],
|
revision: str | Iterable[str | None] | Iterable[str],
|
||||||
directives: list[MigrationScript],
|
directives: list[MigrationScript],
|
||||||
) -> None:
|
) -> None:
|
||||||
script = directives[0]
|
script = directives[0]
|
||||||
@@ -88,6 +97,7 @@ def do_run_migrations(connection: Connection) -> None:
|
|||||||
compare_type=True,
|
compare_type=True,
|
||||||
compare_server_default=True,
|
compare_server_default=True,
|
||||||
transaction_per_migration=True,
|
transaction_per_migration=True,
|
||||||
|
include_name=include_name,
|
||||||
process_revision_directives=process_revision_directives,
|
process_revision_directives=process_revision_directives,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ import uvicorn
|
|||||||
from config.env import AppConfig
|
from config.env import AppConfig
|
||||||
from server import create_app
|
from server import create_app
|
||||||
|
|
||||||
app = create_app()
|
if __name__ != '__main__':
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
uvicorn.run(
|
uvicorn.run(
|
||||||
app='app:app',
|
app='server:create_app',
|
||||||
host=AppConfig.app_host,
|
host=AppConfig.app_host,
|
||||||
port=AppConfig.app_port,
|
port=AppConfig.app_port,
|
||||||
root_path=AppConfig.app_root_path,
|
root_path=AppConfig.app_root_path,
|
||||||
reload=AppConfig.app_reload,
|
reload=AppConfig.app_reload,
|
||||||
|
workers=AppConfig.app_workers,
|
||||||
|
factory=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable, Callable
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, Callable, Literal, Optional, TypeVar
|
from typing import Any, Literal, TypeVar
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from async_lru import alru_cache
|
from async_lru import alru_cache
|
||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
|
from fastapi.responses import JSONResponse, ORJSONResponse, UJSONResponse
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
from starlette.status import HTTP_200_OK
|
from starlette.status import HTTP_200_OK
|
||||||
from typing_extensions import ParamSpec
|
from typing_extensions import ParamSpec
|
||||||
from user_agents import parse
|
from user_agents import parse
|
||||||
@@ -20,7 +19,7 @@ from common.enums import BusinessType
|
|||||||
from config.env import AppConfig
|
from config.env import AppConfig
|
||||||
from exceptions.exception import LoginException, ServiceException, ServiceWarning
|
from exceptions.exception import LoginException, ServiceException, ServiceWarning
|
||||||
from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel
|
from module_admin.entity.vo.log_vo import LogininforModel, OperLogModel
|
||||||
from module_admin.service.log_service import LoginLogService, OperationLogService
|
from module_admin.service.log_service import LogQueueService
|
||||||
from utils.dependency_util import DependencyUtil
|
from utils.dependency_util import DependencyUtil
|
||||||
from utils.log_util import logger
|
from utils.log_util import logger
|
||||||
from utils.response_util import ResponseUtil
|
from utils.response_util import ResponseUtil
|
||||||
@@ -38,7 +37,7 @@ class Log:
|
|||||||
self,
|
self,
|
||||||
title: str,
|
title: str,
|
||||||
business_type: BusinessType,
|
business_type: BusinessType,
|
||||||
log_type: Optional[Literal['login', 'operation']] = 'operation',
|
log_type: Literal['login', 'operation'] | None = 'operation',
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
日志装饰器
|
日志装饰器
|
||||||
@@ -63,8 +62,6 @@ class Log:
|
|||||||
request_name_list = get_function_parameters_name_by_type(func, Request)
|
request_name_list = get_function_parameters_name_by_type(func, Request)
|
||||||
request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs)
|
request = get_function_parameters_value_by_name(func, request_name_list[0], *args, **kwargs)
|
||||||
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用Log装饰器')
|
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用Log装饰器')
|
||||||
session_name_list = get_function_parameters_name_by_type(func, AsyncSession)
|
|
||||||
query_db = get_function_parameters_value_by_name(func, session_name_list[0], *args, **kwargs)
|
|
||||||
request_method = request.method
|
request_method = request.method
|
||||||
user_agent = request.headers.get('User-Agent')
|
user_agent = request.headers.get('User-Agent')
|
||||||
# 获取操作类型
|
# 获取操作类型
|
||||||
@@ -122,7 +119,7 @@ class Log:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await LoginLogService.add_login_log_services(query_db, LogininforModel(**login_log))
|
await LogQueueService.enqueue_login_log(request, LogininforModel(**login_log), func_path)
|
||||||
else:
|
else:
|
||||||
current_user = RequestContext.get_current_user()
|
current_user = RequestContext.get_current_user()
|
||||||
oper_name = current_user.user.user_name
|
oper_name = current_user.user.user_name
|
||||||
@@ -145,7 +142,7 @@ class Log:
|
|||||||
operTime=oper_time,
|
operTime=oper_time,
|
||||||
costTime=int(cost_time),
|
costTime=int(cost_time),
|
||||||
)
|
)
|
||||||
await OperationLogService.add_operation_log_services(query_db, operation_log)
|
await LogQueueService.enqueue_operation_log(request, operation_log, func_path)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from fastapi import Depends, Request, params
|
from fastapi import Depends, Request, params
|
||||||
|
from sqlalchemy import ColumnElement, func, or_, select
|
||||||
|
|
||||||
from common.context import RequestContext
|
from common.context import RequestContext
|
||||||
|
from config.database import Base
|
||||||
|
from module_admin.entity.do.dept_do import SysDept
|
||||||
|
from module_admin.entity.do.role_do import SysRoleDept
|
||||||
from utils.dependency_util import DependencyUtil
|
from utils.dependency_util import DependencyUtil
|
||||||
|
|
||||||
|
|
||||||
@@ -19,25 +21,22 @@ class GetDataScope:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
query_alias: Optional[str] = '',
|
query_alias: Base,
|
||||||
db_alias: Optional[str] = 'db',
|
user_alias: str | None = 'user_id',
|
||||||
user_alias: Optional[str] = 'user_id',
|
dept_alias: str | None = 'dept_id',
|
||||||
dept_alias: Optional[str] = 'dept_id',
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
获取当前用户数据权限对应的查询sql语句
|
获取当前用户数据权限对应的查询sql语句
|
||||||
|
|
||||||
:param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为''
|
:param query_alias: 所要查询表对应的sqlalchemy模型类,不可为空
|
||||||
:param db_alias: orm对象别名,默认为'db'
|
|
||||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||||
"""
|
"""
|
||||||
self.query_alias = query_alias
|
self.query_alias = query_alias
|
||||||
self.db_alias = db_alias
|
|
||||||
self.user_alias = user_alias
|
self.user_alias = user_alias
|
||||||
self.dept_alias = dept_alias
|
self.dept_alias = dept_alias
|
||||||
|
|
||||||
def __call__(self, request: Request) -> str:
|
def __call__(self, request: Request) -> ColumnElement:
|
||||||
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用GetDataScope依赖项')
|
DependencyUtil.check_exclude_routes(request, err_msg='当前路由不在认证规则内,不可使用GetDataScope依赖项')
|
||||||
current_user = RequestContext.get_current_user()
|
current_user = RequestContext.get_current_user()
|
||||||
user_id = current_user.user.user_id
|
user_id = current_user.user.user_id
|
||||||
@@ -48,50 +47,66 @@ class GetDataScope:
|
|||||||
param_sql_list = []
|
param_sql_list = []
|
||||||
for role in current_user.user.role:
|
for role in current_user.user.role:
|
||||||
if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL:
|
if current_user.user.admin or role.data_scope == self.DATA_SCOPE_ALL:
|
||||||
param_sql_list = ['1 == 1']
|
param_sql_list = [True]
|
||||||
break
|
break
|
||||||
if role.data_scope == self.DATA_SCOPE_CUSTOM:
|
if role.data_scope == self.DATA_SCOPE_CUSTOM:
|
||||||
if len(custom_data_scope_role_id_list) > 1:
|
if len(custom_data_scope_role_id_list) > 1:
|
||||||
param_sql_list.append(
|
param_sql_list.append(
|
||||||
f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_({custom_data_scope_role_id_list}))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
getattr(self.query_alias, self.dept_alias).in_(
|
||||||
|
select(SysRoleDept.dept_id).where(SysRoleDept.role_id.in_(custom_data_scope_role_id_list))
|
||||||
|
)
|
||||||
|
if hasattr(self.query_alias, self.dept_alias)
|
||||||
|
else False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
param_sql_list.append(
|
param_sql_list.append(
|
||||||
f"{self.query_alias}.{self.dept_alias}.in_(select(SysRoleDept.dept_id).where(SysRoleDept.role_id == {role.role_id})) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
getattr(self.query_alias, self.dept_alias).in_(
|
||||||
|
select(SysRoleDept.dept_id).where(SysRoleDept.role_id == role.role_id)
|
||||||
|
)
|
||||||
|
if hasattr(self.query_alias, self.dept_alias)
|
||||||
|
else False
|
||||||
)
|
)
|
||||||
elif role.data_scope == self.DATA_SCOPE_DEPT:
|
elif role.data_scope == self.DATA_SCOPE_DEPT:
|
||||||
param_sql_list.append(
|
param_sql_list.append(
|
||||||
f"{self.query_alias}.{self.dept_alias} == {dept_id} if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
getattr(self.query_alias, self.dept_alias) == dept_id
|
||||||
|
if hasattr(self.query_alias, self.dept_alias)
|
||||||
|
else False
|
||||||
)
|
)
|
||||||
elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD:
|
elif role.data_scope == self.DATA_SCOPE_DEPT_AND_CHILD:
|
||||||
param_sql_list.append(
|
param_sql_list.append(
|
||||||
f"{self.query_alias}.{self.dept_alias}.in_(select(SysDept.dept_id).where(or_(SysDept.dept_id == {dept_id}, func.find_in_set({dept_id}, SysDept.ancestors)))) if hasattr({self.query_alias}, '{self.dept_alias}') else 1 == 0"
|
getattr(self.query_alias, self.dept_alias).in_(
|
||||||
|
select(SysDept.dept_id).where(
|
||||||
|
or_(SysDept.dept_id == dept_id, func.find_in_set(dept_id, SysDept.ancestors))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if hasattr(self.query_alias, self.dept_alias)
|
||||||
|
else False
|
||||||
)
|
)
|
||||||
elif role.data_scope == self.DATA_SCOPE_SELF:
|
elif role.data_scope == self.DATA_SCOPE_SELF:
|
||||||
param_sql_list.append(
|
param_sql_list.append(
|
||||||
f"{self.query_alias}.{self.user_alias} == {user_id} if hasattr({self.query_alias}, '{self.user_alias}') else 1 == 0"
|
getattr(self.query_alias, self.user_alias) == user_id
|
||||||
|
if hasattr(self.query_alias, self.user_alias)
|
||||||
|
else False
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
param_sql_list.append('1 == 0')
|
param_sql_list.append(False)
|
||||||
param_sql_list = list(dict.fromkeys(param_sql_list))
|
param_sql_list = list(dict.fromkeys(param_sql_list))
|
||||||
param_sql = f'or_({", ".join(param_sql_list)})'
|
param_sql = or_(*param_sql_list)
|
||||||
|
|
||||||
return param_sql
|
return param_sql
|
||||||
|
|
||||||
|
|
||||||
def DataScopeDependency( # noqa: N802
|
def DataScopeDependency( # noqa: N802
|
||||||
query_alias: Optional[str] = '',
|
query_alias: Base,
|
||||||
db_alias: Optional[str] = 'db',
|
user_alias: str | None = 'user_id',
|
||||||
user_alias: Optional[str] = 'user_id',
|
dept_alias: str | None = 'dept_id',
|
||||||
dept_alias: Optional[str] = 'dept_id',
|
|
||||||
) -> params.Depends:
|
) -> params.Depends:
|
||||||
"""
|
"""
|
||||||
当前用户数据权限依赖
|
当前用户数据权限依赖
|
||||||
|
|
||||||
:param query_alias: 所要查询表对应的sqlalchemy模型名称,默认为''
|
:param query_alias: 所要查询表对应的sqlalchemy模型类,不可为空
|
||||||
:param db_alias: orm对象别名,默认为'db'
|
|
||||||
:param user_alias: 用户id字段别名,默认为'user_id'
|
:param user_alias: 用户id字段别名,默认为'user_id'
|
||||||
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
:param dept_alias: 部门id字段别名,默认为'dept_id'
|
||||||
:return: 当前用户数据权限依赖
|
:return: 当前用户数据权限依赖
|
||||||
"""
|
"""
|
||||||
return Depends(GetDataScope(query_alias, db_alias, user_alias, dept_alias))
|
return Depends(GetDataScope(query_alias, user_alias, dept_alias))
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from typing import Union
|
|
||||||
|
|
||||||
from fastapi import Depends, Request, params
|
from fastapi import Depends, Request, params
|
||||||
|
|
||||||
from common.context import RequestContext
|
from common.context import RequestContext
|
||||||
@@ -12,7 +10,7 @@ class CheckUserInterfaceAuth:
|
|||||||
校验当前用户是否具有相应的接口权限
|
校验当前用户是否具有相应的接口权限
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, perm: Union[str, list], is_strict: bool = False) -> None:
|
def __init__(self, perm: str | list, is_strict: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
校验当前用户是否具有相应的接口权限
|
校验当前用户是否具有相应的接口权限
|
||||||
|
|
||||||
@@ -46,7 +44,7 @@ class CheckRoleInterfaceAuth:
|
|||||||
根据角色校验当前用户是否具有相应的接口权限
|
根据角色校验当前用户是否具有相应的接口权限
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, role_key: Union[str, list], is_strict: bool = False) -> None:
|
def __init__(self, role_key: str | list, is_strict: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
根据角色校验当前用户是否具有相应的接口权限
|
根据角色校验当前用户是否具有相应的接口权限
|
||||||
|
|
||||||
@@ -74,7 +72,7 @@ class CheckRoleInterfaceAuth:
|
|||||||
raise PermissionException(data='', message='该用户无此接口权限')
|
raise PermissionException(data='', message='该用户无此接口权限')
|
||||||
|
|
||||||
|
|
||||||
def UserInterfaceAuthDependency(perm: Union[str, list], is_strict: bool = False) -> params.Depends: # noqa: N802
|
def UserInterfaceAuthDependency(perm: str | list, is_strict: bool = False) -> params.Depends: # noqa: N802
|
||||||
"""
|
"""
|
||||||
根据权限标识校验当前用户接口权限依赖
|
根据权限标识校验当前用户接口权限依赖
|
||||||
|
|
||||||
@@ -85,7 +83,7 @@ def UserInterfaceAuthDependency(perm: Union[str, list], is_strict: bool = False)
|
|||||||
return Depends(CheckUserInterfaceAuth(perm, is_strict))
|
return Depends(CheckUserInterfaceAuth(perm, is_strict))
|
||||||
|
|
||||||
|
|
||||||
def RoleInterfaceAuthDependency(role_key: Union[str, list], is_strict: bool = False) -> params.Depends: # noqa: N802
|
def RoleInterfaceAuthDependency(role_key: str | list, is_strict: bool = False) -> params.Depends: # noqa: N802
|
||||||
"""
|
"""
|
||||||
根据角色校验当前用户接口权限依赖
|
根据角色校验当前用户接口权限依赖
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import re
|
import re
|
||||||
from typing import Literal, Optional, TypedDict, Union
|
from typing import Literal, TypedDict
|
||||||
|
|
||||||
from fastapi import Depends, Request, params
|
from fastapi import Depends, Request, params
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
@@ -37,7 +37,7 @@ class PreAuth:
|
|||||||
登录认证前置校验依赖类
|
登录认证前置校验依赖类
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, exclude_routes: Optional[list[ExcludeRoute]] = None) -> None:
|
def __init__(self, exclude_routes: list[ExcludeRoute] | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
初始化登录认证前置校验依赖
|
初始化登录认证前置校验依赖
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ class PreAuth:
|
|||||||
# 添加开始和结束锚点,确保精确匹配
|
# 添加开始和结束锚点,确保精确匹配
|
||||||
return re.compile(f'^{pattern_str}$')
|
return re.compile(f'^{pattern_str}$')
|
||||||
|
|
||||||
async def __call__(self, request: Request, db: AsyncSession = Depends(get_db)) -> Union[CurrentUserModel, None]:
|
async def __call__(self, request: Request, db: AsyncSession = Depends(get_db)) -> CurrentUserModel | None:
|
||||||
"""
|
"""
|
||||||
执行登录认证校验
|
执行登录认证校验
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ class PreAuth:
|
|||||||
return current_user
|
return current_user
|
||||||
|
|
||||||
|
|
||||||
def PreAuthDependency(exclude_routes: Optional[list[ExcludeRoute]] = None) -> params.Depends: # noqa: N802
|
def PreAuthDependency(exclude_routes: list[ExcludeRoute] | None = None) -> params.Depends: # noqa: N802
|
||||||
"""
|
"""
|
||||||
登录认证前置校验依赖
|
登录认证前置校验依赖
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,16 @@ class JobConstant:
|
|||||||
JOB_WHITE_LIST = ['module_task']
|
JOB_WHITE_LIST = ['module_task']
|
||||||
|
|
||||||
|
|
||||||
|
class LockConstant:
|
||||||
|
"""
|
||||||
|
分布式锁常量
|
||||||
|
"""
|
||||||
|
|
||||||
|
APP_STARTUP_LOCK_KEY = 'app:startup:lock'
|
||||||
|
LOCK_EXPIRE_SECONDS = 60
|
||||||
|
LOCK_RENEWAL_INTERVAL = 20
|
||||||
|
|
||||||
|
|
||||||
class MenuConstant:
|
class MenuConstant:
|
||||||
"""
|
"""
|
||||||
菜单常量
|
菜单常量
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from contextvars import ContextVar, Token
|
from contextvars import ContextVar, Token
|
||||||
from typing import Literal, Optional, Union
|
from typing import Literal
|
||||||
|
|
||||||
from exceptions.exception import LoginException
|
from exceptions.exception import LoginException
|
||||||
from module_admin.entity.vo.user_vo import CurrentUserModel
|
from module_admin.entity.vo.user_vo import CurrentUserModel
|
||||||
@@ -8,14 +8,10 @@ from module_admin.entity.vo.user_vo import CurrentUserModel
|
|||||||
# 定义上下文变量
|
# 定义上下文变量
|
||||||
# 存储当前请求的编译后的排除路由模式列表
|
# 存储当前请求的编译后的排除路由模式列表
|
||||||
current_exclude_patterns: ContextVar[
|
current_exclude_patterns: ContextVar[
|
||||||
Optional[
|
list[dict[str, str | list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']] | re.Pattern]] | None
|
||||||
list[
|
|
||||||
dict[str, Union[str, list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']], re.Pattern]]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
] = ContextVar('current_exclude_patterns', default=None)
|
] = ContextVar('current_exclude_patterns', default=None)
|
||||||
# 存储当前用户信息
|
# 存储当前用户信息
|
||||||
current_user: ContextVar[Optional[CurrentUserModel]] = ContextVar('current_user', default=None)
|
current_user: ContextVar[CurrentUserModel | None] = ContextVar('current_user', default=None)
|
||||||
|
|
||||||
|
|
||||||
class RequestContext:
|
class RequestContext:
|
||||||
@@ -26,7 +22,7 @@ class RequestContext:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def set_current_exclude_patterns(
|
def set_current_exclude_patterns(
|
||||||
exclude_patterns: list[
|
exclude_patterns: list[
|
||||||
dict[str, Union[str, list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']], re.Pattern]]
|
dict[str, str | list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']] | re.Pattern]
|
||||||
],
|
],
|
||||||
) -> Token:
|
) -> Token:
|
||||||
"""
|
"""
|
||||||
@@ -39,7 +35,7 @@ class RequestContext:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_current_exclude_patterns() -> list[
|
def get_current_exclude_patterns() -> list[
|
||||||
dict[str, Union[str, list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']], re.Pattern]]
|
dict[str, str | list[Literal['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS']] | re.Pattern]
|
||||||
]:
|
]:
|
||||||
"""
|
"""
|
||||||
获取当前请求的编译后的排除路由模式列表
|
获取当前请求的编译后的排除路由模式列表
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
|
|
||||||
class BusinessType(Enum):
|
class BusinessType(Enum):
|
||||||
@@ -36,11 +35,11 @@ class RedisInitKeyConfig(Enum):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self) -> Union[str, None]:
|
def key(self) -> str | None:
|
||||||
return self.value.get('key')
|
return self.value.get('key')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def remark(self) -> Union[str, None]:
|
def remark(self) -> str | None:
|
||||||
return self.value.get('remark')
|
return self.value.get('remark')
|
||||||
|
|
||||||
ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'}
|
ACCESS_TOKEN = {'key': 'access_token', 'remark': '登录令牌信息'}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import glob
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Sequence
|
from collections.abc import Callable, Sequence
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Annotated, Any, Callable, Literal, Optional, Union
|
from typing import Annotated, Any, Literal
|
||||||
|
|
||||||
from annotated_doc import Doc
|
from annotated_doc import Doc
|
||||||
from fastapi import FastAPI, params
|
from fastapi import FastAPI, params
|
||||||
@@ -51,7 +52,7 @@ class APIRouterPro(APIRouter):
|
|||||||
order_num: Annotated[int, Doc('An optional order number for the router.')] = 100,
|
order_num: Annotated[int, Doc('An optional order number for the router.')] = 100,
|
||||||
auto_register: Annotated[bool, Doc('An optional auto register flag for the router.')] = True,
|
auto_register: Annotated[bool, Doc('An optional auto register flag for the router.')] = True,
|
||||||
tags: Annotated[
|
tags: Annotated[
|
||||||
Optional[list[Union[str, Enum]]],
|
list[str | Enum] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
A list of tags to be applied to all the *path operations* in this
|
A list of tags to be applied to all the *path operations* in this
|
||||||
@@ -65,7 +66,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
dependencies: Annotated[
|
dependencies: Annotated[
|
||||||
Optional[Sequence[params.Depends]],
|
Sequence[params.Depends] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
A list of dependencies (using `Depends()`) to be applied to all the
|
A list of dependencies (using `Depends()`) to be applied to all the
|
||||||
@@ -88,7 +89,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = Default(JSONResponse),
|
] = Default(JSONResponse),
|
||||||
responses: Annotated[
|
responses: Annotated[
|
||||||
Optional[dict[Union[int, str], dict[str, Any]]],
|
dict[int | str, dict[str, Any]] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
Additional responses to be shown in OpenAPI.
|
Additional responses to be shown in OpenAPI.
|
||||||
@@ -104,7 +105,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
callbacks: Annotated[
|
callbacks: Annotated[
|
||||||
Optional[list[BaseRoute]],
|
list[BaseRoute] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
OpenAPI callbacks that should apply to all *path operations* in this
|
OpenAPI callbacks that should apply to all *path operations* in this
|
||||||
@@ -118,7 +119,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
routes: Annotated[
|
routes: Annotated[
|
||||||
Optional[list[BaseRoute]],
|
list[BaseRoute] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
**Note**: you probably shouldn't use this parameter, it is inherited
|
**Note**: you probably shouldn't use this parameter, it is inherited
|
||||||
@@ -149,7 +150,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = True,
|
] = True,
|
||||||
default: Annotated[
|
default: Annotated[
|
||||||
Optional[ASGIApp],
|
ASGIApp | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
Default function handler for this router. Used to handle
|
Default function handler for this router. Used to handle
|
||||||
@@ -158,7 +159,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
dependency_overrides_provider: Annotated[
|
dependency_overrides_provider: Annotated[
|
||||||
Optional[Any],
|
Any | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
Only used internally by FastAPI to handle dependency overrides.
|
Only used internally by FastAPI to handle dependency overrides.
|
||||||
@@ -180,7 +181,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = APIRoute,
|
] = APIRoute,
|
||||||
on_startup: Annotated[
|
on_startup: Annotated[
|
||||||
Optional[Sequence[Callable[[], Any]]],
|
Sequence[Callable[[], Any]] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
A list of startup event handler functions.
|
A list of startup event handler functions.
|
||||||
@@ -192,7 +193,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
on_shutdown: Annotated[
|
on_shutdown: Annotated[
|
||||||
Optional[Sequence[Callable[[], Any]]],
|
Sequence[Callable[[], Any]] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
A list of shutdown event handler functions.
|
A list of shutdown event handler functions.
|
||||||
@@ -207,7 +208,7 @@ class APIRouterPro(APIRouter):
|
|||||||
# the generic to Lifespan[AppType] is the type of the top level application
|
# the generic to Lifespan[AppType] is the type of the top level application
|
||||||
# which the router cannot know statically, so we use typing.Any
|
# which the router cannot know statically, so we use typing.Any
|
||||||
lifespan: Annotated[
|
lifespan: Annotated[
|
||||||
Optional[Lifespan[Any]],
|
Lifespan[Any] | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
A `Lifespan` context manager handler. This replaces `startup` and
|
A `Lifespan` context manager handler. This replaces `startup` and
|
||||||
@@ -219,7 +220,7 @@ class APIRouterPro(APIRouter):
|
|||||||
),
|
),
|
||||||
] = None,
|
] = None,
|
||||||
deprecated: Annotated[
|
deprecated: Annotated[
|
||||||
Optional[bool],
|
bool | None,
|
||||||
Doc(
|
Doc(
|
||||||
"""
|
"""
|
||||||
Mark all *path operations* in this router as deprecated.
|
Mark all *path operations* in this router as deprecated.
|
||||||
@@ -306,17 +307,8 @@ class RouterRegister:
|
|||||||
|
|
||||||
:return: py文件路径列表
|
:return: py文件路径列表
|
||||||
"""
|
"""
|
||||||
controller_files = []
|
pattern = os.path.join(self.project_root, '*', 'controller', '[!_]*.py')
|
||||||
# 遍历所有目录,查找controller目录
|
return sorted(glob.glob(pattern))
|
||||||
for root, _dirs, files in os.walk(self.project_root):
|
|
||||||
# 检查当前目录是否为controller目录
|
|
||||||
if os.path.basename(root) == 'controller':
|
|
||||||
# 遍历controller目录下的所有py文件
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.py') and not file.startswith('__'):
|
|
||||||
file_path = os.path.join(root, file)
|
|
||||||
controller_files.append(file_path)
|
|
||||||
return controller_files
|
|
||||||
|
|
||||||
def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tuple[str, APIRouter]]:
|
def _import_module_and_get_routers(self, controller_files: list[str]) -> list[tuple[str, APIRouter]]:
|
||||||
"""
|
"""
|
||||||
@@ -331,21 +323,17 @@ class RouterRegister:
|
|||||||
relative_path = os.path.relpath(file_path, self.project_root)
|
relative_path = os.path.relpath(file_path, self.project_root)
|
||||||
module_name = relative_path.replace(os.sep, '.')[:-3]
|
module_name = relative_path.replace(os.sep, '.')[:-3]
|
||||||
|
|
||||||
try:
|
# 动态导入模块
|
||||||
# 动态导入模块
|
module = importlib.import_module(module_name)
|
||||||
module = importlib.import_module(module_name)
|
# 直接遍历模块__dict__,只检查模块自身定义的属性
|
||||||
# 遍历模块属性,寻找APIRouter和APIRouterPro实例
|
for attr_name, attr in module.__dict__.items():
|
||||||
for attr_name in dir(module):
|
# 对于APIRouterPro实例,只有当auto_register=True时才添加
|
||||||
attr = getattr(module, attr_name)
|
if isinstance(attr, APIRouterPro):
|
||||||
# 对于APIRouterPro实例,只有当auto_register=True时才添加
|
if attr.auto_register:
|
||||||
if isinstance(attr, APIRouterPro):
|
|
||||||
if attr.auto_register:
|
|
||||||
routers.append((attr_name, attr))
|
|
||||||
# 对于APIRouter实例,直接添加
|
|
||||||
elif isinstance(attr, APIRouter):
|
|
||||||
routers.append((attr_name, attr))
|
routers.append((attr_name, attr))
|
||||||
except Exception as e:
|
# 对于APIRouter实例,直接添加
|
||||||
print(f'Error importing module {module_name}: {e}')
|
elif isinstance(attr, APIRouter):
|
||||||
|
routers.append((attr_name, attr))
|
||||||
return routers
|
return routers
|
||||||
|
|
||||||
def _sort_routers(self, routers: list[tuple[str, APIRouter]]) -> list[tuple[str, APIRouter]]:
|
def _sort_routers(self, routers: list[tuple[str, APIRouter]]) -> list[tuple[str, APIRouter]]:
|
||||||
@@ -357,7 +345,7 @@ class RouterRegister:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# 按规则排序路由
|
# 按规则排序路由
|
||||||
def sort_key(item: tuple[str, APIRouter]) -> Union[tuple[Literal[0], int, str], tuple[Literal[1], str]]:
|
def sort_key(item: tuple[str, APIRouter]) -> tuple[Literal[0], int, str] | tuple[Literal[1], str]:
|
||||||
attr_name, router = item
|
attr_name, router = item
|
||||||
# APIRouterPro实例按order_num排序,序号越小越靠前
|
# APIRouterPro实例按order_num排序,序号越小越靠前
|
||||||
if isinstance(router, APIRouterPro):
|
if isinstance(router, APIRouterPro):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Generic, Optional, TypeVar, Union
|
from typing import Any, Generic, TypeVar
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, create_model
|
from pydantic import BaseModel, ConfigDict, Field, create_model
|
||||||
from pydantic.alias_generators import to_camel
|
from pydantic.alias_generators import to_camel
|
||||||
@@ -17,7 +17,7 @@ class CrudResponseModel(BaseModel):
|
|||||||
|
|
||||||
is_success: bool = Field(description='操作是否成功')
|
is_success: bool = Field(description='操作是否成功')
|
||||||
message: str = Field(description='响应信息')
|
message: str = Field(description='响应信息')
|
||||||
result: Optional[Any] = Field(default=None, description='响应结果')
|
result: Any | None = Field(default=None, description='响应结果')
|
||||||
|
|
||||||
|
|
||||||
class ResponseBaseModel(BaseModel):
|
class ResponseBaseModel(BaseModel):
|
||||||
@@ -38,7 +38,7 @@ class DynamicResponseModel(ResponseBaseModel, Generic[T]):
|
|||||||
|
|
||||||
model_config = ConfigDict(alias_generator=to_camel)
|
model_config = ConfigDict(alias_generator=to_camel)
|
||||||
|
|
||||||
def __class_getitem__(cls, item: Any) -> Union[Any, Self]:
|
def __class_getitem__(cls, item: Any) -> Any | Self:
|
||||||
"""
|
"""
|
||||||
当使用 DynamicResponseModel[Item] 语法时,动态创建一个包含所有字段的新模型
|
当使用 DynamicResponseModel[Item] 语法时,动态创建一个包含所有字段的新模型
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,29 +1,108 @@
|
|||||||
from urllib.parse import quote_plus
|
from urllib.parse import quote_plus
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
|
from sqlalchemy import Engine, create_engine
|
||||||
from sqlalchemy.orm import DeclarativeBase
|
from sqlalchemy.ext.asyncio import AsyncAttrs, AsyncEngine, async_sessionmaker, create_async_engine
|
||||||
|
from sqlalchemy.orm import DeclarativeBase, sessionmaker
|
||||||
|
|
||||||
from config.env import DataBaseConfig
|
from config.env import DataBaseConfig
|
||||||
|
|
||||||
ASYNC_SQLALCHEMY_DATABASE_URL = (
|
|
||||||
f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
|
def build_async_sqlalchemy_database_url() -> str:
|
||||||
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
|
"""
|
||||||
)
|
构建异步 SQLAlchemy 数据库连接 URL
|
||||||
if DataBaseConfig.db_type == 'postgresql':
|
|
||||||
ASYNC_SQLALCHEMY_DATABASE_URL = (
|
:return: 异步 SQLAlchemy 数据库连接 URL
|
||||||
f'postgresql+asyncpg://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
|
"""
|
||||||
|
if DataBaseConfig.db_type == 'postgresql':
|
||||||
|
return (
|
||||||
|
f'postgresql+asyncpg://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
|
||||||
|
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f'mysql+asyncmy://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
|
||||||
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
|
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
|
||||||
)
|
)
|
||||||
|
|
||||||
async_engine = create_async_engine(
|
|
||||||
ASYNC_SQLALCHEMY_DATABASE_URL,
|
ASYNC_SQLALCHEMY_DATABASE_URL = build_async_sqlalchemy_database_url()
|
||||||
echo=DataBaseConfig.db_echo,
|
|
||||||
max_overflow=DataBaseConfig.db_max_overflow,
|
|
||||||
pool_size=DataBaseConfig.db_pool_size,
|
def build_sync_sqlalchemy_database_url() -> str:
|
||||||
pool_recycle=DataBaseConfig.db_pool_recycle,
|
"""
|
||||||
pool_timeout=DataBaseConfig.db_pool_timeout,
|
构建同步 SQLAlchemy 数据库连接 URL
|
||||||
)
|
|
||||||
AsyncSessionLocal = async_sessionmaker(autocommit=False, autoflush=False, bind=async_engine)
|
:return: 同步 SQLAlchemy 数据库连接 URL
|
||||||
|
"""
|
||||||
|
if DataBaseConfig.db_type == 'postgresql':
|
||||||
|
return (
|
||||||
|
f'postgresql+psycopg2://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
|
||||||
|
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
f'mysql+pymysql://{DataBaseConfig.db_username}:{quote_plus(DataBaseConfig.db_password)}@'
|
||||||
|
f'{DataBaseConfig.db_host}:{DataBaseConfig.db_port}/{DataBaseConfig.db_database}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
SYNC_SQLALCHEMY_DATABASE_URL = build_sync_sqlalchemy_database_url()
|
||||||
|
|
||||||
|
|
||||||
|
def create_async_db_engine(echo: bool | None = None) -> AsyncEngine:
|
||||||
|
"""
|
||||||
|
创建异步 SQLAlchemy Engine
|
||||||
|
|
||||||
|
:param echo: 可选,是否输出 SQLAlchemy SQL 日志
|
||||||
|
:return: 异步 SQLAlchemy Engine
|
||||||
|
"""
|
||||||
|
return create_async_engine(
|
||||||
|
ASYNC_SQLALCHEMY_DATABASE_URL,
|
||||||
|
echo=DataBaseConfig.db_echo if echo is None else echo,
|
||||||
|
max_overflow=DataBaseConfig.db_max_overflow,
|
||||||
|
pool_size=DataBaseConfig.db_pool_size,
|
||||||
|
pool_recycle=DataBaseConfig.db_pool_recycle,
|
||||||
|
pool_timeout=DataBaseConfig.db_pool_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_sync_db_engine(echo: bool | None = None) -> Engine:
|
||||||
|
"""
|
||||||
|
创建同步 SQLAlchemy Engine
|
||||||
|
|
||||||
|
:param echo: 可选,是否输出 SQLAlchemy SQL 日志
|
||||||
|
:return: 同步 SQLAlchemy Engine
|
||||||
|
"""
|
||||||
|
return create_engine(
|
||||||
|
SYNC_SQLALCHEMY_DATABASE_URL,
|
||||||
|
echo=DataBaseConfig.db_echo if echo is None else echo,
|
||||||
|
max_overflow=DataBaseConfig.db_max_overflow,
|
||||||
|
pool_size=DataBaseConfig.db_pool_size,
|
||||||
|
pool_recycle=DataBaseConfig.db_pool_recycle,
|
||||||
|
pool_timeout=DataBaseConfig.db_pool_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_async_session_local(engine: AsyncEngine) -> async_sessionmaker:
|
||||||
|
"""
|
||||||
|
创建异步 Session 工厂
|
||||||
|
|
||||||
|
:param engine: 异步 SQLAlchemy Engine
|
||||||
|
:return: 异步 Session 工厂
|
||||||
|
"""
|
||||||
|
return async_sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
def create_sync_session_local(engine: Engine) -> sessionmaker:
|
||||||
|
"""
|
||||||
|
创建同步 Session 工厂
|
||||||
|
|
||||||
|
:param engine: 同步 SQLAlchemy Engine
|
||||||
|
:return: 同步 Session 工厂
|
||||||
|
"""
|
||||||
|
return sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||||
|
|
||||||
|
|
||||||
|
async_engine = create_async_db_engine()
|
||||||
|
AsyncSessionLocal = create_async_session_local(async_engine)
|
||||||
|
|
||||||
|
|
||||||
class Base(AsyncAttrs, DeclarativeBase):
|
class Base(AsyncAttrs, DeclarativeBase):
|
||||||
|
|||||||
@@ -21,8 +21,12 @@ class AppSettings(BaseSettings):
|
|||||||
app_port: int = 9099
|
app_port: int = 9099
|
||||||
app_version: str = '1.0.0'
|
app_version: str = '1.0.0'
|
||||||
app_reload: bool = True
|
app_reload: bool = True
|
||||||
|
app_workers: int = 1
|
||||||
app_ip_location_query: bool = True
|
app_ip_location_query: bool = True
|
||||||
app_same_time_login: bool = True
|
app_same_time_login: bool = True
|
||||||
|
app_demo_mode: bool = False
|
||||||
|
app_disable_swagger: bool = False
|
||||||
|
app_disable_redoc: bool = False
|
||||||
|
|
||||||
|
|
||||||
class JwtSettings(BaseSettings):
|
class JwtSettings(BaseSettings):
|
||||||
@@ -73,6 +77,36 @@ class RedisSettings(BaseSettings):
|
|||||||
redis_database: int = 2
|
redis_database: int = 2
|
||||||
|
|
||||||
|
|
||||||
|
class LogSettings(BaseSettings):
|
||||||
|
"""
|
||||||
|
日志与队列配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
log_stream_key: str = 'log:stream'
|
||||||
|
log_stream_group: str = 'log_aggregator'
|
||||||
|
log_stream_consumer_prefix: str = 'worker'
|
||||||
|
log_stream_batch_size: int = 100
|
||||||
|
log_stream_block_ms: int = 2000
|
||||||
|
log_stream_maxlen: int = 100000
|
||||||
|
log_stream_claim_idle_ms: int = 60000
|
||||||
|
log_stream_claim_interval_ms: int = 5000
|
||||||
|
log_stream_claim_batch_size: int = 100
|
||||||
|
log_stream_dedup_ttl: int = 3600
|
||||||
|
log_stream_dedup_prefix: str = 'log:dedup'
|
||||||
|
|
||||||
|
loguru_json: bool = False
|
||||||
|
loguru_level: str = 'INFO'
|
||||||
|
loguru_stdout: bool = True
|
||||||
|
log_file_enabled: bool = True
|
||||||
|
log_file_base_dir: str = 'logs'
|
||||||
|
loguru_rotation: str = '50MB'
|
||||||
|
loguru_retention: str = '30 days'
|
||||||
|
loguru_compression: str = 'zip'
|
||||||
|
log_instance_id: str = 'prod'
|
||||||
|
log_service_name: str = 'ruoyi-fastapi-backend'
|
||||||
|
log_worker_id: str = 'auto'
|
||||||
|
|
||||||
|
|
||||||
class GenSettings:
|
class GenSettings:
|
||||||
"""
|
"""
|
||||||
代码生成配置
|
代码生成配置
|
||||||
@@ -182,6 +216,12 @@ class GetConfig:
|
|||||||
# 实例化Redis配置模型
|
# 实例化Redis配置模型
|
||||||
return RedisSettings()
|
return RedisSettings()
|
||||||
|
|
||||||
|
def get_log_config(self) -> LogSettings:
|
||||||
|
"""
|
||||||
|
获取日志配置
|
||||||
|
"""
|
||||||
|
return LogSettings()
|
||||||
|
|
||||||
def get_gen_config(self) -> GenSettings:
|
def get_gen_config(self) -> GenSettings:
|
||||||
"""
|
"""
|
||||||
获取代码生成配置
|
获取代码生成配置
|
||||||
@@ -241,6 +281,8 @@ JwtConfig = get_config.get_jwt_config()
|
|||||||
DataBaseConfig = get_config.get_database_config()
|
DataBaseConfig = get_config.get_database_config()
|
||||||
# Redis配置
|
# Redis配置
|
||||||
RedisConfig = get_config.get_redis_config()
|
RedisConfig = get_config.get_redis_config()
|
||||||
|
# 日志配置
|
||||||
|
LogConfig = get_config.get_log_config()
|
||||||
# 代码生成配置
|
# 代码生成配置
|
||||||
GenConfig = get_config.get_gen_config()
|
GenConfig = get_config.get_gen_config()
|
||||||
# 上传配置
|
# 上传配置
|
||||||
|
|||||||
@@ -26,3 +26,12 @@ async def init_create_table() -> None:
|
|||||||
async with async_engine.begin() as conn:
|
async with async_engine.begin() as conn:
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
logger.info('✅️ 数据库连接成功')
|
logger.info('✅️ 数据库连接成功')
|
||||||
|
|
||||||
|
|
||||||
|
async def close_async_engine() -> None:
|
||||||
|
"""
|
||||||
|
应用关闭时释放数据库连接池
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
await async_engine.dispose()
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ class RedisUtil:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def create_redis_pool(cls) -> aioredis.Redis:
|
async def create_redis_pool(cls, log_enabled: bool = True, log_start_enabled: bool | None = None) -> aioredis.Redis:
|
||||||
"""
|
"""
|
||||||
应用启动时初始化redis连接
|
应用启动时初始化redis连接
|
||||||
|
|
||||||
|
:param log_enabled: 是否输出日志
|
||||||
|
:param log_start_enabled: 是否输出开始连接日志
|
||||||
:return: Redis连接对象
|
:return: Redis连接对象
|
||||||
"""
|
"""
|
||||||
logger.info('🔎 开始连接redis...')
|
|
||||||
redis = await aioredis.from_url(
|
redis = await aioredis.from_url(
|
||||||
url=f'redis://{RedisConfig.redis_host}',
|
url=f'redis://{RedisConfig.redis_host}',
|
||||||
port=RedisConfig.redis_port,
|
port=RedisConfig.redis_port,
|
||||||
@@ -32,19 +33,45 @@ class RedisUtil:
|
|||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
decode_responses=True,
|
decode_responses=True,
|
||||||
)
|
)
|
||||||
|
if log_start_enabled is None:
|
||||||
|
log_start_enabled = log_enabled
|
||||||
|
if log_enabled or log_start_enabled:
|
||||||
|
await cls.check_redis_connection(redis, log_enabled=log_enabled, log_start_enabled=log_start_enabled)
|
||||||
|
return redis
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def check_redis_connection(
|
||||||
|
cls, redis: aioredis.Redis, log_enabled: bool = True, log_start_enabled: bool | None = None
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
检查redis连接状态
|
||||||
|
|
||||||
|
:param redis: redis对象
|
||||||
|
:param log_enabled: 是否输出日志
|
||||||
|
:param log_start_enabled: 是否输出开始连接日志
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
if log_start_enabled is None:
|
||||||
|
log_start_enabled = log_enabled
|
||||||
|
if log_start_enabled:
|
||||||
|
logger.info('🔎 开始连接redis...')
|
||||||
try:
|
try:
|
||||||
connection = await redis.ping()
|
connection = await redis.ping()
|
||||||
|
if not log_enabled:
|
||||||
|
return
|
||||||
if connection:
|
if connection:
|
||||||
logger.info('✅️ redis连接成功')
|
logger.info('✅️ redis连接成功')
|
||||||
else:
|
else:
|
||||||
logger.error('❌️ redis连接失败')
|
logger.error('❌️ redis连接失败')
|
||||||
except AuthenticationError as e:
|
except AuthenticationError as e:
|
||||||
logger.error(f'❌️ redis用户名或密码错误,详细错误信息:{e}')
|
if log_enabled:
|
||||||
|
logger.error(f'❌️ redis用户名或密码错误,详细错误信息:{e}')
|
||||||
except RedisTimeoutError as e:
|
except RedisTimeoutError as e:
|
||||||
logger.error(f'❌️ redis连接超时,详细错误信息:{e}')
|
if log_enabled:
|
||||||
|
logger.error(f'❌️ redis连接超时,详细错误信息:{e}')
|
||||||
except RedisError as e:
|
except RedisError as e:
|
||||||
logger.error(f'❌️ redis连接错误,详细错误信息:{e}')
|
if log_enabled:
|
||||||
return redis
|
logger.error(f'❌️ redis连接错误,详细错误信息:{e}')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def close_redis_pool(cls, app: FastAPI) -> None:
|
async def close_redis_pool(cls, app: FastAPI) -> None:
|
||||||
|
|||||||