Compare commits
5 Commits
cb8f7a4682
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9432116a00 | ||
|
|
5fda36b268 | ||
|
|
84820b1e9b | ||
|
|
550bf0039b | ||
|
|
2653800c3f |
23
README.md
23
README.md
@@ -13,7 +13,6 @@
|
||||
|
||||

|
||||
|
||||
|
||||
## CI/CD 自动化集群部署
|
||||
|
||||

|
||||
@@ -244,14 +243,22 @@ export default class Header extends TSX<Props>()(Vue) implements Props {
|
||||
**全局准备工作:**
|
||||
|
||||
- 1: 准备 >=8台Linux服务器,并提前安装配置好Docker,确保网络可用,建议安装 [1panel](https://1panel.cn/)
|
||||
- 2: 在1台Linux服务器上,部署公司自己的 Docker Registry 镜像存储库
|
||||
- 3: 准本 N台运行 前端各模块 的Linux服务器,并提前组成N个Docker Swarm 集群,以供前端各模块部署使用
|
||||
- 4: 准本 >=3台运行 Nacos服务中心 的Linux服务器,并提前组成Docker Swarm 集群,提前运行好,确保服务可用
|
||||
- [Nacos 部署文档](./docker/nacos-cluster/README.md)
|
||||
- 5: 准本 一台 运行 traefik 网关的Linux服务器,,提前运行好,确保服务可用
|
||||
- [Traefik 部署文档](./docker/traefik/README.md)
|
||||
|
||||
- 2: 在1台Linux服务器上,部署公司自己的 Docker Registry 镜像存储库
|
||||
- 3: 在1台Linux服务器上,部署公司自己 Gitea 仓库
|
||||
|
||||
- 4: 准本 N台运行 前端各模块 的Linux服务器,并提前组成N个Docker Swarm 集群,以供前端各模块部署使用
|
||||
|
||||
- 5: 准本 >=3台运行 Nacos服务中心 的Linux服务器,并提前组成Docker Swarm 集群,提前运行好,确保服务可用
|
||||
- [Nacos 部署文档](./docker/nacos-cluster/README.md)
|
||||
|
||||
- 6: 准本 一台 运行 traefik 网关的Linux服务器,,提前运行好,确保服务可用
|
||||
- [Traefik 部署文档](./docker/traefik/README.md)
|
||||
|
||||
**前端工作:**
|
||||
|
||||
详细请看 [app-product的README.md](./front-end/app-product/README.md) 内的帮助文档!!!
|
||||
详细请看 各前端模块 存储库内的 帮助文档!!!
|
||||
|
||||
- [商品模块 app-product](https://gitee.com/bmycode/app-product)
|
||||
- [共享模块 app-shared](https://gitee.com/bmycode/app-shared)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 包含 [skip actions] 的提交会跳过工作流
|
||||
git commit -m "修复样式问题 [skip actions]"
|
||||
git commit -m "修复样式问题[skip actions]"
|
||||
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
nginx-proxy:
|
||||
image: nginx:latest
|
||||
container_name: nginx-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- proxy-network
|
||||
|
||||
networks:
|
||||
proxy-network:
|
||||
driver: bridge
|
||||
@@ -1,61 +0,0 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# 上游服务器配置
|
||||
upstream nacos_cluster {
|
||||
server 192.168.139.84:8848;
|
||||
server 192.168.139.15:8848;
|
||||
server 192.168.139.178:8848;
|
||||
}
|
||||
|
||||
|
||||
upstream nacos_ui {
|
||||
server 192.168.139.84:8080;
|
||||
server 192.168.139.15:8080;
|
||||
server 192.168.139.178:8080;
|
||||
}
|
||||
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name cluster.nacos.com;
|
||||
|
||||
# 反向代理配置
|
||||
location / {
|
||||
proxy_pass http://nacos_cluster;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name ui.nacos.com;
|
||||
|
||||
# 反向代理配置
|
||||
location / {
|
||||
proxy_pass http://nacos_ui;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 超时设置
|
||||
proxy_connect_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
http:
|
||||
routers:
|
||||
|
||||
# 共享模块的 路由 拦截 配置
|
||||
#
|
||||
app-shared-router:
|
||||
rule: Host(`nacos.ddd.com`) && PathPrefix(`/app-shared`)
|
||||
service: app-shared-service
|
||||
@@ -13,7 +13,6 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
# 商品模块的 路由 拦截 配置
|
||||
app-product-router:
|
||||
rule: Host(`nacos.ddd.com`) && PathPrefix(`/app-product`)
|
||||
service: app-product-service
|
||||
@@ -25,7 +24,7 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
# 使用域名访问前端 main-app 主模块
|
||||
# 使用域名访问前端main-app
|
||||
main-ddd-router:
|
||||
rule: Host(`main.ddd.com`)
|
||||
service: main-ddd-service
|
||||
@@ -37,7 +36,7 @@ http:
|
||||
entryPoints:
|
||||
- web
|
||||
|
||||
# Nacos 集群 管理后台的统一 访问域名
|
||||
# Nacos 集群 管理后台的统一访问域名
|
||||
nacosui-ddd-router:
|
||||
rule: Host(`nacosui.ddd.com`)
|
||||
service: nacosui-ddd-service
|
||||
@@ -78,7 +77,9 @@ http:
|
||||
app-shared-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://192.168.31.101:1333
|
||||
- url: http://192.168.139.172:1333
|
||||
- url: http://192.168.139.184:1333
|
||||
- url: http://192.168.139.186:1333
|
||||
|
||||
app-product-service:
|
||||
loadBalancer:
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.DS_Store
|
||||
*.log
|
||||
docker/ # 排除 docker 配置文件夹本身
|
||||
.editorconfig
|
||||
.eslintrc.js
|
||||
.prettierrc
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
@@ -1,26 +0,0 @@
|
||||
# 请求协议 http or https(建议线上配置证书后,使用https)
|
||||
VITE_PREFIX=http://
|
||||
|
||||
# 内网 中的 Nacos 配置中心 ip:端口 | 域名
|
||||
#VITE_NACOS_ADDRESS=cluster.nacos.com
|
||||
VITE_NACOS_ADDRESS=192.168.139.84:8848
|
||||
|
||||
|
||||
# 注册到 Nacos 的服务名称(微前端模块名称
|
||||
VITE_SERVER_NAME=app-product
|
||||
# 注册到 Nacos 的端口,也是前端运行的端口
|
||||
VITE_SERVER_PORT=1301
|
||||
|
||||
|
||||
#
|
||||
# 说明:项目要调用哪些 微前端 模块
|
||||
# 格式:VITE_MODEL_XXXX_NAME=微前端模块名称(模块 的 开发者提供)
|
||||
# 作用:通过名称从Nacos配置中心发现模块地址和端口,给 federation 实时加载
|
||||
#
|
||||
|
||||
# shared 共享资源模块
|
||||
VITE_MODEL_SHARED_NAME=app-shared
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# 请求协议 http or https(建议线上配置证书后,使用https)
|
||||
VITE_PREFIX=http://
|
||||
|
||||
# 内网 中的 Nacos 配置中心 ip:端口 | 域名
|
||||
#VITE_NACOS_ADDRESS=cluster.nacos.com
|
||||
VITE_NACOS_ADDRESS=192.168.139.84:8848
|
||||
|
||||
|
||||
# 注册到 Nacos 的服务名称(微前端模块名称
|
||||
VITE_SERVER_NAME=app-product
|
||||
# 注册到 Nacos 的端口,也是前端运行的端口
|
||||
VITE_SERVER_PORT=1301
|
||||
|
||||
|
||||
#
|
||||
# 说明:项目要调用哪些 微前端 模块
|
||||
# 格式:VITE_MODEL_XXXX_NAME=微前端模块名称(模块 的 开发者提供)
|
||||
# 作用:通过名称从Nacos配置中心发现模块地址和端口,给 federation 实时加载
|
||||
#
|
||||
|
||||
# shared 共享资源模块
|
||||
VITE_MODEL_SHARED_NAME=app-shared
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
name: build and push
|
||||
|
||||
# 当代码推送到 main 分支时触发
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build-and-push:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: 拉取最新代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置 Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: 登录到自己的 Docker 仓库
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: registry.bmycode.help
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
|
||||
- name: 构建并推送镜像
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
registry.bmycode.help/app-product:latest
|
||||
registry.bmycode.help/app-product:${{ github.sha }}
|
||||
|
||||
|
||||
- name: 替换镜像标签为 github.sha
|
||||
run: |
|
||||
sed -i "s|{{IMAGE_TAG}}|${{ github.sha }}|g" app-product.yml
|
||||
|
||||
- name: 上传 app-product.yml 到前端集群管理机
|
||||
uses: appleboy/scp-action@v1
|
||||
with:
|
||||
host: ${{ vars.SSH_MANAGE }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
source: "app-product.yml"
|
||||
target: "/home/bmy/"
|
||||
|
||||
- name: 登录前端集群,拉镜像
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
# 统一登录三台前端集群的linux,统一拉取刚推到仓库的最新镜像
|
||||
# 解决 Swarm 无法控制 工作机拉取镜像的问题!
|
||||
host: ${{ vars.SSH_HOST_LIST }}
|
||||
sync: true
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
script: |
|
||||
# 拉取刚打包的最新镜像
|
||||
docker pull registry.bmycode.help/app-product:${{ github.sha }}
|
||||
|
||||
- name: 管理节点部署到 Swarm
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ vars.SSH_MANAGE }}
|
||||
sync: true
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.PASSWORD }}
|
||||
port: ${{ secrets.PORT }}
|
||||
script: |
|
||||
# 切换到管理员,要不然没权限在服务器执行 docker 命令,部署到 Swarm 集群
|
||||
sudo docker stack deploy -c /home/bmy/app-product.yml app-product
|
||||
@@ -1,22 +0,0 @@
|
||||
FROM node:24-alpine AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json ./
|
||||
|
||||
# 直接使用 npm install 并解决依赖问题
|
||||
RUN npm install --no-audit --no-fund --legacy-peer-deps
|
||||
|
||||
# 拷贝源码并构建
|
||||
COPY . .
|
||||
|
||||
# 【前端修改】如果要以子路径 /app-product/ 部署,请用 base 指定前缀,同 .env中VITE_SERVER_NAME
|
||||
RUN npx vite build --base=/app-product/
|
||||
|
||||
FROM nginx:alpine
|
||||
# 构建产物放到 /usr/share/nginx/html/app-product
|
||||
# nginx.conf 也需要修改
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html/app-product
|
||||
# 覆盖 Nginx 配置,支持 SPA 子路径
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,156 +0,0 @@
|
||||
|
||||
## app-product 商品模块(微前端模块)
|
||||
|
||||
从项目中拆分出来,用于演示的微前端模块,主要实现 商品相关功能。本案例演示中提供:
|
||||
|
||||
- 调用 微前端 app-shared 模块
|
||||
- 调接口,获取商品首页的数据和展示
|
||||
- 调接口,点击商品列表进入商品详情页
|
||||
|
||||
|
||||
## 运行
|
||||
|
||||
下面文档分为**开发**和**上线**两部分进行说明!**默认前端已经在电脑上安装过 Docker 环境!!**
|
||||
|
||||
> 想要【开发阶段运行】,必须 先参考下面 【上线】-【任务1】配置好两个前端自己的环境变量文件!很简单!
|
||||
|
||||
### 开发
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 开发阶段 运行 项目
|
||||
# PS:注意此命令运行的项目,【无法】被其他 微前端模块 使用,仅用于本地独立开发,独立测试
|
||||
npm run dev
|
||||
|
||||
|
||||
# 启动 实时打包 & 实时预览
|
||||
# PS:注意此命令运行的项目,【可以】被其他 微前端模块 调用,可用于本地开发和微前端联合开发
|
||||
npm run dep
|
||||
```
|
||||
|
||||
### 上线
|
||||
|
||||
代码 到 仓库的 **main 分支** 后会触发 全程自动化,实现自动打包前端代码,构建镜像,自动部署到公司内网的Linux服务器集群中!
|
||||
提交到其他分支不触发自动化操作!具体什么规则,以 技术负责人 说的为准!请遵守规则!
|
||||
|
||||
|
||||
**根目录核心文件说明**
|
||||
|
||||
- `app-product.yml` - Dcoker Swarm 集群部署配置【不准动】
|
||||
- `docker-compose.yml` - 开发阶段容器编排【可选,不要就删除】
|
||||
- `Dockerfile` - 打包构建前端代码为镜像【根据需求改动】
|
||||
- `nginx.conf` - 镜像内前端被访问的Nginx配置【根据需求改动】
|
||||
- `act-runner.yml` - gitea action 的 runner【根据需求改动】
|
||||
|
||||
前端工作:
|
||||
|
||||
前期大致工作内容如下,改好后,以后就不用再动了,很简单:
|
||||
|
||||
- 任务1: 编辑 前端 .env.development 和 .env.production 环境变量(只需修改一次)
|
||||
- 任务2: 确认或编辑 Dockerfile,nginx.conf
|
||||
- 任务3: 编辑 act-runner.yml
|
||||
|
||||
---
|
||||
|
||||
详细展开说明:
|
||||
|
||||
**任务1:**
|
||||
|
||||
主要修改两个环境变量文件 `.env.development`,`.env.production` 中的:
|
||||
|
||||
```bash
|
||||
|
||||
# 找技术负责人要
|
||||
# 情况1: 【内网模块】填写 Nacos 内网 ip 或 域名 即可
|
||||
# 情况2: 【外网模块】填写 traefik 公网 ip 或 域名 即可
|
||||
# .env.development, .env.production 中 可以不一样!
|
||||
VITE_NACOS_ADDRESS=????
|
||||
|
||||
|
||||
# 这个参数很重要,前端可以自定义填写,填写后不允许再改,填完给 技术负责人 即可
|
||||
# 微前端应用的名称,遵守 app-xxxxx
|
||||
# .env.development, .env.production 中 必须一样!
|
||||
VITE_SERVER_NAME=app-???
|
||||
|
||||
#【ps】其他参数看环境变量内说明
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**任务2:**
|
||||
|
||||
主要修改两个 `Dockerfile`,`nginx.conf`,改起来也很简单:
|
||||
|
||||
|
||||
Dockerfile 修改教程:
|
||||
|
||||
```bash
|
||||
|
||||
|
||||
# 将 VITE_SERVER_NAME 替换为 上一步设置的 VITE_SERVER_NAME 的 数值 即可
|
||||
RUN npx vite build --base=/VITE_SERVER_NAME/
|
||||
|
||||
# 将 VITE_SERVER_NAME 替换为 上一步设置的 VITE_SERVER_NAME 的 数值 即可
|
||||
COPY --from=builder /app/dist /usr/share/nginx/html/VITE_SERVER_NAME
|
||||
```
|
||||
|
||||
|
||||
nginx.conf 修改教程:
|
||||
|
||||
```bash
|
||||
# 看仔细了,有三处 VITE_SERVER_NAME 需要替换成 值!!
|
||||
location /VITE_SERVER_NAME/ {
|
||||
alias /usr/share/nginx/html/VITE_SERVER_NAME/;
|
||||
try_files $uri $uri/ /VITE_SERVER_NAME/index.html;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**任务3:**
|
||||
|
||||
act-runner.yml 是 Gitea Action 的核心,Gitea会将Job委托给Runner来运行,**所以我们前端本地必须要先运行 act-runner.yml 文件**,这样在我们提交代码的时候 Gitea Action 的自动构建才会运行!!
|
||||
|
||||
但是,运行 act-runner.yml 之前,需要先对 act-runner.yml 文件进行简单的配置:
|
||||
|
||||
```bash
|
||||
|
||||
# 核心 需要改动的配置
|
||||
# 可以直接让 仓库 的管理员 给你把下面参数之间配置好,当然你也可以自己配
|
||||
|
||||
# GITEA_INSTANCE_URL=这个前端项目模块的 git仓库地址
|
||||
# 格式:协议://域名 即可,例如:https://git.bmycode.help 或 https://gitee.com 等
|
||||
|
||||
# 项目仓库首页-设置-Actions-Runner 右上角可复制令牌
|
||||
# GITEA_RUNNER_REGISTRATION_TOKEN=注册令牌,来源三选一:实例,组织,全存储库
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=https://git.bmycode.help
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=WFiO5JDwz7CHnk7fMGJYMcSITU81CLXuI7Ofg10e
|
||||
|
||||
```
|
||||
|
||||
改好后,在**项目文件夹根目录**打开终端,通过下面命令运行 act-runner.yml 文件:
|
||||
|
||||
```bash
|
||||
docker-compose -f act-runner.yml up -d
|
||||
|
||||
# 运行后,如果 在 docker desktop 的容器中找到 名字叫:gitea-act-runner 的容器,查看日志:
|
||||
|
||||
# 有下面这行,则表示 runner 成功启动!!docker desktop 可以关闭了或者放后台,不用管了!
|
||||
# level=debug msg="Successfully pinged the Gitea instance server"
|
||||
|
||||
# 如果出问题了,找 技术负责人!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
**到这一步,上线 配置已经搞完,很简单吧!剩下的,你提交代码到仓库就行,项目会自动构建,自动打包发布到公司内网集群中部署和运行,不用你管了!!**
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
gitea-actions-runner:
|
||||
image: gitea/act_runner:latest
|
||||
container_name: gitea-act-runner
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- runner-data:/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- GITEA_INSTANCE_URL=https://git.bmycode.help
|
||||
- GITEA_RUNNER_REGISTRATION_TOKEN=WFiO5JDwz7CHnk7fMGJYMcSITU81CLXuI7Ofg10e
|
||||
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest
|
||||
|
||||
volumes:
|
||||
runner-data:
|
||||
@@ -1,39 +0,0 @@
|
||||
version: "3.8" # Docker Compose 文件版本,3.8 支持 Swarm 模式的所有功能
|
||||
|
||||
services:
|
||||
app-product:
|
||||
image: registry.bmycode.help/app-product:{{IMAGE_TAG}}
|
||||
ports:
|
||||
- target: 80 # 容器内部端口
|
||||
published: 1301 # 宿主机对外暴露端口
|
||||
protocol: tcp # 协议类型
|
||||
mode: ingress # 模式:使用路由网格
|
||||
deploy:
|
||||
replicas: 3 # 副本数量,在集群中运行 3 个容器实例
|
||||
placement:
|
||||
max_replicas_per_node: 1 # 每个 Swarm 节点最多运行 1 个副本,确保高可用性
|
||||
restart_policy:
|
||||
condition: any # 重启条件:任何情况下容器退出都重启
|
||||
delay: 5s # 重启延迟 5 秒
|
||||
max_attempts: 3 # 最大重启尝试次数
|
||||
update_config: # 滚动更新配置
|
||||
parallelism: 1 # 并行更新数量:一次只更新 1 个容器
|
||||
delay: 10s # 更新间隔:每个容器更新后等待 10 秒再更新下一个
|
||||
failure_action: rollback # 更新失败时自动回滚到上一版本
|
||||
order: start-first # 更新顺序:先启动新容器,确认健康后再停止旧容器
|
||||
rollback_config: # 回滚配置
|
||||
parallelism: 1 # 回滚时并行数量
|
||||
delay: 5s # 回滚延迟
|
||||
resources: # 资源限制
|
||||
limits: # 资源上限
|
||||
cpus: "0.50" # 最多使用 0.5 个 CPU 核心
|
||||
memory: "256M" # 最多使用 256MB 内存
|
||||
reservations: # 资源预留
|
||||
cpus: "0.25" # 预留 0.25 个 CPU 核心
|
||||
memory: "128M" # 预留 128MB 内存
|
||||
networks:
|
||||
- frontend # 连接到 frontend 网络
|
||||
|
||||
networks:
|
||||
frontend: # 定义 frontend 网络
|
||||
driver: overlay # 使用 overlay 网络驱动,支持跨主机通信
|
||||
@@ -1,13 +0,0 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app-product:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
|
||||
image: registry.bmycode.help/app-product:latest
|
||||
container_name: app-product
|
||||
ports:
|
||||
- "1301:80"
|
||||
restart: unless-stopped
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,14 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name *.ddd.com;
|
||||
|
||||
|
||||
location /app-product/ {
|
||||
alias /usr/share/nginx/html/app-product/;
|
||||
try_files $uri $uri/ /app-product/index.html;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"name": "app-product",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dep": "concurrently \"vite build --watch\" \"sleep 5 && npm run preview\"",
|
||||
"preview": "vite preview --port 1301 --strictPort",
|
||||
"build": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"less": "^4.4.1",
|
||||
"pinia": "^3.0.3",
|
||||
"vite-plugin-nacos": "^1.1.3",
|
||||
"vue": "^3.5.22",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^24.6.1",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"element-plus": "^2.11.4",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.1.7",
|
||||
"vite-federation": "^3.4.4",
|
||||
"vite-plugin-vue-devtools": "^8.0.2",
|
||||
"vue-tsc": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import { defineComponent, onMounted } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
|
||||
onMounted(() => {
|
||||
console.log("import.meta.env: ", import.meta.env)
|
||||
})
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class="order-federation-placeholder">
|
||||
<h2>商品-联邦模块</h2>
|
||||
<p>此页面仅用于 商品模块 的单独开发和调试,可临时引入组件进行本地测试,实际运行时由 主应用
|
||||
加载本项目代码</p>
|
||||
|
||||
<h2>本模块包含的功能:</h2>
|
||||
<ul>
|
||||
<li>1:获取商品首页的数据和展示</li>
|
||||
<li>2:点击商品进入详情页</li>
|
||||
</ul>
|
||||
<p></p>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,22 +0,0 @@
|
||||
import { ErrorText } from "../../domain/enums/ErrorEnums";
|
||||
import { ProductRepository } from "../../ports/repositories/ProductRepository";
|
||||
|
||||
/**
|
||||
* 商品应用服务 - 协调领域对象完成业务用例
|
||||
* 应用服务是领域层的外观,封装了领域逻辑的调用
|
||||
*/
|
||||
export class ProductApplicationService {
|
||||
// 依赖注入仓储接口,而不是具体实现
|
||||
constructor(private productRepository: ProductRepository) { }
|
||||
|
||||
async getProductList() {
|
||||
return await this.productRepository.findAll();
|
||||
}
|
||||
|
||||
async getProductInfo(id: number) {
|
||||
if (!id) {
|
||||
throw new Error(ErrorText.IdNotNull)
|
||||
}
|
||||
return await this.productRepository.findById(id);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
// 从商品联邦模块导入领域层资源
|
||||
import {ProductRepository} from "@domains/product/ports/repositories/ProductRepository";
|
||||
import {HttpProductRepository} from "@domains/product/infr/api/HttpProductRepository";
|
||||
import {ProductApplicationService} from "@domains/product/application/services/ProductApplicationService";
|
||||
|
||||
/**
|
||||
* 商品领域的依赖注入配置
|
||||
* 将 接口 与 具体实现 绑定
|
||||
*
|
||||
* 优点:是一个依赖工厂,它隐藏了对象创建的细节,
|
||||
* 让业务代码只需要关心 “做什么”,而不用关心 “依赖从哪里来”
|
||||
* 所有依赖关系都在di目录下集中配置,一目了然,后续维护时能快速找到所有依赖的创建逻辑。
|
||||
*
|
||||
* 松耦合:
|
||||
* 业务代码(ProductApplicationService)只依赖抽象接口(ProductRepository),
|
||||
* 不依赖具体实现。如果未来需要改用其他数据来源(比如 WebSocket),
|
||||
* 只需修改bindProductRepository的返回值,无需改动业务逻辑。
|
||||
*
|
||||
* 易于测试:
|
||||
* 测试时可以临时替换为模拟实现
|
||||
* productModule.bindProductRepository = () => new MockProductRepository();
|
||||
* 这样测试就可以脱离真实 API,使用预设的模拟数据。
|
||||
*/
|
||||
export const product = {
|
||||
|
||||
// 绑定 仓储抽象接口 到 HTTP具体实现
|
||||
bindProductRepository: (): ProductRepository => {
|
||||
// return new MockProductRepository 直接从真实接口数据改为测试模拟数据
|
||||
return new HttpProductRepository;
|
||||
},
|
||||
|
||||
// 创建应用服务实例
|
||||
createProductApplicationService: (): ProductApplicationService => {
|
||||
return new ProductApplicationService(
|
||||
product.bindProductRepository()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import { ErrorText } from "../enums/ErrorEnums"
|
||||
|
||||
/**
|
||||
* 商品实体 - 包含商品的核心属性和行为
|
||||
* 在DDD中,实体具有唯一标识和生命周期
|
||||
*/
|
||||
export class Product {
|
||||
private readonly _id: number;
|
||||
private _name: string;
|
||||
private _description: string;
|
||||
private _price: number;
|
||||
private _imageUrl: string;
|
||||
private _stock: number;
|
||||
private _categoryId: number;
|
||||
|
||||
constructor(
|
||||
id: number,
|
||||
name: string,
|
||||
description: string,
|
||||
price: number,
|
||||
imageUrl: string,
|
||||
stock: number,
|
||||
categoryId: number,
|
||||
) {
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._description = description;
|
||||
this._price = price;
|
||||
this._imageUrl = imageUrl;
|
||||
this._stock = stock;
|
||||
this._categoryId = categoryId;
|
||||
}
|
||||
|
||||
// 实体唯一标识
|
||||
get id(): number {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
public get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
public set name(value: string) {
|
||||
this._name = value;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
return this._description;
|
||||
}
|
||||
public set description(value: string) {
|
||||
this._description = value;
|
||||
}
|
||||
|
||||
|
||||
public get price(): number {
|
||||
return this._price;
|
||||
}
|
||||
public set price(value: number) {
|
||||
if (value < 0) {
|
||||
throw new Error(ErrorText.PriceNotNegativein);
|
||||
}
|
||||
this._price = value;
|
||||
}
|
||||
|
||||
public get imageUrl(): string {
|
||||
return this._imageUrl;
|
||||
}
|
||||
public set imageUrl(value: string) {
|
||||
this._imageUrl = value;
|
||||
}
|
||||
|
||||
public get stock(): number {
|
||||
return this._stock;
|
||||
}
|
||||
public set stock(value: number) {
|
||||
if (value < 0) {
|
||||
throw new Error(ErrorText.StockNotNegativein)
|
||||
}
|
||||
this._stock = value;
|
||||
}
|
||||
|
||||
public get categoryId(): number {
|
||||
return this._categoryId;
|
||||
}
|
||||
public set categoryId(value: number) {
|
||||
this._categoryId = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查商品是否有库存
|
||||
*/
|
||||
hasStock() {
|
||||
return this._stock > 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少商品库存
|
||||
* @param quantity 减少的数量
|
||||
*/
|
||||
reduceStock(quantity: number): void {
|
||||
if (quantity <= 0) {
|
||||
throw new Error(ErrorText.StockIsGreaterThanZero)
|
||||
}
|
||||
|
||||
if (this._stock < quantity) {
|
||||
throw new Error(ErrorText.LowStock)
|
||||
}
|
||||
|
||||
this._stock -= quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class类转化为普通json数据
|
||||
*/
|
||||
toDTO(): Record<string, string | number> {
|
||||
return {
|
||||
id: this._id,
|
||||
name: this._name,
|
||||
description: this._description,
|
||||
price: this._price,
|
||||
imageUrl: this._imageUrl,
|
||||
stock: this._stock,
|
||||
categoryId: this._categoryId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从json数据创建Product类实例
|
||||
* 转化为领域实体 Product
|
||||
*/
|
||||
static fromDTO(data: Record<string, any>): Product {
|
||||
return new Product(
|
||||
data.id,
|
||||
data.name,
|
||||
data.description,
|
||||
data.price,
|
||||
data.imageUrl,
|
||||
data.stock,
|
||||
data.categoryId,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export enum ErrorText {
|
||||
PriceNotNegativein = "商品 价格 不能为负数",
|
||||
StockNotNegativein = "商品 库存 不能为负数",
|
||||
StockIsGreaterThanZero = "减少的库存数量必须大于0",
|
||||
LowStock = "库存不足",
|
||||
IdNotNull = "商品Id不能为空"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1,9 +0,0 @@
|
||||
export interface ProductType {
|
||||
id: number,
|
||||
name: string,
|
||||
description: string,
|
||||
price: number,
|
||||
imageUrl: string,
|
||||
stock: number,
|
||||
categoryId: number,
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1,6 +0,0 @@
|
||||
export * from './application/services/ProductApplicationService';
|
||||
export * from './domain/entities/Product';
|
||||
export * from './domain/enums/ErrorEnums';
|
||||
export * from './domain/type/ProductType';
|
||||
export * from './infr/api/HttpProductRepository';
|
||||
export * from './ports/repositories/ProductRepository';
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Product } from "@domains/product/domain/entities/Product.ts";
|
||||
import { ProductType } from "@domains/product/domain/type/ProductType.ts";
|
||||
import { ProductRepository } from "@domains/product/ports/repositories/ProductRepository";
|
||||
import { http } from "shared/infra";
|
||||
import HttpConfig from "../config/HttpConfig";
|
||||
|
||||
|
||||
/**
|
||||
* HTTP商品仓储实现 - 实现商品数据的HTTP访问
|
||||
* 这是基础设施层,负责具体的数据获取实现
|
||||
*/
|
||||
export class HttpProductRepository implements ProductRepository {
|
||||
|
||||
async findAll(): Promise<Array<ProductType>> {
|
||||
const result: Array<ProductType> = await http.get({ url: HttpConfig.apiUrlList.productsListUrl });
|
||||
const items = result.map((item) => Product.fromDTO(item))
|
||||
return items;
|
||||
}
|
||||
|
||||
async findById(id: number): Promise<Product> {
|
||||
const result: ProductType = await http.get({
|
||||
url: HttpConfig.apiUrlList.productsInfoUrl,
|
||||
data: { id }
|
||||
});
|
||||
return Product.fromDTO(result);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
interface apiUrlListType {
|
||||
productsListUrl: string
|
||||
productsInfoUrl: string
|
||||
}
|
||||
|
||||
export default class HttpConfig {
|
||||
public static apiUrlList: apiUrlListType = {
|
||||
productsListUrl: '/productsList',
|
||||
productsInfoUrl: '/productsInfo',
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { Product } from "../../domain/entities/Product"
|
||||
import { ProductType } from "../../domain/type/ProductType"
|
||||
|
||||
/**
|
||||
* 商品仓储接口 - 定义商品数据访问的契约
|
||||
* 在DDD中,端口定义了领域层与外部世界的交互方式
|
||||
*/
|
||||
export interface ProductRepository {
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
*/
|
||||
findAll(): Promise<Array<ProductType>>;
|
||||
|
||||
/**
|
||||
* 根据ID获取商品详情
|
||||
* @param id 商品ID
|
||||
*/
|
||||
findById(id: number): Promise<Product>
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1,16 +0,0 @@
|
||||
import { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
export const productRouter: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/Products',
|
||||
name: 'ProductList',
|
||||
component: () => import('../views/ProductList/ProductList.jsx'),
|
||||
meta: { title: '商品列表' }
|
||||
},
|
||||
{
|
||||
path: '/ProductDetail',
|
||||
name: 'ProductDetail',
|
||||
component: () => import('../views/ProductDetail/ProductDetail.jsx'),
|
||||
meta: { title: '商品详情' }
|
||||
},
|
||||
]
|
||||
@@ -1,5 +0,0 @@
|
||||
.ProductDetail {
|
||||
.title {
|
||||
color: blue;
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import styles from './ProductDetail.module.less'
|
||||
|
||||
import { product } from '@domains/product/di/productModule';
|
||||
import { ProductType } from '@domains/product/domain/type/ProductType';
|
||||
import { defineComponent, onMounted, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
interface storeType {
|
||||
item: ProductType
|
||||
id: number
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const productService = product.createProductApplicationService();
|
||||
|
||||
const state = reactive<storeType>({
|
||||
item: {} as ProductType,
|
||||
id: 0
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!route.query.id) {
|
||||
throw new Error("缺少id参数");
|
||||
}
|
||||
|
||||
state.id = Number(route.query.id)
|
||||
console.log("id: ", state.id);
|
||||
loadProductDetail()
|
||||
})
|
||||
|
||||
const loadProductDetail = async () => {
|
||||
state.item = await productService.getProductInfo(state.id);
|
||||
console.log("state.item: ", state.item);
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class={ styles.ProductDetail }>
|
||||
<h1 class={ styles.title }>商品领域模块 - 商品详情页面</h1>
|
||||
<hr/>
|
||||
<p>商品id:{ state.id }</p>
|
||||
{ state.item.name }
|
||||
<ul>
|
||||
{
|
||||
Object.entries(state.item).map(([ key, value ]) =>
|
||||
<li>{ key }: { value }</li>
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// export default defineComponent({
|
||||
// setup() {
|
||||
// return () => (
|
||||
// <div>商品详情</div>
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
@@ -1,5 +0,0 @@
|
||||
.ProductList {
|
||||
.test {
|
||||
color: var(--theme-color);
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import styles from './ProductList.module.less'
|
||||
|
||||
import { defineComponent, onMounted, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router';
|
||||
import { product } from '@domains/product/di/productModule';
|
||||
import { ProductType } from '@domains/product/domain/type/ProductType';
|
||||
import { ElCard, ElIcon, Icons } from "shared/element-plus";
|
||||
|
||||
interface storeType {
|
||||
list: Array<ProductType>
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
|
||||
const router = useRouter();
|
||||
const productService = product.createProductApplicationService();
|
||||
|
||||
const state = reactive<storeType>({
|
||||
list: []
|
||||
})
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
loadProductList()
|
||||
})
|
||||
|
||||
const loadProductList = async () => {
|
||||
state.list = await productService.getProductList();
|
||||
console.log("state.list: ", state.list);
|
||||
}
|
||||
|
||||
const ElCardHeader = () => {
|
||||
return (
|
||||
<div class="card-header">
|
||||
<ElIcon size={ 15 }>
|
||||
<Icons.Position></Icons.Position>
|
||||
</ElIcon>
|
||||
<span>Card 头部</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ElCardFooter = () => {
|
||||
return <div>Footer 底部</div>
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class={ styles.ProductList }>
|
||||
<h1 class={ styles.test }>商品领域模块 - 商品列表页面3</h1>
|
||||
<hr/>
|
||||
<h2>从Deno 后端 加载的数据,点击进详情</h2>
|
||||
<ElCard>
|
||||
{ {
|
||||
header: () => ElCardHeader(),
|
||||
footer: () => ElCardFooter(),
|
||||
default: () =>
|
||||
state.list.map(item =>
|
||||
<p key={ item.id } onClick={ () => {
|
||||
router.push({ name: 'ProductDetail', query: { id: item.id } });
|
||||
} }>
|
||||
{ item.id } | { item.name } | ¥{ item.price }
|
||||
</p>
|
||||
)
|
||||
} }
|
||||
</ElCard>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// export default defineComponent({
|
||||
// setup() {
|
||||
// return () => (
|
||||
// <div>商品列表</div>
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
@@ -1,12 +0,0 @@
|
||||
// import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App'
|
||||
|
||||
|
||||
createApp(App)
|
||||
.use(createPinia())
|
||||
.mount('#app')
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
declare module '*.module.less' {
|
||||
const classes: { [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module 'shared/infra' {
|
||||
export const http: any;
|
||||
}
|
||||
|
||||
declare module 'shared/utils' {
|
||||
export const UrlCheckUtils: any;
|
||||
}
|
||||
|
||||
declare module 'shared/element-plus' {
|
||||
import * as ElementPlus from 'element-plus';
|
||||
import * as ElementPlusIcons from '@element-plus/icons-vue';
|
||||
import type { App } from 'vue';
|
||||
export * from 'element-plus';
|
||||
export import Icons = ElementPlusIcons;
|
||||
|
||||
export function init(app: App): void;
|
||||
|
||||
const defaultExport: typeof ElementPlus & {
|
||||
Icons: typeof ElementPlusIcons;
|
||||
init: typeof init;
|
||||
};
|
||||
export default defaultExport;
|
||||
}
|
||||
|
||||
declare module 'shared/normalize' {
|
||||
export const init: () => void;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"verbatimModuleSyntax": false,
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "vue",
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/app/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./src/shared/*"
|
||||
],
|
||||
"@features/*": [
|
||||
"./src/features/*"
|
||||
],
|
||||
"@domains/*": [
|
||||
"./src/domains/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"extends": "@tsconfig/node22/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import { defineConfig, loadEnv } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import federation from 'vite-federation';
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { reg, init } from 'vite-plugin-nacos';
|
||||
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
const { VITE_MODEL_SHARED_URL } = await init(env)
|
||||
|
||||
// console.log("VITE_MODEL_SHARED_URL: ", VITE_MODEL_SHARED_URL);
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
reg(),
|
||||
|
||||
federation({
|
||||
name: env.VITE_SERVER_NAME,
|
||||
remotes: {
|
||||
shared: `${VITE_MODEL_SHARED_URL}/${env.VITE_MODEL_SHARED_NAME}/assets/remoteEntry.js`
|
||||
},
|
||||
exposes: { // 暴露给主应用的资源(按功能分组)
|
||||
'./ProductDomain': './src/domains/product/index.ts', // 领域层(实体、服务、接口等)
|
||||
'./ProductRouter': './src/features/product/router/index.ts' // 商品路由
|
||||
},
|
||||
shared: {
|
||||
vue: { import: false, generate: false, requiredVersion: '^3.5.18' },
|
||||
'vue-router': { import: false, generate: false, requiredVersion: '^4.0.6' },
|
||||
pinia: { import: false, generate: false, requiredVersion: '^3.0.3' },
|
||||
}
|
||||
}),
|
||||
vue(),
|
||||
vueJsx(),
|
||||
vueDevTools(),
|
||||
visualizer({
|
||||
title: "商品模块依赖分析",
|
||||
filename: 'dist/stats.html', // 分析文件输出路径
|
||||
open: false, // 构建完成后自动打开
|
||||
gzipSize: true, // 显示gzip后的大小
|
||||
brotliSize: true, // 显示brotli后的大小
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'esnext',
|
||||
cssCodeSplit: true,
|
||||
rollupOptions: {
|
||||
// 关键配置:告诉 Rollup 不要处理模块联邦的导入
|
||||
external: ['shared/normalize'],
|
||||
output: {
|
||||
minifyInternalExports: false
|
||||
}
|
||||
}
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src/', import.meta.url)),
|
||||
'@shared': fileURLToPath(new URL('./src/shared/', import.meta.url)),
|
||||
'@features': fileURLToPath(new URL('./src/features/', import.meta.url)),
|
||||
'@domains': fileURLToPath(new URL('./src/domains/', import.meta.url))
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
# 请求协议 http or https(建议线上配置证书后,使用https)
|
||||
VITE_PREFIX=http://
|
||||
|
||||
# Nacos 配置中心 ip:端口 | 域名
|
||||
VITE_NACOS_ADDRESS=cluster.nacos.com
|
||||
#VITE_NACOS_ADDRESS=192.168.139.84:8848
|
||||
|
||||
|
||||
# 注册到 Nacos 的服务名称(微前端模块名称)
|
||||
VITE_SERVER_NAME=app-shared
|
||||
|
||||
# 注册到 Nacos 的端口,也是前端运行的端口
|
||||
VITE_SERVER_PORT=1333
|
||||
@@ -1,12 +0,0 @@
|
||||
# 请求协议 http or https(建议线上配置证书后,使用https)
|
||||
VITE_PREFIX=http://
|
||||
|
||||
# Nacos 配置中心 ip:端口 | 域名
|
||||
#VITE_NACOS_ADDRESS=cluster.nacos.com
|
||||
VITE_NACOS_ADDRESS=192.168.139.84:8848
|
||||
|
||||
# 注册到 Nacos 的服务名称(微前端模块名称)
|
||||
VITE_SERVER_NAME=app-shared
|
||||
|
||||
# 注册到 Nacos 的端口,也是前端运行的端口
|
||||
VITE_SERVER_PORT=1333
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link href="/favicon.ico" rel="icon">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/src/main.ts" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"name": "app-shared",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dep": "concurrently \"vite build --watch\" \"npm run preview\"",
|
||||
"preview": "vite preview --port 1333 --strictPort",
|
||||
"build": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"axios": "^1.12.2",
|
||||
"element-plus": "^2.11.4",
|
||||
"vite-plugin-nacos": "link:../vite-plugin-nacos",
|
||||
"vue": "^3.5.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.1.1",
|
||||
"rollup-plugin-visualizer": "^6.0.3",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^7.1.7",
|
||||
"vite-federation": "^3.4.4"
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { defineComponent, onMounted } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
|
||||
onMounted({})
|
||||
return () => (
|
||||
<>
|
||||
<div class="order-federation-placeholder">
|
||||
<h2>共享资源-联邦模块</h2>
|
||||
<p>此页面仅用于 商品模块 的单独开发和调试,可临时引入组件进行本地测试,实际运行时由 主应用
|
||||
加载本项目代码</p>
|
||||
|
||||
{/*<h2>本模块包含的功能:</h2>*/ }
|
||||
{/*<ul>*/ }
|
||||
{/* <li>1:获取商品首页的数据和展示</li>*/ }
|
||||
{/* <li>2:点击视频进入详情页</li>*/ }
|
||||
{/*</ul>*/ }
|
||||
{/*<p></p>*/ }
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
export class HttpConfig {
|
||||
// public static DevBaseUrl: string = "http://127.0.0.1:3210/api"
|
||||
public static DevBaseUrl: string = window.__APP_CONFIG__.VITE_DEVBASEURL
|
||||
|
||||
// public static ProdBaseUrl: string = "http://127.0.0.1:3210/api/"
|
||||
public static ProdBaseUrl: string = window.__APP_CONFIG__.VITE_PRODBASEURL
|
||||
|
||||
public static apiList: string = ""
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export class ThemeConfig {
|
||||
public static primaryColor: string = '#1890ff'; // 主色调
|
||||
public static layoutType: 'side' | 'top' = 'side'; // 侧边栏布局
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './HttpConfig';
|
||||
export * from './ThemeConfig';
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1,3 +0,0 @@
|
||||
export * from './infra';
|
||||
export * from './utils';
|
||||
export * from './config';
|
||||
@@ -1,98 +0,0 @@
|
||||
import axios, {
|
||||
AxiosError,
|
||||
type AxiosInstance,
|
||||
type AxiosRequestConfig,
|
||||
AxiosResponse,
|
||||
InternalAxiosRequestConfig
|
||||
} from "axios";
|
||||
import { UrlCheckUtils } from '../utils/index'
|
||||
|
||||
class AxiosInfra {
|
||||
public instance: AxiosInstance;
|
||||
|
||||
public constructor() {
|
||||
this.instance = axios.create({
|
||||
baseURL: UrlCheckUtils.check(),
|
||||
timeout: Number(window.__APP_CONFIG__.VITE_HTTPTIMEOUT),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
});
|
||||
this.ResponseInterceptor();
|
||||
this.RequestInterceptor();
|
||||
}
|
||||
|
||||
public async get(params: AxiosRequestConfig): Promise<any> {
|
||||
return await this.instance.get(<string>params.url, {
|
||||
params: params.data,
|
||||
headers: Object.assign({ Authorization: localStorage.getItem("token") }, params.headers)
|
||||
});
|
||||
}
|
||||
|
||||
public async post(params: AxiosRequestConfig): Promise<any> {
|
||||
return await this.instance.post(<string>params.url, params.data, {
|
||||
headers: Object.assign({ Authorization: localStorage.getItem("token") }, params.headers)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Put 请求
|
||||
* 请求参数请参考接口:RequestParams
|
||||
* @param params
|
||||
*/
|
||||
public async put(params: AxiosRequestConfig): Promise<any> {
|
||||
return await this.instance.put(<string>params.url, params.data)
|
||||
}
|
||||
|
||||
/**
|
||||
* delete 请求
|
||||
* 请求参数请参考接口:RequestParams
|
||||
* @param params
|
||||
*/
|
||||
public async delete(params: AxiosRequestConfig): Promise<any> {
|
||||
return await this.instance.delete(<string>params.url, { params: params.data })
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应拦截器
|
||||
* @constructor
|
||||
*/
|
||||
public async ResponseInterceptor(): Promise<void> {
|
||||
this.instance.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
return response.data.result;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
console.log(`
|
||||
❌ 请求错误 ❌
|
||||
1、基地址: ${ error.config?.baseURL }
|
||||
2、短地址: ${ error.config?.url }
|
||||
3、请求 方式: ${ error.config?.method }
|
||||
4、请求 参数: ${ JSON.stringify(error.config?.params) }
|
||||
5、请求 头 : ${ JSON.stringify(error.config?.headers) }
|
||||
|
||||
------------------------------------------------------------
|
||||
|
||||
6、错误信息为: ${ error.message }
|
||||
7、错误代码为: ${ error.stack }
|
||||
`);
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加请求拦截器
|
||||
* @constructor
|
||||
*/
|
||||
public async RequestInterceptor(): Promise<void> {
|
||||
this.instance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
|
||||
return config
|
||||
}, function (error: AxiosError) {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default new AxiosInfra();
|
||||
@@ -1 +0,0 @@
|
||||
export {default as http} from "./AxiosInfra"
|
||||
@@ -1 +0,0 @@
|
||||
# 临时占位,上传空文件夹,保存文件结构用,后面删除
|
||||
@@ -1,22 +0,0 @@
|
||||
// app-shared/src/lib/element-plus.ts
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css';
|
||||
import * as ElementPlusIcons from '@element-plus/icons-vue';
|
||||
import { App } from 'vue';
|
||||
|
||||
|
||||
export * from 'element-plus';
|
||||
export * as Icons from '@element-plus/icons-vue';
|
||||
|
||||
// 保留你的 init 函数
|
||||
export const init = (app: App) => {
|
||||
app.use(ElementPlus);
|
||||
};
|
||||
|
||||
// 如果你仍然需要一个默认导出,可以这样做
|
||||
const sharedUI = {
|
||||
...ElementPlus, // 合并 ElementPlus 的所有导出
|
||||
Icons: ElementPlusIcons,
|
||||
init
|
||||
};
|
||||
export default sharedUI;
|
||||
@@ -1,8 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App'
|
||||
|
||||
|
||||
createApp(App)
|
||||
.mount('#app')
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import "./normalize.less";
|
||||
|
||||
export const initNormalizeStyles = () => {
|
||||
console.log("[app-shared] 清除默认样式normalize.less 已加载...")
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020. bmy
|
||||
*/
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body, h1, h2, h3, h4, h5, h6, p, a {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif; /* 1 */
|
||||
line-height: 1.15; /* 2 */
|
||||
-ms-text-size-adjust: 100%; /* 3 */
|
||||
-webkit-text-size-adjust: 100%; /* 3 */
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
width: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
background-color: #fafafa;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main { /* 1 */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct margin in IE 8.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Remove the gray background on active links in IE 10.
|
||||
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||
*/
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
background-color: transparent; /* 1 */
|
||||
-webkit-text-decoration-skip: objects; /* 2 */
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the outline on focused links when they are also active or hovered
|
||||
* in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Firefox 39-.
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font style in Android 4.3-.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct background and color in IE 9-.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers (opinionated).
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||
* controls in Android 4.
|
||||
* 2. Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
html [type="button"], /* 1 */
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the border, margin, and padding in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct display in IE 9-.
|
||||
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10-.
|
||||
* 2. Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||
*/
|
||||
|
||||
details, /* 1 */
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Scripting
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hidden
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import "./variables.less";
|
||||
// import "./mixins.less";
|
||||
|
||||
export const initPubStyles = () => {
|
||||
console.log("[app-shared] variables.less 和 mixins.less 已加载...")
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020. bmy
|
||||
*/
|
||||
|
||||
.clearfix() {
|
||||
&:after,
|
||||
&:before {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
&:after {
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.hairline(@position, @color) when (@position = top) {
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
right: auto;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: @color;
|
||||
display: block;
|
||||
z-index: 15;
|
||||
// .transform-origin(50% 0%);
|
||||
html.pixel-ratio-2 & {
|
||||
.transform(scaleY(0.5));
|
||||
}
|
||||
|
||||
html.pixel-ratio-3 & {
|
||||
.transform(scaleY(0.33));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hairline(@position, @color) when (@position = left) {
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
right: auto;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: @color;
|
||||
display: block;
|
||||
z-index: 15;
|
||||
// .transform-origin(0% 50%);
|
||||
html.pixel-ratio-2 & {
|
||||
.transform(scaleX(0.5));
|
||||
}
|
||||
|
||||
html.pixel-ratio-3 & {
|
||||
.transform(scaleX(0.33));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.hairline(@position, @color) when (@position = bottom) {
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: auto;
|
||||
top: auto;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
background-color: @color;
|
||||
display: block;
|
||||
z-index: 15;
|
||||
|
||||
html.pixel-ratio-2 & {
|
||||
.transform(scaleY(0.5));
|
||||
}
|
||||
|
||||
html.pixel-ratio-3 & {
|
||||
.transform(scaleY(0.33));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hairline(@position, @color) when (@position = right) {
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: @color;
|
||||
display: block;
|
||||
z-index: 15;
|
||||
// .transform-origin(100% 50%);
|
||||
html.pixel-ratio-2 & {
|
||||
.transform(scaleX(0.5));
|
||||
}
|
||||
|
||||
html.pixel-ratio-3 & {
|
||||
.transform(scaleX(0.33));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For right and bottom
|
||||
.hairline-remove(@position) when not (@position = left) and not (@position = top) {
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// For left and top
|
||||
.hairline-remove(@position) when not (@position = right) and not (@position = bottom) {
|
||||
&:before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// For right and bottom
|
||||
.hairline-color(@position, @color) when not (@position = left) and not (@position = top) {
|
||||
&:after {
|
||||
background-color: @color;
|
||||
}
|
||||
}
|
||||
|
||||
// For left and top
|
||||
.hairline-color(@position, @color) when not (@position = right) and not (@position = bottom) {
|
||||
&:before {
|
||||
background-color: @color;
|
||||
}
|
||||
}
|
||||
|
||||
// Encoded SVG Background
|
||||
.encoded-svg-background(@svg) {
|
||||
@url: `encodeURIComponent(@{svg})`;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,@{url}");
|
||||
}
|
||||
|
||||
// Preserve3D
|
||||
.preserve3d() {
|
||||
-webkit-transform-style: preserve-3d;
|
||||
-moz-transform-style: preserve-3d;
|
||||
-ms-transform-style: preserve-3d;
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
|
||||
// Shadow
|
||||
.depth(@level:1) {
|
||||
& when (@level = 0) {
|
||||
box-shadow: none;
|
||||
}
|
||||
& when (@level = 1) {
|
||||
box-shadow: 0 2px 1px -1px rgba(0, 0, 0, .2), 0 1px 1px 0 rgba(0, 0, 0, .14), 0 1px 3px 0 rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 2) {
|
||||
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .2), 0 2px 2px 0 rgba(0, 0, 0, .14), 0 1px 5px 0 rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 3) {
|
||||
box-shadow: 0 3px 3px -2px rgba(0, 0, 0, .2), 0 3px 4px 0 rgba(0, 0, 0, .14), 0 1px 8px 0 rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 4) {
|
||||
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, .2), 0 4px 5px 0 rgba(0, 0, 0, .14), 0 1px 10px 0 rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 5) {
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 5px 8px 0 rgba(0, 0, 0, .14), 0 1px 14px 0 rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 6) {
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 7) {
|
||||
box-shadow: 0 4px 5px -2px rgba(0, 0, 0, .2), 0 7px 10px 1px rgba(0, 0, 0, .14), 0 2px 16px 1px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 8) {
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 9) {
|
||||
box-shadow: 0 5px 6px -3px rgba(0, 0, 0, .2), 0 9px 12px 1px rgba(0, 0, 0, .14), 0 3px 16px 2px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 10) {
|
||||
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, .2), 0 10px 14px 1px rgba(0, 0, 0, .14), 0 4px 18px 3px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 11) {
|
||||
box-shadow: 0 6px 7px -4px rgba(0, 0, 0, .2), 0 11px 15px 1px rgba(0, 0, 0, .14), 0 4px 20px 3px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 12) {
|
||||
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, .2), 0 12px 17px 2px rgba(0, 0, 0, .14), 0 5px 22px 4px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 13) {
|
||||
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, .2), 0 13px 19px 2px rgba(0, 0, 0, .14), 0 5px 24px 4px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 14) {
|
||||
box-shadow: 0 7px 9px -4px rgba(0, 0, 0, .2), 0 14px 21px 2px rgba(0, 0, 0, .14), 0 5px 26px 4px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 15) {
|
||||
box-shadow: 0 8px 9px -5px rgba(0, 0, 0, .2), 0 15px 22px 2px rgba(0, 0, 0, .14), 0 6px 28px 5px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 16) {
|
||||
box-shadow: 0 8px 10px -5px rgba(0, 0, 0, .2), 0 16px 24px 2px rgba(0, 0, 0, .14), 0 6px 30px 5px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 17) {
|
||||
box-shadow: 0 8px 11px -5px rgba(0, 0, 0, .2), 0 17px 26px 2px rgba(0, 0, 0, .14), 0 6px 32px 5px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 18) {
|
||||
box-shadow: 0 9px 11px -5px rgba(0, 0, 0, .2), 0 18px 28px 2px rgba(0, 0, 0, .14), 0 7px 34px 6px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 19) {
|
||||
box-shadow: 0 9px 12px -6px rgba(0, 0, 0, .2), 0 19px 29px 2px rgba(0, 0, 0, .14), 0 7px 36px 6px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 20) {
|
||||
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, .2), 0 20px 31px 3px rgba(0, 0, 0, .14), 0 8px 38px 7px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 21) {
|
||||
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, .2), 0 21px 33px 3px rgba(0, 0, 0, .14), 0 8px 40px 7px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 22) {
|
||||
box-shadow: 0 10px 14px -6px rgba(0, 0, 0, .2), 0 22px 35px 3px rgba(0, 0, 0, .14), 0 8px 42px 7px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 23) {
|
||||
box-shadow: 0 11px 14px -7px rgba(0, 0, 0, .2), 0 23px 36px 3px rgba(0, 0, 0, .14), 0 9px 44px 8px rgba(0, 0, 0, .12);
|
||||
}
|
||||
& when (@level = 24) {
|
||||
box-shadow: 0 11px 15px -7px rgba(0, 0, 0, .2), 0 24px 38px 3px rgba(0, 0, 0, .14), 0 9px 46px 8px rgba(0, 0, 0, .12);
|
||||
}
|
||||
// & when (@level = 1) {
|
||||
// box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px;
|
||||
// }
|
||||
// & when (@level = 2) {
|
||||
// box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px;
|
||||
// }
|
||||
// & when (@level = 3) {
|
||||
// box-shadow: rgba(0, 0, 0, 0.188235) 0px 10px 30px, rgba(0, 0, 0, 0.227451) 0px 6px 10px;
|
||||
// }
|
||||
// & when (@level = 4) {
|
||||
// box-shadow: rgba(0, 0, 0, 0.247059) 0px 14px 45px, rgba(0, 0, 0, 0.219608) 0px 10px 18px;
|
||||
// }
|
||||
// & when (@level = 5) {
|
||||
// box-shadow: rgba(0, 0, 0, 0.298039) 0px 19px 60px, rgba(0, 0, 0, 0.219608) 0px 15px 20px;
|
||||
// }
|
||||
}
|
||||
|
||||
// Highlighted Links
|
||||
.active-highlight(@color:rgba(255, 255, 255, 0.15)) {
|
||||
&:before {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-color: @color;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100% 100%;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
.transition(600ms);
|
||||
}
|
||||
&.active-state:before,
|
||||
html:not(.watch-active-state) &:active:before {
|
||||
opacity: 1;
|
||||
.transition(150ms);
|
||||
}
|
||||
}
|
||||
|
||||
.active-highlight-color(@color) {
|
||||
&:before {
|
||||
background-image: -webkit-radial-gradient(center, circle cover, @color 66%, rgba(red(@color), green(@color), blue(@color), 0) 66%);
|
||||
background-image: radial-gradient(circle at center, @color 66%, rgba(red(@color), green(@color), blue(@color), 0) 66%);
|
||||
}
|
||||
}
|
||||
|
||||
// No Scrollbar
|
||||
.no-scrollbar() {
|
||||
&::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
opacity: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.ellipsis() {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020. bmy
|
||||
*/
|
||||
|
||||
// 公共css样式,可以在 页面组件中直接使用变量名称
|
||||
:root {
|
||||
--theme-color: red;
|
||||
--test-color: yellow;
|
||||
}
|
||||
1
front-end/app-shared/src/types/env.d.ts
vendored
1
front-end/app-shared/src/types/env.d.ts
vendored
@@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
front-end/app-shared/src/types/global.d.ts
vendored
13
front-end/app-shared/src/types/global.d.ts
vendored
@@ -1,13 +0,0 @@
|
||||
export interface AppConfig {
|
||||
VITE_DEVBASEURL: string;
|
||||
VITE_PRODBASEURL: string;
|
||||
VITE_HTTPTIMEOUT: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__APP_CONFIG__: AppConfig;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
@@ -1,12 +0,0 @@
|
||||
import { HttpConfig } from '../config'
|
||||
|
||||
export class UrlCheckUtils {
|
||||
|
||||
public static check() {
|
||||
if (import.meta.env.MODE === 'production') {
|
||||
return HttpConfig.ProdBaseUrl;
|
||||
} else {
|
||||
return HttpConfig.DevBaseUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './UrlCheckUtils';
|
||||
@@ -1,45 +0,0 @@
|
||||
{
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"**/*.test.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"jsx": "preserve",
|
||||
"jsxImportSource": "vue",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"verbatimModuleSyntax": false,
|
||||
"preserveValueImports": false,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/app/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./src/shared/*"
|
||||
],
|
||||
"@features/*": [
|
||||
"./src/features/*"
|
||||
],
|
||||
"@domains/*": [
|
||||
"./src/domains/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { defineConfig, loadEnv, UserConfig } from 'vite'
|
||||
import federation from "vite-federation";
|
||||
// @ts-ignore
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
|
||||
// @ts-ignore
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import { reg, init } from 'vite-plugin-nacos';
|
||||
|
||||
|
||||
export default defineConfig(async ({ mode }): Promise<UserConfig> => {
|
||||
const env = loadEnv(mode, process.cwd());
|
||||
init(env)
|
||||
return {
|
||||
plugins: [
|
||||
reg(),
|
||||
federation({
|
||||
name: env.VITE_SERVER_NAME,
|
||||
transformFileTypes: ['.css', '.less'],
|
||||
exposes: {
|
||||
"./config": "./src/config/index.ts",
|
||||
"./infra": "./src/infra/index.ts",
|
||||
"./utils": "./src/utils/index.ts",
|
||||
"./element-plus": "./src/lib/element-plus.ts",
|
||||
"./normalize": "./src/styles/common/index.ts",
|
||||
"./pub": "./src/styles/pub/index.ts",
|
||||
},
|
||||
shared: {
|
||||
vue: { import: false, generate: false, requiredVersion: '^3.5.18' },
|
||||
axios: { requiredVersion: '^1.12.2' },
|
||||
'element-plus': { requiredVersion: '^2.11.3' }
|
||||
}
|
||||
}),
|
||||
vue(),
|
||||
vueJsx(),
|
||||
visualizer({
|
||||
title: "共享模块依赖分析",
|
||||
filename: 'dist/stats.html', // 分析文件输出路径
|
||||
open: false, // 构建完成后自动打开
|
||||
gzipSize: true, // 显示gzip后的大小
|
||||
brotliSize: true, // 显示brotli后的大小
|
||||
})
|
||||
],
|
||||
build: {
|
||||
target: 'esnext',
|
||||
minify: false,
|
||||
cssCodeSplit: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
format: 'esm',
|
||||
minifyInternalExports: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user