Compare commits

...

2 Commits

Author SHA1 Message Date
编码猿
550bf0039b 移除 app-shared 为独立模块 2025-10-07 21:14:15 +08:00
编码猿
2653800c3f 从主项目中删除 app-product,抽离为独立仓库存储!! 2025-10-07 04:37:32 +08:00
69 changed files with 8 additions and 2275 deletions

View File

@@ -13,7 +13,6 @@
![架构图](./doc/jgt.png)
## CI/CD 自动化集群部署
![cicd](./doc/newci.png)
@@ -247,11 +246,14 @@ export default class Header extends TSX<Props>()(Vue) implements Props {
- 2: 在1台Linux服务器上部署公司自己的 Docker Registry 镜像存储库
- 3: 准本 N台运行 前端各模块 的Linux服务器并提前组成N个Docker Swarm 集群,以供前端各模块部署使用
- 4: 准本 >=3台运行 Nacos服务中心 的Linux服务器并提前组成Docker Swarm 集群,提前运行好,确保服务可用
- [Nacos 部署文档](./docker/nacos-cluster/README.md)
- [Nacos 部署文档](./docker/nacos-cluster/README.md)
- 5: 准本 一台 运行 traefik 网关的Linux服务器提前运行好确保服务可用
- [Traefik 部署文档](./docker/traefik/README.md)
- [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)

View File

@@ -1,5 +1,5 @@
# 包含 [skip actions] 的提交会跳过工作流
git commit -m "修复样式问题 [skip actions]"
git commit -m "修复样式问题[skip actions]"
```bash

View File

@@ -1,14 +0,0 @@
node_modules
dist
.git
.DS_Store
*.log
docker/ # 排除 docker 配置文件夹本身
.editorconfig
.eslintrc.js
.prettierrc
.vscode
.idea
*.swp
*.swo

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;"]

View File

@@ -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: 确认或编辑 Dockerfilenginx.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"
# 如果出问题了,找 技术负责人!
```
---
**到这一步,上线 配置已经搞完,很简单吧!剩下的,你提交代码到仓库就行,项目会自动构建,自动打包发布到公司内网集群中部署和运行,不用你管了!!**

View File

@@ -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:

View File

@@ -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 网络驱动,支持跨主机通信

View File

@@ -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

View File

@@ -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>

View File

@@ -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';
}
}

View File

@@ -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"
}
}

View File

@@ -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>
</>
)
}
})

View File

@@ -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);
}
}

View File

@@ -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()
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -1,7 +0,0 @@
export enum ErrorText {
PriceNotNegativein = "商品 价格 不能为负数",
StockNotNegativein = "商品 库存 不能为负数",
StockIsGreaterThanZero = "减少的库存数量必须大于0",
LowStock = "库存不足",
IdNotNull = "商品Id不能为空"
}

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -1,9 +0,0 @@
export interface ProductType {
id: number,
name: string,
description: string,
price: number,
imageUrl: string,
stock: number,
categoryId: number,
}

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -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';

View File

@@ -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);
}
}

View File

@@ -1,11 +0,0 @@
interface apiUrlListType {
productsListUrl: string
productsInfoUrl: string
}
export default class HttpConfig {
public static apiUrlList: apiUrlListType = {
productsListUrl: '/productsList',
productsInfoUrl: '/productsInfo',
}
}

View File

@@ -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>
}

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -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: '商品详情' }
},
]

View File

@@ -1,5 +0,0 @@
.ProductDetail {
.title {
color: blue;
}
}

View File

@@ -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>
// )
// }
// })

View File

@@ -1,5 +0,0 @@
.ProductList {
.test {
color: var(--theme-color);
}
}

View File

@@ -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>
// )
// }
// })

View File

@@ -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')

View File

@@ -1,4 +0,0 @@
declare module '*.module.less' {
const classes: { [key: string]: string }
export default classes
}

View File

@@ -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;
}

View File

@@ -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/*"
]
}
}
}

View File

@@ -1,11 +0,0 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

View File

@@ -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"]
}
}

View File

@@ -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))
},
},
}
});

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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"
}
}

View File

@@ -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>
</>
)
}
})

View File

@@ -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 = ""
}

View File

@@ -1,4 +0,0 @@
export class ThemeConfig {
public static primaryColor: string = '#1890ff'; // 主色调
public static layoutType: 'side' | 'top' = 'side'; // 侧边栏布局
}

View File

@@ -1,2 +0,0 @@
export * from './HttpConfig';
export * from './ThemeConfig';

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -1,3 +0,0 @@
export * from './infra';
export * from './utils';
export * from './config';

View File

@@ -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();

View File

@@ -1 +0,0 @@
export {default as http} from "./AxiosInfra"

View File

@@ -1 +0,0 @@
# 临时占位,上传空文件夹,保存文件结构用,后面删除

View File

@@ -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;

View File

@@ -1,8 +0,0 @@
import { createApp } from 'vue'
import App from './App'
createApp(App)
.mount('#app')

View File

@@ -1,5 +0,0 @@
import "./normalize.less";
export const initNormalizeStyles = () => {
console.log("[app-shared] 清除默认样式normalize.less 已加载...")
}

View File

@@ -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;
}

View File

@@ -1,6 +0,0 @@
import "./variables.less";
// import "./mixins.less";
export const initPubStyles = () => {
console.log("[app-shared] variables.less 和 mixins.less 已加载...")
}

View File

@@ -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;
}

View File

@@ -1,9 +0,0 @@
/*
* Copyright (c) 2020. bmy
*/
// 公共css样式可以在 页面组件中直接使用变量名称
:root {
--theme-color: red;
--test-color: yellow;
}

View File

@@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@@ -1,13 +0,0 @@
export interface AppConfig {
VITE_DEVBASEURL: string;
VITE_PRODBASEURL: string;
VITE_HTTPTIMEOUT: string;
}
declare global {
interface Window {
__APP_CONFIG__: AppConfig;
}
}
export {};

View File

@@ -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;
}
}
}

View File

@@ -1 +0,0 @@
export * from './UrlCheckUtils';

View File

@@ -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/*"
]
}
}
}

View File

@@ -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
}
}
}
}
});