基于Vue3+OOP+JSX改造项目代码,增加pnpm统一管理依赖

This commit is contained in:
编码猿
2025-09-19 02:20:18 +08:00
parent 064d368ae4
commit 1e16236e13
18 changed files with 254 additions and 224 deletions

View File

@@ -26,6 +26,7 @@
- [x] 基于DDD理念改造 - [x] 基于DDD理念改造
- [x] 基于 vite-plugin-federation 拆分领域模块为联邦模块,实现微前端 - [x] 基于 vite-plugin-federation 拆分领域模块为联邦模块,实现微前端
- [x] 基于Vue3+OOP+JSX改造项目代码让项目更适合开发和维护
- [ ] 领域模块实现 docker 自动化构建部署上线 - [ ] 领域模块实现 docker 自动化构建部署上线
- [ ] Deno集成GraphQL - [ ] Deno集成GraphQL

View File

@@ -22,9 +22,9 @@ export default defineConfig({
'./ProductRouter': './src/features/product/router/index.ts' // 商品路由 './ProductRouter': './src/features/product/router/index.ts' // 商品路由
}, },
shared: { shared: {
vue: {generate: false, requiredVersion: '^3.5.18'}, vue: {import: false, generate: false, requiredVersion: '^3.5.18'},
'vue-router': { generate:false, requiredVersion: '^4.0.6' }, 'vue-router': {import: false, generate: false, requiredVersion: '^4.0.6'},
pinia: { generate:false, requiredVersion: '^3.0.3' } pinia: {import: false, generate: false, requiredVersion: '^3.0.3'},
} }
}), }),
vue(), vue(),

View File

@@ -17,7 +17,7 @@ export default defineConfig({
"./utils": "./src/utils/index.ts", "./utils": "./src/utils/index.ts",
}, },
shared: { shared: {
axios: {generate: false, requiredVersion: '^1.12.2'} axios: {requiredVersion: '^1.12.2'}
} }
}) })
], ],

View File

@@ -1,5 +1,5 @@
{ {
"name": "vue-project", "name": "main-app",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"type": "module", "type": "module",
@@ -19,16 +19,15 @@
"vue-router": "^4.0.6" "vue-router": "^4.0.6"
}, },
"devDependencies": { "devDependencies": {
"@abraham/reflection": "^0.13.0",
"@originjs/vite-plugin-federation": "^1.4.1", "@originjs/vite-plugin-federation": "^1.4.1",
"@tsconfig/node22": "^22.0.2", "@types/node": "^24.5.2",
"@types/node": "^22.16.5", "@vue3-oop/plugin-vue-jsx": "^1.5.1",
"@vitejs/plugin-vue": "^6.0.1", "injection-js": "^2.5.0",
"@vitejs/plugin-vue-jsx": "^5.0.1", "tslib": "^2.8.1",
"@vue/tsconfig": "^0.7.0",
"npm-run-all2": "^8.0.4",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"vite": "^7.0.6", "vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0", "vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^3.0.4" "vue3-oop": "^1.2.1"
} }
} }

View File

@@ -1,10 +1,10 @@
import { defineComponent } from 'vue' import {Component, VueComponent} from 'vue3-oop'
import {RouterLink, RouterView} from "vue-router"; import {RouterLink, RouterView} from "vue-router";
export default defineComponent({ @Component()
name: 'App', export class App extends VueComponent {
setup() { render() {
return () => ( return (
<> <>
<div id="nav"> <div id="nav">
<RouterLink to="/"></RouterLink> | <RouterLink to="/"></RouterLink> |
@@ -15,4 +15,4 @@ export default defineComponent({
</> </>
) )
} }
}) }

View File

@@ -1,59 +1,56 @@
import { defineComponent, defineEmits, onMounted, PropType } from 'vue' import {Component, ComponentProps, Hook, useProps, VueComponent} from 'vue3-oop'
import type { JSX } from "vue/jsx-runtime"; import {JSX} from "vue/jsx-runtime";
import {VNodeChild} from "vue";
export default defineComponent({ interface ListItemType {
name: "Header", id: number
props: { title: string
text: {
type: Array<{ id: number, title: string }>,
default: () => []
},
// 点击事件
onClick: {
type: Function as PropType<() => void>,
default: () => { }
}
},
emits: {
sendMessage: (message: string) => typeof message === 'string'
},
setup(props, { attrs, slots, emit, expose }) {
onMounted(() => {
console.log("onMounted")
})
const handleClick = () => {
console.log("handleClick")
emit('sendMessage', '子给父的数据')
} }
const button = (): JSX.Element => { interface HeaderProps {
return <button onClick={handleClick}>emit点我</button> text?: string;
list: ListItemType[]
slots: {
default: () => JSX.Element;
haveName: () => JSX.Element;
item: (val: ListItemType) => VNodeChild;
}
} }
return () => ( @Component()
<div> export default class Header extends VueComponent<HeaderProps> {
<h1 onClick={props.onClick}>-</h1>
{button()} // required 的 true or false 需要和 类型中 text的? 对应
static defaultProps: ComponentProps<HeaderProps> = {
text: {default: '默认数值', required: true},
list: {default: () => []}
};
{slots.default?.()} @Hook('Mounted')
mounted() {
console.log("this: ", this);
console.log('Header 组件的 mounted 生命周期...')
}
{slots.header?.()}
<div class="ScopedSlots"> render() {
<p></p> const {text, list} = useProps<HeaderProps>();
return (
<div class="header">
<p>{text}</p>
{this.context.slots.default?.()}
{this.context.slots.haveName?.()}
<ul> <ul>
{ {
props.text.map(item => { list.map(
return slots.scoped?.(item) item =>
}) this.context.slots.item?.(item)
)
} }
</ul> </ul>
</div> </div>
</div>
) )
} }
}) }

View File

@@ -1,13 +1,21 @@
// import './assets/main.css' // import './assets/main.css'
import '@abraham/reflection'
import {createApp} from 'vue' import {createApp} from 'vue'
import {createPinia} from 'pinia' import {createPinia} from 'pinia'
import {App} from './App'
import App from './App'
import router from './router' import router from './router'
class Main {
constructor() {
createApp(App) createApp(App)
.use(createPinia()) .use(createPinia())
.use(router) .use(router)
.mount('#app') .mount('#app')
}
}
new Main

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

View File

@@ -1,27 +1,5 @@
import {createRouter, createWebHistory, RouteRecordRaw} from 'vue-router' import {createRouter, createWebHistory} from 'vue-router'
import {productRouter} from 'product/ProductRouter' import routes from './Config'
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];
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),

View File

@@ -1,12 +1,12 @@
import { defineComponent } from 'vue' import {Component, VueComponent} from 'vue3-oop'
export default defineComponent({ @Component()
name: "About", export default class About extends VueComponent {
setup() { render() {
return () => ( return (
<div class="about"> <div class="about">
<h1></h1> <h1></h1>
</div> </div>
) )
} }
}) }

View File

@@ -1,64 +1,54 @@
import {defineComponent, onMounted, reactive} from 'vue' import {Autobind, Component, Link, Mut, VueComponent} from 'vue3-oop'
import Header from '@/components/Header' 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 { interface ListItemType {
id: number id: number
title: string title: string
} }
export default defineComponent({ @Component()
name: "Home", export default class Home extends VueComponent {
setup() {
const counterStore = useCounterStore() @Mut()
const { increment } = counterStore count: number = 1
const { count, doubleCount } = storeToRefs(counterStore)
const state = reactive<stateType>({ @Mut()
list: [ text: string = "Home props 传给 Header 的字符串"
@Mut()
newList: ListItemType[] = [
{id: 1, title: '新闻1'}, {id: 1, title: '新闻1'},
{id: 2, title: '新闻2'}, {id: 2, title: '新闻2'},
{id: 3, title: '新闻3'}, {id: 3, title: '新闻3'},
] ]
})
@Link() H1?: HTMLDivElement
@Autobind()
onMounted(() => { async addCount() {
// console.log("onMounted: ", HttpConfig.DevBaseUrl); this.count++
console.log("check: ", UrlCheckUtils.check()) this.text = "22222"
}) console.log(this.H1)
// 定义处理子组件事件的函数
const handleChildEvent = (message: string) => {
console.log('收到子组件事件:', message)
} }
render() {
return () => ( return (
<div class="home"> <div class="home" ref="H1">
<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> <h1></h1>
<div class="count" onClick={increment}>+1{count.value}</div> <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> </div>
) )
} }
}) }

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

View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import type {DefineComponent} from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

View File

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

View File

@@ -1,11 +1,46 @@
{ {
"files": [], "compilerOptions": {
"references": [ "target": "ESNext",
{ "module": "ESNext",
"path": "./tsconfig.node.json" "importHelpers": true,
}, "isolatedModules": true,
{ "experimentalDecorators": true,
"path": "./tsconfig.app.json" "emitDecoratorMetadata": true,
} "useDefineForClassFields": false,
"moduleResolution": "Bundler",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": [
"ES2020",
"dom",
"dom.iterable",
"scripthost"
],
"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"
] ]
} }

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,12 +1,10 @@
import {fileURLToPath, URL} from 'node:url' import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite' import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue' import vueJsx from '@vue3-oop/plugin-vue-jsx'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import federation from '@originjs/vite-plugin-federation'; import federation from '@originjs/vite-plugin-federation';
// https://vite.dev/config/
export default defineConfig({ export default defineConfig({
server: { server: {
port: 1300, port: 1300,
@@ -14,9 +12,7 @@ export default defineConfig({
strictPort: true, strictPort: true,
}, },
plugins: [ plugins: [
vue(),
vueJsx(), vueJsx(),
vueDevTools(),
federation({ federation({
name: 'mainApp', // 主应用模块名(联邦模块需通过此名引用) name: 'mainApp', // 主应用模块名(联邦模块需通过此名引用)
remotes: { // 配置远程联邦模块地址(开发/生产需对应) remotes: { // 配置远程联邦模块地址(开发/生产需对应)
@@ -25,7 +21,6 @@ export default defineConfig({
}, },
shared: { shared: {
vue: { requiredVersion: '^3.5.18' }, vue: { requiredVersion: '^3.5.18' },
axios: { requiredVersion: '^1.12.2' },
'vue-router': { requiredVersion: '^4.0.6' }, 'vue-router': { requiredVersion: '^4.0.6' },
pinia: { requiredVersion: '^3.0.3' } pinia: { requiredVersion: '^3.0.3' }
} }

View File

@@ -0,0 +1,4 @@
packages:
- 'main-app'
- 'app-product'
- 'app-shared'