first commit

This commit is contained in:
编码猿
2024-09-27 00:48:07 +08:00
commit 342c8c1b1b
95 changed files with 23969 additions and 0 deletions

3
.browserslistrc Normal file
View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not ie <= 10

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

1
.pydio Normal file
View File

@@ -0,0 +1 @@
599f9574-b305-4f7f-9987-46a7d0b47c5c

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

432
README.md Normal file
View File

@@ -0,0 +1,432 @@
## 前言:
前端发展真的是一个轮回,越发展越偏向**原生**`Vue3`开发阶段就参与并使用了,但是`Vue3`真的垃圾,十分的不喜欢这种垃圾的语法,钟爱`class``ts装饰器`,所以有了本项目!!
本项目作为`Vue`脚手架而存在已在很多公司项目中落地并经过不断打磨同时为了更优雅的代码开发本人写了很多周边的辅助插件装饰器shell为你提供类似`java`开发中`SpringBoot`那般优雅的爽快感!
**围绕项目开发的插件有:**
1: [vue-cli-plugin-tsx-autorouter](https://www.npmjs.com/package/vue-cli-plugin-tsx-autorouter) tsx自动路由插件
`Nuxt.js`的思想启发,针对本项目`tsx`开发,根据页面结构自动生成路由配置文件,借助`babel``ast`实现提取`tsx`中类的属性进而实现路由`meta`
2./bin/tinit 封装shell命令行工具实现通过命令行直接创建众多代码文件
3解耦前端 页面 & JSON数据实现JSON映射存储到类属性
- 1: [json-mapper-class](https://www.npmjs.com/package/json-mapper-class) 核心插件
- 2: [json-class-interface](https://www.npmjs.com/package/json-class-interface) `TypeScript`项目中通用的JSON数据自动生成类型工具支持生成 [json-mapper-class](https://www.npmjs.com/package/json-mapper-class) 标准代码。
- 3: [json-class-desktop](https://gitee.com/bmycode/json-class-desktop) 基于`tauri` `Svelte`写的[json-class-interface](https://www.npmjs.com/package/json-class-interface) 桌面端工具,支持`Mac``linux``windows`
4装饰器将封装成通用TS项目插件
- 1: 让代码更加优雅简洁高效的请求中间层装饰器 @GET@POST@DELETE@PUT
```typescript
@GET()
@Mapper(ResponseType)
public async IndexTest (params: object): Promise<ResponseType[]> {
// 留空,不要写请求代码 即可发送请求,除非 useHandle: true这时 方法入参 params 为 ajax 接口数据,直接处理 params
// 即可,同时也不需要写请求代码!!
}
```
- 2: 依赖注入装饰器:@Autowired
- 3: 其他装饰器:@GlobalMethod@Mapper@RegVuePlugs@RegServiceAndMethods@Start 等等..
5AutoServiceCodeGeneration实时监听`configure.config.ts`配置文件,然后根据`ApiList`中的对象`key`名称自动生成请求中间层的代码,降低手动复制的低效率!
介绍:
1: 文件监听已通过Nodejs流操作实现文件监听读取提取核心代码并实现同一文件中不同编辑下的新旧数据`Diff`。
2请求中间层代码自动生成**思路已有,挑战性不足,所以还未进行代码实现**
## TsxVueClassApi
基于`Vue Class Api``TypeScript``JSX`,搭建的一套项目脚手架,实现了很多常用的注解,同时基于`babel`插件通过`Ast`语法树进行路由配置文件的自动生成等。
如果你喜欢`JSX`,那么这套脚手架你应该会十分喜欢,代码语法如下:
```tsx
import "@pageLess/Home.less";
import { Component } from 'vue-property-decorator';
import HelloWorld from '@components/HelloWorld';
import { HomeLogic } from "@logic/page/Home.logic";
import { Getter } from "vuex-class";
import { IndexUtils } from "@utils/index.utils";
import { Autowired } from "@ann/Ioc.annotation";
import { VueCustomize, RouterMeta } from "@logic/Base.type";
@Component
export default class Home extends VueCustomize {
// RouterMeta 是 vue-router 路由meta配置信息会被 我们的插件
// vue-cli-plugin-tsx-autorouter 进行 ast 提取注入到 自动生成的 路由配置文件中
public RouterMeta: RouterMeta = {
title: '首页',
showNav: true,
isLogin: true
}
// 抽离组件所有逻辑到独立类,解耦 页面层 和 逻辑层
// 通过 Autowired 依赖注入装饰器 实现 注入 逻辑 -> 页面
// 同时 传入 vue 的 this实现逻辑层可以调用vue方法
@Autowired(Home)
private logic!: HomeLogic<Home>
@Autowired()
public readonly Utils!: IndexUtils;
@Getter('GettersTabsContentArr')
public readonly getterFoo!: Array<any>;
public async created() {
await this.logic.StartUp();
}
public ClickButton(): JSX.Element {
return (
<a-button type="primary" onClick={async () => {
console.log("按钮被点击22222");
}}>
Primary
</a-button>
)
}
public back() {
console.log("qqqq");
}
protected render() {
return (
<div class="home">
<img src="/img/baidu.png" alt="" />
<h1 on-click={this.back}>111</h1>
<HelloWorld msg="向HelloWorld props传参" />
<input type="text" v-model={this.logic.title} />
<h2>{this.logic.title}</h2>
{this.ClickButton()}
<ul>
{
this.logic.List.map(v => {
return <li>
{v.user_id}
{v.user_name}
</li>
})
}
</ul>
</div>
)
}
}
```
**这个脚手架我们正在项目中使用,同时也在不断的开发和完善!!!!**
## 1安装
需要额外全局安装的插件有:
```bash
npm i -g concurrently cross-env gulp
```
然后下载项目,初始化并启动:
```bash
git clone https://gitee.com/bmycode/vue-class-api-tsx.git
npm i
npm run start
# 注意网站运行后使用电脑本机的局域网ip地址来访问例如http://192.168.1.101:8080/#/Home
```
**注意**
`Vue`官方原生底层就支持`JSX`语法,但是官方没有关于`Vue`中使用`JSX`的文档,而且很多`Vue`ui框架和第三方插件都没有相关语法但是这不影响我们在`Vue`中正常使用`JSX`。很多语法需要特殊注意和改造一下!!
## 2使用
下面说下这个脚手架的使用和注意事项。
### 2.1:项目结构
请仔细看文件后面的中文说明!!!
```txt
├── README.md
├── babel.config.js babel配置文件
├── bin 终端NewFile工具
│   ├── config
│   │   └── index.js
│   └── tinit
├── dist 打包后的文件夹
├── gulpfile.js gulp配置文件实现监听page自动生成路由文件
├── package.json 核心依赖配置文件
├── public 公共的资源文件
├── src 存放核心源码
│   ├── application 页面相关
│   │   ├── app.tsx 最外层顶级父组件
│   │   ├── assets 静态资源回合webpack打包进源码
│   │   ├── components 公共组件
│   │   ├── logic 【重要】TSX组件中的js逻辑抽象层
│   │   │   ├── base.logic.ts 【重要】所有TSX组件的js逻辑父类
│   │   │   ├── base.type.ts 组件中 通用类型的编写
│   │   │   └── page 【重要】对应页面的TSX组件逻辑抽象
│   │   └── page 【重要】存放TSX页面组件
│   └── core 核心JS层
│   ├── annotation 存放自己开发的装饰器
   │   ├── JsonToClass json-mapper-class 的源代码
│   │   ├── http.annotation.ts 请求装饰器
│   │   ├── ioc.annotation.ts IOC依赖装饰器
│   │   └── register.annotation.ts 启动装饰器
│   ├── config 站点配置文件
│   │   ├── configure.config.ts 核心配置文件
│   │   └── route.config.ts 自动生成的路由配置文件
│   ├── dao axios的封装
│   ├── model TS存取器
│   ├── run 项目的启动类
│   │   ├── init.run.ts 初始化Vue各种配置
│   ├── service 请求的service层封装
│   │   └── impl 请求service的具体类
│   ├── store Vuex的配置
│   ├── types 自定义的声明文件
│   └── utils 工具函数
├── tsconfig.json TS的配置文件
└── vue.config.js Vue-cli3 的配合文件
```
### 2.2:编码注意事项和规范,严格遵守!!!
1`page/`文件夹中的所有`tsx`组件必须继承自`VueCustomize``VueCustomize`是所有页面的父类,可以在这里定义一些公共的类型!
2`logic/`文件夹中的所有`logic.ts`必须继承`BaseLogic`并实现接口`Methods``BaseLogic`是所有页面逻辑层的公共封装,可以把使用率高的接口或者变量放在`BaseLogic`
中去定义和实现,这样页面的逻辑层子类可以实现直接使用!
3`logic/`中页面的逻辑层可以使用装饰器`@Autowired()`去注入多个`service`的依赖,实现对`ajax`中间层方法的调用,以发送`ajax`请求!具体的看下面代码
```typescript
import { Autowired } from "@/core/annotation/Ioc.annotation";
import { UserServiceImpl, ResponseType} from "@/core/service/impl/User.service.impl";
import { BaseLogic } from "@logic/Base.logic";
import { Methods, VueCustomize } from "@logic/Base.type";
export class HomeLogic<T extends VueCustomize> extends BaseLogic implements Methods {
@Autowired()
public readonly UserServiceImpl!: UserServiceImpl;
public title: string = "12222";
public List: ResponseType[] = [];
constructor(_: T) {
super();
this._ = _
}
/**
* 在这里面调用需要打开页面就执行的ajax请求
* @constructor
*/
public async StartUp (): Promise<void> {
await this.getData();
}
/**
* 请求首页数据
*/
public async getData (): Promise<void> {
this.List = await this.UserServiceImpl.IndexTest({ type: 1, page: 1 });
console.log("this.List: ", this.List);
// let res = await this.UserServiceImpl.login({ type: 2, page: 2 });
// console.log("this.res: ", res);
// this._.$setToken("")
// await this._.$router.push('/About')
}
}
```
4`logic/`中必须实现构造函数`constructor`,在构造函数中保存`tsx`页面传入指向`vue`实例的`this`,这样才能在`logic`中正确使用`vue`的一些方法,具体看代码:
```typescript
constructor(_: T) {
super();
this._ = _
}
/**
* 请求首页数据
*/
public async getData (): Promise<void> {
this.List = await this.UserServiceImpl.IndexTest({ type: 1, page: 1 });
console.log("this.List: ", this.List);
}
```
5如何将自定义的方法通过 `Vue.prototype.xxx` 挂载到`Vue`上以方便全局使用?
答:在`utils/index.utils.ts`中定义方法的时候请使用装饰器`@GlobalMethod()`装饰方法,然后如果你在`tsx`页面组件中需要使用此方法,那么请在`base.type.ts`中定义变量并编写变量类型!
同时如果你需要在`logic`逻辑层使用此方法,那么需要在`base.logic.ts`的`VueType`接口中编写类型。具体请看代码:
```typescript
// src/core/utils/index.utils.ts
import { ConfigureConfig } from "@config/configure.config";
import { GlobalMethod } from "@ann/Register.annotation";
export class IndexUtils {
/**
* 方法上打上 @GlobalMethod() 注解这个方法会自动成为vue的全局方法
* 组件中直接this.$setToken() 即可
*/
@GlobalMethod()
public setToken (args: string): void {
localStorage.setItem("token", args)
}
}
// src/application/logic/base.logic.ts
// 定义 $setToken 方法和入参类型,返回值等!
export class VueCustomize extends Vue {
$setToken!: (token: string) => void
}
```
如果你觉得上面方式使用全局方法太过于麻烦,那么你可以在你需要使用的`logic`类或`tsx`组件中使用装饰器`@Autowired()`来注入`index.utils.ts`到类属性中,代码示例:
```typescript
import "@pageLess/Home.less";
import { Component } from 'vue-property-decorator';
import HelloWorld from '@components/HelloWorld';
import { HomeLogic } from "@logic/page/Home.logic";
import { Getter } from "vuex-class";
// 1导入需要被注入的类
import { IndexUtils } from "@utils/index.utils";
// 2导入装饰器
import { Autowired } from "@ann/Ioc.annotation";
import { VueCustomize, RouterMeta } from "@logic/Base.type";
import { toClass, toInterface, configFilePath } from "json-class-interface";
@Component
export default class Home extends VueCustomize {
// 抽离组件所有逻辑到独立类,解耦页面和数据
@Autowired(Home)
private logic!: HomeLogic<Home>
// RouterMeta 是 vue-router 路由meta配置信息会被ast提取注入到自动生成的路由配置文件中
public RouterMeta: RouterMeta = {
title: '首页',
showNav: true,
isLogin: true
}
// 3将 IndexUtils 注入到属性 Utils 中
@Autowired()
public readonly Utils!: IndexUtils;
@Getter('GettersTabsContentArr')
public readonly getterFoo!: Array<any>;
public async created () {
// 4使用 setToken 方法
this.Utils.setToken("1111")
}
public ClickButton(): JSX.Element {
return (
<a-button type="primary" onClick={async () => {
console.log("按钮被点击22222");
}}>
Primary
</a-button>
)
}
public back() {
console.log("qqqq");
}
protected render() {
return (
<div class="home">
<img src="/img/baidu.png" alt="" />
<h1 on-click={this.back}>111</h1>
<HelloWorld msg="向HelloWorld props传参" />
<input type="text" v-model={this.logic.title} />
<h2>{this.logic.title}</h2>
{this.ClickButton()}
<ul>
{
this.logic.List.map(v => {
return <li>
{v.user_id}
{v.user_name}
</li>
})
}
</ul>
</div>
)
}
}
```
### 2.3:使用注意事项
**1** 项目中的路由使用的是`vue-router`,同时我借鉴了`Nuxt.js`的思想,自己开发了路由配置文件自动生成的插件,这个插件是`Vue-cli 3`的插件,所以不能在`Vue cli 2`的项目中使用!!
插件源码已经提交`npm`包市场!请完整阅读下面的插件文档,路由的配置文件让插件帮我们自动生成就好了!!!
[vue-cli-plugin-tsx-autorouter](https://www.npmjs.com/package/vue-cli-plugin-tsx-autorouter)
**2** 同时借鉴`PHP`开发框架`Laravel`开发终端命令行工具,通过命令帮我们创建带有模板代码能直接运行的文件!代码放在 `bin/` 文件夹中!
使用手册:
```bash
╭─bmy@MacBook-Pro /Applications/Source/TsxVueClassApi master*
╰─$ ./bin/tinit -h
Usage: tinit [options]
Options:
-t, --file-type <type> 文件类型: [pagelogicserviceimplless]
-n, --file-name <name> 被创建的文件名称
-i, --is-create <is> 是否为 -t 参数自动创建相关文件类型 (default: false)
-v, --version 显示当前的版本
-h, --help display help for command
```
参数说明:
- -t需要被创建的文件类型取值 pageTSX页面logicTSX页面的js抽象层service请求中间层的接口impl实现接口的类lesscss样式文件。可以多传
- -n需要被创建的文件名
- -i如果数值为true那么会自动创建 -t 参数所有的文件类型
使用案例:
```bash
╭─bmy@MacBook-Pro /Applications/Source/TsxVueClassApi master*
╰─$ ./bin/tinit -t page logic -n Test
成功创建文件...
```
上面命令执行后会分别在`src/application/page`中创建创建文件`Text.tsx`页面组件,同时在`src/application/logic/page`中创建`Test.logic.ts`的逻辑层文件。
多试几次,请自行熟练掌握!!
## 3开发中功能
- [x] 【重要】1实现`ajax`请求到的数据和`model`层的映射绑定,这种数据绑定关系,可以完全解决后端改接口字段从而导致前端代码也需要同步大量修改的问题!!
- 插件文档:[https://gitee.com/bmycode/json-to-class-or-interface](https://gitee.com/bmycode/json-to-class-or-interface)
- [x] 【不太重要】1实时监听`configure.config.ts`配置文件,然后根据`ApiList`中的对象`key`名称自动在`service`层的类中生成对应的方法,降低手动复制的低效率!
- 已实现功能,但未整合进项目!

9
babel.config.js Normal file
View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2020. bmy
*/
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

1
bin/.pydio Normal file
View File

@@ -0,0 +1 @@
a06d094b-d1c2-4adc-8c5c-0a9986f5aea8

1
bin/config/.pydio Normal file
View File

@@ -0,0 +1 @@
97f1b24f-9aff-44d4-b3af-c8a5ea6943c7

94
bin/config/index.js Normal file
View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2020. bmy
*/
module.exports = {
pageConfig: (name) => {
return `
import "@pageLess/${ name }.less";
import { Component, Vue } from 'vue-property-decorator';
import { ${ name }Logic } from "@logic/page/${ name }.logic";
import { Route, RawLocation } from 'vue-router';
import { BaseLayout, RouterMeta } from "@layout/base.layout";
@Component
export default class ${ name } extends BaseLayout {
private service: ${ name }Logic = new ${ name }Logic(this);
private RouterMeta: RouterMeta = {
title: '${ name }',
showNav: true,
isLogin: true
};
public async created() {
await this.service.StartUp();
};
// 路由拦截器
beforeRouteEnter(to: Route, from: Route, next: (to?: RawLocation | false | ((vm: Vue) => void)) => void) {
next();
}
protected render() {
return (
<div class="${ name }">
${ name }
</div>
)
}
}
`
},
logicConfig: (name) => {
return `
import { BaseLogic, VueType, Methods } from "@logic/Base.logic";
import { Inject } from "@ann/Ioc.annotation";
import { UserServiceImpl } from "@impl/User.service.impl";
export class ${ name }Logic extends BaseLogic implements Methods {
@Inject()
public readonly UserServiceImpl!: UserServiceImpl;
constructor(point: any) {
super();
this.VueApp = point;
}
public async StartUp(): Promise<void> {
}
}
`
},
serviceConfig: (name) => {
return `
export interface ${ name }Service {
IndexClass(params: object): Promise<any>;
}
`
},
implPathConfig: (name) => {
return `
import { ${ name }Service } from "@service/${ name }.service";
import { Injectable } from "@ann/Ioc.annotation";
import { GET } from "@ann/Http.annotation";
@Injectable()
export class ${ name }ServiceImpl implements ${ name }Service {
@GET({ useHandle: false })
public async IndexClass(params: object): Promise<any> { }
}
`
},
lessConfig: (name) => {
return `
@import "../color/index";
@import "../mixin/index";
.${ name } {
}
`
}
}

View File

@@ -0,0 +1 @@
1331c103-912a-4f75-9045-03aaa69824f8

View File

@@ -0,0 +1 @@
8bb6e31a-650f-4f25-9e76-72e5118f6438

View File

@@ -0,0 +1,6 @@
module.exports = {
// 需要被监听的api地址路径
apiListPath: "F:\\desktop\\vue-class-api-tsx\\src\\core\\config\\configure.config.ts",
// 生成的service层代码存放路径
outPutServicePath: "F:\\desktop\\vue-class-api-tsx\\src\\core\\service"
}

View File

@@ -0,0 +1,29 @@
const config = require('./config')
const readApiList = require('./utils/ReadApiList')
const file = require('./utils/File')
let ServerMap = new Map();
// Map(3) {
// 'Car' => Map(2) { 'IndexTest' => 'IndexTest', 'carList' => 'carList' },
// 'Index' => Map(1) { 'reg' => 'reg' },
// 'Order' => Map(1) { 'test' => 'test' }
// }
let main = async () => {
let readApi = new readApiList(config.apiListPath)
let res = await readApi.getFileList()
res.forEach(v => {
// [ 'Car', 'IndexTest' ]
let key = v.split(":")[0].split("_")
if (!ServerMap.get(key[0])) {
ServerMap.set(key[0], new Map())
}
ServerMap.get(key[0]).set(key[1],key[1])
});
// 根据接口对象的配置文件自动生成service文件和代码
ServerMap.forEach((value,key) => {
file.createdService(key,value)
})
}
main()

View File

@@ -0,0 +1 @@
7fcbe2a8-4ef1-42fd-8123-b5ed331cbd41

View File

@@ -0,0 +1,59 @@
const { writeFileSync, existsSync } = require("fs");
const config = require("../config");
class File {
createdService(ServiceName, ServiceMethods) {
let fullPath = `${config.outPutServicePath}\\${ServiceName}.service.ts`
if (!existsSync(fullPath)) {
let mArray = Array.from(ServiceMethods, ([name, value]) => ({ name, value }));
writeFileSync(fullPath, this.stripIndent`
// @ts-nocheck
import { GET, Mapper, POST, PUT, DELETE } from "@ann/Http.annotation";
import { property, defaultValue, deserialize } from "@ann/JsonToClass";
// 建议用工具json-class-desktop生成这个类 类型
export class ResponseType {
@property("id")
user_id!: number;
}
export class ${ServiceName}Service {
${mArray.map(v => {
return `
@GET()
@Mapper(ResponseType)
public async ${v.name} (params: object): Promise<ResponseType[]> {}
`
}).toString().replaceAll(",", "")
}
}
`)
}
}
stripIndent(template, ...expressions) {
let result = template.reduce((prev, next, i) => {
let expression = expressions[i - 1];
if ( Array.isArray(expression)) {
expression = expression.join('');
}
return prev + expression + next;
});
const match = result.match(/^[^\S\n]*(?=\S)/gm);
const indent = match && Math.min(...match.map(el => el.length));
if (indent) {
const regexp = new RegExp(`^.{${indent}}`, 'gm');
result = result.replace(regexp, '');
}
result = result.replace(/^[^\S\n]+/gm, ' ');
result = result.trim();
return result;
}
}
module.exports = new File

View File

@@ -0,0 +1,61 @@
const { createReadStream } = require('fs')
class ReadApiList {
apiListPath = "";
lineLength = 0;
textArr = [];
lineBuffer = Buffer.alloc(4096);
text = "";
constructor(path) {
this.apiListPath = path
}
getFileList() {
this.text = createReadStream(this.apiListPath)
return new Promise((s, e) => {
this.textArr = [];
let status = false
this.text.on('data', (data) => {
for (var i = 0; i < data.length; i++) {
if (data[i] == 10 || data[i] == 13) {
if (data[i] == 10) {
try {
var line = this.lineBuffer.slice(0, this.lineLength);
if (line.length != 0) {
let str = line.toString('utf8')
.replace(/^\s+|\s+$/g, '')
.replace(/\'/g, "")
.replace(/,/g, "")
if (str.includes("start -")) {
status = true
continue;
}
if (str.includes("end -")) {
status = false
break;
}
if (status) {
// console.log("str: ", str.split(":"))
this.textArr.push(str);
}
}
} finally {
this.lineLength = 0;
}
}
} else {
this.lineBuffer[this.lineLength] = data[i];
this.lineLength++;
}
}
s(this.textArr)
})
})
}
}
module.exports = ReadApiList

105
bin/tinit Executable file
View File

@@ -0,0 +1,105 @@
#!/usr/bin/env node
const {program} = require('commander');
const packageConfig = require("../package.json");
const fs = require("fs");
const path = require('path');
const Config = require("./config");
var isWin = /^win/.test(process.platform);
const RootPath = process.cwd();
program
.requiredOption('-t, --file-type <type>', '文件类型: [pagelogicserviceimplless]')
.option('-n, --file-name <name>', '被创建的文件名称')
.option('-i, --is-create <is>', '是否为 -t 参数自动创建相关文件类型', false);
program.version(packageConfig.version, '-v, --version', '显示当前的版本');
program.parse(process.argv);
// 存储终端的参数
let programOptions = program.opts();
let OtherFileType = program.parse(process.argv).args
// tsx 页面路径
let PagePath = isWin ? `${ RootPath }\\src\\application\\page\\${ UpperCase(programOptions.fileName) }.tsx` : `${ RootPath }/src/application/page/${ UpperCase(programOptions.fileName) }.tsx`;
// logic 页面路径
let LogicPath = isWin ? `${ RootPath }\\src\\application\\logic\\page\\${ UpperCase(programOptions.fileName) }.logic.ts` : `${ RootPath }/src/application/logic/page/${ UpperCase(programOptions.fileName) }.logic.ts`;
// service 页面路径
let ServicePath = isWin ? `${ RootPath }\\src\\core\\service\\${ UpperCase(programOptions.fileName) }.service.ts` : `${ RootPath }/src/core/service/${ UpperCase(programOptions.fileName) }.service.ts`;
// Impl 页面路径
let ImplPath = isWin ? `${ RootPath }\\src\\core\\service\\impl\\${ UpperCase(programOptions.fileName) }.service.impl.ts` : `${ RootPath }/src/core/service/impl/${ UpperCase(programOptions.fileName) }.service.impl.ts`;
// Less 页面路径
let LessPath = isWin ? `${ RootPath }\\src\\application\\assets\\less\\page\\${ UpperCase(programOptions.fileName) }.less` : `${ RootPath }/src/application/assets/less/page/${ UpperCase(programOptions.fileName) }.less`;
// 需要创建的文件类型
let FileList = ['page', 'logic', 'service', 'impl', 'less'];
// 是否需要为 controller 联合创建所有文件
if (programOptions.isCreate) {
FileList.forEach(val => replaceTplText(val));
console.log("成功创建文件...");
} else {
// 循环创建多个文件
if (OtherFileType.length != 0) {
[...OtherFileType, programOptions.fileType].forEach(v => replaceTplText(v))
} else {
replaceTplText(programOptions.fileType)
}
console.log("成功创建文件...");
}
/**
* 替换模板的内容,并将内容写入文件。
*/
let FileText = ""
function replaceTplText(action) {
switch (action) {
case 'page':
fs.writeFileSync(PagePath, Config.pageConfig(UpperCase(programOptions.fileName)));
break;
case 'logic':
fs.writeFileSync(LogicPath, Config.logicConfig(UpperCase(programOptions.fileName)));
break;
case 'service':
fs.writeFileSync(ServicePath, Config.serviceConfig(UpperCase(programOptions.fileName)));
break;
case 'impl':
fs.writeFileSync(ImplPath, Config.implPathConfig(UpperCase(programOptions.fileName)));
break;
case 'less':
fs.writeFileSync(LessPath, Config.lessConfig(UpperCase(programOptions.fileName)));
break;
default:
break
}
}
/**
* 单词首字母大写
* @param str
* @returns {void | string | *}
* @constructor
*/
function UpperCase(str) {
return str.replace(str[0], str[0].toUpperCase())
}
/**
* 读取文件内容
* @param path
* @returns {*}
* @constructor
*/
function GetFileText(path) {
return (fs.readFileSync(pathJoin(path), 'utf-8')).toString();
}
/**
* 拼接路径
* @param paths
* @returns {string}
*/
function pathJoin(paths) {
return path.join(__dirname, paths)
}

70
gulpfile.js Normal file
View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2020. bmy
*/
const gulp = require('gulp');
const exec = require('child_process').exec;
const file = require('./test');
var oldText = [], newText = [];
/**
* 执行 vue-cli-service route 命令进行路由编译
*/
gulp.task('AutoCompileRouter', function (cb) {
return exec('npm run route', (err, stdout, stderr) => {
if (err) {
console.log("编译失败: ", err);
cb(err)
} else {
console.log("编译成功");
}
});
});
gulp.task('AutoAddUrl', async (cb) => {
newText = await file.getFileList();
console.log("newText ------------- ", newText)
let cy = diff(newText, oldText);
console.log("diff ------------- ", cy)
oldText = newText;
cb()
})
/**
* 监听 page 所有文件的修改,自动触发 AutoCompileRouter 任务
*/
gulp.task('auto', async () => {
oldText = await file.getFileList();
console.log("oldText ------------- ", oldText)
// gulp.watch('src/application/page/**/*', gulp.parallel('AutoCompileRouter'));
gulp.watch('src/core/config/configure.config.ts', gulp.parallel('AutoAddUrl'));
});
/**
* diff 对比文件修改前后的差异,取出差异和结果
* @param {*} newV 修改后
* @param {*} oldV 修改前
* @returns
*/
function diff(newV, oldV) {
let status = "";
let oV = null, nV = null;
if (newV.length > oldV.length) {
status = "add"
oV = new Set(newV)
nV = new Set(oldV)
} else {
status = "delete"
oV = new Set(oldV);
nV = new Set(newV);
}
return {
status,
item: Array.from(new Set([...oV].filter(x => !nV.has(x))))
}
}
gulp.task('default', gulp.parallel('auto'));

19372
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

57
package.json Normal file
View File

@@ -0,0 +1,57 @@
{
"name": "tsx-vue-class-api",
"version": "1.1.0",
"private": true,
"scripts": {
"start": "concurrently \"npm run serve\" \"gulp\" ",
"serve": "cross-env NODE_ENV=dev vue-cli-service serve",
"build": "cross-env NODE_ENV=production vue-cli-service build",
"route": "vue-cli-service route",
"auto": "node bin/service-code-generation/index.js"
},
"bin": {
"tinit": "bin/tinit"
},
"dependencies": {
"ant-design-vue": "^1.6.5",
"axios": "^0.20.0",
"core-js": "^3.6.5",
"isarray": "^2.0.5",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^8.4.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0",
"vuex-class": "^0.3.2"
},
"devDependencies": {
"@babel/core": "^7.23.5",
"@babel/generator": "^7.23.5",
"@babel/parser": "^7.23.5",
"@babel/traverse": "^7.23.5",
"@babel/types": "^7.23.5",
"@bmy/cli-service": "^4.5.19",
"@types/isarray": "^2.0.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"babel-polyfill": "^6.26.0",
"chalk": "^4.1.2",
"commander": "^6.1.0",
"compression-webpack-plugin": "^5.0.1",
"es6-promise": "^4.2.8",
"glob": "^7.1.3",
"gulp": "^4.0.2",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"pify": "^4.0.1",
"reflect-metadata": "^0.1.13",
"typescript": "~3.9.3",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-cli-plugin-tsx-autorouter": "^1.3.6",
"vue-template-compiler": "^2.6.11",
"webpack-bundle-analyzer": "^3.8.0",
"yamljs": "^0.3.0"
}
}

1
public/.pydio Normal file
View File

@@ -0,0 +1 @@
da7a2feb-9485-4fcf-855a-b6dbcedaad05

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

1
public/fonts/.pydio Normal file
View File

@@ -0,0 +1 @@
282d544a-c6a9-4e6d-ab20-93a15ca73330

1
public/img/.pydio Normal file
View File

@@ -0,0 +1 @@
609d9c08-74bf-4b6b-b612-c63a5bf29b24

BIN
public/img/baidu.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

22
public/index.html Normal file
View File

@@ -0,0 +1,22 @@
<!--
~ Copyright (c) 2020. bmy
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="IE=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width,initial-scale=1.0" name="viewport">
<link href="<%= BASE_URL %>favicon.ico" rel="icon">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

1
src/.pydio Normal file
View File

@@ -0,0 +1 @@
37bbcaf2-9403-4c45-a641-e6a09bbec803

1
src/application/.pydio Normal file
View File

@@ -0,0 +1 @@
55e88823-96b5-4e92-a121-1743226e7284

23
src/application/app.tsx Normal file
View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2020. bmy
*/
import "@less/imports.less"
import { Component } from 'vue-property-decorator';
import { VueCustomize } from "@logic/base.type";
@Component
export default class App extends VueCustomize {
protected render () {
return (
<div id="app">
<div id="nav">
<router-link to="/Home">Home</router-link>
|
<router-link to="/About">About</router-link>
</div>
<router-view/>
</div>
)
}
}

View File

@@ -0,0 +1 @@
e9a6fea5-8124-47ee-8764-2a2b38559e29

View File

@@ -0,0 +1 @@
08d2ddbb-779b-48d9-a2f0-eff54b1a49ba

View File

@@ -0,0 +1 @@
984cb101-d8c8-46fc-bdf2-404c29895807

View File

@@ -0,0 +1,463 @@
/*
* Copyright (c) 2020. bmy
*/
html, body, #app {
width: 100%;
height: 100%;
}
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

@@ -0,0 +1,8 @@
/*
* Copyright (c) 2020. bmy
*/
// 重置ui框架css
.el-button {
width: 300px;
}

View File

@@ -0,0 +1 @@
29ff899f-7099-4b35-97d9-7eb12e8b1b67

View File

@@ -0,0 +1,6 @@
/*
* Copyright (c) 2020. bmy
*/
// 公共css
@import "./common/normalize";
@import "./common/resetUi";

View File

@@ -0,0 +1 @@
e36141e2-2743-4aa7-89cf-e34088c48df2

View File

@@ -0,0 +1,3 @@
@import "./color.less";
@import "./index.less";

View File

@@ -0,0 +1,313 @@
/*
* Copyright (c) 2020. bmy
*/
// 公共css样式可以在 页面组件中直接使用变量名称
// 具体看 components/home.styl
@theme-color: red;
@test-color: yellow;
@red50: #ffebee;
@red100: #ffcdd2;
@red200: #ef9a9a;
@red300: #e57373;
@red400: #ef5350;
@red500: #f44336;
@red600: #e53935;
@red700: #d32f2f;
@red800: #c62828;
@red900: #b71c1c;
@redA100: #ff8a80;
@redA200: #ff5252;
@redA400: #ff1744;
@redA700: #d50000;
@red: @red500;
@pink50: #fce4ec;
@pink100: #f8bbd0;
@pink200: #f48fb1;
@pink300: #f06292;
@pink400: #ec407a;
@pink500: #e91e63;
@pink600: #d81b60;
@pink700: #c2185b;
@pink800: #ad1457;
@pink900: #880e4f;
@pinkA100: #ff80ab;
@pinkA200: #ff4081;
@pinkA400: #f50057;
@pinkA700: #c51162;
@pink: @pink500;
@purple50: #f3e5f5;
@purple100: #e1bee7;
@purple200: #ce93d8;
@purple300: #ba68c8;
@purple400: #ab47bc;
@purple500: #9c27b0;
@purple600: #8e24aa;
@purple700: #7b1fa2;
@purple800: #6a1b9a;
@purple900: #4a148c;
@purpleA100: #ea80fc;
@purpleA200: #e040fb;
@purpleA400: #d500f9;
@purpleA700: #aa00ff;
@purple: @purple500;
@deepPurple50: #ede7f6;
@deepPurple100: #d1c4e9;
@deepPurple200: #b39ddb;
@deepPurple300: #9575cd;
@deepPurple400: #7e57c2;
@deepPurple500: #673ab7;
@deepPurple600: #5e35b1;
@deepPurple700: #512da8;
@deepPurple800: #4527a0;
@deepPurple900: #311b92;
@deepPurpleA100: #b388ff;
@deepPurpleA200: #7c4dff;
@deepPurpleA400: #651fff;
@deepPurpleA700: #6200ea;
@deepPurple: @deepPurple500;
@indigo50: #e8eaf6;
@indigo100: #c5cae9;
@indigo200: #9fa8da;
@indigo300: #7986cb;
@indigo400: #5c6bc0;
@indigo500: #3f51b5;
@indigo600: #3949ab;
@indigo700: #303f9f;
@indigo800: #283593;
@indigo900: #1a237e;
@indigoA100: #8c9eff;
@indigoA200: #536dfe;
@indigoA400: #3d5afe;
@indigoA700: #304ffe;
@indigo: @indigo500;
@blue50: #e3f2fd;
@blue100: #bbdefb;
@blue200: #90caf9;
@blue300: #64b5f6;
@blue400: #42a5f5;
@blue500: #2196f3;
@blue600: #1e88e5;
@blue700: #1976d2;
@blue800: #1565c0;
@blue900: #0d47a1;
@blueA100: #82b1ff;
@blueA200: #448aff;
@blueA400: #2979ff;
@blueA700: #2962ff;
@blue: @blue500;
@lightBlue50: #e1f5fe;
@lightBlue100: #b3e5fc;
@lightBlue200: #81d4fa;
@lightBlue300: #4fc3f7;
@lightBlue400: #29b6f6;
@lightBlue500: #03a9f4;
@lightBlue600: #039be5;
@lightBlue700: #0288d1;
@lightBlue800: #0277bd;
@lightBlue900: #01579b;
@lightBlueA100: #80d8ff;
@lightBlueA200: #40c4ff;
@lightBlueA400: #00b0ff;
@lightBlueA700: #0091ea;
@lightBlue: @lightBlue500;
@cyan50: #e0f7fa;
@cyan100: #b2ebf2;
@cyan200: #80deea;
@cyan300: #4dd0e1;
@cyan400: #26c6da;
@cyan500: #00bcd4;
@cyan600: #00acc1;
@cyan700: #0097a7;
@cyan800: #00838f;
@cyan900: #006064;
@cyanA100: #84ffff;
@cyanA200: #18ffff;
@cyanA400: #00e5ff;
@cyanA700: #00b8d4;
@cyan: @cyan500;
@teal50: #e0f2f1;
@teal100: #b2dfdb;
@teal200: #80cbc4;
@teal300: #4db6ac;
@teal400: #26a69a;
@teal500: #009688;
@teal600: #00897b;
@teal700: #00796b;
@teal800: #00695c;
@teal900: #004d40;
@tealA100: #a7ffeb;
@tealA200: #64ffda;
@tealA400: #1de9b6;
@tealA700: #00bfa5;
@teal: @teal500;
@green50: #e8f5e9;
@green100: #c8e6c9;
@green200: #a5d6a7;
@green300: #81c784;
@green400: #66bb6a;
@green500: #4caf50;
@green600: #43a047;
@green700: #388e3c;
@green800: #2e7d32;
@green900: #1b5e20;
@greenA100: #b9f6ca;
@greenA200: #69f0ae;
@greenA400: #00e676;
@greenA700: #00c853;
@green: @green500;
@lightGreen50: #f1f8e9;
@lightGreen100: #dcedc8;
@lightGreen200: #c5e1a5;
@lightGreen300: #aed581;
@lightGreen400: #9ccc65;
@lightGreen500: #8bc34a;
@lightGreen600: #7cb342;
@lightGreen700: #689f38;
@lightGreen800: #558b2f;
@lightGreen900: #33691e;
@lightGreenA100: #ccff90;
@lightGreenA200: #b2ff59;
@lightGreenA400: #76ff03;
@lightGreenA700: #64dd17;
@lightGreen: @lightGreen500;
@lime50: #f9fbe7;
@lime100: #f0f4c3;
@lime200: #e6ee9c;
@lime300: #dce775;
@lime400: #d4e157;
@lime500: #cddc39;
@lime600: #c0ca33;
@lime700: #afb42b;
@lime800: #9e9d24;
@lime900: #827717;
@limeA100: #f4ff81;
@limeA200: #eeff41;
@limeA400: #c6ff00;
@limeA700: #aeea00;
@lime: @lime500;
@yellow50: #fffde7;
@yellow100: #fff9c4;
@yellow200: #fff59d;
@yellow300: #fff176;
@yellow400: #ffee58;
@yellow500: #ffeb3b;
@yellow600: #fdd835;
@yellow700: #fbc02d;
@yellow800: #f9a825;
@yellow900: #f57f17;
@yellowA100: #ffff8d;
@yellowA200: #ffff00;
@yellowA400: #ffea00;
@yellowA700: #ffd600;
@yellow: @yellow500;
@amber50: #fff8e1;
@amber100: #ffecb3;
@amber200: #ffe082;
@amber300: #ffd54f;
@amber400: #ffca28;
@amber500: #ffc107;
@amber600: #ffb300;
@amber700: #ffa000;
@amber800: #ff8f00;
@amber900: #ff6f00;
@amberA100: #ffe57f;
@amberA200: #ffd740;
@amberA400: #ffc400;
@amberA700: #ffab00;
@amber: @amber500;
@orange50: #fff3e0;
@orange100: #ffe0b2;
@orange200: #ffcc80;
@orange300: #ffb74d;
@orange400: #ffa726;
@orange500: #ff9800;
@orange600: #fb8c00;
@orange700: #f57c00;
@orange800: #ef6c00;
@orange900: #e65100;
@orangeA100: #ffd180;
@orangeA200: #ffab40;
@orangeA400: #ff9100;
@orangeA700: #ff6d00;
@orange: @orange500;
@deepOrange50: #fbe9e7;
@deepOrange100: #ffccbc;
@deepOrange200: #ffab91;
@deepOrange300: #ff8a65;
@deepOrange400: #ff7043;
@deepOrange500: #ff5722;
@deepOrange600: #f4511e;
@deepOrange700: #e64a19;
@deepOrange800: #d84315;
@deepOrange900: #bf360c;
@deepOrangeA100: #ff9e80;
@deepOrangeA200: #ff6e40;
@deepOrangeA400: #ff3d00;
@deepOrangeA700: #dd2c00;
@deepOrange: @deepOrange500;
@brown50: #efebe9;
@brown100: #d7ccc8;
@brown200: #bcaaa4;
@brown300: #a1887f;
@brown400: #8d6e63;
@brown500: #795548;
@brown600: #6d4c41;
@brown700: #5d4037;
@brown800: #4e342e;
@brown900: #3e2723;
@brown: @brown500;
@blueGrey50: #eceff1;
@blueGrey100: #cfd8dc;
@blueGrey200: #b0bec5;
@blueGrey300: #90a4ae;
@blueGrey400: #78909c;
@blueGrey500: #607d8b;
@blueGrey600: #546e7a;
@blueGrey700: #455a64;
@blueGrey800: #37474f;
@blueGrey900: #263238;
@blueGrey: @blueGrey500;
@grey50: #fafafa;
@grey100: #f5f5f5;
@grey200: #eeeeee;
@grey300: #e0e0e0;
@grey400: #bdbdbd;
@grey500: #9e9e9e;
@grey600: #757575;
@grey700: #616161;
@grey800: #424242;
@grey900: #212121;
@grey: @grey500;
@black: #000000;
@white: #ffffff;
@transparent: rgba(0, 0, 0, 0);
@fullBlack: rgba(0, 0, 0, 1);
@darkBlack: rgba(0, 0, 0, 0.87);
@lightBlack: rgba(0, 0, 0, 0.54);
@minBlack: rgba(0, 0, 0, 0.26);
@faintBlack: rgba(0, 0, 0, 0.12);
@fullWhite: rgba(255, 255, 255, 1);
@darkWhite: rgba(255, 255, 255, 0.87);
@lightWhite: rgba(255, 255, 255, 0.54);

View File

@@ -0,0 +1,297 @@
/*
* 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

@@ -0,0 +1 @@
88b35c97-9424-4ba5-a009-00ba813fb0fa

View File

@@ -0,0 +1,8 @@
/*
* Copyright (c) 2020. bmy
*/
@import "../mixin/_";
.About {
}

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) 2020. bmy
*/
@import "../mixin/_";
.home {
h1 {
color: @theme-color;
}
}

View File

@@ -0,0 +1 @@
50ce6829-d7b9-49e3-91b2-ead60352a710

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2020. bmy
*/
import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
@Prop({ default: 'default value' }) public readonly msg!: string;
private mounted () {
}
private showAlert () {
alert("showAlert")
}
private render () {
return (
<div class="hello">
<h1>{ this.msg }</h1>
<h2 onClick={ () => this.showAlert() }>HelloWorld组件点击事件{ this.msg }</h2>
</div>
)
}
}

View File

@@ -0,0 +1 @@
e99cbd4e-9620-464d-ad1f-a319540d4885

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2020. bmy
*/
import { VueCustomize } from "@logic/base.type";
/**
* logic 层的父类
* 主要实现 页面公共参数和ajax方法的地方
*/
export class BaseLogic {
// 保存Vue对象
public _!: any;
public page: number = 1;
public total_page: number = 1;
public count: number = 10;
// $refs 类型
public $refs!: { [key: string]: Vue | Element | Vue[] | Element[] };
public $confirm!: any;
public $message!: any;
// 请求的总页数
public TotalCount!: string;
// 请求参数
public PageParams: { limit: number, page: number } = {
limit : 10, page : 1
}
/**
* 【页面中公共的多次被用到的接口】可以在这个父类中定义,
* 子类logic中直接使用避免方法的冗余
* @constructor
*/
public async DepartmentListRequest(): Promise<void> {
}
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright (c) 2023. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
import { Vue } from 'vue-property-decorator';
// 组件中路由元信息的类型
export interface RouterMeta {
title?: string,
isLogin?: boolean,
showNav: boolean,
}
// 对 logic 的部分通用变量和方法进行约束的接口
export interface Methods {
StartUp(): Promise<void>;
PageLeave?(): Promise<void>;
}
// tsx组件中一些方法找不到声明文件需要在这里定义
export class VueCustomize extends Vue {
$setToken!: (token: string) => void
$test!: () => string
$formatDate!: (data: number) => string
$isLogin!: () => void
$setStorage!: (key: String, data: any) => void
$getStorage!: (key: String) => any
}

View File

@@ -0,0 +1 @@
d3709c65-896f-4fce-83c8-72248f572930

View File

@@ -0,0 +1,18 @@
/*
* Copyright (c) 2020. bmy
*/
import { Autowired } from "@ann/ioc.annotation";
import { BaseLogic } from "@logic/base.logic";
import { Methods, VueCustomize } from "@logic/base.type";
export class AboutLogic <T extends VueCustomize> extends BaseLogic implements Methods {
constructor(_: T) {
super();
this._ = _
}
public async StartUp (): Promise<void> {
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2020. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
import { Autowired } from "@ann/ioc.annotation";
import { UserService, ResponseType } from "@service/user.service";
import { BaseLogic } from "@logic/base.logic";
import { Methods, VueCustomize } from "@logic/base.type";
export class HomeLogic<T extends VueCustomize> extends BaseLogic implements Methods {
@Autowired()
public readonly UserService!: UserService;
public title: string = "12222";
public List: ResponseType[] = [];
// constructor() {
// super();
// }
constructor(_: T) {
super();
console.log("constructor params: ", _);
this._ = _
}
/**
* 在这里面调用需要打开页面就执行的ajax请求
* @constructor
*/
public async StartUp (): Promise<void> {
await this.getData();
}
/**
* 请求首页数据
*/
public async getData (): Promise<void> {
this.List = await this.UserService.UserList({ type: 1, page: 1 });
console.log("this.List: ", this.List);
// let res = await this.UserService.login({ type: 2, page: 2 });
// console.log("this.res: ", res);
//
// await this._.$router.push('/About')
}
}

View File

@@ -0,0 +1 @@
9b3b1288-db3f-4438-9341-a54217a9d678

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2020. bmy
*/
import "@pageLess/About.less";
import { Component, Vue } from 'vue-property-decorator';
import { AboutLogic } from "@logic/page/about.logic";
import { RawLocation, Route } from 'vue-router';
import { VueCustomize, RouterMeta } from "@logic/base.type";
import { Autowired } from "@ann/ioc.annotation";
@Component
export default class About extends VueCustomize {
@Autowired(About)
private logic!: AboutLogic<About>
private RouterMeta: RouterMeta = {
title: '关于我页面',
showNav: true,
isLogin: true
};
public async created () {
await this.logic.StartUp();
};
// 路由拦截器
beforeRouteEnter (to: Route, from: Route, next: (to?: RawLocation | false | ((vm: Vue) => void)) => void) {
next();
}
protected render () {
return (
<div class="About">
About
</div>
)
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2020. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
import "@pageLess/Home.less";
import { Component } from 'vue-property-decorator';
import HelloWorld from '@components/HelloWorld';
import { HomeLogic } from "@logic/page/home.logic";
import { Getter } from "vuex-class";
import { IndexUtils } from "@utils/index.utils";
import { Autowired } from "@ann/ioc.annotation";
import { VueCustomize, RouterMeta } from "@logic/base.type";
// import { toClass, toInterface, configFilePath } from "json-class-interface";
@Component
export default class Home extends VueCustomize {
public RouterMeta: RouterMeta = {
title: '首页',
showNav: true,
isLogin: true
}
@Autowired(Home)
private logic!: HomeLogic<Home>
@Autowired()
public readonly Utils!: IndexUtils;
@Getter('GettersTabsContentArr')
public readonly getterFoo!: Array<any>;
public async created() {
await this.logic.StartUp();
}
public ClickButton(): JSX.Element {
return (
<a-button type="primary" onClick={async () => {
console.log("按钮被点击22222");
}}>
Primary
</a-button>
)
}
public back() {
console.log("qqqq");
}
protected render() {
return (
<div class="home">
<img src="/img/baidu.png" alt="" />
<h1 on-click={this.back}>111</h1>
<HelloWorld msg="向HelloWorld props传参" />
<input type="text" v-model={this.logic.title} />
<h2>{this.logic.title}</h2>
{this.ClickButton()}
<ul>
{
this.logic.List.map(v => {
return <li>
{v.user_id}
{v.user_name}
</li>
})
}
</ul>
</div>
)
}
}

1
src/core/.pydio Normal file
View File

@@ -0,0 +1 @@
d7cca889-05d9-4311-b88d-a72968fc4581

View File

@@ -0,0 +1 @@
c2c6ec3b-b4b9-42f4-92b0-4e10d1e027a6

View File

@@ -0,0 +1 @@
cbff3cfc-8ebf-4480-a698-e4a7c6040408

View File

@@ -0,0 +1,103 @@
import { setStore } from './store';
import { StoreItemType, StoreItemOptions } from './typing';
export function serialize(serializer: StoreItemType['serializer'], disallowIgnore = false) {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
serializer,
disallowIgnoreSerializer: disallowIgnore,
});
};
}
export function deserialize(deserializer: StoreItemType['deserializer'], disallowIgnore = false) {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
deserializer,
disallowIgnoreDeserializer: disallowIgnore,
});
};
}
export function beforeDeserialize(beforeDeserializer: StoreItemType['beforeDeserializer'], disallowIgnore = false) {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
beforeDeserializer,
disallowIgnoreBeforeDeserializer: disallowIgnore,
});
};
}
export function afterSerialize(afterSerializer: StoreItemType['afterSerializer'], disallowIgnore = false) {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
afterSerializer,
disallowIgnoreAfterSerializer: disallowIgnore,
});
};
}
export function serializeTarget() {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
serializeTarget: true,
});
};
}
export function property(originalKey?: string, targetClass?: StoreItemType['targetClass'], isOptional = false) {
return (target: any, propertyKey: string) => {
// propertyKey 使用装饰器的 属性 名称例如id
// originalKey json 数据的 key例如: { i: 1, name: 123 } 中的 i
// console.log("property target: ", target);
// console.log("property propertyKey: ", propertyKey);
// console.log("property originalKey: ", originalKey);
const config: StoreItemOptions = {
originalKey: originalKey || propertyKey,
key: propertyKey,
};
if (targetClass) {
config.targetClass = targetClass.prototype.constructor;
// console.log("有 targetClass ...");
// console.log("config: ",config.targetClass);
}
if (isOptional) {
config.optional = isOptional;
}
setStore(target, config);
};
}
export function typed(targetClass: StoreItemType['targetClass']) {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
targetClass,
});
};
}
export function optional() {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
optional: true,
});
};
}
export function defaultValue(value: any) {
return (target: any, propertyKey: string) => {
setStore(target, {
key: propertyKey,
default: value,
});
};
}

View File

@@ -0,0 +1,4 @@
export * from './to-class';
export * from './to-plain';
export * from './decorators';
export * from './typing';

View File

@@ -0,0 +1,27 @@
// @ts-nocheck
import { StoreItemOptions, StoreItemType } from './typing';
const store = new Map<Function, Map<string, StoreItemType>>();
export const originalKeyStores = new Map<Function, Map<string, StoreItemOptions[]>>();
export const keyStores = new Map<Function, Map<string, StoreItemType>>();
export const setStore = (target: Function, options: StoreItemOptions) => {
const { key, ...rest } = options;
// 获取被装饰 属性 所在类的 构造函数,作为存储 Map 的 key
const storeKey = target.constructor;
// 判断是否已经存储过,如果已经存在 get 获取之前存在的 key 对应的数值,不存在 创建新的 map
const targetStore = store.has(storeKey) ? store.get(storeKey) : new Map<string, StoreItemType>();
// 判断被装饰的 属性id,name是否已经存在 存在 获取 key 对应 的 StoreItemType不存在为 {}
let value = targetStore.has(key) ? targetStore.get(key) : {};
value = {
...value,
...rest,
};
targetStore.set(key, value);
store.set(storeKey, targetStore);
};
export default store;

View File

@@ -0,0 +1,79 @@
import isArray from 'isarray';
import { getOriginalKeyStore, isUndefined, isNullOrUndefined } from './utils';
import { JosnType, StoreItemOptions, BasicClass, ToClassOptions } from './typing';
export const arrayItemToClass = <T>(arrayVal: any[], Clazz: BasicClass<T>, options: ToClassOptions): any => {
return arrayVal.map((v: any) =>
// eslint-disable-next-line @typescript-eslint/no-use-before-define
isArray(v) ? arrayItemToClass(v, Clazz, options) : objectToClass(v, Clazz, options),
);
};
/**
*
* @param jsonObj json 数据
* @param Clazz 传入类的 构造函数
* @param options
* @returns
*/
const objectToClass = <T>(jsonObj: Record<string, any>, Clazz: BasicClass<T>, options: ToClassOptions): T => {
// 初始化 传入的类
const instance: any = new Clazz();
const originalKeyStore = getOriginalKeyStore(Clazz);
originalKeyStore.forEach((propertiesOptions: StoreItemOptions[], originalKey) => {
const originalValue = jsonObj[originalKey];
propertiesOptions.forEach((storeItemoptions: StoreItemOptions) => {
// console.log("propertiesOptions storeItemoptions: ", storeItemoptions);
const { key, beforeDeserializer, deserializer, targetClass, optional } = storeItemoptions;
const disallowIgnoreDeserializer = storeItemoptions.disallowIgnoreDeserializer || !options.ignoreDeserializer;
const disallowIgnoreBeforeDeserializer =
storeItemoptions.disallowIgnoreBeforeDeserializer || !options.ignoreBeforeDeserializer;
const isValueNotExist = options.distinguishNullAndUndefined ? isUndefined : isNullOrUndefined;
let value = originalValue;
if (isValueNotExist(value)) {
if (!isValueNotExist(storeItemoptions.default)) {
instance[key] = storeItemoptions.default;
return;
}
if (!optional) {
throw new Error(`Can't map '${originalKey}' to ${Clazz.name}.${key}, property '${originalKey}' not found`);
}
return;
}
value =
beforeDeserializer && disallowIgnoreBeforeDeserializer
? beforeDeserializer(value, instance, jsonObj, options)
: value;
if (value && targetClass) {
if (isArray(value)) {
value = arrayItemToClass(value, targetClass, options);
} else {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
value = toClass(value, targetClass, options);
}
}
instance[key] =
deserializer && disallowIgnoreDeserializer ? deserializer(value, instance, jsonObj, options) : value;
});
});
return instance;
};
export const toClasses = <T>(rawJson: JosnType[], Clazz: BasicClass<T>, options: ToClassOptions = {}): T[] => {
if (!isArray(rawJson)) {
throw new Error(`rawJson ${rawJson} must be an array`);
}
const { constructor } = Clazz.prototype;
return rawJson.map((item: object) => objectToClass<T>(item, constructor, options));
};
export const toClass = <T>(rawJson: JosnType, Clazz: BasicClass<T>, options: ToClassOptions = {}): T => {
const { constructor } = Clazz.prototype;
return objectToClass<T>(rawJson as object, constructor, options);
};
export const JsontoClass = toClass
export const JsontoClasses = toClasses

View File

@@ -0,0 +1,55 @@
// @ts-nocheck
import isArray from 'isarray';
import { getKeyStore, isUndefined, isNullOrUndefined } from './utils';
import { JosnType, BasicClass, StoreItemType, ToPlainOptions } from './typing';
export const arrayItemToObject = <T>(arrayVal: any[], Clazz: BasicClass<T>, options: ToPlainOptions): any => {
return arrayVal.map((v: any) =>
// eslint-disable-next-line @typescript-eslint/no-use-before-define
isArray(v) ? arrayItemToObject(v, Clazz, options) : classToObject(v, Clazz, options),
);
};
const classToObject = <T>(instance: JosnType, Clazz: BasicClass<T>, options: ToPlainOptions): JosnType => {
const obj: JosnType = {};
const keyStore = getKeyStore(Clazz);
keyStore.forEach((propertiesOption: StoreItemType, key: keyof JosnType) => {
const isValueNotExist = options.distinguishNullAndUndefined ? isUndefined : isNullOrUndefined;
const instanceValue = isValueNotExist(instance[key]) ? propertiesOption.default : instance[key];
const { originalKey, afterSerializer, serializer, targetClass, optional } = propertiesOption;
const disallowIgnoreSerializer = propertiesOption.disallowIgnoreSerializer || !options.ignoreSerializer;
const disallowIgnoreAfterSerializer =
propertiesOption.disallowIgnoreAfterSerializer || !options.ignoreAfterSerializer;
if (isValueNotExist(instanceValue)) {
if (!optional) {
throw new Error(`Property '${Clazz.name}.${key}' not found`);
}
return;
}
let value =
serializer && disallowIgnoreSerializer ? serializer(instanceValue, instance, obj, options) : instanceValue;
if (value && targetClass) {
if (isArray(value)) {
value = arrayItemToObject(value, targetClass, options);
} else {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
value = toPlain(value, targetClass, options);
}
}
obj[originalKey] =
afterSerializer && disallowIgnoreAfterSerializer ? afterSerializer(value, instance, obj, options) : value;
});
return obj;
};
export const toPlains = <T>(instances: (T | JosnType)[], Clazz: BasicClass<T>, options: ToPlainOptions = {}): any[] => {
if (!isArray(instances)) {
throw new Error(`${Clazz} instances must be an array`);
}
return instances.map((item: JosnType) => classToObject<T>(item, Clazz, options));
};
export const toPlain = <T>(instance: T | JosnType, Clazz: BasicClass<T>, options: ToPlainOptions = {}): any => {
return classToObject<T>(instance, Clazz, options);
};

View File

@@ -0,0 +1,38 @@
export type StoreItemType = {
originalKey?: string;
targetClass?: BasicClass;
serializer?: (value: any, instance: any, origin: any, options: ToPlainOptions) => any;
disallowIgnoreSerializer?: boolean;
afterSerializer?: (value: any, instance: any, origin: any, options: ToPlainOptions) => any;
disallowIgnoreAfterSerializer?: boolean;
deserializer?: (value: any, instance: any, origin: any, options: ToClassOptions) => any;
disallowIgnoreDeserializer?: boolean;
beforeDeserializer?: (value: any, instance: any, origin: any, options: ToClassOptions) => any;
disallowIgnoreBeforeDeserializer?: boolean;
default?: any;
autoTypeDetection?: boolean;
optional?: boolean;
serializeTarget?: boolean;
};
export type StoreItemOptions = StoreItemType & {
key: string;
};
export type BasicClass<T = any> = {
new (...args: any[]): T;
};
export type JosnType = { [key: string]: any };
export interface ToClassOptions {
ignoreDeserializer?: boolean;
ignoreBeforeDeserializer?: boolean;
distinguishNullAndUndefined?: boolean;
}
export interface ToPlainOptions {
ignoreSerializer?: boolean;
ignoreAfterSerializer?: boolean;
distinguishNullAndUndefined?: boolean;
}

View File

@@ -0,0 +1,78 @@
// @ts-nocheck
import { StoreItemOptions, StoreItemType, BasicClass } from './typing';
import store, { keyStores, originalKeyStores } from './store';
export const isNull = (val: any) => val === null;
export const isUndefined = (val: any) => val === undefined;
export const isNullOrUndefined = (val: any) => isNull(val) || isUndefined(val);
export const getOriginalKeyStore = <T>(Clazz: BasicClass<T>) => {
let curLayer = Clazz;
const cacheOriginalKeyStore = originalKeyStores.get(curLayer);
// console.log("cacheOriginalKeyStore: ", cacheOriginalKeyStore);
if (cacheOriginalKeyStore) {
return cacheOriginalKeyStore;
}
const originalKeyStore = new Map<string, StoreItemOptions[]>();
while (curLayer.name && curLayer.prototype) {
const { constructor } = curLayer.prototype;
const targetStore = store.get(constructor);
// console.log("targetStore: ", targetStore);
if (targetStore) {
targetStore.forEach((storeItem, key) => {
const item = {
key,
...storeItem,
};
// console.log("item: ", item);
if (!originalKeyStore.has(storeItem.originalKey)) {
originalKeyStore.set(storeItem.originalKey, [item]);
} else {
const exists = originalKeyStore.get(storeItem.originalKey);
if (!exists.find((exist: StoreItemOptions) => exist.key === key)) {
originalKeyStore.set(storeItem.originalKey, [...originalKeyStore.get(storeItem.originalKey), item]);
}
}
});
}
curLayer = Object.getPrototypeOf(constructor);
}
originalKeyStores.set(Clazz, originalKeyStore);
return originalKeyStore;
};
export const getKeyStore = <T>(Clazz: BasicClass<T>) => {
const cacheKeyStore = keyStores.get(Clazz);
if (cacheKeyStore) {
return cacheKeyStore;
}
const keyStore = new Map<string, StoreItemType>();
const originalKeyStore = getOriginalKeyStore(Clazz);
originalKeyStore.forEach(storeItems => {
const [firstStoreItem] = storeItems;
if (storeItems.length === 1) {
keyStore.set(firstStoreItem.key, firstStoreItem);
} else {
const hasStoreItems = storeItems.filter(storeItem => storeItem.serializeTarget);
if (hasStoreItems.length !== 1) {
throw new Error(
`Only one of keys(${storeItems.map(storeItem => storeItem.key).join(', ')}) in ${
Clazz.name
} can contain a serializeTarget when use toPlain`,
);
}
const hasStoreItem = hasStoreItems[0];
keyStore.set(hasStoreItem.key, hasStoreItem);
}
});
keyStores.set(Clazz, keyStore);
return keyStore;
};

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2023. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
import { ConfigureConfig } from "@config/configure.config";
import AxioDao from "@dao/index.dao";
import { JsontoClass, JsontoClasses } from "@ann/JsonToClass";
interface ParamsType {
url?: string;
useHandle?: boolean;
header?: { [key: string]: any };
}
/**
* 将 接口返回 数据所对应的 类在类的方法上定义元数据key为mapper值为 传入的 type
* @param type
* @returns
*/
export function Mapper(type: { new(...args: any[]): any }) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
Reflect.defineMetadata('mapper', type, target, propertyKey);
}
}
/**
* GET 请求注解
* @param RequestParams
* @constructor
*/
export function GET(RequestParams?: ParamsType) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
CheckParams('get', target, propertyKey, descriptor, <ParamsType>RequestParams)
}
}
/**
* POST 请求注解
* @param RequestParams
* @constructor
*/
export function POST(RequestParams?: ParamsType) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
CheckParams('post', target, propertyKey, descriptor, <ParamsType>RequestParams)
}
}
/**
* PUT 请求注解
* @param RequestParams
* @constructor
*/
export function PUT(RequestParams?: ParamsType) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
CheckParams('put', target, propertyKey, descriptor, <ParamsType>RequestParams)
}
}
/**
* DELETE 请求注解
* @param RequestParams
* @constructor
*/
export function DELETE(RequestParams?: ParamsType) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
CheckParams('delete', target, propertyKey, descriptor, <ParamsType>RequestParams)
}
}
// GET POST PUT DELETE 四个装饰器最终 都会走这里代码进行发送请求
// 所以 在 http.annotation.ts 文件中 引入插件 json-class-interface
// 调 json-class-interface 的方法 来配置 生成的 class 存放路径 为 /service/type 文件夹
// 请求完成后 先使用 装饰器 的 target.name+_+propertyKey 作为文件名并 在路径下生成空的 .ts 文件
// 然后调用 json-class-interface 的 toClass 方法传入propertyKey作为第一层类的名称
// 并传入 res 请求到的json数据得到最终的 类代码,然后让 json-class-interface 将 类代码 写入刚生成的 .ts 文件中
// 唯一需要开发者手动做的事情如下,举例:
// User.service.impl.ts 中 的 xxx 方法 GET 装饰器 传入 刚生成文件中的 propertyKey 类名
// 这里的代码会自动调用 插件 json-mapper-class 进行 JsontoClasses 或者 JsontoClass 进行序列化
// 伪代码
// configTypePath("service/type")
// wirteFileType(`${target.name_propertyKey}.ts`, toClass(propertyKey,res) )
//
function CheckParams(
methods: "get" | "post" | "put" | "delete",
target: any, propertyKey: string,
descriptor: PropertyDescriptor,
Params: ParamsType) {
Params == undefined ? Params = {} : null;
Params.url = Params.url || ConfigureConfig.AjaxConfig.ApiList[propertyKey]
Params.useHandle = Params.useHandle || false
Params.header = Params.header || {}
let MapperClass = Reflect.getMetadata('mapper', target, propertyKey);
if (MapperClass) {
let OldMethods = descriptor.value;
descriptor.value = async (data: { [key: string]: any }) => {
let res = await AxioDao[methods]({
url: <string>Params.url, data,
headers: Params.header
});
res = res instanceof Array ? JsontoClasses(res, MapperClass) : JsontoClass(res, MapperClass)
if (Params.useHandle) {
return await OldMethods.call(target, res)
} else {
return res
}
}
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (c) 2020. bmy
*/
export function Autowired(params?: any) {
return (target: any, propertyKey: string) => {
let typeClass = Reflect.getMetadata('design:type', target, propertyKey);
Object.defineProperty(target, propertyKey, {
value: params ? new typeClass(new params) : new typeClass(),
writable: true,
configurable: true
});
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020. bmy
*/
import Vue from 'vue';
import { ConfigureConfig } from "@config/configure.config";
import { CreateElement } from 'vue/types/umd';
import Es6Promise from 'es6-promise'
Es6Promise.polyfill()
export function Start<T extends { new ( ...args: any[] ): { } }>( _ : T ) : T {
// @ts-ignore
return class extends _ {
constructor() {
super();
(new Vue({
// @ts-ignore
router: this.Router, store: this.VueX, render: (h: CreateElement) => h(this.app)
}) as Vue).$mount('#app')
}
}
}
export function RegServiceAndMethods(constructor: { new(...args: any[]): {}; }) {
// newInstance(require.context("../service/impl", false, /\.ts$/))
newInstance(require.context("../utils", false, /\.ts$/))
}
function newInstance(context: __WebpackModuleApi.RequireContext) {
context.keys().forEach((key: any) => {
let m = context(key), className: string = Object.keys(m)[0];
new m[className]()
});
}
/**
* 自动注册Vue插件
* @constructor
*/
export function RegVuePlugs(constructor: { new(...args: any[]): {}; }) {
ConfigureConfig.VuePlugs.forEach((v: any) => Vue.use(v))
}
/**
* 自动挂载Vue 全局方法
* @constructor
*/
export function GlobalMethod(target: any, propertyKey: string) {
Vue.prototype[`$${ propertyKey }`] = target[propertyKey]
}

1
src/core/config/.pydio Normal file
View File

@@ -0,0 +1 @@
f0ed9e16-3f2b-4d50-9858-120dc2e2ae2c

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2020. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
import VueRouter, { RouterOptions } from 'vue-router';
import Vuex from 'vuex';
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.css';
import { AutoRoutesConfig } from "@config/route.config";
import { PluginObject } from "vue";
export class ConfigureConfig {
/**
* 接口配置: 测试环境基地址,正式环境基地址,具体页面接口
*/
public static AjaxConfig: {
DevUrl: string,
ProdUrl: string,
ApiList: { [key: string]: string }
} = {
DevUrl: 'http://127.0.0.1:8080',
ProdUrl: 'http://127.0.0.1:9090/api',
ApiList: {
// start - 注释内的key都将被Node.js监听并diff取出实时编辑后的差异项然后程序决定是否增删请求中间层xx文件中的xx方法代码【不要删除】
UserList: '/UserList',
// User_IndexTest: '/test',
// Car_carList: '/test',
// Car_addCar: '/test',
// Index_reg: '/reg',
// Order_test: '/test',
// end - 注释内的key都将被Node.js监听并diff取出实时编辑后的差异项然后程序决定是否增删请求中间层xx文件中的xx方法代码【不要删除】
}
};
/**
* Vue 插件
*/
public static VuePlugs: PluginObject<never>[] = [
VueRouter, Vuex, Antd
];
/**
* 路由配置
*/
public static RouterConfigUrl: RouterOptions = {
mode: 'hash',
base: './',
routes: AutoRoutesConfig
};
}

View File

@@ -0,0 +1,18 @@
// 根据page目录结构自动生成的路由配置文件
// 参考Nuxt.js 源码,同时自己 + ast 实现 https://zh.nuxtjs.org/guide/routing
// @ts-ignore
export const AutoRoutesConfig = [
{
name: "About",
path: "/About",
component: () => import(/* webpackChunkName: 'About' */ '@page/about'),
meta: { title: '关于我页面', showNav: true, isLogin: true }
},
{
name: "Home",
path: "/Home",
component: () => import(/* webpackChunkName: 'Home' */ '@page/home'),
meta: { title: '首页', showNav: true, isLogin: true }
}
];

1
src/core/dao/.pydio Normal file
View File

@@ -0,0 +1 @@
852dea68-05ee-4958-8884-27c503363732

98
src/core/dao/index.dao.ts Normal file
View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2020. bmy
*/
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig } from "axios";
import { IndexUtils } from "@utils/index.utils";
/**
* axios的封装
*/
export class AxioDao {
instance: AxiosInstance;
public constructor() {
this.instance = axios.create({
baseURL: IndexUtils.CheckAjaxUrl(),
timeout: 10000,
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<any> {
this.instance.interceptors.response.use(
(response: any) => {
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<any> {
this.instance.interceptors.request.use((config: any) => {
return config
}, function (error: AxiosError) {
return Promise.reject(error)
})
}
}
export default new AxioDao();

1
src/core/run/.pydio Normal file
View File

@@ -0,0 +1 @@
45df40b6-06b8-4b49-9ece-3df4b113d74f

51
src/core/run/init.run.ts Normal file
View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2020. bmy
*/
import 'reflect-metadata';
import 'babel-polyfill'
// import '@types/RouterHooks';
import Vue, { VueConstructor, Component } from 'vue';
import App from '@application/app'
import { ConfigureConfig } from "@config/configure.config";
import NavigationGuards from "@utils/navigation-guards.utils";
import VueRouter, { RawLocation, Route } from 'vue-router';
import Vuex, { Store } from 'vuex'
import StoreModeule from '@store/index'
import { RegServiceAndMethods, RegVuePlugs, Start } from "@ann/register.annotation";
import { Autowired } from "@ann/ioc.annotation";
@Start
@RegVuePlugs
@RegServiceAndMethods
export class InitRun {
@Autowired()
protected NavigationGuards!: NavigationGuards;
protected Router!: VueRouter;
protected VueX!: Store<typeof StoreModeule>;
protected app: Component = App;
constructor() {
Vue.config.productionTip = false;
this.VueRouterAndVuex()
}
private VueRouterAndVuex(): void {
this.Router = new VueRouter(ConfigureConfig.RouterConfigUrl);
// this.NavigationGuards.beforeEach(this.Router)
// this.NavigationGuards.afterEach(this.Router)
// 重写路由Push
const routerPush: (location: RawLocation) => Promise<Route> = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location: RawLocation): Promise<Route> {
// @ts-ignore
return routerPush.call(this, location).catch((error: Error) => error)
};
this.VueX = new Vuex.Store(StoreModeule)
}
}
new InitRun();

1
src/core/service/.pydio Normal file
View File

@@ -0,0 +1 @@
b6c30b87-ba50-4860-a92d-3dfe07d0ff5c

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023. bmy
*/
import { GET, Mapper } from "@ann/http.annotation";
import { property, defaultValue, deserialize } from "@ann/JsonToClass";
// ajax接口数据返回 为 ResponseType[]
// 这里定义类,将 接口返回的数据 映射到 类中存储
export class ResponseType {
// 接口返回数据的字段 为 id这里映射改为 user_id代码里面用 user_id
@property("id")
@defaultValue(0)
user_id!: number;
@property("name")
@defaultValue("如果接口字段无数值,那这个参数就是默认值")
user_name!: string
}
export class UserService {
/**
* 1 提供 四个【请求装饰器】:@GET @POST @PUT @DELETE
* 参数说明:
* url 可选:接口请求短地址,如果不填默认使用 configure.config.ts 中和方法名IndexTest同名的对象key中地址
* 如果传,使用你传的地址
*
* useHandle 可选:是否需要对网络请求的结果进行修改后再返回给页面,如果需要传 true则请求方法的入参 params 会变为ajax请求
* 得到的接口数据,处理完数据后,请 return 结果。否则 IndexTest 代码内部留空,节省大量 冗余代码!!
*
* header 可选:自定义请求头,例如文件上传等 可以在这自定义
*
* 2 @Mapper() 装饰器
* 参数说明:
* 请求方法的返回类型必须用类来描述JSON数据类型会被 Reflect.defineMetadata 注入 元数据,在 上一步
* 【请求装饰器】结束请求后,会从 Reflect.getMetadata 取出 元数据,然后 被 JsonToClass 使用到。
* JsonToClass 会将json数据映射到 @Mapper() 装饰器的 参数中,也就是 json数据自动存储到类属性中
*/
@GET()
@Mapper(ResponseType)
// @ts-ignore
public async UserList (params: object | ResponseType[]): Promise<ResponseType[]> {
// 留空,不要写请求代码 即可发送请求,除非 useHandle: true这时 方法入参 params 为 ajax 接口数据,直接处理 params
// 即可,也不需要写请求代码!!
}
}

1
src/core/store/.pydio Normal file
View File

@@ -0,0 +1 @@
6dd651dd-0493-4995-a44b-c15ccfdf75a4

10
src/core/store/getters.ts Normal file
View File

@@ -0,0 +1,10 @@
/*
* Copyright (c) 2020. bmy
*/
export default {
GettersTabsContentArr: (state: any) => state.PublicConfig.TabsContentArr,
GettersTabsActive: (state: any) => state.PublicConfig.TabsActive,
GettersAsideMenu: (state: any) => state.PublicConfig.AsideMenu,
GettersIsLogin: (state: any) => state.PublicConfig.isLogin,
}

12
src/core/store/index.ts Normal file
View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) 2020. bmy
*/
import index from './modules'
import getters from './getters'
// 统一导出各模块
export default {
modules: { ...index },
getters
}

View File

@@ -0,0 +1 @@
d3359e2f-bc25-4e14-b202-20080d06b04a

View File

@@ -0,0 +1,9 @@
/*
* Copyright (c) 2020. bmy
*/
import PublicConfig from './publicConfig'
export default {
PublicConfig
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2020. bmy
*/
interface state {
TabsContentArr: Array<{
title: string,
url: string,
closable: boolean,
icon: string
}>,
TabsActive: string,
isLogin: boolean,
AsideMenu: Array<{
web_href: string,
web_title: string,
icon: string,
children: Array<{
web_href: string,
web_title: string,
icon: string,
}>
}>
}
export default {
state: {
isLogin: false,
TabsContentArr: [
{ title: '系统首页', url: '/', closable: false, icon: 'el-icon-house' },
],
TabsActive: '/',
AsideMenu: [
{
web_href: '/', web_title: '系统首页', icon: 'el-icon-house',
children: []
},
{
web_href: '/System', web_title: '系统设置', icon: 'el-icon-setting',
children: [
{ web_href: '/System/MenuConfig', web_title: '菜单配置', icon: 'el-icon-tickets' },
{ web_href: '/System/wardenList', web_title: '管理员列表', icon: 'el-icon-user' },
]
},
{
web_href: '/Jurisdiction', web_title: '权限管理', icon: 'el-icon-lock',
children: [
{ web_href: '/Jurisdiction/RolePermissions', web_title: '角色权限', icon: 'el-icon-key' },
]
}
]
},
mutations: {
// 保存顶部菜单
SET_TABCONTENT: (state: state, activeMenu: string) => {
// 检查是否已经添加过了
let s = state.TabsContentArr.filter(function (val: any, index: number) {
return activeMenu == val.url
});
// 如果没有添加过
if (s.length == 0) {
state.AsideMenu.forEach((v, i) => {
// 如果等于,表示一级菜单
if (activeMenu == v.web_href) {
state.TabsContentArr.push({
title: v.web_title,
url: v.web_href,
icon: v.icon,
closable: true
})
} else {
v.children.forEach((va, ix) => {
if (activeMenu == va.web_href) {
state.TabsContentArr.push({
title: va.web_title,
url: va.web_href,
icon: va.icon,
closable: true
})
}
})
}
})
}
},
// 切换激活
SET_TABACTIVE: (state: state, active: string) => {
state.TabsActive = active
},
// 移除激活
SET_REMOVETABS: (state: state, targetName: string) => {
let tabs = state.TabsContentArr;
let activeName = state.TabsActive;
// 如果删除的是当前激活菜单
if (activeName === targetName) {
tabs.forEach((tab: { [key: string]: any }, index: number) => {
if (tab.url === targetName) {
let nextTab: { [key: string]: any } = tabs[index + 1] || tabs[index - 1];
if (nextTab) {
activeName = nextTab.url;
}
}
});
}
state.TabsActive = activeName;
state.TabsContentArr = tabs.filter((tab: { [key: string]: any }) => {
return tab.url !== targetName
});
},
// 修改状态
SET_LOGINSTATUS: (state: state, status: boolean) => {
state.isLogin = status
}
},
}

1
src/core/types/.pydio Normal file
View File

@@ -0,0 +1 @@
e5b979e5-1ac3-429c-bf03-7d570eba9033

View File

@@ -0,0 +1,11 @@
/*
* Copyright (c) 2020. bmy
*/
import Component from 'vue-class-component'
Component.registerHooks([
'beforeRouteEnter',
'beforeRouteLeave',
'beforeRouteUpdate'
])

359
src/core/types/process.d.ts vendored Normal file
View File

@@ -0,0 +1,359 @@
// Type definitions for webpack (module API) 1.15
// Project: https://github.com/webpack/webpack
// Definitions by: use-strict <https://github.com/use-strict>
// rhonsby <https://github.com/rhonsby>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.1
/**
* Webpack module API - variables and global functions available inside modules
*/
declare namespace __WebpackModuleApi {
interface RequireResolve {
(id: string): string | number;
}
interface RequireContext {
keys(): string[];
(id: string): any;
<T>(id: string): T;
resolve(id: string): string;
/** The module id of the context module. This may be useful for module.hot.accept. */
id: string;
}
interface RequireFunction {
/**
* Returns the exports from a dependency. The call is sync. No request to the server is fired. The compiler ensures that the dependency is available.
*/
(path: string): any;
<T>(path: string): T;
/**
* Behaves similar to require.ensure, but the callback is called with the exports of each dependency in the paths array. There is no option to provide a chunk name.
*/
(paths: string[], callback: (...modules: any[]) => void): void;
/**
* Download additional dependencies on demand. The paths array lists modules that should be available. When they are, callback is called. If the callback is a function expression, dependencies in that source part are extracted and also loaded on demand. A single request is fired to the server, except if all modules are already available.
*
* This creates a chunk. The chunk can be named. If a chunk with this name already exists, the dependencies are merged into that chunk and that chunk is used.
*/
ensure(paths: string[], callback: (require: NodeRequire) => void, chunkName?: string): void;
ensure(paths: string[], callback: (require: NodeRequire) => void, errorCallback?: (error: any) => void, chunkName?: string): void;
context(path: string, deep?: boolean, filter?: RegExp, mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once"): RequireContext;
/**
* Returns the module id of a dependency. The call is sync. No request to the server is fired. The compiler ensures that the dependency is available.
*
* The module id is a number in webpack (in contrast to node.js where it is a string, the filename).
*/
resolve: NodeJS.RequireResolve;
/**
* Like require.resolve, but doesnt include the module into the bundle. Its a weak dependency.
*/
resolveWeak(path: string): number | string;
/**
* Ensures that the dependency is available, but dont execute it. This can be use for optimizing the position of a module in the chunks.
*/
include(path: string): void;
/**
* Multiple requires to the same module result in only one module execution and only one export. Therefore a cache in the runtime exists. Removing values from this cache cause new module execution and a new export. This is only needed in rare cases (for compatibility!).
*/
cache: {
[id: string]: NodeModule | undefined;
}
}
interface Module {
exports: any;
id: string;
filename: string;
loaded: boolean;
parent: NodeModule | null | undefined;
children: NodeModule[];
hot?: Hot;
}
// @ts-ignore
type ModuleId = string|number;
interface HotNotifierInfo {
type:
| 'self-declined'
| 'declined'
| 'unaccepted'
| 'accepted'
| 'disposed'
| 'accept-errored'
| 'self-accept-errored'
| 'self-accept-error-handler-errored';
/**
* The module in question.
*/
moduleId: number;
/**
* For errors: the module id owning the accept handler.
*/
dependencyId?: number;
/**
* For declined/accepted/unaccepted: the chain from where the update was propagated.
*/
chain?: number[];
/**
* For declined: the module id of the declining parent
*/
parentId?: number;
/**
* For accepted: the modules that are outdated and will be disposed
*/
outdatedModules?: number[];
/**
* For accepted: The location of accept handlers that will handle the update
*/
outdatedDependencies?: {
[dependencyId: number]: number[];
};
/**
* For errors: the thrown error
*/
error?: Error;
/**
* For self-accept-error-handler-errored: the error thrown by the module
* before the error handler tried to handle it.
*/
originalError?: Error;
}
interface Hot {
/**
* Accept code updates for the specified dependencies. The callback is called when dependencies were replaced.
* @param dependencies
* @param callback
*/
accept(dependencies: string[], callback?: (updatedDependencies: ModuleId[]) => void): void;
/**
* Accept code updates for the specified dependencies. The callback is called when dependencies were replaced.
* @param dependency
* @param callback
*/
accept(dependency: string, callback?: () => void): void;
/**
* Accept code updates for this module without notification of parents.
* This should only be used if the module doesnt export anything.
* The errHandler can be used to handle errors that occur while loading the updated module.
* @param errHandler
*/
accept(errHandler?: (err: Error) => void): void;
/**
* Do not accept updates for the specified dependencies. If any dependencies is updated, the code update fails with code "decline".
*/
decline(dependencies: string[]): void;
/**
* Do not accept updates for the specified dependencies. If any dependencies is updated, the code update fails with code "decline".
*/
decline(dependency: string): void;
/**
* Flag the current module as not update-able. If updated the update code would fail with code "decline".
*/
decline(): void;
/**
* Add a one time handler, which is executed when the current module code is replaced.
* Here you should destroy/remove any persistent resource you have claimed/created.
* If you want to transfer state to the new module, add it to data object.
* The data will be available at module.hot.data on the new module.
* @param callback
*/
dispose(callback: (data: any) => void): void;
dispose(callback: <T>(data: T) => void): void;
/**
* Add a one time handler, which is executed when the current module code is replaced.
* Here you should destroy/remove any persistent resource you have claimed/created.
* If you want to transfer state to the new module, add it to data object.
* The data will be available at module.hot.data on the new module.
* @param callback
*/
addDisposeHandler(callback: (data: any) => void): void;
addDisposeHandler<T>(callback: (data: T) => void): void;
/**
* Remove a handler.
* This can useful to add a temporary dispose handler. You could i. e. replace code while in the middle of a multi-step async function.
* @param callback
*/
removeDisposeHandler(callback: (data: any) => void): void;
removeDisposeHandler<T>(callback: (data: T) => void): void;
/**
* Throws an exceptions if status() is not idle.
* Check all currently loaded modules for updates and apply updates if found.
* If no update was found, the callback is called with null.
* If autoApply is truthy the callback will be called with all modules that were disposed.
* apply() is automatically called with autoApply as options parameter.
* If autoApply is not set the callback will be called with all modules that will be disposed on apply().
* @param autoApply
* @param callback
*/
check(autoApply: boolean, callback: (err: Error, outdatedModules: ModuleId[]) => void): void;
/**
* Throws an exceptions if status() is not idle.
* Check all currently loaded modules for updates and apply updates if found.
* If no update was found, the callback is called with null.
* The callback will be called with all modules that will be disposed on apply().
* @param callback
*/
check(callback: (err: Error, outdatedModules: ModuleId[]) => void): void;
/**
* If status() != "ready" it throws an error.
* Continue the update process.
* @param options
* @param callback
*/
apply(options: AcceptOptions, callback: (err: Error, outdatedModules: ModuleId[]) => void): void;
/**
* If status() != "ready" it throws an error.
* Continue the update process.
* @param callback
*/
apply(callback: (err: Error, outdatedModules: ModuleId[]) => void): void;
/**
* Return one of idle, check, watch, watch-delay, prepare, ready, dispose, apply, abort or fail.
*/
status(): string;
/** Register a callback on status change. */
status(callback: (status: string) => void): void;
/** Register a callback on status change. */
addStatusHandler(callback: (status: string) => void): void;
/**
* Remove a registered status change handler.
* @param callback
*/
removeStatusHandler(callback: (status: string) => void): void;
active: boolean;
data: any;
}
interface AcceptOptions {
/**
* If true the update process continues even if some modules are not accepted (and would bubble to the entry point).
*/
ignoreUnaccepted?: boolean;
/**
* Ignore changes made to declined modules.
*/
ignoreDeclined?: boolean;
/**
* Ignore errors throw in accept handlers, error handlers and while reevaluating module.
*/
ignoreErrored?: boolean;
/**
* Notifier for declined modules.
*/
onDeclined?: (info: HotNotifierInfo) => void;
/**
* Notifier for unaccepted modules.
*/
onUnaccepted?: (info: HotNotifierInfo) => void;
/**
* Notifier for accepted modules.
*/
onAccepted?: (info: HotNotifierInfo) => void;
/**
* Notifier for disposed modules.
*/
onDisposed?: (info: HotNotifierInfo) => void;
/**
* Notifier for errors.
*/
onErrored?: (info: HotNotifierInfo) => void;
/**
* Indicates that apply() is automatically called by check function
*/
autoApply?: boolean;
}
/**
* Inside env you can pass any variable
*/
interface NodeProcess {
// @ts-ignore
env: {
NODE_ENV: string
};
}
// @ts-ignore
type __Require1 = (id: string) => any;
// @ts-ignore
type __Require2 = <T>(id: string) => T;
// @ts-ignore
type RequireLambda = __Require1 & __Require2;
}
interface NodeRequire extends NodeJS.Require {}
declare var require: NodeRequire;
/**
* The resource query of the current module.
*
* e.g. __resourceQuery === "?test" // Inside "file.js?test"
*/
declare var __resourceQuery: string;
/**
* Equals the config options output.publicPath.
*/
declare var __webpack_public_path__: string;
/**
* The raw require function. This expression isnt parsed by the Parser for dependencies.
*/
declare var __webpack_require__: any;
/**
* The internal chunk loading function
*
* @param chunkId The id for the chunk to load.
* @param callback A callback function called once the chunk is loaded.
*/
declare var __webpack_chunk_load__: (chunkId: any, callback: (require: __WebpackModuleApi.RequireLambda) => void) => void;
/**
* Access to the internal object of all modules.
*/
declare var __webpack_modules__: any[];
/**
* Access to the hash of the compilation.
*
* Only available with the HotModuleReplacementPlugin or the ExtendedAPIPlugin
*/
declare var __webpack_hash__: any;
/**
* Generates a require function that is not parsed by webpack. Can be used to do cool stuff with a global require function if available.
*/
declare var __non_webpack_require__: any;
/**
* Adds nonce to all scripts that webpack loads.
*
* To activate the feature a __webpack_nonce__ variable needs to be set in your entry script.
*/
declare var __webpack_nonce__: string;
/**
* Equals the config option debug
*/
declare var DEBUG: boolean;
interface NodeModule extends NodeJS.Module {}
declare var module: NodeModule;
/**
* Declare process variable
*/
declare namespace NodeJS {
interface Process extends __WebpackModuleApi.NodeProcess {}
interface RequireResolve extends __WebpackModuleApi.RequireResolve {}
interface Module extends __WebpackModuleApi.Module {}
interface Require extends __WebpackModuleApi.RequireFunction {}
}
declare var process: NodeJS.Process;

48
src/core/types/shims-tsx.d.ts vendored Normal file
View File

@@ -0,0 +1,48 @@
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
}
}
}

8
src/core/types/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
/*
* Copyright (c) 2020. bmy
*/
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

1
src/core/utils/.pydio Normal file
View File

@@ -0,0 +1 @@
23f23313-fd2c-4dfc-aeaa-6758f2f76ec2

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2020. bmy
*/
import { ConfigureConfig } from "@config/configure.config";
import { GlobalMethod } from "@ann/register.annotation";
export class IndexUtils {
/**
* 动态返回接口基地址
* @constructor
*/
public static CheckAjaxUrl(): string {
if(process.env.NODE_ENV === 'production') {
return ConfigureConfig.AjaxConfig.ProdUrl;
} else {
return ConfigureConfig.AjaxConfig.DevUrl;
}
}
/**
* 方法上打上 @GlobalMethod() 注解这个方法会自动成为vue的全局方法
* 组件中直接this.$setToken() 即可
*/
@GlobalMethod
public setToken(args: string): void {
localStorage.setItem("token", args)
}
@GlobalMethod
public setStorage<T extends [] | {} | string>(key: string, data: T): void {
if (data.constructor == Array || data.constructor == Object) {
localStorage.setItem(`moehu_${key}`, JSON.stringify(data));
} else {
// @ts-ignore
localStorage.setItem(`moehu_${key}`, data.toString());
}
}
@GlobalMethod
public getStorage(key: String): typeof JSON | boolean | string{
let Storage: string | null = localStorage.getItem(`moehu_${key}`);
if (Storage == null) {
return false
} else {
try {
return JSON.parse(Storage);
} catch (e) {
return Storage;
}
}
}
@GlobalMethod
public isPhone(phone: string): boolean {
return /^1[3-9]\d{9}$/.test(phone)
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2023. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
import VueRouter from 'vue-router';
export default class NavigationGuardsUtils {
public beforeEach(Router: VueRouter) : void {
Router.beforeEach((to, from, next) => {
document.title = to.meta && to.meta.title;
if (to.meta && to.meta.isLogin) {
if (localStorage.getItem("moehu_token") != null) {
next()
} else {
next({
path: '/login'
})
}
} else {
if (localStorage.getItem("moehu_token") != null
&& to.path == "/login") {
Router.back();
}
next()
}
});
}
public afterEach(Router: VueRouter) : void {
Router.afterEach((to, from) => {
console.log("Router.afterEach")
})
}
}

56
test.js Normal file
View File

@@ -0,0 +1,56 @@
const fs = require('fs')
class readFileLine {
configPath = "./src/core/config/configure.config.ts";
lineLength = 0;
textArr = [];
lineBuffer = new Buffer(4096);
getFileList() {
this.text = fs.createReadStream(this.configPath)
return new Promise((s, e) => {
this.textArr = [];
let status = false
this.text.on('data', (data) => {
for (var i = 0; i < data.length; i++) {
if (data[i] == 10 || data[i] == 13) {
if (data[i] == 10) {
try {
var line = this.lineBuffer.slice(0, this.lineLength);
if (line.length != 0) {
let str = line.toString('utf8')
.replace(/^\s+|\s+$/g, '')
.replace(/\'/g, "")
.replace(/,/g, "")
if (str.includes("start -")) {
status = true
continue;
}
if (str.includes("end -")) {
status = false
break;
}
if (status) {
this.textArr.push(str);
}
}
} finally {
this.lineLength = 0;
}
}
} else {
this.lineBuffer[this.lineLength] = data[i];
this.lineLength++;
}
}
s(this.textArr)
})
})
}
}
module.exports = new readFileLine();

22
test2.js Normal file
View File

@@ -0,0 +1,22 @@
let parser = require("@babel/parser")
let _traverse = require("@babel/traverse")
const traverse = _traverse.default;
let _generator = require("@babel/generator")
const generator = _generator.default;
const code = `let val = 1 + 2`
const ast = parser.parse(code)
// console.log(ast)
traverse(ast, {
enter(path) { // 这个path会找到所有的node
if (path.node.type == 'Identifier') {
path.node.name = 'modify'
path.stop()
}
}
})
const ret = generator(ast)
console.log(ret)

104
tsconfig.json Normal file
View File

@@ -0,0 +1,104 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
],
"@core/*": [
"src/core/*"
],
"@router/*": [
"src/core/router/*"
],
"@store/*": [
"src/core/store/*"
],
"@types/*": [
"src/core/types/*"
],
"@config/*": [
"src/core/config/*"
],
"@utils/*": [
"src/core/utils/*"
],
"@run/*": [
"src/core/run/*"
],
"@ann/*": [
"src/core/annotation/*"
],
"@model/*": [
"src/core/model/*"
],
"@dao/*": [
"src/core/dao/*"
],
"@entity/*": [
"src/core/entity/*"
],
"@service/*": [
"src/core/service/*"
],
"@impl/*": [
"src/core/service/impl/*"
],
"@application/*": [
"src/application/*"
],
"@img/*": [
"src/application/assets/img/*"
],
"@less/*": [
"src/application/assets/less/*"
],
"@pageLess/*": [
"src/application/assets/less/page/*"
],
"@components/*": [
"src/application/components/*"
],
"@page/*": [
"src/application/page/*"
],
"@logic/*": [
"src/application/logic/*"
],
"@layout/*": [
"src/application/layout/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

174
vue.config.js Normal file
View File

@@ -0,0 +1,174 @@
/*
* Copyright (c) 2020. bmy
* Email2271608011@qq.com
* Githubhttps://github.com/helpcode
*/
const isProduction = process.env.NODE_ENV === 'production';
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const CompressionPlugin = require('compression-webpack-plugin')
const path = require('path');
const { ProgressPlugin } = require("webpack")
const chalk = require("chalk");
let js = [
'https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js',
'https://cdn.bootcdn.net/ajax/libs/vuex/3.5.1/vuex.min.js',
'https://cdn.bootcdn.net/ajax/libs/vue-router/3.2.0/vue-router.min.js',
'https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js',
'https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.2/index.min.js'
]
let exclude = {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios': 'axios',
'element-ui': 'ELEMENT'
}
module.exports = {
publicPath: './',
filenameHashing: true,
productionSourceMap: false,
parallel: require('os').cpus().length > 1,
lintOnSave: false,
pluginOptions: {
route: {
// 默认page。存放Vue路由页面的文件夹名称
TemplateFolderName: 'page',
// 默认:./src/application。从src/开始到TemplateFolderName文件夹父一级文件夹相对路径
RootFolderName: './src/application',
// 默认 src/core/config/route.config.ts。生成的配置文件保存路径相对插件的位置来。
SaveConfigPath: '../../../src/core/config/route.config.ts'
}
},
devServer: {
hot: true,
port: 8080,
compress: false,
open: false,
openPage: '#/Home',
progress: false,
overlay: {
warnings: false,
errors: true
},
// before 这块代码都可以删除,仅仅为脚手架提供一个用于测试的接口
before: function (app, server) {
app.all('*', function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', '*');
res.header('Access-Control-Allow-Methods', '*');
res.header('Content-Type', 'application/json;charset=utf-8');
next();
});
app.get('/UserList', function (req, res) {
res.json({
status: 200,
message: '请求成功',
result: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
});
});
},
// proxy: {
// '/api': {
// target: 'http://115.159.153.142:9010/h5',
// changeOrigin: true,
// ws: true,
// pathRewrite: {
// '^/api': ''
// }
// }
// }
},
configureWebpack: config => {
// 设置程序核心入口
config.entry.app = ['babel-polyfill', './src/core/run/init.run.ts'];
if (isProduction) {
// 需要被打包排除的插件列表
config.externals = exclude
// 开启 Gzip压缩
return {
plugins: [
new BundleAnalyzerPlugin(),
// new CompressionPlugin({
// test: /\.js$|\.html$|\.css/, //匹配文件名
// threshold: 10240, //对超过10k的数据进行压缩
// deleteOriginalAssets: false //是否删除原文件
// })
]
};
} else {
return {
plugins: [
new ProgressPlugin({
handler: (percentage, message, ...args) => {
console.log(`${chalk.yellow("进度: ")}${chalk.green.bold(~~(percentage * 100) + "%")} ${args[0] ? chalk.black(args[0]) : " " } ${chalk.blue.bold(message)}`)
},
})
]
}
}
},
chainWebpack: (config) => {
if (isProduction) {
// 去除混淆加密
config.optimization.minimize(false)
// 移除 prefetchpreload 插件 减少不必要的请求
config.plugins.delete('prefetch')
config.plugins.delete('preload')
// 生产环境下注入一个变量cdn 变量可以在html中通过htmlWebpackPlugin.options进行获取
config.plugin('html')
.tap(args => {
args[0].cdn = js;
return args;
})
}
/**
* 配置路径别名,项目中引用资源的时候路径可以更简短
* 但是ts文件在用的时候编译器会报错需要添加 @ts-ignore
* 忽略开发工具的错误。
*/
config.resolve.alias
.set('@', resolve('src'))
.set('@core', resolve('src/core'))
.set('@router', resolve('src/core/router'))
.set('@store', resolve('src/core/store'))
.set('@types', resolve('src/core/types'))
.set('@config', resolve('src/core/config'))
.set('@utils', resolve('src/core/utils'))
.set('@run', resolve('src/core/run'))
.set('@ann', resolve('src/core/annotation'))
.set('@model', resolve('src/core/model'))
.set('@dao', resolve('src/core/dao'))
.set('@entity', resolve('src/core/entity'))
.set('@service', resolve('src/core/service'))
.set('@impl', resolve('src/core/service/impl'))
.set('@application', resolve('src/application'))
.set('@img', resolve('src/application/assets/img'))
.set('@less', resolve('src/application/assets/less'))
.set('@pageLess', resolve('src/application/assets/less/page'))
.set('@componentsLess', resolve('src/application/assets/less/components'))
.set('@components', resolve('src/application/components'))
.set('@page', resolve('src/application/page'))
.set('@logic', resolve('src/application/logic'))
.set('@layout', resolve('src/application/layout'))
}
};
function resolve(dir) {
return path.join(__dirname, dir);
}

115
思路.txt Normal file
View File

@@ -0,0 +1,115 @@
需要被实现的功能
将ajax请求到的数据存和数据中间层model进行映射数据存入model中model可以对数据key进行任意命名然后vue的tsx页面组件可以依赖注入model
然后使用model进行页面的渲染
好处:
实现中间层的目的在于完全解耦了数据和页面的绑定如果后端进行接口字段的大量修改可以在不修改前端页面代码字段的情况下只需要修改model即可
原本程序思路整理:
1Tsx组件在对应的logic逻辑中使用service层发送请求核心代码细节
@Inject()
public readonly UserServiceImpl!: UserServiceImpl;
await this.UserServiceImpl.IndexTest({ type: 1, page: 1 });
2虽然service层的方法被logic组件调用但是方法只是空壳方法内部无法编写任何逻辑和返回请求发送在 请求装饰器中实现,代码细节:
@GET({ useHandle: false, header: { test: '1111' } })
public async IndexTest(params: object): Promise<any> { }
3装饰器 @GET() 会根据方法名 IndexTest 去 配置文件 configure.config.ts 中自动寻找对应key的接口地址来自动 发送请求。
4请求成功后装饰器会根据参数 useHandle 的true or false 决定是否把数据 返还给方法如果数据需要特殊处理那么设置为true
方法的入参 IndexTest(params: object) params 会被装饰器调用存放ajax请求到的数据我们可以对数据进行处理然后再把数据交给页面来
渲染展示。代码实现细节:
descriptor.value = async (data: { [key:string]: any }) => await OldMethods.apply(target, [
// @ts-ignore
await AxioDao[methods]({
url: Params.url,
data: data,
header: Params.header
})
])
5TSX 页面组件拿到数据,可以渲染页面了!
改造思路分析:
1需要拦截 步骤4 到 步骤5 的过程在步骤4 和 步骤5 中间实现Model层在请求装饰器中拿到ajax的数据后应该把数据给 中间层 Model基类
中间层Model存储数据。
2用户自定义的 XxxModel 继承 Model类用户自定义的 XxxModel 通过自定义的装饰器实现和Model中存储
的ajax数据进行数据的映射和复制伪代码如下
// ajax 数据
{
name: '刘兵',
age: 18,
like: ['编程','骑车']
}
// 基类Model
class Model {}
// 关联基类Model 和 ajax 数据的具体model
// 核心数据关联在 装饰器 @Mapping() 中实现。
@Injectable()
export class IndexTestModel extends Model {
@Mapping("name")
public myName: string;
@Mapping("id")
public myId: number;
@Mapping("like")
public myLike!: Array<string>;
}
3原本步骤5中再依赖注入 XxxModel从 XxxModel 中获取数据然后渲染Vue的Tsx页面实现的效果如下
@Inject()
public readonly IndexModel!: IndexTestModel;
<ul>
{
this.IndexModel.myLike.map(v => {
return <li>{ v }</li>
})
}
</ul>
对于步骤1 和 步骤2 实现具体过程的详细思路拆解和代码过程
状态:待思考 ---> 思考介绍 ---> 待实现!
角色:
1基类Model
主要封装对存放在存取器里面的数据进行增删改读的一些操作
2GETPOST 等..这些请求装饰器,获取到数据后,将数据构造为对象,格式为:
{
IndexTestModel: ajax 数据
}
然后存入存取器中。
3在用户在自定义的Model中例如 IndexTestModel 中使用属性装饰器 @Mapping("字段名")装饰类的属性,
@Mapping 可以获取被装饰属性的类根据类名去存取器中获取对应的ajax数据
然后 @Mapping 装饰器可也以获得被装饰属性的名称使用名称key去寻找ajax里面的值value
最后在 @Mapping装饰器中将数值取出赋值给被装饰的类属性
4页面上使用 用户自定的model因为model继承基类model所以基类model可以实现很多其他骚操作或者常用的
一些方法,方便拓展!