v1.1.0
16
.editorconfig
Normal file
@@ -0,0 +1,16 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
9
.eslintignore
Normal file
@@ -0,0 +1,9 @@
|
||||
/lambda/
|
||||
/config/
|
||||
/scripts
|
||||
/config
|
||||
.history
|
||||
public
|
||||
dist
|
||||
.umi
|
||||
mock
|
||||
7
.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: [require.resolve('@umijs/lint/dist/config/eslint')],
|
||||
globals: {
|
||||
page: true,
|
||||
REACT_APP_ENV: true,
|
||||
},
|
||||
};
|
||||
41
.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
**/node_modules
|
||||
# roadhog-api-doc ignore
|
||||
/src/utils/request-temp.js
|
||||
_roadhog-api-doc
|
||||
|
||||
# production
|
||||
/dist
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
yarn-error.log
|
||||
|
||||
/coverage
|
||||
.idea
|
||||
yarn.lock
|
||||
# package-lock.json
|
||||
pnpm-lock.yaml
|
||||
*bak
|
||||
|
||||
|
||||
# visual studio code
|
||||
.vscode
|
||||
.history
|
||||
*.log
|
||||
functions/*
|
||||
.temp/**
|
||||
|
||||
# umi
|
||||
.umi
|
||||
.umi-production
|
||||
|
||||
# screenshot
|
||||
screenshot
|
||||
.firebase
|
||||
.eslintcache
|
||||
|
||||
build
|
||||
7
.husky/commit-msg
Normal file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
# Export Git hook params
|
||||
export GIT_PARAMS=$*
|
||||
|
||||
npx --no-install fabric verify-commit
|
||||
4
.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx --no-install lint-staged
|
||||
22
.prettierignore
Normal file
@@ -0,0 +1,22 @@
|
||||
**/*.svg
|
||||
.umi
|
||||
.umi-production
|
||||
/dist
|
||||
.dockerignore
|
||||
.DS_Store
|
||||
.eslintignore
|
||||
*.png
|
||||
*.toml
|
||||
docker
|
||||
.editorconfig
|
||||
Dockerfile*
|
||||
.gitignore
|
||||
.prettierignore
|
||||
LICENSE
|
||||
.eslintcache
|
||||
*.lock
|
||||
yarn-error.log
|
||||
.history
|
||||
CNAME
|
||||
/build
|
||||
/public
|
||||
21
.prettierrc.js
Normal file
@@ -0,0 +1,21 @@
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
printWidth: 100,
|
||||
proseWrap: 'never',
|
||||
endOfLine: 'lf',
|
||||
overrides: [
|
||||
{
|
||||
files: '.prettierrc',
|
||||
options: {
|
||||
parser: 'json',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: 'document.ejs',
|
||||
options: {
|
||||
parser: 'html',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
51
README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
## Environment Prepare
|
||||
|
||||
Install `node_modules`:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
||||
|
||||
## Change your config
|
||||
|
||||
> src->config
|
||||
>
|
||||
> 如果以 ip+端口形式配置 建议**不要修改端口号** 按照下方对应关系进行配置
|
||||
|
||||
```bash
|
||||
export const API_URL = 'http://43.154.157.177:10002';
|
||||
export const CHAT_URL = 'http://43.154.157.177:10008';
|
||||
export const ACCOUNT_URL = 'http://43.154.157.177:10009';
|
||||
```
|
||||
|
||||
### Start project
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
### Build project
|
||||
|
||||
> generate resource folder `dist`
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Check code style
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
You can also use script to auto fix some lint error:
|
||||
|
||||
```bash
|
||||
npm run lint:fix
|
||||
```
|
||||
157
config/config.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
// https://umijs.org/config/
|
||||
import { defineConfig } from '@umijs/max';
|
||||
import { join } from 'path';
|
||||
import defaultSettings from './defaultSettings';
|
||||
import proxy from './proxy';
|
||||
import routes from './routes';
|
||||
|
||||
const { REACT_APP_ENV = 'dev' } = process.env;
|
||||
|
||||
export default defineConfig({
|
||||
esbuildMinifyIIFE: true,
|
||||
/**
|
||||
* @name 开启 hash 模式
|
||||
* @description 让 build 之后的产物包含 hash 后缀。通常用于增量发布和避免浏览器加载缓存。
|
||||
* @doc https://umijs.org/docs/api/config#hash
|
||||
*/
|
||||
hash: true,
|
||||
|
||||
/**
|
||||
* @name 兼容性设置
|
||||
* @description 设置 ie11 不一定完美兼容,需要检查自己使用的所有依赖
|
||||
* @doc https://umijs.org/docs/api/config#targets
|
||||
*/
|
||||
// targets: {
|
||||
// ie: 11,
|
||||
// },
|
||||
/**
|
||||
* @name 路由的配置,不在路由中引入的文件不会编译
|
||||
* @description 只支持 path,component,routes,redirect,wrappers,title 的配置
|
||||
* @doc https://umijs.org/docs/guides/routes
|
||||
*/
|
||||
// umi routes: https://umijs.org/docs/routing
|
||||
routes,
|
||||
/**
|
||||
* @name 主题的配置
|
||||
* @description 虽然叫主题,但是其实只是 less 的变量设置
|
||||
* @doc antd的主题设置 https://ant.design/docs/react/customize-theme-cn
|
||||
* @doc umi 的theme 配置 https://umijs.org/docs/api/config#theme
|
||||
*/
|
||||
theme: {
|
||||
// 如果不想要 configProvide 动态设置主题需要把这个设置为 default
|
||||
// 只有设置为 variable, 才能使用 configProvide 动态设置主色调
|
||||
'root-entry-name': 'variable',
|
||||
},
|
||||
/**
|
||||
* @name moment 的国际化配置
|
||||
* @description 如果对国际化没有要求,打开之后能减少js的包大小
|
||||
* @doc https://umijs.org/docs/api/config#ignoremomentlocale
|
||||
*/
|
||||
ignoreMomentLocale: true,
|
||||
/**
|
||||
* @name 代理配置
|
||||
* @description 可以让你的本地服务器代理到你的服务器上,这样你就可以访问服务器的数据了
|
||||
* @see 要注意以下 代理只能在本地开发时使用,build 之后就无法使用了。
|
||||
* @doc 代理介绍 https://umijs.org/docs/guides/proxy
|
||||
* @doc 代理配置 https://umijs.org/docs/api/config#proxy
|
||||
*/
|
||||
proxy: proxy[REACT_APP_ENV as keyof typeof proxy],
|
||||
/**
|
||||
* @name 快速热更新配置
|
||||
* @description 一个不错的热更新组件,更新时可以保留 state
|
||||
*/
|
||||
fastRefresh: true,
|
||||
//============== 以下都是max的插件配置 ===============
|
||||
/**
|
||||
* @name 数据流插件
|
||||
* @@doc https://umijs.org/docs/max/data-flow
|
||||
*/
|
||||
model: {},
|
||||
/**
|
||||
* 一个全局的初始数据流,可以用它在插件之间共享数据
|
||||
* @description 可以用来存放一些全局的数据,比如用户信息,或者一些全局的状态,全局初始状态在整个 Umi 项目的最开始创建。
|
||||
* @doc https://umijs.org/docs/max/data-flow#%E5%85%A8%E5%B1%80%E5%88%9D%E5%A7%8B%E7%8A%B6%E6%80%81
|
||||
*/
|
||||
initialState: {},
|
||||
/**
|
||||
* @name layout 插件
|
||||
* @doc https://umijs.org/docs/max/layout-menu
|
||||
*/
|
||||
title: 'OpemIM Admin',
|
||||
layout: {
|
||||
locale: true,
|
||||
...defaultSettings,
|
||||
},
|
||||
/**
|
||||
* @name moment2dayjs 插件
|
||||
* @description 将项目中的 moment 替换为 dayjs
|
||||
* @doc https://umijs.org/docs/max/moment2dayjs
|
||||
*/
|
||||
moment2dayjs: {
|
||||
preset: 'antd',
|
||||
plugins: ['duration'],
|
||||
},
|
||||
/**
|
||||
* @name 国际化插件
|
||||
* @doc https://umijs.org/docs/max/i18n
|
||||
*/
|
||||
locale: {
|
||||
// default zh-CN
|
||||
default: 'zh-CN',
|
||||
antd: true,
|
||||
// default true, when it is true, will use `navigator.language` overwrite default
|
||||
baseNavigator: true,
|
||||
},
|
||||
/**
|
||||
* @name antd 插件
|
||||
* @description 内置了 babel import 插件
|
||||
* @doc https://umijs.org/docs/max/antd#antd
|
||||
*/
|
||||
antd: {},
|
||||
/**
|
||||
* @name 网络请求配置
|
||||
* @description 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
|
||||
* @doc https://umijs.org/docs/max/request
|
||||
*/
|
||||
request: {},
|
||||
/**
|
||||
* @name 权限插件
|
||||
* @description 基于 initialState 的权限插件,必须先打开 initialState
|
||||
* @doc https://umijs.org/docs/max/access
|
||||
*/
|
||||
access: {},
|
||||
/**
|
||||
* @name <head> 中额外的 script
|
||||
* @description 配置 <head> 中额外的 script
|
||||
*/
|
||||
headScripts: [
|
||||
// 解决首次加载时白屏的问题
|
||||
{ src: '/scripts/loading.js', async: true },
|
||||
'/wasm_exec.js',
|
||||
],
|
||||
//================ pro 插件配置 =================
|
||||
presets: ['umi-presets-pro'],
|
||||
/**
|
||||
* @name openAPI 插件的配置
|
||||
* @description 基于 openapi 的规范生成serve 和mock,能减少很多样板代码
|
||||
* @doc https://pro.ant.design/zh-cn/docs/openapi/
|
||||
*/
|
||||
openAPI: [
|
||||
{
|
||||
requestLibPath: "import { request } from '@umijs/max'",
|
||||
// 或者使用在线的版本
|
||||
// schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
|
||||
schemaPath: join(__dirname, 'oneapi.json'),
|
||||
mock: false,
|
||||
},
|
||||
{
|
||||
requestLibPath: "import { request } from '@umijs/max'",
|
||||
schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
|
||||
projectName: 'swagger',
|
||||
},
|
||||
],
|
||||
mfsu: {
|
||||
strategy: 'normal',
|
||||
},
|
||||
requestRecord: {},
|
||||
});
|
||||
28
config/defaultSettings.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ProLayoutProps } from '@ant-design/pro-components';
|
||||
|
||||
/**
|
||||
* @name
|
||||
*/
|
||||
const Settings: ProLayoutProps & {
|
||||
pwa?: boolean;
|
||||
logo?: string;
|
||||
} = {
|
||||
navTheme: 'light',
|
||||
// 拂晓蓝
|
||||
colorPrimary: '#1890ff',
|
||||
layout: 'mix',
|
||||
contentWidth: 'Fluid',
|
||||
fixedHeader: false,
|
||||
fixSiderbar: true,
|
||||
colorWeak: false,
|
||||
title: 'OpemIM Admin',
|
||||
pwa: true,
|
||||
logo: '/icons/icon.png',
|
||||
iconfontUrl: '',
|
||||
token: {
|
||||
// 参见ts声明,demo 见文档,通过token 修改样式
|
||||
//https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
|
||||
},
|
||||
};
|
||||
|
||||
export default Settings;
|
||||
593
config/oneapi.json
Normal file
@@ -0,0 +1,593 @@
|
||||
{
|
||||
"openapi": "3.0.1",
|
||||
"info": {
|
||||
"title": "Ant Design Pro",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:8000/"
|
||||
},
|
||||
{
|
||||
"url": "https://localhost:8000/"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/api/currentUser": {
|
||||
"get": {
|
||||
"tags": ["api"],
|
||||
"description": "获取当前的用户",
|
||||
"operationId": "currentUser",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/CurrentUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/api/login/captcha": {
|
||||
"post": {
|
||||
"description": "发送验证码",
|
||||
"operationId": "getFakeCaptcha",
|
||||
"tags": ["login"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "phone",
|
||||
"in": "query",
|
||||
"description": "手机号",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FakeCaptcha"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/login/outLogin": {
|
||||
"post": {
|
||||
"description": "登录接口",
|
||||
"operationId": "outLogin",
|
||||
"tags": ["login"],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/api/login/account": {
|
||||
"post": {
|
||||
"tags": ["login"],
|
||||
"description": "登录接口",
|
||||
"operationId": "login",
|
||||
"requestBody": {
|
||||
"description": "登录系统",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-codegen-request-body-name": "body"
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/api/notices": {
|
||||
"summary": "getNotices",
|
||||
"description": "NoticeIconItem",
|
||||
"get": {
|
||||
"tags": ["api"],
|
||||
"operationId": "getNotices",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NoticeIconList"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/rule": {
|
||||
"get": {
|
||||
"tags": ["rule"],
|
||||
"description": "获取规则列表",
|
||||
"operationId": "rule",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "current",
|
||||
"in": "query",
|
||||
"description": "当前的页码",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "pageSize",
|
||||
"in": "query",
|
||||
"description": "页面的容量",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RuleList"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": ["rule"],
|
||||
"description": "新建规则",
|
||||
"operationId": "addRule",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RuleListItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": ["rule"],
|
||||
"description": "新建规则",
|
||||
"operationId": "updateRule",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RuleListItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": ["rule"],
|
||||
"description": "删除规则",
|
||||
"operationId": "removeRule",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Success",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"x-swagger-router-controller": "api"
|
||||
},
|
||||
"/swagger": {
|
||||
"x-swagger-pipe": "swagger_raw"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"CurrentUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"userid": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"signature": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifyCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"unreadCount": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"country": {
|
||||
"type": "string"
|
||||
},
|
||||
"access": {
|
||||
"type": "string"
|
||||
},
|
||||
"geographic": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"province": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"city": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"address": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"currentAuthority": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PageParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"current": {
|
||||
"type": "number"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RuleListItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"href": {
|
||||
"type": "string"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"owner": {
|
||||
"type": "string"
|
||||
},
|
||||
"desc": {
|
||||
"type": "string"
|
||||
},
|
||||
"callNo": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "datetime"
|
||||
},
|
||||
"progress": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
"RuleList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RuleListItem"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"description": "列表的内容总数",
|
||||
"format": "int32"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FakeCaptcha": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginParams": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoLogin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ErrorResponse": {
|
||||
"required": ["errorCode"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"errorCode": {
|
||||
"type": "string",
|
||||
"description": "业务约定的错误码"
|
||||
},
|
||||
"errorMessage": {
|
||||
"type": "string",
|
||||
"description": "业务上的错误信息"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean",
|
||||
"description": "业务上的请求是否成功"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NoticeIconList": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/NoticeIconItem"
|
||||
}
|
||||
},
|
||||
"total": {
|
||||
"type": "integer",
|
||||
"description": "列表的内容总数",
|
||||
"format": "int32"
|
||||
},
|
||||
"success": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"NoticeIconItemType": {
|
||||
"title": "NoticeIconItemType",
|
||||
"description": "已读未读列表的枚举",
|
||||
"type": "string",
|
||||
"properties": {},
|
||||
"enum": ["notification", "message", "event"]
|
||||
},
|
||||
"NoticeIconItem": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"extra": {
|
||||
"type": "string",
|
||||
"format": "any"
|
||||
},
|
||||
"key": { "type": "string" },
|
||||
"read": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"avatar": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"datetime": {
|
||||
"type": "string",
|
||||
"format": "date"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"extensions": {
|
||||
"x-is-enum": true
|
||||
},
|
||||
"$ref": "#/components/schemas/NoticeIconItemType"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
44
config/proxy.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @name 代理的配置
|
||||
* @see 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
|
||||
* -------------------------------
|
||||
* The agent cannot take effect in the production environment
|
||||
* so there is no configuration of the production environment
|
||||
* For details, please see
|
||||
* https://pro.ant.design/docs/deploy
|
||||
*
|
||||
* @doc https://umijs.org/docs/guides/proxy
|
||||
*/
|
||||
export default {
|
||||
// 如果需要自定义本地开发服务器 请取消注释按需调整
|
||||
// dev: {
|
||||
// // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||
// '/api/': {
|
||||
// // 要代理的地址
|
||||
// target: 'https://preview.pro.ant.design',
|
||||
// // 配置了这个可以从 http 代理到 https
|
||||
// // 依赖 origin 的功能可能需要这个,比如 cookie
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// },
|
||||
|
||||
/**
|
||||
* @name 详细的代理配置
|
||||
* @doc https://github.com/chimurai/http-proxy-middleware
|
||||
*/
|
||||
test: {
|
||||
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||
'/api/': {
|
||||
target: 'https://proapi.azurewebsites.net',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^': '' },
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
'/api/': {
|
||||
target: 'your pre url',
|
||||
changeOrigin: true,
|
||||
pathRewrite: { '^': '' },
|
||||
},
|
||||
},
|
||||
};
|
||||
250
config/routes.ts
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* @name umi 的路由配置
|
||||
* @description 只支持 path,component,routes,redirect,wrappers,name,icon 的配置
|
||||
* @param path path 只支持两种占位符配置,第一种是动态参数 :id 的形式,第二种是 * 通配符,通配符只能出现路由字符串的最后。
|
||||
* @param component 配置 location 和 path 匹配后用于渲染的 React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。
|
||||
* @param routes 配置子路由,通常在需要为多个路径增加 layout 组件时使用。
|
||||
* @param redirect 配置路由跳转
|
||||
* @param wrappers 配置路由组件的包装组件,通过包装组件可以为当前的路由组件组合进更多的功能。 比如,可以用于路由级别的权限校验
|
||||
* @param name 配置路由的标题,默认读取国际化文件 menu.ts 中 menu.xxxx 的值,如配置 name 为 login,则读取 menu.ts 中 menu.login 的取值作为标题
|
||||
* @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 <StepBackwardOutlined /> 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 <UserOutlined /> 则取值应为 user 或者 User
|
||||
* @doc https://umijs.org/docs/guides/routes
|
||||
*/
|
||||
export default [
|
||||
{
|
||||
layout: false,
|
||||
routes: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '/login',
|
||||
component: './login/Login',
|
||||
},
|
||||
{
|
||||
component: './404',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business_system',
|
||||
name: 'BusinessSystem',
|
||||
icon: 'MedicineBoxOutlined',
|
||||
routes: [
|
||||
// {
|
||||
// path: '/business_system/statistics',
|
||||
// name: 'Statistics',
|
||||
// routes: [
|
||||
// {
|
||||
// path: '/business_system/statistics',
|
||||
// redirect: '/business_system/statistics/user',
|
||||
// },
|
||||
// {
|
||||
// name: 'UserStatistics',
|
||||
// path: '/business_system/statistics/user',
|
||||
// component: './business_system/statistics/User',
|
||||
// },
|
||||
// {
|
||||
// name: 'MomentsStatistics',
|
||||
// path: '/business_system/statistics/moments',
|
||||
// component: './business_system/statistics/Moments',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/business_system/user_manage',
|
||||
name: 'UserManage',
|
||||
routes: [
|
||||
{
|
||||
path: '/business_system/user_manage',
|
||||
redirect: '/business_system/user_manage/user_list',
|
||||
},
|
||||
{
|
||||
name: 'UserList',
|
||||
path: '/business_system/user_manage/user_list',
|
||||
component: './business_system/user_manage/UserList',
|
||||
},
|
||||
{
|
||||
name: 'BlockList',
|
||||
path: '/business_system/user_manage/block_list',
|
||||
component: './business_system/user_manage/BlockList',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business_system/register_manage',
|
||||
name: 'RegisterManage',
|
||||
routes: [
|
||||
{
|
||||
path: '/business_system/register_manage',
|
||||
redirect: '/business_system/register_manage/invition_code',
|
||||
},
|
||||
{
|
||||
name: 'InvitionCode',
|
||||
path: '/business_system/register_manage/invition_code',
|
||||
component: './business_system/register_manage/InvitionCode',
|
||||
},
|
||||
{
|
||||
name: 'DefualtFriends',
|
||||
path: '/business_system/register_manage/defualt_friends',
|
||||
component: './business_system/register_manage/DefualtFriends',
|
||||
},
|
||||
{
|
||||
name: 'DefualtGroup',
|
||||
path: '/business_system/register_manage/defualt_group',
|
||||
component: './business_system/register_manage/DefualtGroup',
|
||||
},
|
||||
],
|
||||
},
|
||||
// 应用管理 工作台=小程序
|
||||
{
|
||||
path: '/business_system/app_manage',
|
||||
name: 'AppManage',
|
||||
routes: [
|
||||
{
|
||||
path: '/business_system/app_manage',
|
||||
redirect: '/business_system/app_manage/discover_manage',
|
||||
},
|
||||
{
|
||||
name: 'DiscoveryPageManage',
|
||||
path: '/business_system/app_manage/discover_manage',
|
||||
component: './business_system/app_manage/DiscoveryPageManage',
|
||||
},
|
||||
// {
|
||||
// name: 'Workbench',
|
||||
// path: '/business_system/app_manage/workbench',
|
||||
// component: './business_system/app_manage/Workbench',
|
||||
// },
|
||||
// {
|
||||
// name: 'InvitationPage',
|
||||
// path: '/business_system/app_manage/invitation_page',
|
||||
// component: './business_system/app_manage/InvitationPage',
|
||||
// },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/im_system',
|
||||
name: 'IMSyetem',
|
||||
icon: 'MessageOutlined',
|
||||
routes: [
|
||||
{
|
||||
path: '/im_system/user_manage',
|
||||
name: 'UserManage',
|
||||
routes: [
|
||||
{
|
||||
path: '/im_system/user_manage',
|
||||
redirect: '/im_system/user_manage/user_list',
|
||||
},
|
||||
{
|
||||
name: 'UserList',
|
||||
path: '/im_system/user_manage/user_list',
|
||||
component: './im_system/UserList',
|
||||
},
|
||||
{
|
||||
name: 'RelationList',
|
||||
path: '/im_system/user_manage/relation_list',
|
||||
component: './im_system/RelationList',
|
||||
hideInMenu: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/im_system/group_manage',
|
||||
name: 'GroupManage',
|
||||
routes: [
|
||||
{
|
||||
path: '/im_system/group_manage',
|
||||
redirect: '/im_system/group_manage/group_list',
|
||||
},
|
||||
{
|
||||
name: 'GroupList',
|
||||
path: '/im_system/group_manage/group_list',
|
||||
component: './im_system/group_manage/GroupList',
|
||||
},
|
||||
{
|
||||
name: 'GroupMember',
|
||||
path: '/im_system/group_manage/group_member',
|
||||
component: './im_system/group_manage/GroupMember',
|
||||
hideInMenu: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/im_system/message_manage',
|
||||
name: 'MessageManage',
|
||||
routes: [
|
||||
{
|
||||
path: '/im_system/message_manage',
|
||||
redirect: '/im_system/message_manage/user_message',
|
||||
},
|
||||
{
|
||||
name: 'UserMessage',
|
||||
path: '/im_system/message_manage/user_message',
|
||||
component: './im_system/message_manage/UserMessage',
|
||||
},
|
||||
{
|
||||
name: 'GroupMessage',
|
||||
path: '/im_system/message_manage/group_message',
|
||||
component: './im_system/message_manage/GroupMessage',
|
||||
},
|
||||
{
|
||||
name: 'SendMessage',
|
||||
path: '/im_system/message_manage/send_message',
|
||||
component: './im_system/message_manage/SendMessage',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/rtc_system',
|
||||
name: 'RtcSystem',
|
||||
icon: 'VideoCameraOutlined',
|
||||
routes: [
|
||||
{
|
||||
path: '/rtc_system',
|
||||
redirect: '/rtc_system/video_call',
|
||||
},
|
||||
{
|
||||
name: 'Meeting',
|
||||
path: '/rtc_system/meeting',
|
||||
component: './rtc_system/Meeting',
|
||||
},
|
||||
{
|
||||
name: 'VideoCall',
|
||||
path: '/rtc_system/video_call',
|
||||
component: './rtc_system/VideoCall',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
icon: 'setting',
|
||||
routes: [
|
||||
{
|
||||
path: '/profile',
|
||||
redirect: '/profile/admin_info',
|
||||
},
|
||||
{
|
||||
name: 'AdminInfo',
|
||||
path: '/profile/admin_info',
|
||||
component: './profile',
|
||||
},
|
||||
{
|
||||
name: 'AdminPassword',
|
||||
path: '/profile/admin_password',
|
||||
component: './profile/Password',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/',
|
||||
redirect: '/business_system/user_manage/user_list',
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
layout: false,
|
||||
component: './404',
|
||||
},
|
||||
];
|
||||
23
jest.config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { configUmiAlias, createConfig } from '@umijs/max/test';
|
||||
|
||||
export default async () => {
|
||||
const config = await configUmiAlias({
|
||||
...createConfig({
|
||||
target: 'browser',
|
||||
}),
|
||||
});
|
||||
|
||||
console.log();
|
||||
return {
|
||||
...config,
|
||||
testEnvironmentOptions: {
|
||||
...(config?.testEnvironmentOptions || {}),
|
||||
url: 'http://localhost:8000',
|
||||
},
|
||||
setupFiles: [...(config.setupFiles || []), './tests/setupTests.jsx'],
|
||||
globals: {
|
||||
...config.globals,
|
||||
localStorage: null,
|
||||
},
|
||||
};
|
||||
};
|
||||
11
jsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
176
mock/listTableList.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { Request, Response } from 'express';
|
||||
import moment from 'moment';
|
||||
import { parse } from 'url';
|
||||
|
||||
// mock tableListDataSource
|
||||
const genList = (current: number, pageSize: number) => {
|
||||
const tableListDataSource: API.RuleListItem[] = [];
|
||||
|
||||
for (let i = 0; i < pageSize; i += 1) {
|
||||
const index = (current - 1) * 10 + i;
|
||||
tableListDataSource.push({
|
||||
key: index,
|
||||
disabled: i % 6 === 0,
|
||||
href: 'https://ant.design',
|
||||
avatar: [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
][i % 2],
|
||||
name: `TradeCode ${index}`,
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 4,
|
||||
updatedAt: moment().format('YYYY-MM-DD'),
|
||||
createdAt: moment().format('YYYY-MM-DD'),
|
||||
progress: Math.ceil(Math.random() * 100),
|
||||
});
|
||||
}
|
||||
tableListDataSource.reverse();
|
||||
return tableListDataSource;
|
||||
};
|
||||
|
||||
let tableListDataSource = genList(1, 100);
|
||||
|
||||
function getRule(req: Request, res: Response, u: string) {
|
||||
let realUrl = u;
|
||||
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
|
||||
realUrl = req.url;
|
||||
}
|
||||
const { current = 1, pageSize = 10 } = req.query;
|
||||
const params = parse(realUrl, true).query as unknown as API.PageParams &
|
||||
API.RuleListItem & {
|
||||
sorter: any;
|
||||
filter: any;
|
||||
};
|
||||
|
||||
let dataSource = [...tableListDataSource].slice(
|
||||
((current as number) - 1) * (pageSize as number),
|
||||
(current as number) * (pageSize as number),
|
||||
);
|
||||
if (params.sorter) {
|
||||
const sorter = JSON.parse(params.sorter);
|
||||
dataSource = dataSource.sort((prev, next) => {
|
||||
let sortNumber = 0;
|
||||
(Object.keys(sorter) as Array<keyof API.RuleListItem>).forEach((key) => {
|
||||
let nextSort = next?.[key] as number;
|
||||
let preSort = prev?.[key] as number;
|
||||
if (sorter[key] === 'descend') {
|
||||
if (preSort - nextSort > 0) {
|
||||
sortNumber += -1;
|
||||
} else {
|
||||
sortNumber += 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (preSort - nextSort > 0) {
|
||||
sortNumber += 1;
|
||||
} else {
|
||||
sortNumber += -1;
|
||||
}
|
||||
});
|
||||
return sortNumber;
|
||||
});
|
||||
}
|
||||
if (params.filter) {
|
||||
const filter = JSON.parse(params.filter as any) as {
|
||||
[key: string]: string[];
|
||||
};
|
||||
if (Object.keys(filter).length > 0) {
|
||||
dataSource = dataSource.filter((item) => {
|
||||
return (Object.keys(filter) as Array<keyof API.RuleListItem>).some((key) => {
|
||||
if (!filter[key]) {
|
||||
return true;
|
||||
}
|
||||
if (filter[key].includes(`${item[key]}`)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (params.name) {
|
||||
dataSource = dataSource.filter((data) => data?.name?.includes(params.name || ''));
|
||||
}
|
||||
const result = {
|
||||
data: dataSource,
|
||||
total: tableListDataSource.length,
|
||||
success: true,
|
||||
pageSize,
|
||||
current: parseInt(`${params.current}`, 10) || 1,
|
||||
};
|
||||
|
||||
return res.json(result);
|
||||
}
|
||||
|
||||
function postRule(req: Request, res: Response, u: string, b: Request) {
|
||||
let realUrl = u;
|
||||
if (!realUrl || Object.prototype.toString.call(realUrl) !== '[object String]') {
|
||||
realUrl = req.url;
|
||||
}
|
||||
|
||||
const body = (b && b.body) || req.body;
|
||||
const { method, name, desc, key } = body;
|
||||
|
||||
switch (method) {
|
||||
/* eslint no-case-declarations:0 */
|
||||
case 'delete':
|
||||
tableListDataSource = tableListDataSource.filter((item) => key.indexOf(item.key) === -1);
|
||||
break;
|
||||
case 'post':
|
||||
(() => {
|
||||
const i = Math.ceil(Math.random() * 10000);
|
||||
const newRule: API.RuleListItem = {
|
||||
key: tableListDataSource.length,
|
||||
href: 'https://ant.design',
|
||||
avatar: [
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
][i % 2],
|
||||
name,
|
||||
owner: '曲丽丽',
|
||||
desc,
|
||||
callNo: Math.floor(Math.random() * 1000),
|
||||
status: Math.floor(Math.random() * 10) % 2,
|
||||
updatedAt: moment().format('YYYY-MM-DD'),
|
||||
createdAt: moment().format('YYYY-MM-DD'),
|
||||
progress: Math.ceil(Math.random() * 100),
|
||||
};
|
||||
tableListDataSource.unshift(newRule);
|
||||
return res.json(newRule);
|
||||
})();
|
||||
return;
|
||||
|
||||
case 'update':
|
||||
(() => {
|
||||
let newRule = {};
|
||||
tableListDataSource = tableListDataSource.map((item) => {
|
||||
if (item.key === key) {
|
||||
newRule = { ...item, desc, name };
|
||||
return { ...item, desc, name };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
return res.json(newRule);
|
||||
})();
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const result = {
|
||||
list: tableListDataSource,
|
||||
pagination: {
|
||||
total: tableListDataSource.length,
|
||||
},
|
||||
};
|
||||
|
||||
res.json(result);
|
||||
}
|
||||
|
||||
export default {
|
||||
'GET /api/rule': getRule,
|
||||
'POST /api/rule': postRule,
|
||||
};
|
||||
115
mock/notices.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
const getNotices = (req: Request, res: Response) => {
|
||||
res.json({
|
||||
data: [
|
||||
{
|
||||
id: '000000001',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/MSbDR4FR2MUAAAAAAAAAAAAAFl94AQBr',
|
||||
title: '你收到了 14 份新周报',
|
||||
datetime: '2017-08-09',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000002',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/hX-PTavYIq4AAAAAAAAAAAAAFl94AQBr',
|
||||
title: '你推荐的 曲妮妮 已通过第三轮面试',
|
||||
datetime: '2017-08-08',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000003',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/jHX5R5l3QjQAAAAAAAAAAAAAFl94AQBr',
|
||||
title: '这种模板可以区分多种通知类型',
|
||||
datetime: '2017-08-07',
|
||||
read: true,
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000004',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Wr4mQqx6jfwAAAAAAAAAAAAAFl94AQBr',
|
||||
title: '左侧图标用于区分不同的类型',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000005',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/Mzj_TbcWUj4AAAAAAAAAAAAAFl94AQBr',
|
||||
title: '内容不要超过两行字,超出时自动截断',
|
||||
datetime: '2017-08-07',
|
||||
type: 'notification',
|
||||
},
|
||||
{
|
||||
id: '000000006',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/eXLzRbPqQE4AAAAAAAAAAAAAFl94AQBr',
|
||||
title: '曲丽丽 评论了你',
|
||||
description: '描述信息描述信息描述信息',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000007',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/w5mRQY2AmEEAAAAAAAAAAAAAFl94AQBr',
|
||||
title: '朱偏右 回复了你',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000008',
|
||||
avatar:
|
||||
'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/wPadR5M9918AAAAAAAAAAAAAFl94AQBr',
|
||||
title: '标题',
|
||||
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
|
||||
datetime: '2017-08-07',
|
||||
type: 'message',
|
||||
clickClose: true,
|
||||
},
|
||||
{
|
||||
id: '000000009',
|
||||
title: '任务名称',
|
||||
description: '任务需要在 2017-01-12 20:00 前启动',
|
||||
extra: '未开始',
|
||||
status: 'todo',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000010',
|
||||
title: '第三方紧急代码变更',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '马上到期',
|
||||
status: 'urgent',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000011',
|
||||
title: '信息安全考试',
|
||||
description: '指派竹尔于 2017-01-09 前完成更新并发布',
|
||||
extra: '已耗时 8 天',
|
||||
status: 'doing',
|
||||
type: 'event',
|
||||
},
|
||||
{
|
||||
id: '000000012',
|
||||
title: 'ABCD 版本发布',
|
||||
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
|
||||
extra: '进行中',
|
||||
status: 'processing',
|
||||
type: 'event',
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
'GET /api/notices': getNotices,
|
||||
};
|
||||
324
mock/requestRecord.mock.js
Normal file
@@ -0,0 +1,324 @@
|
||||
module.exports = {
|
||||
'GET /api/currentUser': {
|
||||
data: {
|
||||
name: 'Serati Ma',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
|
||||
userid: '00000001',
|
||||
email: 'antdesign@alipay.com',
|
||||
signature: '海纳百川,有容乃大',
|
||||
title: '交互专家',
|
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
|
||||
tags: [
|
||||
{ key: '0', label: '很有想法的' },
|
||||
{ key: '1', label: '专注设计' },
|
||||
{ key: '2', label: '辣~' },
|
||||
{ key: '3', label: '大长腿' },
|
||||
{ key: '4', label: '川妹子' },
|
||||
{ key: '5', label: '海纳百川' },
|
||||
],
|
||||
notifyCount: 12,
|
||||
unreadCount: 11,
|
||||
country: 'China',
|
||||
geographic: {
|
||||
province: { label: '浙江省', key: '330000' },
|
||||
city: { label: '杭州市', key: '330100' },
|
||||
},
|
||||
address: '西湖区工专路 77 号',
|
||||
phone: '0752-268888888',
|
||||
},
|
||||
},
|
||||
'GET /api/rule': {
|
||||
data: [
|
||||
{
|
||||
key: 99,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 99',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 503,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 81,
|
||||
},
|
||||
{
|
||||
key: 98,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 98',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 164,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 12,
|
||||
},
|
||||
{
|
||||
key: 97,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 97',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 174,
|
||||
status: '1',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 81,
|
||||
},
|
||||
{
|
||||
key: 96,
|
||||
disabled: true,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 96',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 914,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 7,
|
||||
},
|
||||
{
|
||||
key: 95,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 95',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 698,
|
||||
status: '2',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 82,
|
||||
},
|
||||
{
|
||||
key: 94,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 94',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 488,
|
||||
status: '1',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 14,
|
||||
},
|
||||
{
|
||||
key: 93,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 93',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 580,
|
||||
status: '2',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 77,
|
||||
},
|
||||
{
|
||||
key: 92,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 92',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 244,
|
||||
status: '3',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 58,
|
||||
},
|
||||
{
|
||||
key: 91,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 91',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 959,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 66,
|
||||
},
|
||||
{
|
||||
key: 90,
|
||||
disabled: true,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 90',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 958,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 72,
|
||||
},
|
||||
{
|
||||
key: 89,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 89',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 301,
|
||||
status: '2',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 2,
|
||||
},
|
||||
{
|
||||
key: 88,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 88',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 277,
|
||||
status: '1',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 12,
|
||||
},
|
||||
{
|
||||
key: 87,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 87',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 810,
|
||||
status: '1',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 82,
|
||||
},
|
||||
{
|
||||
key: 86,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 86',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 780,
|
||||
status: '3',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 22,
|
||||
},
|
||||
{
|
||||
key: 85,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 85',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 705,
|
||||
status: '3',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 12,
|
||||
},
|
||||
{
|
||||
key: 84,
|
||||
disabled: true,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 84',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 203,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 79,
|
||||
},
|
||||
{
|
||||
key: 83,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 83',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 491,
|
||||
status: '2',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 59,
|
||||
},
|
||||
{
|
||||
key: 82,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 82',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 73,
|
||||
status: '0',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 100,
|
||||
},
|
||||
{
|
||||
key: 81,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/udxAbMEhpwthVVcjLXik.png',
|
||||
name: 'TradeCode 81',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 406,
|
||||
status: '3',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 61,
|
||||
},
|
||||
{
|
||||
key: 80,
|
||||
disabled: false,
|
||||
href: 'https://ant.design',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/eeHMaZBwmTvLdIwMfBpg.png',
|
||||
name: 'TradeCode 80',
|
||||
owner: '曲丽丽',
|
||||
desc: '这是一段描述',
|
||||
callNo: 112,
|
||||
status: '2',
|
||||
updatedAt: '2022-12-06T05:00:57.040Z',
|
||||
createdAt: '2022-12-06T05:00:57.040Z',
|
||||
progress: 20,
|
||||
},
|
||||
],
|
||||
total: 100,
|
||||
success: true,
|
||||
pageSize: 20,
|
||||
current: 1,
|
||||
},
|
||||
'POST /api/login/outLogin': { data: {}, success: true },
|
||||
'POST /api/login/account': {
|
||||
status: 'ok',
|
||||
type: 'account',
|
||||
currentAuthority: 'admin',
|
||||
},
|
||||
};
|
||||
5
mock/route.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'/api/auth_routes': {
|
||||
'/form/advanced-form': { authority: ['admin', 'user'] },
|
||||
},
|
||||
};
|
||||
203
mock/user.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
const waitTime = (time: number = 100) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
|
||||
async function getFakeCaptcha(req: Request, res: Response) {
|
||||
await waitTime(2000);
|
||||
return res.json('captcha-xxx');
|
||||
}
|
||||
|
||||
const { ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION } = process.env;
|
||||
|
||||
/**
|
||||
* 当前用户的权限,如果为空代表没登录
|
||||
* current user access, if is '', user need login
|
||||
* 如果是 pro 的预览,默认是有权限的
|
||||
*/
|
||||
let access = ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION === 'site' ? 'admin' : '';
|
||||
|
||||
const getAccess = () => {
|
||||
return access;
|
||||
};
|
||||
|
||||
// 代码中会兼容本地 service mock 以及部署站点的静态数据
|
||||
export default {
|
||||
// 支持值为 Object 和 Array
|
||||
'GET /api/currentUser': (req: Request, res: Response) => {
|
||||
if (!getAccess()) {
|
||||
res.status(401).send({
|
||||
data: {
|
||||
isLogin: false,
|
||||
},
|
||||
errorCode: '401',
|
||||
errorMessage: '请先登录!',
|
||||
success: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.send({
|
||||
success: true,
|
||||
data: {
|
||||
name: 'Serati Ma',
|
||||
avatar: 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png',
|
||||
userid: '00000001',
|
||||
email: 'antdesign@alipay.com',
|
||||
signature: '海纳百川,有容乃大',
|
||||
title: '交互专家',
|
||||
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
|
||||
tags: [
|
||||
{
|
||||
key: '0',
|
||||
label: '很有想法的',
|
||||
},
|
||||
{
|
||||
key: '1',
|
||||
label: '专注设计',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: '辣~',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: '大长腿',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: '川妹子',
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
label: '海纳百川',
|
||||
},
|
||||
],
|
||||
notifyCount: 12,
|
||||
unreadCount: 11,
|
||||
country: 'China',
|
||||
access: getAccess(),
|
||||
geographic: {
|
||||
province: {
|
||||
label: '浙江省',
|
||||
key: '330000',
|
||||
},
|
||||
city: {
|
||||
label: '杭州市',
|
||||
key: '330100',
|
||||
},
|
||||
},
|
||||
address: '西湖区工专路 77 号',
|
||||
phone: '0752-268888888',
|
||||
},
|
||||
});
|
||||
},
|
||||
// GET POST 可省略
|
||||
'GET /api/users': [
|
||||
{
|
||||
key: '1',
|
||||
name: 'John Brown',
|
||||
age: 32,
|
||||
address: 'New York No. 1 Lake Park',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
name: 'Jim Green',
|
||||
age: 42,
|
||||
address: 'London No. 1 Lake Park',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
name: 'Joe Black',
|
||||
age: 32,
|
||||
address: 'Sidney No. 1 Lake Park',
|
||||
},
|
||||
],
|
||||
'POST /api/login/account': async (req: Request, res: Response) => {
|
||||
const { password, username, type } = req.body;
|
||||
await waitTime(2000);
|
||||
if (password === 'ant.design' && username === 'admin') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
access = 'admin';
|
||||
return;
|
||||
}
|
||||
if (password === 'ant.design' && username === 'user') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'user',
|
||||
});
|
||||
access = 'user';
|
||||
return;
|
||||
}
|
||||
if (type === 'mobile') {
|
||||
res.send({
|
||||
status: 'ok',
|
||||
type,
|
||||
currentAuthority: 'admin',
|
||||
});
|
||||
access = 'admin';
|
||||
return;
|
||||
}
|
||||
|
||||
res.send({
|
||||
status: 'error',
|
||||
type,
|
||||
currentAuthority: 'guest',
|
||||
});
|
||||
access = 'guest';
|
||||
},
|
||||
'POST /api/login/outLogin': (req: Request, res: Response) => {
|
||||
access = '';
|
||||
res.send({ data: {}, success: true });
|
||||
},
|
||||
'POST /api/register': (req: Request, res: Response) => {
|
||||
res.send({ status: 'ok', currentAuthority: 'user', success: true });
|
||||
},
|
||||
'GET /api/500': (req: Request, res: Response) => {
|
||||
res.status(500).send({
|
||||
timestamp: 1513932555104,
|
||||
status: 500,
|
||||
error: 'error',
|
||||
message: 'error',
|
||||
path: '/base/category/list',
|
||||
});
|
||||
},
|
||||
'GET /api/404': (req: Request, res: Response) => {
|
||||
res.status(404).send({
|
||||
timestamp: 1513932643431,
|
||||
status: 404,
|
||||
error: 'Not Found',
|
||||
message: 'No message available',
|
||||
path: '/base/category/list/2121212',
|
||||
});
|
||||
},
|
||||
'GET /api/403': (req: Request, res: Response) => {
|
||||
res.status(403).send({
|
||||
timestamp: 1513932555104,
|
||||
status: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Forbidden',
|
||||
path: '/base/category/list',
|
||||
});
|
||||
},
|
||||
'GET /api/401': (req: Request, res: Response) => {
|
||||
res.status(401).send({
|
||||
timestamp: 1513932555104,
|
||||
status: 401,
|
||||
error: 'Unauthorized',
|
||||
message: 'Unauthorized',
|
||||
path: '/base/category/list',
|
||||
});
|
||||
},
|
||||
|
||||
'GET /api/login/captcha': getFakeCaptcha,
|
||||
};
|
||||
26323
package-lock.json
generated
Normal file
116
package.json
Normal file
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"name": "ant-design-pro",
|
||||
"version": "6.0.0",
|
||||
"private": true,
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"scripts": {
|
||||
"analyze": "cross-env ANALYZE=1 max build",
|
||||
"build": "max build",
|
||||
"deploy": "npm run build && npm run gh-pages",
|
||||
"dev": "npm run start:dev",
|
||||
"gh-pages": "gh-pages -d dist",
|
||||
"i18n-remove": "pro i18n-remove --locale=zh-CN --write",
|
||||
"postinstall": "max setup",
|
||||
"jest": "jest",
|
||||
"lint": "npm run lint:js && npm run lint:prettier && npm run tsc",
|
||||
"lint-staged": "lint-staged",
|
||||
"lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
|
||||
"lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src ",
|
||||
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
|
||||
"lint:prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\" --end-of-line auto",
|
||||
"openapi": "max openapi",
|
||||
"prepare": "husky install",
|
||||
"prettier": "prettier -c --write \"**/**.{js,jsx,tsx,ts,less,md,json}\"",
|
||||
"preview": "npm run build && max preview --port 8000",
|
||||
"record": "cross-env NODE_ENV=development REACT_APP_ENV=test max record --scene=login",
|
||||
"serve": "umi-serve",
|
||||
"start": "cross-env UMI_ENV=dev max dev",
|
||||
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev max dev",
|
||||
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev max dev",
|
||||
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev max dev",
|
||||
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev max dev",
|
||||
"test": "jest",
|
||||
"test:coverage": "npm run jest -- --coverage",
|
||||
"test:update": "npm run jest -- -u",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
|
||||
"**/*.{js,jsx,tsx,ts,less,md,json}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 10"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^4.8.0",
|
||||
"@ant-design/pro-components": "^2.3.57",
|
||||
"@ant-design/use-emotion-css": "1.0.4",
|
||||
"@umijs/route-utils": "^2.2.2",
|
||||
"antd": "^5.2.2",
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^2.0.0",
|
||||
"cos-js-sdk-v5": "^1.4.18",
|
||||
"date-fns": "^2.30.0",
|
||||
"dayjs": "^1.11.10",
|
||||
"file2md5": "^1.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
"moment": "^2.29.4",
|
||||
"omit.js": "^2.0.2",
|
||||
"open-im-sdk-wasm": "^3.3.0",
|
||||
"rc-menu": "^9.8.2",
|
||||
"rc-upload": "^4.3.4",
|
||||
"rc-util": "^5.27.2",
|
||||
"rc-virtual-list": "^3.5.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dev-inspector": "^1.8.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-player": "^2.12.0",
|
||||
"react-virtuoso": "^4.6.0",
|
||||
"twemoji": "^14.0.2",
|
||||
"uuid": "^9.0.0",
|
||||
"xgplayer": "^3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ant-design/pro-cli": "^2.1.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/history": "^4.7.11",
|
||||
"@types/jest": "^29.4.0",
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/md5": "^2.3.2",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-helmet": "^6.1.6",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"@umijs/fabric": "^2.14.1",
|
||||
"@umijs/lint": "^4.0.52",
|
||||
"@umijs/max": "^4.0.52",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.34.0",
|
||||
"express": "^4.18.2",
|
||||
"gh-pages": "^3.2.3",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^29.4.3",
|
||||
"jest-environment-jsdom": "^29.4.3",
|
||||
"lint-staged": "^10.5.4",
|
||||
"mockjs": "^1.1.0",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.4",
|
||||
"swagger-ui-dist": "^4.15.5",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5",
|
||||
"umi-presets-pro": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
}
|
||||
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
1
public/CNAME
Normal file
@@ -0,0 +1 @@
|
||||
preview.pro.ant.design
|
||||
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/icons/icon-128x128.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/icons/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/icons/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
public/icons/icon.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
1
public/logo.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200" version="1.1" viewBox="0 0 200 200"><title>Group 28 Copy 5</title><desc>Created with Sketch.</desc><defs><linearGradient id="linearGradient-1" x1="62.102%" x2="108.197%" y1="0%" y2="37.864%"><stop offset="0%" stop-color="#4285EB"/><stop offset="100%" stop-color="#2EC7FF"/></linearGradient><linearGradient id="linearGradient-2" x1="69.644%" x2="54.043%" y1="0%" y2="108.457%"><stop offset="0%" stop-color="#29CDFF"/><stop offset="37.86%" stop-color="#148EFF"/><stop offset="100%" stop-color="#0A60FF"/></linearGradient><linearGradient id="linearGradient-3" x1="69.691%" x2="16.723%" y1="-12.974%" y2="117.391%"><stop offset="0%" stop-color="#FA816E"/><stop offset="41.473%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient><linearGradient id="linearGradient-4" x1="68.128%" x2="30.44%" y1="-35.691%" y2="114.943%"><stop offset="0%" stop-color="#FA8E7D"/><stop offset="51.264%" stop-color="#F74A5C"/><stop offset="100%" stop-color="#F51D2C"/></linearGradient></defs><g id="Page-1" fill="none" fill-rule="evenodd" stroke="none" stroke-width="1"><g id="logo" transform="translate(-20.000000, -20.000000)"><g id="Group-28-Copy-5" transform="translate(20.000000, 20.000000)"><g id="Group-27-Copy-3"><g id="Group-25" fill-rule="nonzero"><g id="2"><path id="Shape" fill="url(#linearGradient-1)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C99.2571609,26.9692191 101.032305,26.9692191 102.20193,28.1378823 L129.985225,55.8983314 C134.193707,60.1033528 141.017005,60.1033528 145.225487,55.8983314 C149.433969,51.69331 149.433969,44.8756232 145.225487,40.6706018 L108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/><path id="Shape" fill="url(#linearGradient-2)" d="M91.5880863,4.17652823 L4.17996544,91.5127728 C-0.519240605,96.2081146 -0.519240605,103.791885 4.17996544,108.487227 L91.5880863,195.823472 C96.2872923,200.518814 103.877304,200.518814 108.57651,195.823472 L145.225487,159.204632 C149.433969,154.999611 149.433969,148.181924 145.225487,143.976903 C141.017005,139.771881 134.193707,139.771881 129.985225,143.976903 L102.20193,171.737352 C101.032305,172.906015 99.2571609,172.906015 98.0875359,171.737352 L28.285908,101.993122 C27.1162831,100.824459 27.1162831,99.050775 28.285908,97.8821118 L98.0875359,28.1378823 C100.999864,25.6271836 105.751642,20.541824 112.729652,19.3524487 C117.915585,18.4685261 123.585219,20.4140239 129.738554,25.1889424 C125.624663,21.0784292 118.571995,14.0340304 108.58055,4.05574592 C103.862049,-0.537986846 96.2692618,-0.500797906 91.5880863,4.17652823 Z"/></g><path id="Shape" fill="url(#linearGradient-3)" d="M153.685633,135.854579 C157.894115,140.0596 164.717412,140.0596 168.925894,135.854579 L195.959977,108.842726 C200.659183,104.147384 200.659183,96.5636133 195.960527,91.8688194 L168.690777,64.7181159 C164.472332,60.5180858 157.646868,60.5241425 153.435895,64.7316526 C149.227413,68.936674 149.227413,75.7543607 153.435895,79.9593821 L171.854035,98.3623765 C173.02366,99.5310396 173.02366,101.304724 171.854035,102.473387 L153.685633,120.626849 C149.47715,124.83187 149.47715,131.649557 153.685633,135.854579 Z"/></g><ellipse id="Combined-Shape" cx="100.519" cy="100.437" fill="url(#linearGradient-4)" rx="23.6" ry="23.581"/></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/openIM.wasm
Normal file
5
public/pro_icon.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="42" height="42" xmlns="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<path fill="#070707" d="m6.717392,13.773912l5.6,0c2.8,0 4.7,1.9 4.7,4.7c0,2.8 -2,4.7 -4.9,4.7l-2.5,0l0,4.3l-2.9,0l0,-13.7zm2.9,2.2l0,4.9l1.9,0c1.6,0 2.6,-0.9 2.6,-2.4c0,-1.6 -0.9,-2.4 -2.6,-2.4l-1.9,0l0,-0.1zm8.9,11.5l2.7,0l0,-5.7c0,-1.4 0.8,-2.3 2.2,-2.3c0.4,0 0.8,0.1 1,0.2l0,-2.4c-0.2,-0.1 -0.5,-0.1 -0.8,-0.1c-1.2,0 -2.1,0.7 -2.4,2l-0.1,0l0,-1.9l-2.7,0l0,10.2l0.1,0zm11.7,0.1c-3.1,0 -5,-2 -5,-5.3c0,-3.3 2,-5.3 5,-5.3s5,2 5,5.3c0,3.4 -1.9,5.3 -5,5.3zm0,-2.1c1.4,0 2.2,-1.1 2.2,-3.2c0,-2 -0.8,-3.2 -2.2,-3.2c-1.4,0 -2.2,1.2 -2.2,3.2c0,2.1 0.8,3.2 2.2,3.2z" class="st0" id="Ant-Design-Pro"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 681 B |
202
public/scripts/loading.js
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* loading 占位
|
||||
* 解决首次加载时白屏的问题
|
||||
*/
|
||||
(function () {
|
||||
const _root = document.querySelector('#root');
|
||||
if (_root && _root.innerHTML === '') {
|
||||
_root.innerHTML = `
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#root {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
.loading-title {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.loading-sub-title {
|
||||
margin-top: 20px;
|
||||
font-size: 1rem;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.page-loading-warp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 26px;
|
||||
}
|
||||
.ant-spin {
|
||||
position: absolute;
|
||||
display: none;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
color: #1890ff;
|
||||
font-size: 14px;
|
||||
font-variant: tabular-nums;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
list-style: none;
|
||||
opacity: 0;
|
||||
-webkit-transition: -webkit-transform 0.3s
|
||||
cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
transition: -webkit-transform 0.3s
|
||||
cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
transition: transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86),
|
||||
-webkit-transform 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
|
||||
-webkit-font-feature-settings: "tnum";
|
||||
font-feature-settings: "tnum";
|
||||
}
|
||||
|
||||
.ant-spin-spinning {
|
||||
position: static;
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.ant-spin-dot {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-color: #1890ff;
|
||||
border-radius: 100%;
|
||||
-webkit-transform: scale(0.75);
|
||||
-ms-transform: scale(0.75);
|
||||
transform: scale(0.75);
|
||||
-webkit-transform-origin: 50% 50%;
|
||||
-ms-transform-origin: 50% 50%;
|
||||
transform-origin: 50% 50%;
|
||||
opacity: 0.3;
|
||||
-webkit-animation: antspinmove 1s infinite linear alternate;
|
||||
animation: antSpinMove 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-animation-delay: 0.8s;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.ant-spin-dot-item:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
-webkit-animation-delay: 1.2s;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
.ant-spin-dot-spin {
|
||||
-webkit-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
-webkit-animation: antrotate 1.2s infinite linear;
|
||||
animation: antRotate 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.ant-spin-lg .ant-spin-dot {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.ant-spin-lg .ant-spin-dot i {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
.ant-spin-blur {
|
||||
background: #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antSpinMove {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antSpinMove {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes antRotate {
|
||||
to {
|
||||
-webkit-transform: rotate(405deg);
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antRotate {
|
||||
to {
|
||||
-webkit-transform: rotate(405deg);
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
min-height: 362px;
|
||||
">
|
||||
<div class="page-loading-warp">
|
||||
<div class="ant-spin ant-spin-lg ant-spin-spinning">
|
||||
<span class="ant-spin-dot ant-spin-dot-spin">
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
<i class="ant-spin-dot-item"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-title">
|
||||
正在加载资源
|
||||
</div>
|
||||
<div class="loading-sub-title">
|
||||
初次加载资源可能需要较多时间 请耐心等待
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})();
|
||||
BIN
public/sql-wasm.wasm
Executable file
681
public/wasm_exec.js
Normal file
@@ -0,0 +1,681 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// @ts-nocheck
|
||||
|
||||
(() => {
|
||||
// Map multiple JavaScript environments to a single common API,
|
||||
// preferring web standards over Node.js API.
|
||||
//
|
||||
// Environments considered:
|
||||
// - Browsers
|
||||
// - Node.js
|
||||
// - Electron
|
||||
// - Parcel
|
||||
// - Webpack
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.global = window;
|
||||
} else if (typeof self !== 'undefined') {
|
||||
self.global = self;
|
||||
} else {
|
||||
throw new Error(
|
||||
'cannot export Go (neither global, window nor self is defined)'
|
||||
);
|
||||
}
|
||||
|
||||
const enosys = () => {
|
||||
const err = new Error('not implemented');
|
||||
err.code = 'ENOSYS';
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!global.fs) {
|
||||
let outputBuf = '';
|
||||
global.fs = {
|
||||
constants: {
|
||||
O_WRONLY: -1,
|
||||
O_RDWR: -1,
|
||||
O_CREAT: -1,
|
||||
O_TRUNC: -1,
|
||||
O_APPEND: -1,
|
||||
O_EXCL: -1,
|
||||
}, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf('\n');
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substr(0, nl));
|
||||
outputBuf = outputBuf.substr(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
chown(path, uid, gid, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
close(fd, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
fchmod(fd, mode, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
fchown(fd, uid, gid, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
fstat(fd, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
fsync(fd, callback) {
|
||||
callback(null);
|
||||
},
|
||||
ftruncate(fd, length, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
lchown(path, uid, gid, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
link(path, link, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
lstat(path, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
mkdir(path, perm, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
open(path, flags, mode, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
read(fd, buffer, offset, length, position, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
readdir(path, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
readlink(path, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
rename(from, to, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
rmdir(path, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
stat(path, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
symlink(path, link, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
truncate(path, length, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
unlink(path, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
utimes(path, atime, mtime, callback) {
|
||||
callback(enosys());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.process) {
|
||||
global.process = {
|
||||
getuid() {
|
||||
return -1;
|
||||
},
|
||||
getgid() {
|
||||
return -1;
|
||||
},
|
||||
geteuid() {
|
||||
return -1;
|
||||
},
|
||||
getegid() {
|
||||
return -1;
|
||||
},
|
||||
getgroups() {
|
||||
throw enosys();
|
||||
},
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() {
|
||||
throw enosys();
|
||||
},
|
||||
cwd() {
|
||||
throw enosys();
|
||||
},
|
||||
chdir() {
|
||||
throw enosys();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.crypto) {
|
||||
throw new Error(
|
||||
'global.crypto is not available, polyfill required (getRandomValues only)'
|
||||
);
|
||||
}
|
||||
|
||||
if (!global.performance) {
|
||||
global.performance = {
|
||||
now() {
|
||||
const [sec, nsec] = process.hrtime();
|
||||
return sec * 1000 + nsec / 1000000;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (!global.TextEncoder) {
|
||||
throw new Error('global.TextEncoder is not available, polyfill required');
|
||||
}
|
||||
|
||||
if (!global.TextDecoder) {
|
||||
throw new Error('global.TextDecoder is not available, polyfill required');
|
||||
}
|
||||
|
||||
// End of polyfills for common API.
|
||||
|
||||
const encoder = new TextEncoder('utf-8');
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
|
||||
global.Go = class {
|
||||
constructor() {
|
||||
this.argv = ['js'];
|
||||
this.env = {};
|
||||
this.exit = code => {
|
||||
if (code !== 0) {
|
||||
console.warn('exit code:', code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise(resolve => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
};
|
||||
|
||||
const getInt64 = addr => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
};
|
||||
|
||||
const loadValue = addr => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
};
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7ff80000;
|
||||
|
||||
if (typeof v === 'number' && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case 'object':
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case 'symbol':
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case 'function':
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
};
|
||||
|
||||
const loadSlice = addr => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
};
|
||||
|
||||
const loadSliceOfValues = addr => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
const loadString = addr => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(
|
||||
new DataView(this._inst.exports.mem.buffer, saddr, len)
|
||||
);
|
||||
};
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
go: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
'runtime.wasmExit': sp => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
'runtime.wasmWrite': sp => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(
|
||||
fd,
|
||||
new Uint8Array(this._inst.exports.mem.buffer, p, n)
|
||||
);
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
'runtime.resetMemoryDataView': sp => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
'runtime.nanotime1': sp => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
'runtime.walltime': sp => {
|
||||
sp >>>= 0;
|
||||
const msec = new Date().getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
'runtime.scheduleTimeoutEvent': sp => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(
|
||||
id,
|
||||
setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn('scheduleTimeoutEvent: missed timeout event');
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8) + 1 // setTimeout has been seen to fire up to 1 millisecond early
|
||||
)
|
||||
);
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
'runtime.clearTimeoutEvent': sp => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
'runtime.getRandomData': sp => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
'syscall/js.finalizeRef': sp => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
'syscall/js.stringVal': sp => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
'syscall/js.valueGet': sp => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
'syscall/js.valueSet': sp => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(
|
||||
loadValue(sp + 8),
|
||||
loadString(sp + 16),
|
||||
loadValue(sp + 32)
|
||||
);
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
'syscall/js.valueDelete': sp => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
'syscall/js.valueIndex': sp => {
|
||||
sp >>>= 0;
|
||||
storeValue(
|
||||
sp + 24,
|
||||
Reflect.get(loadValue(sp + 8), getInt64(sp + 16))
|
||||
);
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
'syscall/js.valueSetIndex': sp => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(
|
||||
loadValue(sp + 8),
|
||||
getInt64(sp + 16),
|
||||
loadValue(sp + 24)
|
||||
);
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
'syscall/js.valueCall': sp => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
'syscall/js.valueInvoke': sp => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
'syscall/js.valueNew': sp => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
'syscall/js.valueLength': sp => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
'syscall/js.valuePrepareString': sp => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
'syscall/js.valueLoadString': sp => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
'syscall/js.valueInstanceOf': sp => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(
|
||||
sp + 24,
|
||||
loadValue(sp + 8) instanceof loadValue(sp + 16) ? 1 : 0
|
||||
);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
'syscall/js.copyBytesToGo': sp => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (
|
||||
!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)
|
||||
) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
'syscall/js.copyBytesToJS': sp => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (
|
||||
!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)
|
||||
) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
debug: value => {
|
||||
console.log(value);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error('Go.run: WebAssembly.Instance expected');
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [
|
||||
// JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
global,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([
|
||||
// mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[global, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = str => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + '\0');
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach(arg => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach(key => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach(ptr => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error(
|
||||
'total length of command line and environment variables exceeds limit'
|
||||
);
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error('Go program has already exited');
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
};
|
||||
})();
|
||||
9
src/access.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @see https://umijs.org/zh-CN/plugins/plugin-access
|
||||
* */
|
||||
export default function access(initialState: { currentUser?: any } | undefined) {
|
||||
const { currentUser } = initialState ?? {};
|
||||
return {
|
||||
canAdmin: currentUser && currentUser.level === 100,
|
||||
};
|
||||
}
|
||||
155
src/app.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import Footer from '@/components/Footer';
|
||||
import { LinkOutlined } from '@ant-design/icons';
|
||||
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
|
||||
import { SettingDrawer } from '@ant-design/pro-components';
|
||||
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
|
||||
import { history, Link } from '@umijs/max';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import defaultSettings from '../config/defaultSettings';
|
||||
import { AvatarDropdown, AvatarName } from './components/RightContent/AvatarDropdown';
|
||||
import VideoPlayerModal from './components/VideoPlayerModal';
|
||||
import { errorConfig } from './requestErrorConfig';
|
||||
import { adminInfo } from './services/server/login';
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
const loginPath = '/login';
|
||||
|
||||
/**
|
||||
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
|
||||
* */
|
||||
export async function getInitialState(): Promise<{
|
||||
settings?: Partial<LayoutSettings>;
|
||||
currentUser?: any;
|
||||
videoUrl?: string;
|
||||
loading?: boolean;
|
||||
fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;
|
||||
}> {
|
||||
const videoUrl = '';
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const { data } = await adminInfo();
|
||||
return data;
|
||||
} catch (error) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
// 如果不是登录页面,执行
|
||||
const { location } = history;
|
||||
if (location.pathname !== loginPath) {
|
||||
const currentUser = await fetchUserInfo();
|
||||
return {
|
||||
fetchUserInfo,
|
||||
currentUser,
|
||||
settings: defaultSettings as Partial<LayoutSettings>,
|
||||
};
|
||||
}
|
||||
return {
|
||||
videoUrl,
|
||||
fetchUserInfo,
|
||||
settings: defaultSettings as Partial<LayoutSettings>,
|
||||
};
|
||||
}
|
||||
|
||||
// ProLayout 支持的api https://procomponents.ant.design/components/layout
|
||||
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) => {
|
||||
return {
|
||||
// actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />],
|
||||
actionsRender: () => [<VideoPlayerModal key="video" />],
|
||||
avatarProps: {
|
||||
src: initialState?.currentUser?.faceURL || '/icons/icon.png',
|
||||
title: <AvatarName />,
|
||||
render: (_, avatarChildren) => {
|
||||
return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
|
||||
},
|
||||
},
|
||||
// 水印
|
||||
// waterMarkProps: {
|
||||
// content: initialState?.currentUser?.nickname,
|
||||
// },
|
||||
footerRender: () => <Footer />,
|
||||
onPageChange: () => {
|
||||
const { location } = history;
|
||||
// 如果没有登录,重定向到 login
|
||||
if (!initialState?.currentUser && location.pathname !== loginPath) {
|
||||
history.push(loginPath);
|
||||
}
|
||||
},
|
||||
layoutBgImgList: [
|
||||
{
|
||||
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
|
||||
left: 85,
|
||||
bottom: 100,
|
||||
height: '303px',
|
||||
},
|
||||
{
|
||||
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
|
||||
bottom: -68,
|
||||
right: -45,
|
||||
height: '303px',
|
||||
},
|
||||
{
|
||||
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '331px',
|
||||
},
|
||||
],
|
||||
links: isDev
|
||||
? [
|
||||
<Link key="openapi" to="/umi/plugin/openapi" target="_blank">
|
||||
<LinkOutlined />
|
||||
<span>OpenAPI 文档</span>
|
||||
</Link>,
|
||||
]
|
||||
: [],
|
||||
menuHeaderRender: undefined,
|
||||
// 自定义 403 页面
|
||||
// unAccessible: <div>unAccessible</div>,
|
||||
// 增加一个 loading 的状态
|
||||
childrenRender: (children) => {
|
||||
// if (initialState?.loading) return <PageLoading />;
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<SettingDrawer
|
||||
disableUrlParams
|
||||
enableDarkTheme
|
||||
settings={initialState?.settings}
|
||||
onSettingChange={(settings) => {
|
||||
setInitialState((preInitialState) => ({
|
||||
...preInitialState,
|
||||
settings,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
...initialState?.settings,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @name request 配置,可以配置错误处理
|
||||
* 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
|
||||
* @doc https://umijs.org/docs/max/request#配置
|
||||
*/
|
||||
const authHeaderInterceptor = (url: string, options: RequestConfig) => {
|
||||
const authHeader = {
|
||||
...options.headers,
|
||||
token: localStorage.getItem(options.headers?.isAccount ? 'IMAccountToken' : 'IMAdminToken'),
|
||||
operationID: uuidv4(),
|
||||
};
|
||||
|
||||
return {
|
||||
url: `${url}`,
|
||||
options: { ...options, interceptors: true, headers: authHeader },
|
||||
};
|
||||
};
|
||||
|
||||
export const request: RequestConfig = {
|
||||
...errorConfig,
|
||||
timeout: 300000,
|
||||
// 新增自动添加AccessToken的请求前拦截器
|
||||
requestInterceptors: [authHeaderInterceptor],
|
||||
};
|
||||
BIN
src/assets/defualtAvatar/ic_avatar_01.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
src/assets/defualtAvatar/ic_avatar_02.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
src/assets/defualtAvatar/ic_avatar_03.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
src/assets/defualtAvatar/ic_avatar_04.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
src/assets/defualtAvatar/ic_avatar_05.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/defualtAvatar/ic_avatar_06.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
src/assets/images/avatar_upload.png
Normal file
|
After Width: | Height: | Size: 447 B |
BIN
src/assets/images/call_video.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
src/assets/images/file_icon.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/file_unknow.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/assets/images/group.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
src/assets/images/group_icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/assets/images/location.png
Normal file
|
After Width: | Height: | Size: 946 B |
BIN
src/assets/images/login_bg.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
src/assets/images/speaker.png
Normal file
|
After Width: | Height: | Size: 695 B |
367
src/components/ContentParse/index.tsx
Normal file
@@ -0,0 +1,367 @@
|
||||
import { ContentType } from '@/constants/enum';
|
||||
import { useModel } from '@umijs/max';
|
||||
import { Image, Typography } from 'antd';
|
||||
|
||||
import call_video from '@/assets/images/call_video.png';
|
||||
|
||||
type ContentParseProps = {
|
||||
record: any;
|
||||
};
|
||||
|
||||
const ContentParse = ({ record }: ContentParseProps) => {
|
||||
const { setInitialState } = useModel('@@initialState');
|
||||
|
||||
const showVideo = (url: string) => {
|
||||
setInitialState((s) => ({
|
||||
...s,
|
||||
videoUrl: url,
|
||||
}));
|
||||
};
|
||||
|
||||
// 101
|
||||
if (record.contentType === ContentType.TextMessage) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: JSON.parse(record.content).content }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(record.content).content}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 102
|
||||
if (record.contentType === ContentType.PicMessage) {
|
||||
return (
|
||||
<Image
|
||||
width={100}
|
||||
src={
|
||||
JSON.parse(record.content).snapshotPicture.url ??
|
||||
JSON.parse(record.content).sourcePicture.url
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// 103
|
||||
if (record.contentType === ContentType.VoiceMessage) {
|
||||
return (
|
||||
<Typography.Link
|
||||
href={JSON.parse(record.content).sourceUrl}
|
||||
target="_blank"
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
【音频下载】
|
||||
</Typography.Link>
|
||||
);
|
||||
}
|
||||
// 104
|
||||
if (record.contentType === ContentType.VideoMessage) {
|
||||
return (
|
||||
<div
|
||||
className="video-img cursor-pointer"
|
||||
onClick={() => showVideo(JSON.parse(record.content).videoUrl)}
|
||||
>
|
||||
<img src={JSON.parse(record.content).snapshotUrl} alt="video" width={100} />
|
||||
<div className="video-image-mask">
|
||||
<img src={call_video} alt="" width={40} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 105
|
||||
if (record.contentType === ContentType.FileMessage) {
|
||||
return (
|
||||
<Typography.Link
|
||||
href={JSON.parse(record.content).sourceUrl}
|
||||
target="_blank"
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
【文件下载】
|
||||
</Typography.Link>
|
||||
);
|
||||
}
|
||||
// 106
|
||||
if (record.contentType === ContentType.TextAtMessage) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: JSON.parse(record.content).text }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(record.content).text}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 107
|
||||
if (record.contentType === ContentType.MergeMessage) {
|
||||
return <span>合并消息</span>;
|
||||
}
|
||||
// 108
|
||||
if (record.contentType === ContentType.CardMessage) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${JSON.parse(record.content).nickname}的名片`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(record.content).nickname}的名片
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 109
|
||||
if (record.contentType === ContentType.LocationMessage) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: JSON.parse(JSON.parse(record.content).description).addr }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).description).addr}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 110
|
||||
if (record.contentType === ContentType.CustomMessage) {
|
||||
const customType = JSON.parse(JSON.parse(record.content).data).customType;
|
||||
if (customType === 905) {
|
||||
return <span>【视频会议】</span>;
|
||||
}
|
||||
return <span>【自定义消息】</span>;
|
||||
}
|
||||
// 114
|
||||
if (record.contentType === ContentType.QuoteMessage) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: JSON.parse(record.content).text }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(record.content).text}{' '}
|
||||
</Typography.Text>
|
||||
);
|
||||
} // 115
|
||||
if (record.contentType === ContentType.FaceMessage) {
|
||||
return <Image width={100} src={JSON.parse(JSON.parse(record.content).data).url} />;
|
||||
}
|
||||
// 1201
|
||||
if (record.contentType === ContentType.FriendAppApproved) {
|
||||
return <span>同意</span>;
|
||||
}
|
||||
// 1501
|
||||
if (record.contentType === ContentType.GroupCreated) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).groupOwnerUser.userID + ' '
|
||||
}创建了群`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).groupOwnerUser.userID + ' '}创建了群
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1504
|
||||
if (record.contentType === ContentType.MemberQuit) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).quitUser.nickname + ' '
|
||||
}退出群聊通知`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).quitUser.nickname + ' '}退出群聊通知
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1507
|
||||
if (record.contentType === ContentType.GroupOwnerTransferred) {
|
||||
const opUser = JSON.parse(JSON.parse(record.content).detail).opUser.nickname;
|
||||
const newOwner = JSON.parse(JSON.parse(record.content).detail).newGroupOwner.nickname;
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: `${opUser + ' '}将群转让给了${' ' + newOwner}` }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{opUser + ' '}将群转让给了{' ' + newOwner}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1508
|
||||
if (record.contentType === ContentType.MemberKicked) {
|
||||
const opUser = JSON.parse(JSON.parse(record.content).detail).opUser.nickname;
|
||||
const listStr = JSON.parse(JSON.parse(record.content).detail)
|
||||
.kickedUserList.map((e: any) => e.nickname)
|
||||
.join(',');
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: `${listStr + ' '}被${' ' + opUser}踢出群聊` }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{listStr + ' '}被{' ' + opUser}踢出群聊
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1509
|
||||
if (record.contentType === ContentType.MemberInvited) {
|
||||
const opUser = JSON.parse(JSON.parse(record.content).detail).opUser.nickname;
|
||||
const listStr = JSON.parse(JSON.parse(record.content).detail)
|
||||
.invitedUserList.map((e: any) => e.nickname)
|
||||
.join(',');
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: `${opUser + ' '}邀请${'' + listStr}加入群聊` }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{opUser + ' '}邀请{'' + listStr}加入群聊
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1510
|
||||
if (record.contentType === ContentType.MemberEnter) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).entrantUser.nickname + ' '
|
||||
}加入了群`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).entrantUser.nickname + ' '}加入了群
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1511
|
||||
if (record.contentType === ContentType.DismissGroup) {
|
||||
const opUser = JSON.parse(JSON.parse(record.content).detail).opUser.nickname;
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: `${opUser + ' '}解散了群聊` }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{opUser + ' '}解散了群聊
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1512
|
||||
if (record.contentType === ContentType.MemberMutedNotification) {
|
||||
const opUser = JSON.parse(JSON.parse(record.content).detail).opUser.nickname;
|
||||
const mutedUser = JSON.parse(JSON.parse(record.content).detail).mutedUser.nickname;
|
||||
const mutedSeconds = JSON.parse(JSON.parse(record.content).detail).mutedSeconds;
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: `${mutedUser + ' '}被${' ' + opUser + ' '}禁言${mutedSeconds}秒` }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{mutedUser + ' '}被{' ' + opUser + ' '}禁言{mutedSeconds}秒
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1513
|
||||
if (record.contentType === ContentType.MemberCancelMutedNotification) {
|
||||
const opUser = JSON.parse(JSON.parse(record.content).detail).opUser.nickname;
|
||||
const mutedUser = JSON.parse(JSON.parse(record.content).detail).mutedUser.nickname;
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{ tooltip: `${opUser + ' '}取消了${' ' + mutedUser + ' '}的禁言` }}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{opUser + ' '}取消了{' ' + mutedUser + ' '}的禁言
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1514
|
||||
if (record.contentType === ContentType.MutedNotification) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '
|
||||
}开起了群禁言`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '}开起了群禁言
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1515
|
||||
if (record.contentType === ContentType.CancelMutedNotification) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '
|
||||
}关闭了群禁言`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '}关闭了群禁言
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1519
|
||||
if (record.contentType === ContentType.GroupAnnouncementUpdated) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '
|
||||
}修改了群公告`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '}修改了群公告
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1520
|
||||
if (record.contentType === ContentType.GroupNameUpdated) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '
|
||||
}修改了群名称`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).opUser.nickname + ' '}修改了群名称
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 1701
|
||||
if (record.contentType === ContentType.PrivateMessage) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).isPrivate ? '私有消息' : '非私有消息'
|
||||
}`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).isPrivate ? '私有消息' : '非私有消息'}
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
// 2101
|
||||
if (record.contentType === ContentType.MsgRevokeNotification) {
|
||||
return (
|
||||
<Typography.Text
|
||||
ellipsis={{
|
||||
tooltip: `${
|
||||
JSON.parse(JSON.parse(record.content).detail).revokerNickname + ' '
|
||||
}撤回了一条消息`,
|
||||
}}
|
||||
style={{ width: '200px' }}
|
||||
>
|
||||
{JSON.parse(JSON.parse(record.content).detail).revokerNickname + ' '}撤回了一条消息
|
||||
</Typography.Text>
|
||||
);
|
||||
}
|
||||
return <Typography.Text style={{ width: '200px' }}>{record.content}</Typography.Text>;
|
||||
};
|
||||
|
||||
export default ContentParse;
|
||||
24
src/components/Footer/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { DefaultFooter } from '@ant-design/pro-components';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import React from 'react';
|
||||
|
||||
const Footer: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const defaultMessage = intl.formatMessage({
|
||||
id: 'app.copyright.produced',
|
||||
defaultMessage: 'OpenIM后台管理系统',
|
||||
});
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<DefaultFooter
|
||||
style={{
|
||||
background: 'none',
|
||||
}}
|
||||
copyright={`${currentYear} ${defaultMessage}`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
23
src/components/HeaderDropdown/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { useEmotionCss } from '@ant-design/use-emotion-css';
|
||||
import { Dropdown } from 'antd';
|
||||
import type { DropDownProps } from 'antd/es/dropdown';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
export type HeaderDropdownProps = {
|
||||
overlayClassName?: string;
|
||||
placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topCenter' | 'topRight' | 'bottomCenter';
|
||||
} & Omit<DropDownProps, 'overlay'>;
|
||||
|
||||
const HeaderDropdown: React.FC<HeaderDropdownProps> = ({ overlayClassName: cls, ...restProps }) => {
|
||||
const className = useEmotionCss(({ token }) => {
|
||||
return {
|
||||
[`@media screen and (max-width: ${token.screenXS})`]: {
|
||||
width: '100%',
|
||||
},
|
||||
};
|
||||
});
|
||||
return <Dropdown overlayClassName={classNames(className, cls)} {...restProps} />;
|
||||
};
|
||||
|
||||
export default HeaderDropdown;
|
||||
74
src/components/OIMAvatar/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { Avatar as AntdAvatar, AvatarProps } from "antd";
|
||||
import clsx from "clsx";
|
||||
import { FC, useEffect, useMemo, useState } from "react";
|
||||
|
||||
// import default_group from "@/assets/images/contact/group.png";
|
||||
import default_group from "@/assets/images/group.png";
|
||||
|
||||
interface IOIMAvatarProps extends AvatarProps {
|
||||
text?: string;
|
||||
color?: string;
|
||||
bgColor?: string;
|
||||
isgroup?: boolean;
|
||||
isnotification?: boolean;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const OIMAvatar: FC<IOIMAvatarProps> = (props) => {
|
||||
const {
|
||||
src,
|
||||
text,
|
||||
size = 42,
|
||||
color = "#fff",
|
||||
bgColor = "#2074de",
|
||||
isgroup = false,
|
||||
isnotification,
|
||||
} = props;
|
||||
const [errorHolder, setErrorHolder] = useState<string>();
|
||||
const getAvatarUrl = useMemo(() => {
|
||||
if (src) {
|
||||
return src;
|
||||
}
|
||||
return isgroup ? default_group : undefined;
|
||||
}, [src, isgroup, isnotification]);
|
||||
|
||||
const avatarProps = { ...props, isgroup: undefined, isnotification: undefined };
|
||||
|
||||
useEffect(() => {
|
||||
if (!isgroup) {
|
||||
setErrorHolder(undefined);
|
||||
}
|
||||
}, [isgroup]);
|
||||
|
||||
const errorHandler = () => {
|
||||
if (isgroup) {
|
||||
setErrorHolder(default_group);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<AntdAvatar
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
minWidth: `${size}px`,
|
||||
minHeight: `${size}px`,
|
||||
lineHeight: `${size - 2}px`,
|
||||
color,
|
||||
}}
|
||||
shape="square"
|
||||
{...avatarProps}
|
||||
className={clsx(
|
||||
{
|
||||
"cursor-pointer": Boolean(props.onClick),
|
||||
},
|
||||
props.className,
|
||||
)}
|
||||
src={errorHolder ?? getAvatarUrl}
|
||||
onError={errorHandler as any}
|
||||
>
|
||||
{text}
|
||||
</AntdAvatar>
|
||||
);
|
||||
};
|
||||
|
||||
export default OIMAvatar;
|
||||
134
src/components/RightContent/AvatarDropdown.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import { useEmotionCss } from '@ant-design/use-emotion-css';
|
||||
import { history, useModel } from '@umijs/max';
|
||||
import { Spin } from 'antd';
|
||||
import { stringify } from 'querystring';
|
||||
import type { MenuInfo } from 'rc-menu/lib/interface';
|
||||
import React, { useCallback } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import HeaderDropdown from '../HeaderDropdown';
|
||||
|
||||
export type GlobalHeaderRightProps = {
|
||||
menu?: boolean;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export const AvatarName = () => {
|
||||
const { initialState } = useModel('@@initialState');
|
||||
const { currentUser } = initialState || {};
|
||||
return <span className="anticon">{currentUser?.nickname}</span>;
|
||||
};
|
||||
|
||||
export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu, children }) => {
|
||||
/**
|
||||
* 退出登录,并且将当前的 url 保存
|
||||
*/
|
||||
const loginOut = async () => {
|
||||
// await outLogin();
|
||||
localStorage.removeItem('IMAdminToken');
|
||||
localStorage.removeItem('IMAccountToken');
|
||||
const { search, pathname } = window.location;
|
||||
const urlParams = new URL(window.location.href).searchParams;
|
||||
/** 此方法会跳转到 redirect 参数所在的位置 */
|
||||
const redirect = urlParams.get('redirect');
|
||||
// Note: There may be security issues, please note
|
||||
if (window.location.pathname !== '/login' && !redirect) {
|
||||
history.replace({
|
||||
pathname: '/login',
|
||||
search: stringify({
|
||||
redirect: pathname + search,
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
const actionClassName = useEmotionCss(({ token }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
height: '48px',
|
||||
marginLeft: 'auto',
|
||||
overflow: 'hidden',
|
||||
alignItems: 'center',
|
||||
padding: '0 8px',
|
||||
cursor: 'pointer',
|
||||
borderRadius: token.borderRadius,
|
||||
'&:hover': {
|
||||
backgroundColor: token.colorBgTextHover,
|
||||
},
|
||||
};
|
||||
});
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
|
||||
const onMenuClick = useCallback(
|
||||
(event: MenuInfo) => {
|
||||
const { key } = event;
|
||||
if (key === 'logout') {
|
||||
flushSync(() => {
|
||||
setInitialState((s) => ({ ...s, currentUser: undefined }));
|
||||
});
|
||||
loginOut();
|
||||
return;
|
||||
}
|
||||
history.push(`/account/${key}`);
|
||||
},
|
||||
[setInitialState],
|
||||
);
|
||||
|
||||
const loading = (
|
||||
<span className={actionClassName}>
|
||||
<Spin
|
||||
size="small"
|
||||
style={{
|
||||
marginLeft: 8,
|
||||
marginRight: 8,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
|
||||
if (!initialState) {
|
||||
return loading;
|
||||
}
|
||||
|
||||
const { currentUser } = initialState;
|
||||
|
||||
if (!currentUser || !currentUser.nickname) {
|
||||
return loading;
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
...(menu
|
||||
? [
|
||||
{
|
||||
key: 'center',
|
||||
icon: <UserOutlined />,
|
||||
label: '个人中心',
|
||||
},
|
||||
{
|
||||
key: 'settings',
|
||||
icon: <SettingOutlined />,
|
||||
label: '个人设置',
|
||||
},
|
||||
{
|
||||
type: 'divider' as const,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: 'logout',
|
||||
icon: <LogoutOutlined />,
|
||||
label: '退出登录',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<HeaderDropdown
|
||||
menu={{
|
||||
selectedKeys: [],
|
||||
onClick: onMenuClick,
|
||||
items: menuItems,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</HeaderDropdown>
|
||||
);
|
||||
};
|
||||
30
src/components/RightContent/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { QuestionCircleOutlined } from '@ant-design/icons';
|
||||
import { SelectLang as UmiSelectLang } from '@umijs/max';
|
||||
|
||||
export type SiderTheme = 'light' | 'dark';
|
||||
|
||||
export const SelectLang = () => {
|
||||
return (
|
||||
<UmiSelectLang
|
||||
style={{
|
||||
padding: 4,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Question = () => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
height: 26,
|
||||
}}
|
||||
onClick={() => {
|
||||
window.open('https://pro.ant.design/docs/getting-started');
|
||||
}}
|
||||
>
|
||||
<QuestionCircleOutlined />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
245
src/components/SelectUserTable/index.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
// import type { SelectedRowDataItem } from '@/pages/message_manage/SendMessage';
|
||||
import type { UserListItem } from '@/pages/business_system/user_manage/data';
|
||||
import { getSomeGroupMemberList } from '@/services/server/group_manage';
|
||||
import { getUserList } from '@/services/server/user_manage';
|
||||
import { DefaultAvatar, defaultAvatarStr } from '@/utils/avatar';
|
||||
import { SearchOutlined } from '@ant-design/icons';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { ProTable } from '@ant-design/pro-components';
|
||||
import type { ColProps, FormInstance } from 'antd';
|
||||
import { Col, Input } from 'antd';
|
||||
import type { ForwardRefRenderFunction } from 'react';
|
||||
import React, { forwardRef, memo, useImperativeHandle, useMemo, useRef, useState } from 'react';
|
||||
import styles from './style.less';
|
||||
|
||||
export type SelectedRowDataItem = Record<
|
||||
number,
|
||||
{
|
||||
keys: React.Key[];
|
||||
items: UserListItem[];
|
||||
}
|
||||
>;
|
||||
|
||||
interface SelectUserTableProps {
|
||||
colProps?: ColProps;
|
||||
selectType?: 'radio' | 'checkbox';
|
||||
groupID?: string;
|
||||
defaultSelected?: SelectedRowDataItem;
|
||||
onSelectedChange?: (data: UserListItem[], selectedRowData: SelectedRowDataItem) => void;
|
||||
}
|
||||
|
||||
export type SelectUserTableHandle = {
|
||||
// SelectedList: UserListItem[];
|
||||
cancelSelect: (value: string) => void;
|
||||
clearSelect: () => void;
|
||||
};
|
||||
|
||||
const SelectUserTable: ForwardRefRenderFunction<SelectUserTableHandle, SelectUserTableProps> = (
|
||||
props,
|
||||
ref,
|
||||
) => {
|
||||
const { colProps, selectType, defaultSelected, groupID, onSelectedChange } = props;
|
||||
const actionRef = useRef<ActionType>();
|
||||
const formRef = useRef<FormInstance>();
|
||||
const [selectedRowData, setSelectedRowData] = useState(
|
||||
defaultSelected ?? ({} as SelectedRowDataItem),
|
||||
);
|
||||
const [current, setCurrent] = useState(1);
|
||||
|
||||
const columns: ProColumns<UserListItem>[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: '用户头像',
|
||||
dataIndex: 'avatar',
|
||||
valueType: 'avatar',
|
||||
key: 'avatar',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
render(_, record) {
|
||||
return <DefaultAvatar faceURL={record.faceURL} nickname={record.nickname} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
key: 'nickname',
|
||||
dataIndex: 'nickname',
|
||||
hideInSearch: true,
|
||||
width: 120,
|
||||
ellipsis: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '是否在群中',
|
||||
key: 'inGroup',
|
||||
dataIndex: 'inGroup',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
hideInTable: groupID === '',
|
||||
render: (_, record) => (record.inGroup ? '是' : '否'),
|
||||
},
|
||||
{
|
||||
title: '用户ID',
|
||||
key: 'userID',
|
||||
dataIndex: 'userID',
|
||||
align: 'center',
|
||||
ellipsis: true,
|
||||
formItemProps: (form, config) => {
|
||||
return { ...config, label: null, name: 'userID', style: { marginBottom: 0 } };
|
||||
},
|
||||
renderFormItem: () => {
|
||||
return (
|
||||
<>
|
||||
<div className="font-medium ml-3 mb-4">选择接收用户</div>
|
||||
<Input
|
||||
// value={value}
|
||||
onChange={(e) => {
|
||||
formRef.current?.setFieldValue('userID', e.target.value);
|
||||
formRef.current?.setFieldValue('nickname', e.target.value);
|
||||
}}
|
||||
className={styles.search_input}
|
||||
prefix={<SearchOutlined />}
|
||||
placeholder="输入用户ID或昵称"
|
||||
bordered={false}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
},
|
||||
editable: false,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
|
||||
const getItems = (data: SelectedRowDataItem) => {
|
||||
return Object.values(data)
|
||||
.map((data) => data.items)
|
||||
.flat();
|
||||
};
|
||||
|
||||
const onSelectChange = (keys: React.Key[], items: UserListItem[]) => {
|
||||
const tmpData = { ...selectedRowData };
|
||||
tmpData[actionRef.current!.pageInfo!.current] = {
|
||||
keys,
|
||||
items,
|
||||
};
|
||||
console.log(keys, tmpData);
|
||||
setSelectedRowData(tmpData);
|
||||
if (onSelectedChange) {
|
||||
const data = getItems(tmpData);
|
||||
onSelectedChange(data, tmpData);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelSelect = (value: string) => {
|
||||
const tmpData = { ...selectedRowData };
|
||||
for (const key in tmpData) {
|
||||
if (tmpData.hasOwnProperty(key)) {
|
||||
const keyIdx = tmpData[key].keys.findIndex((k) => k === value);
|
||||
const itemdIdx = tmpData[key].items.findIndex((item) => item.userID === value);
|
||||
if (keyIdx !== -1 && itemdIdx !== -1) {
|
||||
tmpData[key].keys.splice(keyIdx, 1);
|
||||
tmpData[key].items.splice(itemdIdx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
setSelectedRowData(tmpData);
|
||||
setCurrent(current + 1);
|
||||
setTimeout(() => setCurrent(actionRef.current!.pageInfo!.current));
|
||||
if (onSelectedChange) {
|
||||
const data = getItems(tmpData);
|
||||
onSelectedChange(data, selectedRowData);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSelect = () => {
|
||||
actionRef.current?.clearSelected!();
|
||||
};
|
||||
|
||||
const getSelectKey = useMemo(
|
||||
() => selectedRowData[current]?.keys ?? [],
|
||||
[selectedRowData, current],
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
cancelSelect,
|
||||
clearSelect,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Col className={styles.send_user_table} {...(colProps ?? {})}>
|
||||
<ProTable<UserListItem>
|
||||
search={{
|
||||
optionRender: false,
|
||||
}}
|
||||
columns={columns}
|
||||
rowSelection={{
|
||||
columnTitle: '-',
|
||||
selectedRowKeys: getSelectKey,
|
||||
type: selectType,
|
||||
onChange: onSelectChange,
|
||||
}}
|
||||
tableAlertRender={false}
|
||||
tableAlertOptionRender={false}
|
||||
actionRef={actionRef}
|
||||
formRef={formRef}
|
||||
columnsState={{
|
||||
defaultValue: {
|
||||
option: {
|
||||
fixed: 'right',
|
||||
},
|
||||
},
|
||||
persistenceKey: 'user_table_insearch',
|
||||
persistenceType: 'sessionStorage',
|
||||
}}
|
||||
request={async (params = {}, sort, filter) => {
|
||||
console.log(params, sort, filter);
|
||||
const { data } = await getUserList({
|
||||
...params,
|
||||
keyword: params.userID,
|
||||
pagination: {
|
||||
pageNumber: params.current as number,
|
||||
showNumber: params.pageSize as number,
|
||||
},
|
||||
});
|
||||
const tmpData: UserListItem[] = data.users ?? [];
|
||||
let inGroupUserIDs = [] as string[];
|
||||
if (groupID) {
|
||||
const { data } = await getSomeGroupMemberList({
|
||||
groupID,
|
||||
userIDs: tmpData.map((item) => item.userID),
|
||||
});
|
||||
console.log(data);
|
||||
inGroupUserIDs = ((data.members as UserListItem[]) || []).map((user) => user.userID);
|
||||
}
|
||||
tmpData.forEach((user: UserListItem, idx: number) => {
|
||||
user.index = idx;
|
||||
user.inGroup = inGroupUserIDs.includes(user.userID);
|
||||
user.avatar = defaultAvatarStr(user.faceURL);
|
||||
});
|
||||
return {
|
||||
data: tmpData,
|
||||
success: true,
|
||||
total: data.total,
|
||||
};
|
||||
}}
|
||||
rowKey="userID"
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
onChange: (page) => {
|
||||
console.log(page);
|
||||
setCurrent(page);
|
||||
},
|
||||
showSizeChanger: false,
|
||||
}}
|
||||
dateFormatter="string"
|
||||
// scroll={{ x: 'max-content' }}
|
||||
toolbar={{
|
||||
actions: [],
|
||||
settings: [],
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(forwardRef(SelectUserTable));
|
||||
15
src/components/SelectUserTable/style.less
Normal file
@@ -0,0 +1,15 @@
|
||||
.send_user_table {
|
||||
background: #fff;
|
||||
.search_input {
|
||||
width: 300px;
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
:global {
|
||||
.ant-table-thead > tr > th {
|
||||
background: #fff;
|
||||
}
|
||||
.ant-pro-card {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/components/Twemoji/index.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React, { ReactNode, useEffect, useRef } from "react";
|
||||
import twemoji from "twemoji";
|
||||
|
||||
interface TwemojiOptions {
|
||||
className?: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
interface TwemojiProps {
|
||||
children: ReactNode;
|
||||
options?: TwemojiOptions;
|
||||
tag?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
const baseOptions = {
|
||||
className: "emojione",
|
||||
base: "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/",
|
||||
};
|
||||
|
||||
const Twemoji: React.FC<TwemojiProps> = (props) => {
|
||||
const { children, tag, options = {}, ...rest } = props;
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const childrenRefs = useRef<{ [key: string]: React.RefObject<HTMLElement> }>({});
|
||||
|
||||
const noWrapper = !tag;
|
||||
|
||||
const parseTwemoji = () => {
|
||||
if (noWrapper) {
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const i in childrenRefs.current) {
|
||||
const node = childrenRefs.current[i].current!;
|
||||
twemoji.parse(node, { ...baseOptions, ...options });
|
||||
}
|
||||
} else {
|
||||
const node = rootRef.current!;
|
||||
twemoji.parse(node, { ...baseOptions, ...options });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
parseTwemoji();
|
||||
}, []);
|
||||
|
||||
if (noWrapper) {
|
||||
return (
|
||||
<>
|
||||
{React.Children.map(children, (child, index) => {
|
||||
if (typeof child === "string") {
|
||||
console.warn(
|
||||
`Twemoji can't parse string child when noWrapper is set. Skipping child "${child}"`,
|
||||
);
|
||||
return child;
|
||||
}
|
||||
childrenRefs.current[index] =
|
||||
childrenRefs.current[index] || React.createRef<HTMLElement>();
|
||||
return React.cloneElement(child as JSX.Element, {
|
||||
ref: childrenRefs.current[index],
|
||||
});
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return React.createElement(tag ?? "div", { ref: rootRef, ...rest }, children);
|
||||
};
|
||||
|
||||
export default Twemoji;
|
||||
|
||||
export const parseTwemoji = (unicode: string) => {
|
||||
const div = document.createElement("div");
|
||||
div.textContent = unicode;
|
||||
document.body.appendChild(div);
|
||||
twemoji.parse(document.body, baseOptions);
|
||||
setTimeout(() => document.body.removeChild(div));
|
||||
return div.innerHTML;
|
||||
};
|
||||
1
src/components/Twemoji/type.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare module "@twemoji/parser";
|
||||
59
src/components/VideoPlayerModal/index.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'xgplayer/dist/index.min.css';
|
||||
|
||||
import { Modal } from 'antd';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { I18N, SimplePlayer } from 'xgplayer';
|
||||
import ZH from 'xgplayer/es/lang/zh-cn';
|
||||
import Error from 'xgplayer/es/plugins/error';
|
||||
import Mobile from 'xgplayer/es/plugins/mobile';
|
||||
import PC from 'xgplayer/es/plugins/pc';
|
||||
import Play from 'xgplayer/es/plugins/play';
|
||||
import Progress from 'xgplayer/es/plugins/progress';
|
||||
// 引入es目录下的插件
|
||||
import { useModel } from '@umijs/max';
|
||||
import Start from 'xgplayer/es/plugins/start';
|
||||
import Time from 'xgplayer/es/plugins/time';
|
||||
|
||||
I18N.use(ZH);
|
||||
|
||||
const VideoPlayerModal = () => {
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
|
||||
useEffect(() => {
|
||||
if (!!initialState?.videoUrl) {
|
||||
new SimplePlayer({
|
||||
id: 'video_player',
|
||||
url: initialState?.videoUrl,
|
||||
plugins: [Start, PC, Mobile, Progress, Play, Time, Error], // 传入需要组装的插件
|
||||
});
|
||||
}
|
||||
}, [initialState?.videoUrl]);
|
||||
|
||||
const onCancel = () => {
|
||||
setInitialState((s: any) => ({
|
||||
...s,
|
||||
videoUrl: '',
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={null}
|
||||
footer={null}
|
||||
// closeIcon={<CloseOutlined className="text-lg font-medium text-gray-400" />}
|
||||
open={!!initialState?.videoUrl}
|
||||
centered
|
||||
onCancel={onCancel}
|
||||
maskStyle={{
|
||||
opacity: 0,
|
||||
transition: 'none',
|
||||
}}
|
||||
className="no-padding-modal"
|
||||
maskTransitionName=""
|
||||
>
|
||||
<div id="video_player"></div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(VideoPlayerModal);
|
||||
8
src/config/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// ws 10001 api 10002 chat 10008 Admin 10009
|
||||
|
||||
export const WS_URL = "wss://web.rentsoft.cn/msg_gateway_enterprise";
|
||||
export const API_URL = 'https://web.rentsoft.cn/api_enterprise';
|
||||
export const CHAT_URL = 'https://web.rentsoft.cn/chat_enterprise';
|
||||
export const ACCOUNT_URL = 'https://web.rentsoft.cn/complete_admin_enterprise';
|
||||
|
||||
export const OBJECT_STORAGE = 'minio';
|
||||
15
src/constants/defualtAvatar.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import ic_avatar_01 from '@/assets/defualtAvatar/ic_avatar_01.png';
|
||||
import ic_avatar_02 from '@/assets/defualtAvatar/ic_avatar_02.png';
|
||||
import ic_avatar_03 from '@/assets/defualtAvatar/ic_avatar_03.png';
|
||||
import ic_avatar_04 from '@/assets/defualtAvatar/ic_avatar_04.png';
|
||||
import ic_avatar_05 from '@/assets/defualtAvatar/ic_avatar_05.png';
|
||||
import ic_avatar_06 from '@/assets/defualtAvatar/ic_avatar_06.png';
|
||||
|
||||
export default {
|
||||
ic_avatar_01: ic_avatar_01,
|
||||
ic_avatar_02: ic_avatar_02,
|
||||
ic_avatar_03: ic_avatar_03,
|
||||
ic_avatar_04: ic_avatar_04,
|
||||
ic_avatar_05: ic_avatar_05,
|
||||
ic_avatar_06: ic_avatar_06,
|
||||
};
|
||||
79
src/constants/enum.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
export enum SessionType {
|
||||
Single = 1,
|
||||
Group = 2,
|
||||
SuperGroup = 3,
|
||||
Notification = 4,
|
||||
}
|
||||
|
||||
export enum ContentType {
|
||||
TextMessage = 101,
|
||||
PicMessage = 102,
|
||||
VoiceMessage = 103,
|
||||
VideoMessage = 104,
|
||||
FileMessage = 105,
|
||||
TextAtMessage = 106,
|
||||
MergeMessage = 107,
|
||||
CardMessage = 108,
|
||||
LocationMessage = 109,
|
||||
CustomMessage = 110,
|
||||
QuoteMessage = 114,
|
||||
FaceMessage = 115,
|
||||
FriendAppApproved = 1201,
|
||||
GroupCreated = 1501,
|
||||
MemberQuit = 1504,
|
||||
GroupOwnerTransferred = 1507,
|
||||
MemberKicked = 1508,
|
||||
MemberInvited = 1509,
|
||||
MemberEnter = 1510,
|
||||
DismissGroup = 1511,
|
||||
MemberMutedNotification = 1512,
|
||||
MemberCancelMutedNotification = 1513,
|
||||
MutedNotification = 1514,
|
||||
CancelMutedNotification = 1515,
|
||||
GroupAnnouncementUpdated = 1519,
|
||||
GroupNameUpdated = 1520,
|
||||
PrivateMessage = 1701,
|
||||
MsgRevokeNotification = 2101,
|
||||
}
|
||||
|
||||
export enum InvitationCodeStatus {
|
||||
All = 1,
|
||||
UnUsed = 2,
|
||||
Used = 3,
|
||||
}
|
||||
|
||||
export enum GroupVerificationType {
|
||||
ApplyNeedInviteNot,
|
||||
AllNeed,
|
||||
AllNot,
|
||||
}
|
||||
|
||||
export enum AllowType {
|
||||
Allowed,
|
||||
NotAllowed,
|
||||
}
|
||||
|
||||
export enum GroupStatus {
|
||||
Nomal = 0,
|
||||
Baned = 1,
|
||||
Dismissed = 2,
|
||||
Muted = 3,
|
||||
}
|
||||
|
||||
// export enum GroupRole {
|
||||
// Nomal = 1,
|
||||
// Owner = 2,
|
||||
// Admin = 3,
|
||||
// }
|
||||
|
||||
export enum GroupRole {
|
||||
Nomal = 20,
|
||||
Owner = 100,
|
||||
Admin = 60,
|
||||
}
|
||||
|
||||
export enum GroupJoinSource {
|
||||
Invitation = 2,
|
||||
Search = 3,
|
||||
QrCode = 4,
|
||||
}
|
||||
116
src/constants/messageTypeOtions.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { ContentType } from './enum';
|
||||
|
||||
export const MessageTypeOtions = [
|
||||
{
|
||||
label: '文字消息',
|
||||
value: ContentType.TextMessage,
|
||||
},
|
||||
{
|
||||
label: '图片消息',
|
||||
value: ContentType.PicMessage,
|
||||
},
|
||||
{
|
||||
label: '语音消息',
|
||||
value: ContentType.VoiceMessage,
|
||||
},
|
||||
{
|
||||
label: '视频消息',
|
||||
value: ContentType.VideoMessage,
|
||||
},
|
||||
{
|
||||
label: '文件消息',
|
||||
value: ContentType.FileMessage,
|
||||
},
|
||||
{
|
||||
label: '@消息',
|
||||
value: ContentType.TextAtMessage,
|
||||
},
|
||||
{
|
||||
label: '合并消息',
|
||||
value: ContentType.MergeMessage,
|
||||
},
|
||||
{
|
||||
label: '名片消息',
|
||||
value: ContentType.CardMessage,
|
||||
},
|
||||
{
|
||||
label: '位置消息',
|
||||
value: ContentType.LocationMessage,
|
||||
},
|
||||
{
|
||||
label: '自定义消息',
|
||||
value: ContentType.CustomMessage,
|
||||
},
|
||||
{
|
||||
label: '引用消息',
|
||||
value: ContentType.QuoteMessage,
|
||||
},
|
||||
{
|
||||
label: '表情消息',
|
||||
value: ContentType.FaceMessage,
|
||||
},
|
||||
{
|
||||
label: '同意加好友申请通知',
|
||||
value: ContentType.FriendAppApproved,
|
||||
},
|
||||
{
|
||||
label: '群创建',
|
||||
value: ContentType.GroupCreated,
|
||||
},
|
||||
{
|
||||
label: '群成员退出通知',
|
||||
value: ContentType.MemberQuit,
|
||||
},
|
||||
{
|
||||
label: '群主更换通知',
|
||||
value: ContentType.GroupOwnerTransferred,
|
||||
},
|
||||
{
|
||||
label: '群成员被踢通知',
|
||||
value: ContentType.MemberKicked,
|
||||
},
|
||||
{
|
||||
label: '邀请好友',
|
||||
value: ContentType.MemberInvited,
|
||||
},
|
||||
{
|
||||
label: '群成员进群通知',
|
||||
value: ContentType.MemberEnter,
|
||||
},
|
||||
{
|
||||
label: '群解散',
|
||||
value: ContentType.DismissGroup,
|
||||
},
|
||||
{
|
||||
label: '群成员禁言通知',
|
||||
value: ContentType.MemberMutedNotification,
|
||||
},
|
||||
{
|
||||
label: '取消群成员禁言通知',
|
||||
value: ContentType.MemberCancelMutedNotification,
|
||||
},
|
||||
{
|
||||
label: '群禁言通知',
|
||||
value: ContentType.MutedNotification,
|
||||
},
|
||||
{
|
||||
label: '取消群禁言通知',
|
||||
value: ContentType.CancelMutedNotification,
|
||||
},
|
||||
{
|
||||
label: '群公告修改',
|
||||
value: ContentType.GroupAnnouncementUpdated,
|
||||
},
|
||||
{
|
||||
label: '群名称修改',
|
||||
value: ContentType.GroupNameUpdated,
|
||||
},
|
||||
{
|
||||
label: '私有会话',
|
||||
value: ContentType.PrivateMessage,
|
||||
},
|
||||
{
|
||||
label: '撤回消息',
|
||||
value: ContentType.MsgRevokeNotification,
|
||||
},
|
||||
];
|
||||
143
src/global.less
Normal file
@@ -0,0 +1,143 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
--full-height: 100%;
|
||||
--chat-bubble: #f4f5f7;
|
||||
--chat-bubble-sender: #cce7fe;
|
||||
--base-black: #0c1c33;
|
||||
--primary: #0089ff;
|
||||
--primary-active: #f3f8ff;
|
||||
--top-search-bar: #0289fa;
|
||||
--sub-text: #8e9ab0;
|
||||
--gap-text: #e8eaef;
|
||||
--warn-text: #ff381f;
|
||||
--moment-text: #6085b1;
|
||||
--searchbar-height: 2.5rem;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
|
||||
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
}
|
||||
|
||||
*,
|
||||
:before,
|
||||
:after {
|
||||
box-sizing: border-box;
|
||||
border-color: #e5e7eb;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 10px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: #b3b3b3;
|
||||
border-radius: 10px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.colorWeak {
|
||||
filter: invert(80%);
|
||||
}
|
||||
.ant-avatar-image {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.ant-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
|
||||
left: unset;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
&-thead > tr,
|
||||
&-tbody > tr {
|
||||
> th,
|
||||
> td {
|
||||
white-space: pre;
|
||||
> span {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-pro-table-list-toolbar:empty {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.ant-pro-field-index-column-border {
|
||||
color: black;
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.no-padding-modal {
|
||||
.ant-modal-content {
|
||||
padding: 0;
|
||||
// overflow: hidden;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-img {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
// .video-image-mask:hover {
|
||||
// opacity: 1;
|
||||
// }
|
||||
|
||||
.video-image-mask {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.emojione {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
91
src/global.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, message, notification } from 'antd';
|
||||
import defaultSettings from '../config/defaultSettings';
|
||||
|
||||
const { pwa } = defaultSettings;
|
||||
const isHttps = document.location.protocol === 'https:';
|
||||
|
||||
const clearCache = () => {
|
||||
// remove all caches
|
||||
if (window.caches) {
|
||||
caches
|
||||
.keys()
|
||||
.then((keys) => {
|
||||
keys.forEach((key) => {
|
||||
caches.delete(key);
|
||||
});
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
}
|
||||
};
|
||||
|
||||
// if pwa is true
|
||||
if (pwa) {
|
||||
// Notify user if offline now
|
||||
window.addEventListener('sw.offline', () => {
|
||||
message.warning(useIntl().formatMessage({ id: 'app.pwa.offline' }));
|
||||
});
|
||||
|
||||
// Pop up a prompt on the page asking the user if they want to use the latest version
|
||||
window.addEventListener('sw.updated', (event: Event) => {
|
||||
const e = event as CustomEvent;
|
||||
const reloadSW = async () => {
|
||||
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration
|
||||
const worker = e.detail && e.detail.waiting;
|
||||
if (!worker) {
|
||||
return true;
|
||||
}
|
||||
// Send skip-waiting event to waiting SW with MessageChannel
|
||||
await new Promise((resolve, reject) => {
|
||||
const channel = new MessageChannel();
|
||||
channel.port1.onmessage = (msgEvent) => {
|
||||
if (msgEvent.data.error) {
|
||||
reject(msgEvent.data.error);
|
||||
} else {
|
||||
resolve(msgEvent.data);
|
||||
}
|
||||
};
|
||||
worker.postMessage({ type: 'skip-waiting' }, [channel.port2]);
|
||||
});
|
||||
|
||||
clearCache();
|
||||
window.location.reload();
|
||||
return true;
|
||||
};
|
||||
const key = `open${Date.now()}`;
|
||||
const btn = (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
notification.destroy(key);
|
||||
reloadSW();
|
||||
}}
|
||||
>
|
||||
{useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.ok' })}
|
||||
</Button>
|
||||
);
|
||||
notification.open({
|
||||
message: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated' }),
|
||||
description: useIntl().formatMessage({ id: 'app.pwa.serviceworker.updated.hint' }),
|
||||
btn,
|
||||
key,
|
||||
onClose: async () => null,
|
||||
});
|
||||
});
|
||||
} else if ('serviceWorker' in navigator && isHttps) {
|
||||
// unregister service worker
|
||||
const { serviceWorker } = navigator;
|
||||
if (serviceWorker.getRegistrations) {
|
||||
serviceWorker.getRegistrations().then((sws) => {
|
||||
sws.forEach((sw) => {
|
||||
sw.unregister();
|
||||
});
|
||||
});
|
||||
}
|
||||
serviceWorker.getRegistration().then((sw) => {
|
||||
if (sw) sw.unregister();
|
||||
});
|
||||
|
||||
clearCache();
|
||||
}
|
||||
26
src/locales/en-US.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import component from './en-US/component';
|
||||
import globalHeader from './en-US/globalHeader';
|
||||
import menu from './en-US/menu';
|
||||
import pages from './en-US/pages';
|
||||
import pwa from './en-US/pwa';
|
||||
import settingDrawer from './en-US/settingDrawer';
|
||||
import settings from './en-US/settings';
|
||||
|
||||
export default {
|
||||
'login.welcome': 'Welcome use OpenIM',
|
||||
'navBar.lang': 'Languages',
|
||||
'layout.user.link.help': 'Help',
|
||||
'layout.user.link.privacy': 'Privacy',
|
||||
'layout.user.link.terms': 'Terms',
|
||||
'app.copyright.produced': 'Produced by Ant Financial Experience Department',
|
||||
'app.preview.down.block': 'Download this page to your local project',
|
||||
'app.welcome.link.fetch-blocks': 'Get all block',
|
||||
'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
|
||||
...globalHeader,
|
||||
...menu,
|
||||
...settingDrawer,
|
||||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
...pages,
|
||||
};
|
||||
5
src/locales/en-US/component.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'component.tagSelect.expand': 'Expand',
|
||||
'component.tagSelect.collapse': 'Collapse',
|
||||
'component.tagSelect.all': 'All',
|
||||
};
|
||||
17
src/locales/en-US/globalHeader.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
'component.globalHeader.search': 'Search',
|
||||
'component.globalHeader.search.example1': 'Search example 1',
|
||||
'component.globalHeader.search.example2': 'Search example 2',
|
||||
'component.globalHeader.search.example3': 'Search example 3',
|
||||
'component.globalHeader.help': 'Help',
|
||||
'component.globalHeader.notification': 'Notification',
|
||||
'component.globalHeader.notification.empty': 'You have viewed all notifications.',
|
||||
'component.globalHeader.message': 'Message',
|
||||
'component.globalHeader.message.empty': 'You have viewed all messsages.',
|
||||
'component.globalHeader.event': 'Event',
|
||||
'component.globalHeader.event.empty': 'You have viewed all events.',
|
||||
'component.noticeIcon.clear': 'Clear',
|
||||
'component.noticeIcon.cleared': 'Cleared',
|
||||
'component.noticeIcon.empty': 'No notifications',
|
||||
'component.noticeIcon.view-more': 'View more',
|
||||
};
|
||||
52
src/locales/en-US/menu.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
export default {
|
||||
'menu.welcome': 'Welcome',
|
||||
'menu.more-blocks': 'More Blocks',
|
||||
'menu.home': 'Home',
|
||||
'menu.admin': 'Admin',
|
||||
'menu.admin.sub-page': 'Sub-Page',
|
||||
'menu.login': 'Login',
|
||||
'menu.register': 'Register',
|
||||
'menu.register-result': 'Register Result',
|
||||
'menu.dashboard': 'Dashboard',
|
||||
'menu.dashboard.analysis': 'Analysis',
|
||||
'menu.dashboard.monitor': 'Monitor',
|
||||
'menu.dashboard.workplace': 'Workplace',
|
||||
'menu.exception.403': '403',
|
||||
'menu.exception.404': '404',
|
||||
'menu.exception.500': '500',
|
||||
'menu.form': 'Form',
|
||||
'menu.form.basic-form': 'Basic Form',
|
||||
'menu.form.step-form': 'Step Form',
|
||||
'menu.form.step-form.info': 'Step Form(write transfer information)',
|
||||
'menu.form.step-form.confirm': 'Step Form(confirm transfer information)',
|
||||
'menu.form.step-form.result': 'Step Form(finished)',
|
||||
'menu.form.advanced-form': 'Advanced Form',
|
||||
'menu.list': 'List',
|
||||
'menu.list.table-list': 'Search Table',
|
||||
'menu.list.basic-list': 'Basic List',
|
||||
'menu.list.card-list': 'Card List',
|
||||
'menu.list.search-list': 'Search List',
|
||||
'menu.list.search-list.articles': 'Search List(articles)',
|
||||
'menu.list.search-list.projects': 'Search List(projects)',
|
||||
'menu.list.search-list.applications': 'Search List(applications)',
|
||||
'menu.profile': 'Profile',
|
||||
'menu.profile.basic': 'Basic Profile',
|
||||
'menu.profile.advanced': 'Advanced Profile',
|
||||
'menu.result': 'Result',
|
||||
'menu.result.success': 'Success',
|
||||
'menu.result.fail': 'Fail',
|
||||
'menu.exception': 'Exception',
|
||||
'menu.exception.not-permission': '403',
|
||||
'menu.exception.not-find': '404',
|
||||
'menu.exception.server-error': '500',
|
||||
'menu.exception.trigger': 'Trigger',
|
||||
'menu.account': 'Account',
|
||||
'menu.account.center': 'Account Center',
|
||||
'menu.account.settings': 'Account Settings',
|
||||
'menu.account.trigger': 'Trigger Error',
|
||||
'menu.account.logout': 'Logout',
|
||||
'menu.editor': 'Graphic Editor',
|
||||
'menu.editor.flow': 'Flow Editor',
|
||||
'menu.editor.mind': 'Mind Editor',
|
||||
'menu.editor.koni': 'Koni Editor',
|
||||
};
|
||||
68
src/locales/en-US/pages.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
export default {
|
||||
'pages.layouts.userLayout.title':
|
||||
'Ant Design is the most influential web design specification in Xihu district',
|
||||
'pages.login.accountLogin.tab': 'Account Login',
|
||||
'pages.login.accountLogin.errorMessage': 'Incorrect username/password(admin/ant.design)',
|
||||
'pages.login.failure': 'Login failed, please try again!',
|
||||
'pages.login.success': 'Login successful!',
|
||||
'pages.login.username.placeholder': 'Username: admin or user',
|
||||
'pages.login.username.required': 'Please input your username!',
|
||||
'pages.login.password.placeholder': 'Password: ant.design',
|
||||
'pages.login.password.required': 'Please input your password!',
|
||||
'pages.login.phoneLogin.tab': 'Phone Login',
|
||||
'pages.login.phoneLogin.errorMessage': 'Verification Code Error',
|
||||
'pages.login.phoneNumber.placeholder': 'Phone Number',
|
||||
'pages.login.phoneNumber.required': 'Please input your phone number!',
|
||||
'pages.login.phoneNumber.invalid': 'Phone number is invalid!',
|
||||
'pages.login.captcha.placeholder': 'Verification Code',
|
||||
'pages.login.captcha.required': 'Please input verification code!',
|
||||
'pages.login.phoneLogin.getVerificationCode': 'Get Code',
|
||||
'pages.getCaptchaSecondText': 'sec(s)',
|
||||
'pages.login.rememberMe': 'Remember me',
|
||||
'pages.login.forgotPassword': 'Forgot Password ?',
|
||||
'pages.login.submit': 'Login',
|
||||
'pages.login.loginWith': 'Login with :',
|
||||
'pages.login.registerAccount': 'Register Account',
|
||||
'pages.welcome.link': 'Welcome',
|
||||
'pages.welcome.alertMessage': 'Faster and stronger heavy-duty components have been released.',
|
||||
'pages.admin.subPage.title': 'This page can only be viewed by Admin',
|
||||
'pages.admin.subPage.alertMessage':
|
||||
'Umi ui is now released, welcome to use npm run ui to start the experience.',
|
||||
'pages.searchTable.createForm.newRule': 'New Rule',
|
||||
'pages.searchTable.updateForm.ruleConfig': 'Rule configuration',
|
||||
'pages.searchTable.updateForm.basicConfig': 'Basic Information',
|
||||
'pages.searchTable.updateForm.ruleName.nameLabel': 'Rule Name',
|
||||
'pages.searchTable.updateForm.ruleName.nameRules': 'Please enter the rule name!',
|
||||
'pages.searchTable.updateForm.ruleDesc.descLabel': 'Rule Description',
|
||||
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': 'Please enter at least five characters',
|
||||
'pages.searchTable.updateForm.ruleDesc.descRules':
|
||||
'Please enter a rule description of at least five characters!',
|
||||
'pages.searchTable.updateForm.ruleProps.title': 'Configure Properties',
|
||||
'pages.searchTable.updateForm.object': 'Monitoring Object',
|
||||
'pages.searchTable.updateForm.ruleProps.templateLabel': 'Rule Template',
|
||||
'pages.searchTable.updateForm.ruleProps.typeLabel': 'Rule Type',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.title': 'Set Scheduling Period',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': 'Starting Time',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeRules': 'Please choose a start time!',
|
||||
'pages.searchTable.titleDesc': 'Description',
|
||||
'pages.searchTable.ruleName': 'Rule name is required',
|
||||
'pages.searchTable.titleCallNo': 'Number of Service Calls',
|
||||
'pages.searchTable.titleStatus': 'Status',
|
||||
'pages.searchTable.nameStatus.default': 'default',
|
||||
'pages.searchTable.nameStatus.running': 'running',
|
||||
'pages.searchTable.nameStatus.online': 'online',
|
||||
'pages.searchTable.nameStatus.abnormal': 'abnormal',
|
||||
'pages.searchTable.titleUpdatedAt': 'Last Scheduled at',
|
||||
'pages.searchTable.exception': 'Please enter the reason for the exception!',
|
||||
'pages.searchTable.titleOption': 'Option',
|
||||
'pages.searchTable.config': 'Configuration',
|
||||
'pages.searchTable.subscribeAlert': 'Subscribe to alerts',
|
||||
'pages.searchTable.title': 'Enquiry Form',
|
||||
'pages.searchTable.new': 'New',
|
||||
'pages.searchTable.chosen': 'chosen',
|
||||
'pages.searchTable.item': 'item',
|
||||
'pages.searchTable.totalServiceCalls': 'Total Number of Service Calls',
|
||||
'pages.searchTable.tenThousand': '0000',
|
||||
'pages.searchTable.batchDeletion': 'batch deletion',
|
||||
'pages.searchTable.batchApproval': 'batch approval',
|
||||
};
|
||||
6
src/locales/en-US/pwa.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
'app.pwa.offline': 'You are offline now',
|
||||
'app.pwa.serviceworker.updated': 'New content is available',
|
||||
'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
|
||||
'app.pwa.serviceworker.updated.ok': 'Refresh',
|
||||
};
|
||||
31
src/locales/en-US/settingDrawer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
'app.setting.pagestyle': 'Page style setting',
|
||||
'app.setting.pagestyle.dark': 'Dark style',
|
||||
'app.setting.pagestyle.light': 'Light style',
|
||||
'app.setting.content-width': 'Content Width',
|
||||
'app.setting.content-width.fixed': 'Fixed',
|
||||
'app.setting.content-width.fluid': 'Fluid',
|
||||
'app.setting.themecolor': 'Theme Color',
|
||||
'app.setting.themecolor.dust': 'Dust Red',
|
||||
'app.setting.themecolor.volcano': 'Volcano',
|
||||
'app.setting.themecolor.sunset': 'Sunset Orange',
|
||||
'app.setting.themecolor.cyan': 'Cyan',
|
||||
'app.setting.themecolor.green': 'Polar Green',
|
||||
'app.setting.themecolor.daybreak': 'Daybreak Blue (default)',
|
||||
'app.setting.themecolor.geekblue': 'Geek Glue',
|
||||
'app.setting.themecolor.purple': 'Golden Purple',
|
||||
'app.setting.navigationmode': 'Navigation Mode',
|
||||
'app.setting.sidemenu': 'Side Menu Layout',
|
||||
'app.setting.topmenu': 'Top Menu Layout',
|
||||
'app.setting.fixedheader': 'Fixed Header',
|
||||
'app.setting.fixedsidebar': 'Fixed Sidebar',
|
||||
'app.setting.fixedsidebar.hint': 'Works on Side Menu Layout',
|
||||
'app.setting.hideheader': 'Hidden Header when scrolling',
|
||||
'app.setting.hideheader.hint': 'Works when Hidden Header is enabled',
|
||||
'app.setting.othersettings': 'Other Settings',
|
||||
'app.setting.weakmode': 'Weak Mode',
|
||||
'app.setting.copy': 'Copy Setting',
|
||||
'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/models/setting.js',
|
||||
'app.setting.production.hint':
|
||||
'Setting panel shows in development environment only, please manually modify',
|
||||
};
|
||||
60
src/locales/en-US/settings.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
export default {
|
||||
'app.settings.menuMap.basic': 'Basic Settings',
|
||||
'app.settings.menuMap.security': 'Security Settings',
|
||||
'app.settings.menuMap.binding': 'Account Binding',
|
||||
'app.settings.menuMap.notification': 'New Message Notification',
|
||||
'app.settings.basic.avatar': 'Avatar',
|
||||
'app.settings.basic.change-avatar': 'Change avatar',
|
||||
'app.settings.basic.email': 'Email',
|
||||
'app.settings.basic.email-message': 'Please input your email!',
|
||||
'app.settings.basic.nickname': 'Nickname',
|
||||
'app.settings.basic.nickname-message': 'Please input your Nickname!',
|
||||
'app.settings.basic.profile': 'Personal profile',
|
||||
'app.settings.basic.profile-message': 'Please input your personal profile!',
|
||||
'app.settings.basic.profile-placeholder': 'Brief introduction to yourself',
|
||||
'app.settings.basic.country': 'Country/Region',
|
||||
'app.settings.basic.country-message': 'Please input your country!',
|
||||
'app.settings.basic.geographic': 'Province or city',
|
||||
'app.settings.basic.geographic-message': 'Please input your geographic info!',
|
||||
'app.settings.basic.address': 'Street Address',
|
||||
'app.settings.basic.address-message': 'Please input your address!',
|
||||
'app.settings.basic.phone': 'Phone Number',
|
||||
'app.settings.basic.phone-message': 'Please input your phone!',
|
||||
'app.settings.basic.update': 'Update Information',
|
||||
'app.settings.security.strong': 'Strong',
|
||||
'app.settings.security.medium': 'Medium',
|
||||
'app.settings.security.weak': 'Weak',
|
||||
'app.settings.security.password': 'Account Password',
|
||||
'app.settings.security.password-description': 'Current password strength',
|
||||
'app.settings.security.phone': 'Security Phone',
|
||||
'app.settings.security.phone-description': 'Bound phone',
|
||||
'app.settings.security.question': 'Security Question',
|
||||
'app.settings.security.question-description':
|
||||
'The security question is not set, and the security policy can effectively protect the account security',
|
||||
'app.settings.security.email': 'Backup Email',
|
||||
'app.settings.security.email-description': 'Bound Email',
|
||||
'app.settings.security.mfa': 'MFA Device',
|
||||
'app.settings.security.mfa-description':
|
||||
'Unbound MFA device, after binding, can be confirmed twice',
|
||||
'app.settings.security.modify': 'Modify',
|
||||
'app.settings.security.set': 'Set',
|
||||
'app.settings.security.bind': 'Bind',
|
||||
'app.settings.binding.taobao': 'Binding Taobao',
|
||||
'app.settings.binding.taobao-description': 'Currently unbound Taobao account',
|
||||
'app.settings.binding.alipay': 'Binding Alipay',
|
||||
'app.settings.binding.alipay-description': 'Currently unbound Alipay account',
|
||||
'app.settings.binding.dingding': 'Binding DingTalk',
|
||||
'app.settings.binding.dingding-description': 'Currently unbound DingTalk account',
|
||||
'app.settings.binding.bind': 'Bind',
|
||||
'app.settings.notification.password': 'Account Password',
|
||||
'app.settings.notification.password-description':
|
||||
'Messages from other users will be notified in the form of a station letter',
|
||||
'app.settings.notification.messages': 'System Messages',
|
||||
'app.settings.notification.messages-description':
|
||||
'System messages will be notified in the form of a station letter',
|
||||
'app.settings.notification.todo': 'To-do Notification',
|
||||
'app.settings.notification.todo-description':
|
||||
'The to-do list will be notified in the form of a letter from the station',
|
||||
'app.settings.open': 'Open',
|
||||
'app.settings.close': 'Close',
|
||||
};
|
||||
35
src/locales/zh-CN.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import component from './zh-CN/component';
|
||||
import globalHeader from './zh-CN/globalHeader';
|
||||
import menu from './zh-CN/menu';
|
||||
import pages from './zh-CN/pages';
|
||||
import pwa from './zh-CN/pwa';
|
||||
import settingDrawer from './zh-CN/settingDrawer';
|
||||
import settings from './zh-CN/settings';
|
||||
|
||||
export default {
|
||||
and: '和',
|
||||
login: '登录',
|
||||
'login.welcome': '欢迎使用OpenIM',
|
||||
'login.account': '账户',
|
||||
'login.account.required': '请输入账户!',
|
||||
'login.secrect': '密码',
|
||||
'login.secrect.required': '请输入密码!',
|
||||
'login.agreement': '我已阅读并同意',
|
||||
'login.agreement.service': '服务协议',
|
||||
'login.agreement.privacy': '隐私政策',
|
||||
'navBar.lang': '语言',
|
||||
'layout.user.link.help': '帮助',
|
||||
'layout.user.link.privacy': '隐私',
|
||||
'layout.user.link.terms': '条款',
|
||||
'app.copyright.produced': 'OpenIM后台管理系统',
|
||||
'app.preview.down.block': '下载此页面到本地项目',
|
||||
'app.welcome.link.fetch-blocks': '获取全部区块',
|
||||
'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
|
||||
...pages,
|
||||
...globalHeader,
|
||||
...menu,
|
||||
...settingDrawer,
|
||||
...settings,
|
||||
...pwa,
|
||||
...component,
|
||||
};
|
||||
5
src/locales/zh-CN/component.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
'component.tagSelect.expand': '展开',
|
||||
'component.tagSelect.collapse': '收起',
|
||||
'component.tagSelect.all': '全部',
|
||||
};
|
||||
17
src/locales/zh-CN/globalHeader.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export default {
|
||||
'component.globalHeader.search': '站内搜索',
|
||||
'component.globalHeader.search.example1': '搜索提示一',
|
||||
'component.globalHeader.search.example2': '搜索提示二',
|
||||
'component.globalHeader.search.example3': '搜索提示三',
|
||||
'component.globalHeader.help': '使用文档',
|
||||
'component.globalHeader.notification': '通知',
|
||||
'component.globalHeader.notification.empty': '你已查看所有通知',
|
||||
'component.globalHeader.message': '消息',
|
||||
'component.globalHeader.message.empty': '您已读完所有消息',
|
||||
'component.globalHeader.event': '待办',
|
||||
'component.globalHeader.event.empty': '你已完成所有待办',
|
||||
'component.noticeIcon.clear': '清空',
|
||||
'component.noticeIcon.cleared': '清空了',
|
||||
'component.noticeIcon.empty': '暂无数据',
|
||||
'component.noticeIcon.view-more': '查看更多',
|
||||
};
|
||||
96
src/locales/zh-CN/menu.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
export default {
|
||||
'menu.BusinessSystem': '业务系统',
|
||||
|
||||
'menu.BusinessSystem.Statistics': '数据统计',
|
||||
'menu.BusinessSystem.Statistics.UserStatistics': '用户统计',
|
||||
'menu.BusinessSystem.Statistics.MomentsStatistics': '工作圈统计',
|
||||
|
||||
'menu.BusinessSystem.UserManage': '用户管理',
|
||||
'menu.BusinessSystem.UserManage.UserList': '用户列表',
|
||||
'menu.BusinessSystem.UserManage.BlockList': '禁用列表',
|
||||
|
||||
'menu.BusinessSystem.RegisterManage': '注册管理',
|
||||
'menu.BusinessSystem.RegisterManage.InvitionCode': '邀请码',
|
||||
'menu.BusinessSystem.RegisterManage.DefualtFriends': '默认好友',
|
||||
'menu.BusinessSystem.RegisterManage.DefualtGroup': '默认群组',
|
||||
|
||||
'menu.BusinessSystem.AppManage': '应用管理',
|
||||
'menu.BusinessSystem.AppManage.DiscoveryPageManage': '登录全局配置项',
|
||||
'menu.BusinessSystem.AppManage.Workbench': '工作台',
|
||||
'menu.BusinessSystem.AppManage.InvitationPage': '邀请码页配置',
|
||||
|
||||
'menu.IMSyetem': 'IM系统',
|
||||
'menu.IMSyetem.UserManage': '用户管理',
|
||||
'menu.IMSyetem.UserManage.UserList': '用户列表',
|
||||
'menu.IMSyetem.UserManage.RelationList': '查看关系链',
|
||||
|
||||
'menu.IMSyetem.GroupManage': '群组管理',
|
||||
'menu.IMSyetem.GroupManage.GroupList': '群组列表',
|
||||
'menu.IMSyetem.GroupManage.GroupMember': '群成员列表',
|
||||
|
||||
'menu.IMSyetem.MessageManage': '消息管理',
|
||||
'menu.IMSyetem.MessageManage.UserMessage': '用户消息',
|
||||
'menu.IMSyetem.MessageManage.GroupMessage': '群组消息',
|
||||
'menu.IMSyetem.MessageManage.SendMessage': '系统通知',
|
||||
|
||||
'menu.RtcSystem': 'RTC系统',
|
||||
'menu.RtcSystem.Meeting': '视频会议',
|
||||
'menu.RtcSystem.VideoCall': '音视频通话',
|
||||
|
||||
'menu.Profile': '设置',
|
||||
'menu.Profile.AdminInfo': '个人信息',
|
||||
'menu.Profile.AdminPassword': '修改密码',
|
||||
|
||||
'menu.welcome': '欢迎',
|
||||
'menu.more-blocks': '更多区块',
|
||||
'menu.home': '首页',
|
||||
'menu.admin': '管理页',
|
||||
'menu.admin.sub-page': '二级管理页',
|
||||
'menu.login': '登录',
|
||||
'menu.register': '注册',
|
||||
'menu.register-result': '注册结果',
|
||||
'menu.dashboard': 'Dashboard',
|
||||
'menu.dashboard.analysis': '分析页',
|
||||
'menu.dashboard.monitor': '监控页',
|
||||
'menu.dashboard.workplace': '工作台',
|
||||
'menu.exception.403': '403',
|
||||
'menu.exception.404': '404',
|
||||
'menu.exception.500': '500',
|
||||
'menu.form': '表单页',
|
||||
'menu.form.basic-form': '基础表单',
|
||||
'menu.form.step-form': '分步表单',
|
||||
'menu.form.step-form.info': '分步表单(填写转账信息)',
|
||||
'menu.form.step-form.confirm': '分步表单(确认转账信息)',
|
||||
'menu.form.step-form.result': '分步表单(完成)',
|
||||
'menu.form.advanced-form': '高级表单',
|
||||
'menu.list': '列表页',
|
||||
'menu.list.table-list': '查询表格',
|
||||
'menu.list.basic-list': '标准列表',
|
||||
'menu.list.card-list': '卡片列表',
|
||||
'menu.list.search-list': '搜索列表',
|
||||
'menu.list.search-list.articles': '搜索列表(文章)',
|
||||
'menu.list.search-list.projects': '搜索列表(项目)',
|
||||
'menu.list.search-list.applications': '搜索列表(应用)',
|
||||
'menu.profile': '详情页',
|
||||
'menu.profile.basic': '基础详情页',
|
||||
'menu.profile.advanced': '高级详情页',
|
||||
'menu.result': '结果页',
|
||||
'menu.result.success': '成功页',
|
||||
'menu.result.fail': '失败页',
|
||||
'menu.exception': '异常页',
|
||||
'menu.exception.not-permission': '403',
|
||||
'menu.exception.not-find': '404',
|
||||
'menu.exception.server-error': '500',
|
||||
'menu.exception.trigger': '触发错误',
|
||||
'menu.account': '个人页',
|
||||
'menu.account.center': '个人中心',
|
||||
'menu.account.settings': '个人设置',
|
||||
'menu.account.trigger': '触发报错',
|
||||
'menu.account.logout': '退出登录',
|
||||
'menu.editor': '图形编辑器',
|
||||
'menu.editor.flow': '流程编辑器',
|
||||
'menu.editor.mind': '脑图编辑器',
|
||||
'menu.editor.koni': '拓扑编辑器',
|
||||
'menu.UserManage': '用户管理',
|
||||
'menu.UserManage.UserList': '用户列表',
|
||||
};
|
||||
65
src/locales/zh-CN/pages.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
export default {
|
||||
'pages.layouts.userLayout.title': 'Ant Design 是西湖区最具影响力的 Web 设计规范',
|
||||
'pages.login.accountLogin.tab': '账户密码登录',
|
||||
'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)',
|
||||
'pages.login.failure': '登录失败,请重试!',
|
||||
'pages.login.success': '登录成功!',
|
||||
'pages.login.username.placeholder': '用户名: admin or user',
|
||||
'pages.login.username.required': '用户名是必填项!',
|
||||
'pages.login.password.placeholder': '密码: ant.design',
|
||||
'pages.login.password.required': '密码是必填项!',
|
||||
'pages.login.phoneLogin.tab': '手机号登录',
|
||||
'pages.login.phoneLogin.errorMessage': '验证码错误',
|
||||
'pages.login.phoneNumber.placeholder': '请输入手机号!',
|
||||
'pages.login.phoneNumber.required': '手机号是必填项!',
|
||||
'pages.login.phoneNumber.invalid': '不合法的手机号!',
|
||||
'pages.login.captcha.placeholder': '请输入验证码!',
|
||||
'pages.login.captcha.required': '验证码是必填项!',
|
||||
'pages.login.phoneLogin.getVerificationCode': '获取验证码',
|
||||
'pages.getCaptchaSecondText': '秒后重新获取',
|
||||
'pages.login.rememberMe': '自动登录',
|
||||
'pages.login.forgotPassword': '忘记密码 ?',
|
||||
'pages.login.submit': '登录',
|
||||
'pages.login.loginWith': '其他登录方式 :',
|
||||
'pages.login.registerAccount': '注册账户',
|
||||
'pages.welcome.link': '欢迎使用',
|
||||
'pages.welcome.alertMessage': '更快更强的重型组件,已经发布。',
|
||||
'pages.admin.subPage.title': ' 这个页面只有 admin 权限才能查看',
|
||||
'pages.admin.subPage.alertMessage': 'umi ui 现已发布,欢迎使用 npm run ui 启动体验。',
|
||||
'pages.searchTable.createForm.newRule': '新建规则',
|
||||
'pages.searchTable.updateForm.ruleConfig': '规则配置',
|
||||
'pages.searchTable.updateForm.basicConfig': '基本信息',
|
||||
'pages.searchTable.updateForm.ruleName.nameLabel': '规则名称',
|
||||
'pages.searchTable.updateForm.ruleName.nameRules': '请输入规则名称!',
|
||||
'pages.searchTable.updateForm.ruleDesc.descLabel': '规则描述',
|
||||
'pages.searchTable.updateForm.ruleDesc.descPlaceholder': '请输入至少五个字符',
|
||||
'pages.searchTable.updateForm.ruleDesc.descRules': '请输入至少五个字符的规则描述!',
|
||||
'pages.searchTable.updateForm.ruleProps.title': '配置规则属性',
|
||||
'pages.searchTable.updateForm.object': '监控对象',
|
||||
'pages.searchTable.updateForm.ruleProps.templateLabel': '规则模板',
|
||||
'pages.searchTable.updateForm.ruleProps.typeLabel': '规则类型',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.title': '设定调度周期',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeLabel': '开始时间',
|
||||
'pages.searchTable.updateForm.schedulingPeriod.timeRules': '请选择开始时间!',
|
||||
'pages.searchTable.titleDesc': '描述',
|
||||
'pages.searchTable.ruleName': '规则名称为必填项',
|
||||
'pages.searchTable.titleCallNo': '服务调用次数',
|
||||
'pages.searchTable.titleStatus': '状态',
|
||||
'pages.searchTable.nameStatus.default': '关闭',
|
||||
'pages.searchTable.nameStatus.running': '运行中',
|
||||
'pages.searchTable.nameStatus.online': '已上线',
|
||||
'pages.searchTable.nameStatus.abnormal': '异常',
|
||||
'pages.searchTable.titleUpdatedAt': '上次调度时间',
|
||||
'pages.searchTable.exception': '请输入异常原因!',
|
||||
'pages.searchTable.titleOption': '操作',
|
||||
'pages.searchTable.config': '配置',
|
||||
'pages.searchTable.subscribeAlert': '订阅警报',
|
||||
'pages.searchTable.title': '查询表格',
|
||||
'pages.searchTable.new': '新建',
|
||||
'pages.searchTable.chosen': '已选择',
|
||||
'pages.searchTable.item': '项',
|
||||
'pages.searchTable.totalServiceCalls': '服务调用次数总计',
|
||||
'pages.searchTable.tenThousand': '万',
|
||||
'pages.searchTable.batchDeletion': '批量删除',
|
||||
'pages.searchTable.batchApproval': '批量审批',
|
||||
};
|
||||
6
src/locales/zh-CN/pwa.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
'app.pwa.offline': '当前处于离线状态',
|
||||
'app.pwa.serviceworker.updated': '有新内容',
|
||||
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
|
||||
'app.pwa.serviceworker.updated.ok': '刷新',
|
||||
};
|
||||
31
src/locales/zh-CN/settingDrawer.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
'app.setting.pagestyle': '整体风格设置',
|
||||
'app.setting.pagestyle.dark': '暗色菜单风格',
|
||||
'app.setting.pagestyle.light': '亮色菜单风格',
|
||||
'app.setting.content-width': '内容区域宽度',
|
||||
'app.setting.content-width.fixed': '定宽',
|
||||
'app.setting.content-width.fluid': '流式',
|
||||
'app.setting.themecolor': '主题色',
|
||||
'app.setting.themecolor.dust': '薄暮',
|
||||
'app.setting.themecolor.volcano': '火山',
|
||||
'app.setting.themecolor.sunset': '日暮',
|
||||
'app.setting.themecolor.cyan': '明青',
|
||||
'app.setting.themecolor.green': '极光绿',
|
||||
'app.setting.themecolor.daybreak': '拂晓蓝(默认)',
|
||||
'app.setting.themecolor.geekblue': '极客蓝',
|
||||
'app.setting.themecolor.purple': '酱紫',
|
||||
'app.setting.navigationmode': '导航模式',
|
||||
'app.setting.sidemenu': '侧边菜单布局',
|
||||
'app.setting.topmenu': '顶部菜单布局',
|
||||
'app.setting.fixedheader': '固定 Header',
|
||||
'app.setting.fixedsidebar': '固定侧边菜单',
|
||||
'app.setting.fixedsidebar.hint': '侧边菜单布局时可配置',
|
||||
'app.setting.hideheader': '下滑时隐藏 Header',
|
||||
'app.setting.hideheader.hint': '固定 Header 时可配置',
|
||||
'app.setting.othersettings': '其他设置',
|
||||
'app.setting.weakmode': '色弱模式',
|
||||
'app.setting.copy': '拷贝设置',
|
||||
'app.setting.copyinfo': '拷贝成功,请到 config/defaultSettings.js 中替换默认配置',
|
||||
'app.setting.production.hint':
|
||||
'配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件',
|
||||
};
|
||||
55
src/locales/zh-CN/settings.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
export default {
|
||||
'app.settings.menuMap.basic': '基本设置',
|
||||
'app.settings.menuMap.security': '安全设置',
|
||||
'app.settings.menuMap.binding': '账号绑定',
|
||||
'app.settings.menuMap.notification': '新消息通知',
|
||||
'app.settings.basic.avatar': '头像',
|
||||
'app.settings.basic.change-avatar': '更换头像',
|
||||
'app.settings.basic.email': '邮箱',
|
||||
'app.settings.basic.email-message': '请输入您的邮箱!',
|
||||
'app.settings.basic.nickname': '昵称',
|
||||
'app.settings.basic.nickname-message': '请输入您的昵称!',
|
||||
'app.settings.basic.profile': '个人简介',
|
||||
'app.settings.basic.profile-message': '请输入个人简介!',
|
||||
'app.settings.basic.profile-placeholder': '个人简介',
|
||||
'app.settings.basic.country': '国家/地区',
|
||||
'app.settings.basic.country-message': '请输入您的国家或地区!',
|
||||
'app.settings.basic.geographic': '所在省市',
|
||||
'app.settings.basic.geographic-message': '请输入您的所在省市!',
|
||||
'app.settings.basic.address': '街道地址',
|
||||
'app.settings.basic.address-message': '请输入您的街道地址!',
|
||||
'app.settings.basic.phone': '联系电话',
|
||||
'app.settings.basic.phone-message': '请输入您的联系电话!',
|
||||
'app.settings.basic.update': '更新基本信息',
|
||||
'app.settings.security.strong': '强',
|
||||
'app.settings.security.medium': '中',
|
||||
'app.settings.security.weak': '弱',
|
||||
'app.settings.security.password': '账户密码',
|
||||
'app.settings.security.password-description': '当前密码强度',
|
||||
'app.settings.security.phone': '密保手机',
|
||||
'app.settings.security.phone-description': '已绑定手机',
|
||||
'app.settings.security.question': '密保问题',
|
||||
'app.settings.security.question-description': '未设置密保问题,密保问题可有效保护账户安全',
|
||||
'app.settings.security.email': '备用邮箱',
|
||||
'app.settings.security.email-description': '已绑定邮箱',
|
||||
'app.settings.security.mfa': 'MFA 设备',
|
||||
'app.settings.security.mfa-description': '未绑定 MFA 设备,绑定后,可以进行二次确认',
|
||||
'app.settings.security.modify': '修改',
|
||||
'app.settings.security.set': '设置',
|
||||
'app.settings.security.bind': '绑定',
|
||||
'app.settings.binding.taobao': '绑定淘宝',
|
||||
'app.settings.binding.taobao-description': '当前未绑定淘宝账号',
|
||||
'app.settings.binding.alipay': '绑定支付宝',
|
||||
'app.settings.binding.alipay-description': '当前未绑定支付宝账号',
|
||||
'app.settings.binding.dingding': '绑定钉钉',
|
||||
'app.settings.binding.dingding-description': '当前未绑定钉钉账号',
|
||||
'app.settings.binding.bind': '绑定',
|
||||
'app.settings.notification.password': '账户密码',
|
||||
'app.settings.notification.password-description': '其他用户的消息将以站内信的形式通知',
|
||||
'app.settings.notification.messages': '系统消息',
|
||||
'app.settings.notification.messages-description': '系统消息将以站内信的形式通知',
|
||||
'app.settings.notification.todo': '待办任务',
|
||||
'app.settings.notification.todo-description': '待办任务将以站内信的形式通知',
|
||||
'app.settings.open': '开',
|
||||
'app.settings.close': '关',
|
||||
};
|
||||
22
src/manifest.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "Ant Design Pro",
|
||||
"short_name": "Ant Design Pro",
|
||||
"display": "standalone",
|
||||
"start_url": "./?utm_source=homescreen",
|
||||
"theme_color": "#002140",
|
||||
"background_color": "#001529",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icons/icon-192x192.png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-128x128.png",
|
||||
"sizes": "128x128"
|
||||
},
|
||||
{
|
||||
"src": "icons/icon-512x512.png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
]
|
||||
}
|
||||
18
src/pages/404.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { history } from '@umijs/max';
|
||||
import { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const NoFoundPage: React.FC = () => (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle="Sorry, the page you visited does not exist."
|
||||
extra={
|
||||
<Button type="primary" onClick={() => history.push('/')}>
|
||||
Back Home
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
export default NoFoundPage;
|
||||
67
src/pages/business_system/app_manage/AppModal.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { crteateApplet } from '@/services/server/app_manage';
|
||||
import { PlusOutlined, UploadOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Modal, Upload } from 'antd';
|
||||
|
||||
type AppModalProps = {
|
||||
isOpen: boolean;
|
||||
isCreate: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
};
|
||||
|
||||
const AppModal = ({ isOpen, isCreate, setIsOpen }: AppModalProps) => {
|
||||
console.log(isCreate);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const onFinish = async (e: any) => {
|
||||
console.log(e);
|
||||
await crteateApplet({ ...e, id: '' });
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal title="应用信息" open={isOpen} footer={null} onCancel={handleCancel}>
|
||||
<Form
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 14 }}
|
||||
layout="horizontal"
|
||||
form={form}
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item label="AppID" name="appID">
|
||||
<Input placeholder="openim" />
|
||||
</Form.Item>
|
||||
<Form.Item label="应用名称" name="name">
|
||||
<Input placeholder="openim" />
|
||||
</Form.Item>
|
||||
<Form.Item label="应用描述">
|
||||
<Input placeholder="即时通讯" />
|
||||
</Form.Item>
|
||||
<Form.Item label="应用版本" name="version">
|
||||
<Input placeholder="1.1.1" />
|
||||
</Form.Item>
|
||||
<Form.Item label="应用图标" valuePropName="fileList" name="icon">
|
||||
<Upload action="/upload.do" listType="picture-card">
|
||||
<div>
|
||||
<PlusOutlined />
|
||||
</div>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
<Form.Item label="应用文件" valuePropName="fileList">
|
||||
<Upload>
|
||||
<Button icon={<UploadOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</Form.Item>
|
||||
<Form.Item wrapperCol={{ span: 14, offset: 4 }}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
上传
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppModal;
|
||||
134
src/pages/business_system/app_manage/DiscoveryPageManage.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { getClientConfig, setClientConfig } from '@/services/server/app_manage';
|
||||
import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Button, Checkbox, Input, message, Space, Tooltip } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface AppConfig {
|
||||
discoverPageURL?: string;
|
||||
allowSendMsgNotFriend?: string;
|
||||
needInvitationCodeRegister?: string;
|
||||
}
|
||||
|
||||
const ConfigManage = () => {
|
||||
const [appConfig, setAppConfig] = useState<AppConfig>({
|
||||
discoverPageURL: '',
|
||||
});
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const orzConfig = useRef<AppConfig>();
|
||||
|
||||
const getConfig = () => {
|
||||
getClientConfig().then((res: any) => {
|
||||
if (!res.data.config) return;
|
||||
setAppConfig(res.data.config);
|
||||
console.log(res.data);
|
||||
orzConfig.current = res.data;
|
||||
// setAppConfig(res.data);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log(121);
|
||||
getConfig();
|
||||
return () => {};
|
||||
}, []);
|
||||
|
||||
const updateConfig = () => {
|
||||
if (orzConfig.current?.discoverPageURL === appConfig.discoverPageURL) {
|
||||
return;
|
||||
}
|
||||
setClientConfig({
|
||||
discoverPageURL: appConfig.discoverPageURL,
|
||||
allowSendMsgNotFriend: appConfig.allowSendMsgNotFriend,
|
||||
needInvitationCodeRegister: appConfig.needInvitationCodeRegister,
|
||||
})
|
||||
.then(() => {
|
||||
message.success('设置成功!');
|
||||
getClientConfig();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('设置失败!');
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<div>
|
||||
<div className="text-base font-medium mb-4">APP发现页配置</div>
|
||||
<Space.Compact>
|
||||
<Input
|
||||
style={{ width: '300px' }}
|
||||
value={appConfig.discoverPageURL}
|
||||
onChange={(e) => {
|
||||
setAppConfig({
|
||||
discoverPageURL: e.target.value,
|
||||
});
|
||||
}}
|
||||
suffix={
|
||||
<Tooltip title="URL需配置https/http前缀">
|
||||
<InfoCircleOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
|
||||
</Tooltip>
|
||||
}
|
||||
placeholder="URL需配置https/http前缀"
|
||||
/>
|
||||
<Button onClick={updateConfig} type="primary">
|
||||
保存
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</div>
|
||||
|
||||
<div className=" mt-[2rem]">
|
||||
<div className="text-base font-medium mb-6 py-5 px-5 bg-white">
|
||||
<Checkbox
|
||||
disabled={loading}
|
||||
checked={appConfig?.allowSendMsgNotFriend === '1' ? true : false}
|
||||
onChange={(e) => {
|
||||
console.log(e);
|
||||
setLoading(true);
|
||||
setClientConfig({
|
||||
allowSendMsgNotFriend: e.target.checked ? '1' : '0',
|
||||
})
|
||||
.then(() => {
|
||||
message.success('设置成功!');
|
||||
getConfig();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('设置失败!');
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}}
|
||||
>
|
||||
非好友是否允许发送消息
|
||||
<div className=" text-xs text-gray-400">*用户需要修改服务端配置文件并重启</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-base font-medium mb-6 py-5 px-5 bg-white">
|
||||
<Checkbox
|
||||
className="needInvitationCodeRegister"
|
||||
disabled={loading}
|
||||
checked={appConfig?.needInvitationCodeRegister === '1' ? true : false}
|
||||
onChange={(e) => {
|
||||
setLoading(true);
|
||||
setClientConfig({
|
||||
needInvitationCodeRegister: e.target.checked ? '1' : '0',
|
||||
})
|
||||
.then(() => {
|
||||
message.success('设置成功!');
|
||||
getConfig();
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('设置失败!');
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}}
|
||||
>
|
||||
是否需要邀请码才能注册
|
||||
</Checkbox>
|
||||
</div>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigManage;
|
||||
68
src/pages/business_system/app_manage/InvitationPage.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getClientConfig, setClientConfig } from '@/services/server/app_manage';
|
||||
import { PageContainer } from '@ant-design/pro-components';
|
||||
import { Button, Input, message, Space } from 'antd';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface AppConfig {
|
||||
adminURL: string;
|
||||
}
|
||||
|
||||
const ConfigManage = () => {
|
||||
const [appConfig, setAppConfig] = useState<AppConfig>({
|
||||
adminURL: '',
|
||||
});
|
||||
|
||||
const orzConfig = useRef<AppConfig>();
|
||||
|
||||
const getConfig = () => {
|
||||
getClientConfig().then((res) => {
|
||||
if (!res.data.config) return;
|
||||
setAppConfig(res.data.config);
|
||||
orzConfig.current = res.data;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getConfig();
|
||||
|
||||
return () => {};
|
||||
}, []);
|
||||
|
||||
const updateConfig = () => {
|
||||
if (orzConfig.current?.adminURL === appConfig.adminURL) {
|
||||
return;
|
||||
}
|
||||
setClientConfig({
|
||||
adminURL: appConfig.adminURL,
|
||||
})
|
||||
.then(() => {
|
||||
message.success('设置成功!');
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('设置失败!');
|
||||
getClientConfig();
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<div className="text-base font-medium mb-4">邀请码页配置</div>
|
||||
<Space.Compact>
|
||||
<Input
|
||||
style={{ width: '300px' }}
|
||||
value={appConfig.adminURL}
|
||||
onChange={(e) =>
|
||||
setAppConfig({
|
||||
adminURL: e.target.value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<Button onClick={updateConfig} type="primary">
|
||||
保存
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfigManage;
|
||||
106
src/pages/business_system/app_manage/Workbench.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import { getApplet } from '@/services/server/app_manage';
|
||||
import { ActionType, PageContainer, ProColumns, ProTable } from '@ant-design/pro-components';
|
||||
import { Button } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import AppModal from './AppModal';
|
||||
|
||||
const Workbench = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const columns: ProColumns<applet>[] = [
|
||||
{
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
width: 48,
|
||||
},
|
||||
{
|
||||
title: '应用名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'icon',
|
||||
dataIndex: 'icon',
|
||||
key: 'icon',
|
||||
valueType: 'avatar',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '版本',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'APPID',
|
||||
key: 'appID',
|
||||
dataIndex: 'appID',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
key: 'option',
|
||||
render: (text, record, _, action) => (
|
||||
<a
|
||||
key="editable"
|
||||
onClick={() => {
|
||||
action?.startEditable?.(record.id);
|
||||
}}
|
||||
>
|
||||
移除
|
||||
</a>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<AppModal isOpen={isOpen} isCreate={true} setIsOpen={setIsOpen} />
|
||||
|
||||
<ProTable
|
||||
columns={columns}
|
||||
actionRef={actionRef}
|
||||
cardBordered
|
||||
request={async (params = {}, sort, filter) => {
|
||||
console.log(params, sort, filter);
|
||||
const { data } = await getApplet({
|
||||
keyword: params.name ?? '',
|
||||
pagination: {
|
||||
pageNumber: (params.current as number) - 1,
|
||||
showNumber: params.pageSize as number,
|
||||
},
|
||||
});
|
||||
return {
|
||||
data: data.applets,
|
||||
success: true,
|
||||
total: data.total,
|
||||
};
|
||||
}}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
}}
|
||||
rowKey="id"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
onChange: (page) => console.log(page),
|
||||
}}
|
||||
dateFormatter="string"
|
||||
toolBarRender={() => [
|
||||
<Button key="button" onClick={() => {}} type="primary">
|
||||
新建
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default Workbench;
|
||||
13
src/pages/business_system/app_manage/data.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
type applet = {
|
||||
appID: string;
|
||||
createTime: number;
|
||||
icon: string;
|
||||
id: string;
|
||||
md5: string;
|
||||
name: string;
|
||||
priority: number;
|
||||
size: number;
|
||||
status: number;
|
||||
url: string;
|
||||
version: string;
|
||||
};
|
||||
5
src/pages/business_system/moments_manage/MomentsList.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
const MomentsManage = () => {
|
||||
return <div>MomentsManage</div>;
|
||||
};
|
||||
|
||||
export default MomentsManage;
|
||||
148
src/pages/business_system/register_manage/DefualtFriends.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import {
|
||||
addDefaultFriends,
|
||||
getDefaultFriends,
|
||||
removeDefaultFriends,
|
||||
} from '@/services/server/register_manage';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { PageContainer, ProTable } from '@ant-design/pro-components';
|
||||
import { Button, message, Popconfirm } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { DefualtFriendItem } from './data';
|
||||
import SelectUserModal, { SelectedListItem, SelectModalOptions } from './SelectUserModal';
|
||||
|
||||
const columns: ProColumns<DefualtFriendItem>[] = [
|
||||
{
|
||||
key: 'index',
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '账号ID',
|
||||
dataIndex: 'userID',
|
||||
key: 'userID',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '账号名称',
|
||||
key: 'nickname',
|
||||
hideInSearch: true,
|
||||
render: (text, record) => <span>{record.user.nickname}</span>,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
key: 'option',
|
||||
align: 'center',
|
||||
render: (text, record, _, action) => (
|
||||
<Popconfirm
|
||||
key={'delect'}
|
||||
title="确定要移除该用户吗?"
|
||||
onConfirm={() => {
|
||||
removeDefaultFriends([record.userID]).then(() => {
|
||||
action?.reload();
|
||||
});
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a href="#">移除</a>
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const DefualtFriends = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [selectModalOptions, setSelectModalOptions] = useState<SelectModalOptions>({
|
||||
visible: false,
|
||||
selectType: 'member',
|
||||
});
|
||||
|
||||
const closeSelectModal = () => {
|
||||
setSelectModalOptions({
|
||||
visible: false,
|
||||
selectType: 'member',
|
||||
});
|
||||
};
|
||||
|
||||
const selectedCallBack = async (data: SelectedListItem) => {
|
||||
const tmpArr = data.data.map((user) => user.userID);
|
||||
try {
|
||||
await addDefaultFriends(tmpArr);
|
||||
actionRef.current?.reload();
|
||||
message.success('新增成功!');
|
||||
} catch (error) {
|
||||
message.error('新增失败!');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<DefualtFriendItem>
|
||||
columns={columns}
|
||||
actionRef={actionRef}
|
||||
cardBordered
|
||||
columnsState={{
|
||||
defaultValue: {
|
||||
option: {
|
||||
fixed: 'right',
|
||||
},
|
||||
},
|
||||
}}
|
||||
request={async (params = {}, sort, filter) => {
|
||||
console.log(params, sort, filter);
|
||||
const { data } = await getDefaultFriends(
|
||||
params.current ?? 1,
|
||||
params.pageSize ?? 10,
|
||||
params.userID,
|
||||
);
|
||||
// const tmpData = data.chat_logs ?? [];
|
||||
return {
|
||||
data: data.users,
|
||||
success: true,
|
||||
total: data.total,
|
||||
};
|
||||
}}
|
||||
rowKey="userID"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
onChange: (page) => console.log(page),
|
||||
}}
|
||||
dateFormatter="string"
|
||||
scroll={{ x: 'max-content' }}
|
||||
toolbar={{
|
||||
actions: [
|
||||
<Button
|
||||
key={'add'}
|
||||
type="primary"
|
||||
// disabled
|
||||
onClick={() => {
|
||||
setSelectModalOptions({
|
||||
visible: true,
|
||||
selectType: 'member',
|
||||
});
|
||||
}}
|
||||
>
|
||||
新增
|
||||
</Button>,
|
||||
],
|
||||
settings: [],
|
||||
}}
|
||||
/>
|
||||
<SelectUserModal
|
||||
selectModalOptions={selectModalOptions}
|
||||
selectedCallBack={selectedCallBack}
|
||||
closeSelectModal={closeSelectModal}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefualtFriends;
|
||||
165
src/pages/business_system/register_manage/DefualtGroup.tsx
Normal file
@@ -0,0 +1,165 @@
|
||||
import {
|
||||
addDefaultGroup,
|
||||
getuserregistergroupidlist,
|
||||
removeDefaultGroup,
|
||||
} from '@/services/server/register_manage';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { PageContainer, ProTable } from '@ant-design/pro-components';
|
||||
import { Button, Input, message, Popconfirm } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { DefualtGroupItem } from './data';
|
||||
|
||||
const DefualtGroups = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [addInputval, setaddInputval] = useState<string>();
|
||||
|
||||
const columns: ProColumns<DefualtGroupItem>[] = [
|
||||
{
|
||||
title: '序号',
|
||||
key: 'index',
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
width: 88,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '群组ID',
|
||||
dataIndex: 'groupID',
|
||||
key: 'groupID',
|
||||
align: 'center',
|
||||
// formItemProps: (form, config) => {
|
||||
// return {
|
||||
// ...config,
|
||||
// label: '群组ID',
|
||||
// name: 'groupID',
|
||||
// style: { marginBottom: 0 },
|
||||
// };
|
||||
// },
|
||||
renderFormItem: () => {
|
||||
return (
|
||||
<Input
|
||||
value={addInputval}
|
||||
onChange={(e) => {
|
||||
let val = e.target.value;
|
||||
setaddInputval(val);
|
||||
}}
|
||||
// className={styles.search_input}
|
||||
placeholder="输入群组ID"
|
||||
bordered={true}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '群组名称',
|
||||
key: 'groupName',
|
||||
dataIndex: 'groupName',
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => <span>{record.groupName}</span>,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
key: 'option',
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<Popconfirm
|
||||
key={'remove'}
|
||||
title="确定要移除该群组吗?"
|
||||
onConfirm={async () => {
|
||||
try {
|
||||
let val = [record.groupID];
|
||||
await removeDefaultGroup(val);
|
||||
actionRef.current?.reload();
|
||||
message.success('删除成功!');
|
||||
} catch (error) {
|
||||
message.error('删除失败!');
|
||||
}
|
||||
}}
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
>
|
||||
<a
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
href="#"
|
||||
>
|
||||
移除
|
||||
</a>
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<DefualtGroupItem>
|
||||
columns={columns}
|
||||
actionRef={actionRef}
|
||||
cardBordered
|
||||
columnsState={{
|
||||
defaultValue: {
|
||||
option: {
|
||||
fixed: 'right',
|
||||
},
|
||||
},
|
||||
}}
|
||||
request={async (params = {}, sort, filter) => {
|
||||
console.log(params, sort, filter);
|
||||
const { data } = await getuserregistergroupidlist(
|
||||
params.current ?? 1,
|
||||
params.pageSize ?? 10,
|
||||
);
|
||||
return {
|
||||
data: data.groups,
|
||||
success: true,
|
||||
total: data.total,
|
||||
};
|
||||
}}
|
||||
rowKey="groupID"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
searchText: '添加',
|
||||
optionRender: () => [
|
||||
<Button
|
||||
key={'add'}
|
||||
onClick={async () => {
|
||||
if (!addInputval) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let groupIDList = [];
|
||||
groupIDList.push(addInputval);
|
||||
await addDefaultGroup(groupIDList);
|
||||
setaddInputval('');
|
||||
actionRef.current?.reload();
|
||||
message.success('新增成功!');
|
||||
} catch (error) {
|
||||
message.error('新增失败!');
|
||||
}
|
||||
}}
|
||||
className=" "
|
||||
type="primary"
|
||||
>
|
||||
添加
|
||||
</Button>,
|
||||
],
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
onChange: (page) => console.log(page),
|
||||
}}
|
||||
dateFormatter="string"
|
||||
scroll={{ x: 'max-content' }}
|
||||
toolbar={{
|
||||
actions: [],
|
||||
settings: [],
|
||||
}}
|
||||
/>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefualtGroups;
|
||||
167
src/pages/business_system/register_manage/InvitionCode.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import { generateInvitationCode, getInvitationCode } from '@/services/server/register_manage';
|
||||
import { copy2Text } from '@/utils/common';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { PageContainer, ProTable } from '@ant-design/pro-components';
|
||||
import { Button, InputNumber, message, Modal } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { InvitationCodeItem } from './data';
|
||||
|
||||
const columns: ProColumns<InvitationCodeItem>[] = [
|
||||
{
|
||||
key: 'index',
|
||||
title: '序号',
|
||||
dataIndex: 'index',
|
||||
valueType: 'indexBorder',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '邀请码',
|
||||
dataIndex: 'invitationCode',
|
||||
key: 'invitationCode',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '使用人昵称',
|
||||
key: 'usedUserName',
|
||||
dataIndex: 'usedUserName',
|
||||
valueType: 'select',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '使用状态',
|
||||
key: 'status',
|
||||
dataIndex: 'status',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
initialValue: '全部',
|
||||
valueEnum: {
|
||||
2: '未使用',
|
||||
1: '已使用',
|
||||
0: '全部',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '使用人手机号',
|
||||
key: 'usedUserPhoneNumber',
|
||||
dataIndex: 'usedUserPhoneNumber',
|
||||
valueType: 'select',
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
key: 'option',
|
||||
render: (text, record) => {
|
||||
return [
|
||||
<a href="#" key="copy" onClick={() => copy2Text(record.invitationCode)}>
|
||||
复制
|
||||
</a>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const InvitionCode = () => {
|
||||
const actionRef = useRef<ActionType>();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [genNumber, setGenNumber] = useState(10);
|
||||
|
||||
const showModal = () => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const genCode = () => {
|
||||
generateInvitationCode(genNumber).then(() => {
|
||||
message.success('生成成功!');
|
||||
actionRef.current?.reload();
|
||||
});
|
||||
// .catch(() => message.error('生成失败!'));
|
||||
};
|
||||
|
||||
const handleOk = () => {
|
||||
setIsModalOpen(false);
|
||||
genCode();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<ProTable<InvitationCodeItem>
|
||||
columns={columns}
|
||||
actionRef={actionRef}
|
||||
cardBordered
|
||||
columnsState={{
|
||||
defaultValue: {
|
||||
option: {
|
||||
fixed: 'right',
|
||||
},
|
||||
},
|
||||
}}
|
||||
request={async (params = {}, sort, filter) => {
|
||||
console.log(params, sort, filter);
|
||||
if (!params.status) {
|
||||
params.status = 0;
|
||||
}
|
||||
|
||||
if (params.invitationCode) {
|
||||
params.keyword = params.invitationCode;
|
||||
}
|
||||
|
||||
const { data } = await getInvitationCode({
|
||||
...(params as any),
|
||||
status: Number(params.status) ?? 0,
|
||||
userIDs: [],
|
||||
pagination: {
|
||||
pageNumber: params.current as number,
|
||||
showNumber: params.pageSize,
|
||||
},
|
||||
});
|
||||
// const tmpData = data.chat_logs ?? [];
|
||||
return {
|
||||
data: data.list,
|
||||
success: true,
|
||||
total: data.total,
|
||||
};
|
||||
}}
|
||||
rowKey="invitationCode"
|
||||
search={{
|
||||
labelWidth: 'auto',
|
||||
}}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
onChange: (page) => console.log(page),
|
||||
}}
|
||||
dateFormatter="string"
|
||||
scroll={{ x: 'max-content' }}
|
||||
toolbar={{
|
||||
actions: [
|
||||
<Button key="create" type="primary" onClick={showModal}>
|
||||
生成邀请码
|
||||
</Button>,
|
||||
],
|
||||
settings: [],
|
||||
}}
|
||||
/>
|
||||
<Modal title="请选择生成数量" open={isModalOpen} onOk={handleOk} onCancel={handleCancel}>
|
||||
<div className="w-full flex justify-center">
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={100}
|
||||
value={genNumber}
|
||||
onChange={(value) => {
|
||||
setGenNumber(value as number);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default InvitionCode;
|
||||
154
src/pages/business_system/register_manage/SelectUserModal.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { SelectUserTableHandle } from '@/components/SelectUserTable';
|
||||
import SelectUserTable from '@/components/SelectUserTable';
|
||||
import { DefaultAvatar } from '@/utils/avatar';
|
||||
import { Button, Checkbox, Col, Modal, Row, Space } from 'antd';
|
||||
import type { FC } from 'react';
|
||||
import { memo, useEffect, useRef, useState } from 'react';
|
||||
import type { UserListItem } from '../user_manage/data';
|
||||
|
||||
type SelectedRowDataItem = Record<
|
||||
number,
|
||||
{
|
||||
keys: React.Key[];
|
||||
items: UserListItem[];
|
||||
}
|
||||
>;
|
||||
|
||||
type SelectUserModalProps = {
|
||||
groupID?: string;
|
||||
selectModalOptions: SelectModalOptions;
|
||||
closeSelectModal: () => void;
|
||||
selectedCallBack?: (data: SelectedListItem, type: 'owner' | 'member') => void;
|
||||
};
|
||||
|
||||
export type SelectedListItem = {
|
||||
data: UserListItem[];
|
||||
selectedRowData?: SelectedRowDataItem;
|
||||
};
|
||||
|
||||
export type SelectModalOptions = {
|
||||
visible: boolean;
|
||||
selectType: 'owner' | 'member';
|
||||
preSelectData?: SelectedListItem;
|
||||
};
|
||||
|
||||
const SelectUserModal: FC<SelectUserModalProps> = ({
|
||||
groupID,
|
||||
selectModalOptions,
|
||||
closeSelectModal,
|
||||
selectedCallBack,
|
||||
}) => {
|
||||
const [selectedList, setSelectedList] = useState<SelectedListItem>({
|
||||
data: [],
|
||||
selectedRowData: {},
|
||||
});
|
||||
const selectTableRef = useRef<SelectUserTableHandle>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectModalOptions.visible && selectModalOptions.preSelectData) {
|
||||
setSelectedList({
|
||||
data: selectModalOptions.preSelectData.data,
|
||||
selectedRowData: selectModalOptions.preSelectData.selectedRowData,
|
||||
});
|
||||
}
|
||||
}, [selectModalOptions.visible]);
|
||||
|
||||
const onSelectedChange = (data: UserListItem[], selectedRowData: SelectedRowDataItem) => {
|
||||
setSelectedList({
|
||||
data,
|
||||
selectedRowData,
|
||||
});
|
||||
console.log(selectedRowData);
|
||||
};
|
||||
const cancelAll = () => {};
|
||||
|
||||
const closeAndRestState = () => {
|
||||
setSelectedList({
|
||||
data: [],
|
||||
selectedRowData: {},
|
||||
});
|
||||
selectTableRef.current?.clearSelect();
|
||||
closeSelectModal();
|
||||
};
|
||||
|
||||
const comfirmSelect = () => {
|
||||
selectedCallBack?.(selectedList, selectModalOptions.selectType);
|
||||
closeAndRestState();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
closable={false}
|
||||
bodyStyle={{ padding: 0 }}
|
||||
width="80%"
|
||||
footer={null}
|
||||
destroyOnClose
|
||||
open={selectModalOptions.visible}
|
||||
onCancel={closeAndRestState}
|
||||
>
|
||||
<Row>
|
||||
<SelectUserTable
|
||||
groupID={groupID}
|
||||
ref={selectTableRef}
|
||||
defaultSelected={selectModalOptions.preSelectData?.selectedRowData}
|
||||
selectType={selectModalOptions.selectType === 'owner' ? 'radio' : 'checkbox'}
|
||||
onSelectedChange={onSelectedChange}
|
||||
colProps={{ span: 12 }}
|
||||
/>
|
||||
<Col span={12}>
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="font-medium mt-6 mb-4 ml-8">
|
||||
已选择群成员:{selectedList.data.length}人
|
||||
</div>
|
||||
<div className="flex justify-between mx-8">
|
||||
<div>
|
||||
<Checkbox
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
cancelAll();
|
||||
}
|
||||
}}
|
||||
checked={selectedList.data.length !== 0}
|
||||
/>
|
||||
<span className="ml-2">头像</span>
|
||||
</div>
|
||||
<div>用户昵称</div>
|
||||
<div>用户ID</div>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto custom_scrollbar">
|
||||
{selectedList.data.map((user) => (
|
||||
<div key={user.userID} className="flex justify-between mx-8 my-3">
|
||||
<div>
|
||||
<Checkbox
|
||||
checked
|
||||
className="mr-2"
|
||||
onChange={(e) => {
|
||||
if (!e.target.checked) {
|
||||
selectTableRef.current?.cancelSelect(user.userID);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/* <Avatar className="!ml-2" shape="square" src={user.avatar} size={30} /> */}
|
||||
<DefaultAvatar faceURL={user.faceURL} nickname={user.nickname} />;
|
||||
</div>
|
||||
<div className="max-w-[100px] truncate ml-[42px]">{user.nickname}</div>
|
||||
<div className="max-w-[100px] truncate">{user.userID}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-full h-14 text-right pr-8">
|
||||
<Space size="middle">
|
||||
<Button onClick={closeSelectModal}>取消</Button>
|
||||
<Button onClick={comfirmSelect} type="primary">
|
||||
确定
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(SelectUserModal);
|
||||
33
src/pages/business_system/register_manage/data.d.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { InvitationCodeStatus } from '@/constants/enum';
|
||||
|
||||
export interface InvitationCodeItem {
|
||||
createTime: string;
|
||||
invitationCode: string;
|
||||
lastTime: string;
|
||||
status: InvitationCodeStatus;
|
||||
userID: string;
|
||||
}
|
||||
|
||||
export interface DefualtFriendItem {
|
||||
user: {
|
||||
nickname: string;
|
||||
};
|
||||
userID: string;
|
||||
}
|
||||
export interface DefualtGroupItem {
|
||||
groupID: string;
|
||||
groupName: any;
|
||||
createTime?: number;
|
||||
}
|
||||
|
||||
export interface AssignIPItem {
|
||||
userID: string;
|
||||
ip: string;
|
||||
}
|
||||
|
||||
export interface BrowseIPItem {
|
||||
createTime: string;
|
||||
ip: string;
|
||||
limitLogin: number;
|
||||
limitRegister: number;
|
||||
}
|
||||
5
src/pages/business_system/statistics/Moments.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
const Moments = () => {
|
||||
return <div>b/s/m</div>;
|
||||
};
|
||||
|
||||
export default Moments;
|
||||