Compare commits
8 Commits
064d368ae4
...
687154f0be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
687154f0be | ||
|
|
6c5b25e0d7 | ||
|
|
64db72b3d6 | ||
|
|
886308ee90 | ||
|
|
05bcc3376c | ||
|
|
4e06a0caed | ||
|
|
523e115906 | ||
|
|
1e16236e13 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,6 +3,9 @@ node_modules
|
||||
dist
|
||||
|
||||
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
@@ -13,6 +16,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
231
README.md
231
README.md
@@ -1,13 +1,16 @@
|
||||
|
||||
|
||||
## DDD领域驱动设计的微服务前端案例
|
||||
|
||||
> 持续开发中,及时拉取代码,文档还不够完善,先凑合看!!目前只是demo,代码还会进行大规模封装细化和改造!
|
||||
|
||||
搭建的一套**基于DDD领域驱动设计的微服务前端**,适用于大型企业,开发大型复杂且拓展灵活的前端应用的架构方案。
|
||||
搭建的一套 **基于DDD领域驱动设计的微服务前端** 项目脚手架,适用于大型企业,开发大型复杂且拓展灵活的前端应用的架构方案。
|
||||
|
||||
代码编写建议使用 **OOP面向对象 + TypeScript + JSX** 进行开发,让项目更适合开发和维护,获得更好更极致的编码体验。
|
||||
|
||||
本案例中 **[主应用]** 采取的就是OOP风格,当然如果你不熟悉上面的写法,那么可以在
|
||||
【其他模块】中使用Vue2,Vue3都行,但仍然需要遵守DDD的规范进行开发!
|
||||
|
||||
## 技术栈:
|
||||
|
||||
项目大致技术栈:
|
||||
- [DDD领域驱动设计](https://zh.wikipedia.org/zh-cn/%E9%A0%98%E5%9F%9F%E9%A9%85%E5%8B%95%E8%A8%AD%E8%A8%88)
|
||||
- [@originjs/vite-plugin-federation](https://www.npmjs.com/package/@originjs/vite-plugin-federation)
|
||||
- [Deno](https://deno.com/)
|
||||
@@ -22,13 +25,6 @@
|
||||
- [axios](https://axios-http.com/)
|
||||
- 等等等....
|
||||
|
||||
## 开发任务
|
||||
|
||||
- [x] 基于DDD理念改造
|
||||
- [x] 基于 vite-plugin-federation 拆分领域模块为联邦模块,实现微前端
|
||||
- [ ] 领域模块实现 docker 自动化构建部署上线
|
||||
- [ ] Deno集成GraphQL
|
||||
|
||||
## 文件夹结构
|
||||
|
||||
**根目录:**
|
||||
@@ -91,89 +87,164 @@ src/
|
||||
└── index.html # 主HTML文件
|
||||
```
|
||||
|
||||
## 代码风格
|
||||
|
||||
## 问 & 答
|
||||
```tsx
|
||||
// 首页 页面
|
||||
import { Autobind, Component, Link, Mut, VueComponent } from 'vue3-oop'
|
||||
import Header from '@/components/Header'
|
||||
|
||||
interface ListItemType {
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
@Component()
|
||||
export default class Home extends VueComponent {
|
||||
|
||||
@Mut()
|
||||
count: number = 1
|
||||
|
||||
@Mut()
|
||||
text: string = "Home props 传给 Header 的字符串"
|
||||
|
||||
@Mut()
|
||||
newList: ListItemType[] = [
|
||||
{ id: 1, title: '新闻1' },
|
||||
{ id: 2, title: '新闻2' },
|
||||
{ id: 3, title: '新闻3' },
|
||||
]
|
||||
|
||||
@Link() H1?: HTMLDivElement
|
||||
|
||||
@Autobind()
|
||||
async addCount() {
|
||||
this.count++
|
||||
this.text = "22222"
|
||||
console.log(this.H1)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="home" ref="H1">
|
||||
<h1>首页</h1>
|
||||
<p onClick={ this.addCount }>count响应式变量(点我):{ this.count }</p>
|
||||
<Header
|
||||
v-slots={ {
|
||||
default: () => <div class="default">父到子 default 插槽</div>,
|
||||
haveName: () => <div class="haveName">父到子 具名 插槽</div>,
|
||||
item: (val) => <li key={ val.id }>{ val.title }</li>
|
||||
} }
|
||||
list={ this.newList }
|
||||
text={ this.text }>
|
||||
</Header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```tsx
|
||||
// 头部组件
|
||||
import { Component, ComponentProps, Hook, useProps, VueComponent } from 'vue3-oop'
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { VNodeChild } from "vue";
|
||||
|
||||
interface ListItemType {
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
interface HeaderProps {
|
||||
text?: string;
|
||||
list: ListItemType[]
|
||||
slots: {
|
||||
default: () => JSX.Element;
|
||||
haveName: () => JSX.Element;
|
||||
item: (val: ListItemType) => VNodeChild;
|
||||
}
|
||||
}
|
||||
|
||||
@Component()
|
||||
export default class Header extends VueComponent<HeaderProps> {
|
||||
|
||||
// required 的 true or false 需要和 类型中 text的? 对应
|
||||
static defaultProps: ComponentProps<HeaderProps> = {
|
||||
text: { default: '默认数值', required: true },
|
||||
list: { default: () => [] }
|
||||
};
|
||||
|
||||
@Hook('Mounted')
|
||||
mounted() {
|
||||
console.log("this: ", this);
|
||||
console.log('Header 组件的 mounted 生命周期...')
|
||||
}
|
||||
|
||||
render() {
|
||||
const { text, list } = useProps<HeaderProps>();
|
||||
return (
|
||||
<div class="header">
|
||||
头部组件
|
||||
<p>{ text }</p>
|
||||
{ this.context.slots.default?.() }
|
||||
{ this.context.slots.haveName?.() }
|
||||
<ul>
|
||||
{
|
||||
list.map(
|
||||
item =>
|
||||
this.context.slots.item?.(item)
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 开发任务
|
||||
|
||||
- [x] 基于DDD理念改造
|
||||
- [x] 基于 vite-plugin-federation 拆分领域模块为联邦模块,实现微前端
|
||||
- [x] 基于Vue3+OOP+JSX改造项目代码
|
||||
- [ ] 领域模块实现 docker 自动化构建部署上线
|
||||
- [ ] Deno集成GraphQL
|
||||
|
||||
## 问 & 答
|
||||
|
||||
**问: 为什么vue项目要抛弃SFC写法而尝试jsx/tsx方式来编码?**
|
||||
|
||||
答:https://juejin.cn/post/6911175470255964174
|
||||
|
||||
|
||||
**问:不会 vue3 jsx 语法,怎么办?**
|
||||
|
||||
答:https://juejin.cn/post/7141674726434439176
|
||||
|
||||
## 运行 & 打包
|
||||
|
||||
**问:vue3 jsx defineComponent 语法,实际项目中 setup 代码太长,怎么办?**
|
||||
```bash
|
||||
# 1 下载代码
|
||||
git clone https://gitee.com/bmycode/dddmicro-frontend.git
|
||||
|
||||
答:使用组合式函数 (Composables)
|
||||
# 2 运行后端
|
||||
cd back-end # 进入 后端 文件夹
|
||||
deno install # 安装依赖
|
||||
deno run dev # 运行后端
|
||||
|
||||
案例:
|
||||
# 3 运行前端
|
||||
cd front-end # 进入 前端 文件夹
|
||||
pnpm install # 会同时为 所有模块 安装依赖
|
||||
pnpm add -g concurrently # 全局安装 并发命令执行 插件,局部安装也行,随你。
|
||||
pnpm run all # 构建 app-product 和 app-shared 并启动预览,并开发阶段启动 main-app 模块
|
||||
# 注意:命令跑完后 ctrl + c 停止,重跑一次!!因为子模块内命令并发执行预览跑在构建之前,解决办法很简单,后面再搞。
|
||||
pnpm run all
|
||||
# 打开 main-app 主模块访问地址即可:[http://localhost:1300/](http://localhost:1300/)
|
||||
|
||||
```jsx
|
||||
// composables/useUserData.js
|
||||
import { ref, onMounted } from 'vue'
|
||||
import api from '@/api'
|
||||
# 4 部署上线
|
||||
pnpm run build:main # 构建并预览主应用,预览打包后的主应用,没问题,ctrl + c 停止。
|
||||
# 子模块在 第三步 已经打包好了,不需要重新打包了。
|
||||
|
||||
export function useUserData(userId) {
|
||||
const user = ref(null)
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
# 上传部署 各模块内的 dist/ 文件夹到服务器上,注意修改 vite.config.ts 内 plugins.federation.remotes.xxx 的地址!
|
||||
# 后期会实现集中管理 地址配置,并根据环境变量动态加载!!
|
||||
|
||||
const fetchUser = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
user.value = await api.getUser(userId)
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(fetchUser)
|
||||
|
||||
return {
|
||||
user,
|
||||
loading,
|
||||
error,
|
||||
refetch: fetchUser
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
主组件:
|
||||
|
||||
```jsx
|
||||
// UserProfile.jsx
|
||||
import { defineComponent } from 'vue'
|
||||
import { useUserData } from '@/composables/useUserData'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'UserProfile',
|
||||
|
||||
props: {
|
||||
userId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
setup(props) {
|
||||
const { user, loading, error, refetch } = useUserData(props.userId)
|
||||
|
||||
return () => (
|
||||
<div>
|
||||
{loading.value && <div>Loading...</div>}
|
||||
{error.value && <div>Error: {error.value}</div>}
|
||||
{user.value && (
|
||||
<div>
|
||||
<h2>{user.value.name}</h2>
|
||||
<p>{user.value.email}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
```
|
||||
@@ -22,9 +22,9 @@ export default defineConfig({
|
||||
'./ProductRouter': './src/features/product/router/index.ts' // 商品路由
|
||||
},
|
||||
shared: {
|
||||
vue: {generate: false, requiredVersion: '^3.5.18'},
|
||||
'vue-router': { generate:false, requiredVersion: '^4.0.6' },
|
||||
pinia: { generate:false, requiredVersion: '^3.0.3' }
|
||||
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(),
|
||||
|
||||
@@ -17,7 +17,7 @@ export default defineConfig({
|
||||
"./utils": "./src/utils/index.ts",
|
||||
},
|
||||
shared: {
|
||||
axios: {generate: false, requiredVersion: '^1.12.2'}
|
||||
axios: {requiredVersion: '^1.12.2'}
|
||||
}
|
||||
})
|
||||
],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "vue-project",
|
||||
"name": "main-app",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
@@ -19,16 +19,15 @@
|
||||
"vue-router": "^4.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@abraham/reflection": "^0.13.0",
|
||||
"@originjs/vite-plugin-federation": "^1.4.1",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/node": "^22.16.5",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
||||
"@vue/tsconfig": "^0.7.0",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"@types/node": "^24.5.2",
|
||||
"@vue3-oop/plugin-vue-jsx": "^1.5.1",
|
||||
"injection-js": "^2.5.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "~5.8.0",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0",
|
||||
"vue-tsc": "^3.0.4"
|
||||
"vue3-oop": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import { Component, VueComponent } from 'vue3-oop'
|
||||
import { RouterLink, RouterView } from "vue-router";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
setup() {
|
||||
return () => (
|
||||
@Component()
|
||||
export class App extends VueComponent {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<div id="nav">
|
||||
<RouterLink to="/">首页</RouterLink> |
|
||||
@@ -15,4 +15,4 @@ export default defineComponent({
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,59 +1,54 @@
|
||||
import { defineComponent, defineEmits, onMounted, PropType } from 'vue'
|
||||
import type { JSX } from "vue/jsx-runtime";
|
||||
import { Component, ComponentProps, Hook, useProps, VueComponent } from 'vue3-oop'
|
||||
import { JSX } from "vue/jsx-runtime";
|
||||
import { VNodeChild } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Header",
|
||||
props: {
|
||||
text: {
|
||||
type: Array<{ id: number, title: string }>,
|
||||
default: () => []
|
||||
},
|
||||
// 点击事件
|
||||
onClick: {
|
||||
type: Function as PropType<() => void>,
|
||||
default: () => { }
|
||||
}
|
||||
},
|
||||
emits: {
|
||||
sendMessage: (message: string) => typeof message === 'string'
|
||||
},
|
||||
interface ListItemType {
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
setup(props, { attrs, slots, emit, expose }) {
|
||||
interface HeaderProps {
|
||||
text?: string;
|
||||
list: ListItemType[]
|
||||
slots: {
|
||||
default: () => JSX.Element;
|
||||
haveName: () => JSX.Element;
|
||||
item: (val: ListItemType) => VNodeChild;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log("onMounted")
|
||||
})
|
||||
@Component()
|
||||
export default class Header extends VueComponent<HeaderProps> {
|
||||
|
||||
const handleClick = () => {
|
||||
console.log("handleClick")
|
||||
emit('sendMessage', '子给父的数据')
|
||||
}
|
||||
// required 的 true or false 需要和 类型中 text的? 对应
|
||||
static defaultProps: ComponentProps<HeaderProps> = {
|
||||
text: { default: '默认数值', required: true },
|
||||
list: { default: () => [] }
|
||||
};
|
||||
|
||||
const button = (): JSX.Element => {
|
||||
return <button onClick={handleClick}>父向子emit点我</button>
|
||||
}
|
||||
@Hook('Mounted')
|
||||
mounted() {
|
||||
console.log("this: ", this);
|
||||
console.log('Header 组件的 mounted 生命周期...')
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div>
|
||||
<h1 onClick={props.onClick}>头部组件-点我</h1>
|
||||
|
||||
{button()}
|
||||
|
||||
{slots.default?.()}
|
||||
|
||||
{slots.header?.()}
|
||||
|
||||
<div class="ScopedSlots">
|
||||
<p>作用于插槽:</p>
|
||||
<ul>
|
||||
{
|
||||
props.text.map(item => {
|
||||
return slots.scoped?.(item)
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
render() {
|
||||
const { text, list } = useProps<HeaderProps>();
|
||||
return (
|
||||
<div class="header">
|
||||
头部组件
|
||||
<p>{ text }</p>
|
||||
{ this.context.slots.default?.() }
|
||||
{ this.context.slots.haveName?.() }
|
||||
<ul>
|
||||
{
|
||||
list.map(
|
||||
item =>
|
||||
this.context.slots.item?.(item)
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
// import './assets/main.css'
|
||||
|
||||
import '@abraham/reflection'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App'
|
||||
import { App } from './App'
|
||||
import router from './router'
|
||||
|
||||
createApp(App)
|
||||
.use(createPinia())
|
||||
.use(router)
|
||||
.mount('#app')
|
||||
|
||||
class Main {
|
||||
constructor() {
|
||||
createApp(App)
|
||||
.use(createPinia())
|
||||
.use(router)
|
||||
.mount('#app')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
new Main
|
||||
|
||||
|
||||
|
||||
25
front-end/main-app/src/app/router/Config.ts
Normal file
25
front-end/main-app/src/app/router/Config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {productRouter} from 'product/ProductRouter'
|
||||
import {RouteRecordRaw} from 'vue-router'
|
||||
|
||||
|
||||
const baseRouter: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('../views/Home'),
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/About'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('../views/NotFound')
|
||||
}
|
||||
];
|
||||
|
||||
// 聚合所有功能模块的路由
|
||||
const routes: RouteRecordRaw[] = [...baseRouter, ...productRouter];
|
||||
export default routes;
|
||||
@@ -1,27 +1,5 @@
|
||||
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router'
|
||||
import {productRouter} from 'product/ProductRouter'
|
||||
|
||||
|
||||
const baseRouter: Array<RouteRecordRaw> = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('../views/Home'),
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/About'),
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('../views/NotFound')
|
||||
}
|
||||
];
|
||||
|
||||
// 聚合所有功能模块的路由
|
||||
const routes: RouteRecordRaw[] = [...baseRouter, ...productRouter];
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import routes from './Config'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import {Component, VueComponent} from 'vue3-oop'
|
||||
|
||||
export default defineComponent({
|
||||
name: "About",
|
||||
setup() {
|
||||
return () => (
|
||||
<div class="about">
|
||||
<h1>我的</h1>
|
||||
</div>
|
||||
@Component()
|
||||
export default class About extends VueComponent {
|
||||
render() {
|
||||
return (
|
||||
<div class="about">
|
||||
<h1>我的</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,64 +1,51 @@
|
||||
import {defineComponent, onMounted, reactive} from 'vue'
|
||||
import { Autobind, Component, Link, Mut, VueComponent } from 'vue3-oop'
|
||||
import Header from '@/components/Header'
|
||||
import {UrlCheckUtils} from 'shared/utils'
|
||||
import {useCounterStore} from '@/stores/counter'
|
||||
import {storeToRefs} from 'pinia'
|
||||
|
||||
interface stateType {
|
||||
list: ListItemType[]
|
||||
}
|
||||
|
||||
interface ListItemType {
|
||||
id: number
|
||||
title: string
|
||||
id: number
|
||||
title: string
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: "Home",
|
||||
setup() {
|
||||
@Component()
|
||||
export default class Home extends VueComponent {
|
||||
|
||||
const counterStore = useCounterStore()
|
||||
const { increment } = counterStore
|
||||
const { count, doubleCount } = storeToRefs(counterStore)
|
||||
@Mut()
|
||||
count: number = 1
|
||||
|
||||
const state = reactive<stateType>({
|
||||
list: [
|
||||
@Mut()
|
||||
text: string = "Home props 传给 Header 的字符串"
|
||||
|
||||
@Mut()
|
||||
newList: ListItemType[] = [
|
||||
{ id: 1, title: '新闻1' },
|
||||
{ id: 2, title: '新闻2' },
|
||||
{ id: 3, title: '新闻3' },
|
||||
]
|
||||
})
|
||||
]
|
||||
|
||||
@Link() H1?: HTMLDivElement
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
// console.log("onMounted: ", HttpConfig.DevBaseUrl);
|
||||
console.log("check: ", UrlCheckUtils.check())
|
||||
})
|
||||
|
||||
// 定义处理子组件事件的函数
|
||||
const handleChildEvent = (message: string) => {
|
||||
console.log('收到子组件事件:', message)
|
||||
@Autobind()
|
||||
async addCount() {
|
||||
this.count++
|
||||
this.text = "22222"
|
||||
console.log(this.H1)
|
||||
}
|
||||
|
||||
|
||||
return () => (
|
||||
<div class="home">
|
||||
<Header
|
||||
text={state.list}
|
||||
onClick={() => {
|
||||
console.log("通过props自定义事件传给子组件");
|
||||
}}
|
||||
onSendMessage={handleChildEvent}>
|
||||
{{
|
||||
scoped: (item: ListItemType) => <li key={item.id}>{item.title}</li>,
|
||||
header: () => <div class="header">父到子 具名 插槽</div>,
|
||||
default: () => <div class="default">父到子 default 插槽</div>
|
||||
}}
|
||||
</Header>
|
||||
<h1>首页</h1>
|
||||
<div class="count" onClick={increment}>点我+1:{count.value}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
render() {
|
||||
return (
|
||||
<div class="home" ref="H1">
|
||||
<h1>首页</h1>
|
||||
<p onClick={ this.addCount }>count响应式变量(点我):{ this.count }</p>
|
||||
<Header
|
||||
v-slots={ {
|
||||
default: () => <div class="default">父到子 default 插槽</div>,
|
||||
haveName: () => <div class="haveName">父到子 具名 插槽</div>,
|
||||
item: (val) => <li key={ val.id }>{ val.title }</li>
|
||||
} }
|
||||
list={ this.newList }
|
||||
text={ this.text }>
|
||||
</Header>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
46
front-end/main-app/src/shared/types/ shims-tsx.d.ts
vendored
Normal file
46
front-end/main-app/src/shared/types/ shims-tsx.d.ts
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
import Vue, {VNode} from 'vue'
|
||||
|
||||
declare global {
|
||||
namespace Vue {
|
||||
any
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface ElementAttributesProperty {
|
||||
$props: any // 设置类组件的props类型检查属性
|
||||
}
|
||||
|
||||
// tslint:disable no-empty-interface
|
||||
interface Element extends VNode {
|
||||
}
|
||||
|
||||
// tslint:disable no-empty-interface
|
||||
interface ElementClass extends Vue {
|
||||
}
|
||||
|
||||
interface IntrinsicAttributes {
|
||||
// 给组件元素增加属性
|
||||
on?: any
|
||||
vModel?: any
|
||||
vShow?: boolean
|
||||
value?: any
|
||||
oninput?: Function
|
||||
nativeOnClick?: (event: MouseEvent) => void
|
||||
oncontextmenu?: (event: MouseEvent) => void
|
||||
onmouseup?: (event: MouseEvent) => void
|
||||
onclick?: (event: MouseEvent) => void
|
||||
onclick_prevent?: (event: MouseEvent) => void
|
||||
ref?: string
|
||||
key?: string | number
|
||||
class?: string | string[]
|
||||
slot?: string
|
||||
style?: Partial<CSSStyleDeclaration> | string | object | object[]
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any
|
||||
}
|
||||
}
|
||||
}
|
||||
5
front-end/main-app/src/shared/types/shims-vue.d.ts
vendored
Normal file
5
front-end/main-app/src/shared/types/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module '*.vue' {
|
||||
import type {DefineComponent} from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": [
|
||||
"src/shared/types/*.d.ts",
|
||||
"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 +1,45 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"useDefineForClassFields": false,
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"jsx": "preserve",
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"dom",
|
||||
"dom.iterable"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/app/*"
|
||||
],
|
||||
"@shared/*": [
|
||||
"./src/shared/*"
|
||||
],
|
||||
"@features/*": [
|
||||
"./src/features/*"
|
||||
],
|
||||
"@domains/*": [
|
||||
"./src/domains/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/shared/types/*.d.ts",
|
||||
"src/**/*.ts",
|
||||
"src/**/*.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.vue"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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,12 +1,10 @@
|
||||
import {fileURLToPath, URL} from 'node:url'
|
||||
|
||||
import {defineConfig} from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
import vueJsx from '@vue3-oop/plugin-vue-jsx'
|
||||
|
||||
import federation from '@originjs/vite-plugin-federation';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 1300,
|
||||
@@ -14,9 +12,7 @@ export default defineConfig({
|
||||
strictPort: true,
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
vueDevTools(),
|
||||
federation({
|
||||
name: 'mainApp', // 主应用模块名(联邦模块需通过此名引用)
|
||||
remotes: { // 配置远程联邦模块地址(开发/生产需对应)
|
||||
@@ -25,7 +21,6 @@ export default defineConfig({
|
||||
},
|
||||
shared: {
|
||||
vue: { requiredVersion: '^3.5.18' },
|
||||
axios: { requiredVersion: '^1.12.2' },
|
||||
'vue-router': { requiredVersion: '^4.0.6' },
|
||||
pinia: { requiredVersion: '^3.0.3' }
|
||||
}
|
||||
|
||||
15
front-end/package.json
Normal file
15
front-end/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "front-end",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"init": "pnpm install",
|
||||
"all": "concurrently \"pnpm run build\" \"pnpm run dev:main\"",
|
||||
"build": "pnpm --parallel --filter app-product --filter app-shared dep",
|
||||
"dev:main": "pnpm --filter main-app dev",
|
||||
"build:main": "pnpm --filter main-app dep"
|
||||
}
|
||||
}
|
||||
4
front-end/pnpm-workspace.yaml
Normal file
4
front-end/pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
- 'main-app'
|
||||
- 'app-product'
|
||||
- 'app-shared'
|
||||
Reference in New Issue
Block a user