Quellcode durchsuchen

新版本

storemanagementweb
zhaoy vor 8 Monaten
Ursprung
Commit
0e32c9c675
100 geänderte Dateien mit 7147 neuen und 0 gelöschten Zeilen
  1. +16
    -0
      .editorconfig
  2. +9
    -0
      .eslintignore
  3. +8
    -0
      .eslintrc.js
  4. +23
    -0
      .prettierignore
  5. +5
    -0
      .prettierrc.js
  6. +5
    -0
      .stylelintrc.js
  7. +142
    -0
      Jenkinsfile
  8. +14
    -0
      config/config.dev.js
  9. +74
    -0
      config/config.js
  10. +16
    -0
      config/defaultSettings.js
  11. +619
    -0
      config/oneapi.json
  12. +89
    -0
      config/proxy.js
  13. +217
    -0
      config/routes.js
  14. +13
    -0
      default.conf
  15. +5
    -0
      dockerfile
  16. +10
    -0
      jest.config.js
  17. +12
    -0
      jsconfig.json
  18. +57
    -0
      k8s_kitchenweb.yaml
  19. +178
    -0
      mock/listTableList.js
  20. +105
    -0
      mock/notices.js
  21. +7
    -0
      mock/route.js
  22. +207
    -0
      mock/user.js
  23. +129
    -0
      package.json
  24. +1
    -0
      public/CNAME
  25. BIN
     
  26. BIN
     
  27. BIN
     
  28. BIN
     
  29. BIN
     
  30. +1
    -0
      public/logo.svg
  31. +5
    -0
      public/pro_icon.svg
  32. +50
    -0
      src/access.js
  33. +467
    -0
      src/app.jsx
  34. +79
    -0
      src/components/ConditionQuery/index.jsx
  35. +93
    -0
      src/components/ConditionQuery/index.less
  36. +18
    -0
      src/components/Footer/index.jsx
  37. +29
    -0
      src/components/HeaderDropdown/index.jsx
  38. +16
    -0
      src/components/HeaderDropdown/index.less
  39. +83
    -0
      src/components/HeaderSearch/index.jsx
  40. +25
    -0
      src/components/HeaderSearch/index.less
  41. +114
    -0
      src/components/NoticeIcon/NoticeIcon.jsx
  42. +97
    -0
      src/components/NoticeIcon/NoticeList.jsx
  43. +103
    -0
      src/components/NoticeIcon/NoticeList.less
  44. +147
    -0
      src/components/NoticeIcon/index.jsx
  45. +35
    -0
      src/components/NoticeIcon/index.less
  46. +98
    -0
      src/components/RightContent/AvatarDropdown.jsx
  47. +63
    -0
      src/components/RightContent/index.jsx
  48. +62
    -0
      src/components/RightContent/index.less
  49. +128
    -0
      src/components/TagView/Tags/index.jsx
  50. +88
    -0
      src/components/TagView/Tags/index.less
  51. +174
    -0
      src/components/TagView/index.jsx
  52. +11
    -0
      src/components/TagView/index.less
  53. +271
    -0
      src/components/index.md
  54. +61
    -0
      src/e2e/baseLayout.e2e.js
  55. +101
    -0
      src/global.jsx
  56. +57
    -0
      src/global.less
  57. +7
    -0
      src/global_data.js
  58. +24
    -0
      src/locales/en-US.js
  59. +5
    -0
      src/locales/en-US/component.js
  60. +17
    -0
      src/locales/en-US/globalHeader.js
  61. +52
    -0
      src/locales/en-US/menu.js
  62. +70
    -0
      src/locales/en-US/pages.js
  63. +6
    -0
      src/locales/en-US/pwa.js
  64. +31
    -0
      src/locales/en-US/settingDrawer.js
  65. +60
    -0
      src/locales/en-US/settings.js
  66. +24
    -0
      src/locales/zh-CN.js
  67. +5
    -0
      src/locales/zh-CN/component.js
  68. +17
    -0
      src/locales/zh-CN/globalHeader.js
  69. +55
    -0
      src/locales/zh-CN/menu.js
  70. +67
    -0
      src/locales/zh-CN/pages.js
  71. +6
    -0
      src/locales/zh-CN/pwa.js
  72. +31
    -0
      src/locales/zh-CN/settingDrawer.js
  73. +55
    -0
      src/locales/zh-CN/settings.js
  74. +19
    -0
      src/locales/zh-TW.js
  75. +5
    -0
      src/locales/zh-TW/component.js
  76. +17
    -0
      src/locales/zh-TW/globalHeader.js
  77. +52
    -0
      src/locales/zh-TW/menu.js
  78. +6
    -0
      src/locales/zh-TW/pwa.js
  79. +31
    -0
      src/locales/zh-TW/settingDrawer.js
  80. +55
    -0
      src/locales/zh-TW/settings.js
  81. +22
    -0
      src/manifest.json
  82. +18
    -0
      src/pages/404.jsx
  83. +52
    -0
      src/pages/Admin.jsx
  84. +47
    -0
      src/pages/Welcome.jsx
  85. +8
    -0
      src/pages/Welcome.less
  86. +69
    -0
      src/pages/admin/users/components/CreateForm.jsx
  87. +64
    -0
      src/pages/admin/users/components/OrgForm.jsx
  88. +85
    -0
      src/pages/admin/users/components/RoleForm.jsx
  89. +437
    -0
      src/pages/admin/users/index.jsx
  90. +0
    -0
     
  91. +55
    -0
      src/pages/admin/users/service.js
  92. +55
    -0
      src/pages/bom/bomreplace/components/CreateForm.jsx
  93. +291
    -0
      src/pages/bom/bomreplace/index.jsx
  94. +49
    -0
      src/pages/bom/bomreplace/services.js
  95. +61
    -0
      src/pages/bom/bomtype/components/CreateForm.jsx
  96. +314
    -0
      src/pages/bom/bomtype/index.jsx
  97. +37
    -0
      src/pages/bom/bomtype/services.js
  98. +54
    -0
      src/pages/bom/cookingentry/components/AddBomInfo.jsx
  99. +188
    -0
      src/pages/bom/cookingentry/components/BomList.jsx
  100. +87
    -0
      src/pages/bom/cookingentry/components/CreateForm.jsx

+ 16
- 0
.editorconfig Datei anzeigen

@@ -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
- 0
.eslintignore Datei anzeigen

@@ -0,0 +1,9 @@
/lambda/
/scripts
/config
.history
public
dist
.umi
mock
src

+ 8
- 0
.eslintrc.js Datei anzeigen

@@ -0,0 +1,8 @@
module.exports = {
extends: [require.resolve('@umijs/fabric/dist/eslint')],
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
page: true,
REACT_APP_ENV: true,
},
};

+ 23
- 0
.prettierignore Datei anzeigen

@@ -0,0 +1,23 @@
**/*.svg
package.json
.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

+ 5
- 0
.prettierrc.js Datei anzeigen

@@ -0,0 +1,5 @@
const fabric = require('@umijs/fabric');

module.exports = {
...fabric.prettier,
};

+ 5
- 0
.stylelintrc.js Datei anzeigen

@@ -0,0 +1,5 @@
const fabric = require('@umijs/fabric');

module.exports = {
...fabric.stylelint,
};

+ 142
- 0
Jenkinsfile Datei anzeigen

@@ -0,0 +1,142 @@

def getHost(){
def remote = [:]
remote.name = 'eip'
remote.host = '10.6.1.50'
remote.user = 'root'
remote.port = 22
remote.password = '120962839'
remote.allowAnyHosts = true
return remote
}

pipeline{
agent any
parameters {
choice(
description: 'EIP环境',
name: 'environment',
choices: ['dev1']
)

choice(
description: '执行操作(发布|回滚)',
name: 'operation',
choices: ['develop', 'rollback']
)

choice(
description: '是否下载包',
name: 'isDownloadPackage',
choices: ['是','否']
)

string(
name: 'tag',
defaultValue: '',
description: '版本tag'
)

}

stages
{

stage('Prepare') {
steps {
script{
echo "1.Prepare Stage"
echo "当前环境${params.environment}"
if(params.operation=='develop')
{
checkout scm
script {
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
if (env.BRANCH_NAME != 'master') {
build_tag = "${env.BRANCH_NAME}-${build_tag}"
}
}
}
else{
build_tag="${params.tag}"
}
}
}
}
stage('Test') {
steps {
script{
echo "2.Test Stage"
}
}

}
stage('Build') {
steps {
script{
echo "3.Build Docker Image Stage"
if(params.operation=='develop')
{
nodejs("nodejs") {

}
if(params.isDownloadPackage=='是')
{
sh "npm install --unsafe-perm=true --allow-root"
sh "rm -rf ./dist/*"
sh "npm run build"
}
sh "docker build -t 10.2.1.24:10242/bpa/kitchenweb:${build_tag} ."
}
}
}

}





stage('Push') {
steps {
script{
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'harbor', passwordVariable: 'harborPassword', usernameVariable: 'harborUser')]) {
sh "docker login -u ${harborUser} -p ${harborPassword} 10.2.1.24:10242"
if(params.operation=='develop')
{
sh "docker push 10.2.1.24:10242/bpa/kitchenweb:${build_tag}"
}
}
}
}




}


stage('Deploy') {

steps {
script{
echo "5. Deploy Stage"
// server = getHost()
// sshCommand remote: server, command: """
// /root/eip/web/shell/linux-eipweb.install.sh eipweb ${build_tag} 80
// """

sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s_kitchenweb.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s_kitchenweb.yaml"
sh "sed -i 's/<NAMESPACES>/${params.environment}/' k8s_kitchenweb.yaml"
sh "kubectl apply -f k8s_kitchenweb.yaml --record"
}



}
}
}

}

+ 14
- 0
config/config.dev.js Datei anzeigen

@@ -0,0 +1,14 @@
// https://umijs.org/config/
import { defineConfig } from 'umi';
export default defineConfig({
plugins: [
// https://github.com/zthxxx/react-dev-inspector
'react-dev-inspector/plugins/umi/react-inspector',
],
// https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
inspectorConfig: {
exclude: [],
babelPlugins: [],
babelOptions: {},
},
});

+ 74
- 0
config/config.js Datei anzeigen

@@ -0,0 +1,74 @@
// https://umijs.org/config/
import { defineConfig } from 'umi';
import { join } from 'path';
import defaultSettings from './defaultSettings';
import proxy from './proxy';
import routes from './routes';
const { REACT_APP_ENV } = process.env;
export default defineConfig({
hash: true,
antd: {},
dva: {
hmr: true,
},
define: {
'process.env.UMI_ENV': process.env.UMI_ENV || 'dev',
},
layout: {
// https://umijs.org/zh-CN/plugins/plugin-layout
locale: false,
siderWidth: 208,
...defaultSettings,
},
// https://umijs.org/zh-CN/plugins/plugin-locale
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
dynamicImport: {
loading: '@ant-design/pro-layout/es/PageLoading',
},
targets: {
ie: 11,
},
// umi routes: https://umijs.org/docs/routing
routes,
// Theme for antd: https://ant.design/docs/react/customize-theme-cn
theme: {
'primary-color': defaultSettings.primaryColor,
},
// esbuild is father build tools
// https://umijs.org/plugins/plugin-esbuild
esbuild: {},
title: false,
ignoreMomentLocale: true,
proxy: proxy[REACT_APP_ENV || 'dev'],
manifest: {
basePath: '/',
},
// Fast Refresh 热更新
fastRefresh: {},
openAPI: [
{
requestLibPath: "import { request } from 'umi'",
// 或者使用在线的版本
// schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
schemaPath: join(__dirname, 'oneapi.json'),
mock: false,
},
{
requestLibPath: "import { request } from 'umi'",
schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
projectName: 'swagger',
},
],
nodeModulesTransform: {
type: 'none',
},
mfsu: {},
webpack5: {},
exportStatic: {},
});

+ 16
- 0
config/defaultSettings.js Datei anzeigen

@@ -0,0 +1,16 @@
const Settings = {
navTheme: 'dark',
// 拂晓蓝
primaryColor: '#FA541C', //'#1890ff',
layout: 'side',
contentWidth: 'Fluid',
fixedHeader: false,
fixSiderbar: true,
colorWeak: false,
title: '黑菠萝智慧餐厨',
pwa: false,
logo: '/logo.svg',
iconfontUrl: '',
};

export default Settings;

+ 619
- 0
config/oneapi.json Datei anzeigen

@@ -0,0 +1,619 @@
{
"openapi": "3.0.1",
"info": {
"title": "Ant Design Pro",
"version": "1.0.0"
},
"servers": [
{
"url": "http://localhost:44324/"
},
{
"url": "https://localhost:44324/"
}
],
"paths": {
"/kitchen/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"
},
"/kitchen/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"
}
}
}
}
}
}
},
"/kitchen/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"
},
"/kitchen/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"
},
"/kitchen/api/notices": {
"summary": "getNotices",
"description": "NoticeIconItem",
"get": {
"tags": [
"api"
],
"operationId": "getNotices",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NoticeIconList"
}
}
}
}
}
}
},
"/kitchen/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"
}
}
}
}
}
}

+ 89
- 0
config/proxy.js Datei anzeigen

@@ -0,0 +1,89 @@
/**
* 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
* -------------------------------
* 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
*/
export default {
dev: {
'/saasbase/': {
target: 'http://192.168.1.19:5006',
changeOrigin: true,
secure: false, //关闭证书验证
pathRewrite: {
'/saasbase/': '',
},
},
// Nginx发布的时候需要配置
'/cos/':{
target: 'https://hbl-test-1305371387.cos.ap-chengdu.myqcloud.com',
changeOrigin: true,
secure: false, //关闭证书验证
pathRewrite: {
'/cos/': '/',
},
}
},
test: {
'/api/': {
target: 'http://localhost:5006',
changeOrigin: true,
secure: false,
pathRewrite: {
'^': '',
},
},
},
pre: {
'/api/': {
target: 'http://localhost:5006',
changeOrigin: true,
secure: false,
pathRewrite: {
'^': '',
},
},
},
};
// /**
// * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
// * -------------------------------
// * 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
// */
// export default {
// dev: {
// '/api/': {
// target: 'http://114.117.161.250:7002',
// changeOrigin: true,
// secure: false, //关闭证书验证
// pathRewrite: {
// '^': '',
// },
// },
// },
// test: {
// '/api/': {
// target: 'http://114.117.161.250:7002',
// changeOrigin: true,
// secure: false,
// pathRewrite: {
// '^': '',
// },
// },
// },
// pre: {
// '/api/': {
// target: 'http://114.117.161.250:7002',
// changeOrigin: true,
// secure: false,
// pathRewrite: {
// '^': '',
// },
// },
// },
// };

+ 217
- 0
config/routes.js Datei anzeigen

@@ -0,0 +1,217 @@
/**
* SYS 系统设置
* admin 系统用户
* erp 供应链管理
* basic 基础信息管理
* bill 单据管理
* crm crm会员管理
* franchisee 加盟商
* srd 店铺管理
**/
export default [
{
path: '/user',
layout: false,
routes: [
{
name: '系统登录',
path: '/user/login',
component: './user/login',
access: 'k1',
},
],
},
{
name: '系统管理',
icon: 'SettingOutlined',
path: '/sys',
routes: [
{
name: '系统菜单',
icon: 'smile',
path: '/sys/menus',
component: './sys/menus',
access: 'k6',
},
// {
// name: '字典信息',
// icon: 'smile',
// path: '/sys/dictionary/dictdata',
// component: './sys/dictionary/dictdata',
// access: 'k6',
// },
{
name: '字典类型',
icon: 'smile',
path: '/sys/dictionary/dicttype',
component: './sys/dictionary/dicttype',
access: 'k6',
},
// {
// name: '操作日志',
// icon: 'smile',
// path: '/sys/log',
// component: './sys/log',
// access: 'k3',
// },
// {
// name: '错误日志',
// icon: 'smile',
// path: '/sys/log',
// component: './sys/log',
// access: 'k3',
// },
],
},
{
name: '加盟商管理',
icon: 'SettingOutlined',
path: '/company',
routes: [
{
name: '账号管理',
icon: 'smile',
path: '/company/account',
component: './company/account',
access: 'k2',
},
]
},
{
name: '组织管理',
icon: 'SettingOutlined',
path: '/org',
routes: [
{
name: '机构管理',
icon: 'smile',
path: '/org/orgamange',
component: './org/orgamange',
access: 'k2',
},
{
name: '角色管理',
icon: 'smile',
path: '/org/roles',
component: './org/roles',
access: 'k5',
},
{
name: '用户账号管理',
icon: 'smile',
path: '/org/users',
component: './org/users',
access: 'k5',
},
]
},
{
name: '元数据管理',
icon: 'DropboxSquareFilled',
path: '/database',
routes: [
{
name: '物料管理',
icon: 'smile',
path: '/database/basic/batching',
component: './database/basic/batching',
access: 'k7',
},
{
name: '商品管理',
icon: 'smile',
path: '/database',
routes: [
{
name: '商品类型',
icon: 'smile',
path: '/database/goods/goodstypemanage',
component: './database/goods/goodstypemanage',
access: 'k7',
},
{
name: '商品多属性',
icon: 'smile',
path: '/database/goods/goodsattribute',
component: './database/goods/goodsattribute',
access: 'k7',
},
{
name: '商品基础信息',
icon: 'smile',
path: '/database/goods/newgoods',
component: './database/goods/newgoods',
access: 'k7',
},
{
name: '添加商品基础信息',
icon: 'smile',
path: '/database/goods/goodsInfo',
component: './database/goods/goodsInfo',
access: 'k7',
},
]
},
],
},

{
name: '设备管理',
icon: 'BankFilled',
path: '/device',
routes: [
{
name: '产品管理',
icon: 'smile',
path: '/device/deviceType',
component: './device/deviceType',
access: 'k12',
},
{
name: '设备信息',
icon: 'smile',
path: '/device/deviceInfo',
component: './device/deviceInfo',
access: 'k14',
},
{
name: '版本管理',
icon: 'smile',
path: '/device/deviceVesion',
component: './device/deviceVesion',
access: 'k14',
},
// {
// name: '设备工艺信息',
// icon: 'smile',
// path: '/device/devicetechnology',
// component: './device/devicetechnology',
// access: 'k9',
// },
// {
// name: '设备商品管理',
// icon: 'smile',
// path: '/device/deviceFood',
// component: './device/deviceFood',
// access: 'k14',
// },
],
},
{
path: '/',
redirect: '/welcome',
},
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
component: './Welcome',
},

//什么都不要想404直接放最后
{
component: './404',
},
];

+ 13
- 0
default.conf Datei anzeigen

@@ -0,0 +1,13 @@
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}




+ 5
- 0
dockerfile Datei anzeigen

@@ -0,0 +1,5 @@
FROM nginx
COPY ./dist /usr/share/nginx/html/
COPY ./default.conf /etc/nginx/conf.d/
EXPOSE 80


+ 10
- 0
jest.config.js Datei anzeigen

@@ -0,0 +1,10 @@
module.exports = {
testURL: 'http://localhost:7002/',
testEnvironment: './tests/PuppeteerEnvironment',
verbose: false,
extraSetupFiles: ['./tests/setupTests.js'],
globals: {
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
localStorage: null,
},
};

+ 12
- 0
jsconfig.json Datei anzeigen

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
}
}
}

+ 57
- 0
k8s_kitchenweb.yaml Datei anzeigen

@@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: kitchenweb
namespace: kube-<NAMESPACES>
spec:

selector:
matchLabels:
app: kitchenweb
replicas: 1
template:
metadata:
labels:
app: kitchenweb
spec:
containers:
- image: 10.2.1.24:10242/bpa/kitchenweb:<BUILD_TAG>
imagePullPolicy: IfNotPresent
name: kitchenweb
env:
- name: branch
value: <BRANCH_NAME>
- name: TZ
value: Asia/Shanghai
ports:
- containerPort: 80
protocol: TCP
volumeMounts:
- mountPath: "/etc/localtime"
name: timezone
resources:
requests:
cpu: "100m"
memory: "112Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumes:
- name: timezone
hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
---
kind: Service
apiVersion: v1
metadata:
labels:
app: kitchenweb
name: kitchenweb
namespace: kube-<NAMESPACES>
spec:
type: ClusterIP
ports:
- port: 80
name: http
selector:
app: kitchenweb

+ 178
- 0
mock/listTableList.js Datei anzeigen

@@ -0,0 +1,178 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import moment from 'moment';
import { parse } from 'url'; // mock tableListDataSource

const genList = (current, pageSize) => {
const tableListDataSource = [];

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, res, u) {
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;
let dataSource = [...tableListDataSource].slice((current - 1) * pageSize, current * pageSize);
const sorter = JSON.parse(params.sorter || '{}');

if (sorter) {
dataSource = dataSource.sort((prev, next) => {
let sortNumber = 0;
Object.keys(sorter).forEach((key) => {
if (sorter[key] === 'descend') {
if (prev[key] - next[key] > 0) {
sortNumber += -1;
} else {
sortNumber += 1;
}

return;
}

if (prev[key] - next[key] > 0) {
sortNumber += 1;
} else {
sortNumber += -1;
}
});
return sortNumber;
});
}

if (params.filter) {
const filter = JSON.parse(params.filter);

if (Object.keys(filter).length > 0) {
dataSource = dataSource.filter((item) => {
return Object.keys(filter).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, res, u, b) {
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 = {
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,
};

+ 105
- 0
mock/notices.js Datei anzeigen

@@ -0,0 +1,105 @@
const getNotices = (req, res) => {
res.json({
data: [
{
id: '000000001',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
datetime: '2017-08-09',
type: 'notification',
},
{
id: '000000002',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: '2017-08-08',
type: 'notification',
},
{
id: '000000003',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
title: '这种模板可以区分多种通知类型',
datetime: '2017-08-07',
read: true,
type: 'notification',
},
{
id: '000000004',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
title: '左侧图标用于区分不同的类型',
datetime: '2017-08-07',
type: 'notification',
},
{
id: '000000005',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '内容不要超过两行字,超出时自动截断',
datetime: '2017-08-07',
type: 'notification',
},
{
id: '000000006',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '曲丽丽 评论了你',
description: '描述信息描述信息描述信息',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000007',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
title: '朱偏右 回复了你',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
datetime: '2017-08-07',
type: 'message',
clickClose: true,
},
{
id: '000000008',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
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 /kitchen/api/notices': getNotices,
};

+ 7
- 0
mock/route.js Datei anzeigen

@@ -0,0 +1,7 @@
export default {
'/saasbase/api/auth_routes': {
'/form/advanced-form': {
authority: ['admin', 'user'],
},
},
};

+ 207
- 0
mock/user.js Datei anzeigen

@@ -0,0 +1,207 @@
const waitTime = (time = 100) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, time);
});
};

async function getFakeCaptcha(req, res) {
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 /kitchen/api/currentUser': (req, res) => {
if (!getAccess()) {
res.status(401).send({
data: {
isLogin: false,
},
errorCode: '401',
errorMessage: '请先登录!',
success: true,
});
return;
}

res.send({
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 /kitchen/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 /kitchen/api/login/account': async (req, res) => {
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 /kitchen/api/login/outLogin': (req, res) => {
access = '';
res.send({
data: {},
success: true,
});
},
'POST /kitchen/api/register': (req, res) => {
res.send({
status: 'ok',
currentAuthority: 'user',
success: true,
});
},
'GET /kitchen/api/500': (req, res) => {
res.status(500).send({
timestamp: 1513932555104,
status: 500,
error: 'error',
message: 'error',
path: '/base/category/list',
});
},
'GET /kitchen/api/404': (req, res) => {
res.status(404).send({
timestamp: 1513932643431,
status: 404,
error: 'Not Found',
message: 'No message available',
path: '/base/category/list/2121212',
});
},
'GET /kitchen/api/403': (req, res) => {
res.status(403).send({
timestamp: 1513932555104,
status: 403,
error: 'Unauthorized',
message: 'Unauthorized',
path: '/base/category/list',
});
},
'GET /kitchen/api/401': (req, res) => {
res.status(401).send({
timestamp: 1513932555104,
status: 401,
error: 'Unauthorized',
message: 'Unauthorized',
path: '/base/category/list',
});
},
'GET /kitchen/api/login/captcha': getFakeCaptcha,
};

+ 129
- 0
package.json Datei anzeigen

@@ -0,0 +1,129 @@
{
"name": "ant-design-pro",
"version": "5.0.0",
"private": true,
"description": "An out-of-box UI solution for enterprise applications",
"scripts": {
"analyze": "cross-env ANALYZE=1 umi build",
"build": "umi 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": "umi g tmp",
"lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier",
"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 && npm run lint:style",
"lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
"lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto",
"lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
"openapi": "umi openapi",
"precommit": "lint-staged",
"prettier": "prettier -c --write \"src/**/*\"",
"start": "cross-env UMI_ENV=dev umi dev",
"start:umi-ui": "HOST=0.0.0.0 UMI-UI=1 umi dev",
"start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev",
"start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev",
"start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev",
"start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev",
"start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev",
"pretest": "node ./tests/beforeTest",
"test": "umi test",
"test:all": "node ./tests/run-tests.js",
"test:component": "umi test ./src/components",
"serve": "umi-serve",
"tsc": "tsc --noEmit"
},
"lint-staged": {
"**/*.less": "stylelint --syntax less",
"**/*.{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/charts": "^1.2.14",
"@ant-design/icons": "^4.5.0",
"@ant-design/pro-card": "^1.18.0",
"@ant-design/pro-descriptions": "^1.10.0",
"@ant-design/pro-form": "^1.48.0",
"@ant-design/pro-layout": "^6.29.0",
"@ant-design/pro-list": "^1.21.37",
"@ant-design/pro-table": "^2.57.0",
"@ant-design/pro-utils": "^1.28.0",
"@umijs/route-utils": "^2.0.3",
"@wangeditor/editor": "^5.1.15",
"@wangeditor/editor-for-react": "^1.0.6",
"antd": "^4.17.0",
"axios": "^0.26.1",
"braft-editor": "^2.3.9",
"classnames": "^2.2.6",
"cos-js-sdk-v5": "^1.3.5",
"echarts": "^5.3.3",
"js-base64": "^3.7.5",
"js-export-excel": "^1.1.4",
"linq": "^4.0.0",
"lodash": "^4.17.11",
"moment": "^2.25.3",
"nanoid": "^4.0.2",
"omit.js": "^2.0.2",
"qrcode.react": "^1.0.1",
"react": "^17.0.0",
"react-custom-scrollbar": "^1.0.0",
"react-custom-scrollbars": "^4.2.1",
"react-dev-inspector": "^1.1.1",
"react-dom": "^17.0.0",
"react-helmet-async": "^1.0.4",
"swagger-ui-react": "^4.14.0",
"umi": "^3.5.0",
"umi-serve": "^1.9.10"
},
"devDependencies": {
"@ant-design/pro-cli": "^2.0.2",
"@types/express": "^4.17.0",
"@types/history": "^4.7.2",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.177",
"@types/react": "^17.0.35",
"@types/react-dom": "^17.0.0",
"@types/react-helmet": "^6.1.0",
"@umijs/fabric": "^2.6.2",
"@umijs/openapi": "^1.3.6",
"@umijs/plugin-blocks": "^2.0.5",
"@umijs/plugin-esbuild": "^1.0.1",
"@umijs/plugin-openapi": "^1.2.0",
"@umijs/preset-ant-design-pro": "^1.2.0",
"@umijs/preset-dumi": "^1.1.32",
"@umijs/preset-react": "^1.8.27",
"@umijs/preset-ui": "^2.2.9",
"@umijs/yorkie": "^2.0.3",
"carlo": "^0.9.46",
"cross-env": "^7.0.0",
"cross-port-killer": "^1.1.1",
"detect-installer": "^1.0.1",
"enzyme": "^3.11.0",
"eslint": "^8.2.0",
"eslint-plugin-react": "^7.29.4",
"express": "^4.17.1",
"gh-pages": "^3.0.0",
"jsdom-global": "^3.0.2",
"lint-staged": "^12.0.2",
"mockjs": "^1.0.1-beta3",
"prettier": "^2.3.2",
"puppeteer-core": "^11.0.0",
"stylelint": "^14.1.0",
"typescript": "^4.2.2"
},
"engines": {
"node": ">=10.0.0"
},
"gitHooks": {
"commit-msg": "fabric verify-commit"
}
}

+ 1
- 0
public/CNAME Datei anzeigen

@@ -0,0 +1 @@
preview.pro.ant.design






+ 1
- 0
public/logo.svg
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 5
- 0
public/pro_icon.svg Datei anzeigen

@@ -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>

+ 50
- 0
src/access.js Datei anzeigen

@@ -0,0 +1,50 @@
/**
* 这里是把登录信息拿来做菜单权限控制
* @see https://umijs.org/zh-CN/plugins/plugin-access
* */
// permission 按钮权限标志
// function checkPermission(currentUser:API.CurrentUser,permission:string)
// {
// return currentUser&& currentUser.data.roles.some(
// (roles)=>roles.permissions.map(permission=>permission.name).indexof(permission)>-1)
// }
export default function access(initialState) {
const { currentUser, menuData } = initialState || {};

return {
// k1:true,
// k2:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k2'): false,
// k3:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k3'): false,
// k4:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k4'): false,
// k5:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k5'): false,
// k6:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k6'): false,
// k7:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k7'): false,
// k8:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k8'): false,
// k9:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k9'): false,
// k10:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k10'): false,
// k11:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k11'): false,
// k12:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k12'): false,
// k13:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k13'): false,
// k14:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k14'): false,
// k15:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k15'): false,
// k16:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k16'): false,
// k17:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k17'): false,
// k18:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k18'): false,
// k19:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k19'): false,
// k20:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k20'): false,
// k21:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k21'): false,
// k22:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k22'): false,
// k23:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k23'): false,
// k24:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k24'): false,
// k25:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k25'): false,
// k26:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k26'): false,
// k27:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k27'): false,
// k28:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k28'): false,
// k29:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k29'): false,
// k30:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k30'): false,
// k31:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k31'): false,
// k32:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k32'): false,
// k33:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k33'): false,
// k34:dymicMenu!=undefined? dymicMenu.data.some(t=>t.access=='k34'): false,
};
}

+ 467
- 0
src/app.jsx Datei anzeigen

@@ -0,0 +1,467 @@
import React from 'react';
import { PageLoading } from '@ant-design/pro-layout';
import { notification } from 'antd';
import { history, Link, RequestConfig } from 'umi';
import RightContent from '@/components/RightContent';
import Footer from '@/components/Footer';
import TagView from '@/components/TagView';
import * as Icon from '@ant-design/icons';
import api from '@/services/api';
const isDev = process.env.NODE_ENV === 'development';
const loginPath = '/user/login';
/** 获取用户信息比较慢的时候会展示一个 loading */

export const initialStateConfig = {
loading: <PageLoading />,
};
/**
* @see https://umijs.org/zh-CN/plugins/plugin-initial-state
* */

export async function getInitialState() {
const fetchUserInfo = async () => {
try {
const currentUser = await api.queryCurrent();

if (currentUser.data == null && history.location.pathname !== '/gateAdmin') {
history.push(loginPath);
}
return currentUser;
} catch (error) {
const { location } = history; // 如果没有登录,重定向到 login
if (location.pathname != loginPath && history.location.pathname !== '/gateAdmin') {
history.push(loginPath);
}
}

return undefined;
};
const queryMenuData = async () => {
try {
const data = [
{
path: '/welcome',
name: '连锁经营平台',
icon: 'smile',
hideInMenu: true,
component: './Welcome',
},
{
code: 'sys',
name: '系统管理',
icon: 'SettingOutlined',
path: '/sys',
routes: [
{
code: 'Menus',
name: '系统菜单',
icon: 'smile',
path: '/sys/Menus',
component: './sys/Menus',
access: 'k6',
},
// {
// code: 'dictdata',
// name: '字典信息',
// icon: 'smile',
// path: '/sys/dictionary/dictdata',
// component: './sys/dictionary/dictdata',
// access: 'k6',
// },
{
code: 'dicttype',
name: '字典类型',
icon: 'smile',
path: '/sys/dictionary/dicttype',
component: './sys/dictionary/dicttype',
access: 'k6',
},
// {
// code: 'Log',
// name: '操作日志',
// icon: 'smile',
// path: '/sys/Log',
// component: './sys/Log',
// access: 'k3',
// },
// {
// code: 'Log',
// name: '错误日志',
// icon: 'smile',
// path: '/sys/errLog',
// component: './sys/errLog',
// access: 'k3',
// },
],
},
{
code: 'company',
name: '加盟商管理',
icon: 'TeamOutlined',
path: '/company',
routes: [
{
code: 'account',
name: '账号管理',
icon: 'smile',
path: '/company/account',
component: './sys/account',
access: 'k4',
},
]
},
{
code: 'orgamange',
name: '组织管理',
icon: 'ClusterOutlined',
path: '/org',
routes: [
{
code: 'Org',
name: '机构管理',
icon: 'smile',
path: '/org/orgamange',
component: './org/orgamange',
access: 'k2',
},
{
code: 'roles',
name: '角色管理',
icon: 'smile',
path: '/org/Roles',
component: './org/Roles',
access: 'k2',
},
{
code: 'users',
name: '用户账号管理',
icon: 'smile',
path: '/org/users',
component: './org/users',
access: 'k2',
},
]
},
{
code: 'device',
name: '设备管理',
icon: 'PrinterOutlined',
path: '/device',
routes: [
{
code: 'deviceType',
name: '产品管理',
icon: 'smile',
path: '/device/deviceType',
component: './device/deviceType',
access: 'k12',
},
{
code: 'deviceInfo',
name: '设备信息',
icon: 'smile',
path: '/device/deviceInfo',
component: './device/deviceInfo',
access: 'k14',
},
{
code: 'deviceVesion',
name: '版本管理',
icon: 'smile',
path: '/device/deviceVesion',
component: './device/deviceVesion',
access: 'k14',
},
// {
// code: 'devicetechnology',
// name: '设备工艺信息',
// icon: 'smile',
// path: '/device/devicetechnology',
// component: './device/devicetechnology',
// access: 'k7',
// },
// {
// code: 'deviceFood',
// name: '设备商品管理',
// icon: 'smile',
// path: '/device/deviceFood',
// component: './device/deviceFood',
// access: 'k14',
// },
],
},
{

code: 'database',
name: '元数据管理',
icon: 'DatabaseOutlined',
path: '/database',
routes:[
{
code: 'batching',
name: '物料管理',
icon: 'smile',
path: '/basic',
routes:[
{
code: 'batching',
name: '物料基础信息',
icon: 'smile',
path: '/database/basic/batching',
component: './database/basic/batching',
access: 'k7',
},
]
},
{
code: 'goods',
name: '商品管理',
icon: 'smile',
path: '/goods',
routes:[
{
code: 'goods-type',
name: '商品类型',
icon: 'smile',
path: '/database/goods/goodstypemanage',
component: './database/goods/goodstypemanage',
access: 'k11',
},
{
code: 'goodsattribute',
name: '商品属性',
icon: 'smile',
path: '/database/goods/goodsattribute',
component: './database/goods/goodsattribute',
access: 'k10',
},
{
code: 'newgoods',
name: '商品基础信息',
icon: 'smile',
path: '/database/goods/newgoods',
component: './database/goods/newgoods',
access: 'k10',
},
]
},
]
},
];
// api.queryMenus();
return data;
} catch (error) {
history.push(loginPath);
}
return [];
};
// 如果是登录页面,不执行
if (history.location.pathname !== loginPath) {
const currentUser = await fetchUserInfo();
var tempMenu = await queryMenuData();
//创建菜单
//await syncMenus(tempMenu);
if (!isDev) {
var data = await dymicMenus(currentUser.data.id);
tempMenu = data.data;
}

return {
fetchUserInfo,
currentUser,
menuData: tempMenu,
settings: {},
};
}
return {
fetchUserInfo,
settings: {},
};
}

/**
* 同步菜单
* @param {*} args
* @returns
*/
const syncMenus = async (args) => {
return await api.queryAndSyncMenu({ json: JSON.stringify(args) });
};

/**
* 动态菜单
* @param {*} userid
* @returns
*/

const dymicMenus = async (userid) => {
return await api.DymicMenus(userid);
};

/**
*
* 动态路由
*
*/
const loopMenuItem = (menus) =>
menus?.map(({ icon, children, ...item }) => {
if (typeof icon != 'undefined' && Icon[icon] != undefined) {
return {
...item,
icon: icon && React.createElement(Icon[icon]),
children: children && loopMenuItem(children),
};
} else {
return {
...item,
children: children && loopMenuItem(children),
};
}
});
/**
* 异常处理程序
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
405: '请求方法不被允许。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
//-----English
200: The server successfully returned the requested data. ',
201: New or modified data is successful. ',
202: A request has entered the background queue (asynchronous task). ',
204: Data deleted successfully. ',
400: 'There was an error in the request sent, and the server did not create or modify data. ',
401: The user does not have permission (token, username, password error). ',
403: The user is authorized, but access is forbidden. ',
404: The request sent was for a record that did not exist. ',
405: The request method is not allowed. ',
406: The requested format is not available. ',
410':
'The requested resource is permanently deleted and will no longer be available. ',
422: When creating an object, a validation error occurred. ',
500: An error occurred on the server, please check the server. ',
502: Gateway error. ',
503: The service is unavailable. ',
504: The gateway timed out. ',
* @see https://beta-pro.ant.design/docs/request-cn
*/
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
405: '请求方法不被允许。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};

export const request = {

errorHandler: (error) => {
const { response } = error;
console.log('response', response);
if (response && response.status !== 200 && response.status !== 422) {
const errorText = codeMessage[response.status] || response.statusText;
const { status, url } = response;
if (response.status === 401 || response.status === 403) {
notification.error({
description: '请先登录。',
message: '未登录',
});
// history.push('/user/login');
const { location } = history; // 如果没有登录,重定向到 login
if (!location.pathname.includes(loginPath) && history.location.pathname !== '/gateAdmin') {
history.push(loginPath);
}
} else {
notification.error({
description: '网络发生异常,无法连接服务器',
message: '网络异常',
});
}
}
if (!response) {
notification.error({
description: '网络发生异常,无法连接服务器',
message: '网络异常',
});
}

throw error;
},
//prefix: `${DOMAIN}`,
requestInterceptors: [
(url, options) => {
url = decodeURI(encodeURI(url).replace(/%E2%80%8B/g, ''));
const newOptions = { ...options };
const auttoken = localStorage.getItem('token');
const userid = localStorage.getItem('userid');
newOptions.headers = {
...newOptions.headers,
userid: userid,
Authorization: 'Bearer ' + auttoken,
LoginType: 1,
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
};
return { url, options: newOptions };
},
],
};
// ProLayout 支持的api https://procomponents.ant.design/components/layout

export const layout = ({ initialState }) => {
return {
rightContentRender: () => <RightContent />,
disableContentMargin: false,
waterMarkProps: {
content: '黑菠萝技术部',
},

footerRender: () => <Footer />,
onPageChange: () => {
const { location } = history; // 如果没有登录,重定向到 login
// console.log(process.env.UMI_ENV)
// console.log('location.pathname', location.pathname);
if (!initialState?.currentUser && (!location.pathname.includes(loginPath) || !location.pathname.includes('/gateAdmin'))) {
history.push(loginPath);
}
},
menuDataRender: () => loopMenuItem(initialState?.menuData),
// childrenRender: (children) => {
// return (
// <>
// {initialState?.currentUser && location.pathname !== loginPath && location.pathname!='/' ? (
// <TagView home="/welcome" current={history.location.pathname}>
// {children}
// </TagView>
// ) : children}
// </>
// );
// },
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
...initialState?.settings,
};
};

+ 79
- 0
src/components/ConditionQuery/index.jsx Datei anzeigen

@@ -0,0 +1,79 @@
import React, { useEffect } from 'react';
import { Button, Card, DatePicker, Col, Row, TreeSelect, Spin } from 'antd';
import styles from './index.less';
import moment from 'moment';
const { RangePicker } = DatePicker;

/**
* 条件查询通用头部
* @returns
*/
export default function Index(props) {

const LoadingCard = () => {
return (
<div className={styles['loading-card']}>
<Spin size="large" />
</div>
)
}

return (
<div>
{props.showLoading ? <LoadingCard></LoadingCard> : null}
<Card className={styles['data-search-card']}>
<Row gutter={20} className={styles['data-search-row']}>
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}>
<RangePicker size='middle' className={styles['data-search-sufixx']} value={props.timeRange} onChange={(date, dateStrings) => {
let tempDate = [
moment(moment(new Date(dateStrings[0])).format('YYYY-MM-DD 00:00:00')),
moment(moment(new Date(dateStrings[1])).format('YYYY-MM-DD 23:59:59')),
]
props.onTimePickerChange(tempDate);
}} />
</Col>
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}>
<div className={props.searchDayIndex === 0 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(0)}>
今天
</div>
<div className={props.searchDayIndex === 1 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(1)}>
昨天
</div>
<div className={props.searchDayIndex === 2 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(2)}>
近7天
</div>
<div className={props.searchDayIndex === 3 ? `${styles['data-search-day']} ${styles['search-day-selected']}` : `${styles['data-search-day']}`} onClick={() => props.onChangeTimeIndex(3)}>
近30天
</div>
</Col>
<Col xs={24} sm={24} md={24} lg={12} xl={6} className={styles['data-search-item']}>
<TreeSelect
className={styles['data-search-sufixx']}
dropdownStyle={{
maxHeight: 400,
overflow: 'auto',
}}
value={props.currentOrg.title}
treeData={props.orgTree}
onSelect={(value, node) => {
if (node.type === 2 || node === 3) {
props.onCurrentOrgChange(node);
} else {
props.onCurrentOrgChange("");
}
}}
placeholder="请选择组织架构"
treeDefaultExpandAll
/>
</Col>
<Col xs={24} sm={24} md={12} lg={12} xl={6} className={styles['data-search-item']}>
<div className={styles['data-search-btns']}>
<Button className={styles['search-btn-item']} onClick={props.onResetSearch}>重置</Button>
<Button className={styles['search-btn-item']} type="primary" onClick={props.onQueryBtn}>查询</Button>
</div>
</Col>
</Row>
</Card>
</div>
)
}

+ 93
- 0
src/components/ConditionQuery/index.less Datei anzeigen

@@ -0,0 +1,93 @@
.data-search-card {
margin-bottom: 20px;
}

.data-search-row {
margin-bottom: 10px;
}

.data-search-item {
display: flex;
align-items: center;
margin-bottom: 10px;
}

.data-search-prefix {
margin-right: 10px;
font-size: 18px;
}

.search-btn-item {
margin-right: 20px;
}

.data-search-day {
padding: 5px 20px;
border: 1px solid #dedede;
cursor: pointer;
}

.data-search-day:nth-child(1), .data-search-day:nth-child(2), .data-search-day:nth-child(3) {
border-right: none;
}

.search-day-selected {
border: 1px solid #FA541C !important;
color: #FA541C;
}

.data-search-sufixx {
width: 100%;
}

// 加载中
.loading-card {
position: fixed;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 999;
background-color: rgba(0, 0, 0, 0.5);
}

.member-card-box {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}

.member-card-prefix {
font-size: 20px;
font-weight: 700;
color: #999;
font-family: '楷体';
}

.member-card-sufixx {
font-size: 22px;
}

.new-member {
width: 100%;
height: 250px;
}

.repur-chase {
width: 100%;
height: 250px;
}

.member-card {
height: 300px;
}

.member-row-common {
margin-bottom: 10px;
}

+ 18
- 0
src/components/Footer/index.jsx Datei anzeigen

@@ -0,0 +1,18 @@
import { useIntl } from 'umi';
import { GithubOutlined } from '@ant-design/icons';
import { DefaultFooter } from '@ant-design/pro-layout';
export default () => {
const intl = useIntl();
const defaultMessage = intl.formatMessage({
id: 'app.copyright.produced',
defaultMessage: '',
});
return (
<DefaultFooter
copyright={`${defaultMessage}`}
links={[

]}
/>
);
};

+ 29
- 0
src/components/HeaderDropdown/index.jsx Datei anzeigen

@@ -0,0 +1,29 @@
import { Dropdown, Button,Avatar } from 'antd';
import React from 'react';
import classNames from 'classnames';
import styles from './index.less';
import { PlayCircleOutlined, FireOutlined } from '@ant-design/icons';
import { useHistory } from "umi"

const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => {

const history = useHistory();
return <div style={{ display: 'flex', alignItems: 'center' }}>
{/* <Button onClick={onQuickStart} type='primary' icon={<PlayCircleOutlined />}>快速开店</Button>
<Button onClick={onQuickActivity} style={{ marginLeft: '10px' }} icon={<FireOutlined />}>创建活动</Button> */}
{/* <Avatar
style={{
backgroundColor: '#f56a00',
verticalAlign: 'middle',
}}
size="large"
gap='4'
>
U
</Avatar> */}
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
</div>

};

export default HeaderDropdown;

+ 16
- 0
src/components/HeaderDropdown/index.less Datei anzeigen

@@ -0,0 +1,16 @@
@import '~antd/es/style/themes/default.less';

.container > * {
background-color: @popover-bg;
border-radius: 4px;
box-shadow: @shadow-1-down;
}

@media screen and (max-width: @screen-xs) {
.container {
width: 100% !important;
}
.container > * {
border-radius: 0 !important;
}
}

+ 83
- 0
src/components/HeaderSearch/index.jsx Datei anzeigen

@@ -0,0 +1,83 @@
import { SearchOutlined } from '@ant-design/icons';
import { AutoComplete, Input } from 'antd';
import useMergedState from 'rc-util/es/hooks/useMergedState';
import React, { useRef } from 'react';
import classNames from 'classnames';
import styles from './index.less';

const HeaderSearch = (props) => {
const {
className,
defaultValue,
onVisibleChange,
placeholder,
visible,
defaultVisible,
...restProps
} = props;
const inputRef = useRef(null);
const [value, setValue] = useMergedState(defaultValue, {
value: props.value,
onChange: props.onChange,
});
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, {
value: props.visible,
onChange: onVisibleChange,
});
const inputClass = classNames(styles.input, {
[styles.show]: searchMode,
});
return (
<div
className={classNames(className, styles.headerSearch)}
onClick={() => {
setSearchMode(true);

if (searchMode && inputRef.current) {
inputRef.current.focus();
}
}}
onTransitionEnd={({ propertyName }) => {
if (propertyName === 'width' && !searchMode) {
if (onVisibleChange) {
onVisibleChange(searchMode);
}
}
}}
>
<SearchOutlined
key="Icon"
style={{
cursor: 'pointer',
}}
/>
<AutoComplete
key="AutoComplete"
className={inputClass}
value={value}
options={restProps.options}
onChange={setValue}
>
<Input
size="small"
ref={inputRef}
defaultValue={defaultValue}
aria-label={placeholder}
placeholder={placeholder}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (restProps.onSearch) {
restProps.onSearch(value);
}
}
}}
onBlur={() => {
setSearchMode(false);
}}
/>
</AutoComplete>
</div>
);
};

export default HeaderSearch;

+ 25
- 0
src/components/HeaderSearch/index.less Datei anzeigen

@@ -0,0 +1,25 @@
@import '~antd/es/style/themes/default.less';

.headerSearch {
display: inline-flex;
align-items: center;
.input {
width: 0;
min-width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
transition: width 0.3s, margin-left 0.3s;
:global(.ant-select-selection) {
background: transparent;
}
input {
box-shadow: none !important;
}

&.show {
width: 210px;
margin-left: 8px;
}
}
}

+ 114
- 0
src/components/NoticeIcon/NoticeIcon.jsx Datei anzeigen

@@ -0,0 +1,114 @@
import { BellOutlined } from '@ant-design/icons';
import { Badge, Spin, Tabs } from 'antd';
import useMergedState from 'rc-util/es/hooks/useMergedState';
import React from 'react';
import classNames from 'classnames';
import NoticeList from './NoticeList';
import HeaderDropdown from '../HeaderDropdown';
import styles from './index.less';
const { TabPane } = Tabs;

const NoticeIcon = (props) => {
const getNotificationBox = () => {
const {
children,
loading,
onClear,
onTabChange,
onItemClick,
onViewMore,
clearText,
viewMoreText,
} = props;

if (!children) {
return null;
}

const panes = [];
React.Children.forEach(children, (child) => {
if (!child) {
return;
}

const { list, title, count, tabKey, showClear, showViewMore } = child.props;
const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len;
const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title;
panes.push(
<TabPane tab={tabTitle} key={tabKey}>
<NoticeList
clearText={clearText}
viewMoreText={viewMoreText}
list={list}
tabKey={tabKey}
onClear={() => onClear && onClear(title, tabKey)}
onClick={(item) => onItemClick && onItemClick(item, child.props)}
onViewMore={(event) => onViewMore && onViewMore(child.props, event)}
showClear={showClear}
showViewMore={showViewMore}
title={title}
/>
</TabPane>,
);
});
return (
<>
<Spin spinning={loading} delay={300}>
<Tabs className={styles.tabs} onChange={onTabChange}>
{panes}
</Tabs>
</Spin>
</>
);
};

const { className, count, bell } = props;
const [visible, setVisible] = useMergedState(false, {
value: props.popupVisible,
onChange: props.onPopupVisibleChange,
});
const noticeButtonClass = classNames(className, styles.noticeButton);
const notificationBox = getNotificationBox();
const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
const trigger = (
<span
className={classNames(noticeButtonClass, {
opened: visible,
})}
>
<Badge
count={count}
style={{
boxShadow: 'none',
}}
className={styles.badge}
>
{NoticeBellIcon}
</Badge>
</span>
);

if (!notificationBox) {
return trigger;
}

return (
<HeaderDropdown
placement="bottomRight"
overlay={notificationBox}
overlayClassName={styles.popover}
trigger={['click']}
visible={visible}
onVisibleChange={setVisible}
>
{trigger}
</HeaderDropdown>
);
};

NoticeIcon.defaultProps = {
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
};
NoticeIcon.Tab = NoticeList;
export default NoticeIcon;

+ 97
- 0
src/components/NoticeIcon/NoticeList.jsx Datei anzeigen

@@ -0,0 +1,97 @@
import { Avatar, List } from 'antd';
import React from 'react';
import classNames from 'classnames';
import styles from './NoticeList.less';

const NoticeList = ({
list = [],
onClick,
onClear,
title,
onViewMore,
emptyText,
showClear = true,
clearText,
viewMoreText,
showViewMore = false,
}) => {
if (!list || list.length === 0) {
return (
<div className={styles.notFound}>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
alt="not found"
/>
<div>{emptyText}</div>
</div>
);
}

return (
<div>
<List
className={styles.list}
dataSource={list}
renderItem={(item, i) => {
const itemCls = classNames(styles.item, {
[styles.read]: item.read,
}); // eslint-disable-next-line no-nested-ternary

const leftIcon = item.avatar ? (
typeof item.avatar === 'string' ? (
<Avatar className={styles.avatar} src={item.avatar} />
) : (
<span className={styles.iconElement}>{item.avatar}</span>
)
) : null;
return (
<List.Item
className={itemCls}
key={item.key || i}
onClick={() => {
onClick?.(item);
}}
>
<List.Item.Meta
className={styles.meta}
avatar={leftIcon}
title={
<div className={styles.title}>
{item.title}
<div className={styles.extra}>{item.extra}</div>
</div>
}
description={
<div>
<div className={styles.description}>{item.description}</div>
<div className={styles.datetime}>{item.datetime}</div>
</div>
}
/>
</List.Item>
);
}}
/>
<div className={styles.bottomBar}>
{showClear ? (
<div onClick={onClear}>
{clearText} {title}
</div>
) : null}
{showViewMore ? (
<div
onClick={(e) => {
if (onViewMore) {
onViewMore(e);
}
}}
>
{viewMoreText}
</div>
) : null}
</div>
</div>
);
};

export default NoticeList;

+ 103
- 0
src/components/NoticeIcon/NoticeList.less Datei anzeigen

@@ -0,0 +1,103 @@
@import '~antd/es/style/themes/default.less';

.list {
max-height: 400px;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.item {
padding-right: 24px;
padding-left: 24px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;

.meta {
width: 100%;
}

.avatar {
margin-top: 4px;
background: @component-background;
}
.iconElement {
font-size: 32px;
}

&.read {
opacity: 0.4;
}
&:last-child {
border-bottom: 0;
}
&:hover {
background: @primary-1;
}
.title {
margin-bottom: 8px;
font-weight: normal;
}
.description {
font-size: 12px;
line-height: @line-height-base;
}
.datetime {
margin-top: 4px;
font-size: 12px;
line-height: @line-height-base;
}
.extra {
float: right;
margin-top: -1.5px;
margin-right: 0;
color: @text-color-secondary;
font-weight: normal;
}
}
.loadMore {
padding: 8px 0;
color: @primary-6;
text-align: center;
cursor: pointer;
&.loadedAll {
color: rgba(0, 0, 0, 0.25);
cursor: unset;
}
}
}

.notFound {
padding: 73px 0 88px;
color: @text-color-secondary;
text-align: center;
img {
display: inline-block;
height: 76px;
margin-bottom: 16px;
}
}

.bottomBar {
height: 46px;
color: @text-color;
line-height: 46px;
text-align: center;
border-top: 1px solid @border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
transition: all 0.3s;
div {
display: inline-block;
width: 50%;
cursor: pointer;
transition: all 0.3s;
user-select: none;

&:only-child {
width: 100%;
}
&:not(:only-child):last-child {
border-left: 1px solid @border-color-split;
}
}
}

+ 147
- 0
src/components/NoticeIcon/index.jsx Datei anzeigen

@@ -0,0 +1,147 @@
import { useEffect, useState } from 'react';
import { Tag, message } from 'antd';
import { groupBy } from 'lodash';
import moment from 'moment';
import { useModel } from 'umi';
import { getNotices } from '@/services/ant-design-pro/api';
import NoticeIcon from './NoticeIcon';
import styles from './index.less';

const getNoticeData = (notices) => {
if (!notices || notices.length === 0 || !Array.isArray(notices)) {
return {};
}

const newNotices = notices.map((notice) => {
const newNotice = { ...notice };

if (newNotice.datetime) {
newNotice.datetime = moment(notice.datetime).fromNow();
}

if (newNotice.id) {
newNotice.key = newNotice.id;
}

if (newNotice.extra && newNotice.status) {
const color = {
todo: '',
processing: 'blue',
urgent: 'red',
doing: 'gold',
}[newNotice.status];
newNotice.extra = (
<Tag
color={color}
style={{
marginRight: 0,
}}
>
{newNotice.extra}
</Tag>
);
}

return newNotice;
});
return groupBy(newNotices, 'type');
};

const getUnreadData = (noticeData) => {
const unreadMsg = {};
Object.keys(noticeData).forEach((key) => {
const value = noticeData[key];

if (!unreadMsg[key]) {
unreadMsg[key] = 0;
}

if (Array.isArray(value)) {
unreadMsg[key] = value.filter((item) => !item.read).length;
}
});
return unreadMsg;
};

const NoticeIconView = () => {
const { initialState } = useModel('@@initialState');
const { currentUser } = initialState || {};
const [notices, setNotices] = useState([]);
useEffect(() => {
getNotices().then(({ data }) => setNotices(data || []));
}, []);
const noticeData = getNoticeData(notices);
const unreadMsg = getUnreadData(noticeData || {});

const changeReadState = (id) => {
setNotices(
notices.map((item) => {
const notice = { ...item };

if (notice.id === id) {
notice.read = true;
}

return notice;
}),
);
};

const clearReadState = (title, key) => {
setNotices(
notices.map((item) => {
const notice = { ...item };

if (notice.type === key) {
notice.read = true;
}

return notice;
}),
);
message.success(`${'清空了'} ${title}`);
};

return (
<NoticeIcon
className={styles.action}
count={currentUser && currentUser.unreadCount}
onItemClick={(item) => {
changeReadState(item.id);
}}
onClear={(title, key) => clearReadState(title, key)}
loading={false}
clearText="清空"
viewMoreText="查看更多"
onViewMore={() => message.info('Click on view more')}
clearClose
>
<NoticeIcon.Tab
tabKey="notification"
count={unreadMsg.notification}
list={noticeData.notification}
title="通知"
emptyText="你已查看所有通知"
showViewMore
/>
<NoticeIcon.Tab
tabKey="message"
count={unreadMsg.message}
list={noticeData.message}
title="消息"
emptyText="您已读完所有消息"
showViewMore
/>
<NoticeIcon.Tab
tabKey="event"
title="待办"
emptyText="你已完成所有待办"
count={unreadMsg.event}
list={noticeData.event}
showViewMore
/>
</NoticeIcon>
);
};

export default NoticeIconView;

+ 35
- 0
src/components/NoticeIcon/index.less Datei anzeigen

@@ -0,0 +1,35 @@
@import '~antd/es/style/themes/default.less';

.popover {
position: relative;
width: 336px;
}

.noticeButton {
display: inline-block;
cursor: pointer;
transition: all 0.3s;
}
.icon {
padding: 4px;
vertical-align: middle;
}

.badge {
font-size: 16px;
}

.tabs {
:global {
.ant-tabs-nav-list {
margin: auto;
}

.ant-tabs-nav-scroll {
text-align: center;
}
.ant-tabs-bar {
margin-bottom: 0;
}
}
}

+ 98
- 0
src/components/RightContent/AvatarDropdown.jsx Datei anzeigen

@@ -0,0 +1,98 @@
import React, { useCallback } from 'react';
import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons';
import { Avatar, Menu, Spin } from 'antd';
import { history, useModel } from 'umi';
import { stringify } from 'querystring';
import HeaderDropdown from '../HeaderDropdown';
import styles from './index.less';
import api from '@/services/api';

/**
* 退出登录,并且将当前的 url 保存
*/
const loginOut = async () => {
await api.outLogin();
const { query = {}, pathname } = history.location;
const { redirect } = query; // Note: There may be security issues, please note

if (window.location.pathname !== '/user/login' && !redirect) {
history.replace({
pathname: '/user/login',
search: stringify({
redirect: pathname,
}),
});
}
};

const AvatarDropdown = ({ menu }) => {
const { initialState, setInitialState } = useModel('@@initialState');
const onMenuClick = useCallback(
(event) => {
const { key } = event;

if (key === 'logout' && initialState) {
setInitialState({ ...initialState, currentUser: undefined });
loginOut();
return;
}

history.push(`/account/${key}`);
},
[initialState, setInitialState],
);
const loading = (
<span className={`${styles.action} ${styles.account}`}>
<Spin
size="small"
style={{
marginLeft: 8,
marginRight: 8,
}}
/>
</span>
);

if (!initialState) {
return loading;
}

const { currentUser } = initialState;

if (!currentUser || !currentUser.succeeded) {
return loading;
}

const menuHeaderDropdown = (
<Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
{menu && (
<Menu.Item key="center">
<UserOutlined />
个人中心1
</Menu.Item>
)}
{menu && (
<Menu.Item key="settings">
<SettingOutlined />
个人设置
</Menu.Item>
)}
{menu && <Menu.Divider />}

<Menu.Item key="logout">
<LogoutOutlined />
退出登录
</Menu.Item>
</Menu>
);
return (
<HeaderDropdown overlay={menuHeaderDropdown}>
<span className={`${styles.action} ${styles.account}`}>
<Avatar size="small" className={styles.avatar} gap='4' alt="avatar" >{currentUser.data.name}</Avatar>
<span className={`${styles.name} anticon`}>{currentUser.data.name}</span>
</span>
</HeaderDropdown>
);
};

export default AvatarDropdown;

+ 63
- 0
src/components/RightContent/index.jsx Datei anzeigen

@@ -0,0 +1,63 @@
import { Space } from 'antd';
import { QuestionCircleOutlined } from '@ant-design/icons';
import React from 'react';
import { useModel, SelectLang } from 'umi';
import Avatar from './AvatarDropdown';
import HeaderSearch from '../HeaderSearch';
import styles from './index.less';

const GlobalHeaderRight = () => {
const { initialState } = useModel('@@initialState');

if (!initialState || !initialState.settings) {
return null;
}

const { navTheme, layout } = initialState.settings;
let className = styles.right;

if ((navTheme === 'dark' && layout === 'top') || layout === 'mix') {
className = `${styles.right} ${styles.dark}`;
}

return (
<Space className={className}>
{/* <HeaderSearch
className={`${styles.action} ${styles.search}`}
placeholder="站内搜索"
defaultValue="umi ui"
options={[
{
label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>,
value: 'umi ui',
},
{
label: <a href="next.ant.design">Ant Design</a>,
value: 'Ant Design',
},
{
label: <a href="https://protable.ant.design/">Pro Table</a>,
value: 'Pro Table',
},
{
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
value: 'Pro Layout',
},
]} // onSearch={value => {
// }}
/> */}
{/* <span
className={styles.action}
onClick={() => {
window.open('https://pro.ant.design/docs/getting-started');
}}
>
<QuestionCircleOutlined />
</span> */}
<Avatar />
{/* <SelectLang className={styles.action} /> */}
</Space>
);
};

export default GlobalHeaderRight;

+ 62
- 0
src/components/RightContent/index.less Datei anzeigen

@@ -0,0 +1,62 @@
@import '~antd/es/style/themes/default.less';

@pro-header-hover-bg: rgba(0, 0, 0, 0.025);

.menu {
:global(.anticon) {
margin-right: 8px;
}
:global(.ant-dropdown-menu-item) {
min-width: 160px;
}
}

.right {
display: flex;
float: right;
height: 48px;
margin-left: auto;
overflow: hidden;
.action {
display: flex;
align-items: center;
height: 48px;
padding: 0 12px;
cursor: pointer;
transition: all 0.3s;
> span {
vertical-align: middle;
}
&:hover {
background: @pro-header-hover-bg;
}
&:global(.opened) {
background: @pro-header-hover-bg;
}
}
.search {
padding: 0 12px;
&:hover {
background: transparent;
}
}
.account {
.avatar {
margin-right: 8px;
color: @primary-color;
vertical-align: top;
background: rgba(226, 163, 163, 0.85);
}
}
}

.dark {
.action {
&:hover {
background: #252a3d;
}
&:global(.opened) {
background: #252a3d;
}
}
}

+ 128
- 0
src/components/TagView/Tags/index.jsx Datei anzeigen

@@ -0,0 +1,128 @@
import React, { useState, useRef, useEffect } from 'react';
import { Scrollbars } from 'react-custom-scrollbars';
import { CloseOutlined } from '@ant-design/icons';
import { history } from 'umi';
// import type { TagsItemType } from '../index';
import styles from './index.less';



const Tags = ({ tagList, closeTag, closeAllTag, closeOtherTag, refreshTag }) => {
const [left, setLeft] = useState(0);
const [top, setTop] = useState(0);
const [menuVisible, setMenuVisible] = useState(false);
const [currentTag, setCurrentTag] = useState();

const tagListRef = useRef();
const contextMenuRef = useRef();
const [currentPath, setCurrentPath] = useState();
useEffect(() => {
return () => {
document.body.removeEventListener('click', handleClickOutside);
};
}, []);

// 由于react的state不能及时穿透到 document.body.addEventListener去,需要在每次值发送改变时进行解绑和再次监听
useEffect(() => {
document.body.removeEventListener('click', handleClickOutside);
document.body.addEventListener('click', handleClickOutside);
}, [menuVisible]);

const handleClickOutside = (event) => {
const isOutside = !(contextMenuRef.current && contextMenuRef.current.contains(event.target));
if (isOutside && menuVisible) {
setMenuVisible(false);
}
};

const openContextMenu = (
event,
tag,
) => {
event.preventDefault();
const menuMinWidth = 105;
const clickX = event.clientX;
const clickY = event.clientY; //事件发生时鼠标的Y坐标
const clientWidth = tagListRef.current?.clientWidth || 0; // container width
const maxLeft = clientWidth - menuMinWidth; // left boundary
setCurrentTag(tag);
setMenuVisible(true);
setTop(clickY);

// 当鼠标点击位置大于左侧边界时,说明鼠标点击的位置偏右,将菜单放在左边
// 反之,当鼠标点击的位置偏左,将菜单放在右边
const Left = clickX > maxLeft ? clickX - menuMinWidth + 15 : clickX;
setLeft(Left);
};

return (
<div className={styles.tags_wrapper} ref={tagListRef}>
<Scrollbars autoHide autoHideTimeout={1000} autoHideDuration={200}>
{tagList.map((item, i) => {
if (item.path == history.location.pathname) {
item.active = true;
} else {
item.active = false;
}
return (
<div
key={item.path}
className={item.active ? `${styles.item} ${styles.active}` : styles.item}
onClick={(e) => {
e.stopPropagation();
setCurrentPath(item.path);
history.push({ pathname: item.path, query: item.query });
}}
onContextMenu={(e) => openContextMenu(e, item)}
>
<span>{item.title}</span>
{i !== 0 && (
<CloseOutlined
className={styles.icon_close}
onClick={(e) => {
e.stopPropagation();
closeTag && closeTag(item);
}}
/>
)}
</div>
);
})}
</Scrollbars>
{menuVisible ? (
<ul
className={styles.contextmenu}
style={{ left: `${left}px`, top: `${top}px` }}
ref={contextMenuRef}
>
<li
onClick={() => {
setMenuVisible(false);
currentTag && refreshTag && refreshTag(currentTag);
}}
>
刷新
</li>
<li
onClick={() => {
setMenuVisible(false);
currentTag && closeOtherTag && closeOtherTag(currentTag);
}}
>
关闭其他
</li>
<li
onClick={() => {
setMenuVisible(false);
closeAllTag && closeAllTag();
}}
>
关闭所有
</li>
</ul>
) : null}
</div>
);
};

export default Tags;

+ 88
- 0
src/components/TagView/Tags/index.less Datei anzeigen

@@ -0,0 +1,88 @@
@primary: #FA541C;
span{
-moz-user-select: none;
user-select: none
}
.tags_wrapper {
position: relative;
width: 100%;
height: 34px;
line-height: 34px;
background: #fff;
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.12), 0 0 1px 0 rgba(0, 0, 0, 0.01);
.item {
position: relative;
display: inline-block;
height: 28px;
margin-top: 2px;
margin-left: 5px;
padding: 0 8px;
color: #495060;
font-size: 13px;
line-height: 26px;
background: #fff;
border: 1px solid #d8dce5;
cursor: pointer;

&:first-of-type {
margin-left: 15px;
}

&:last-of-type {
margin-right: 15px;
}

&.active {
color: #fff;
background-color: @primary;
border-color: @primary;

&::before {
position: relative;
display: inline-block;
width: 8px;
height: 8px;
margin-right: 2px;
background: #fff;
border-radius: 50%;
content: '';
}
}
}

.icon_close {
position: relative;
top: -1px;
margin-left: 6px;
font-size: 10px;

&:hover {
color: red;
}
}

.contextmenu {
position: fixed;
z-index: 3000;
margin: 0;
padding: 5px 0;
color: #333;
font-weight: 400;
font-size: 12px;
list-style-type: none;
background: #fff;
border-radius: 4px;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);

li {
margin: 0;
padding: 2px 16px;
line-height: 24px;
cursor: pointer;

&:hover {
background: #eee;
}
}
}
}

+ 174
- 0
src/components/TagView/index.jsx Datei anzeigen

@@ -0,0 +1,174 @@
import React, { useState, useEffect, useRef } from 'react';
import { RouteContext } from '@ant-design/pro-layout';

import { history } from 'umi';
import Tags from './Tags';
import styles from './index.less';

// export type TagsItemType = {
// title?: string;
// path?: string;
// active: boolean;
// query?: any;
// children: any;
// refresh: number;
// };

// interface IProps {
// home: string;
// current: string;
// }

/**
* @component TagView 标签页组件
*/
const TagView = ({ children, home, current }) => {
// if (current=='/') {
// current = '/welcome'
// }
console.log('current',current);
const [tagList, setTagList] = useState([]);
const routeContextRef = useRef();

useEffect(() => {
if (routeContextRef?.current) {
handleOnChange(routeContextRef.current);
}
}, [current]);

// 初始化 visitedViews,设置project为首页
const initTags = (routeContext) => {
if (tagList.length === 0 && routeContext.menuData) {
const firstTag = routeContext.menuData.filter((el) => el.path === home)[0];
if (!firstTag) {
firstTag = routeContext.menuData[0];
}
const title = firstTag.name;
const path = firstTag.path;
history.push({ pathname: firstTag.path, query: firstTag.query });
setTagList([
{
title,
path,
children,
refresh: 0,
active: true,
},
]);
}
};

// 监听路由改变
const handleOnChange = (routeContext) => {
const { currentMenu } = routeContext;

// tags初始化
if (tagList.length === 0) {
return initTags(routeContext);
}
// 判断是否已打开过该页面
let hasOpen = false;
const tagsCopy = tagList.map((item) => {
if (currentMenu?.path === item.path) {
hasOpen = true;
// 刷新浏览器时,重新覆盖当前 path 的 children
return { ...item, active: true, children };
} else {
return { ...item, active: false };
}
});

// 没有该tag时追加一个,并打开这个tag页面
if (!hasOpen) {
const title = routeContext.title || '';
const path = currentMenu?.path;
tagsCopy.push({
title,
path,
children,
refresh: 0,
active: true,
});
}
return setTagList(tagsCopy);
};

// 关闭标签
const handleCloseTag = (tag) => {
const tagsCopy = tagList.map((el, i) => ({ ...el }));

// 判断关闭标签是否处于打开状态
tagList.forEach((el, i) => {
if (el.path === tag.path && tag.active) {
const next = tagList[i - 1];
next.active = true;
history.push({ pathname: next?.path, query: next?.query });
}
});

setTagList(tagsCopy.filter((el) => el.path !== tag?.path));
};

// 关闭所有标签
const handleCloseAll = () => {
const tagsCopy = tagList.filter((el) => el.path === home);
history.push(home);
setTagList(tagsCopy);
};

// 关闭其他标签
const handleCloseOther = (tag) => {
const tagsCopy = tagList.filter(
(el) => el.path === home || el.path === tag.path,
);
history.push({ pathname: tag?.path, query: tag?.query });
setTagList(tagsCopy);
};

// 刷新选择的标签
const handleRefreshTag = (tag) => {
const tagsCopy = tagList.map((item) => {
if (item.path === tag.path) {
history.push({ pathname: tag?.path, query: tag?.query });
return { ...item, refresh: item.refresh + 1, active: true };
}
return { ...item, active: false };
});
setTagList(tagsCopy);
};

return (
<>
<RouteContext.Consumer>
{(value) => {
// console.log(value);
routeContextRef.current = value;
return null;
}}
</RouteContext.Consumer>
<div className={styles.tag_view}>
<div className={styles.tags_container}>
<Tags
tagList={tagList}
closeTag={handleCloseTag}
closeAllTag={handleCloseAll}
closeOtherTag={handleCloseOther}
refreshTag={handleRefreshTag}
/>
</div>
</div>

{tagList.map((item) => {
return (
<div key={item.path} style={{ display: item.active ? 'block' : 'none' }}>
<div key={item.refresh}>
{item.children}
</div>
</div>
);
})}
</>
);
};

export default TagView;

+ 11
- 0
src/components/TagView/index.less Datei anzeigen

@@ -0,0 +1,11 @@
.tag_view {
.tags_container {
position: relative;
top: -22px;
right: 24px;
z-index: 199;
width: calc(100% + 48px);
height: 36px;
border: 0px dashed coral;
}
}

+ 271
- 0
src/components/index.md Datei anzeigen

@@ -0,0 +1,271 @@
---
title: 业务组件
sidemenu: false
---

> 此功能由[dumi](https://d.umijs.org/zh-CN/guide/advanced#umi-%E9%A1%B9%E7%9B%AE%E9%9B%86%E6%88%90%E6%A8%A1%E5%BC%8F)提供,dumi 是一个 📖 为组件开发场景而生的文档工具,用过的都说好。

# 业务组件

这里列举了 Pro 中所有用到的组件,这些组件不适合作为组件库,但是在业务中却真实需要。所以我们准备了这个文档,来指导大家是否需要使用这个组件。

## Footer 页脚组件

这个组件自带了一些 Pro 的配置,你一般都需要改掉它的信息。

```tsx
/**
* background: '#f0f2f5'
*/
import React from 'react';
import Footer from '@/components/Footer';

export default () => <Footer />;
```

## HeaderDropdown 头部下拉列表

HeaderDropdown 是 antd Dropdown 的封装,但是增加了移动端的特殊处理,用法也是相同的。

```tsx
/**
* background: '#f0f2f5'
*/
import { Button, Menu } from 'antd';
import React from 'react';
import HeaderDropdown from '@/components/HeaderDropdown';

export default () => {
const menuHeaderDropdown = (
<Menu selectedKeys={[]}>
<Menu.Item key="center">个人中心</Menu.Item>
<Menu.Item key="settings">个人设置</Menu.Item>
<Menu.Divider />
<Menu.Item key="logout">退出登录</Menu.Item>
</Menu>
);
return (
<HeaderDropdown overlay={menuHeaderDropdown}>
<Button>hover 展示菜单</Button>
</HeaderDropdown>
);
};
```

## HeaderSearch 头部搜索框

一个带补全数据的输入框,支持收起和展开 Input

```tsx
/**
* background: '#f0f2f5'
*/
import { Button, Menu } from 'antd';
import React from 'react';
import HeaderSearch from '@/components/HeaderSearch';

export default () => {
return (
<HeaderSearch
placeholder="站内搜索"
defaultValue="umi ui"
options={[
{ label: 'Ant Design Pro', value: 'Ant Design Pro' },
{
label: 'Ant Design',
value: 'Ant Design',
},
{
label: 'Pro Table',
value: 'Pro Table',
},
{
label: 'Pro Layout',
value: 'Pro Layout',
},
]}
onSearch={(value) => {
}}
/>
);
};
```

### API

| 参数 | 说明 | 类型 | 默认值 |
| --------------- | ---------------------------------- | ---------------------------- | ------ |
| value | 输入框的值 | `string` | - |
| onChange | 值修改后触发 | `(value?: string) => void` | - |
| onSearch | 查询后触发 | `(value?: string) => void` | - |
| options | 选项菜单的的列表 | `{label,value}[]` | - |
| defaultVisible | 输入框默认是否显示,只有第一次生效 | `boolean` | - |
| visible | 输入框是否显示 | `boolean` | - |
| onVisibleChange | 输入框显示隐藏的回调函数 | `(visible: boolean) => void` | - |

## NoticeIcon 通知工具

通知工具提供一个展示多种通知信息的界面。

```tsx
/**
* background: '#f0f2f5'
*/
import { message } from 'antd';
import React from 'react';
import NoticeIcon from '@/components/NoticeIcon/NoticeIcon';

export default () => {
const list = [
{
id: '000000001',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
title: '你收到了 14 份新周报',
datetime: '2017-08-09',
type: 'notification',
},
{
id: '000000002',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
title: '你推荐的 曲妮妮 已通过第三轮面试',
datetime: '2017-08-08',
type: 'notification',
},
];
return (
<NoticeIcon
count={10}
onItemClick={(item) => {
message.info(`${item.title} 被点击了`);
}}
onClear={(title: string, key: string) => message.info('点击了清空更多')}
loading={false}
clearText="清空"
viewMoreText="查看更多"
onViewMore={() => message.info('点击了查看更多')}
clearClose
>
<NoticeIcon.Tab
tabKey="notification"
count={2}
list={list}
title="通知"
emptyText="你已查看所有通知"
showViewMore
/>
<NoticeIcon.Tab
tabKey="message"
count={2}
list={list}
title="消息"
emptyText="您已读完所有消息"
showViewMore
/>
<NoticeIcon.Tab
tabKey="event"
title="待办"
emptyText="你已完成所有待办"
count={2}
list={list}
showViewMore
/>
</NoticeIcon>
);
};
```

### NoticeIcon API

| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| count | 有多少未读通知 | `number` | - |
| bell | 铃铛的图表 | `ReactNode` | - |
| onClear | 点击清空数据按钮 | `(tabName: string, tabKey: string) => void` | - |
| onItemClick | 未读消息列被点击 | `(item: API.NoticeIconData, tabProps: NoticeIconTabProps) => void` | - |
| onViewMore | 查看更多的按钮点击 | `(tabProps: NoticeIconTabProps, e: MouseEvent) => void` | - |
| onTabChange | 通知 Tab 的切换 | `(tabTile: string) => void;` | - |
| popupVisible | 通知显示是否展示 | `boolean` | - |
| onPopupVisibleChange | 通知信息显示隐藏的回调函数 | `(visible: boolean) => void` | - |
| clearText | 清空按钮的文字 | `string` | - |
| viewMoreText | 查看更多的按钮文字 | `string` | - |
| clearClose | 展示清空按钮 | `boolean` | - |
| emptyImage | 列表为空时的兜底展示 | `ReactNode` | - |

### NoticeIcon.Tab API

| 参数 | 说明 | 类型 | 默认值 |
| ------------ | ------------------ | ------------------------------------ | ------ |
| count | 有多少未读通知 | `number` | - |
| title | 通知 Tab 的标题 | `ReactNode` | - |
| showClear | 展示清除按钮 | `boolean` | `true` |
| showViewMore | 展示加载更 | `boolean` | `true` |
| tabKey | Tab 的唯一 key | `string` | - |
| onClick | 子项的单击事件 | `(item: API.NoticeIconData) => void` | - |
| onClear | 清楚按钮的点击 | `()=>void` | - |
| emptyText | 为空的时候测试 | `()=>void` | - |
| viewMoreText | 查看更多的按钮文字 | `string` | - |
| onViewMore | 查看更多的按钮点击 | `( e: MouseEvent) => void` | - |
| list | 通知信息的列表 | `API.NoticeIconData` | - |

### NoticeIconData

```tsx | pure
export interface NoticeIconData {
id: string;
key: string;
avatar: string;
title: string;
datetime: string;
type: string;
read?: boolean;
description: string;
clickClose?: boolean;
extra: any;
status: string;
}
```

## RightContent

RightContent 是以上几个组件的组合,同时新增了 plugins 的 `SelectLang` 插件。

```tsx | pure
<Space>
<HeaderSearch
placeholder="站内搜索"
defaultValue="umi ui"
options={[
{ label: <a href="https://umijs.org/zh/guide/umi-ui.html">umi ui</a>, value: 'umi ui' },
{
label: <a href="next.ant.design">Ant Design</a>,
value: 'Ant Design',
},
{
label: <a href="https://protable.ant.design/">Pro Table</a>,
value: 'Pro Table',
},
{
label: <a href="https://prolayout.ant.design/">Pro Layout</a>,
value: 'Pro Layout',
},
]}
/>
<Tooltip title="使用文档">
<span
className={styles.action}
onClick={() => {
window.location.href = 'https://pro.ant.design/docs/getting-started';
}}
>
<QuestionCircleOutlined />
</span>
</Tooltip>
<Avatar />
{REACT_APP_ENV && (
<span>
<Tag color={ENVTagColor[REACT_APP_ENV]}>{REACT_APP_ENV}</Tag>
</span>
)}
<SelectLang className={styles.action} />
</Space>
```

+ 61
- 0
src/e2e/baseLayout.e2e.js Datei anzeigen

@@ -0,0 +1,61 @@
const { uniq } = require('lodash');
const RouterConfig = require('../../config/config').default.routes;

const BASE_URL = `http://localhost:${process.env.PORT || 8000}`;

function formatter(routes, parentPath = '') {
const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
let result = [];
routes.forEach((item) => {
if (item.path && !item.path.startsWith('/')) {
result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
}
if (item.path && item.path.startsWith('/')) {
result.push(`${item.path}`.replace(/\/{1,}/g, '/'));
}
if (item.routes) {
result = result.concat(
formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
);
}
});
return uniq(result.filter((item) => !!item));
}

beforeEach(async () => {
await page.goto(`${BASE_URL}`);
await page.evaluate(() => {
localStorage.setItem('antd-pro-authority', '["admin"]');
});
});

describe('Ant Design Pro E2E test', () => {
const testPage = (path) => async () => {
await page.goto(`${BASE_URL}${path}`);
await page.waitForSelector('footer', {
timeout: 2000,
});
const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
};

const routers = formatter(RouterConfig);
routers.forEach((route) => {
it(`test pages ${route}`, testPage(route));
});

it('topmenu should have footer', async () => {
const params = '?navTheme=light&layout=topmenu';
await page.goto(`${BASE_URL}${params}`);
await page.waitForSelector('footer', {
timeout: 2000,
});

const haveFooter = await page.evaluate(
() => document.getElementsByTagName('footer').length > 0,
);
expect(haveFooter).toBeTruthy();
});
});

+ 101
- 0
src/global.jsx Datei anzeigen

@@ -0,0 +1,101 @@
import { Button, message, notification } from 'antd';
import { useIntl } from 'umi';
import defaultSettings from '../config/defaultSettings';
const { pwa } = defaultSettings;
const isHttps = document.location.protocol === 'https:'; // 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) => {
const e = event;

const reloadSW = async () => {
// Check if there is sw whose state is waiting in ServiceWorkerRegistration
// https://developer.mozilla.org/en-US/docs/Web/kitchen/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],
);
}); // Refresh current page to use the updated HTML and other assets after SW has skiped waiting

window.location.reload(true);
return true;
};

const key = `open${Date.now()}`;
const btn = (
<Button
type="primary"
onClick={() => {
notification.close(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();
}); // remove all caches

if (window.caches && window.caches.keys()) {
caches.keys().then((keys) => {
keys.forEach((key) => {
caches.delete(key);
});
});
}
}

+ 57
- 0
src/global.less Datei anzeigen

@@ -0,0 +1,57 @@
@import '~antd/es/style/themes/default.less';

html,
body,
#root {
height: 100%;
}

.colorWeak {
filter: invert(80%);
}

.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: @screen-xs) {
.ant-table {
width: 100%;
overflow-x: auto;
&-thead > tr,
&-tbody > tr {
> th,
> td {
white-space: pre;
> span {
display: block;
}
}
}
}
}

// Compatible with IE11
@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
body .ant-design-pro > .ant-layout {
min-height: 100vh;
}
}

+ 7
- 0
src/global_data.js Datei anzeigen

@@ -0,0 +1,7 @@
export function getApiUrl() {
if (process.env.UMI_ENV === 'dev') {
return 'http://localhost:5006/';
} else if (process.env.UMI_ENV === 'prod') {
return 'http://localhost:7002/';
}
}

+ 24
- 0
src/locales/en-US.js Datei anzeigen

@@ -0,0 +1,24 @@
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 {
'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
- 0
src/locales/en-US/component.js Datei anzeigen

@@ -0,0 +1,5 @@
export default {
'component.tagSelect.expand': 'Expand',
'component.tagSelect.collapse': 'Collapse',
'component.tagSelect.all': 'All',
};

+ 17
- 0
src/locales/en-US/globalHeader.js Datei anzeigen

@@ -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
- 0
src/locales/en-US/menu.js Datei anzeigen

@@ -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',
};

+ 70
- 0
src/locales/en-US/pages.js Datei anzeigen

@@ -0,0 +1,70 @@
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.advancedComponent': 'Advanced Component',
'pages.welcome.link': 'Welcome',
'pages.welcome.advancedLayout': 'Advanced Layout',
'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': 'bacth deletion',
'pages.searchTable.batchApproval': 'batch approval',
};

+ 6
- 0
src/locales/en-US/pwa.js Datei anzeigen

@@ -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
- 0
src/locales/en-US/settingDrawer.js Datei anzeigen

@@ -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
- 0
src/locales/en-US/settings.js Datei anzeigen

@@ -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',
};

+ 24
- 0
src/locales/zh-CN.js Datei anzeigen

@@ -0,0 +1,24 @@
import component from './zh-CN/component';
import globalHeader from './zh-CN/globalHeader';
import menu from './zh-CN/menu';
import pwa from './zh-CN/pwa';
import settingDrawer from './zh-CN/settingDrawer';
import settings from './zh-CN/settings';
import pages from './zh-CN/pages';
export default {
'navBar.lang': '语言',
'layout.user.link.help': '帮助',
'layout.user.link.privacy': '隐私',
'layout.user.link.terms': '条款',
'app.copyright.produced': '2021 四川黑菠萝科技有限公司',
'app.preview.down.block': '下载此页面到本地项目',
'app.welcome.link.fetch-blocks': '获取全部区块',
'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
...pages,
...globalHeader,
...menu,
...settingDrawer,
...settings,
...pwa,
...component,
};

+ 5
- 0
src/locales/zh-CN/component.js Datei anzeigen

@@ -0,0 +1,5 @@
export default {
'component.tagSelect.expand': '展开',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};

+ 17
- 0
src/locales/zh-CN/globalHeader.js Datei anzeigen

@@ -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': '查看更多',
};

+ 55
- 0
src/locales/zh-CN/menu.js Datei anzeigen

@@ -0,0 +1,55 @@
export default {
'menu.welcome': '欢迎',
'menu.usercenter': '个人管理',
'menu.franchiseecenter': '加盟商管理',
'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.DynamicSettings': 'asda',
};

+ 67
- 0
src/locales/zh-CN/pages.js Datei anzeigen

@@ -0,0 +1,67 @@
export default {
'pages.layouts.userLayout.title': '黑菠萝网络科技',
'pages.login.accountLogin.tab': '账户密码登录',
'pages.login.accountLogin.errorMessage': '错误的用户名和密码(admin/ant.design)',
'pages.login.failure': '登录失败,请重试!',
'pages.login.success': '登录成功!',
'pages.login.username.placeholder': '请输入用户名',
'pages.login.username.required': '用户名是必填项!',
'pages.login.password.placeholder': '请输入密码',
'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.advancedComponent': '高级表格',
'pages.welcome.link': '欢迎使用',
'pages.welcome.advancedLayout': '高级布局',
'pages.welcome.alertMessage': '黑菠萝ERP系统',
'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
- 0
src/locales/zh-CN/pwa.js Datei anzeigen

@@ -0,0 +1,6 @@
export default {
'app.pwa.offline': '当前处于离线状态',
'app.pwa.serviceworker.updated': '有新内容',
'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
'app.pwa.serviceworker.updated.ok': '刷新',
};

+ 31
- 0
src/locales/zh-CN/settingDrawer.js Datei anzeigen

@@ -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
- 0
src/locales/zh-CN/settings.js Datei anzeigen

@@ -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': '关',
};

+ 19
- 0
src/locales/zh-TW.js Datei anzeigen

@@ -0,0 +1,19 @@
import component from './zh-TW/component';
import globalHeader from './zh-TW/globalHeader';
import menu from './zh-TW/menu';
import pwa from './zh-TW/pwa';
import settingDrawer from './zh-TW/settingDrawer';
import settings from './zh-TW/settings';
export default {
'navBar.lang': '語言',
'layout.user.link.help': '幫助',
'layout.user.link.privacy': '隱私',
'layout.user.link.terms': '條款',
'app.preview.down.block': '下載此頁面到本地項目',
...globalHeader,
...menu,
...settingDrawer,
...settings,
...pwa,
...component,
};

+ 5
- 0
src/locales/zh-TW/component.js Datei anzeigen

@@ -0,0 +1,5 @@
export default {
'component.tagSelect.expand': '展開',
'component.tagSelect.collapse': '收起',
'component.tagSelect.all': '全部',
};

+ 17
- 0
src/locales/zh-TW/globalHeader.js Datei anzeigen

@@ -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': '查看更多',
};

+ 52
- 0
src/locales/zh-TW/menu.js Datei anzeigen

@@ -0,0 +1,52 @@
export default {
'menu.welcome': '歡迎',
'menu.more-blocks': '更多區塊',
'menu.home': '首頁',
'menu.login': '登錄',
'menu.admin': '权限',
'menu.admin.sub-page': '二级管理页',
'menu.exception.403': '403',
'menu.exception.404': '404',
'menu.exception.500': '500',
'menu.register': '註冊',
'menu.register.result': '註冊結果',
'menu.dashboard': 'Dashboard',
'menu.dashboard.analysis': '分析頁',
'menu.dashboard.monitor': '監控頁',
'menu.dashboard.workplace': '工作臺',
'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.account': '個人頁',
'menu.account.center': '個人中心',
'menu.account.settings': '個人設置',
'menu.account.trigger': '觸發報錯',
'menu.account.logout': '退出登錄',
'menu.exception': '异常页',
'menu.exception.not-permission': '403',
'menu.exception.not-find': '404',
'menu.exception.server-error': '500',
'menu.exception.trigger': '触发错误',
'menu.editor': '圖形編輯器',
'menu.editor.flow': '流程編輯器',
'menu.editor.mind': '腦圖編輯器',
'menu.editor.koni': '拓撲編輯器',
};

+ 6
- 0
src/locales/zh-TW/pwa.js Datei anzeigen

@@ -0,0 +1,6 @@
export default {
'app.pwa.offline': '當前處於離線狀態',
'app.pwa.serviceworker.updated': '有新內容',
'app.pwa.serviceworker.updated.hint': '請點擊“刷新”按鈕或者手動刷新頁面',
'app.pwa.serviceworker.updated.ok': '刷新',
};

+ 31
- 0
src/locales/zh-TW/settingDrawer.js Datei anzeigen

@@ -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
- 0
src/locales/zh-TW/settings.js Datei anzeigen

@@ -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
- 0
src/manifest.json Datei anzeigen

@@ -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
- 0
src/pages/404.jsx Datei anzeigen

@@ -0,0 +1,18 @@
import { Button, Result } from 'antd';
import React from 'react';
import { history } from 'umi';

const NoFoundPage = () => (
<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;

+ 52
- 0
src/pages/Admin.jsx Datei anzeigen

@@ -0,0 +1,52 @@
import React from 'react';
import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons';
import { Card, Typography, Alert } from 'antd';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { useIntl } from 'umi';
export default () => {
const intl = useIntl();
return (
<PageHeaderWrapper
content={intl.formatMessage({
id: 'pages.admin.subPage.title',
defaultMessage: 'This page can only be viewed by admin',
})}
>
<Card>
<Alert
message={intl.formatMessage({
id: 'pages.welcome.alertMessage',
defaultMessage: 'Faster and stronger heavy-duty components have been released.',
})}
type="success"
showIcon
banner
style={{
margin: -12,
marginBottom: 48,
}}
/>
<Typography.Title
level={2}
style={{
textAlign: 'center',
}}
>
<SmileTwoTone /> Ant Design Pro <HeartTwoTone twoToneColor="#eb2f96" /> You
</Typography.Title>
</Card>
<p
style={{
textAlign: 'center',
marginTop: 24,
}}
>
Want to add more pages? Please refer to{' '}
<a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer">
use block
</a>
</p>
</PageHeaderWrapper>
);
};

+ 47
- 0
src/pages/Welcome.jsx Datei anzeigen

@@ -0,0 +1,47 @@
import React from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons';
import { Card, Alert, Typography } from 'antd';
import { useIntl, FormattedMessage } from 'umi';
import styles from './Welcome.less';
// import MembershipAnalysis from './member/memberAnalysis';

const CodePreview = ({ children }) => (
<pre className={styles.pre}>
<code>
<Typography.Text copyable>{children}</Typography.Text>
</code>
</pre>
);

export default () => {
const intl = useIntl();
return (
<PageContainer>
{/* <MembershipAnalysis /> */}
{/* <Card>
<Alert
message={intl.formatMessage({
id: 'pages.welcome.alertMessage',
defaultMessage: 'Faster and stronger heavy-duty components have been released.',
})}
type="success"
showIcon
banner
style={{
margin: -12,
marginBottom: 48,
}}
/>
<Typography.Title
level={2}
style={{
textAlign: 'center',
}}
>
<SmileTwoTone /> Black-Pa <HeartTwoTone twoToneColor="#eb2f96" /> You
</Typography.Title>
</Card> */}
</PageContainer>
);
};

+ 8
- 0
src/pages/Welcome.less Datei anzeigen

@@ -0,0 +1,8 @@
@import '~antd/lib/style/themes/default.less';

.pre {
margin: 12px 0;
padding: 12px 20px;
background: @input-bg;
box-shadow: @card-shadow;
}

+ 69
- 0
src/pages/admin/users/components/CreateForm.jsx Datei anzeigen

@@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { Modal, Form, Input, Button, Select, TreeSelect } from 'antd';
const CreateForm = (props) => {
const { TextArea } = Input;
return (
<Modal
title={props.values.id ? '用户编辑' : '用户新增'}
width={640}
visible={props.modalVisible}
bodyStyle={{ padding: '32px 40px 48px' }}
footer={null}
onCancel={() => {
props.onCancel();
}}
destroyOnClose
>
<Form
layout="horizontal"
labelCol={{
span: 4,
}}
preserve={false}
initialValues={props.values}
onFinish={props.onFinish}
>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>
<Form.Item name="name" label="姓名" rules={[{ required: true, max: 64 }]}>
<Input placeholder="请输姓名" />
</Form.Item>
<Form.Item name="account" label="账号" rules={[{ required: true, max: 64 }]}>
<Input placeholder="请输入账号" />
</Form.Item>
{props.values.id ? (
''
) : (
<Form.Item
hidden={props.values.id ? true : false}
name="password"
label="密码"
rules={[{ required: true, max: 64 }]}
>
<Input.Password placeholder="请输入密码" />
</Form.Item>
)}
<Form.Item name="orgId" label="上级机构" rules={[{ required: true, max: 64 }]}>
<TreeSelect
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={props.treeDatas}
placeholder="请选择机构"
treeDefaultExpandAll
/>
</Form.Item>
<Form.Item name="remark" label="备注">
<TextArea rows={4} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
);
};

export default CreateForm;

+ 64
- 0
src/pages/admin/users/components/OrgForm.jsx Datei anzeigen

@@ -0,0 +1,64 @@
import React, { useEffect, useState } from 'react';
import { Modal, Tree, Checkbox, Spin, Button, Input } from 'antd';
import ProTable, { ProColumns, ActionType, TableDropdown } from '@ant-design/pro-table';

import { Page } from '../../../org/roles/service';

const columns = [
{
title: '角色名称',
dataIndex: 'name',
hideInSearch: true,
},

{
title: '备注',
dataIndex: 'remark',
valueType: 'textarea',
hideInSearch: true,
},
];
const OrgForm = (props) => {
const [options, setOptions] = useState();
const [loading, setLoading] = useState(undefined);
const [keys, setSelectedRows] = useState();
// // componentDidMount
// useEffect(() => {
// setLoading(true);
// async function getRoles() {
// if (loading) {
// return;
// }
// }

// getRoles();
// }, []);

const renderContent = () => {
return (
<Tree
defaultExpandAll={true}
checkable
onCheck={(e) => setSelectedRows(e)}
treeData={props.treeDatas}
/>
);
};

return (
<Modal
width={640}
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose
title={`给 ${props.values.name} 授权数据`}
visible={props.updateModalVisible}
onOk={() => props.onSubmit(keys)}
onCancel={() => props.onCancel()}
maskClosable={false}
>
{loading ? <Spin /> : renderContent()}
</Modal>
);
};

export default OrgForm;

+ 85
- 0
src/pages/admin/users/components/RoleForm.jsx Datei anzeigen

@@ -0,0 +1,85 @@
import React, { useEffect, useState } from 'react';
import { Modal, Table, Checkbox, Spin, Button, Input } from 'antd';
import ProTable, { ProColumns, ActionType, TableDropdown } from '@ant-design/pro-table';

import { Page } from '../../../org/roles/service';

const columns = [
{
title: '角色名称',
dataIndex: 'name',
hideInSearch: true,
},

{
title: '备注',
dataIndex: 'remark',
valueType: 'textarea',
hideInSearch: true,
},
];
const RoleForm = (props) => {
const [options, setOptions] = useState();
const [loading, setLoading] = useState(undefined);
const [selectedRowsState, setSelectedRows] = useState();
// // componentDidMount
// useEffect(() => {
// setLoading(true);
// async function getRoles() {
// if (loading) {
// return;
// }
// }

// getRoles();
// }, []);

const renderContent = () => {
return (
<ProTable
rowKey="id"
toolBarRender={false}
search={false}
pagination={false}
request={async (params) => {
let UserData = [];
var total = 0;
params.pageSize = 100;

//添加机构组织
await Page(params).then((r) => {
UserData = r.data.data;
total = r.data.total;
});
return {
data: UserData,
success: true,
total: total,
};
}}
columns={columns}
rowSelection={{
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
defaultSelectedRowKeys: ['596916ab-300a-464f-b825-4bc5c624b06e'],
}}
/>
);
};

return (
<Modal
width={640}
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose
title={`给 ${props.values.name} 分配角色`}
visible={props.updateModalVisible}
onOk={() => props.onSubmit(selectedRowsState)}
onCancel={() => props.onCancel()}
maskClosable={false}
>
{loading ? <Spin /> : renderContent()}
</Modal>
);
};

export default RoleForm;

+ 437
- 0
src/pages/admin/users/index.jsx Datei anzeigen

@@ -0,0 +1,437 @@
import React, { useState, useRef, useEffect } from 'react';
import { Modal, Card, Tree, Button, message, Drawer, Divider, Menu, Dropdown } from 'antd';
import { PlusOutlined, DownOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { PageContainer, FooterToolbar } from '@ant-design/pro-layout';
import ProTable, { ProColumns, ActionType, TableDropdown } from '@ant-design/pro-table';
import ProDescriptions from '@ant-design/pro-descriptions';
import CreateForm from './components/CreateForm';
import { queryUser, addUser, removeRule, updateUser, setRoles, sysUserGrantData } from './service';
import { gettree } from '../../org/orgamange/service';

import moment from 'moment';
import { useAccess } from 'umi';
import RoleForm from './components/RoleForm';
import OrgForm from './components/OrgForm';

//import { TableListItem as RoleData } from '../roles/data.d';

/**
* 添加节点
* @param fields
*/
const handleAdd = async (fields) => {
const hide = message.loading('正在添加');
try {
await addUser({ ...fields });
hide();
message.success('添加成功');
return true;
} catch (error) {
hide();
message.error('添加失败请重试!');
return false;
}
};

/**
* 更新节点
* @param fields
*/
const handleUpdate = async (fields) => {
const hide = message.loading('正在更新');
try {
await updateUser({ ...fields });
hide();

message.success('更新成功');
return true;
} catch (error) {
hide();
message.error('更新失败请重试!');
return false;
}
};

/**
* 分配角色
* @param fields
*/
const handleRoles = async (fields) => {
const hide = message.loading('正在分配角色');
try {
await setRoles({ ...fields });
hide();

message.success('分配角色成功');
return true;
} catch (error) {
hide();
message.error('分配角色失败请重试!');
return false;
}
};

/**
* 正在分配数据权限
* @param fields
*/
const handleGrantData = async (fields) => {
const hide = message.loading('正在分配数据权限');
try {
await sysUserGrantData({ ...fields });
hide();
message.success('分配数据权限成功');
return true;
} catch (error) {
hide();
message.error('分配数据权限失败请重试!');
return false;
}
};
/**
* 删除节点
* @param selectedRows
*/
const handleRemove = async (selectedRows) => {
const hide = message.loading('正在删除');
if (!selectedRows) return true;
try {
await removeRule({
ids: selectedRows.map((row) => row._id),
});
hide();
message.success('删除成功,即将刷新');
return true;
} catch (error) {
hide();
message.error('删除失败,请重试');
return false;
}
};
const TableList = () => {
const [createModalVisible, handleModalVisible] = useState();
const [updateRoleModalVisible, handleUpdateRoleModalVisible] = useState();
const [updateOrgModalVisible, handleUpdateOrgModalVisible] = useState();
const [stepFormValues, setStepFormValues] = useState({});
const actionRef = useRef();
const [row, setRow] = useState();
const [selectedRowsState, setSelectedRows] = useState();

const [treeData, setTreeData] = useState();
const [Pid, setKey] = useState();

const access = useAccess();
const columns = [
{
title: '关键词',
dataIndex: 'SearchValue',
hideInForm: true,
hideInTable: true,
tip: '可以输入账号,姓名',
placeholder: '',
},
{
title: '机构',
dataIndex: 'orgId',
hideInForm: true,
hideInTable: true,
hideInSearch: true,
},
{
title: '登录账号',
dataIndex: 'account',
tip: '登录账号是唯一的 key',
hideInSearch: true,
formItemProps: {
rules: [
{
required: true,
message: '登录账号必填!',
},
],
},
renderText: (dom, entity) => {
return <a onClick={() => setRow(entity)}>{dom}</a>;
},
},
{
title: '姓名',
dataIndex: 'name',
hideInSearch: true,
formItemProps: {
rules: [
{
required: true,
message: '用户名为必填项',
},
],
},
},

{
title: '创建时间',
search: false,
dataIndex: 'createAt',
hideInForm: true,
sorter: true,
renderText: (val) => moment(val).fromNow(),
},
{
title: '备注',
dataIndex: 'remark',
valueType: 'textarea',
hideInSearch: true,
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => (
<>
<a
onClick={() => {
handleModalVisible(true);
setStepFormValues(record);
}}
>
修改
</a>
<Divider type="vertical" />
<TableDropdown
key="actionGroup"
onSelect={(option) => {
if (option === 'resetPwd') {
Modal.confirm({
title: '重置密码',
icon: <ExclamationCircleOutlined />,
content: '是否确认重置密码',
okText: '确认',
cancelText: '取消',
onOk() {
},
});
} else if (option === 'grantRole') {
handleUpdateRoleModalVisible(true);
setStepFormValues(record);
} else if (option === 'grantData') {
handleUpdateOrgModalVisible(true);
setStepFormValues(record);
} else if (option === 'delete') {
Modal.confirm({
title: '删除用户',
icon: <ExclamationCircleOutlined />,
content: '是否确认删除用户',
okText: '确认',
cancelText: '取消',
});
}
}}
menus={[
{ key: 'resetPwd', name: '重置密码' },
{ key: 'grantRole', name: '授权角色' },
{ key: 'grantData', name: '授权数据' },
{ key: 'delete', name: '删除' },
]}
/>
</>
),
},
];
//初始化数据
useEffect(() => {
function initfranchiseeType() {
gettree().then((r) => {
setTreeData(r.data);
});
}
initfranchiseeType();
}, []);
return (
<PageContainer>
<ProTable
headerTitle="员工列表"
actionRef={actionRef}
rowKey="id"
pagination={{ defaultPageSize: 20 }}
search={{
labelWidth: 120,
}}
tableRender={(_, dom) => (
<div
style={{
display: 'flex',
width: '100%',
}}
>
<Card title="机构部门">
<Tree defaultExpandAll={true} onSelect={(e) => setKey(e[0])} treeData={treeData} />
</Card>
<div
style={{
flex: 1,
}}
>
{dom}
</div>
</div>
)}
toolBarRender={() => [
<Button type="primary" key="create" onClick={() => handleModalVisible(true)}>
<PlusOutlined /> 新建
</Button>,
]}
params={{
Pid,
}}
request={async (params) => {
let UserData = [];
var total = 0;
//添加机构组织
await queryUser(params).then((r) => {
console.log(r);
UserData = r.data.data;
total = r.data.total;
});
return {
data: UserData,
success: true,
total: total,
};
}}
columns={columns}
rowSelection={{
onChange: (_, selectedRows) => setSelectedRows(selectedRows),
}}
/>
{selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
已选择 <a style={{ fontWeight: 600 }}>{selectedRowsState.length}</a> 项&nbsp;&nbsp;
</div>
}
>
<Button
type="primary"
onClick={async () => {
await handleRemove(selectedRowsState);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}}
>
批量删除
</Button>
</FooterToolbar>
)}
<CreateForm
treeDatas={treeData}
onFinish={async (value) => {
var success = false;
if (value.id) {
success = await handleUpdate(value);
} else {
success = await handleAdd(value);
}
if (success) {
handleModalVisible(false);
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleModalVisible(false);
setStepFormValues({});
}}
modalVisible={createModalVisible}
values={stepFormValues || {}}
/>

{stepFormValues && Object.keys(stepFormValues).length ? (
<RoleForm
onSubmit={async (value) => {
if (value == null || value == undefined) {
handleUpdateRoleModalVisible(false);
setStepFormValues({});
return true;
}
const values = {
id: stepFormValues.id,
grantRoleIdList: value.map((key) => key.id),
};

const success = await handleRoles(values);
if (success) {
handleUpdateRoleModalVisible(false);
setStepFormValues({});
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleUpdateRoleModalVisible(false);
setStepFormValues({});
}}
updateModalVisible={updateRoleModalVisible}
values={stepFormValues}
/>
) : null}
{stepFormValues && Object.keys(stepFormValues).length ? (
<OrgForm
treeDatas={treeData}
onSubmit={async (value) => {
if (value == null || value == undefined) {
handleUpdateOrgModalVisible(false);
setStepFormValues({});
return true;
}
const values = {
id: stepFormValues.id,
grantRoleIdList: value,
};

const success = await handleGrantData(values);
if (success) {
handleUpdateOrgModalVisible(false);
setStepFormValues({});
if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleUpdateOrgModalVisible(false);
setStepFormValues({});
}}
updateModalVisible={updateOrgModalVisible}
values={stepFormValues}
/>
) : null}
<Drawer
width={600}
visible={!!row}
onClose={() => {
setRow(undefined);
}}
closable={false}
>
{row?.name && (
<ProDescriptions
column={2}
title={`${row?.name} 的详情`}
request={async () => ({
data: row || {},
})}
params={{
id: row?._id,
}}
columns={columns}
/>
)}
</Drawer>
</PageContainer>
);
};

export default TableList;

+ 0
- 0
Datei anzeigen


+ 55
- 0
src/pages/admin/users/service.js Datei anzeigen

@@ -0,0 +1,55 @@
import { request } from 'umi';

export async function queryUser(params) {
return request('/saasbase/api/sysUser/page', {
method: 'POST',
data: {
...params,
},
});
}

export async function removeRule(params) {
return request('/saasbase/api/rule', {
method: 'POST',
data: {
...params,
method: 'delete',
},
});
}

export async function addUser(params) {
return request('/saasbase/api/sysUser/add', {
method: 'POST',
data: {
...params,
},
});
}

export async function updateUser(params) {
return request('/saasbase/api/sysUser/add', {
method: 'POST',
data: {
...params,
},
});
}

export async function setRoles(params) {
return request(`/saasbase/api/sysUser/grantRole`, {
method: 'POST',
data: {
...params,
},
});
}
export async function sysUserGrantData(params) {
return request(`/saasbase/api/sysUser/grantData`, {
method: 'POST',
data: {
...params,
},
});
}

+ 55
- 0
src/pages/bom/bomreplace/components/CreateForm.jsx Datei anzeigen

@@ -0,0 +1,55 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Button, Select, InputNumber } from 'antd';

const CreateForm = (props) => {
const { Option, OptGroup } = Select;
return (
<Modal
title={props.values.id ? '编辑' : '新建'}
width={640}
visible={props.createModalVisible}
bodyStyle={{ padding: '32px 40px 48px' }}
footer={null}
onCancel={() => {
props.onCancel();
}}
destroyOnClose
>
<Form
layout="vertical"
preserve={false}
initialValues={props.values}
onFinish={props.onFinish}
>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>
<Form.Item
name="itemId"
label="原料名称"
rules={[{ required: true, message: '请选择原料名称' }]}
>
<Select disabled={props.values.id ? true : false} placeholder="请选择原料名称">
{props.stockGoodsData.map((item, index) => {
return (
<Select.Option index={index} value={item.id} key={item.id}>
{item.name}
</Select.Option>
);
})}
</Select>
</Form.Item>
<Form.Item name="price" label="价格" defaultValue={props.values.price}>
<InputNumber placeholder="价格" min={0} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
);
};

export default CreateForm;

+ 291
- 0
src/pages/bom/bomreplace/index.jsx Datei anzeigen

@@ -0,0 +1,291 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, message, Input, Modal } from 'antd';
import React, { useState, useRef, useEffect } from 'react';
import ProTable from '@ant-design/pro-table';
import CreateForm from './components/CreateForm';
import api from './services';

const BomReplace = (props) => {
/** 新建/更新窗口的弹窗 */
const [createModalVisible, handleModalVisible] = useState(false);
const actionRef = useRef();
const [currentRow, setCurrentRow] = useState();
const [selectedRowsState, setSelectedRows] = useState([]);

// //获取商品数据
const [goods, setgoods] = useState([]);

//获取原料名称
const [stockGoods, setstockGoods] = useState([]);
// 监控数据变化
useEffect(() => {
/** 获取原料名称*/
function initStockGoods() {
api.postStockGoods().then((r) => {
var arr = r.data;
arr.forEach((element) => {
element.text = element.name;
element.stockGoodsId = element.id;
});
setstockGoods(arr);
});
}
initStockGoods(); //回调原料信息
}, []);

/**
* 添加节点
*
* @param fields
*/
function handleAdd(fields) {
try {
api
.addGoodsBom({
itemId: fields.itemId,
price: fields.price,
goodsbomId: props.values.id,
replaceId: props.values.batchingId,
})
.then((r) => {
if (r.data) {
message.success('添加成功');
//刷新数据
actionRef.current.reload();
} else {
message.error('添加失败请重试!');
}
});
return true;
} catch (error) {
message.error('添加失败请重试!');
return false;
}
}
/**
* 更新节点
*
* @param fields
*/

function handleUpdate(fields) {
console.log('传参数据', fields);
try {
api
.updateGoodsBom({
id: fields.id,
status: fields.status,
itemId: fields.itemId,
price: fields.price,
goodsbomId: props.values.id,
replaceId: props.values.stockGoodsCode,
})
.then((r) => {
if (r.data) {
message.success('配置成功');
} else {
message.error('配置失败请重试!');
}
//刷新数据
actionRef.current.reload();
});
return true;
} catch (error) {
message.error('配置失败请重试!');
return false;
}
}

/**
* 删除节点
*
*
*/

function handleRemove() {
if (!selectedRowsState) return true;
try {
api.removeGoodsBom(selectedRowsState.map((row) => row.id)).then((r) => {
if (r.data) {
message.success('删除成功,即将刷新');
} else {
message.error('删除失败,请重试');
}
//刷新数据
actionRef.current.reload();
});
return true;
} catch (error) {
message.error('删除失败,请重试');
return false;
}
}

/** 国际化配置 */

const columns = [
{
title: '主键',
dataIndex: 'id',
hideInTable: true,
hideInSearch: true,
tip: '规则名称是唯一的 key',
render: (dom, entity) => {
return (
<a
onClick={() => {
setCurrentRow(entity);
setShowDetail(true);
}}
>
{dom}
</a>
);
},
},
{
title: '原料名称',
dataIndex: 'name',
hideInForm: true,
hideInSearch: true,
},
{
title: '商品价格',
dataIndex: 'price',
valueType: 'money',
},
{
title: '状态',
dataIndex: 'status',
hideInForm: true,
valueEnum: {
0: {
text: '正常',
status: 'Processing',
},
1: {
text: '停用',
status: 'Error',
},
2: {
text: '删除',
status: 'Error',
},
},
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
record.status === 0 && (
<a
key="link"
onClick={() => {
record.status = '1';
handleUpdate(record);
}}
>
停用
</a>
),
(record.status === 1 || record.status === 2) && (
<a
key="warn"
onClick={() => {
record.status = '0';
handleUpdate(record);
}}
>
启用
</a>
),
<a
key="primary"
type="primary"
onClick={() => {
setCurrentRow(record);
handleModalVisible(true);
}}
>
更新
</a>,
],
},
];

return (
<Modal
width={1200}
bodyStyle={{ padding: '32px 40px 48px' }}
destroyOnClose
title="配方物料替换"
visible={props.createModalVisible}
footer={false}
onCancel={() => props.onCancel()}
maskClosable={false}
>
<ProTable
headerTitle="替换物料列表"
actionRef={actionRef}
rowKey="id"
pagination={{ defaultPageSize: 20 }}
search={false}
toolBarRender={() => [
<Button type="primary" key="create" onClick={() => handleModalVisible(true)}>
<PlusOutlined /> 新增配方替换物料
</Button>,
]}
//数据绑定
request={async (params) => {
var goodsBomsData = [];
var total = 0;
//商品编号
if (props.values.id != undefined) {
params['goodsbomId'] = props.values.id;
params['replaceId'] = props.values.stockGoodsCode;
}
await api.goodsBoms(params).then((r) => {
goodsBomsData = r.data.data;
total = r.data.total;
});
return {
data: goodsBomsData,
success: true,
total: total,
};
}}
columns={columns}
/>
{/*商品配方(新增,修改) */}
<CreateForm
stockGoodsData={stockGoods}
onFinish={async (value) => {
var success = false;

if (value.id) {
success = await handleUpdate(value);
} else {
success = await handleAdd(value);
}
if (success) {
handleModalVisible(false);
setCurrentRow(undefined);

if (actionRef.current) {
actionRef.current.reload();
}
}
}}
onCancel={() => {
handleModalVisible(false);
setCurrentRow(undefined);
}}
createModalVisible={createModalVisible}
values={currentRow || {}}
/>
</Modal>
);
};

export default BomReplace;

+ 49
- 0
src/pages/bom/bomreplace/services.js Datei anzeigen

@@ -0,0 +1,49 @@
// @ts-ignore

/* eslint-disable */
import { request } from 'umi';

export default {
/** 获取商品物料 sdsa GET /kitchen/api/rule */
goodsBoms(data) {
return request(`/saasbase/api/GoodsBom/GetBomReplac`, {
method: 'Post',
data: data,
// params: { ...params },
// ...(options || {}),
});
},
updateGoodsBom(data) {
return request('/saasbase/api/GoodsBom/EditReplac', {
method: 'PUT',
data: data,
// ...(options || {}),
});
},

addGoodsBom(data) {
// http://localhost:7002
return request('/saasbase/api/GoodsBom/AddReplac', {
method: 'POST',
// type:'json',
data: data,
// ...(options || {}),
});
},
/** 获取商品商品 sdsa GET /kitchen/api/rule */
goodList(data) {
return request(`/saasbase/api/goods/goodss`, {
method: 'Post',
data: data,
// params: { ...params },
// ...(options || {}),
});
},
/** 获取原料信息 POST /kitchen/api/rule */
postStockGoods() {
return request(`/saasbase/api/frachisee-stock-adjust/product-list`, {
method: 'Get',
// ...(options || {}),
});
},
};

+ 61
- 0
src/pages/bom/bomtype/components/CreateForm.jsx Datei anzeigen

@@ -0,0 +1,61 @@
import React, { useState } from 'react';
import { Modal, Form, Input, Button, Select } from 'antd';
const CreateForm = (props) => {
const { Option, OptGroup } = Select;
return (
<Modal
title={props?.values?.id ? '编辑' : '新建'}
width={640}
visible={props?.createModalVisible}
bodyStyle={{ padding: '32px 40px 48px' }}
footer={null}
onCancel={() => {
props?.onCancel();
}}
destroyOnClose
maskClosable={false}
>
<Form
layout="vertical"
preserve={false}
initialValues={props?.values}
onFinish={props?.onFinish}
>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>
<Form.Item name="name" label="配方类型名称" rules={[{ required: true, max: 64, whitespace: true }]}>
<Input placeholder="请输入配方类型名称" />
</Form.Item>
<Form.Item
name="pertain"
label="归属"
defaultValue={props?.values?.pertain}
rules={[{ required: true, message: '请选择类型归属' }]} >
<Select placeholder="请选择类型归属">
<Option value="1">无</Option>
<Option value="2">TMC </Option>
</Select>
</Form.Item>
<Form.Item
name="status"
label="状态"
defaultValue={props?.values?.status}
rules={[{ required: true, message: '请选择状态' }]}
>
<Select placeholder="请选择状态">
<Option value="0">正常</Option>
<Option value="1">停用</Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
);
};

export default CreateForm;

+ 314
- 0
src/pages/bom/bomtype/index.jsx Datei anzeigen

@@ -0,0 +1,314 @@
import { DropboxOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, message, Input, Drawer, Modal, Popconfirm } from 'antd';
import React, { useState, useRef, useEffect } from 'react';
import { PageContainer, FooterToolbar } from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import ProDescriptions from '@ant-design/pro-descriptions';
import CreateForm from './components/CreateForm';
import { getbomtypepage, deletebomtype, updatebomtype, addbomtype } from './services';

//页面 相当于 class
/**
* 添加节点
*
* @param fields
*/
const handleAdd = async (fields) => {
try {
await addbomtype(JSON.stringify(fields)).then((r) => {
if (r.data) {
message.success('添加成功');
} else {
message.error('添加失败请重试!');
}
});
return true;
} catch (error) {
message.error('添加失败请重试!');
return false;
}
};
/**
* 批量删除节点
*
* @param selectedRows
*/
const handleRemove = async (ids) => {
try {
deletebomtype(ids).then((r) => {
if (r.data) {
message.success('删除成功,即将刷新');
} else {
message.error('删除失败,请重试');
}
});
return true;
} catch (error) {
message.error('删除失败,请重试');
}
};

/**
* 更新节点
*
* @param fields
*/

const handleUpdate = async (fields) => {
try {
updatebomtype(fields).then((r) => {
if (r.data) {
message.success('修改成功');
} else {
message.error('修改失败请重试!');
}
});
return true;
} catch (error) {
message.error('修改失败请重试!');
return false;
}
};
//页面 相当于 class
const FranchiessSrdTypeManage = () => {
/** 新建/更新窗口的弹窗 */
const [createModalVisible, handleModalVisible] = useState(false);
/** 分布更新窗口的弹窗 */

const [showDetail, setShowDetail] = useState(false);
//绑定
const actionRef = useRef();
const [currentRow, setCurrentRow] = useState();
//选中的行
const [selectedRowsState, setSelectedRows] = useState([]);

/** 国际化配置 */

const columns = [
{
title: '主键',
dataIndex: 'id',
tip: '规则名称是唯一的 key',
hideInSearch: true,
hideInTable: true,
render: (dom, entity) => {
return (
<a
onClick={() => {
setCurrentRow(entity);
setShowDetail(true);
}}
>
{dom}
</a>
);
},
},
{
title: '名称',
dataIndex: 'name',
valueType: 'textarea',
ellipsis: true,
search: true,
},
{
title: '归属',
dataIndex: 'pertain',
search: true,
valueEnum: {
1: {
text: '无',
status: 'Processing',
},
2: {
text: 'TMC',
status: 'Success',
},
},
},
{
title: '状态',
dataIndex: 'status',
hideInForm: true,
search: false,
valueEnum: {
0: {
text: '正常',
status: 'Processing',
},
1: {
text: '停用',
status: 'Success',
},
},
},
{
title: '创建时间',
dataIndex: 'createAt',
valueType: 'date',
hideInSearch: true,
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
key="state"
onClick={async () => {

let a="0"
if(record.status=="0"){
a="1"
}
let updateMassge = { id: record.id, name: record.name, pertain: record.pertain, status: a }
handleUpdate(updateMassge).then((r)=>{
if(r){
if (actionRef.current) {
actionRef.current.reload();
}
}
})
}
}
>
{record.status == 0 ? '停用' : '使用'}
</a>,
<a
key="config"
onClick={() => {
handleModalVisible(true);

setCurrentRow(record);
actionRef.current?.reloadAndRest?.();
}}
>
更新
</a>,
<Popconfirm
type="primary"
key="primary"
title="确认删除吗?"
okText="是"
cancelText="否"
onConfirm={() => {
handleRemove([record.id]);
actionRef.current?.reloadAndRest();
}}
onCancel={() => { }}
>
<a href="#">删除</a>
</Popconfirm>,
],
},
];

return (
<PageContainer>
<ProTable
headerTitle="物料分类"
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
columns={columns}
toolBarRender={() => [
<Button
type="primary"
key="primary"
onClick={() => {
handleModalVisible(true);
setCurrentRow({});
}}
>
<PlusOutlined /> 新建
</Button>,
]}
//数据绑定
request={async (params) => {
console.log('fsa', 'gfdsgfd');
var data = [];
var total = 0;
await getbomtypepage(params).then((r) => {
data = r.data.data;
data.forEach((x) => {
x.pertain = x.pertain.toString();
x.status = x.status.toString();
});
total = r.data.total;
});
return {
data: data,
success: true,
total: total,
};
}}
// 每行选择点击事件
rowSelection={{
onChange: (_, selectedRows) => {
setSelectedRows(selectedRows);
},
}}
></ProTable>
{/* {selectedRowsState?.length > 0 && (
<FooterToolbar
extra={
<div>
已选择{' '}
<a
style={{
fontWeight: 600,
}}
>
{selectedRowsState.length}
</a>{' '}
项 &nbsp;&nbsp;
</div>
}
>
<Button
onClick={() => {
handleRemove(selectedRowsState);
setSelectedRows([]);
actionRef.current?.reloadAndRest?.();
}}
type="primary"
key="primary"
>
批量删除
</Button>
</FooterToolbar>
)} */}
{/* 加盟商管理(新增,修改) */}
<CreateForm
onFinish={async (value) => {
var success = false;
if (value.id) {
console.log(value)
success = await handleUpdate(value);
} else {
success = await handleAdd(value);
}
if (success) {
handleModalVisible(false);
actionRef.current.reload();
// if (actionRef.current) {
// actionRef.current.reload();
// }
}
}}
onCancel={() => {
setCurrentRow(undefined);
handleModalVisible(false);
}}
createModalVisible={createModalVisible}
values={currentRow || {}}
/>
</PageContainer>
);
};

export default FranchiessSrdTypeManage;

+ 37
- 0
src/pages/bom/bomtype/services.js Datei anzeigen

@@ -0,0 +1,37 @@
// @ts-ignore

/* eslint-disable */
import { request } from 'umi';

//获取菜谱分类
export async function getbomtypepage(data) {
return request(`/saasbase/api/bom/getbomtypepage`, {
method: 'Post',
data: data,
});
}


//删除菜谱分类
export async function deletebomtype(data) {
return request(`/saasbase/api/bom/deletebomtype`, {
method: 'Post',
data: data,
});
}

//修改菜谱分类
export async function updatebomtype(data) {
return request(`/saasbase/api/bom/updatebomtype`, {
method: 'Post',
data: data,
});
}

//添加菜谱分类
export async function addbomtype(data) {
return request(`/saasbase/api/bom/addbomtype`, {
method: 'Post',
data: data,
});
}

+ 54
- 0
src/pages/bom/cookingentry/components/AddBomInfo.jsx Datei anzeigen

@@ -0,0 +1,54 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Button, Select, Switch, InputNumber } from 'antd';

const AddBomInfo = (props) => {
const { Option, OptGroup } = Select;
return (
<Modal
title={'添加配方原料'}
width={640}
visible={props.createModalVisible}
bodyStyle={{ padding: '32px 40px 48px' }}
footer={null}
onCancel={() => {
props.onCancel();
}}
destroyOnClose
>
<Form
layout="vertical"
preserve={false}
initialValues={props.values}
onFinish={props.onFinish}
>
<Form.Item
name="BatchingId"
label="原料名称"
rules={[{ required: true, message: '请选择原料名称' }]}
>
<Select placeholder="请选择原料名称"
showSearch
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}>
{props.stockGoodsData.map((item, index) => {
return (
<Option index={index} value={item.id} key={item.id}>
{item.name}
</Option>
);
})}
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
);
};

export default AddBomInfo;

+ 188
- 0
src/pages/bom/cookingentry/components/BomList.jsx Datei anzeigen

@@ -0,0 +1,188 @@
import { PlusOutlined } from '@ant-design/icons';
import { Button, message, Input, Drawer, Modal } from 'antd';
import React, { useState, useRef, useEffect } from 'react';
import { PageContainer, FooterToolbar } from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import ProDescriptions from '@ant-design/pro-descriptions';
import api from '../services';
import AddBomInfo from './AddBomInfo';

const BomList = (props) => {
/** 新建/更新窗口的弹窗 */
const actionRef = useRef();
const [AddBomModalVisible, handleAddBomModalVisible] = useState(false);
const [currentRow, setCurrentRow] = useState();
const [stockGoods, setstockGoods] = useState([]);
useEffect(() => {
/** 获取原料名称*/
function initStockGoods() {
api.getbygoods({IsMain: props.IsMain,goodsId: props.values.goodsId}).then((r) => {
var arr = r.data;
arr.forEach((element) => {
element.text = element.name;
element.stockGoodsId = element.value;
});
setstockGoods(arr);
});
}
initStockGoods(); //回调原料信息
}, []);
/**
* 更新节点
*
* @param fields
*/

function handleUpdate(fields) {
try {
api
.DeleteEntry(fields.id)
.then((r) => {
if (r.data) {
message.success('配置成功');
} else {
message.error('配置失败请重试!');
}
//刷新数据
actionRef.current.reload();
});
return true;
} catch (error) {
message.error('配置失败请重试!');
return false;
}
}


/**
* 添加节点
*
* @param fields
*/
function handleAddEntry(fields) {
try {
api.AddUpdateEntry(JSON.stringify(fields)).then((r) => {
if (r.data) {
message.success('添加成功');
//刷新数据
actionRef.current.reload();
} else {
message.error('添加失败请重试!');
}
});
return true;
} catch (error) {
message.error('添加失败请重试!');
return false;
}
}
/** 国际化配置 */

const columns = [
{
title: '主键',
dataIndex: 'id',
hideInTable: true,
hideInSearch: true,
tip: '规则名称是唯一的 key',
render: (dom, entity) => {
return (
<a
onClick={() => {
setCurrentRow(entity);
setShowDetail(true);
}}
>
{dom}
</a>
);
},
},
{
title: '原料名称',
dataIndex: 'batchingName',
hideInForm: true,
hideInSearch: true,
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => [
<a
key="link4"
onClick={() => {
handleUpdate(record);
}}
>
删除
</a>
],
},
];
return (
<Modal
title={props.IsMain?'主料配置':'辅料配置'}
width={640}
visible={props.createModalVisible}
bodyStyle={{ padding: '32px 40px 48px' }}
footer={null}
onCancel={() => {
props.onCancel();
}}
destroyOnClose
>
<ProTable
headerTitle=""
actionRef={actionRef}
rowKey="id"
pagination={{ defaultPageSize: 20 }}
search={false}
toolBarRender={() => [
<Button type="primary" key="create" onClick={() => handleAddBomModalVisible(true)}>
<PlusOutlined /> 添加原料
</Button>,
]}
//数据绑定
request={async (params) => {
var goodsBomsData = [];
var total = 0;
if (props.values.id != undefined) {
params['IsMain'] = props.IsMain;
params['StirFryTimeId'] = props.values.id;
}
await api.GetEntryPage(params).then((r) => {
goodsBomsData = r.data.data;
total = r.data.total;
});
return {
data: goodsBomsData,
success: true,
total: total,
};
}}
columns={columns}
/>
{/*商品配方(新增,修改) */}
<AddBomInfo
onFinish={async (value) => {
value['StirFryTimeId'] = props.values.id;
value['IsMain'] = props.IsMain;
var success = false;
success = await handleAddEntry(value);
if (success) {
handleAddBomModalVisible(false);
}
actionRef.current.reload();
}}
onCancel={() => {
handleAddBomModalVisible(false);
}}
createModalVisible={AddBomModalVisible}
stockGoodsData={stockGoods}
/>
</Modal>
);
};

export default BomList;

+ 87
- 0
src/pages/bom/cookingentry/components/CreateForm.jsx Datei anzeigen

@@ -0,0 +1,87 @@
import React, { useState, useEffect } from 'react';
import { Modal, Form, Input, Button, Select, InputNumber } from 'antd';
import api from '../services';
const CreateForm = (props) => {
const { Option, OptGroup } = Select;
const [options, setoptions] = useState();
//每次触发
useEffect(() => {
const initoptions = async (value) => {
var data = await api.getStirFryArray("stirfrypotaction");
if (data != undefined) {
setoptions(data.data);
}
};
initoptions();
}, []);
return (
<Modal
title={props.values.id ? '编辑' : '新建'}
width={640}
visible={props.createModalVisible}
bodyStyle={{ padding: '32px 40px 48px' }}
footer={null}
onCancel={() => {
props.onCancel();
}}
destroyOnClose
>
<Form
layout="horizontal"
preserve={false}
initialValues={props.values}
onFinish={props.onFinish}
>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>
<Form.Item name="status" hidden={true}>
<Input />
</Form.Item>
<Form.Item name="fryTime" label="步骤名称" rules={[{ required: true, max: 64 }]}>
<Input />
</Form.Item>
<Form.Item name="actions" label="炒锅动作" rules={[{ required: true, max: 64 }]}>
<Select style={{ width: 200 }}
showSearch
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
} >
{options?.map((item, index) => {
return (
<Option
index={item}
value={item}
key={index}
>
{item}
</Option>
);
})}
</Select>
</Form.Item>
<Form.Item name="during" label="持续时间" rules={[{ required: true }]}>
<InputNumber min={0} />
</Form.Item>
<span style={{ color: '#FFCC52' }}>
注: 持续时间(s),若为0,则等待该流程走完结束
</span>

<Form.Item name="sort" label="步骤排序" rules={[{ required: true }]}>
<InputNumber min={0} />
</Form.Item>
<span style={{ color: '#FFCC52' }}>
注: 步骤排序不能重复
</span>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
);
};

export default CreateForm;

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.

Laden…
Abbrechen
Speichern