Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
bwcx_jzy 2023-09-14 18:20:04 +08:00
commit 01bf83a538
No known key found for this signature in database
GPG Key ID: E187D6E9DDDE8C53
8 changed files with 1243 additions and 863 deletions

View File

@ -1,7 +1,77 @@
# Vue 3 + Vite
## 项目介绍
本项目采用 [Vue3](https://cn.vuejs.org/guide/introduction.html#what-is-vue) + [Vite](https://vitejs.dev/) + [TypeScript](https://www.typescriptlang.org/) + [Antdv](https://antdv.com/docs/vue/getting-started-cn) + [Pinia](https://pinia.vuejs.org/)构建。
项目采用Vue 3 `<script setup>` SFC写法请查看[script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup)了解更多信息。
### 构建运行
```bash
pnpm dev # 本地运行
pnpm build # 打包
```
## 参与贡献
### 环境准备
#### Node.js 和 pnpm
开发需要 Node.js 16+ 和 `pnpm` v8。
推荐使用 [`nvm`](https://github.com/nvm-sh/nvm) 管理 Node.js避免权限问题的同时还能够随时切换当前使用的 Node.js 的版本。在 Windows 系统下的开发者可以使用 [`nvm-windows`](https://github.com/coreybutler/nvm-windows)。
推荐使用`pnpm`,节约内存。 在 `pnpm` 的[官网](https://pnpm.io/installation)选择一种方式安装即可。
#### 编辑器
这边我们推荐使用 VSCode 我们我们尽量采用工具化方式来约束开发规范和编码风格, 使用VSCode即可应用现有配置和推荐你安装适合项目的插件。 具体配置看`/.vscode` 目录
因为我们在升级vue3的过程中vue2版本也在不断迭代 为了确保我们始终是在最新的代码基础上开发,编写某个页面前记得先从`web-vue` 目录中找到同名文件先替换下。
## 目录结构
```
.
├── .vscode
│ └── setting.json
├── dist
├── mock
│ └── app.tstsx
├── src
│ ├── components # 公共组件
│ ├── assets # 静态资源
│ ├── interface # 类型定义
│ ├── router # 路由配置
│ ├── stores # 状态管理器
│ │ └── index.ts
│ ├── pages # 页面
│ │ ├── login
│ │ └── user
│ ├── utils # 工具文件
│ │ └── index.ts
│ ├── api # 接口文件
│ │ └── api.ts
│ ├── app.vue
│ ├── main.ts
├── node_modules
├── .env
├── eslint.json
├── vite.config.ts // vite配置
├── package.json
├── tsconfig.json
└── type.d.ts
```
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).

View File

@ -59,6 +59,7 @@ declare global {
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
@ -76,5 +77,5 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
}

View File

@ -1,52 +1,73 @@
<template>
<div class="full-content">
<a-form ref="editForm" :model="temp" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
<a-form
ref="editForm"
:model="temp"
:rules="rules"
:label-col="{ span: 4 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="SMTP 服务器" prop="host">
<a-auto-complete v-model="temp.host" placeholder="SMTP 服务器域名" option-label-prop="value">
<template #dataSource>
<a-select-opt-group v-for="group in hostDataSource" :key="group.title">
<span #label>
{{ group.title }}
</span>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.value">
{{ opt.title }} {{ opt.value }}
</a-select-option>
</a-select-opt-group>
<a-auto-complete
v-model:value="temp.host"
placeholder="SMTP 服务器域名"
class="certain-category-search"
option-label-prop="value"
:options="hostDataSource"
>
<template #option="item">
<template v-if="item.options">
<span>
{{ item.title }}
</span>
</template>
<template v-else>
<div style="display: flex; justify-content: space-between">
{{ item.title }} {{ item.value }}
</div>
</template>
</template>
</a-auto-complete>
</a-form-item>
<a-form-item label="SMTP 端口" prop="port">
<a-auto-complete v-model="temp.port" placeholder="SMTP 服务器端口" option-label-prop="value">
<template #dataSource>
<a-select-opt-group v-for="group in portDataSource" :key="group.title">
<span #label>
{{ group.title }}
<a-auto-complete
v-model:value="temp.port"
placeholder="SMTP 服务器端口"
option-label-prop="value"
:options="portDataSource"
>
<template #option="item">
<template v-if="item.options">
<span>
{{ item.title }}
</span>
<a-select-option v-for="opt in group.children" :key="opt.title" :value="opt.value">
{{ opt.title }} {{ opt.value }}
</a-select-option>
</a-select-opt-group>
</template>
<template v-else>
<div style="display: flex; justify-content: space-between">
{{ item.title }} {{ item.value }}
</div>
</template>
</template>
</a-auto-complete>
</a-form-item>
<a-form-item label="用户名" prop="user">
<a-input v-model="temp.user" type="text" placeholder="发件人名称" />
<a-input v-model:value="temp.user" type="text" placeholder="发件人名称"/>
</a-form-item>
<a-form-item label="密码" :prop="`${this.temp.type === 'add' ? 'pass' : 'pass-update'}`">
<a-input-password v-model="temp.pass" type="text" placeholder="邮箱密码或者授权码" />
<a-form-item :label="temp.type === 'add' ? '密码' : '密码更新'" prop="pass">
<a-input-password v-model:value="temp.pass" type="password" placeholder="邮箱密码或者授权码"/>
</a-form-item>
<a-form-item label="邮箱账号" prop="from">
<!-- <a-input v-model="temp.from" type="text" placeholder="发送方邮箱账号" /> -->
<a-tooltip>
<template #title>
支持配置发送方遵循RFC-822标准 发件人可以是以下形式
支持配置发送方遵循 RFC-822 标准 发件人可以是以下形式
<ul>
<li>1. user@xxx.xx</li>
<li>2. name &lt;user@xxx.xx&gt;</li>
</ul>
</template>
<a-auto-complete
v-model="temp.from"
v-model:value="temp.from"
placeholder="发送方邮箱账号"
option-label-prop="value"
@search="handleFromSearch"
@ -60,182 +81,156 @@
</a-tooltip>
</a-form-item>
<a-form-item label="SSL 连接" prop="sslEnable">
<a-switch v-model="temp.sslEnable" checked-children="启用" un-checked-children="停用" />
<!-- <a-input v-show="temp.sslEnable" v-model="temp.socketFactoryPort" type="text" placeholder="SSL 端口" /> -->
<a-switch v-model:checked="temp.sslEnable" checked-children="启用" un-checked-children="停用"/>
</a-form-item>
<a-form-item label="超时时间" prop="timeout">
<a-input-number
style="width: 100%"
:min="3"
v-model="temp.timeout"
type="text"
placeholder="单位秒,默认 10 秒,最小 3 秒"
/>
<a-input-number style="width: 100%" :min="3" v-model:value="temp.timeout" type="number" placeholder="单位秒,默认 10 秒,最小 3 秒"/>
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
<a-button type="primary" class="btn" :disabled="submitAble" @click="onSubmit">提交</a-button>
</a-form-item>
</a-form>
<a-alert
message="阿里云企业邮箱配置"
description="SMTP 地址smtp.mxhichina.com端口使用 465 并且开启 SSL用户名需要和邮件发送人一致密码为邮箱的登录密码"
type="info"
show-icon
/>
<br />
<a-alert
message="QQ 邮箱配置"
type="info"
description="SMTP 地址【smtp.qq.com】用户名一般是QQ号码密码是邮箱授权码端口默认 587/465"
show-icon
/>
<br />
<a-alert
message="163 邮箱配置"
description="SMTP 地址【smtp.163.com, smtp.126.com...】,密码是邮箱授权码,端口默认 25SSL 端口 465"
type="info"
show-icon
/>
<br />
<a-alert message="Gmail 邮箱配置" description="待完善" type="info" show-icon />
<a-alert message="阿里云企业邮箱配置" description="SMTP 地址smtp.mxhichina.com端口使用 465 并且开启 SSL用户名需要和邮件发送人一致密码为邮箱的登录密码" type="info" show-icon/>
<br/>
<a-alert message="QQ 邮箱配置" type="info" description="SMTP 地址【smtp.qq.com】用户名一般是 QQ 号码,密码是邮箱授权码,端口默认 587/465" show-icon/>
<br/>
<a-alert message="163 邮箱配置" description="SMTP 地址【smtp.163.com, smtp.126.com...】,密码是邮箱授权码,端口默认 25SSL 端口 465" type="info" show-icon/>
<br/>
<a-alert message="Gmail 邮箱配置" description="待完善" type="info" show-icon/>
</div>
</template>
<script>
import { getMailConfigData, editMailConfig } from '@/api/system'
export default {
data() {
return {
temp: {},
submitAble: false,
rules: {
host: [{ required: true, message: 'Please input SMTP host', trigger: 'blur' }],
pass: [{ required: true, message: 'Please input password', trigger: 'blur' }],
user: [{ required: true, message: 'Please input user name', trigger: 'blur' }],
from: [{ required: true, message: 'Please input email account', trigger: 'blur' }]
<script lang="ts" setup>
import {ref, onMounted} from 'vue';
import {getMailConfigData, editMailConfig} from "@/api/system";
import {message} from "ant-design-vue";
const temp = ref({});
const submitAble = ref(false);
const fromResult = ref<string[]>([]);
const hostDataSource = [
{
title: "参考数据",
options: [
{
title: "QQ邮箱",
value: "smtp.qq.com",
},
fromResult: [],
hostDataSource: [
{
title: '参考数据',
children: [
{
title: 'QQ邮箱',
value: 'smtp.qq.com'
},
{
title: '163邮箱',
value: 'smtp.163.com'
},
{
title: '126邮箱',
value: 'smtp.126.com'
},
{
title: '阿里云企业邮箱',
value: 'smtp.mxhichina.com'
},
{
title: 'gmail邮箱',
value: 'smtp.gmail.com'
}
]
}
],
portDataSource: [
{
title: 'QQ邮箱',
children: [
{
title: 'QQ邮箱',
value: '587'
},
{
title: 'QQ邮箱 SSL',
value: '465'
}
]
},
{
title: '163邮箱',
children: [
{
title: '163邮箱',
value: '25'
},
{
title: '163邮箱 SSL',
value: '465'
}
]
},
{
title: '阿里云企业邮箱',
children: [
{
title: '阿里云企业邮箱 SSL',
value: '465'
}
]
},
{
title: '通用邮箱',
children: [
{
title: '通用邮箱 SSL',
value: '465'
}
]
}
]
}
{
title: "163邮箱",
value: "smtp.163.com",
},
{
title: "126邮箱",
value: "smtp.126.com",
},
{
title: "阿里云企业邮箱",
value: "smtp.mxhichina.com",
},
{
title: "gmail邮箱",
value: "smtp.gmail.com",
},
],
},
mounted() {
this.loadData()
];
const portDataSource = ref([
{
title: "QQ邮箱",
options: [
{
title: "QQ邮箱",
value: "587",
},
{
title: "QQ邮箱 SSL",
value: "465",
},
],
},
methods: {
// load data
loadData() {
getMailConfigData().then((res) => {
if (res.code === 200) {
this.temp = res.data || { type: 'add' }
if (this.temp.port) {
this.temp.port = this.temp.port + ''
}
}
})
},
// submit
onSubmit() {
// disabled submit button
this.submitAble = true
// if (this.temp.sslsslEnable === false) {
// this.temp.socketFactoryPort = "";
// }
editMailConfig(this.temp).then((res) => {
if (res.code === 200) {
//
$notification.success({
message: res.msg
})
}
// button recover
this.submitAble = false
})
},
handleFromSearch(value) {
let result
if (!value || value.indexOf('@') >= 0) {
result = []
} else {
result = ['gmail.com', '163.com', 'qq.com'].map((domain) => `${value}@${domain}`)
{
title: "163邮箱",
options: [
{
title: "163邮箱",
value: "25",
},
{
title: "163邮箱 SSL",
value: "465",
},
],
},
{
title: "阿里云企业邮箱",
options: [
{
title: "阿里云企业邮箱 SSL",
value: "465",
},
],
},
{
title: "通用邮箱",
options: [
{
title: "通用邮箱 SSL",
value: "465",
},
],
},
]);
const rules = {
host: [{required: true, message: "Please input SMTP host", trigger: "blur"}],
pass: [{required: true, message: "Please input password", trigger: "blur"}],
user: [{required: true, message: "Please input user name", trigger: "blur"}],
from: [{required: true, message: "Please input email account", trigger: "blur"}],
};
// load data
const loadData = () => {
getMailConfigData().then((res) => {
if (res.code === 200) {
temp.value = res.data || {type: "add"};
if (temp.value.port) {
temp.value.port = temp.value.port + "";
}
this.fromResult = result
}
});
};
// submit
const onSubmit = () => {
// disabled submit button
submitAble.value = true;
editMailConfig(temp.value).then((res) => {
if (res.code === 200) {
//
message.success(res.msg);
}
// button recover
submitAble.value = false;
});
};
const handleFromSearch = (value: string) => {
let result;
if (!value || value.indexOf("@") >= 0) {
result = [];
} else {
result = ["gmail.com", "163.com", "qq.com"].map((domain) => `${value}@${domain}`);
}
}
fromResult.value = result;
};
onMounted(() => {
loadData();
});
</script>
<style scoped>
.btn {
margin-left: 20px;

View File

@ -1,30 +1,34 @@
<template>
<div class="full-content">
<a-alert message="账号如果开启 MFA(两步验证),使用 Oauth2 登录不会验证 MFA(两步验证)" type="info" show-icon />
<a-alert
message="账号如果开启 MFA(两步验证),使用 Oauth2 登录不会验证 MFA(两步验证)"
type="info"
show-icon
/>
<a-tabs>
<a-tab-pane key="gitee" tab="Gitee Oauth2">
<a-form ref="editForm" :model="gitee" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
<a-form-item label="是否开启" prop="enabled">
<a-switch v-model="gitee.enabled" checked-children="启用" un-checked-children="停用" />
<a-switch v-model:checked="gitee.enabled" checked-children="启用" un-checked-children="停用"/>
</a-form-item>
<a-form-item label="客户端ID" prop="clientId">
<a-input v-model="gitee.clientId" type="text" placeholder="请输入客户端ID [clientId]" />
<a-input v-model:value="gitee.clientId" type="text" placeholder="请输入客户端ID [clientId]" />
</a-form-item>
<a-form-item label="客户端密钥" prop="clientSecret">
<a-input-password v-model="gitee.clientSecret" placeholder="请输入客户端密钥 [clientSecret]" />
<a-input-password v-model:value="gitee.clientSecret" placeholder="请输入客户端密钥 [clientSecret]" />
</a-form-item>
<a-form-item label="回调 url" prop="redirectUri">
<template #help>参考地址{{ `${this.host}/oauth2-gitee` }}</template>
<a-input v-model="gitee.redirectUri" type="text" placeholder="请输入回调重定向 url [redirectUri]" />
<template #help>参考地址{{ `${host}/oauth2-gitee` }}</template>
<a-input v-model:value="gitee.redirectUri" type="text" placeholder="请输入回调重定向 url [redirectUri]" />
</a-form-item>
<!-- <a-form-item label="登录url">
<a-input :value="`${this.host}/oauth2-render-gitee`" type="text" />
<a-input :value="`${host}/oauth2-render-gitee`" type="text" />
</a-form-item> -->
<a-form-item label="自动创建用户" prop="autoCreteUser">
<a-switch v-model="gitee.autoCreteUser" checked-children="启用" un-checked-children="停用" />
<a-switch v-model:checked="gitee.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="忽略校验 state" prop="ignoreCheckState">
<a-switch v-model="gitee.ignoreCheckState" checked-children="忽略" un-checked-children="校验" />
<a-switch v-model:checked="gitee.ignoreCheckState" checked-children="忽略" un-checked-children="校验" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
@ -35,35 +39,35 @@
<a-tab-pane key="maxkey" tab="MaxKey Oauth2">
<a-form ref="editForm" :model="maxkey" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
<a-form-item label="是否开启" prop="enabled">
<a-switch v-model="maxkey.enabled" checked-children="启用" un-checked-children="停用" />
<a-switch v-model:checked="maxkey.enabled" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="客户端ID" prop="clientId">
<a-input v-model="maxkey.clientId" type="text" placeholder="请输入客户端ID [clientId]" />
<a-input v-model:value="maxkey.clientId" type="text" placeholder="请输入客户端ID [clientId]" />
</a-form-item>
<a-form-item label="客户端密钥" prop="clientSecret">
<a-input-password v-model="maxkey.clientSecret" placeholder="请输入客户端密钥 [clientSecret]" />
<a-input-password v-model:value="maxkey.clientSecret" placeholder="请输入客户端密钥 [clientSecret]" />
</a-form-item>
<a-form-item label="授权 url" prop="authorizationUri">
<a-input v-model="maxkey.authorizationUri" type="text" placeholder="请输入授权 url [authorizationUri]" />
<a-input v-model:value="maxkey.authorizationUri" type="text" placeholder="请输入授权 url [authorizationUri]" />
</a-form-item>
<a-form-item label="令牌 url" prop="accessTokenUri">
<a-input v-model="maxkey.accessTokenUri" type="text" placeholder="请输入令牌 url [accessTokenUri]" />
<a-input v-model:value="maxkey.accessTokenUri" type="text" placeholder="请输入令牌 url [accessTokenUri]" />
</a-form-item>
<a-form-item label="用户信息 url" prop="userInfoUri">
<a-input v-model="maxkey.userInfoUri" type="text" placeholder="请输入用户信息 url [userInfoUri]" />
<a-input v-model:value="maxkey.userInfoUri" type="text" placeholder="请输入用户信息 url [userInfoUri]" />
</a-form-item>
<a-form-item label="回调 url" prop="redirectUri">
<template #help>参考地址{{ `${this.host}/oauth2-maxkey` }}</template>
<a-input v-model="maxkey.redirectUri" type="text" placeholder="请输入回调重定向 url [redirectUri]" />
<template slot="help">参考地址{{ `${this.host}/oauth2-maxkey` }}</template>
<a-input v-model:value="maxkey.redirectUri" type="text" placeholder="请输入回调重定向 url [redirectUri]" />
</a-form-item>
<!-- <a-form-item label="登录url">
<a-input :value="`${this.host}/oauth2-render-maxkey`" type="text" />
</a-form-item> -->
<a-form-item label="自动创建用户" prop="autoCreteUser">
<a-switch v-model="maxkey.autoCreteUser" checked-children="启用" un-checked-children="停用" />
<a-switch v-model:checked="maxkey.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="忽略校验 state" prop="ignoreCheckState">
<a-switch v-model="maxkey.ignoreCheckState" checked-children="忽略" un-checked-children="校验" />
<a-switch v-model:checked="maxkey.ignoreCheckState" checked-children="忽略" un-checked-children="校验" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
@ -74,27 +78,27 @@
<a-tab-pane key="github" tab="Github Oauth2">
<a-form ref="editForm" :model="github" :rules="rules" :label-col="{ span: 4 }" :wrapper-col="{ span: 16 }">
<a-form-item label="是否开启" prop="enabled">
<a-switch v-model="github.enabled" checked-children="启用" un-checked-children="停用" />
<a-switch v-model:checked="github.enabled" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="客户端ID" prop="clientId">
<a-input v-model="github.clientId" type="text" placeholder="请输入客户端ID [clientId]" />
<a-input v-model:value="github.clientId" type="text" placeholder="请输入客户端ID [clientId]" />
</a-form-item>
<a-form-item label="客户端密钥" prop="clientSecret">
<a-input-password v-model="github.clientSecret" placeholder="请输入客户端密钥 [clientSecret]" />
<a-input-password v-model:value="github.clientSecret" placeholder="请输入客户端密钥 [clientSecret]" />
</a-form-item>
<a-form-item label="回调 url" prop="redirectUri">
<template #help>参考地址{{ `${this.host}/oauth2-github` }}</template>
<a-input v-model="github.redirectUri" type="text" placeholder="请输入回调重定向 url [redirectUri]" />
<template slot="help">参考地址{{ `${this.host}/oauth2-github` }}</template>
<a-input v-model:value="github.redirectUri" type="text" placeholder="请输入回调重定向 url [redirectUri]" />
</a-form-item>
<!-- <a-form-item label="登录url">
<a-input :value="`${this.host}/oauth2-render-github`" type="text" />
</a-form-item> -->
<a-form-item label="自动创建用户" prop="autoCreteUser">
<a-switch v-model="github.autoCreteUser" checked-children="启用" un-checked-children="停用" />
<a-switch v-model:checked="github.autoCreteUser" checked-children="启用" un-checked-children="停用" />
</a-form-item>
<a-form-item label="忽略校验 state" prop="ignoreCheckState">
<a-switch v-model="github.ignoreCheckState" checked-children="忽略" un-checked-children="校验" />
<a-switch v-model:checked="github.ignoreCheckState" checked-children="忽略" un-checked-children="校验" />
</a-form-item>
<a-form-item :wrapper-col="{ span: 14, offset: 4 }">
@ -105,49 +109,41 @@
</a-tabs>
</div>
</template>
<script>
import { oauthConfigOauth2, oauthConfigOauth2Save } from '@/api/system'
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { oauthConfigOauth2, oauthConfigOauth2Save } from '@/api/system';
import { message } from 'ant-design-vue';
export default {
data() {
return {
maxkey: {},
gitee: {},
github: {},
rules: {},
provides: ['gitee', 'maxkey', 'github'],
host: ''
const gitee = ref({});
const maxkey = ref({});
const github = ref({});
const provides = ref(['gitee', 'maxkey', 'github']);
let host = '';
const loadData = () => {
host = `${location.protocol}//${location.host}`;
provides.value.forEach((item) => {
oauthConfigOauth2({ provide: item }).then((res) => {
if (res.code === 200) {
if (item === 'gitee') gitee.value = Object.assign(res.data || {}, { provide: item });
else if (item === 'maxkey') maxkey.value = Object.assign(res.data || {}, { provide: item });
else if (item === 'github') github.value = Object.assign(res.data || {}, { provide: item });
}
});
});
};
const onSubmit = (key) => {
oauthConfigOauth2Save(key === 'gitee' ? gitee.value : key === 'maxkey' ? maxkey.value : github.value).then((res) => {
if (res.code === 200) {
message.success( res.msg);
}
},
mounted() {
this.host = `${location.protocol}//${location.host}`
this.loadData()
},
methods: {
// load data
loadData() {
this.provides.forEach((item) => {
oauthConfigOauth2({
provide: item
}).then((res) => {
if (res.code === 200) {
this[item] = Object.assign(res.data || {}, { provide: item })
}
})
})
},
// submit
onSubmit(key) {
oauthConfigOauth2Save(this[key]).then((res) => {
if (res.code === 200) {
//
$notification.success({
message: res.msg
})
}
})
}
}
}
});
};
onMounted(() => {
loadData();
});
</script>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -28,11 +28,11 @@
<a-tooltip>{{ text }}</a-tooltip>
</template>
<template v-if="column.dataIndex === 'description'">
<template v-else-if="column.dataIndex === 'description'">
<a-tooltip>{{ text }}</a-tooltip>
</template>
<template v-if="column.dataIndex === 'operation'">
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-button size="small" type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button size="small" type="primary" @click="configMeun(record)">菜单</a-button>

View File

@ -32,12 +32,15 @@ const routeMenuMap: Record<string, string> = {
permission_group: '/user/permission-group',
user_log: '/operation/log',
user_login_log: '/user/login-log',
monitorConfigEmail: '/system/mail',
cacheManage: '/system/cache',
logManage: '/system/log',
update: '/system/upgrade',
// 配置管理
sysConfig: '/system/config',
authConfig: '/system/oauth-config',
monitorConfigEmail: '/system/mail',
systemExtConfig: '/system/ext-config',
projectSearch: '/node/search',
// 数据库备份
backup: '/system/backup',

View File

@ -1,29 +1,27 @@
import { GlobalWindow } from '@/interface/common'
// 常量池
export const USER_NAME_KEY = 'Jpom-UserName'
export const USER_NAME_KEY = "Jpom-UserName";
export const TOKEN_KEY = 'Jpom-Token'
export const TOKEN_KEY = "Jpom-Token";
export const LONG_TERM_TOKEN = 'Jpom-Long-Term-Token'
export const LONG_TERM_TOKEN = "Jpom-Long-Term-Token";
export const USER_INFO_KEY = 'Jpom-User'
export const USER_INFO_KEY = "Jpom-User";
export const MENU_KEY = 'Jpom-Menus'
export const MENU_KEY = "Jpom-Menus";
export const TOKEN_HEADER_KEY = 'Authorization'
export const TOKEN_HEADER_KEY = "Authorization";
export const ACTIVE_TAB_KEY = 'Jpom-ActiveTab'
export const ACTIVE_TAB_KEY = "Jpom-ActiveTab";
export const TAB_LIST_KEY = 'Jpom-TabList'
export const TAB_LIST_KEY = "Jpom-TabList";
export const ACTIVE_MENU_KEY = 'Jpom-ActiveMenu'
export const ACTIVE_MENU_KEY = "Jpom-ActiveMenu";
export const MANAGEMENT_ACTIVE_TAB_KEY = 'Jpom-management-ActiveTab'
export const MANAGEMENT_ACTIVE_TAB_KEY = "Jpom-management-ActiveTab";
export const MANAGEMENT_TAB_LIST_KEY = 'Jpom-management-TabList'
export const MANAGEMENT_TAB_LIST_KEY = "Jpom-management-TabList";
export const MANAGEMENT_ACTIVE_MENU_KEY = 'Jpom-management-ActiveMenu'
export const MANAGEMENT_ACTIVE_MENU_KEY = "Jpom-management-ActiveMenu";
// export const GUIDE_FLAG_KEY = "Jpom-GuideFlag";
@ -31,37 +29,33 @@ export const MANAGEMENT_ACTIVE_MENU_KEY = 'Jpom-management-ActiveMenu'
// export const GUIDE_NODE_USED_KEY = "Jpom-Node-Guide-Used";
export const NO_NOTIFY_KEY = 'tip'
export const NO_NOTIFY_KEY = "tip";
export const NO_LOADING_KEY = 'loading'
export const NO_LOADING_KEY = "loading";
export const LOADING_TIP = 'loadingTip'
export const LOADING_TIP = "loadingTip";
const cachePageLimitKeyName = 'page_limit'
const cachePageLimitKeyName = "page_limit";
export function getCachePageLimit(): number {
return Number(localStorage.getItem(cachePageLimitKeyName) || 10)
export function getCachePageLimit() {
return parseInt(localStorage.getItem(cachePageLimitKeyName) || 10);
}
/**
*
*/
export const PAGE_DEFAULT_SIZW_OPTIONS: string[] = ['5', '10', '15', '20', '25', '30', '35', '40', '50']
export const PAGE_DEFAULT_SIZW_OPTIONS = ["5", "10", "15", "20", "25", "30", "35", "40", "50"];
/**
*
* @param {Number} total
* @returns String
*/
export function PAGE_DEFAULT_SHOW_TOTAL(total: number) {
return `总计 ${total}`
export function PAGE_DEFAULT_SHOW_TOTAL(total) {
return `总计 ${total}`;
}
export const PAGE_DEFAULT_LIST_QUERY = {
page: 1,
limit: isNaN(getCachePageLimit()) ? 10 : getCachePageLimit(),
total: 0
}
export const PAGE_DEFAULT_LIST_QUERY = { page: 1, limit: isNaN(getCachePageLimit) ? 10 : getCachePageLimit, total: 0 };
/**
*
@ -69,10 +63,10 @@ export const PAGE_DEFAULT_LIST_QUERY = {
* @param {Array} pageSizeOptions
* @returns
*/
export function COMPUTED_PAGINATION(queryParam: any, pageSizeOptions?: []) {
export function COMPUTED_PAGINATION(queryParam, pageSizeOptions) {
// console.log(queryParam);
const limit = queryParam.limit || PAGE_DEFAULT_LIST_QUERY.limit
const total = queryParam.total || 0
const limit = queryParam.limit || PAGE_DEFAULT_LIST_QUERY.limit;
const total = queryParam.total || 0;
return {
total: total,
current: queryParam.page || 1,
@ -83,10 +77,10 @@ export function COMPUTED_PAGINATION(queryParam: any, pageSizeOptions?: []) {
showLessItems: true,
// 只有在分页条数在 小于 2 的时候隐藏,避免设置太大无法切回
hideOnSinglePage: limit <= 20,
showTotal: (total: number) => {
return PAGE_DEFAULT_SHOW_TOTAL(total)
}
}
showTotal: (total) => {
return PAGE_DEFAULT_SHOW_TOTAL(total);
},
};
}
/**
@ -95,29 +89,29 @@ export function COMPUTED_PAGINATION(queryParam: any, pageSizeOptions?: []) {
* @param {JSON} param1
* @returns
*/
export function CHANGE_PAGE(listQuery: any, { pagination, sorter }: any) {
export function CHANGE_PAGE(listQuery, { pagination, sorter }) {
if (pagination && Object.keys(pagination).length) {
listQuery = { ...listQuery, page: pagination.current, limit: pagination.pageSize }
listQuery = { ...listQuery, page: pagination.current, limit: pagination.pageSize };
//
localStorage.setItem(cachePageLimitKeyName, pagination.pageSize)
localStorage.setItem(cachePageLimitKeyName, pagination.pageSize);
//
PAGE_DEFAULT_LIST_QUERY.limit = pagination.pageSize
PAGE_DEFAULT_LIST_QUERY.limit = pagination.pageSize;
}
if (sorter && Object.keys(sorter).length) {
listQuery = { ...listQuery, order: sorter.order, order_field: sorter.field }
listQuery = { ...listQuery, order: sorter.order, order_field: sorter.field };
}
return listQuery
return listQuery;
}
/**
* ID
*/
export const CACHE_WORKSPACE_ID = 'workspaceId'
export const CACHE_WORKSPACE_ID = "workspaceId";
/**
*
*/
export const RESTART_UPGRADE_WAIT_TIME_COUNT = 80
export const RESTART_UPGRADE_WAIT_TIME_COUNT = 80;
/**
* cron
@ -126,131 +120,144 @@ export const RESTART_UPGRADE_WAIT_TIME_COUNT = 80
*/
export const CRON_DATA_SOURCE = [
{
title: '取消定时,不再定时执行(支持 ! 前缀禁用定时执行,如:!0 0/1 * * * ?',
title: "取消定时,不再定时执行(支持 ! 前缀禁用定时执行,如:!0 0/1 * * * ?",
children: [
{
title: '',
value: ''
}
]
title: "",
value: "",
},
],
},
{
title: '分钟级别',
title: "分钟级别",
children: [
{
title: '1分钟',
value: '0 0/1 * * * ?'
title: "1分钟",
value: "0 0/1 * * * ?",
},
{
title: '5分钟',
value: '0 0/5 * * * ?'
title: "5分钟",
value: "0 0/5 * * * ?",
},
{
title: '10分钟',
value: '0 0/10 * * * ?'
title: "10分钟",
value: "0 0/10 * * * ?",
},
{
title: '30分钟',
value: '0 0/30 * * * ?'
}
]
title: "30分钟",
value: "0 0/30 * * * ?",
},
],
},
{
title: '小时级别',
title: "小时级别",
children: [
{
title: '每小时',
value: '0 0 0/1 * * ?'
}
]
title: "每小时",
value: "0 0 0/1 * * ?",
},
],
},
{
title: '天级别',
title: "天级别",
children: [
{
title: '凌晨0点和中午12点',
value: '0 0 0,12 * * ?'
title: "凌晨0点和中午12点",
value: "0 0 0,12 * * ?",
},
{
title: '凌晨0点',
value: '0 0 0 * * ?'
}
]
title: "凌晨0点",
value: "0 0 0 * * ?",
},
],
},
{
title: '秒级别(默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]',
title: "秒级别(默认未开启秒级别,需要去修改配置文件中:[system.timerMatchSecond]",
children: [
{
title: '5秒一次',
value: '0/5 * * * * ?'
title: "5秒一次",
value: "0/5 * * * * ?",
},
{
title: '10秒一次',
value: '0/10 * * * * ?'
title: "10秒一次",
value: "0/10 * * * * ?",
},
{
title: '30秒一次',
value: '0/30 * * * * ?'
}
]
}
]
title: "30秒一次",
value: "0/30 * * * * ?",
},
],
},
];
/**
*
*/
export const ZIP_ACCEPT: string = '.tar,.bz2,.gz,.zip,.tar.bz2,.tar.gz'
export const ZIP_ACCEPT = ".tar,.bz2,.gz,.zip,.tar.bz2,.tar.gz";
/**
* mfa app
*/
export const MFA_APP_TIP_ARRAY: string[] = [
'<strong>【推荐】微信小程序搜索 数盾OTP',
export const MFA_APP_TIP_ARRAY = [
"<strong>【推荐】微信小程序搜索 数盾OTP",
'<strong>【推荐】腾讯身份验证码</strong> 简单好用 <a href="https://a.app.qq.com/o/simple.jsp?pkgname=com.tencent.authenticator">Android</a>',
'<strong>Authy</strong> 功能丰富 专为两步验证码 <a href="https://authy.com/download/">iOS/Android/Windows/Mac/Linux</a> &nbsp; <a href="https://chrome.google.com/webstore/detail/authy/gaedmjdfmmahhbjefcbgaolhhanlaolb?hl=cn">Chrome 扩展</a>',
'<strong>Google Authenticator</strong> 简单易用,但不支持密钥导出备份 <a href="https://apps.apple.com/us/app/google-authenticator/id388497605">iOS</a> <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=cn">Android</a>',
'<strong>Microsoft Authenticator</strong> 使用微软全家桶的推荐 <a href="https://www.microsoft.com/zh-cn/account/authenticator">iOS/Android</a>',
'<strong>1Password</strong> 强大安全的密码管理付费应用<a href="https://1password.com/zh-cn/downloads/">iOS/Android/Windows/Mac/Linux/ChromeOS</a>'
]
'<strong>1Password</strong> 强大安全的密码管理付费应用<a href="https://1password.com/zh-cn/downloads/">iOS/Android/Windows/Mac/Linux/ChromeOS</a>',
];
/**
* DSL
*/
export const PROJECT_DSL_DEFATUL =
'# scriptId 可以是项目路径下脚本文件名或者系统中的脚本模版ID\r\n' +
'description: 测试\r\n' +
'run:\r\n' +
' start:\r\n' +
'# scriptId: project.sh\r\n' +
' scriptId: \r\n' +
' scriptArgs: start\r\n' +
' scriptEnv:\r\n' +
"# scriptId 可以是项目路径下脚本文件名或者系统中的脚本模版ID\r\n" +
"description: 测试\r\n" +
"run:\r\n" +
" start:\r\n" +
"# scriptId: project.sh\r\n" +
" scriptId: \r\n" +
" scriptArgs: start\r\n" +
" scriptEnv:\r\n" +
' "boot_active": test\r\n' +
' status:\r\n' +
'# scriptId: project.sh\r\n' +
' scriptId: \r\n' +
' scriptArgs: status\r\n' +
' stop:\r\n' +
'# scriptId: project.sh\r\n' +
' scriptId: \r\n' +
' scriptArgs: stop\r\n' +
'# restart:\r\n' +
'## scriptId: project.sh\r\n' +
'# scriptId: \r\n' +
'# scriptArgs: restart\r\n' +
'# scriptEnv:\r\n' +
" status:\r\n" +
"# scriptId: project.sh\r\n" +
" scriptId: \r\n" +
" scriptArgs: status\r\n" +
" stop:\r\n" +
"# scriptId: project.sh\r\n" +
" scriptId: \r\n" +
" scriptArgs: stop\r\n" +
"# restart:\r\n" +
"## scriptId: project.sh\r\n" +
"# scriptId: \r\n" +
"# scriptArgs: restart\r\n" +
"# scriptEnv:\r\n" +
'# "boot_active": test\r\n' +
'file:\r\n' +
'# 备份文件保留个数\r\n' +
'# backupCount: 5\r\n' +
'# 限制备份指定文件后缀(支持正则)\r\n' +
"file:\r\n" +
"# 备份文件保留个数\r\n" +
"# backupCount: 5\r\n" +
"# 限制备份指定文件后缀(支持正则)\r\n" +
"# backupSuffix: [ '.jar','.html','^.+\\.(?i)(txt)$' ]\r\n" +
'# 项目文件备份路径\r\n' +
'# backupPath: /data/jpom_backup\r\n' +
'config:\r\n' +
'# 是否开启日志备份功能\r\n' +
'# autoBackToFile: true\r\n' +
'\r\n'
"# 项目文件备份路径\r\n" +
"# backupPath: /data/jpom_backup\r\n" +
"config:\r\n" +
"# 是否开启日志备份功能\r\n" +
"# autoBackToFile: true\r\n" +
"\r\n";
/**
* socket
* @param {String} url
* @param {String} parameter
* @returns
*/
export function getWebSocketUrl(url, parameter) {
const protocol = location.protocol === "https:" ? "wss://" : "ws://";
const domain = window.routerBase;
const fullUrl = (domain + url).replace(new RegExp("//", "gm"), "/");
return `${protocol}${location.host}${fullUrl}?${parameter}`;
}
/**
*
@ -259,55 +266,54 @@ export const PROJECT_DSL_DEFATUL =
* @params asyncHandle {Function} - `list` return Promise来确定是否继续进行迭代
* @return {Promise} - Promise
*/
export function concurrentExecution<T>(list: T[], limit: number, asyncHandle: (arg: T) => Promise<any>) {
export function concurrentExecution(list, limit, asyncHandle) {
// 递归执行
const recursion = (arr: T[]): Promise<T> => {
const recursion = (arr) => {
// 执行方法 arr.shift() 取出并移除第一个数据
return asyncHandle(arr.shift() as T).then((res: T) => {
return asyncHandle(arr.shift()).then((res) => {
// 数组还未迭代完,递归继续进行迭代
if (arr.length !== 0) {
return recursion(arr)
return recursion(arr);
} else {
return res
return res;
}
})
}
});
};
// 创建新的并发数组
const listCopy = ([] as T[]).concat(list)
let listCopy = [].concat(list);
// 正在进行的所有并发异步操作
let asyncList = [] as Promise<T>[]
limit = limit > listCopy.length ? listCopy.length : limit
let asyncList = [];
limit = limit > listCopy.length ? listCopy.length : limit;
while (limit--) {
asyncList.push(recursion(listCopy))
asyncList.push(recursion(listCopy));
}
// 所有并发异步操作都完成后,本次并发控制迭代完成
return Promise.all(asyncList)
return Promise.all(asyncList);
}
export function readJsonStrField(json: string, key: string) {
export function readJsonStrField(json, key) {
try {
const data = JSON.parse(json)[key] || ''
if (Object.prototype.toString.call(data) === '[object Object]') {
return JSON.stringify(data)
const data = JSON.parse(json)[key] || "";
if (Object.prototype.toString.call(data) === "[object Object]") {
return JSON.stringify(data);
}
return data
return data;
} catch (e) {
//
}
return ''
return "";
}
export function randomStr(len = 2) {
const $chars = 'ABCDEFGHJKMNPQRSTWXYZ0123456789'
const $chars = "ABCDEFGHJKMNPQRSTWXYZ0123456789";
/****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
const maxPos = $chars.length
let repliccaId = ''
const maxPos = $chars.length;
let repliccaId = "";
for (let i = 0; i < len; i++) {
repliccaId += $chars.charAt(Math.floor(Math.random() * maxPos))
repliccaId += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return repliccaId
return repliccaId;
}
/**
@ -315,51 +321,46 @@ export function randomStr(len = 2) {
* @param {*} time
* @param {*} cFormat
*/
export function parseTime(time: string | number | Date | null, cFormat?: string | undefined | null) {
export function parseTime(time, cFormat) {
if (arguments.length === 0) {
return '-'
return "-";
}
if (!time) {
return '-'
return "-";
}
// 处理 time 参数
if (isNaN(Number(time)) === false) {
time = Number(time)
time = Number(time);
}
const format = cFormat || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
const format = cFormat || "{y}-{m}-{d} {h}:{i}:{s}";
let date;
if (typeof time === "object") {
date = time;
} else {
if (('' + time).length === 10) {
time = parseInt('' + time) * 1000
}
date = new Date(time)
if (("" + time).length === 10) time = parseInt(time) * 1000;
date = new Date(time);
}
if (!(date instanceof Date)) {
return time
}
const formatObj: any = {
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
a: date.getDay(),
};
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
let value = formatObj[key];
// Note: getDay() returns 0 on Sunday
if (key === 'a') {
return ['日', '一', '二', '三', '四', '五', '六'][value]
if (key === "a") {
return ["日", "一", "二", "三", "四", "五", "六"][value];
}
if (result.length > 0 && value < 10) {
value = '0' + value
value = "0" + value;
}
return value || 0
})
return time_str
return value || 0;
});
return time_str;
}
/**
@ -368,8 +369,8 @@ export function parseTime(time: string | number | Date | null, cFormat?: string
* @param defaultValue
* @returns
*/
export function renderSize(value: any, defaultValue = '-') {
return formatUnits(value, 1024, ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'], defaultValue)
export function renderSize(value, defaultValue = "-") {
return formatUnits(value, 1024, ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], defaultValue);
}
/**
@ -378,33 +379,31 @@ export function renderSize(value: any, defaultValue = '-') {
* @param defaultValue
* @returns
*/
export function renderBpsSize(value: any, defaultValue = '-') {
return formatUnits(value, 1024, ['bps', 'Kbps', 'Mbps', 'Gbps', 'Tbps', 'Pbps', 'Ebps', 'Zbps', 'Ybps'], defaultValue)
export function renderBpsSize(value, defaultValue = "-") {
return formatUnits(value, 1024, ["bps", "Kbps", "Mbps", "Gbps", "Tbps", "Pbps", "Ebps", "Zbps", "Ybps"], defaultValue);
}
/**
*
* @param {*} value
* @param defaultValue
* @param unitArr
* @param base
* @returns
*/
export function formatUnits(value: any, base: number, unitArr: string[], defaultValue = '-') {
if (null == value || value === '') {
return defaultValue
export function formatUnits(value, base, unitArr, defaultValue = "-") {
if (null == value || value === "") {
return defaultValue;
}
var index: number = 0
var srcsize: number = parseFloat(value)
var index = 0;
var srcsize = parseFloat(value);
if (srcsize <= 0) {
return defaultValue
return defaultValue;
}
// console.log(value, srcsize);
index = Math.floor(Math.log(srcsize) / Math.log(base))
var size: number = srcsize / Math.pow(base, index)
//保留的小数位数
return size.toFixed(2) + unitArr[index]
index = Math.floor(Math.log(srcsize) / Math.log(base));
var size = srcsize / Math.pow(base, index);
size = size.toFixed(2); //保留的小数位数
return size + unitArr[index];
}
/**
@ -412,39 +411,39 @@ export function formatUnits(value: any, base: number, unitArr: string[], default
* @param {function} group
* @returns Object
*/
;(Array.prototype as any).groupBy = function (group: Function) {
return group && typeof group === 'function'
Array.prototype.groupBy = function (group) {
return group && typeof group === "function"
? Array.prototype.reduce.call(
this,
function (c: any, v) {
var k = group(v)
c[k] = v
return c
function (c, v) {
var k = group(v);
c[k] = v;
return c;
},
{}
)
: this
}
: this;
};
//
export function itemGroupBy(arr: any[], groupKey: string, key: string, dataKey: string) {
key = key || 'type'
dataKey = dataKey || 'data'
export function itemGroupBy(arr, groupKey, key, dataKey) {
key = key || "type";
dataKey = dataKey || "data";
let newArr: any[] = [],
types: any = {},
let newArr = [],
types = {},
// newItem,
i,
j,
cur
cur;
for (i = 0, j = arr.length; i < j; i++) {
cur = arr[i]
cur = arr[i];
if (!(cur[groupKey] in types)) {
types[cur[groupKey]] = { [key]: cur[groupKey], [dataKey]: [] }
newArr.push(types[cur[groupKey]])
types[cur[groupKey]] = { [key]: cur[groupKey], [dataKey]: [] };
newArr.push(types[cur[groupKey]]);
}
types[cur[groupKey]][dataKey].push(cur)
types[cur[groupKey]][dataKey].push(cur);
}
return newArr
return newArr;
}
/**
@ -454,102 +453,106 @@ export function itemGroupBy(arr: any[], groupKey: string, key: string, dataKey:
* @param {String} levelCount
* @returns
*/
export function formatDuration(ms: any, seg: string, levelCount: number) {
if (isNaN(Number(ms))) {
return ms
export function formatDuration(ms, seg, levelCount) {
let msNum = Number(ms);
if (isNaN(msNum)) {
return ms;
}
seg = seg || ''
levelCount = levelCount || 5
if (ms < 0) ms = -ms
if (msNum === 0) {
return "-";
}
seg = seg || "";
levelCount = levelCount || 5;
if (msNum < 0) msNum = -msNum;
const time = {
: Math.floor(ms / 86400000),
小时: Math.floor(ms / 3600000) % 24,
分钟: Math.floor(ms / 60000) % 60,
: Math.floor(ms / 1000) % 60,
毫秒: Math.floor(ms) % 1000
}
: Math.floor(msNum / 86400000),
小时: Math.floor(msNum / 3600000) % 24,
分钟: Math.floor(msNum / 60000) % 60,
: Math.floor(msNum / 1000) % 60,
毫秒: Math.floor(msNum) % 1000,
};
return Object.entries(time)
.filter((val) => val[1] !== 0)
.map(([key, val]) => `${val}${key}`)
.splice(0, levelCount)
.join(seg)
.join(seg);
}
//小数转换为分数(小数先转换成number类型再乘以100并且保留2位小数)
export function formatPercent(point: any, keep = 2) {
export function formatPercent(point, keep = 2) {
if (!point) {
return '-'
return "-";
}
return formatPercent2(Number(point) * 100, keep)
return formatPercent2(Number(point) * 100, keep);
}
//小数转换为分数(小数先转换成number类型并且保留2位小数)
export function formatPercent2(point: any, keep = 2) {
export function formatPercent2(point, keep = 2) {
if (null == point) {
return '-'
return "-";
}
var percent: string = Number(point).toFixed(keep)
percent += '%'
return percent
var percent = Number(point).toFixed(keep);
percent += "%";
return percent;
}
//小数转换为分数(小数先转换成number类型再乘以100并且保留2位小数)
export function formatPercent2Number(point: any, keep = 2) {
export function formatPercent2Number(point, keep = 2) {
if (null == point) {
return 0
return 0;
}
return Number(Number(point).toFixed(keep))
return Number(Number(point).toFixed(keep));
}
export function compareVersion(version1: any, version2: any) {
export function compareVersion(version1, version2) {
if (version1 == null && version2 == null) {
return 0
return 0;
} else if (version1 == null) {
// null视为最小版本排在前
return -1
return -1;
} else if (version2 == null) {
return 1
return 1;
}
if (version1 === version2) {
return 0
return 0;
}
const v1s = version1.split('.')
const v2s = version2.split('.')
const v1s = version1.split(".");
const v2s = version2.split(".");
let diff: number = 0
const minLength: number = Math.min(v1s.length, v2s.length) // 取最小长度值
let diff = 0;
const minLength = Math.min(v1s.length, v2s.length); // 取最小长度值
let i: number
for (i = 0; i < minLength; i++) {
let v1 = v1s[i]
let v2 = v2s[i]
for (let i = 0; i < minLength; i++) {
let v1 = v1s[i];
let v2 = v2s[i];
// 先比较长度
diff = v1.length - v2.length
diff = v1.length - v2.length;
if (0 === diff) {
diff = v1.localeCompare(v2)
diff = v1.localeCompare(v2);
}
if (diff !== 0) {
//已有结果,结束
break
break;
}
}
// 如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大;
return diff !== 0 ? diff : v1s.length - v2s.length
return diff !== 0 ? diff : v1s.length - v2s.length;
}
// 当前页面构建信息
export function pageBuildInfo() {
const htmlVersion: any = document.head?.querySelector('[name~=jpom-version][content]')
const buildTime: any = document.head?.querySelector('[name~=build-time][content]')
const buildEnv: any = document.head?.querySelector('[name~=build-env][content]')
const htmlVersion = document.head.querySelector("[name~=jpom-version][content]").content;
const buildTime = document.head.querySelector("[name~=build-time][content]").content;
const buildEnv = document.head.querySelector("[name~=build-env][content]").content;
return {
v: htmlVersion?.content,
t: buildTime?.content,
e: buildEnv?.content,
df: (document.title || '').toLowerCase().includes('jpom'),
t2: Date.now()
}
v: htmlVersion,
t: buildTime,
e: buildEnv,
df: (document.title || "").toLowerCase().includes("jpom"),
t2: Date.now(),
};
}