Parcourir la source

初始化提交

tags/小炒逻辑变更前
txb il y a 2 ans
révision
e77c77ef96
100 fichiers modifiés avec 8903 ajouts et 0 suppressions
  1. +16
    -0
      .editorconfig
  2. +9
    -0
      .eslintignore
  3. +8
    -0
      .eslintrc.js
  4. +63
    -0
      .gitattributes
  5. +41
    -0
      .gitignore
  6. +23
    -0
      .prettierignore
  7. +5
    -0
      .prettierrc.js
  8. +5
    -0
      .stylelintrc.js
  9. +139
    -0
      Jenkinsfile
  10. +57
    -0
      README.md
  11. +14
    -0
      config/config.dev.js
  12. +74
    -0
      config/config.js
  13. +16
    -0
      config/defaultSettings.js
  14. +619
    -0
      config/oneapi.json
  15. +89
    -0
      config/proxy.js
  16. +614
    -0
      config/routes.js
  17. +13
    -0
      default.conf
  18. +4
    -0
      dockerfile
  19. +10
    -0
      jest.config.js
  20. +12
    -0
      jsconfig.json
  21. +55
    -0
      k8s_kitchenweb.yaml
  22. +178
    -0
      mock/listTableList.js
  23. +105
    -0
      mock/notices.js
  24. +7
    -0
      mock/route.js
  25. +207
    -0
      mock/user.js
  26. +120
    -0
      package.json
  27. +1
    -0
      public/CNAME
  28. BIN
     
  29. BIN
     
  30. BIN
     
  31. BIN
     
  32. BIN
     
  33. +1
    -0
      public/logo.svg
  34. +5
    -0
      public/pro_icon.svg
  35. +50
    -0
      src/access.js
  36. +782
    -0
      src/app.jsx
  37. +18
    -0
      src/components/Footer/index.jsx
  38. +10
    -0
      src/components/HeaderDropdown/index.jsx
  39. +16
    -0
      src/components/HeaderDropdown/index.less
  40. +83
    -0
      src/components/HeaderSearch/index.jsx
  41. +25
    -0
      src/components/HeaderSearch/index.less
  42. +114
    -0
      src/components/NoticeIcon/NoticeIcon.jsx
  43. +97
    -0
      src/components/NoticeIcon/NoticeList.jsx
  44. +103
    -0
      src/components/NoticeIcon/NoticeList.less
  45. +147
    -0
      src/components/NoticeIcon/index.jsx
  46. +35
    -0
      src/components/NoticeIcon/index.less
  47. +98
    -0
      src/components/RightContent/AvatarDropdown.jsx
  48. +63
    -0
      src/components/RightContent/index.jsx
  49. +62
    -0
      src/components/RightContent/index.less
  50. +272
    -0
      src/components/index.md
  51. +61
    -0
      src/e2e/baseLayout.e2e.js
  52. +101
    -0
      src/global.jsx
  53. +57
    -0
      src/global.less
  54. +7
    -0
      src/global_data.js
  55. +24
    -0
      src/locales/en-US.js
  56. +5
    -0
      src/locales/en-US/component.js
  57. +17
    -0
      src/locales/en-US/globalHeader.js
  58. +52
    -0
      src/locales/en-US/menu.js
  59. +70
    -0
      src/locales/en-US/pages.js
  60. +6
    -0
      src/locales/en-US/pwa.js
  61. +31
    -0
      src/locales/en-US/settingDrawer.js
  62. +60
    -0
      src/locales/en-US/settings.js
  63. +24
    -0
      src/locales/zh-CN.js
  64. +5
    -0
      src/locales/zh-CN/component.js
  65. +17
    -0
      src/locales/zh-CN/globalHeader.js
  66. +55
    -0
      src/locales/zh-CN/menu.js
  67. +67
    -0
      src/locales/zh-CN/pages.js
  68. +6
    -0
      src/locales/zh-CN/pwa.js
  69. +31
    -0
      src/locales/zh-CN/settingDrawer.js
  70. +55
    -0
      src/locales/zh-CN/settings.js
  71. +19
    -0
      src/locales/zh-TW.js
  72. +5
    -0
      src/locales/zh-TW/component.js
  73. +17
    -0
      src/locales/zh-TW/globalHeader.js
  74. +52
    -0
      src/locales/zh-TW/menu.js
  75. +6
    -0
      src/locales/zh-TW/pwa.js
  76. +31
    -0
      src/locales/zh-TW/settingDrawer.js
  77. +55
    -0
      src/locales/zh-TW/settings.js
  78. +22
    -0
      src/manifest.json
  79. +18
    -0
      src/pages/404.jsx
  80. +52
    -0
      src/pages/Admin.jsx
  81. +47
    -0
      src/pages/Welcome.jsx
  82. +8
    -0
      src/pages/Welcome.less
  83. +279
    -0
      src/pages/activity/activityInfo/components/ConfigurationForm.jsx
  84. +368
    -0
      src/pages/activity/activityInfo/components/CreateForm copy 2.jsx
  85. +217
    -0
      src/pages/activity/activityInfo/components/CreateForm copy.jsx
  86. +589
    -0
      src/pages/activity/activityInfo/components/CreateForm.jsx
  87. +427
    -0
      src/pages/activity/activityInfo/index.jsx
  88. +98
    -0
      src/pages/activity/activityInfo/service.js
  89. +135
    -0
      src/pages/activity/activityRecord/index.jsx
  90. +60
    -0
      src/pages/activity/activityRecord/services.js
  91. +69
    -0
      src/pages/admin/users/components/CreateForm.jsx
  92. +64
    -0
      src/pages/admin/users/components/OrgForm.jsx
  93. +85
    -0
      src/pages/admin/users/components/RoleForm.jsx
  94. +438
    -0
      src/pages/admin/users/index.jsx
  95. +0
    -0
     
  96. +56
    -0
      src/pages/admin/users/service.js
  97. +55
    -0
      src/pages/bom/bomreplace/components/CreateForm.jsx
  98. +291
    -0
      src/pages/bom/bomreplace/index.jsx
  99. +49
    -0
      src/pages/bom/bomreplace/services.js
  100. +55
    -0
      src/pages/bom/manage/components/AddBomInfo.jsx

+ 16
- 0
.editorconfig Voir le fichier

@@ -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 Voir le fichier

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

+ 8
- 0
.eslintrc.js Voir le fichier

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

+ 63
- 0
.gitattributes Voir le fichier

@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

+ 41
- 0
.gitignore Voir le fichier

@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
**/node_modules
# roadhog-api-doc ignore
/src/utils/request-temp.js
_roadhog-api-doc

# production
/dist
/.vscode

# misc
.DS_Store
npm-debug.log*
yarn-error.log

/coverage
.idea
yarn.lock
package-lock.json
*bak
.vscode
.vs
.git
# visual studio code
.history
*.log
functions/*
.temp/**

# umi
.umi
.umi-production

# screenshot
screenshot
.firebase
.eslintcache

build

+ 23
- 0
.prettierignore Voir le fichier

@@ -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 Voir le fichier

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

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

+ 5
- 0
.stylelintrc.js Voir le fichier

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

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

+ 139
- 0
Jenkinsfile Voir le fichier

@@ -0,0 +1,139 @@

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: 'branchName',
choices: ['master']
)

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") {
}
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"
}


}
}
}
}

+ 57
- 0
README.md Voir le fichier

@@ -0,0 +1,57 @@
# Ant Design Pro

This project is initialized with [Ant Design Pro](https://pro.ant.design). Follow is the quick guide for how to use.

## Environment Prepare

Install `node_modules`:

```bash
npm install
```
or

```bash
yarn
```

## Provided Scripts

Ant Design Pro provides some useful script to help you quick start and build with web project, code style check and test.

Scripts provided in `package.json`. It's safe to modify or add additional script:

### Start project

```bash
npm start
```

### Build project

```bash
npm run build
```

### Check code style

```bash
npm run lint
```

You can also use script to auto fix some lint error:

```bash
npm run lint:fix
```

### Test code

```bash
npm test
```

## More

You can view full document on our [official website](https://pro.ant.design). And welcome any feedback in our [github](https://github.com/ant-design/ant-design-pro).

+ 14
- 0
config/config.dev.js Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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: {
'/kitchen/api/': {
target: 'http://10.2.1.26:21527',
changeOrigin: true,
secure: false, //关闭证书验证
pathRewrite: {
'^': '',
},
},
// Nginx发布的时候需要配置
'/cos/':{
target: 'https://hbl-test-1305371387.cos.ap-chengdu.myqcloud.com',
changeOrigin: true,
secure: false, //关闭证书验证
pathRewrite: {
'/cos/': '/',
},
}
},
test: {
'/api/': {
target: 'http://localhost:7002',
changeOrigin: true,
secure: false,
pathRewrite: {
'^': '',
},
},
},
pre: {
'/api/': {
target: 'http://localhost:7002',
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: {
// '^': '',
// },
// },
// },
// };

+ 614
- 0
config/routes.js Voir le fichier

@@ -0,0 +1,614 @@
/**
* 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/company',
component: './sys/company',
access: 'k2',
},
{
name: '组织管理',
icon: 'smile',
path: '/sys/Org',
component: './sys/Org',
access: 'k2',
},
{
name: '操作日志',
icon: 'smile',
path: '/sys/Log',
component: './sys/Log',
access: 'k3',
},
{
name: '系统用户',
icon: 'smile',
path: '/sys/user',
component: './sys/user',
access: 'k4',
},
{
name: '角色权限',
icon: 'smile',
path: '/sys/Roles',
component: './sys/Roles',
access: 'k5',
},
{
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/sysSettings',
component: './sys/sysSettings',
access: 'k6',
},
{
name: '设备管理',
icon: 'smile',
path: '/sys/storemanage',
component: './sys/storemanage',
access: 'k6',
},
],
},
{
name: '物料管理',
icon: 'DropboxSquareFilled',
path: '/erp',
routes: [
{
name: '物料信息',
icon: 'smile',
path: '/erp/basic/product',
component: './erp/basic/product',
access: 'k7',
},
{
name: '物料单位',
icon: 'smile',
path: '/erp/basic/unit',
component: './erp/basic/unit',
access: 'k8',
},
{
name: '物料分类',
icon: 'smile',
path: '/erp/basic/productType',
component: './erp/basic/productType',
access: 'k9',
},
{
name: '验收入库',
icon: 'smile',
path: '/erp/Bill/ReceivingNote',
component: './erp/Bill/ReceivingNote',
access: 'k15',
},
{
name: '采购管理',
icon: 'smile',
path: '/erp/Bill/purchaseOrder',
component: './erp/Bill/purchaseOrder',
},
{
name: '物料库存',
icon: 'smile',
path: '/erp/stock',
component: './erp/stock',
access: 'k16',
},
],
},
{
name: '物料管理',
icon: 'DropboxSquareFilled',
path: '/bom',
routes: [
{
name: '配方管理',
icon: 'smile',
path: '/bom/manage',
component: './bom/manage',
access: 'k7',
},
{
name: '配方下发',
icon: 'smile',
path: '/bom/push',
component: './bom/push',
access: 'k8',
},
],
},
{
name: '商品管理',
icon: 'ProjectFilled',
path: '/goods',
routes: [
{
name: '商品管理',
icon: 'smile',
path: '/goods/goods-manage',
component: './goods/goods-manage',
access: 'k10',
},
{
name: '商品管理新',
icon: 'smile',
path: '/goods/newgoods',
component: './goods/newgoods',
access: 'k10',
},
{
name: '商品口味',
icon: 'smile',
path: '/goods/goods-taste',
component: './goods/goods-taste',
access: 'k11',
},
{
name: '商品小类',
icon: 'smile',
path: '/goods/goods-type-manage',
component: './goods/goods-type-manage',
access: 'k11',
},
{
name: '商品大类',
icon: 'smile',
path: '/goods/goodsParentTypemanage',
component: './goods/goodsParentTypemanage',
access: 'k11',
},
{
name: '商品单位',
icon: 'smile',
path: '/goods/unit',
component: './goods/unit',
access: 'k11',
},
{
name: '商品详情',
icon: 'smile',
path: '/goods/goodsInfo',
component: './goods/goodsInfo',
access: 'k11',
},



],
},

{
name: '店铺管理',
icon: 'BankFilled',
path: '/store',
routes: [
{
name: '店铺类型',
icon: 'smile',
path: '/store/storeType',
component: './store/storeType',
access: 'k12',
},
{
name: '店铺小票打印机',
icon: 'smile',
path: '/store/printer/Printer',
component: './store/printer/Printer',
access: 'k12',
},
{
name: '打印机模板',
icon: 'smile',
path: '/store/printer/printerTemplate',
component: './store/printer/printerTemplate',
access: 'k12',
},

{
name: '店铺桌面码',
icon: 'smile',
path: '/store/storeDesktopNumber',
component: './store/storeDesktopNumber',
access: 'k12',
},
{
name: '店铺等级',
icon: 'smile',
path: '/store/storeLevel',
component: './store/storeLevel',
access: 'k13',
},
{
name: '店铺信息',
icon: 'smile',
path: '/store/storeInfo',
component: './store/storeInfo',
access: 'k14',
},
{
name: '加购商品',
icon: 'smile',
path: '/store/storeAddGoodsInfo',
component: './store/storeAddGoodsInfo',
access: 'k14',
},
{
name: '店铺商品',
icon: 'smile',
path: '/store/storeGoodsInfo',
component: './store/storeGoodsInfo',
access: 'k14',
},
{
name: '店铺广告',
icon: 'smile',
path: '/store/storeAdvertisement',
component: './store/storeAdvertisement',
access: 'k39',
},
],
},
{
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/deviceFood',
component: './device/deviceFood',
access: 'k14',
},
{
name: '设备补货',
icon: 'smile',
path: '/device/deviceStock',
component: './device/deviceStock',
access: 'k14',
}


],
},
{
name: '积分管理',
icon: 'BankFilled',
path: '/integral',
routes: [
{
name: '积分规则',
icon: 'smile',
path: '/integral/integralConfigure',
component: './integral/integralConfigure',
access: 'k12',
},
]
},
{
name: '菜谱管理',
icon: 'BankFilled',
path: '/foodMenu',
routes: [
{
name: '菜谱设置',
icon: 'smile',
path: '/foodMenu/foodMenuInfo',
component: './foodMenu/foodMenuInfo',
access: 'k12',
},
{
name: '菜谱关联门店',
icon: 'smile',
path: '/foodMenu/storeFoodMenu',
component: './foodMenu/storeFoodMenu',
access: 'k14',
},
{
name: '菜谱关联菜品',
icon: 'smile',
path: '/foodMenu/foodMenu',
component: './foodMenu/foodMenu',
access: 'k14',
}
]
},
{
name: '会员管理',
icon: 'UserSwitchOutlined',
path: '/member',
routes: [
{
name: '会员信息',
icon: 'smile',
path: '/member/memberInfo',
component: './member/memberInfo',
access: 'k17',
},
{
name: '会员标签信息',
icon: 'smile',
path: '/member/membertag',
component: './member/membertag',
access: 'k18',
},
{
name: '会员数据分析',
icon: 'smile',
path: '/member/memberAnalysis',
component: './member/memberAnalysis',
access: 'k19',
},
],
},
{
name: '活动管理',
icon: 'CarryOutOutlined',
path: '/activity',
routes: [
{
name: '活动信息',
icon: 'smile',
path: '/activity/activityInfo',
component: './activity/activityInfo',
access: 'k20',
},
{
name: '活动记录信息',
icon: 'smile',
path: '/activity/activityRecord',
component: './activity/activityRecord',
access: 'k21',
},
],
},
{
name: '优惠券管理',
icon: 'WalletOutlined',
path: '/crm',
routes: [
{
name: '优惠卷管理',
icon: 'smile',
path: '/crm/coupon/couponInfo',
component: './crm/coupon/couponInfo',
access: 'k22',
},
{
name: '优惠卷批次信息',
icon: 'smile',
path: '/crm/coupon/coupon-Batch',
component: './crm/coupon/coupon-Batch',
access: 'k23',
},
{
name: '优惠卷领取信息',
icon: 'smile',
path: '/crm/coupon/customerCoupon',
component: './crm/coupon/customerCoupon',
access: 'k24',
},
],
},
{
name: '分账管理',
icon: 'RedEnvelopeOutlined',
path: '/split',
routes: [
{
name: '分账账户管理',
icon: 'smile',
path: '/split/account',
component: './split/account',
access: 'k25',
},
{
name: '加盟商分账方案',
icon: 'smile',
path: '/split/FranchiseePlan',
component: './split/FranchiseePlan',
access: 'k25',
},
{
name: '分账方案管理',
icon: 'smile',
path: '/split/plan',
component: './split/plan',
access: 'k26',
},
{
name: '分账订单管理',
icon: 'smile',
path: '/split/splitOrders',
component: './split/splitOrders',
access: 'k27',
},
],
},
{
name: '退款操作',
icon: 'AccountBookOutlined',
path: '/refundment',
routes: [
{
name: '售后申请',
icon: 'smile',
path: '/refundment/OrderAfterSales',
component: './refundment/OrderAfterSales',
access: 'k28',
},
],
},
{
name: '报表统计',
icon: 'SmileOutlined',
path: '/order',
routes: [
{
name: '订单汇总',
path: '/order/order-summary-manage',
component: './order/order-summary-manage',
access: 'k29',
},
{
name: '订单流水',
path: '/order/order-flow-manage',
component: './order/order-flow-manage',
access: 'k30',
},
{
name: '订单明细',
path: '/order/orderDetail',
component: './order/orderDetail',
access: 'k31',
},
{
name: '营业日报',
path: '/order/ordersalescountbyday',
component: './order/ordersalescountbyday',
access: 'k32',
},
{
name: '异常订单信息',
path: '/order/ExOrder',
component: './order/ExOrder',
access: 'k31',
},
],
},
{
name: '库存管理',
icon: 'HddOutlined',
path: '/stockManager',
routes: [
{
name: '其它物料入库',
path: '/stockManager/stockItem',
component: './stockManager/stockItem',
access: 'k36',
},
{
name: '库存盘点',
path: '/stockManager/stockInventory',
component: './stockManager/stockInventory',
access: 'k37',
},
{
name: '库存报表',
path: '/stockManager/stockReports',
component: './stockManager/stockReports',
access: 'k38',
},
],
},
{
name: '财务报表',
icon: 'HddOutlined',
path: '/financereport',
routes: [
{
name: '财务账单',
path: '/financereport/finance',
component: './financereport/finance',
access: 'k37',
},
],
},
{
path: '/',
redirect: '/welcome',
},
{
path: '/welcome',
name: 'welcome',
icon: 'smile',
component: './Welcome',
},
{
name: 'test',
icon: 'HddOutlined',
path: '/test',
routes: [
{
name: 'test',
path: '/test/update',
component: './test/update',
access: 'k37',
},
{
name: 'test',
path: '/test',
component: './test',
access: 'k37',
},
],
},
//什么都不要想404直接放最后
{
component: './404',
},
];

+ 13
- 0
default.conf Voir le fichier

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




+ 4
- 0
dockerfile Voir le fichier

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

+ 10
- 0
jest.config.js Voir le fichier

@@ -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 Voir le fichier

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

+ 55
- 0
k8s_kitchenweb.yaml Voir le fichier

@@ -0,0 +1,55 @@
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>
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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

+ 207
- 0
mock/user.js Voir le fichier

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

+ 120
- 0
package.json Voir le fichier

@@ -0,0 +1,120 @@
{
"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",
"antd": "^4.17.0",
"axios": "^0.26.1",
"braft-editor": "^2.3.9",
"classnames": "^2.2.6",
"cos-js-sdk-v5": "^1.3.5",
"js-export-excel": "^1.1.4",
"linq": "^4.0.0",
"lodash": "^4.17.11",
"moment": "^2.25.3",
"omit.js": "^2.0.2",
"qrcode.react": "^1.0.1",
"react": "^17.0.0",
"react-dev-inspector": "^1.1.1",
"react-dom": "^17.0.0",
"react-helmet-async": "^1.0.4",
"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",
"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 Voir le fichier

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






+ 1
- 0
public/logo.svg
Fichier diff supprimé car celui-ci est trop grand
Voir le fichier


+ 5
- 0
public/pro_icon.svg Voir le fichier

@@ -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 Voir le fichier

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

+ 782
- 0
src/app.jsx Voir le fichier

@@ -0,0 +1,782 @@
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 * 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.push(loginPath);
}
return currentUser;
} catch (error) {
const { location } = history; // 如果没有登录,重定向到 login
console.log(location.pathname);
if (location.pathname != loginPath) {
history.push(loginPath);
}
}

return undefined;
};
const queryMenuData = async () => {
try {
const data = [

{
code:'device',
name: '设备管理',
icon: 'BankFilled',
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:'deviceFood',
name: '设备商品管理',
icon: 'smile',
path: '/device/deviceFood',
component: './device/deviceFood',
access: 'k14',
}
],
},
{
code:'integral',
name: '积分管理',
icon: 'BankFilled',
path: '/integral',
routes: [
{
code:'integralConfigure',
name: '积分规则',
icon: 'smile',
path: '/integral/integralConfigure',
component: './integral/integralConfigure',
access: 'k12',
},]

},
{
code:'foodMenu',
name: '菜谱管理',
icon: 'BankFilled',
path: '/foodMenu',
routes: [
{
code:'foodMenuInfo',
name: '菜谱设置',
icon: 'smile',
path: '/foodMenu/foodMenuInfo',
component: './foodMenu/foodMenuInfo',
access: 'k12',
},
{
code:'storeFoodMenu',
name: '菜谱关联门店',
icon: 'smile',
path: '/foodMenu/storeFoodMenu',
component: './foodMenu/storeFoodMenu',
access: 'k14',
},
{
code:'foodMenu',
name: '菜谱关联菜品',
icon: 'smile',
path: '/foodMenu/foodMenu',
component: './foodMenu/foodMenu',
access: 'k14',
}
]
},
{
code:'sys',
name: '系统管理',
icon: 'SettingOutlined',
path: '/sys',
routes: [
{
code:'company',
name: '加盟商管理',
icon: 'smile',
path: '/sys/company',
component: './sys/company',
access: 'k2',
},
{
code:'Org',
name: '组织管理',
icon: 'smile',
path: '/sys/Org',
component: './sys/Org',
access: 'k2',
},
{
code:'Log',
name: '操作日志',
icon: 'smile',
path: '/sys/Log',
component: './sys/Log',
access: 'k3',
},
{
code:'User',
name: '系统用户',
icon: 'smile',
path: '/sys/User',
component: './sys/User',
access: 'k4',
},
{
code:'Roles',
name: '角色权限',
icon: 'smile',
path: '/sys/Roles',
component: './sys/Roles',
access: 'k5',
},
{
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:'sysSettings',
name: '系统配置',
icon: 'smile',
path: '/sys/sysSettings',
component: './sys/sysSettings',
access: 'k6',
},
],
},
{
code:'bom',
name: '配方管理',
icon: 'DropboxSquareFilled',
path: '/bom',
routes: [
{
code:'bommanage',
name: '配方列表',
icon: 'smile',
path: '/bom/manage',
component: './bom/manage',
access: 'k7',
},
],
},
{
code:'erp',
name: '物料管理',
icon: 'DropboxSquareFilled',
path: '/erp',
routes: [
{
code:'product',
name: '物料信息',
icon: 'smile',
path: '/erp/basic/product',
component: './erp/basic/product',
access: 'k7',
},
{
code:'unit',
name: '物料单位',
icon: 'smile',
path: '/erp/basic/unit',
component: './erp/basic/unit',
access: 'k8',
},
{
code:'productType',
name: '物料分类',
icon: 'smile',
path: '/erp/basic/productType',
component: './erp/basic/productType',
access: 'k9',
},

],
},
{
code:'goods',
name: '商品管理',
icon: 'ProjectFilled',
path: '/goods',
routes: [
{
code:'newgoods',
name: '商品管理',
icon: 'smile',
path: '/goods/newgoods',
component: './goods/newgoods',
access: 'k10',
},
{
code:'goods-taste',
name: '商品口味',
icon: 'smile',
path: '/goods/goods-taste',
component: './goods/goods-taste',
access: 'k11',
},
{
code:'goods-type',
name: '商品小类',
icon: 'smile',
path: '/goods/goods-type-manage',
component: './goods/goods-type-manage',
access: 'k11',
},
{
code:'goodsbigtype',
name: '商品大类',
icon: 'smile',
path: '/goods/goodsParentTypemanage',
component: './goods/goodsParentTypemanage',
access: 'k11',
},
{
code:'unit',
name: '商品单位',
icon: 'smile',
path: '/goods/unit',
component: './goods/unit',
access: 'k11',
},
],
},
{
code:'store',
name: '店铺管理',
icon: 'BankFilled',
path: '/store',
routes: [
{
code:'storeDesktopNumber',
name: '店铺桌面码',
icon: 'smile',
path: '/store/storeDesktopNumber',
component: './store/storeDesktopNumber',
access: 'k12',
},
{
code:'Printer',
name: '店铺小票打印机',
icon: 'smile',
path: '/store/printer/Printer',
component: './store/printer/Printer',
access: 'k12',
},
{
code:'printerTemplate',
name: '打印机模板',
icon: 'smile',
path: '/store/printer/printerTemplate',
component: './store/printer/printerTemplate',
access: 'k12',
},
{
code:'storeGoodsInfo',
name: '店铺商品',
icon: 'smile',
path: '/store/storeGoodsInfo',
component: './store/storeGoodsInfo',
access: 'k14',
},
{
code:'storeAdvertisement',
name: '店铺广告',
icon: 'smile',
path: '/store/storeAdvertisement',
component: './store/storeAdvertisement',
access: 'k39',
},
],
},
{
code:'member',
name: '会员管理',
icon: 'UserSwitchOutlined',
path: '/member',
routes: [
{
code:'memberInfo',
name: '会员信息',
icon: 'smile',
path: '/member/memberInfo',
component: './member/memberInfo',
access: 'k17',
},
{
code:'membertag',
name: '会员标签信息',
icon: 'smile',
path: '/member/membertag',
component: './member/membertag',
access: 'k18',
},
{
code:'memberAnalysis',
name: '会员数据分析',
icon: 'smile',
path: '/member/memberAnalysis',
component: './member/memberAnalysis',
access: 'k19',
},
],
},
{
code:'activity',
name: '活动管理',
icon: 'CarryOutOutlined',
path: '/activity',
routes: [
{
code:'activityInfo',
name: '活动信息',
icon: 'smile',
path: '/activity/activityInfo',
component: './crm/activity/activityInfo',
access: 'k20',
},
{
code:'activityRecord',
name: '活动记录信息',
icon: 'smile',
path: '/activity/activityRecord',
component: './activity/activityRecord',
access: 'k21',
},
],
},
{
code:'Coupon',
name: '优惠券管理',
icon: 'WalletOutlined',
path: '/crm',
routes: [
{
code:'couponInfo',
name: '优惠卷管理',
icon: 'smile',
path: '/crm/Coupon/couponInfo',
component: './crm/Coupon/couponInfo',
access: 'k22',
},
{
code:'coupon-Batch',
name: '优惠卷批次信息',
icon: 'smile',
path: '/crm/Coupon/coupon-Batch',
component: './crm/Coupon/coupon-Batch',
access: 'k23',
},
{
code:'customerCoupon',
name: '优惠卷领取信息',
icon: 'smile',
path: '/crm/Coupon/customerCoupon',
component: './crm/Coupon/customerCoupon',
access: 'k24',
},
],
},
{
code:'split',
name: '分账管理',
icon: 'RedEnvelopeOutlined',
path: '/split',
routes: [
{
code:'account',
name: '分账账户管理',
icon: 'smile',
path: '/split/account',
component: './split/account',
access: 'k25',
},
{
code:'FranchiseePlan',
name: '加盟商分账方案',
icon: 'smile',
path: '/split/FranchiseePlan',
component: './split/FranchiseePlan',
access: 'k25',
},
{
code:'plan',
name: '分账方案管理',
icon: 'smile',
path: '/split/plan',
component: './split/plan',
access: 'k26',
},
{
code:'splitOrders',
name: '分账订单管理',
icon: 'smile',
path: '/split/splitOrders',
component: './split/splitOrders',
access: 'k27',
},
],
},
{
code:'refundment',
name: '退款操作',
icon: 'AccountBookOutlined',
path: '/refundment',
routes: [
{
code:'OrderAfterSales',
name: '售后申请',
icon: 'smile',
path: '/refundment/OrderAfterSales',
component: './refundment/OrderAfterSales',
access: 'k28',
},
],
},
{
code:'order',
name: '报表统计',
icon: 'SmileOutlined',
path: '/order',
routes: [
{
code:'ordersummarymanage',
name: '订单汇总',
path: '/order/order-summary-manage',
component: './order/order-summary-manage',
access: 'k29',
},
{
code:'orderflowmanage',
name: '订单流水',
path: '/order/order-flow-manage',
component: './order/order-flow-manage',
access: 'k30',
},
{
code:'orderDetail',
name: '订单明细',
path: '/order/orderDetail',
component: './order/orderDetail',
access: 'k31',
},
{
code:'ordersalescountbyday',
name: '营业日报',
path: '/order/ordersalescountbyday',
component: './order/ordersalescountbyday',
access: 'k32',
},
{
code:'ExOrder',
name: '异常订单',
path: '/order/ExOrder',
component: './order/ExOrder',
access: 'k32',
},
],
},
{
code:'stockManager',
name: '库存管理',
icon: 'HddOutlined',
path: '/stockManager',
routes: [
{
code:'stockItem',
name: '其它物料入库',
path: '/stockManager/stockItem',
component: './stockManager/stockItem',
access: 'k36',
},
{
code:'stockInventory',
name: '库存盘点',
path: '/stockManager/stockInventory',
component: './stockManager/stockInventory',
access: 'k37',
},
{
code:'stockReports',
name: '库存报表',
path: '/stockManager/stockReports',
component: './stockManager/stockReports',
access: 'k38',
},
],
},
{
code:'financereport',
name: '财务报表',
icon: 'HddOutlined',
path: '/financereport',
routes: [
{
code:'finance',
name: '财务账单',
path: '/financereport/finance',
component: './financereport/finance',
access: 'k37',
},
],
},
];
// 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;
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.push(loginPath);
}
} else {
notification.error({
message: `请求错误 ${status}: ${url}`,
description: errorText,
});
}
}
if (!response) {
notification.error({
description: '您的网络发生异常,无法连接服务器',
message: '网络异常',
});
}

throw error;
},
//prefix: `${DOMAIN}`,
requestInterceptors: [
(url, options) => {
console.log(url);
console.log(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
if (!initialState?.currentUser && !location.pathname.includes(loginPath)) {
history.push(loginPath);
}
},
menuDataRender: () => loopMenuItem(initialState?.menuData),
// 自定义 403 页面
// unAccessible: <div>unAccessible</div>,
...initialState?.settings,
};
};

+ 18
- 0
src/components/Footer/index.jsx Voir le fichier

@@ -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={[

]}
/>
);
};

+ 10
- 0
src/components/HeaderDropdown/index.jsx Voir le fichier

@@ -0,0 +1,10 @@
import { Dropdown } from 'antd';
import React from 'react';
import classNames from 'classnames';
import styles from './index.less';

const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
<Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
);

export default HeaderDropdown;

+ 16
- 0
src/components/HeaderDropdown/index.less Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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} src={currentUser.data.avatar} alt="avatar" />
<span className={`${styles.name} anticon`}>{currentUser.data.name}</span>
</span>
</HeaderDropdown>
);
};

export default AvatarDropdown;

+ 63
- 0
src/components/RightContent/index.jsx Voir le fichier

@@ -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 Voir le fichier

@@ -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(255, 255, 255, 0.85);
}
}
}

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

+ 272
- 0
src/components/index.md Voir le fichier

@@ -0,0 +1,272 @@
---
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) => {
console.log('input', 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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

+ 24
- 0
src/locales/en-US.js Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

+ 17
- 0
src/locales/zh-CN/globalHeader.js Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

+ 17
- 0
src/locales/zh-TW/globalHeader.js Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

+ 279
- 0
src/pages/activity/activityInfo/components/ConfigurationForm.jsx Voir le fichier

@@ -0,0 +1,279 @@
import React, { useState, useEffect } from 'react';
import {
Row,
Col,
Checkbox,
Space,
Steps,
message,
Modal,
Form,
Input,
Button,
Select,
DatePicker,
} from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
import { GetActivityConfiguration, GetCoupon, AddactivityConfiguration, goodsType, GetStoreInfos } from '../service';
const key = 'message';
const CreateConfigForm = (props) => {
const [form] = Form.useForm();
const { Option, OptGroup } = Select;
const [isFormShow, SetisFormShow] = useState(true);
const [GoodsType, SetGoodsType] = useState([]);
const [Shops, Setshops] = useState([]);

//初始化数据
useEffect(() => {
const initgoodsType = async (value) => {
var data = await goodsType({ current: 1, pageSize: 100 });
if (data != undefined) {
SetGoodsType(data.data);
}
};
const initShops = async (value) => {
var data = await GetStoreInfos({ current: 1, pageSize: 100 });
if (data) {
Setshops(data.data);
}
};
initgoodsType();
initShops();
}, []);

const tailFormItemLayout = {
wrapperCol: {
sm: {
span: 500,
offset: 1,
},
},
};
let numChar = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']

const GetSelectVal = (info) => {
let value = [];
if (info.configurationName == "星期限制") {
for (let i = 0; i < 7; i++) {
value.push({
label: `星期${i == 0 ? "日" : numChar[i]}`,
value: i.toString(),
});
}
}

if (info.configurationName == "商品种类限制") {
GoodsType.data.forEach(element => {
value.push({
label: element.goodsType_Name,
value: element.id,
});
});
}
if (info.configurationName == "门店限制") {
Shops.data.forEach(element => {
value.push({
label: element.storeName,
value: element.storeId,
});
});
}

return value;
}

//新增
const onFinish = async (values) => {
message.loading('正在添加...', key);
try {
if (values.users) {
values.users.forEach(element => {
element.activity_Id = props.activityId;
});

}
await AddactivityConfiguration(JSON.stringify(values.users)).then((r) => {
message.destroy(key);
if (r.data) {
props.onCancel();
message.success('添加成功');
} else {
message.error('添加失败请重试!');
}
});
return true;
} catch (error) {
message.destroy(key);
message.error('添加失败请重试!');
return false;
}
};




return (
<Modal
title={'活动配置'}
width={700}
visible={props.createModalVisible}
footer={null} //底部内容
onCancel={() => {
props.onCancel();
}}
//关闭时销毁 Modal 里的子元素
destroyOnClose >
{/* ----------------------------------------------------------------------------------------- */}

<Form name="dynamic_form_nest_item" autoComplete="off" onFinish={onFinish}
initialValues={{
"users": props.config
}}
>
<Form.List name="users">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">

<Form.Item
{...restField}
name={[name, 'valueType']}
fieldKey={[fieldKey, 'valueType']}
hidden={true}>
<Input style={{ width: 80 }} size='Small' />
</Form.Item>
<Form.Item
{...restField}
name={[name, 'configId']}
fieldKey={[fieldKey, 'configId']}>
<Checkbox.Group >
<Row>
<Col span={32} >
<Checkbox value={props.config[key].configuration_Id} style={{ lineHeight: '32px' }}>
{props.config[key].configurationName}
</Checkbox>
</Col>
</Row>
</Checkbox.Group>
</Form.Item>
<span>条件:{props.config[key].configurationRule}</span>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.activityType !== currentValues.activityType
} >
{({ getFieldValue }) =>
props.config[key].configurationValueCount != 0 ? (
<Form.Item
{...restField}
name={[name, 'operator']}
fieldKey={[fieldKey, 'operator']}
// rules={[{ required: true, message: '请输入判定符' }]}
>
<Select style={{ width: 80 }} size='Small' >
<Option value=">">大于</Option>
<Option value="<">小于</Option>
<Option value="==">等于</Option>
<Option value=">=and<=">包含</Option>
</Select>
</Form.Item>
) : (
<span></span>
)
}
</Form.Item>

<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.activityType !== currentValues.activityType
} >
{({ getFieldValue }) =>
props.config[key].configurationValueCount == 1 ? (
<Form.Item
{...restField}
name={[name, 'value1']}
fieldKey={[fieldKey, 'value1']}
// rules={[{ required: true, message: '请输入判定符' }]}
>
{props.config[key].valueType == 1 ? (
<Input style={{ width: 80 }} size='Small' />
) : (
<Select {...{
mode: 'multiple',
style: { width: '250px' },
options: GetSelectVal(props.config[key]),
placeholder: 'Select Item...',
maxTagCount: 'responsive',
mode: 'multiple',
}} />
)}

</Form.Item>
) : (
<span></span>
)
}
</Form.Item>


<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.activityType !== currentValues.activityType
} >
{({ getFieldValue }) =>
props.config[key].configurationValueCount == 2 ? (
<Form.Item
{...restField}
name={[name, 'value1']}
fieldKey={[fieldKey, 'value1']}
// rules={[{ required: true, message: '请输入判定符' }]}
>
<Input style={{ width: 80 }} size='Small' />
</Form.Item>
) : (
<span></span>
)
}
</Form.Item>

<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.activityType !== currentValues.activityType
} >
{({ getFieldValue }) =>
props.config[key].configurationValueCount == 2 ? (

<Form.Item
{...restField}
name={[name, 'value2']}
fieldKey={[fieldKey, 'value2']}
// rules={[{ required: true, message: '请输入判定符' }]}
>
<Input style={{ width: 80 }} size='Small' />
</Form.Item>
) : (
<span></span>
)
}
</Form.Item>
</Space>
))}
</>
)}
</Form.List>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
{/* ----------------------------------------------------------------------------------------- */}
</Modal>
);
};

export default CreateConfigForm;

+ 368
- 0
src/pages/activity/activityInfo/components/CreateForm copy 2.jsx Voir le fichier

@@ -0,0 +1,368 @@
import React, { useState, useEffect } from 'react';
import {
InputNumber,
Row,
Col,
Checkbox,
Space,
Steps,
message,
Modal,
Form,
Input,
Button,
Select,
DatePicker,
} from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';

import { GetActivityConfiguration, GetCouponCanUseCountAndLimit, GetGoodsInfo } from '../service';

const CreateForm = (props) => {
const [form] = Form.useForm();
const { Option, OptGroup } = Select;
const [options, setoptions] = useState();
const [current, setCurrent] = React.useState(0);
const [firstItem, SetFirstItem] = useState();
const [secondItem, SetSecondItem] = useState();
const [isFormShow, SetisFormShow] = useState(true);
const [couponInfo, SetCouponInfo] = useState([]);
const [goodsInfo, SetGoodsInfo] = useState([]);
const [isCouponModalVisible, SetIsCouponModalVisible] = useState(false);
const [selectCoupon, SetselectCoupon] = useState(false);
const [quantityLimit, SetQuantityLimit] = useState([]);

const [goodsPrice, SetGoodsPrice] = useState([]);

const tailFormItemLayout = {
wrapperCol: {
sm: {
span: 500,
offset: 1,
},
},
};

//初始化数据
useEffect(() => {
const initcouponType = async (value) => {
var data = (await GetCouponCanUseCountAndLimit()).data;
if (data != undefined) {
SetCouponInfo(data);
}
};
const initGoodsInfoType = async (value) => {
var data = (await GetGoodsInfo()).data;
if (data) {
SetGoodsInfo(data);
}
};
initcouponType();
initGoodsInfoType();
}, []);

const canUseCountOrlimit = (key, value, type) => {
var result = 0;
if (value) {
if (type == 3) {
if (goodsPrice[key]) {
return goodsPrice[key].price;
} else {
const arr = [[...goodsPrice]]; //克隆的时候改变地址
goodsInfo.filter((item) => {
if (item.id == value[key]?.rewardValue) {
arr[key] = item;
}
});
var vv = arr;
// SetGoodsPrice(arr??[]);
return arr[key] ? arr[key].price : 0;
}
} else {
var rewardValue = value[key];
var count = 0;
couponInfo.filter((item) => {
if (rewardValue && item.couponId == rewardValue?.rewardValue) {
if (type == 1) {
count = item.canUseCount;
} else {
count = item.limit;
}
}
});
}

if (type == 1) {
result = parseInt(
quantityLimit[key] ? JSON.stringify(quantityLimit[key].canUseCount) : count,
);
} else {
result = parseInt(quantityLimit[key] ? JSON.stringify(quantityLimit[key].limit) : count);
}
} else if (quantityLimit[key]) {
if (type == 1) {
result = quantityLimit[key].canUseCount;
} else {
result = quantityLimit[key].limit;
}
}
return result;
};

return (
<Modal
title={props.values.id ? '编辑优活动' : '新建活动'}
width={700}
visible={props.createModalVisible}
footer={null} //底部内容
onCancel={() => {
props.onCancel();
}}
//关闭时销毁 Modal 里的子元素
destroyOnClose={true}
>
<Form
preserve={false}
// form={form}
labelCol={{ span: 4 }}
layout="Horizontal"
initialValues={props.values}
onFinish={props.onFinish}
>
<span style={{ display: isFormShow ? 'block' : 'none' }}>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>

<Form.Item name="activityName" label="活动名称" rules={[{ required: true, max: 64 }]}>
<Input placeholder="请输入活动名称" />
</Form.Item>
<Form.Item name="activityImg" label="活动图片" rules={[{ required: true, max: 500 }]}>
<Input placeholder="请输入活动图片" />
</Form.Item>
<Form.Item
name="activityType"
label="活动类型"
defaultValue={props.values.activityType}
rules={[{ required: true }]}
>
<Select
onChange={() => {
// form.setFieldsValue({ activityValue: undefined });
}}
allowClear
placeholder="请选择活动类型"
>
<OptGroup>
<Select.Option key={1} value={1}>
{'送券'}
</Select.Option>
<Select.Option key={2} value={2}>
{' 其他'}
</Select.Option>
<Select.Option key={3} value={3}>
{'商品促销'}
</Select.Option>
<Select.Option key={4} value={4}>
{'商城优惠券'}
</Select.Option>
</OptGroup>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.activityType !== currentValues.activityType
}
>
{({ getFieldValue }) =>
getFieldValue('activityType') == '2' ? (
<Form.Item name="activityValue" label="活动值" rules={[{ required: true }]}>
<Form.Item name="activityValue" noStyle>
<Input placeholder="请输入活动值" />
</Form.Item>
</Form.Item>
) : getFieldValue('activityType') == '3' ? (
<Form.Item label="商品信息">
<Form.List name="activityRewards">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8 }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, 'rewardValue']}
fieldKey={[fieldKey, 'rewardValue']}
rules={[{ required: true, message: '请选择商品' }]}
>
<Select
style={{ width: 200 }}
onChange={(value) => {
const arr = [...goodsPrice]; //克隆的时候改变地址
goodsInfo.filter((item) => {
if (item.id == value) {
arr[key] = item;
}
});
SetGoodsPrice(arr);
}}
>
{goodsInfo.map((item, index) => {
return (
<Select.Option index={index} value={item.id} key={item.id}>
{item.name}
</Select.Option>
);
})}
</Select>
</Form.Item>
{/* {`原价:${goodsPrice[key]?.price}`} */}
{`原价:${canUseCountOrlimit(key, props.values.activityRewards, 3)}`}
&nbsp;&nbsp;
<Form.Item
{...restField}
label="促销价"
name={[name, 'sendNum']}
fieldKey={[fieldKey, 'sendNum']}
rules={[{ required: true, message: '请输入促销价' }]}
>
<InputNumber min={0} placeholder="促销价" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
) : (
<Form.Item label="优惠券">
<Form.List name="activityRewards">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8 }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, 'rewardValue']}
fieldKey={[fieldKey, 'rewardValue']}
rules={[{ required: true, message: '请选择优惠券' }]}
>
<Select
style={{ width: 200 }}
onChange={(value) => {
const arr = [...quantityLimit]; //克隆的时候改变地址
couponInfo.filter((item) => {
if (item.couponId == value) {
arr[key] = item;
}
});
SetQuantityLimit(arr);
}}
>
{couponInfo.map((item, index) => {
return (
<Select.Option
index={index}
value={item.couponId}
key={item.couponId}
>
{item.couponName}
</Select.Option>
);
})}
</Select>
</Form.Item>
{`可用数量:${canUseCountOrlimit(key, props.values.activityRewards, 1)}`}
&nbsp;&nbsp;
<Form.Item
{...restField}
label={getFieldValue('activityType')==4?"兑换值":"每次领取数量"}
name={[name, 'sendNum']}
fieldKey={[fieldKey, 'sendNum']}
rules={[
{ required: true, message: getFieldValue('activityType')==4?"请输入兑换值":"请输入发送数量"},
{
type: 'number',
min: 0,
max: parseInt(
getFieldValue('activityType')==4?999999999:canUseCountOrlimit(key, props.values.activityRewards, 2),
),
message: '超出领取限制',
},
]}
>
<InputNumber
min={0}
max={getFieldValue('activityType')==4?999999999:canUseCountOrlimit(key, props.values.activityRewards, 2)}
placeholder={getFieldValue('activityType')==4?"兑换值":"每次领取数量"}
/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
)
}
</Form.Item>

<Form.Item noStyle>
<Form.Item {...tailFormItemLayout} style={{ height: 32 }}>
<Form.Item
name="activityStartTime"
label="开始时间"
style={{ float: 'left' }}
rules={[{ required: true }]}
>
<DatePicker placeholder="开始时间" />
</Form.Item>
<Form.Item
name="activityEndTime"
label="结束时间"
style={{ float: 'left', marginLeft: 20 }}
rules={[{ required: true }]}
>
<DatePicker placeholder="结束时间" />
</Form.Item>
</Form.Item>
</Form.Item>
<Form.Item name="remark" label="备注" rules={[{ required: true, max: 200 }]}>
<Input placeholder="请输入备注" />
</Form.Item>
<Form.Item name="activityRule" label="规则说明" rules={[{ required: true, max: 500 }]}>
<Input placeholder="请输入规则说明" />
</Form.Item>

<Form.Item>
<Button type="primary" htmlType="submit" style={{ float: 'right' }}>
保存
</Button>
</Form.Item>
</span>
</Form>
</Modal>
);
};

export default CreateForm;

+ 217
- 0
src/pages/activity/activityInfo/components/CreateForm copy.jsx Voir le fichier

@@ -0,0 +1,217 @@
import React, { useState, useEffect } from 'react';
import {
Row,
Col,
Checkbox,
Space,
Steps,
message,
Modal,
Form,
Input,
Button,
Select,
DatePicker,
} from 'antd';
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';

import { GetActivityConfiguration, GetCoupon } from '../service';

const CreateForm = (props) => {
const { Option, OptGroup } = Select;
const [options, setoptions] = useState();
const [current, setCurrent] = React.useState(0);
const [firstItem, SetFirstItem] = useState();
const [secondItem, SetSecondItem] = useState();
const [isFormShow, SetisFormShow] = useState(true);
const [activityConfiguration, SetactivityConfiguration] = useState([]);

const { Step } = Steps;
const tailFormItemLayout = {
wrapperCol: {
sm: {
span: 500,
offset: 1,
},
},
};

const steps = [
{
title: '活动信息',
content: 'First-content1',
},
{
title: '活动配置',
content: 'Second-content2',
},
];
//初始化数据
useEffect(() => {
const initcouponType = async (value) => {
var data = (await GetActivityConfiguration()).data;
if (data != undefined) {
SetactivityConfiguration(data);
}
};
initcouponType();
}, []);
const next = () => {
SetisFormShow(false);
setCurrent(current + 1);
};

const prev = () => {
SetisFormShow(true);
setCurrent(current - 1);
};
return (
<Modal
title={props.values.id ? '编辑优活动' : '新建活动'}
width={700}
visible={props.createModalVisible}
footer={null} //底部内容
onCancel={() => {
props.onCancel();
}}
//关闭时销毁 Modal 里的子元素
destroyOnClose
>
{/* //-------------------------------------------------------------------------- */}
<Steps current={current}>
{steps.map((item) => (
<Step key={item.title} title={item.title} />
))}
</Steps>
<div className="steps-content" style={{ marginTop: '20px' }}>
<Form
labelCol={{ span: 4 }}
layout="Horizontal"
preserve={false}
initialValues={props.values}
onFinish={props.onFinish}
>
<span style={{ display: isFormShow ? 'block' : 'none' }}>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>

<Form.Item name="activityName" label="活动名称" rules={[{ required: true, max: 50 }]}>
<Input placeholder="请输入活动名称" />
</Form.Item>
<Form.Item name="activityImg" label="活动图片">
<Input placeholder="请输入活动图片" />
</Form.Item>
<Form.Item
name="activityType"
label="活动类型"
defaultValue={props.values.activityType}
rules={[{ required: true }]}
>
<Select placeholder="请选择活动类型">
<OptGroup>
<Select.Option key={1} value={1}>
{' '}
送券
</Select.Option>
<Select.Option key={2} value={2}>
{' '}
其他{' '}
</Select.Option>
</OptGroup>
</Select>
</Form.Item>
<Form.Item noStyle>
<Form.Item {...tailFormItemLayout} style={{ height: 32 }}>
<Form.Item
name="activityStartTime"
label="开始时间"
style={{ float: 'left' }}
rules={[{ required: true }]}
>
<DatePicker placeholder="开始时间" />
</Form.Item>
<Form.Item
name="activityEndTime"
label="结束时间"
style={{ float: 'left', marginLeft: 20 }}
rules={[{ required: true }]}
>
<DatePicker placeholder="结束时间" />
</Form.Item>
</Form.Item>
</Form.Item>
<Form.Item name="remark" label="备注" rules={[{ required: true }]}>
<Input placeholder="请输入备注" />
</Form.Item>
<Form.Item name="activityRule" label="规则说明" rules={[{ required: true }]}>
<Input placeholder="请输入规则说明" />
</Form.Item>
</span>
<span style={{ display: !isFormShow ? 'block' : 'none' }}>
<Form.List name="activityConfiguration">
{(fields, { add, remove }) => (
<>
{activityConfiguration.map(({ value, showName, type, ...restField }) => (
<Space key={value} style={{ marginBottom: 8 }}>
<Form.Item
{...restField}
name={[showName, 'showName']}
fieldKey={[value, 'value']}
>
<Checkbox onChange={value}>{showName}</Checkbox>
</Form.Item>

{/* <Form.Item
{...restField}
name={[name, 'showName']}
fieldKey={[fieldKey, 'showName']}
rules={[{ required: true, message: 'Missing first name' }]}>
<Input placeholder="First Name" />
</Form.Item> */}

{/* <Form.Item
{...restField}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[{ required: true, message: 'Missing last name' }]} >
<Input placeholder="Last Name" />
</Form.Item> */}
</Space>
))}
{/* <Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Add field
</Button>
</Form.Item> */}
</>
)}
</Form.List>

{/* <Form.Item>
<Button type="primary" htmlType="submit" style={{ float: 'right' }}>
保存
</Button>
</Form.Item> */}
</span>
</Form>
</div>
<div className="steps-action">
{current < steps.length - 1 && <Button onClick={() => next()}>下一步</Button>}
{current === steps.length - 1 && <Button onClick={() => prev()}>上一步</Button>}
{current > 0 && (
<Button
type="primary"
style={{ margin: '0 8px' }}
onClick={() => message.success('Processing complete!')}
>
保存
</Button>
)}
</div>
{/* //-------------------------------------------------------------------------- */}
</Modal>
);
};

export default CreateForm;

+ 589
- 0
src/pages/activity/activityInfo/components/CreateForm.jsx Voir le fichier

@@ -0,0 +1,589 @@
import React, { useState, useEffect } from 'react';
import {
InputNumber,
Row,
Col,
Checkbox,
Space,
Steps,
message,
Modal,
Form,
Input,
Button,
Select,
DatePicker,
Upload
} from 'antd';
import { MinusCircleOutlined, PlusOutlined ,UploadOutlined} from '@ant-design/icons';

import { GetActivityConfiguration, GetCouponCanUseCountAndLimit, GetGoodsInfo,GetCosRequestURL } from '../service';
import axios from 'axios';
const CreateForm = (props) => {
const [form] = Form.useForm();
const { Option, OptGroup } = Select;
const [options, setoptions] = useState();
const [current, setCurrent] = React.useState(0);
const [firstItem, SetFirstItem] = useState();
const [secondItem, SetSecondItem] = useState();
const [isFormShow, SetisFormShow] = useState(true);
const [couponInfo, SetCouponInfo] = useState([]);
const [goodsInfo, SetGoodsInfo] = useState([]);
const [isCouponModalVisible, SetIsCouponModalVisible] = useState(false);
const [selectCoupon, SetselectCoupon] = useState(false);
const [quantityLimit, SetQuantityLimit] = useState([]);
const [imgurl, setimgUrl] = useState(props.values.activityImg ? props.values.activityImg : null);
const [goodsPrice, SetGoodsPrice] = useState([]);



const children = [];
for (let i = 10; i < 36; i++) {
children.push(<Option key={i.toString(36) + i}>{i.toString(36) + i}</Option>);
}


const tailFormItemLayout = {
wrapperCol: {
sm: {
span: 500,
offset: 1,
},
},
};
//每次触发
useEffect(()=>{

return ()=>{
if (!props.createModalVisible) {
setimgUrl(null);
}
}
})

//初始化数据
useEffect(() => {
const initcouponType = async (value) => {
var data = (await GetCouponCanUseCountAndLimit()).data;
if (data != undefined) {
SetCouponInfo(data);
}
};
const initGoodsInfoType = async (value) => {
var data = (await GetGoodsInfo()).data;
if (data) {
SetGoodsInfo(data);
}
};
initcouponType();
initGoodsInfoType();
}, []);

const canUseCountOrlimit = (key, value, type) => {
var result = 0;
if (value) {
if (type == 3) {
if (goodsPrice[key]) {
return goodsPrice[key].price;
} else {
const arr = [[...goodsPrice]]; //克隆的时候改变地址
goodsInfo.filter((item) => {
if (item.id == value[key]?.rewardValue) {
arr[key] = item;
}
});
// SetGoodsPrice(arr??[]);
return arr[key] ? (arr[key].price ? arr[key].price : 0) : 0;
}
} else {
var rewardValue = value[key];
var count = 0;
couponInfo.filter((item) => {
if (rewardValue && item.couponId == rewardValue?.rewardValue) {
if (type == 1) {
count = item.canUseCount;
} else {
count = item.limit;
}
}
});
}

if (type == 1) {
result = parseInt(
quantityLimit[key] ? JSON.stringify(quantityLimit[key].canUseCount) : count,
);
} else {
result = parseInt(quantityLimit[key] ? JSON.stringify(quantityLimit[key].limit) : count);
}
} else if (quantityLimit[key]) {
if (type == 1) {
result = quantityLimit[key].canUseCount;
} else {
result = quantityLimit[key].limit;
}
}
return result;
};
const uploadProp = {
name: 'file',
showUploadList: false,
multiple: false,
accept: '.png, .jpg, .jpeg, .gif',
// 这里需要指定文件上传的content-type
headers: {
'Content-Type': 'application/octet-stream',
},
customRequest({
action,
file,
headers,
onError,
onProgress,
onSuccess,
withCredentials,
}) {

//覆盖action 上传之前获取上传地址
var index = file.name.lastIndexOf(".");
var ext = file.name.substr(index + 1);

let fileData = null;
GetCosRequestURL({ directory: "activity", fileExtension: ext, method: "PUT" }).then((r) => {
action = r.allUrl;
const reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = (e) => {
fileData = e.target.result;
// 使用 axios 进行文件上传的请求
axios.put(action, fileData, {
withCredentials,
headers,
onUploadProgress: ({ total, loaded }) => {
// 进行上传进度输出,更加直观
onProgress({ percent: Math.round(loaded / total * 100).toFixed(2) }, file);
},
}).then(response => {
form.setFieldsValue({ activityImg: r.seeUrl });
if (response.status == 200 || response.statusText == 'OK') {
setimgUrl(r.seeUrl)
}
// onSuccess(response, file);
})
.catch(onError);
};
});
// return {
// abort() {
// console.log('upload progress is aborted.');
// },
// };
},
//defaultFileList: props.location.query.values ? props.location.query.values : null

};
return (
<Modal
title={props.values.id ? '编辑优活动' : '新建活动'}
width={700}
visible={props.createModalVisible}
footer={null} //底部内容
onCancel={() => {
props.onCancel();
}}
//关闭时销毁 Modal 里的子元素
destroyOnClose={true}
>
<Form
preserve={false}
form={form}
labelCol={{ span: 4 }}
layout="Horizontal"
initialValues={props.values}
onFinish={props.onFinish}
>
<span style={{ display: isFormShow ? 'block' : 'none' }}>
<Form.Item name="id" hidden={true}>
<Input />
</Form.Item>

<Form.Item name="activityName" label="活动名称" rules={[{ required: true, max: 64 }]}>
<Input placeholder="请输入活动名称" />
</Form.Item>
<Form.Item name="activityImg1" label="请输入活动图片" rules={[{ required: true }]}
>
<Upload
{...uploadProp}
>
{imgurl ? <img src={imgurl} alt="avatar" style={{ width: '30px',height:'40px' }} /> : <Button icon={<UploadOutlined />}>上传</Button>}

</Upload>
</Form.Item>
<Form.Item name="activityImg" label="请输入活动图片" hidden={true} >
<Input placeholder="请输入活动图片" />
</Form.Item>
<Form.Item
name="activityType"
label="活动类型"
defaultValue={props.values.activityType}
rules={[{ required: true }]}
>
<Select
onChange={() => {
// form.setFieldsValue({ activityValue: undefined });
}}
allowClear
placeholder="请选择活动类型"
>
<OptGroup>
<Select.Option key={1} value={1}>
{'送券'}
</Select.Option>
<Select.Option key={2} value={2}>
{'阶梯抵扣金'}
</Select.Option>
<Select.Option key={3} value={3}>
{'特价商品'}
</Select.Option>
<Select.Option key={4} value={4}>
{'阶梯优惠券'}
</Select.Option>
<Select.Option key={5} value={5}>
{'阶梯商品'}
</Select.Option>
<Select.Option key={6} value={6}>
{'阶梯消费次数'}
</Select.Option>
<Select.Option key={7} value={7}>
{'循环消费次数'}
</Select.Option>
{/* <Select.Option key={4} value={4}>
{'商城优惠券'}
</Select.Option> */}
</OptGroup>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) =>
prevValues.activityType !== currentValues.activityType
}
>
{({ getFieldValue }) =>


// (getFieldValue('activityType') == '6' || getFieldValue('activityType') == '7') ? (

// <Form.Item label="消费次数">
// <Form.List name="activityRewards">
// {(fields, { add, remove }) => (
// <>
// {fields.map(({ key, name, fieldKey, ...restField }) => (
// <Space
// key={key}
// style={{ display: 'flex', marginBottom: 8 }}
// align="baseline"
// >
// <Form.Item
// {...restField}
// name={[name, 'rewardValue']}
// fieldKey={[fieldKey, 'rewardValue']}
// rules={[{ required: true, message: '消费次数' }]}
// >
// <InputNumber />
// </Form.Item>
// &nbsp;&nbsp;
// {`根据店铺实际情况奖励`}
// <MinusCircleOutlined onClick={() => remove(name)} />
// </Space>
// ))}
// <Form.Item>
// <Button type="dashed" onClick={() => {

// if (getFieldValue('activityType') == '7') {
// if (!getFieldValue('activityRewards')||getFieldValue('activityRewards').length < 1) {
// add();
// }else{
// message.warning('添加数量已达到上限');
// }
// } else {
// add();
// }


// }} block icon={<PlusOutlined />}>
// 添加
// </Button>
// </Form.Item>
// </>
// )}
// </Form.List>
// </Form.Item>
// ) :
(getFieldValue('activityType') == '2' ||
getFieldValue('activityType') == '4' ||
getFieldValue('activityType') == '5'||
getFieldValue('activityType') == '6'||
getFieldValue('activityType') == '7') ? (

<Form.Item label={getFieldValue('activityType')=='6'||getFieldValue('activityType')=='7'?'消费次数':'消费金额'}>
<Form.List name="activityRewards">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8 }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, 'rewardValue']}
fieldKey={[fieldKey, 'rewardValue']}
rules={[{ required: true, message: '请输入消费金额' }]}
>
<InputNumber />
</Form.Item>
&nbsp;&nbsp;
<Form.Item
{...restField}
label={getFieldValue('activityType')=='6'||getFieldValue('activityType')=='7'||getFieldValue('activityType')=='4' ? "赠送优惠券" : getFieldValue('activityType') == '5' ? "赠送商品" : "赠送金额"}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[{ required: true, message: '请选择' }]} >

{[4,6,7].includes(getFieldValue('activityType')) ? (
<Select style={{ width: 200 }} >
{couponInfo.map((item, index) => {
return (
<Select.Option
index={index}
value={item.couponId}
key={item.couponId}
>
{item.couponName}
</Select.Option>
);
})}
</Select>) :
getFieldValue('activityType') == '5' ? (
<Select style={{ width: 200 }} >
{goodsInfo.map((item, index) => {
return (
<Select.Option index={index} value={item.id} key={item.id}>
{item.name}
</Select.Option>
);
})}
</Select>
) :
(<InputNumber />)
}

</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>

) : getFieldValue('activityType') == '3' ? (
// --------------特价商品--------------
<>
<Form.Item label="商品信息">
<Form.List name="activityRewards">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8 }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, 'rewardValue']}
fieldKey={[fieldKey, 'rewardValue']}
rules={[{ required: true, message: '请选择商品' }]}
>
<Select
style={{ width: 200 }}
onChange={(value) => {
const arr = [...goodsPrice]; //克隆的时候改变地址
goodsInfo.filter((item) => {
if (item.id == value) {
arr[key] = item;
}
});
SetGoodsPrice(arr);
}}
>
{goodsInfo.map((item, index) => {
return (
<Select.Option index={index} value={item.id} key={item.id}>
{item.name}
</Select.Option>
);
})}
</Select>
</Form.Item>
{/* {`原价:${goodsPrice[key]?.price}`} */}
{`原价:${canUseCountOrlimit(key, props.values.activityRewards, 3)}`}
&nbsp;&nbsp;
<Form.Item
{...restField}
label="促销价"
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[{ required: true, message: '请输入促销价' }]} >
<Select mode="tags" style={{ width: '160px' }} placeholder="请输入促销价" maxTagCount='responsive'>
{[]}
</Select>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
</>
// --------------end特价商品end--------------
) : (
// --------------优惠券--------------
<>
<Form.Item label="优惠券">
<Form.List name="activityRewards">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8 }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, 'rewardValue']}
fieldKey={[fieldKey, 'rewardValue']}
rules={[{ required: true, message: '请选择优惠券' }]}
>
<Select
style={{ width: 200 }}
onChange={(value) => {
const arr = [...quantityLimit]; //克隆的时候改变地址
couponInfo.filter((item) => {
if (item.couponId == value) {
arr[key] = item;
}
});
SetQuantityLimit(arr);
}}
>
{couponInfo.map((item, index) => {
return (
<Select.Option
index={index}
value={item.couponId}
key={item.couponId}
>
{item.couponName}
</Select.Option>
);
})}
</Select>
</Form.Item>
{`可用数量:${canUseCountOrlimit(key, props.values.activityRewards, 1)}`}
&nbsp;&nbsp;
<Form.Item
{...restField}
label={getFieldValue('activityType') == 4 ? "兑换值" : "每次领取数量"}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[
{ required: true, message: getFieldValue('activityType') == 4 ? "请输入兑换值" : "请输入发送数量" },
{
type: 'number',
min: 0,
max: parseInt(
getFieldValue('activityType') == 4 ? 999999999 : canUseCountOrlimit(key, props.values.activityRewards, 2),
),
message: '超出领取限制',
},
]}
>
<InputNumber
min={0}
max={getFieldValue('activityType') == 4 ? 999999999 : canUseCountOrlimit(key, props.values.activityRewards, 2)}
placeholder={getFieldValue('activityType') == 4 ? "兑换值" : "每次领取数量"}
/>
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
添加
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
</>
//--------------end优惠券end--------------
)
}
</Form.Item>

<Form.Item noStyle>
<Form.Item {...tailFormItemLayout} style={{ height: 32 }}>
<Form.Item
name="activityStartTime"
label="开始时间"
style={{ float: 'left' }}
rules={[{ required: true }]}
>
<DatePicker placeholder="开始时间" />
</Form.Item>
<Form.Item
name="activityEndTime"
label="结束时间"
style={{ float: 'left', marginLeft: 20 }}
rules={[{ required: true }]}
>
<DatePicker placeholder="结束时间" />
</Form.Item>
</Form.Item>
</Form.Item>
<Form.Item name="remark" label="备注" rules={[{ required: true, max: 200 }]}>
<Input placeholder="请输入备注" />
</Form.Item>
<Form.Item name="activityRule" label="规则说明" rules={[{ required: true, max: 500 }]}>
<Input placeholder="请输入规则说明" />
</Form.Item>

<Form.Item>
<Button type="primary" htmlType="submit" style={{ float: 'right' }}>
保存
</Button>
</Form.Item>
</span>
</Form>
</Modal>
);
};

export default CreateForm;

+ 427
- 0
src/pages/activity/activityInfo/index.jsx Voir le fichier

@@ -0,0 +1,427 @@
import { PlusOutlined } from '@ant-design/icons';
import { Switch, Popconfirm, Space, Tag, 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 moment from 'moment';
import CreateForm from './components/CreateForm';
import ConfigurationForm from './components/ConfigurationForm';
import { CloseOutlined, CheckOutlined } from '@ant-design/icons';
import QRCode from 'qrcode.react';
import {
GetactivityInfo,
AddActivityInfo,
UpdateActivityInfo,
DelActivityInfo,
GetActivityConfiguration,
UpdateActivityAllState,
GetCanUseCoupon,
} from './service';

const key = 'message';

const activityInfManage = () => {
const actionRef = useRef();
const [currentRow, setCurrentRow] = useState();
const [createModalVisible, handleModalVisible] = useState(false);
const [ConfigurationModalVisible, handleConfigurationModalVisible] = useState(false);
const [activityConfiguration, SetactivityConfiguration] = useState([]);
const [activityId, SetActivityId] = useState([]);

//新增
const handleAdd = async (fields) => {
message.loading('正在添加...', key);
try {
if(fields.activityType==3){
fields.activityRewards.forEach(element => {
element.value=`[${element.value.join()}]`
});
}
await AddActivityInfo(JSON.stringify(fields)).then((r) => {
message.destroy(key);
if (r.data) {
message.success('添加成功');
} else {
message.error('添加失败请重试!');
}
});
//刷新数据
actionRef.current.reload();
return true;
} catch (error) {
message.destroy(key);
message.error('添加失败请重试!');
return false;
}
};

//删除(支持批量删除)
const handleRemove = async (value) => {
message.loading('正在删除...', key);
try {
let ids = [];
value.forEach((item) => {
ids.push(item.id);
});
await DelActivityInfo(JSON.stringify(ids)).then((r) => {
message.destroy(key);
if (r.data) {
message.destroy(key);
message.success('删除成功');
} else {
message.error('删除失败请重试!');
}
});
//刷新数据
actionRef.current.reload();
return true;
} catch (error) {
message.destroy(key);
message.error('删除失败请重试!');
return false;
}
};

//修改
const handleUpdate = async (fields) => {
message.loading('正在修改...', key);
try {
debugger
if(fields.activityType==3){
debugger
fields.activityRewards.forEach(element => {
element.value=`[${element.value.join()}]`
});
}
await UpdateActivityInfo(JSON.stringify(fields)).then((r) => {
message.destroy(key);
if (r.data) {
message.success('修改成功');
} else {
message.error(r.errors);
}
});
//刷新数据
actionRef.current.reload();
return true;
} catch (error) {
message.destroy(key);
message.error('修改失败请重试!');
return false;
}
};

//修改活动状态
const updateActivityState = async (fields) => {
message.loading('正在修改...', key);
try {
await UpdateActivityAllState(JSON.stringify(fields)).then((r) => {
message.destroy(key);
if (r.data) {
message.success('修改成功');
} else {
message.error(r.errors);
}
});
//刷新数据
actionRef.current.reload();
return true;
} catch (error) {
message.destroy(key);
message.error('修改失败请重试!');
return false;
}
};

const columns = [
{
title: '主键',
dataIndex: 'id',
hideInSearch: true,
hideInTable: true,
tip: '规则名称是唯一的 key',
},
{
title: '图片',
dataIndex: 'activityImg',
valueType: 'textarea',
hideInSearch: true,
render: (_, record) => (
<img src={record.activityImg} style={{ width: '45px', height: '45px' }}></img>
),
},
{
title: '活动名称',
dataIndex: 'activityName',
valueType: 'textarea',
},
{
title: '活动类型',
dataIndex: 'activityType',
valueEnum: {
1: { text: '送券', status: 'Processing' },
2: { text: '送现金', status: 'Success' },
3: { text: '商品促销', status: 'Success' },
4: { text: '商城优惠券', status: 'Success' },
},
},
{
title: '状态',
dataIndex: 'status',
valueEnum: {
0: { text: '启用', status: 'Processing' },
1: { text: '禁用', status: 'Error' },
},
},
{
title: '是否显示',
dataIndex: 'isShow',
valueEnum: {
0: { text: '不显示', status: 'Processing' },
1: { text: '显示', status: 'Error' },
},
render: (_, record) => (
<>
<Switch
checkedChildren="显示"
unCheckedChildren="不显示"
checked={record.isShow == 1}
onChange={(checked) => {
var data = { activityId: record.id, type: 2, value: record.isShow };
data.value = checked ? 1 : 0;
updateActivityState(data);
}}
/>
</>
),
},
{
title: '是否后台计算',
dataIndex: 'isAutoSend',
valueEnum: {
0: { text: '不显示', status: 'Processing' },
1: { text: '显示', status: 'Error' },
},
render: (_, record) => (
<>
<Switch
checkedChildren="点击参与"
unCheckedChildren="后台计算"
checked={record.isAutoSend == 1}
onChange={(checked) => {
var data = { activityId: record.id, type: 3, value: record.isAutoSend };
data.value = checked ? 1 : 0;
updateActivityState(data);
}}
/>
</>
),
},
{
title: '活动时间',
hideInSearch: true,
render: (text, record, index) => {
return (
<span>
<div>
{moment(record.activityStartTime).format('YYYY-MM-DD')}至
{moment(record.activityEndTime).format('YYYY-MM-DD')}
</div>
</span>
);
},
},
{
title: '创建时间',
dataIndex: 'createAt',
valueType: 'date',
renderFormItem: (item, { defaultRender, ...rest }, form) => {
const status = form.getFieldValue('status');

if (`${status}` === '0') {
return false;
}

if (`${status}` === '3') {
return <Input {...rest} placeholder="请输入异常原因!" />;
}

return defaultRender(item);
},
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
render: (_, record) => {
let template = [
<a
key="update"
type="primary"
onClick={() => {
GetCanUseCoupon(record.id).then((r) => {
if (r.data) {
handleModalVisible(true);
setCurrentRow(() => {
record.activityStartTime = moment(record.activityStartTime);
record.activityEndTime = moment(record.activityEndTime);
record.activityRewards = r.data;
if (record.activityType==3)
{
record.activityRewards.forEach((item)=>{
item.value= JSON.parse(item.value)
})
}
return record;
});
}
});
}}
>
更新
</a>,
<a
key="jy"
type="primary"
onClick={() => {
var data = { activityId: record.id, type: 1, value: record.status };
data.value = record.status == 0 ? 1 : 0;
updateActivityState(data);
}}
>
{record.status == 0 ? '禁用' : '启用'}
</a>,
<a
key="pz"
type="primary"
onClick={() => {
GetActivityConfiguration(record.id).then((r) => {
if (r.data) {
SetActivityId(record.id);
SetactivityConfiguration(r.data);
handleConfigurationModalVisible(true);
}
});
}}
>
配置
</a>,
<Popconfirm
type="primary"
key="del"
title="确认删除吗?"
okText="是"
cancelText="否"
onConfirm={() => {
handleRemove([record]);
}}
onCancel={() => {}}
>
<a href="#">删除</a>
</Popconfirm>,
<a
key="primary"
onClick={() => {
const config = {
title: '二维码',
content: (
<QRCode
id="qrCode"
value={`https://black-pa.com:5443/lead?activityId=${record.id}`}
size={200} // 二维码的大小
fgColor="#000000" // 二维码的颜色
style={{ margin: 'auto' }}
/>
),
};
Modal.info(config);

}}
>
{' '}
二维码
</a>
];
return template;
},
},
];

return (
<PageContainer>
<ProTable
headerTitle="活动信息"
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
request={async (params) => {
var data = [];
var total = 0;
await GetactivityInfo(params).then((r) => {
data = r.data.data;
total = r.data.total;
});
return {
data: data,
success: true,
total: total,
};
}}
toolBarRender={() => [
<Button
key="primary"
type="primary"
onClick={() => {
handleModalVisible(true);
}}
>
新建
</Button>,
]}
columns={columns}
// rowSelection={{}}
/>

<CreateForm
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();
}
}
actionRef.current.reload();
}}
onCancel={() => {
setCurrentRow(undefined);
handleModalVisible(false);
}}
createModalVisible={createModalVisible}
values={currentRow || {}}
/>

<ConfigurationForm
onCancel={() => {
setCurrentRow(undefined);
handleConfigurationModalVisible(false);
}}
activityId={activityId}
config={activityConfiguration}
createModalVisible={ConfigurationModalVisible}
values={currentRow || {}}
/>
</PageContainer>
);
};

export default activityInfManage;

+ 98
- 0
src/pages/activity/activityInfo/service.js Voir le fichier

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

export function GetactivityInfo(data) {
return request(`/kitchen/api/activity-info/activity-info`, {
method: 'post',
data: data,
});
}

export function AddActivityInfo(data) {
return request(`/kitchen/api/activity-info`, {
method: 'post',
data: data,
});
}
export async function GetCosRequestURL(data) {
return request(`/kitchen/api/systemconfig/GetCosRequestSignURL`, {
method: 'POST',
data: data,
});
}


export function UpdateActivityInfo(data) {
return request(`/kitchen/api/activity-info`, {
method: 'put',
data: data,
});
}

export function DelActivityInfo(data) {
return request(`/kitchen/api/activity-info/batch-del-activity-info`, {
method: 'post',
data: data,
});
}

export function GetActivityConfiguration(data) {
return request(`/kitchen/api/activity-info/activity-configuration/${data}`, {
method: 'get',
});
}

//添加活动配置
export function AddactivityConfiguration(data) {
return request(`/kitchen/api/activity-info/addactivity-configuration`, {
method: 'Post',
data: data,
});
}

//修改活动状态
export function UpdateActivityAllState(data) {
return request(`/kitchen/api/activity-info/activity-all-state`, {
method: 'put',
data: data,
});
}

//获取优惠券
export function GetCouponCanUseCountAndLimit(data) {
return request(`/kitchen/api/coupon-helper/coupon-can-use-count-and-limit`, {
method: 'get',
});
}

//获取活动优惠券的配置
export function GetCanUseCoupon(data) {
return request(`/kitchen/api/activity-info/activity-reward-list/${data}`, {
method: 'get',
});
}

//获取商品信息
export function GetGoodsInfo() {
return request(`/kitchen/api/activity-info/goods-info`, {
method: 'get',
});
}


/** 获取商品类型 GET /kitchen/api/rule */
export async function goodsType(data) {
return request(`/kitchen/api/goods-type/goods-types`, {
method: 'POST',
data: data,
// params: { ...params },
// ...(options || {}),
});
}

//获取店铺信息
export async function GetStoreInfos(data) {
return request(`/kitchen/api/printer/store-infos`, {
method: 'Post',
data: data,
});
}

+ 135
- 0
src/pages/activity/activityRecord/index.jsx Voir le fichier

@@ -0,0 +1,135 @@
import { DropboxOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, message, Input, Drawer, Popconfirm } from 'antd';
import React, { useState, useRef, useEffect } from 'react';
import moment from 'moment';
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';

//页面 相当于 class
const activityRecordManage = () => {
/** 新建/更新窗口的弹窗 */
const [createModalVisible, handleModalVisible] = useState(false);
/** 分布更新窗口的弹窗 */

const [showDetail, setShowDetail] = useState(false);
//绑定
const actionRef = useRef();
//活动信息
const [activityInfo, setActivityInfo] = useState();

//初始化数据
useEffect(() => {
const initActivityInfo = async (value) => {
var data = await api.GetActivityInfo();
var list = [];
for (let k in data.data) {
list.push({ activity_Id: k.toString(), text: data.data[k.toString()] });
}
setActivityInfo(list);
};
initActivityInfo();
}, []);

/** 国际化配置 */

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: 'customerName',
valueType: 'textarea',
},
{
title: '客户电话',
dataIndex: 'telePhone',
valueType: 'textarea',
},
{
title: '活动名称',
dataIndex: 'activity_Id',
hideInForm: true,
hideInTable: true,
valueType: 'select',
valueEnum: activityInfo,
},
{
title: '活动名称',
dataIndex: 'activityName',
hideInForm: true,
hideInSearch: true,
},
{
title: '活动时间范围',
hideInSearch: true,
width: 300,
render: (text, record, index) => {
return (
<span>
<div>
{moment(record.activityStartTime).format('YYYY-MM-DD')}至
{moment(record.activityEndTime).format('YYYY-MM-DD')}
</div>
</span>
);
},
},
{
title: '创建时间',
dataIndex: 'createAt',
valueType: 'date',
},
];

return (
<PageContainer>
<ProTable
headerTitle="活动参与记录信息"
actionRef={actionRef}
rowKey="id"
search={{
labelWidth: 120,
}}
columns={columns}
//数据绑定
request={async (params) => {
var activityRecordData = [];
var total = 0;
if (params.activity_Id != undefined) {
params.activity_Id = activityInfo[params.activity_Id].activity_Id;
}
await api.activityRecordList(params).then((r) => {
activityRecordData = r.data.data;
total = r.data.total;
});
return {
data: activityRecordData,
success: true,
total: total,
};
}}
></ProTable>
</PageContainer>
);
};

export default activityRecordManage;

+ 60
- 0
src/pages/activity/activityRecord/services.js Voir le fichier

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

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

export default {
/** 获取活动记录信息 sdsa GET /kitchen/api/rule */
activityRecordList(data) {
return request(`/kitchen/api/activity-participation-record/activity-record`, {
method: 'POST',
data: data,
// params: { ...params },
// ...(options || {}),
});
},
/** 新建活动记录信息 POST /kitchen/api/rule */
addActivityRecord(data) {
// http://localhost:7002
return request(`/kitchen/api/goods`, {
method: 'POST',
// type:'json',
data: data,
// ...(options || {}),
});
},
/** 修改活动记录信息 POST /kitchen/api/rule */
updateActivityRecord(data) {
// http://localhost:7002
return request(`/kitchen/api/goods`, {
method: 'POST',
// type:'json',
data: data,
// ...(options || {}),
});
},
/** 删除活动记录信息 POST /kitchen/api/rule */
removeActivityRecord(data) {
return request(`/kitchen/api/goods`, {
method: 'DELETE',
data: data,
// ...(options || {}),
});
},
/** 删除单个活动记录信息 DELETE /kitchen/api/rule */
removeSingleActivityRecord(id) {
return request(`/kitchen/api/franchisee/${id}`, {
method: 'DELETE',
// data: data,
// ...(options || {}),
});
},
/** 获取活动信息 DELETE /kitchen/api/rule */
GetActivityInfo() {
return request(`/kitchen/api/select/activity`, {
method: 'get',
// params: { ...params },
// ...(options || {}),
});
},
};

+ 69
- 0
src/pages/admin/users/components/CreateForm.jsx Voir le fichier

@@ -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 Voir le fichier

@@ -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 '../../../sys/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 Voir le fichier

@@ -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 '../../../sys/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;

+ 438
- 0
src/pages/admin/users/index.jsx Voir le fichier

@@ -0,0 +1,438 @@
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 '../../sys/Org/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() {
console.log('OK', record);
},
});
} 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
Voir le fichier


+ 56
- 0
src/pages/admin/users/service.js Voir le fichier

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

export async function queryUser(params) {
console.log('查询', params);
return request('/kitchen/api/sysUser/page', {
method: 'POST',
data: {
...params,
},
});
}

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

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

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

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

+ 55
- 0
src/pages/bom/bomreplace/components/CreateForm.jsx Voir le fichier

@@ -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 Voir le fichier

@@ -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 Voir le fichier

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

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

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

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

+ 55
- 0
src/pages/bom/manage/components/AddBomInfo.jsx Voir le fichier

@@ -0,0 +1,55 @@
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="请选择原料名称">
{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="bomQty" label="配方用量">
<InputNumber placeholder="配方用量" min={0} />
</Form.Item>
<Form.Item name="isReplace" label="是否可以替换" >
<Switch checkedChildren="否" unCheckedChildren="是" />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit">
保存
</Button>
</Form.Item>
</Form>
</Modal>
);
};

export default AddBomInfo;

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff

Chargement…
Annuler
Enregistrer