create project

This commit is contained in:
jhnine 2024-03-21 16:43:44 +08:00
parent ab9b9f5250
commit 3d276e556d
207 changed files with 29076 additions and 1 deletions

View File

@ -0,0 +1,92 @@
version: 2
name: 阿里云test环境
description: ""
global:
concurrent: 1
param:
- ref: secret_name
name: ""
value: '"jcce-aliyuncs"'
required: false
type: STRING
hidden: false
- ref: project_name
name: ""
value: '"kubex-frontend"'
required: false
type: STRING
hidden: false
trigger:
webhook: gitlink@1.0.0
event:
- ref: create_tag
ruleset:
- param-ref: tag
operator: EQ
value: '""'
ruleset-operator: AND
workflow:
- ref: start
name: 开始
task: start
- ref: git_clone_0
name: git clone
task: git_clone@1.2.6
input:
ssh_key: ((test.gitlink_sshkey))
remote_url: '"https://gitlink.org.cn/JointCloud/JCC-RIP.git"'
ref: '"refs/heads/earth"'
commit_id: '""'
depth: 1
needs:
- start
- ref: docker_image_build_0
name: docker镜像构建
task: docker_image_build@1.6.0
input:
docker_username: ((test.docker_user))
docker_password: ((test.docker_password))
image_name: '"registry.cn-hangzhou.aliyuncs.com/jcce/jcc-rip'
image_tag: git_clone_0.commit_time
registry_address: '"registry.cn-hangzhou.aliyuncs.com"'
docker_build_path: git_clone_0.git_path
workspace: git_clone_0.git_path
image_clean: true
image_push: true
build_args: '""'
needs:
- shell_0
- ref: end
name: 结束
task: end
needs:
- kubectl_deploy_0
- ref: kubectl_deploy_0
name: kubectl部署资源
task: kubectl_deploy@1.1.0
input:
command: '"apply"'
resource_file_path: git_clone_0.git_path + '/deploy/k8s/jcc-rip-frontend.yaml'
certificate_authority_data: ((test.k8s_cad))
server: '"https://47.92.39.128:6443"'
client_certificate_data: ((test.k8s_ccd))
client_key_data: ((test.k8s_ckd))
hosts: '""'
needs:
- docker_image_build_0
- ref: shell_0
name: shell
image: docker.jianmuhub.com/library/debian:buster-slim
env:
IMAGE_NAME: '"registry.cn-hangzhou.aliyuncs.com/jcce/jcc-rip"'
IMAGE_TAG: git_clone_0.commit_time
SECRET_NAME: global.secret_name
PROJECT_NAME: global.project_name
PROJECT_PATH: git_clone_0.git_path + '/deploy/k8s/'
script:
- cd ${PROJECT_PATH}
- sed -i "s#image_name#${IMAGE_NAME}:${IMAGE_TAG}#" ${PROJECT_NAME}.yaml
- sed -i "s#secret_name#${SECRET_NAME}#" ${PROJECT_NAME}.yaml
- cat ${PROJECT_NAME}.yaml
needs:
- git_clone_0

7
.env.development Normal file
View File

@ -0,0 +1,7 @@
# just a flag
ENV = 'development'
# base api
VUE_APP_BASE_API = '/jcc-ks'
VUE_APP_FUNCTION_API = ''
VUE_APP_PUBLIC_SOURCE_API = ''

8
.env.production Normal file
View File

@ -0,0 +1,8 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/jcc-ks'
VUE_APP_FUNCTION_API = 'apis/v1'
VUE_APP_PUBLIC_SOURCE_API = '/monitor'

5
.eslintignore Normal file
View File

@ -0,0 +1,5 @@
build/*.js
src/assets
public
dist
src/icons/iconfont.js

198
.eslintrc.js Normal file
View File

@ -0,0 +1,198 @@
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}

27
.gitignore vendored Normal file
View File

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

25
Dockerfile Normal file
View File

@ -0,0 +1,25 @@
FROM node:16.20-alpine3.17 AS builder
WORKDIR /app
COPY . .
COPY deploy/nginx/ /app/
RUN npm install --registry=https://registry.npmmirror.com &&\
npm run build:prod
FROM nginx:stable-alpine
WORKDIR /app
#修改alpine源为上海交通大学
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.sjtug.sjtu.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk upgrade && \
apk add --no-cache ca-certificates && update-ca-certificates && \
apk add --update tzdata && \
rm -rf /var/cache/apk/*
COPY --from=builder /app/dist /usr/share/nginx/html/jcc-rip
RUN rm /etc/nginx/conf.d/default.conf
COPY deploy/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,2 +1,45 @@
# JCC-TASK # JCCE
Thanks to KubeSphere and Harvester for providing back-end support for this system.
## need node environment
## install dependencies
```
npm install
```
### Commands for Compilation and Hot Reloading
```
npm run dev
```
### Production Environment Build Commands
```
npm run build
```
```bash
# build for test environment
npm run build:stage
# build for production environment
npm run build:prod
```
## Advanced
```bash
# preview the release environment effect
npm run preview
# preview the release environment effect + static resource analysis
npm run preview -- --report
# code format check
npm run lint
# code format check and auto fix
npm run lint -- --fix
```

14
babel.config.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
'plugins': ['dynamic-import-node']
}
}
}

35
build/index.js Normal file
View File

@ -0,0 +1,35 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

View File

@ -0,0 +1,12 @@
module: {
rules: [
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}

View File

@ -0,0 +1,63 @@
kind: Deployment
apiVersion: apps/v1
metadata:
name: jcc-rip-frontend
namespace: jcce-system
labels:
k8s-app: jcc-rip
spec:
replicas: 1
selector:
matchLabels:
k8s-app: jcc-rip
template:
metadata:
name: jcc-rip
labels:
k8s-app: jcc-rip
spec:
imagePullSecrets:
- name: secret_name
containers:
- name: jcc-rip
image: image_name
resources: {}
imagePullPolicy: Always
securityContext:
privileged: false
procMount: Default
ports:
- containerPort: 80
volumeMounts: []
volumes: []
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
securityContext: {}
schedulerName: default-scheduler
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
---
apiVersion: v1
kind: Service
metadata:
namespace: jcce-system
name: jcc-rip-service
labels:
k8s-service: jcc-rip
spec:
selector:
k8s-app: jcc-rip
ports:
- name: web
protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

55
deploy/nginx/default.conf Normal file
View File

@ -0,0 +1,55 @@
upstream jcc-backend {
server dev.jointcloud.net:443;
}
upstream pcm-backend {
server pcm-core-api:8999;
}
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/host.access.log main;
error_log /var/log/nginx/error.log error;
location / {
root /usr/share/nginx/html/jcc-rip; #站点目录
index index.html index.htm;
}
#monitor项目
location /monitor {
alias /usr/share/nginx/html/jcc-rip/;
try_files $uri $uri/ /monitor/index.html; #解决页面刷新404问题
index index.html index.htm;
autoindex on;
}
location /pcm/ {
proxy_http_version 1.1;
proxy_pass http://pcm-backend/pcm/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 12288m;
root html;
index index.html index.htm;
}
location /apis/ {
proxy_http_version 1.1;
proxy_pass https://jcc-backend/apis/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 12288m;
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

9
jsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

134
package.json Normal file
View File

@ -0,0 +1,134 @@
{
"name": "jcce",
"version": "4.4.0",
"description": "",
"author": "",
"scripts": {
"dev": "vue-cli-service serve",
"lint": "eslint --ext .js,.vue src",
"build:prod": "vue-cli-service build --mode production",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"new": "plop",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"@babel/helper-define-map": "^7.13.12",
"@babel/helper-regex": "^7.10.5",
"@jiaminghi/data-view": "^2.10.0",
"@tweenjs/tween.js": "^18.6.4",
"axios": "0.18.1",
"clipboard": "2.0.4",
"codemirror": "5.45.0",
"core-js": "3.6.5",
"driver.js": "0.9.5",
"dropzone": "5.5.1",
"echarts": "^5.0.0",
"element-ui": "^2.13.2",
"file-saver": "2.0.1",
"flv.js": "^1.6.2",
"fuse.js": "3.4.4",
"is-svg": "^4.3.1",
"js-cookie": "2.2.0",
"jsonlint": "1.6.3",
"jszip": "3.2.1",
"less-loader": "^6.0.0",
"levenary": "^1.1.1",
"moment": "^2.29.1",
"nanoid": "^1.0.2",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"proj4": "^2.8.1",
"randomstring": "^1.2.1",
"screenfull": "4.2.0",
"script-loader": "0.7.2",
"sortablejs": "1.8.4",
"spark-md5": "^3.0.2",
"vue": "2.6.10",
"vue-codemirror": "^4.0.6",
"vue-count-to": "1.0.13",
"vue-dplayer": "^0.0.10",
"vue-grid-layout": "^2.4.0",
"vue-i18n": "^7.3.3",
"vue-json-editor": "^1.4.3",
"vue-router": "3.0.2",
"vue-splitpane": "1.0.4",
"vuedraggable": "^2.20.0",
"vuex": "3.1.0",
"xlsx": "0.14.1",
"xterm": "^4.12.0",
"xterm-addon-attach": "^0.6.0",
"xterm-addon-fit": "^0.5.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "9.5.1",
"babel-eslint": "10.1.0",
"babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"chokidar": "2.1.5",
"connect": "3.6.6",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"filemanager-webpack-plugin": "^3.0.0-alpha.4",
"html-webpack-plugin": "3.2.0",
"husky": "^1.3.1",
"less": "^4.1.1",
"lint-staged": "8.1.5",
"mockjs": "1.0.1-beta3",
"plop": "2.3.0",
"runjs": "4.3.2",
"sass": "1.26.2",
"sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3",
"serve-static": "1.13.2",
"svg-sprite-loader": "4.1.3",
"svgo": "1.2.0",
"uglifyjs-webpack-plugin": "^2.2.0",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"bugs": {
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"keywords": [
"vue",
"admin",
"dashboard",
"element-ui",
"boilerplate",
"admin-template",
"management-system"
],
"license": "MIT",
"lint-staged": {
"src/**/*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/PanJiaChen/vue-element-admin.git"
}
}

5
postcss.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

25
public/click-point.svg Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="_层_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 36 38" style="enable-background:new 0 0 36 38;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状_6_拷贝_3_2_);}
.st2{fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状_6_拷贝_3_3_);}
</style>
<radialGradient id="SVGID_1_" cx="18.1244" cy="1032.4509" r="9.92" gradientTransform="matrix(1.8182 0 0 1.5085 -14.9171 -1535.1727)" gradientUnits="userSpaceOnUse">
<stop offset="0" style="stop-color:#FFF76A;stop-opacity:0.5"/>
<stop offset="1" style="stop-color:#FFE66A;stop-opacity:0"/>
</radialGradient>
<ellipse class="st0" cx="18" cy="22.9" rx="18" ry="15.1"/>
<linearGradient id="形状_6_拷贝_3_2_" gradientUnits="userSpaceOnUse" x1="17.7698" y1="24.9024" x2="18.0223" y2="0.9229">
<stop offset="0.2549" style="stop-color:#FFD153"/>
<stop offset="1" style="stop-color:#FFD153;stop-opacity:0"/>
</linearGradient>
<path id="形状_6_拷贝_3" class="st1" d="M1.8,0L18,25L34.3,0"/>
<linearGradient id="形状_6_拷贝_3_3_" gradientUnits="userSpaceOnUse" x1="17.8853" y1="18.3956" x2="18.0265" y2="4.9804">
<stop offset="0.2644" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<path id="形状_6_拷贝_3_1_" class="st2" d="M8.9,4.5l9.1,14l9.1-14"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

11
public/cs.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
<path d="M131.2,28.5c1.4,0,2.4,1.1,2.4,2.4V169c0,1.4-1.1,2.4-2.4,2.4H68.8c-1.4,0-2.4-1.1-2.4-2.4V31c0-1.4,1.1-2.4,2.4-2.4H131.2
M131.2,18.8H68.8c-6.7,0-12.2,5.4-12.2,12.2V169c0,6.7,5.4,12.2,12.2,12.2h62.4c6.7,0,12.2-5.4,12.2-12.2V31
C143.3,24.2,137.9,18.8,131.2,18.8z"/>
<path d="M79,54.8h42.9v9.7H79V54.8z M79,75.9h42.9v9.7H79V75.9z M94.4,128.3h10.2v10.2H94.4V128.3z M171.6,33.2h-20.6V43h20.6v118.3
h-20.6v9.7h20.6c5.4,0,9.7-4.4,9.7-9.7V43C181.4,37.6,177,33.2,171.6,33.2z M29.7,33.2h20.6V43H29.7v118.3h20.6v9.7H29.7
c-5.4,0-9.7-4.4-9.7-9.7V43C20,37.6,24.3,33.2,29.7,33.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 938 B

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

31
public/index.html Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- 使用CDN的CSS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (var i in htmlWebpackPlugin.options.cdn &&
htmlWebpackPlugin.options.cdn.js) { %>
<script
type="text/javascript"
src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"
></script>
<% } %>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

151
public/locales/en.json Normal file
View File

@ -0,0 +1,151 @@
{
"message": {
"china": "中文",
"english": "English",
"login": "登录r",
"logout": "退出登录r",
"back": "Back",
"save": "Save",
"cancel": "Cancel",
"to": "To",
"startDate": "Start Date",
"endDate": "End Date",
"search": "Search",
"reset": "Reset",
"count": "Count:",
"create": "Create",
"name": "Name",
"confirm": "确定",
"easyCreate": "快捷创建",
"next": "下一步",
"success": "操作成功",
"before": "上一步",
"pleaseInput": "请输入",
"pleaseChoose": "请选择"
},
"menu": {
"taskManagement": "Task Management",
"taskList": "Task List",
"jobTask": "作业任务",
"strategyManagement": "策略管理r",
"schedulingStrategy": "调度策略r",
"warnStrategy": "告警策略r",
"adapterManagement": "驱动器管理r",
"adapterList": "驱动器列表r",
"clusterManagement": "集群管理r",
"resourceUsageTop": "资源中心使用TOP3r",
"resourceCenter": "资源中心r",
"scheduleStatus": "调度情况r",
"messageList": "消息列表r",
"alertNotification": "告警通知r",
"hpcManagement": "超算管理",
"hpcOverview": "超算概览r",
"aiManagement": "智算管理",
"autoStudy": "自动学习",
"warnCenter": "告警中心",
"warnList": "告警列表",
"warnSet": "告警设置",
"overview": "概览",
"clusterNodes": "集群节点",
"project": "项目",
"storage": "存储",
"workload": "工作负载",
"containerNodes": "容器节点",
"services": "服务",
"containerManagement": "容器管理",
"mainEngine": "主机",
"virtualMachine": "虚拟机",
"volume": "卷",
"image": "镜像",
"namespace": "命名空间",
"virtualMachineMana": "虚拟机管理"
},
"check": {
"input": "请输入",
"select": "请选择",
"inputInvalid": "名称格式不合法。字母开头,仅能包含小写字母、数字和-。",
"requireSelect": "必选项"
},
"page": {
"cloud": "cloud",
"hpc": "hpc",
"ai": "ai",
"title": "JCCE",
"JointDomainresourceflux": "Joint Domain resource flux",
"task": "Task",
"taskDescription": "Support for creating tasks that are interoperable and interconnected between different computing environments or hardware platforms, ensuring that each task can run independently on different computing environments or hardware platforms.",
"taskName": "Task Name",
"jobStatus": "Job Status",
"policy": "Policy",
"synergyStatus": "Synergy Status",
"adapter": "Adapter",
"cluster": "Cluster",
"startTime": "Start Time",
"more": "More",
"participantName": "Participant Name",
"calculationType":"计算类型",
"stackName":"技术栈名称",
"stackVersion":"技术栈版本号",
"address":"地址",
"creationTime":"创建时间",
"delete":"删除",
"associatedCluster":"已关联集群",
"description":"description",
"edit":"编辑",
"total":"总条数",
"addAdapter":"新增驱动器",
"updateAdapter": "编辑驱动器",
"bindCluster":"绑定集群",
"clusterAssociation":"集群关联",
"adapterSetting":"驱动器设置",
"adapterDesc":"支持用户上传新的PCM适配技术栈并通过当前功能进行适配绑定",
"clusterDesc": "对多集群以及每个集群的基础资源, 服务组件和应用资源等的统一管理",
"deleteWarn":"您确定要删除吗",
"deleteSuccess":"删除成功",
"viewDetail": "View Detail",
"cpType": "计算类型r",
"taskType": "任务类型r",
"createApplication": "创建容器应用r",
"createJob": "创建容器定时任务r",
"memory": "内存",
"storage": "存储",
"distributedTask": "已下发任务",
"reservedTask": "已保留任务",
"errorTask": "错误任务",
"reason": "原因",
"eventDetail": "事件详情",
"updateDate": "更新时间",
"clusterName": "集群名",
"kbVersion": "kubernetes版本",
"createTime": "创建时间",
"classification": "分类r",
"vmCluster": "虚拟机集群",
"containerCluster": "容器集群",
"hpcDomain": "超算域",
"job": "作业",
"ClusterSum": "集群总数",
"AdapterSum": "驱动器总数",
"TaskSum": "任务总数",
"dispatchAdapter": "分配驱动器",
"strategySelected": "调度策略选择",
"updateCluster": "编辑集群",
"addCluster": "新增集群",
"chooseAdapter": "选择适配器",
"name": "名称",
"monitorAddress": "监控地址",
"version": "版本号",
"otherName": "别名",
"area": "区域",
"projectId": "项目Id",
"tag": "标签",
"authType": "认证方式",
"skak": "SK/AK认证",
"passAuth": "密码认证",
"tokenAuth": "Token认证",
"userName": "用户名",
"password": "密码",
"createVirtualmachine": "创建虚拟机",
"createHpcbase": "创建超算基础模板",
"createAibase": "创建智算任务"
}
}

151
public/locales/zh.json Normal file
View File

@ -0,0 +1,151 @@
{
"message": {
"china": "中文",
"english": "English",
"login": "登录",
"logout": "退出登录",
"back": "返回",
"save": "保存",
"cancel": "取消",
"to": "至",
"startDate": "开始日期",
"endDate": "结束日期",
"search": "搜索",
"reset": "重置",
"count": "总条数:",
"create": "创建",
"name": "名称",
"confirm": "确定",
"easyCreate": "快捷创建",
"next": "下一步",
"success": "操作成功",
"before": "上一步",
"pleaseInput": "请输入",
"pleaseChoose": "请选择"
},
"menu": {
"taskManagement": "任务管理",
"taskList": "任务列表",
"jobTask": "作业任务",
"strategyManagement": "策略管理",
"schedulingStrategy": "调度策略",
"warnStrategy": "告警策略",
"adapterManagement": "驱动器管理",
"adapterList": "驱动器列表",
"clusterManagement": "集群管理",
"resourceUsageTop": "资源中心使用TOP3",
"resourceCenter": "资源中心",
"scheduleStatus": "调度情况",
"messageList": "消息列表",
"alertNotification": "告警通知",
"hpcManagement": "超算管理",
"hpcOverview": "超算概览",
"aiManagement": "智算管理",
"autoStudy": "自动学习",
"warnCenter": "告警中心",
"warnList": "告警列表",
"warnSet": "告警设置",
"overview": "概览",
"clusterNodes": "集群节点",
"project": "项目",
"storage": "存储",
"workload": "工作负载",
"containerNodes": "容器节点",
"services": "服务",
"containerManagement": "容器管理",
"mainEngine": "主机",
"virtualMachine": "虚拟机",
"volume": "卷",
"image": "镜像",
"namespace": "命名空间",
"virtualMachineMana": "虚拟机管理"
},
"check": {
"input": "请输入",
"select": "请选择",
"inputInvalid": "名称格式不合法。字母开头,仅能包含小写字母、数字和-。",
"requireSelect": "必选项"
},
"page": {
"cloud": "数算",
"hpc": "超算",
"ai": "智算",
"title": "云际计算基础平台",
"JointDomainresourceflux": " 云际全域资源态势感知 ",
"task": "任务",
"taskDescription": "支持创建不同计算环境或不同硬件平台之间的任务进行互联互通互操作,确保每个任务在不同的计算环境或硬件平台上都可以独立地运行。 ",
"taskName": "任务名称",
"jobStatus": "作业状态",
"policy": "作业策略",
"synergyStatus": "协同状态",
"adapter": "驱动器",
"cluster": "集群",
"startTime": "作业开始时间",
"more": "更多操作",
"participantName": "Participant名称",
"calculationType":"计算类型",
"stackName":"技术栈名称",
"stackVersion":"技术栈版本号",
"address":"地址",
"creationTime":"创建时间",
"delete":"删除",
"associatedCluster":"已关联集群",
"description":"描述",
"edit":"编辑",
"total":"总条数",
"addAdapter":"新增驱动器",
"updateAdapter": "编辑驱动器",
"bindCluster":"绑定集群",
"clusterAssociation":"集群关联",
"adapterSetting":"驱动器设置",
"adapterDesc":"支持用户上传新的PCM适配技术栈并通过当前功能进行适配绑定",
"clusterDesc": "对多集群以及每个集群的基础资源, 服务组件和应用资源等的统一管理",
"deleteWarn":"您确定要删除吗",
"deleteSuccess":"删除成功",
"viewDetail": "View Detail",
"cpType": "计算类型",
"taskType": "任务类型",
"createApplication": "创建容器应用",
"createJob": "创建容器定时任务",
"memory": "内存",
"storage": "存储",
"distributedTask": "已下发任务",
"reservedTask": "已保留任务",
"errorTask": "错误任务",
"reason": "原因",
"eventDetail": "事件详情",
"updateDate": "更新时间",
"clusterName": "集群名",
"kbVersion": "kubernetes版本",
"createTime": "创建时间",
"classification": "分类",
"vmCluster": "虚拟机集群",
"containerCluster": "容器集群",
"hpcDomain": "超算域",
"job": "作业",
"ClusterSum": "集群总数",
"AdapterSum": "驱动器总数",
"TaskSum": "任务总数",
"dispatchAdapter": "分配驱动器",
"strategySelected": "调度策略选择",
"updateCluster": "编辑集群",
"addCluster": "新增集群",
"chooseAdapter": "选择适配器",
"name": "名称",
"monitorAddress": "监控地址",
"version": "版本号",
"otherName": "别名",
"area": "区域",
"projectId": "项目Id",
"tag": "标签",
"authType": "认证方式",
"skak": "SK/AK认证",
"passAuth": "密码认证",
"tokenAuth": "Token认证",
"userName": "用户名",
"password": "密码",
"createVirtualmachine": "创建虚拟机",
"createHpcbase": "创建超算基础模板",
"createAibase": "创建智算任务"
}
}

13
public/point.svg Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="_层_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 25.3 22" style="enable-background:new 0 0 25.3 22;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#形状_6_拷贝_3_1_);}
</style>
<linearGradient id="形状_6_拷贝_3_1_" gradientUnits="userSpaceOnUse" x1="12.4196" y1="21.8865" x2="12.6413" y2="0.822">
<stop offset="0.2441" style="stop-color:#FFFFFF"/>
<stop offset="1" style="stop-color:#FFFFFF;stop-opacity:0"/>
</linearGradient>
<path id="形状_6_拷贝_3" class="st0" d="M0,0l12.6,22L25.3,0"/>
</svg>

After

Width:  |  Height:  |  Size: 789 B

1
public/ys.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1679903317167" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7202" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M896 853.333333h-128a42.666667 42.666667 0 0 1-42.666667-42.666666v-128a213.333333 213.333333 0 0 0 60.586667-417.28A298.666667 298.666667 0 0 0 215.893333 346.026667 170.666667 170.666667 0 0 0 256 682.666667h42.666667v128a42.666667 42.666667 0 0 1-42.666667 42.666666H128a42.666667 42.666667 0 0 0 0 85.333334h128a128 128 0 0 0 128-128v-128h85.333333v213.333333a42.666667 42.666667 0 1 0 85.333334 0v-213.333333h85.333333v128a128 128 0 0 0 128 128h128a42.666667 42.666667 0 1 0 0-85.333334zM256 597.333333a85.333333 85.333333 0 1 1 0-170.666666 42.666667 42.666667 0 0 0 42.666667-42.666667 213.333333 213.333333 0 0 1 415.146666-68.693333 42.666667 42.666667 0 0 0 33.28 28.586666A128 128 0 0 1 853.333333 469.333333a128 128 0 0 1-128 128H256z" p-id="7203"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

12
public/zs.svg Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
<path d="M175.1,112h-12.6V88h12.6c4.9,0,8.8-3.9,8.8-8.8s-3.9-8.8-8.8-8.8h-12.6V59.8c0-12.3-10-22.2-22.2-22.2h-10.6V24.9
c0-4.9-3.9-8.8-8.8-8.8c-4.9,0-8.8,3.9-8.8,8.8v12.6H88V24.9c0-4.9-3.9-8.8-8.8-8.8s-8.8,3.9-8.8,8.8v12.6H59.8
c-12.3,0-22.2,10-22.2,22.2v10.6H24.9c-4.9,0-8.8,3.9-8.8,8.8s3.9,8.8,8.8,8.8h12.6V112H24.9c-4.9,0-8.8,3.9-8.8,8.8
c0,4.9,3.9,8.8,8.8,8.8h12.6v10.6c0,12.3,10,22.2,22.2,22.2h10.6v12.6c0,4.9,3.9,8.8,8.8,8.8s8.8-3.9,8.8-8.8v-12.6H112v12.6
c0,4.9,3.9,8.8,8.8,8.8s8.8-3.9,8.8-8.8v-12.6h10.6c12.3,0,22.2-10,22.2-22.2v-10.6h12.6c4.9,0,8.8-3.9,8.8-8.8
C183.9,116,179.9,112,175.1,112z M144.8,140.2c0,2.5-2.1,4.6-4.6,4.6H59.8c-2.5,0-4.6-2.1-4.6-4.6V59.8c0-2.5,2.1-4.6,4.6-4.6h80.4
c2.5,0,4.6,2.1,4.6,4.6V140.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

27
src/App.vue Normal file
View File

@ -0,0 +1,27 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
computed: {
jcceTheme() {
return localStorage.getItem('jcceTheme')
}
},
mounted() {
localStorage.setItem('jcceTheme', 'jcceDark') // Please delete code if you need other color
const jcceTheme = this.jcceTheme || 'jcceDark'
document.documentElement.setAttribute('jcceTheme', jcceTheme)
localStorage.setItem('jcceTheme', jcceTheme)
if (jcceTheme === 'jcceDark') {
require('./styles/dark-theme/dark.scss')
document.body.className = jcceTheme
}
}
}
</script>

261
src/api/common/actions.js Normal file
View File

@ -0,0 +1,261 @@
import request from '@/utils/request'
// import moment from 'moment'
export default {
// pod delete
deleteCurrent(clusterName, namespace, name, classification) {
if (classification === 'userProject') {
return request({
url: `/jcc-schedule/api/v1/namespace/delete/${clusterName}/${namespace}/false`,
method: 'delete'
})
} else if (classification === 'storage') {
return request({
url: `/jcc-schedule/api/v1/storage/pvc/${clusterName}/${namespace}/${name}/false`,
method: 'delete'
})
} else if (classification === 'pods') {
return request({
url: `/jcc-schedule/api/v1/pod/delete/${clusterName}/${namespace}/${name}/false`,
method: 'delete'
})
} else if (classification === 'services') { // 服务
return request({
url: `/jcc-schedule/api/v1/service/delete/${clusterName}/${namespace}/${name}/false`,
method: 'delete'
})
} else {
return request({
url: `/jcc-schedule/api/v1/${classification}/delete/${clusterName}/${namespace}/${name}/false`,
method: 'delete'
})
}
},
// return exist: false
// http://119.45.100.73:30880/api/clusters/bj-member2/v1/namespaces post
// {"apiVersion":"v1","kind":"Namespace","metadata":{"name":"what","labels":{},"annotations":{"kubesphere.io/creator":"nudt"}}}
// http://119.45.100.73:30880/apis/apps/v1/namespaces/kubesphere-system/deployments/tower
getVirtualStorage() {
return request({
url: '/jcc-vm/v1/harvester/longhorn.io.nodes',
method: 'get'
})
},
// 通道hash查询接口
getCurrentChannel() {
return request({
url: `/blockChain/apiLedger/curChannel`,
method: 'get'
})
},
// 通道指标统计查询接口
getStatisticsCount(hash) {
return request({
url: `/blockChain/apiLedger/status/${hash}`,
method: 'get'
})
},
getUserCount() {
return request({
url: `/jcc-ledger/contract/getUserNum`
})
},
getTradeCount() {
return request({
url: `/jcc-ledger/contract/getVolume`
})
},
// 块链列表
getBlockList(hash, query) {
return request({
url: `/blockChain/apiLedger/blockAndTxList/${hash}/0`,
method: 'get',
params: query
})
},
// 监控列表
getMonitorList(query) {
// const time = { from: moment().subtract(1, 'months').format('YYYY-MM-DD HH:mm:ss.SSS'), to: moment().add(1, 'days').format('YYYY-MM-DD HH:mm:ss.SSS') }
console.log(query)
return request({
url: `/jcc-ledger/transactions/getTxInfo?chainCodeName=monitor&`,
method: 'get',
params: query
})
},
// 评论列表
getEvaluationList(query) {
// const time = { from: moment().subtract(1, 'months').format('YYYY-MM-DD HH:mm:ss.SSS'), to: moment().add(1, 'days').format('YYYY-MM-DD HH:mm:ss.SSS') }
console.log(query)
return request({
url: `/jcc-ledger/transactions/getTxInfo?chainCodeName=evaluation&`,
method: 'get',
params: query
})
},
// 交易列表
getTradeList(query) {
// const time = { from: moment().subtract(1, 'months').format('YYYY-MM-DD HH:mm:ss.SSS'), to: moment().add(1, 'days').format('YYYY-MM-DD HH:mm:ss.SSS') }
console.log(query)
return request({
url: `/jcc-ledger/transactions/getTxInfo?chainCodeName=record`,
method: 'get',
params: query
})
},
// 交易详情
getTradeDetail(hash) {
return request({
url: `/jcc-ledger/transactions/getTxByHash`,
method: 'get',
params: { txHash: hash }
})
},
getTradeMapByDate(query) {
return request({
url: `/jcc-ledger/transactions/getTxCntGroup`,
method: 'get',
params: query
})
},
// 证书详情
getCertificationDetail(hash) {
return request({
url: `/jcc-ledger/transactions/getCert`,
method: 'get',
params: { txHash: hash }
})
},
// 搜索证书
getCertificationByType(query, queryType) {
return request({
url: `/jcc-ledger/transactions/${queryType}`,
method: 'get',
params: query
})
},
// 函数
checkFunctionName(functionName) {
return request({
url: `/jcc-faas-manager/function/exist/${functionName}`,
method: 'get'
})
},
createFunction(query) {
return request({
url: '/jcc-faas-manager/function/createFunction',
method: 'post',
data: query
})
},
deleteFunctionByName() {
return request({
url: '/jcc-faas-manager/function/delete/functionByName',
method: 'delete'
})
},
deleteFunction(functionId) {
return request({
url: `/jcc-faas-manager/function/deleteFunction/${functionId}`,
method: 'delete'
})
},
getFunctionByFunctionName() {
return request({
url: '/jcc-faas-manager/function/delete/functionByName',
method: 'get'
})
},
invokeFunction(params, query) {
return request({
url: '/jcc-faas-manager/function/invokeFunction',
method: 'post',
params: params,
data: query
})
},
listFunctions(query) {
return request({
url: '/jcc-faas-manager/function/lists',
method: 'get',
params: query || { page: 1, size: 10 }
})
},
// updateFunction() {
// return request({
// url: '/function/updateFunction',
// method: 'put'
// })
// },
getFunctionOverview() {
return request({
url: '/jcc-faas-manager/function/selectFunctionOverview',
method: 'get'
})
},
getFunctionMap() {
return request({
url: '/jcc-faas-manager/function/selectFunctionList',
method: 'get'
})
},
// 大屏总营收接口
getRevenue() {
return request({
url: '/jcc-mall/order/queryRevenue',
method: 'get'
})
},
// 大屏地球查询
getMapArea() {
return request({
url: '/jcc-faas-manager/function/device/queryByArea',
method: 'get'
})
},
// 大屏云厂商服务器数量查询接口
getCloudServerCount() {
return request({
url: '/jcc-faas-manager/function/device/queryByManufacturer',
method: 'get'
})
},
// 大屏服务器数量统计接口
getServerDeviceCount() {
return request({
url: '/jcc-faas-manager/function/device/queryDeviceCount',
method: 'get'
})
},
// 大屏服务器数量统计接口
getOrderStatus() {
return request({
url: '/jcc-mall/order/countOrderStatus',
method: 'get'
})
},
// CPU、内存 总量 用量 旧旧大屏kubeshpere接口
getCoreStatus() {
// return request({
// url: process.env.VUE_APP_BASE_API + '/kapis/clusters/host/monitoring.kubesphere.io/v1alpha3/cluster?start=1628697600&end=1628783999&step=3600s&times=20&metrics_filter=cluster_cpu_usage%7Ccluster_cpu_total%7Ccluster_memory_usage_wo_cache%7Ccluster_memory_total%24',
// method: 'get'
// })
},
// harvester
// cpu 内存状态获取
getUsageStatus() {
return request({
url: '/jcc-vm/v1/metrics.k8s.io.nodes',
method: 'get'
})
}
}

41
src/api/common/article.js Normal file
View File

@ -0,0 +1,41 @@
import request from '@/utils/request'
export function fetchList(query) {
return request({
url: '/vue-element-admin/article/list',
method: 'get',
params: query
})
}
export function fetchArticle(id) {
return request({
url: '/vue-element-admin/article/detail',
method: 'get',
params: { id }
})
}
export function fetchPv(pv) {
return request({
url: '/vue-element-admin/article/pv',
method: 'get',
params: { pv }
})
}
export function createArticle(data) {
return request({
url: '/vue-element-admin/article/create',
method: 'post',
data
})
}
export function updateArticle(data) {
return request({
url: '/vue-element-admin/article/update',
method: 'post',
data
})
}

8
src/api/common/qiniu.js Normal file
View File

@ -0,0 +1,8 @@
import request from '@/utils/request'
export function getToken() {
return request({
url: '/qiniu/upload/token', // 假地址 自行替换
method: 'get'
})
}

View File

@ -0,0 +1,17 @@
import request from '@/utils/request'
export function searchUser(name) {
return request({
url: '/vue-element-admin/search/user',
method: 'get',
params: { name }
})
}
export function transactionList(query) {
return request({
url: '/vue-element-admin/transaction/list',
method: 'get',
params: query
})
}

38
src/api/common/role.js Normal file
View File

@ -0,0 +1,38 @@
import request from '@/utils/request'
export function getRoutes() {
return request({
url: '/vue-element-admin/routes',
method: 'get'
})
}
export function getRoles() {
return request({
url: '/vue-element-admin/roles',
method: 'get'
})
}
export function addRole(data) {
return request({
url: '/vue-element-admin/role',
method: 'post',
data
})
}
export function updateRole(id, data) {
return request({
url: `/vue-element-admin/role/${id}`,
method: 'put',
data
})
}
export function deleteRole(id) {
return request({
url: `/vue-element-admin/role/${id}`,
method: 'delete'
})
}

45
src/api/common/user.js Normal file
View File

@ -0,0 +1,45 @@
import request from '@/utils/request'
// const qs = require('qs')
// export function login(data) {
// return request({
// url: process.env.VUE_APP_BASE_API + '/login',
// method: 'post',
// data: data,
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// }
// export function harvesterLogin(data) {
// return request({
// url: '/virtual/v3-public/localProviders/local?action=login',
// method: 'post',
// // params: { action: 'login'},
// data: data,
// headers: {
// 'Content-Type': 'application/json;charset=UTF-8'
// }
// })
// }
// export function harvesterFirstLogin(data) {
// return request({
// url: '/virtual/v3-public/authProviders',
// method: 'get',
// headers: {
// 'Content-Type': 'application/json;charset=UTF-8'
// }
// })
// }
export function blockChainLogin(query) {
return request({
url: `/blockChain/authLedger/login`,
method: 'post',
data: query,
headers: {
'Content-Type': 'application/json'
}
})
}

62
src/api/common/warning.js Normal file
View File

@ -0,0 +1,62 @@
import request from '@/utils/request'
const preUrl = '/jcc-alert'
export function getWarningListByPod(params) {
return request({
url: preUrl + '/alarm/getAllAlarms/1',
method: 'get',
params: params
})
}
export function getWarningListByVirtual(params) {
return request({
url: preUrl + '/alarm/getAllAlarms/2',
method: 'get',
params: params
})
}
export function getWarningDetail(id) {
return request({
url: preUrl + '/alarm/getAlarmDetail/' + id,
method: 'get'
})
}
export function getWarningSet(params) {
return request({
url: preUrl + '/alarm/getAllRules/3',
method: 'get',
params: params
})
}
export function getClusters() {
return request({
url: preUrl + '/alarm/getClusters',
method: 'get'
})
}
// 删除告警
export function deleteWarning(id) {
return request({
url: preUrl + '/alarm/deleteById/' + id,
method: 'delete'
})
}
export function deleteWarningRule(id) {
return request({
url: preUrl + '/alarm/deleteRule/' + id,
method: 'delete'
})
}
export function postWarningRule(data) {
return request({
url: preUrl + '/alarm/createAlarmRule',
method: 'post',
data: data
})
}

View File

@ -0,0 +1,25 @@
import request from '@/utils/request'
// 集群管理页面--所有集群信息
export function getClusterList(params) {
return request({
url: '/pcm/v1/adapter/cluster/list',
method: 'get',
params
})
}
// 资源清单
export function getServerResources() {
return request({
url: '/jcc-schedule/api/v1/resource/getServerResources',
method: 'get'
})
}
export function getClusterSum() {
return request({
url: '/pcm/v1/adapter/clusterSum',
method: 'get'
})
}

48
src/api/task/task.js Normal file
View File

@ -0,0 +1,48 @@
import request from '@/utils/request'
export const getAppList = (params) => {
return request({ url: '/pcm/v1/core/task/list', method: 'get', params })
}
export const getAppPodsByAppName = (name, params) => {
return request({ url: '/pcm/v1/apps/pods/' + name, method: 'get', params })
}
export const addApp = (data) => {
return request({ url: '/pcm/v1/core/task/create', method: 'post', data })
}
export const addNewApp = (params) => {
return request({ url: '/pcm/v1/core/commitTask', method: 'post', params })
}
export const getDetail = (name, params) => {
return request({ url: '/pcm/v1/apps/detail/' + name, method: 'get', params })
}
export const getCPUsage = (params) => {
return request({ url: `/pcm/v1/storage/perCenterComputerPowers`, method: 'get', params })
}
export const getCharts = (params) => {
return request({ url: '/pcm/v1/cloud/controller/Metrics', method: 'get', params })
}
export const getAppDetail = (appName, params) => {
return request({ url: `/pcm/v1/apps/appDetail/${appName}`, method: 'get', params })
}
export const getAppDetailInfo = (appName, params) => {
return request({ url: `/pcm/v1/apps/getAppByAppName/${appName}`, method: 'get', params })
}
export const getAppDistributeInfo = (appName, params) => {
return request({ url: `/pcm/v1/apps/distribute/${appName}`, method: 'get', params })
}
// 暂停
export const pauseApp = (params) => {
return request({ url: `/pcm/v1/apps/pauseApp?nsID=${params.nsID}&name=${params.name}`, method: 'put' })
}
// 启动
export const startApp = (params) => {
return request({ url: `/pcm/v1/apps/startApp?nsID=${params.nsID}&name=${params.name}`, method: 'put' })
}
// 重启
export const restartApp = (params) => {
return request({ url: `/pcm/v1/apps/restartApp?nsID=${params.nsID}&name=${params.name}`, method: 'put' })
}
// 删除
export const deleteApp = (params) => {
return request({ url: `/pcm/v1/apps/deleteApp?nsID=${params.nsID}&name=${params.name}`, method: 'delete' })
}

View File

@ -0,0 +1,161 @@
// 大屏云厂商服务器数量查询接口
import request from '@/utils/request'
export function getTotalName() {
return request({
url: '/jcc-faas-manager/function/device/queryByManufacturer',
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
// 大屏服务器数量统计接口
export function getTotalNum() {
return request({
url: '/jcc-faas-manager/function/device/queryDeviceCount',
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
// 大屏地球地区查询接口
export function originEarthRegion() {
return request({
url: '/jcc-faas-manager/function/device/queryByArea',
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
export function getEarthRegion() {
return request({
// url: '/jcc-faas-manager/function/device/queryByArea',
url: '/pcm/v1/core/listCenter',
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
export function getProvinceDetail(id) {
return request({
url: '/pcm/v1/core/listCluster/' + id,
method: 'get'
// headers: {
// 'Content-Type': 'application/json'
// }
})
}
// 大屏地球各个区域服务器资源详细接口
export function getEarthDetails(area) {
return request({
url: '/jcc-extend/container/device/queryResDetailByArea?area=' + area,
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
// CPU平均使用率接口
// export function getCpuAverage(start, end, step) {
// return request({
// url: '/monitoringscreen/api/v1/query_range?query=avg(1%20-%20avg(rate(node_cpu_seconds_total%7Borigin_prometheus%3D~%22%22%2Cjob%3D~%22node-exporter%22%2Cmode%3D%22idle%22%7D%5B2m%5D))%20by%20(instance))%20*%20100' + '&start=' + start + '&end=' + end + '&step=' + step + '&_=1642578431700',
// method: 'get',
// data: JSON,
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// }
// 整体总负载 平均使用情况
export function getTotalAverage() {
return request({
url: '/jcc-schedule/api/v1/resource/getOverallMetrics',
method: 'get'
})
}
// 计量计费接口总价格
export function getMeasure(start, end, step) {
return request({
url: process.env.VUE_APP_BASE_API + '/kapis/clusters/host/metering.kubesphere.io/v1alpha1/cluster' + '?start=' + start + '&end=' + end + '&step=' + step + 's&metrics_filter=meter_cluster_cpu_usage%7Cmeter_cluster_memory_usage%7Cmeter_cluster_net_bytes_transmitted%7Cmeter_cluster_net_bytes_received%7Cmeter_cluster_pvc_bytes_total&resources_filter=host',
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
// // 内存整体负载接口
// export function getRamLoad(start, end, step) {
// return request({
// url: '/monitoringscreen/api/v1/query_range?query=sum(node_memory_MemTotal_bytes%7Borigin_prometheus%3D~%22%22%2Cjob%3D~%22node-exporter%22%7D%20-%20node_memory_MemAvailable_bytes%7Borigin_prometheus%3D~%22%22%2Cjob%3D~%22node-exporter%22%7D)&start=' + start + '&end=' + end + '&step=' + step + '&_=1642669327729',
// method: 'get',
// data: JSON,
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// }
// 计量计费列表接口
export function getMeteringList(start, end, step) {
return request({
url: process.env.VUE_APP_BASE_API + '/kapis/clusters/host/metering.kubesphere.io/v1alpha1/cluster' + '?start=' + start + '&end=' + end + '&step=' + step + 's&metrics_filter=meter_cluster_cpu_usage%7Cmeter_cluster_memory_usage%7Cmeter_cluster_net_bytes_transmitted%7Cmeter_cluster_net_bytes_received%7Cmeter_cluster_pvc_bytes_total&resources_filter=host',
method: 'get',
data: JSON,
headers: {
'Content-Type': 'application/json'
}
})
}
// // CPU整体负载接口
// export function getCpuAllload(start, end) {
// return request({
// url: '/monitoringscreen/api/v1/query_range?query=node_load15%7Binstance%3D~%22k8s-master%22%7D&start=' + start + '&end=' + end + '&step=900&_=1645409802467',
// method: 'get',
// data: JSON,
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// }
// 累计任务情况接口
export function getTaskList() {
return request({
url: '/pcm/v1/core/taskList',
method: 'get'
})
}
// 累计任务情况计数
export function getTaskCount() {
return request({
url: '/pcm/v1/core/jobTotal',
method: 'get'
})
}
// 控制面板 控制跨域调度任务
export function createScheduleTask(data) {
return request({
url: '/pcm/v1/core/scheduleTaskByYaml',
method: 'post',
data
})
}

View File

@ -0,0 +1,282 @@
import request from '@/utils/request'
const preUrl = '/apis/jcc-admin'
export function login(username, password) {
return request({
url: preUrl + '/admin/login',
method: 'post',
data: {
username,
password
}
})
}
export function getInfo() {
return request({
url: preUrl + '/admin/info',
method: 'get'
})
}
export function logout() {
return request({
url: preUrl + '/admin/logout',
method: 'post'
})
}
export function getUserList(params) {
return request({
url: preUrl + '/admin/list',
method: 'get',
params: params
})
}
export function createAdmin(data) {
return request({
url: preUrl + '/admin/register',
method: 'post',
data: data
})
}
export function updateAdmin(id, data) {
return request({
url: preUrl + '/admin/update/' + id,
method: 'put',
data: data
})
}
export function updateUserStatus(id, params) {
return request({
url: preUrl + '/admin/updateStatus/' + id,
method: 'put',
params: params
})
}
export function deleteAdmin(id) {
return request({
url: preUrl + '/admin/delete/' + id,
method: 'delete'
})
}
export function getRoleByAdmin(id) {
return request({
url: preUrl + '/admin/role/' + id,
method: 'get'
})
}
export function allocRole(data) {
return request({
url: preUrl + '/admin/role/update',
method: 'put',
data: data
})
}
// 角色列表
export function getRoleList(params) {
return request({
url: preUrl + '/role/list',
method: 'get',
params: params
})
}
export function createRole(data) {
return request({
url: preUrl + '/role/create',
method: 'post',
data: data
})
}
export function updateRole(id, data) {
return request({
url: preUrl + '/role/update/' + id,
method: 'put',
data: data
})
}
export function updateRoleStatus(id, params) {
return request({
url: preUrl + '/role/updateStatus/' + id,
method: 'put',
params: params
})
}
export function deleteRole(data) {
return request({
url: preUrl + '/role/delete',
method: 'delete',
data: data
})
}
export function fetchAllRoleList() {
return request({
url: preUrl + '/role/listAll',
method: 'get'
})
}
export function listMenuByRole(roleId) {
return request({
url: preUrl + '/role/listMenu/' + roleId,
method: 'get'
})
}
export function listResourceByRole(roleId) {
return request({
url: preUrl + '/role/listResource/' + roleId,
method: 'get'
})
}
export function allocMenu(data) {
return request({
url: preUrl + '/role/allocMenu',
method: 'post',
params: data
})
}
export function allocResource(data) {
return request({
url: preUrl + '/role/allocResource',
method: 'post',
params: data
})
}
// 菜单列表
export function getMenuList(parentId, params) {
return request({
url: preUrl + '/menu/list/' + parentId,
method: 'get',
params: params
})
}
export function deleteMenu(id) {
return request({
url: preUrl + '/menu/delete/' + id,
method: 'delete'
})
}
export function createMenu(data) {
return request({
url: preUrl + '/menu/create',
method: 'post',
data: data
})
}
export function updateMenu(id, data) {
return request({
url: preUrl + '/menu/update/' + id,
method: 'put',
data: data
})
}
export function getMenu(id) {
return request({
url: preUrl + '/menu/' + id,
method: 'get'
})
}
export function updateHidden(id, params) {
return request({
url: preUrl + '/menu/updateHidden/' + id,
method: 'put',
params: params
})
}
export function fetchTreeList() {
return request({
url: preUrl + '/menu/treeList',
method: 'get'
})
}
// 资源列表
export function getResourceList(params) {
return request({
url: preUrl + '/resource/list',
method: 'get',
params: params
})
}
export function createResource(data) {
return request({
url: preUrl + '/resource/create',
method: 'post',
data: data
})
}
export function updateResource(id, data) {
return request({
url: preUrl + '/resource/update/' + id,
method: 'put',
data: data
})
}
export function deleteResource(id) {
return request({
url: preUrl + '/resource/delete/' + id,
method: 'delete'
})
}
export function fetchAllResourceList() {
return request({
url: preUrl + '/resource/listAll',
method: 'get'
})
}
export function listAllCate() {
return request({
url: preUrl + '/resourceCategory/listAll',
method: 'get'
})
}
export function createResourceCategory(data) {
return request({
url: preUrl + '/resourceCategory/create',
method: 'post',
data: data
})
}
export function updateResourceCategory(id, data) {
return request({
url: preUrl + '/resourceCategory/update/' + id,
method: 'put',
data: data
})
}
export function deleteResourceCategory(id) {
return request({
url: preUrl + '/resourceCategory/delete/' + id,
method: 'delete'
})
}

BIN
src/assets/JCCE-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1708954274349" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13419" xmlns:xlink="http://www.w3.org/1999/xlink" width="18" height="18"><path d="M892.489143 917.942857a35.620571 35.620571 0 0 1-39.643429-31.451428 36.571429 36.571429 0 0 1 0-6.509715v-140.361143c0-38.546286-25.380571-38.546286-34.962285-38.546285a43.885714 43.885714 0 0 0-45.129143 43.154285V880.274286a39.277714 39.277714 0 0 1-78.409143 0v-212.845715a35.766857 35.766857 0 0 1 33.206857-38.180571 47.981714 47.981714 0 0 1 41.691429 20.333714 102.4 102.4 0 0 1 62.976-20.333714 92.818286 92.818286 0 0 1 99.035428 102.912v147.968a36.571429 36.571429 0 0 1-10.166857 27.136 39.058286 39.058286 0 0 1-28.452571 10.678857z m-267.117714-8.557714H471.04c-49.005714 0-70.875429-21.942857-70.875429-70.363429V622.153143c0-48.64 21.942857-70.363429 70.875429-70.363429h147.529143a37.083429 37.083429 0 1 1 0 74.166857H485.376a10.313143 10.313143 0 0 0-4.096 0.585143v63.414857h121.417143a34.011429 34.011429 0 0 1 37.376 36.132572 34.596571 34.596571 0 0 1-37.376 36.571428H480.987429v68.681143a12.288 12.288 0 0 0 0.365714 3.584 31.232 31.232 0 0 0 3.876571 0H625.371429a35.254857 35.254857 0 0 1 37.376 32.914286 35.84 35.84 0 0 1 0 4.022857 34.669714 34.669714 0 0 1-31.817143 37.302857 35.620571 35.620571 0 0 1-5.632 0zM298.422857 479.012571a36.571429 36.571429 0 0 1-41.106286-38.692571V350.573714H159.524571c-52.516571 0-75.995429-23.478857-75.995428-75.776V167.424c0-52.077714 23.478857-75.337143 75.995428-75.337143h97.718858V56.027429a35.547429 35.547429 0 0 1 32.621714-38.253715 36.571429 36.571429 0 0 1 6.582857 0c34.669714 0 41.984 20.772571 41.984 38.180572v36.132571h98.669714c52.516571 0 75.995429 23.259429 75.995429 75.337143v107.373714c0 52.443429-23.478857 75.776-75.995429 75.776H338.505143V440.32a36.571429 36.571429 0 0 1-33.645714 38.765714 36.571429 36.571429 0 0 1-6.436572 0zM172.032 168.521143c-7.314286 0-8.118857 0.658286-8.118857 8.118857v89.380571c0 7.314286 0.658286 8.192 8.118857 8.192h85.284571V168.521143H171.958857z m166.546286 105.618286h86.235428c7.314286 0 8.118857-0.950857 8.118857-8.118858V176.566857c0-7.314286-0.950857-8.118857-8.118857-8.118857h-86.308571v105.691429zM512.146286 1024a512 512 0 0 1-497.371429-632.758857 41.325714 41.325714 0 0 1 49.298286-30.061714 40.740571 40.740571 0 0 1 30.134857 49.078857 431.542857 431.542857 0 0 0 417.938286 532.187428 40.740571 40.740571 0 1 1 0 81.554286z m456.557714-354.450286a40.740571 40.740571 0 0 1-39.643429-50.761143 431.542857 431.542857 0 0 0-416.914285-537.307428 40.740571 40.740571 0 1 1 0-81.481143 512 512 0 0 1 496.201143 638.829714 40.813714 40.813714 0 0 1-39.789715 30.72z" fill="#eeeeee" p-id="13420"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
src/assets/log-img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

31
src/common/font/font.css Normal file
View File

@ -0,0 +1,31 @@
@font-face {
font-family: 'DISPLAY FREE TFB'; /* 重命名字体名 */
src: url('DISPLAY FREE TFB.ttf'); /* 引入字体 */
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'PangMenZhengDao'; /* 重命名字体名 */
src: url('PangMenZhengDao.ttf'); /* 引入字体 */
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'SourceHanSansCN'; /* 重命名字体名 */
src: url('SourceHanSansCN.otf'); /* 引入字体 */
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'PingFang SC'; /* 重命名字体名 */
src: url('PingFang SC.ttf'); /* 引入字体 */
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Impact'; /* 重命名字体名 */
src: url('impact.ttf'); /* 引入字体 */
font-weight: normal;
font-style: normal;
}

BIN
src/common/font/impact.ttf Normal file

Binary file not shown.

View File

@ -0,0 +1,241 @@
<template>
<!-- 初始容器 其他 -->
<el-dialog v-if="dialogVisible" :close-on-click-modal="false" width="80%" :title="(!createContainerForm.name?'添加':'编辑')+'容器'" :visible.sync="dialogVisible" append-to-body>
<el-form ref="createContainerForm" :model="createContainerForm">
<div class="border">
<p>
容器设置
<span class="tips">对容器的名称及容器的计算资源进行设置</span>
</p>
<el-form-item label="镜像" required="">
<el-input v-model="createContainerForm.image" placeholder="直接输入名称 例nginx:latest" />
</el-form-item>
</div>
<div class="border">
<p>
端口设置
<span class="tips">设置容器的访问策略</span>
</p>
<div>
<div>
<el-form-item
v-for="(tag, index) in createContainerForm.ports"
:key="'tag'+index"
label=""
>
<el-row>
<el-col :span="5">
<el-tooltip
class="item"
effect="dark"
content="为了充分利用应用治理的能力,请选择服务实际使用的协议。例如,如果服务暴露的是 HTTP 服务,则选择 http 协议,会生成形如 http-[name] 的端口名称。"
placement="top-start"
>
<i class="el-icon-question" />
</el-tooltip>
协议
<el-select v-model="tag.protocol" style="width:70%" @change="selectPolicy(tag)">
<el-option
v-for="item in protocolOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-col>
<el-col :span="5">
名称
<el-input v-model="tag.name" style="width:70%" />
</el-col>
<el-col :span="5">
容器端口
<el-input v-model="tag.containerPort" :max="65535" type="number" style="width:70%" />
</el-col>
<el-col v-if="classification === 'statefulsets'" :span="5">
服务端口
<el-input v-model="tag.servicePort" :max="65535" type="number" style="width:70%" />
</el-col>
<el-col :span="4"><el-button icon="el-icon-delete" circle @click.prevent="removeTag(tag,index)" /></el-col>
</el-row>
</el-form-item>
<el-button type="primary" plain round :disabled="addTagNumCheck" @click="addTag">添加端口</el-button>
</div>
</div></div></el-form>
<div slot="footer" class="dialog-footer">
<el-button icon="el-icon-close" circle @click="dialogVisible = false" />
<el-button icon="el-icon-check" circle @click="ok" />
</div>
</el-dialog>
</template>
<script>
import moment from 'moment'
import { isEmpty } from 'lodash'
import generate from 'nanoid/generate'
import { mountOptions, imagePullPolicyOptions, protocolOptions } from '@/utils/map'
export default {
props: {
value: {
type: Boolean,
default: false
},
formData: {
type: Object,
default: () => {
return {
ports: []
}
}
},
namespace: {
type: String,
default: ''
},
classification: {
type: String,
default: ''
}
// isEdit: {
// type: Boolean,
// default: false
// }
},
data() {
return {
imageList: [],
activeName: 'first',
imageData: undefined,
noImage: false,
loading: false,
mountOptions,
editInfoForm: {},
imagePullPolicyOptions,
protocolOptions,
moment
}
},
computed: {
dialogVisible: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
},
createContainerForm: {
get() {
return this.formData
},
set(value) {
// this.$emit('input', value)
}
},
addTagNumCheck() {
let flag = false
this.createContainerForm.ports.forEach(e => {
if (e.containerPort === '') {
flag = true
}
})
return flag
}
},
watch: {
// 'createContainerForm.image'(newVal) {
// throttle(this.selectMirror(), 800)
// }
},
mounted() {
moment.locale('zh-cn')
// this.getImageList()
if (this.classification === 'statefulsets') {
this.createContainerForm.ports.push({ protocol: 'HTTP', name: 'http-0', containerPort: 1, servicePort: 1 })
}
},
methods: {
selectPolicy(tag) {
tag.name = tag.protocol.toLowerCase() + '-'
},
addTag() {
if (this.classification === 'statefulsets') {
this.createContainerForm.ports.push({ protocol: 'HTTP', name: 'http-0', containerPort: 1, servicePort: 1 })
} else {
this.createContainerForm.ports.push({ protocol: 'HTTP', name: 'http-0', containerPort: 1 })
}
},
removeTag(tag, index) {
this.createContainerForm.ports.splice(index, 1)
// this.blurInput()
},
// getImageList() {
// this.$Api.getImagesList().then(res => {
// const arr = res.summaries.map(e => {
// return { name: e.name, description: e.short_description, star: e.star_count, imgUrl: e.logo_url.large || e.logo_url.small }
// })
// this.imageList = arr
// })
// },
useDefaultPorts() {
//
const ports = this.imageData.exposedPorts.map(port => {
const protocol = port.split('/')[1]
const containerPort = Number(port.split('/')[0])
return {
name: `${protocol}-${containerPort}`,
protocol: protocol.toUpperCase(),
containerPort: parseInt(containerPort),
servicePort: containerPort
}
})
if (!isEmpty(ports)) {
this.createContainerForm.ports = ports
}
},
ok() {
// generate('0123456789abcdefghijklmnopqrstuvwxyz', length || 6)
const container = Object.assign(this.createContainerForm)
console.log(container)
if (isEmpty(this.createContainerForm.ports)) {
this.$delete(this.createContainerForm, 'ports')
}
// container.imageTag = this.imageData.imageTag
// TODO container
if (!this.createContainerForm.name) {
container.name = 'container-' + generate('0123456789abcdefghijklmnopqrstuvwxyz', 6)
container.imagePullPolicy = 'IfNotPresent'
this.$emit('addImage', container)
} else {
this.$emit('editImage', container)
}
this.dialogVisible = false
//
// if(this.createContainerForm.readOnly !== 'null'){
// this.formData.spec.template.spec.containers.forEach(e=>{
// })
},
handleClick() {}
}
}
</script>
<style lang="scss" scoped>
.border{
border: 1px solid var(--tabsCardBorderColor);
padding: 10px;
margin-bottom: 10px;
}
.grayCard{
// background: #eeeeee;
p{margin: 0;}
.tips{
margin-top: 0;
margin-bottom: 5px;
}
table td{padding: 0 5px; border: 0!important;}
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<!-- 添加存储卷 -->
<el-dialog v-if="dialogVisible" :close-on-click-modal="false" width="80%" title="存储卷" :visible.sync="dialogVisible" append-to-body>
<el-form ref="volumeData" :model="volumeData">
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="已有存储卷" name="first">
<el-form-item
v-if="activeName==='first'"
prop="firstName"
label=""
required
>
<el-select v-model="volumeData.firstName" class="selectPro" placeholder="选择已有存储卷">
<el-option
v-for="item in volumesList"
:key="item.label"
:label="item.label"
:value="item.label"
>
<span style="width:50%; display:block;float:left">{{ item.label }}{{ item.alias?'('+item.alias+')':'' }}存储类型{{ item.storageClassName }}</span>
<span style="width:10%;display:block;float:left; color: #8492a6; font-size: 13px">容量{{ item.storage }}</span>
<span style="width:40%; display:block;float:left; text-align:right">访问模式{{ item.accessModes }}</span>
</el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="临时存储卷" name="second">
<el-form-item
v-if="activeName==='second'"
prop="secondName"
label="存储卷名称"
required
>
<el-input v-model="volumeData.secondName" :maxlength="63" />
<span class="tips">最长 63 个字符只能包含小写字母数字及分隔符("-")且必须以小写字母或数字开头及结尾</span>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="HostPath" name="third">
<el-alert
title="HostPath 将主机的文件系统挂载到Pod中它使一些应用程序能逃出对其做出的隔离限制请谨慎使用。"
type="warning"
/>
<el-form-item
v-if="activeName==='third'"
prop="thirdName"
label="存储卷名称"
required
>
<el-input v-model="volumeData.thirdName" :maxlength="63" />
<span class="tips">最长 63 个字符只能包含小写字母数字及分隔符("-")且必须以小写字母或数字开头及结尾</span>
</el-form-item>
<el-form-item
v-if="activeName==='third'"
prop="hostPath"
label="HostPath"
required
>
<el-input v-model="volumeData.path" :maxlength="63" />
<span class="tips">最长 63 个字符只能包含小写字母数字及分隔符("-")且必须以小写字母或数字开头及结尾</span>
</el-form-item>
</el-tab-pane>
</el-tabs>
<table v-if="formData.spec.template.spec.containers">
<tr v-for="(item, index) in formData.spec.template.spec.containers" :key="index" class="dataList">
<td>容器</td>
<td>{{ item.name }}</td>
<td>
<el-select v-model="createVolumesForm[index].readOnly">
<el-option
v-for="it in mountOptions"
:key="it.value"
:label="it.label"
:value="it.value"
/>
</el-select>
</td>
<td><el-input v-model="createVolumesForm[index].mountPath" :disabled="createVolumesForm[index].readOnly == null" placeholder="容器挂载路径, 例如: /data" /></td>
</tr>
</table>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button icon="el-icon-close" circle @click="dialogVisible = false" />
<el-button icon="el-icon-check" circle @click="ok" />
</div>
</el-dialog>
</template>
<script>
import { mountOptions } from '@/utils/map'
import generate from 'nanoid/generate'
import { getVolume } from '@/api/container/storageManagement'
export default {
props: {
value: {
type: Boolean,
default: false
},
formData: {
type: Object,
default: () => {}
},
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
createVolumesForm: [],
volumeData: {
firstName: '',
secondName: '',
thirdName: '',
hostPath: ''
},
volumesList: [],
activeName: 'first',
mountOptions
}
},
computed: {
clusterName() {
return localStorage.getItem('clusterName')
},
dialogVisible: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
}
// codeData: {
// get() {
// return this.code + ''
// },
// set(value) {
// this.$emit('code', value)
// }
// }
},
watch: {
'formData.spec.template.spec.containers': {
handler(val) {
this.createVolumesForm = []
for (let i = 0; i < this.formData.spec.template.spec.containers.length; i++) {
this.createVolumesForm.push({ readOnly: null, mountPath: '' })
}
},
immediate: true
}
// code(val) {
// this.codeData = val
// }
},
mounted() {
this.getVolumeList()
},
methods: {
getVolumeList() {
getVolume({ clusterName: this.clusterName, pageNum: 1, pageSize: 999 }).then(res => {
const arr = res.data.list.map(e => { return { label: e.metadata.name, alias: e.metadata?.annotations?.['kubesphere.io/alias-name'], accessModes: e.spec.accessModes.join(','), storage: e.spec.resources.requests.storage, storageClassName: e.spec.storageClassName } })
this.volumesList = arr
})
},
ok() {
this.$refs.volumeData.validate((valid) => {
if (valid) {
//
const newFormData = Object.assign({}, this.formData)
let newSpecVolume = {}
switch (this.activeName) {
case 'first':
newSpecVolume = {
name: `volume-${generate('0123456789abcdefghijklmnopqrstuvwxyz', 6)}`,
persistentVolumeClaim: { claimName: this.volumeData[this.activeName + 'Name'] }
}
break
case 'second':
newSpecVolume = {
name: this.volumeData[this.activeName + 'Name'],
emptyDir: {}
}
break
case 'third':
newSpecVolume = {
name: this.volumeData[this.activeName + 'Name'],
hostPath: {
path: this.volumeData.hostPath
}
}
break
}
newFormData.spec.template.spec.containers.forEach((e, index) => {
if (this.createVolumesForm[index]?.readOnly !== null) {
if (!e.volumeMounts) { e.volumeMounts = [] }
e.volumeMounts.push({
name: newSpecVolume.name,
readOnly: this.createVolumesForm[index]?.readOnly,
mountPath: this.createVolumesForm[index]?.mountPath
})
}
})
newFormData.spec.template.spec.volumes.push(newSpecVolume)
this.$emit('addVolumes', newFormData)
this.dialogVisible = false
}
})
},
handleClick() {}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,167 @@
<template>
<!-- 编辑基本信息对话框 -->
<el-dialog v-if="dialogFormVisible" :close-on-click-modal="false" title="编辑信息" :visible.sync="dialogFormVisible">
<el-form ref="editInfoForm" :model="editInfoForm">
<el-form-item
prop="name"
label="名称"
>
<el-input v-model="editInfoForm.name" :disabled="true" />
</el-form-item>
<el-form-item
prop="aliasName"
label="别名"
>
<el-input v-model="editInfoForm.aliasName" :maxlength="63" />
<span class="tips">别名可以由任意字符组成帮助您更好的区分资源最长 63 个字符</span>
</el-form-item>
<el-form-item
prop="description"
label="描述信息"
>
<el-input v-model="editInfoForm.description" type="textarea" :maxlength="256" />
<span class="tips">描述信息不超过 256 个字符</span>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">{{ $t("message.cancel") }}</el-button>
<el-button type="primary" @click="submitInfoEdit"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { updateProjectData } from '@/api/container/projectManagement'
import { updatePvcAlias } from '@/api/container/storageManagement'
import { updateWorkloads } from '@/api/container/workloadManagement'
export default {
props: {
value: {
type: Boolean,
default: false
},
type: { //
type: String,
default: ''
},
data: {
type: Object,
default: () => {
return {}
}
},
classification: {
type: String,
default: ''
}
},
data() {
return {
editInfoForm: {
name: '',
aliasName: '',
description: ''
}
}
},
computed: {
dialogFormVisible: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
},
clusterName() {
return localStorage.getItem('clusterName')
}
},
watch: {
data(val) {
if (this.type === 'project') {
this.editInfoForm = {
name: val.metadata.name || '',
aliasName: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/alias-name'] : '',
description: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/description'] : ''
}
} else if (this.type === 'storage') {
this.editInfoForm = {
name: val.metadata.name || '',
aliasName: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/alias-name'] : '',
description: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/description'] : '',
namespace: val.metadata.annotations ? val.metadata.namespace : '',
clusterName: this.clusterName
}
} else if (this.type === 'workloads') {
this.editInfoForm = {
name: val.metadata.name || '',
aliasName: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/alias-name'] : '',
description: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/description'] : '',
nsName: val.metadata.annotations ? val.metadata.namespace : ''
}
} else {
this.editInfoForm = {
name: val.metadata.name || '',
aliasName: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/alias-name'] : '',
description: val.metadata.annotations ? val.metadata.annotations['kubesphere.io/description'] : ''
}
}
}
},
methods: {
submitInfoEdit() {
if (this.type === 'project') {
updateProjectData(this.clusterName, {
metadata: {
...this.data.metadata,
annotations: {
'kubesphere.io/alias-name': this.editInfoForm.aliasName,
'kubesphere.io/description': this.editInfoForm.description
}
},
spec: this.data.spec,
status: this.data.status
}).then(res => {
this.$message.success('操作成功')
this.dialogFormVisible = false
this.$emit('getList')
})
} else if (this.type === 'storage') {
updatePvcAlias(this.clusterName, {
...this.data,
metadata: {
...this.data.metadata,
annotations: {
'kubesphere.io/alias-name': this.editInfoForm.aliasName,
'kubesphere.io/description': this.editInfoForm.description
}
}
}).then(res => {
this.$message.success('操作成功')
this.dialogFormVisible = false
this.$emit('getList')
})
} else if (this.type === 'workloads') {
updateWorkloads(this.classification, this.clusterName, {
metadata: {
...this.data.metadata,
annotations: {
'kubesphere.io/alias-name': this.editInfoForm.aliasName,
'kubesphere.io/description': this.editInfoForm.description
}
},
spec: this.data.spec
}).then(res => {
this.$message.success('操作成功')
this.dialogFormVisible = false
this.$emit('getList')
})
}
}
}
}
</script>

View File

@ -0,0 +1,41 @@
<template>
<!-- 证书显示 -->
<el-dialog :close-on-click-modal="false" width="70%" title="交易证书" :visible.sync="dialogCertVisible">
<div class="certification">
<p>兹证明<br>
申请人 {{ certificationData.buyerName }} {{ certificationData.transTime }} 通过 {{ certificationData.sellerName }} 平台提交了以下电子数据及信息
</p>
<FormData :column="1" :data="certificationData" :data-map="certDataMap" />
</div>
</el-dialog>
</template>
<style lang="scss">
.certification{
width: 100%;
height: 750px;
background: url('../../assets/cert_bg.png') white center no-repeat;
background-size: auto 100%;
text-align: center;
padding-top: 120px;
p{
font-size: 0.8rem;
text-align: left;
width: 370px;
margin: auto;
padding: 10px 15px;
}
table.table.formData {
width: 50%;
margin: 0 auto;
font-size: 0.5rem;
td {
line-height: 0.9rem;
text-align: left;
}
td:nth-child(odd) {
width: 40%;
}
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<!-- 查看/编辑配置文件 -->
<el-dialog :close-on-click-modal="false" width="80%" :title="(isEdit? '编辑': '查看') +'配置文件'" :visible.sync="dialogVisible">
<codemirror v-model="codeData" class="code-mirror" :options="cmOption" />
<div v-if="isEdit" slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">{{ $t("message.cancel") }}</el-button>
<el-button type="primary" @click="submitSettingEdit"> </el-button>
</div>
</el-dialog>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
},
code: {
type: String,
default: ``
},
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
cmOption: {
autoCloseBrackets: true,
tabSize: 4,
styleActiveLine: true,
lineNumbers: true,
line: true,
mode: 'text/x-yaml'
// keyMap: "emacs"
},
codeData: ``
}
},
computed: {
dialogVisible: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
}
// codeData: {
// get() {
// return this.code + ''
// },
// set(value) {
// this.$emit('code', value)
// }
// }
},
watch: {
code(val) {
this.codeData = val
}
},
methods: {
submitSettingEdit() {
return this.$emit('submitSettingEdit', this.codeData)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,198 @@
<template>
<!-- 配置文件和密钥 -->
<el-dialog v-if="dialogVisible" :close-on-click-modal="false" width="80%" title="配置文件和密钥" :visible.sync="dialogVisible" append-to-body>
<el-form ref="settingForm" :model="settingForm">
<el-tabs v-model="activeName" type="card" @tab-click="handleClick">
<el-tab-pane label="配置字典" name="first">
<el-form-item
v-if="activeName==='first'"
prop="firstName"
label=""
required
>
<el-select v-model="settingForm.firstName" class="selectPro" placeholder="请选择配置文件">
<el-option
v-for="item in configmapList"
:key="item.label"
:label="item.label"
:value="item.label"
>
{{ item.label }}
</el-option>
</el-select>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="保密字典" name="second">
<el-form-item
v-if="activeName==='second'"
prop="secondName"
label=""
required
>
<el-select v-model="settingForm.secondName" class="selectPro" placeholder="请选择配置文件">
<el-option
v-for="item in secretList"
:key="item.label"
:label="item.label"
:value="item.label"
>
{{ item.label }}
</el-option>
</el-select>
</el-form-item>
</el-tab-pane>
</el-tabs>
<table v-if="formData.spec.template.spec.containers">
<tr v-for="(item, index) in formData.spec.template.spec.containers" :key="index" class="dataList">
<td>容器</td>
<td>{{ item.name }}</td>
<td>
<el-select v-model="createVolumesForm[index].readOnly">
<el-option
v-for="it in settingMountOptions"
:key="it.value"
:label="it.label"
:value="it.value"
/>
</el-select>
</td>
<td><el-input v-model="createVolumesForm[index].mountPath" :disabled="createVolumesForm[index].readOnly == null" placeholder="容器挂载路径, 例如: /data" /></td>
</tr>
</table>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button icon="el-icon-close" circle @click="dialogVisible = false" />
<el-button icon="el-icon-check" circle @click="ok" />
</div>
</el-dialog>
</template>
<script>
import { settingMountOptions } from '@/utils/map'
import { getConfigmapData, getSecretData } from '@/api/container/workloadManagement'
import generate from 'nanoid/generate'
export default {
props: {
value: {
type: Boolean,
default: false
},
formData: {
type: Object,
default: () => {}
},
isEdit: {
type: Boolean,
default: false
}
},
data() {
return {
createVolumesForm: [],
settingForm: {
firstName: '',
secondName: ''
},
configmapList: [],
secretList: [],
activeName: 'first',
settingMountOptions
}
},
computed: {
clusterName() {
return localStorage.getItem('clusterName')
},
dialogVisible: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
}
// codeData: {
// get() {
// return this.code + ''
// },
// set(value) {
// this.$emit('code', value)
// }
// }
},
watch: {
'formData.spec.template.spec.containers': {
handler(val) {
this.createVolumesForm = []
for (let i = 0; i < this.formData.spec.template.spec.containers.length; i++) {
this.createVolumesForm.push({ readOnly: null, mountPath: '' })
}
},
immediate: true
}
// code(val) {
// this.codeData = val
// }
},
mounted() {
this.getConfigmapList()
this.getSecretList()
},
methods: {
getConfigmapList() {
getConfigmapData({ clusterName: this.clusterName, namespace: this.formData.metadata.namespace }).then(res => {
const arr = res.data.map(e => { return { label: e.metadata.name } })
this.configmapList = arr
})
},
getSecretList() {
getSecretData({ clusterName: this.clusterName, namespace: this.formData.metadata.namespace }).then(res => {
const arr = res.data.map(e => { return { label: e.metadata.name } })
this.secretList = arr
})
},
ok() {
this.$refs.settingForm.validate((valid) => {
if (valid) {
//
const newFormData = Object.assign({}, this.formData)
let newSpecVolume = {}
switch (this.activeName) {
case 'first':
newSpecVolume = {
name: `volume-${generate('0123456789abcdefghijklmnopqrstuvwxyz', 6)}`,
configMap: { name: this.settingForm[this.activeName + 'Name'] }
}
break
case 'second':
newSpecVolume = {
name: `volume-${generate('0123456789abcdefghijklmnopqrstuvwxyz', 6)}`,
secret: { secretName: this.settingForm[this.activeName + 'Name'] }
}
break
}
newFormData.spec.template.spec.containers.forEach((e, index) => {
if (this.createVolumesForm[index]?.readOnly !== null) {
if (!e.volumeMounts) { e.volumeMounts = [] }
e.volumeMounts.push({
name: newSpecVolume.name,
readOnly: this.createVolumesForm[index]?.readOnly,
mountPath: this.createVolumesForm[index]?.mountPath
})
}
})
newFormData.spec.template.spec.volumes.push(newSpecVolume)
this.$emit('addSettingOrKey', newFormData)
this.dialogVisible = false
}
})
},
handleClick() {}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,23 @@
<template>
<div />
</template>
<script>
export default {
// get http://119.45.100.73:30880/kapis/tenant.kubesphere.io/v1alpha2/workspaces?sortBy=createTime&limit=10
// get http://119.45.100.73:30880/kapis/iam.kubesphere.io/v1alpha2/workspaces/{system-workspace}/workspacemembers?sortBy=createTime&limit=10
// http://119.45.100.73:30880/api/clusters/bj-member2/v1/namespaces/kube-federation-system
// {
// "metadata": {
// "labels": {
// "kubesphere.io/workspace":"system-workspace"
// },
// "annotations": {
// "kubesphere.io/creator":"admin"
// }
// }
// }
}
</script>

View File

@ -0,0 +1,86 @@
<template>
<div>
<el-upload
class="avatar-uploader"
action="#"
:limit="1"
:http-request="httpRequest"
:show-file-list="false"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</div>
</template>
<script>
import { uploadImage } from '@/api/container/monitorSelect'
export default {
props: {
value: {
type: String,
default: ''
}
},
computed: {
imageUrl: {
get() {
return this.value
},
set(value) {
this.$emit('input', value)
}
}
},
methods: {
httpRequest(data) {
// const isJar = data.file.name.indexOf('.jar') === (data.file.name.length - 4)
// const isJPG = data.file.type === 'image/jpeg'
// const isLt2M = data.file.size / 1024 / 1024 < 2
// if (!isJPG) {
// this.$message.error(' JPG !')
// } else {
// base64
const form = new FormData()
form.set('file', data.file)
form.set('mode', '1')
uploadImage(form).then((e) => {
if (e.code === 200) {
// console.log(e) e.url
this.imageUrl = e.data[0].url
this.$message.success('文件上传成功')
}
})
// }
}
}
}
</script>
<style lang="scss" scoped>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
border: 1px dashed #d9d9d9;
font-size: 28px;
color: #8c939d;
width: 100px;
height: 100px;
line-height: 100px;
text-align: center;
}
.avatar {
width: 100px;
height: 100px;
display: block;
}
</style>

View File

@ -0,0 +1,111 @@
<template>
<transition :name="transitionName">
<div v-show="visible" :style="customStyle" class="back-to-ceiling" @click="backToTop">
<svg width="16" height="16" viewBox="0 0 17 17" xmlns="http://www.w3.org/2000/svg" class="Icon Icon--backToTopArrow" aria-hidden="true" style="height:16px;width:16px"><path d="M12.036 15.59a1 1 0 0 1-.997.995H5.032a.996.996 0 0 1-.997-.996V8.584H1.03c-1.1 0-1.36-.633-.578-1.416L7.33.29a1.003 1.003 0 0 1 1.412 0l6.878 6.88c.782.78.523 1.415-.58 1.415h-3.004v7.004z" /></svg>
</div>
</transition>
</template>
<script>
export default {
name: 'BackToTop',
props: {
visibilityHeight: {
type: Number,
default: 400
},
backPosition: {
type: Number,
default: 0
},
customStyle: {
type: Object,
default: function() {
return {
right: '50px',
bottom: '50px',
width: '40px',
height: '40px',
'border-radius': '4px',
'line-height': '45px',
background: '#e7eaf1'
}
}
},
transitionName: {
type: String,
default: 'fade'
}
},
data() {
return {
visible: false,
interval: null,
isMoving: false
}
},
mounted() {
window.addEventListener('scroll', this.handleScroll)
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll)
if (this.interval) {
clearInterval(this.interval)
}
},
methods: {
handleScroll() {
this.visible = window.pageYOffset > this.visibilityHeight
},
backToTop() {
if (this.isMoving) return
const start = window.pageYOffset
let i = 0
this.isMoving = true
this.interval = setInterval(() => {
const next = Math.floor(this.easeInOutQuad(10 * i, start, -start, 500))
if (next <= this.backPosition) {
window.scrollTo(0, this.backPosition)
clearInterval(this.interval)
this.isMoving = false
} else {
window.scrollTo(0, next)
}
i++
}, 16.7)
},
easeInOutQuad(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b
return -c / 2 * (--t * (t - 2) - 1) + b
}
}
}
</script>
<style scoped>
.back-to-ceiling {
position: fixed;
display: inline-block;
text-align: center;
cursor: pointer;
}
.back-to-ceiling:hover {
background: #d5dbe7;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity .5s;
}
.fade-enter,
.fade-leave-to {
opacity: 0
}
.back-to-ceiling .Icon {
fill: #9aaabf;
background: none;
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<div>
<a href="/monitor/overview"><img class="logo" src="@/assets/JCCE-logo.png" alt=""></a>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</div>
</template>
<script>
import pathToRegexp from 'path-to-regexp'
export default {
data() {
return {
levelList: null
}
},
watch: {
$route(route) {
// if you go to the redirect page, do not update the breadcrumbs
if (route.path.startsWith('/redirect/')) {
return
}
this.getBreadcrumb()
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
isDashboard(route) {
const name = route && route.name
if (!name) {
return false
}
return name.trim().toLocaleLowerCase() === 'Dashboard'.toLocaleLowerCase()
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style lang="scss" scoped>
.logo{
height: 40px;
margin-top: 5px;
display: block;
float: left;
}
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@ -0,0 +1,155 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xAxisData = []
const data = []
const data2 = []
for (let i = 0; i < 50; i++) {
xAxisData.push(i)
data.push((Math.sin(i / 5) * (i / 5 - 10) + i / 6) * 5)
data2.push((Math.sin(i / 5) * (i / 5 + 10) + i / 6) * 3)
}
this.chart.setOption({
backgroundColor: '#08263a',
grid: {
left: '5%',
right: '5%'
},
xAxis: [{
show: false,
data: xAxisData
}, {
show: false,
data: xAxisData
}],
visualMap: {
show: false,
min: 0,
max: 50,
dimension: 0,
inRange: {
color: ['#4a657a', '#308e92', '#b1cfa5', '#f5d69f', '#f5898b', '#ef5055']
}
},
yAxis: {
axisLine: {
show: false
},
axisLabel: {
textStyle: {
color: '#4a657a'
}
},
splitLine: {
show: true,
lineStyle: {
color: '#08263f'
}
},
axisTick: {
show: false
}
},
series: [{
name: 'back',
type: 'bar',
data: data2,
z: 1,
itemStyle: {
normal: {
opacity: 0.4,
barBorderRadius: 5,
shadowBlur: 3,
shadowColor: '#111'
}
}
}, {
name: 'Simulate Shadow',
type: 'line',
data,
z: 2,
showSymbol: false,
animationDelay: 0,
animationEasing: 'linear',
animationDuration: 1200,
lineStyle: {
normal: {
color: 'transparent'
}
},
areaStyle: {
normal: {
color: '#08263a',
shadowBlur: 50,
shadowColor: '#000'
}
}
}, {
name: 'front',
type: 'bar',
data,
xAxisIndex: 1,
z: 3,
itemStyle: {
normal: {
barBorderRadius: 5
}
}
}],
animationEasing: 'elasticOut',
animationEasingUpdate: 'elasticOut',
animationDelay(idx) {
return idx * 20
},
animationDelayUpdate(idx) {
return idx * 20
}
})
}
}
}
</script>

View File

@ -0,0 +1,227 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
this.chart.setOption({
backgroundColor: '#394056',
title: {
top: 20,
text: 'Requests',
textStyle: {
fontWeight: 'normal',
fontSize: 16,
color: '#F1F1F3'
},
left: '1%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
legend: {
top: 20,
icon: 'rect',
itemWidth: 14,
itemHeight: 5,
itemGap: 13,
data: ['CMCC', 'CTCC', 'CUCC'],
right: '4%',
textStyle: {
fontSize: 12,
color: '#F1F1F3'
}
},
grid: {
top: 100,
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#57617B'
}
},
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
}],
yAxis: [{
type: 'value',
name: '(%)',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#57617B'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 14
}
},
splitLine: {
lineStyle: {
color: '#57617B'
}
}
}],
series: [{
name: 'CMCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
}, {
name: 'CTCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(0, 136, 212, 0.3)'
}, {
offset: 0.8,
color: 'rgba(0, 136, 212, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(0,136,212)',
borderColor: 'rgba(0,136,212,0.2)',
borderWidth: 12
}
},
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
}, {
name: 'CUCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(219, 50, 51, 0.3)'
}, {
offset: 0.8,
color: 'rgba(219, 50, 51, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(219,50,51)',
borderColor: 'rgba(219,50,51,0.2)',
borderWidth: 12
}
},
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
}]
})
}
}
}
</script>

View File

@ -0,0 +1,271 @@
<template>
<div :id="id" :class="className" :style="{height:height,width:width}" />
</template>
<script>
import echarts from 'echarts'
import resize from './mixins/resize'
export default {
mixins: [resize],
props: {
className: {
type: String,
default: 'chart'
},
id: {
type: String,
default: 'chart'
},
width: {
type: String,
default: '200px'
},
height: {
type: String,
default: '200px'
}
},
data() {
return {
chart: null
}
},
mounted() {
this.initChart()
},
beforeDestroy() {
if (!this.chart) {
return
}
this.chart.dispose()
this.chart = null
},
methods: {
initChart() {
this.chart = echarts.init(document.getElementById(this.id))
const xData = (function() {
const data = []
for (let i = 1; i < 13; i++) {
data.push(i + 'month')
}
return data
}())
this.chart.setOption({
backgroundColor: '#344b58',
title: {
text: 'statistics',
x: '20',
top: '20',
textStyle: {
color: '#fff',
fontSize: '22'
},
subtextStyle: {
color: '#90979c',
fontSize: '16'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
textStyle: {
color: '#fff'
}
}
},
grid: {
left: '5%',
right: '5%',
borderWidth: 0,
top: 150,
bottom: 95,
textStyle: {
color: '#fff'
}
},
legend: {
x: '5%',
top: '10%',
textStyle: {
color: '#90979c'
},
data: ['female', 'male', 'average']
},
calculable: true,
xAxis: [{
type: 'category',
axisLine: {
lineStyle: {
color: '#90979c'
}
},
splitLine: {
show: false
},
axisTick: {
show: false
},
splitArea: {
show: false
},
axisLabel: {
interval: 0
},
data: xData
}],
yAxis: [{
type: 'value',
splitLine: {
show: false
},
axisLine: {
lineStyle: {
color: '#90979c'
}
},
axisTick: {
show: false
},
axisLabel: {
interval: 0
},
splitArea: {
show: false
}
}],
dataZoom: [{
show: true,
height: 30,
xAxisIndex: [
0
],
bottom: 30,
start: 10,
end: 80,
handleIcon: 'path://M306.1,413c0,2.2-1.8,4-4,4h-59.8c-2.2,0-4-1.8-4-4V200.8c0-2.2,1.8-4,4-4h59.8c2.2,0,4,1.8,4,4V413z',
handleSize: '110%',
handleStyle: {
color: '#d3dee5'
},
textStyle: {
color: '#fff' },
borderColor: '#90979c'
}, {
type: 'inside',
show: true,
height: 15,
start: 1,
end: 35
}],
series: [{
name: 'female',
type: 'bar',
stack: 'total',
barMaxWidth: 35,
barGap: '10%',
itemStyle: {
normal: {
color: 'rgba(255,144,128,1)',
label: {
show: true,
textStyle: {
color: '#fff'
},
position: 'insideTop',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
709,
1917,
2455,
2610,
1719,
1433,
1544,
3285,
5208,
3372,
2484,
4078
]
},
{
name: 'male',
type: 'bar',
stack: 'total',
itemStyle: {
normal: {
color: 'rgba(0,191,183,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
327,
1776,
507,
1200,
800,
482,
204,
1390,
1001,
951,
381,
220
]
}, {
name: 'average',
type: 'line',
stack: 'total',
symbolSize: 10,
symbol: 'circle',
itemStyle: {
normal: {
color: 'rgba(252,230,48,1)',
barBorderRadius: 0,
label: {
show: true,
position: 'top',
formatter(p) {
return p.value > 0 ? p.value : ''
}
}
}
},
data: [
1036,
3693,
2962,
3810,
2519,
1915,
1748,
4675,
6209,
4323,
2865,
4298
]
}
]
})
}
}
}
</script>

View File

@ -0,0 +1,56 @@
import { debounce } from '@/utils'
export default {
data() {
return {
$_sidebarElm: null,
$_resizeHandler: null
}
},
mounted() {
this.initListener()
},
activated() {
if (!this.$_resizeHandler) {
// avoid duplication init
this.initListener()
}
// when keep-alive chart activated, auto resize
this.resize()
},
beforeDestroy() {
this.destroyListener()
},
deactivated() {
this.destroyListener()
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_sidebarResizeHandler(e) {
if (e.propertyName === 'width') {
this.$_resizeHandler()
}
},
initListener() {
this.$_resizeHandler = debounce(() => {
this.resize()
}, 100)
window.addEventListener('resize', this.$_resizeHandler)
this.$_sidebarElm = document.getElementsByClassName('sidebar-container')[0]
this.$_sidebarElm && this.$_sidebarElm.addEventListener('transitionend', this.$_sidebarResizeHandler)
},
destroyListener() {
window.removeEventListener('resize', this.$_resizeHandler)
this.$_resizeHandler = null
this.$_sidebarElm && this.$_sidebarElm.removeEventListener('transitionend', this.$_sidebarResizeHandler)
},
resize() {
const { chart } = this
chart && chart.resize()
}
}
}

View File

@ -0,0 +1,166 @@
<template>
<div class="dndList">
<div :style="{width:width1}" class="dndList-list">
<h3>{{ list1Title }}</h3>
<draggable :set-data="setData" :list="list1" group="article" class="dragArea">
<div v-for="element in list1" :key="element.id" class="list-complete-item">
<div class="list-complete-item-handle">
{{ element.id }}[{{ element.author }}] {{ element.title }}
</div>
<div style="position:absolute;right:0px;">
<span style="float: right ;margin-top: -20px;margin-right:5px;" @click="deleteEle(element)">
<i style="color:#ff4949" class="el-icon-delete" />
</span>
</div>
</div>
</draggable>
</div>
<div :style="{width:width2}" class="dndList-list">
<h3>{{ list2Title }}</h3>
<draggable :list="list2" group="article" class="dragArea">
<div v-for="element in list2" :key="element.id" class="list-complete-item">
<div class="list-complete-item-handle2" @click="pushEle(element)">
{{ element.id }} [{{ element.author }}] {{ element.title }}
</div>
</div>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'DndList',
components: { draggable },
props: {
list1: {
type: Array,
default() {
return []
}
},
list2: {
type: Array,
default() {
return []
}
},
list1Title: {
type: String,
default: 'list1'
},
list2Title: {
type: String,
default: 'list2'
},
width1: {
type: String,
default: '48%'
},
width2: {
type: String,
default: '48%'
}
},
methods: {
isNotInList1(v) {
return this.list1.every(k => v.id !== k.id)
},
isNotInList2(v) {
return this.list2.every(k => v.id !== k.id)
},
deleteEle(ele) {
for (const item of this.list1) {
if (item.id === ele.id) {
const index = this.list1.indexOf(item)
this.list1.splice(index, 1)
break
}
}
if (this.isNotInList2(ele)) {
this.list2.unshift(ele)
}
},
pushEle(ele) {
for (const item of this.list2) {
if (item.id === ele.id) {
const index = this.list2.indexOf(item)
this.list2.splice(index, 1)
break
}
}
if (this.isNotInList1(ele)) {
this.list1.push(ele)
}
},
setData(dataTransfer) {
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
}
}
}
</script>
<style lang="scss" scoped>
.dndList {
background: #fff;
padding-bottom: 40px;
&:after {
content: "";
display: table;
clear: both;
}
.dndList-list {
float: left;
padding-bottom: 30px;
&:first-of-type {
margin-right: 2%;
}
.dragArea {
margin-top: 15px;
min-height: 50px;
padding-bottom: 30px;
}
}
}
.list-complete-item {
cursor: pointer;
position: relative;
font-size: 14px;
padding: 5px 12px;
margin-top: 4px;
border: 1px solid #bfcbd9;
transition: all 1s;
}
.list-complete-item-handle {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 50px;
}
.list-complete-item-handle2 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: 20px;
}
.list-complete-item.sortable-chosen {
background: #4AB7BD;
}
.list-complete-item.sortable-ghost {
background: #30B08F;
}
.list-complete-enter,
.list-complete-leave-active {
opacity: 0;
}
</style>

View File

@ -0,0 +1,41 @@
export const FormData = {
props: {
data: { type: Object },
dataMap: { type: Object },
columns: { type: Number, default: 2 }
// column: { type: Number, default: 2 }
},
render(h) {
const formatDate = (e) => {
return new Date(e).toLocaleString()
}
const td = (e) => {
const res = []
for (const k in e) {
res.push(<td style='width: 18%'>{this.dataMap[e[k]]}</td>)
res.push(<td style='width: 32%'>{this.dataMap[e[k]].indexOf('时间') > -1 ? formatDate(this.data[e[k]]) : this.data[e[k] || '-']}</td>)
}
return res
}
const list = () => {
const res = []
const liData = []
// 分组
for (let i = 0; i < Object.keys(this.dataMap).length; i += this.columns) {
res.push(Object.keys(this.dataMap).slice(i, i + this.columns))
}
for (const i in res) {
liData.push(<tr>
{td(res[i])}
</tr>)
}
return liData
}
return (
<table class='table formData'>
{list()}
</table>
)
}
}

View File

@ -0,0 +1,181 @@
<template>
<div :class="{'show':show}" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select
ref="headerSearchSelect"
v-model="search"
:remote-method="querySearch"
filterable
default-first-option
remote
placeholder="Search"
class="header-search-select"
@change="change"
>
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
</el-select>
</div>
</template>
<script>
// fuse is a lightweight fuzzy-search module
// make search results more in line with expectations
import Fuse from 'fuse.js'
import path from 'path'
export default {
name: 'HeaderSearch',
data() {
return {
search: '',
options: [],
searchPool: [],
show: false,
fuse: undefined
}
},
computed: {
routes() {
return this.$store.getters.permission_routes
}
},
watch: {
routes() {
this.searchPool = this.generateRoutes(this.routes)
},
searchPool(list) {
this.initFuse(list)
},
show(value) {
if (value) {
document.body.addEventListener('click', this.close)
} else {
document.body.removeEventListener('click', this.close)
}
}
},
mounted() {
this.searchPool = this.generateRoutes(this.routes)
},
methods: {
click() {
this.show = !this.show
if (this.show) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
this.options = []
this.show = false
},
change(val) {
this.$router.push(val.path)
this.search = ''
this.options = []
this.$nextTick(() => {
this.show = false
})
},
initFuse(list) {
this.fuse = new Fuse(list, {
shouldSort: true,
threshold: 0.4,
location: 0,
distance: 100,
maxPatternLength: 32,
minMatchCharLength: 1,
keys: [{
name: 'title',
weight: 0.7
}, {
name: 'path',
weight: 0.3
}]
})
},
// Filter out the routes that can be displayed in the sidebar
// And generate the internationalized title
generateRoutes(routes, basePath = '/', prefixTitle = []) {
let res = []
for (const router of routes) {
// skip hidden router
if (router.hidden) { continue }
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
}
if (router.meta && router.meta.title) {
data.title = [...data.title, router.meta.title]
if (router.redirect !== 'noRedirect') {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
}
}
// recursive child routes
if (router.children) {
const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
if (tempRoutes.length >= 1) {
res = [...res, ...tempRoutes]
}
}
}
return res
},
querySearch(query) {
if (query !== '') {
this.options = this.fuse.search(query)
} else {
this.options = []
}
}
}
}
</script>
<style lang="scss" scoped>
.header-search {
font-size: 0 !important;
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
height: 4em;
}
.header-search-select {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
::v-deep .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>

View File

@ -0,0 +1,31 @@
// doc: https://nhnent.github.io/tui.editor/api/latest/ToastUIEditor.html#ToastUIEditor
export default {
minHeight: '200px',
previewStyle: 'vertical',
useCommandShortcut: true,
useDefaultHTMLSanitizer: true,
usageStatistics: false,
hideModeSwitch: false,
toolbarItems: [
'heading',
'bold',
'italic',
'strike',
'divider',
'hr',
'quote',
'divider',
'ul',
'ol',
'task',
'indent',
'outdent',
'divider',
'table',
'image',
'link',
'divider',
'code',
'codeblock'
]
}

View File

@ -0,0 +1,118 @@
<template>
<div :id="id" />
</template>
<script>
// deps for editor
import 'codemirror/lib/codemirror.css' // codemirror
import 'tui-editor/dist/tui-editor.css' // editor ui
import 'tui-editor/dist/tui-editor-contents.css' // editor content
import Editor from 'tui-editor'
import defaultOptions from './default-options'
export default {
name: 'MarkdownEditor',
props: {
value: {
type: String,
default: ''
},
id: {
type: String,
required: false,
default() {
return 'markdown-editor-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
}
},
options: {
type: Object,
default() {
return defaultOptions
}
},
mode: {
type: String,
default: 'markdown'
},
height: {
type: String,
required: false,
default: '300px'
},
language: {
type: String,
required: false,
default: 'en_US' // https://github.com/nhnent/tui.editor/tree/master/src/js/langs
}
},
data() {
return {
editor: null
}
},
computed: {
editorOptions() {
const options = Object.assign({}, defaultOptions, this.options)
options.initialEditType = this.mode
options.height = this.height
options.language = this.language
return options
}
},
watch: {
value(newValue, preValue) {
if (newValue !== preValue && newValue !== this.editor.getValue()) {
this.editor.setValue(newValue)
}
},
language(val) {
this.destroyEditor()
this.initEditor()
},
height(newValue) {
this.editor.height(newValue)
},
mode(newValue) {
this.editor.changeMode(newValue)
}
},
mounted() {
this.initEditor()
},
destroyed() {
this.destroyEditor()
},
methods: {
initEditor() {
this.editor = new Editor({
el: document.getElementById(this.id),
...this.editorOptions
})
if (this.value) {
this.editor.setValue(this.value)
}
this.editor.on('change', () => {
this.$emit('input', this.editor.getValue())
})
},
destroyEditor() {
if (!this.editor) return
this.editor.off('change')
this.editor.remove()
},
setValue(value) {
this.editor.setValue(value)
},
getValue() {
return this.editor.getValue()
},
setHtml(value) {
this.editor.setHtml(value)
},
getHtml() {
return this.editor.getHtml()
}
}
}
</script>

View File

@ -0,0 +1,101 @@
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
}
</style>

View File

@ -0,0 +1,142 @@
<template>
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
<div class="pan-info">
<div class="pan-info-roles-container">
<slot />
</div>
</div>
<!-- eslint-disable-next-line -->
<div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
</div>
</template>
<script>
export default {
name: 'PanThumb',
props: {
image: {
type: String,
required: true
},
zIndex: {
type: Number,
default: 1
},
width: {
type: String,
default: '150px'
},
height: {
type: String,
default: '150px'
}
}
}
</script>
<style scoped>
.pan-item {
width: 200px;
height: 200px;
border-radius: 50%;
display: inline-block;
position: relative;
cursor: default;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.pan-info-roles-container {
padding: 20px;
text-align: center;
}
.pan-thumb {
width: 100%;
height: 100%;
background-position: center center;
background-size: cover;
border-radius: 50%;
overflow: hidden;
position: absolute;
transform-origin: 95% 40%;
transition: all 0.3s ease-in-out;
}
/* .pan-thumb:after {
content: '';
width: 8px;
height: 8px;
position: absolute;
border-radius: 50%;
top: 40%;
left: 95%;
margin: -4px 0 0 -4px;
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
} */
.pan-info {
position: absolute;
width: inherit;
height: inherit;
border-radius: 50%;
overflow: hidden;
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
}
.pan-info h3 {
color: #fff;
text-transform: uppercase;
position: relative;
letter-spacing: 2px;
font-size: 18px;
margin: 0 60px;
padding: 22px 0 0 0;
height: 85px;
font-family: 'Open Sans', Arial, sans-serif;
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
}
.pan-info p {
color: #fff;
padding: 10px 5px;
font-style: italic;
margin: 0 30px;
font-size: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
}
.pan-info p a {
display: block;
color: #333;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: #fff;
font-style: normal;
font-weight: 700;
text-transform: uppercase;
font-size: 9px;
letter-spacing: 1px;
padding-top: 24px;
margin: 7px auto 0;
font-family: 'Open Sans', Arial, sans-serif;
opacity: 0;
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
transform: translateX(60px) rotate(90deg);
}
.pan-info p a:hover {
background: rgba(255, 255, 255, 0.5);
}
.pan-item:hover .pan-thumb {
transform: rotate(-110deg);
}
.pan-item:hover .pan-info p a {
opacity: 1;
transform: translateX(0px) rotate(0deg);
}
</style>

View File

@ -0,0 +1,145 @@
<template>
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
<i :class="show?'el-icon-close':'el-icon-setting'" />
</div>
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script>
import { addClass, removeClass } from '@/utils'
export default {
name: 'RightPanel',
props: {
clickNotClose: {
default: false,
type: Boolean
},
buttonTop: {
default: 250,
type: Number
}
},
data() {
return {
show: false
}
},
computed: {
theme() {
return this.$store.state.settings.theme
}
},
watch: {
show(value) {
if (value && !this.clickNotClose) {
this.addEventClick()
}
if (value) {
addClass(document.body, 'showRightPanel')
} else {
removeClass(document.body, 'showRightPanel')
}
}
},
mounted() {
this.insertToBody()
},
beforeDestroy() {
const elx = this.$refs.rightPanel
elx.remove()
},
methods: {
addEventClick() {
window.addEventListener('click', this.closeSidebar)
},
closeSidebar(evt) {
const parent = evt.target.closest('.rightPanel')
if (!parent) {
this.show = false
window.removeEventListener('click', this.closeSidebar)
}
},
insertToBody() {
const elx = this.$refs.rightPanel
const body = document.querySelector('body')
body.insertBefore(elx, body.firstChild)
}
}
}
</script>
<style>
.showRightPanel {
overflow: hidden;
position: relative;
width: calc(100% - 15px);
}
</style>
<style lang="scss" scoped>
.rightPanel-background {
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
background: rgba(0, 0, 0, .2);
z-index: -1;
}
.rightPanel {
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
}
.show {
transition: all .3s cubic-bezier(.7, .3, .1, 1);
.rightPanel-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.rightPanel {
transform: translate(0);
}
}
.handle-button {
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
i {
font-size: 24px;
line-height: 48px;
}
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
import '@/icons/iconfont'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
// const sp = document.createElement('script')
// sp.src = '//at.alicdn.com/t/font_3285719_pjqspf3oomf.js' // iconfont Symbol
// document.body.appendChild(sp)
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.34em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<div id="xterm" class="xterm" />
</template>
<script>
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { AttachAddon } from 'xterm-addon-attach'
export default {
name: 'Terminal',
props: {
url: {
type: Object,
default: () => {
return {
cluster: '',
podName: '',
namespaces: '',
container: ''
}
}
}
},
mounted() {
this.initSocket()
},
beforeDestroy() {
this.socket.close()
this.term.dispose()
},
methods: {
initTerm() {
const term = new Terminal({
lineHeight: 1.2,
cursorBlink: true,
cursorStyle: 'underline',
fontSize: 12,
fontFamily: "Monaco, Menlo, Consolas, 'Courier New', monospace",
theme: {
background: '#181d28'
}
})
const attachAddon = new AttachAddon(this.socket)
const fitAddon = new FitAddon()
term.loadAddon(attachAddon)
term.loadAddon(fitAddon)
term.open(document.getElementById('xterm'))
fitAddon.fit()
term.focus()
term.writeln('Welcome to xterm.js')
term.prompt = () => {
term.write('\r\n$ ')
}
// term.onKey(e => {
// const printable = !e.domEvent.altKey && !e.domEvent.altGraphKey && !e.domEvent.ctrlKey && !e.domEvent.metaKey;
// if (e.domEvent.code === 13) {
// console.log('baa')
// // prompt(term);
// term.prompt()
// } else if (e.domEvent.code === 8) {
// // Do not delete the prompt
// if (term._core.buffer.x > 2) {
// term.write('\b \b');
// }
// } else if (printable) {
// term.write(e.key);
// }
// });
term.onData((data) => {
const order = JSON.stringify({
Op: 'stdin',
Data: data
})
this.socket.send(order)
})
this.term = term
},
initSocket() {
// this.socket = new WebSocket(`ws://${location.host}/${this.getUrl(this.url)}`);
this.socket = new WebSocket(`wss://echo.websocket.org`)
this.socketOnClose()
this.socketOnOpen()
this.socketOnMessage()
this.socketOnError()
},
getUrl(url) {
return `kapis/terminal.kubesphere.io/v1alpha2${url.cluster}/namespaces/${url.namespaces}/pods/${url.podName}?container=${url.container}&shell=sh`
},
socketOnMessage() {
this.socket.onmessage = (event) => {
//
const data = (typeof JSON.parse(event.data)) === 'object' ? JSON.parse(event.data).Data : event.data
console.log(data)
this.term.write(data)
}
},
socketOnOpen() {
this.socket.onopen = () => {
//
console.log('success')
this.initTerm()
}
},
socketOnClose() {
this.socket.onclose = () => {
// console.log('close socket')
}
},
socketOnError() {
this.socket.onerror = (err) => {
console.log(err)
console.log('socket 链接失败')
}
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,113 @@
<template>
<a :class="className" class="link--mallki" href="#">
{{ text }}
<span :data-letters="text" />
<span :data-letters="text" />
</a>
</template>
<script>
export default {
props: {
className: {
type: String,
default: ''
},
text: {
type: String,
default: 'vue-element-admin'
}
}
}
</script>
<style>
/* Mallki */
.link--mallki {
font-weight: 800;
color: #4dd9d5;
font-family: 'Dosis', sans-serif;
-webkit-transition: color 0.5s 0.25s;
transition: color 0.5s 0.25s;
overflow: hidden;
position: relative;
display: inline-block;
line-height: 1;
outline: none;
text-decoration: none;
}
.link--mallki:hover {
-webkit-transition: none;
transition: none;
color: transparent;
}
.link--mallki::before {
content: '';
width: 100%;
height: 6px;
margin: -3px 0 0 0;
background: #3888fa;
position: absolute;
left: 0;
top: 50%;
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
-webkit-transition: -webkit-transform 0.4s;
transition: transform 0.4s;
-webkit-transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
transition-timing-function: cubic-bezier(0.7, 0, 0.3, 1);
}
.link--mallki:hover::before {
-webkit-transform: translate3d(100%, 0, 0);
transform: translate3d(100%, 0, 0);
}
.link--mallki span {
position: absolute;
height: 50%;
width: 100%;
left: 0;
top: 0;
overflow: hidden;
}
.link--mallki span::before {
content: attr(data-letters);
color: red;
position: absolute;
left: 0;
width: 100%;
color: #3888fa;
-webkit-transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
.link--mallki span:nth-child(2) {
top: 50%;
}
.link--mallki span:first-child::before {
top: 0;
-webkit-transform: translate3d(0, 100%, 0);
transform: translate3d(0, 100%, 0);
}
.link--mallki span:nth-child(2)::before {
bottom: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
.link--mallki:hover span::before {
-webkit-transition-delay: 0.3s;
transition-delay: 0.3s;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
-webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1);
}
</style>

View File

@ -0,0 +1,175 @@
<template>
<el-color-picker
v-model="theme"
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
computed: {
defaultTheme() {
return this.$store.state.settings.theme
}
},
watch: {
defaultTheme: {
handler: function(val, oldVal) {
this.theme = val
},
immediate: true
},
async theme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
console.log(themeCluster, originalCluster)
const $message = this.$message({
message: ' Compiling the theme',
customClass: 'theme-message',
type: 'success',
duration: 0,
iconClass: 'el-icon-loading'
})
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
$message.close()
}
},
methods: {
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div class="upload-container">
<el-upload
:data="dataObj"
:multiple="false"
:show-file-list="false"
:on-success="handleImageSuccess"
class="image-uploader"
drag
action="https://httpbin.org/post"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
</el-upload>
<div class="image-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl+'?imageView2/1/w/200/h/200'">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
</div>
</template>
<script>
import { getToken } from '@/api/common/qiniu'
export default {
name: 'SingleImageUpload',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
tempUrl: '',
dataObj: { token: '', key: '' }
}
},
computed: {
imageUrl() {
return this.value
}
},
methods: {
rmImage() {
this.emitInput('')
},
emitInput(val) {
this.$emit('input', val)
},
handleImageSuccess() {
this.emitInput(this.tempUrl)
},
beforeUpload() {
const _self = this
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key
const token = response.data.qiniu_token
_self._data.dataObj.token = token
_self._data.dataObj.key = key
this.tempUrl = response.data.qiniu_url
resolve(true)
}).catch(err => {
console.log(err)
reject(false)
})
})
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
.upload-container {
width: 100%;
position: relative;
@include clearfix;
.image-uploader {
width: 60%;
float: left;
}
.image-preview {
width: 200px;
height: 200px;
position: relative;
border: 1px dashed #d9d9d9;
float: left;
margin-left: 50px;
.image-preview-wrapper {
position: relative;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.image-preview-action {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
cursor: pointer;
text-align: center;
line-height: 200px;
.el-icon-delete {
font-size: 36px;
}
}
&:hover {
.image-preview-action {
opacity: 1;
}
}
}
}
</style>

View File

@ -0,0 +1,130 @@
<template>
<div class="singleImageUpload2 upload-container">
<el-upload
:data="dataObj"
:multiple="false"
:show-file-list="false"
:on-success="handleImageSuccess"
class="image-uploader"
drag
action="https://httpbin.org/post"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
Drag或<em>点击上传</em>
</div>
</el-upload>
<div v-show="imageUrl.length>0" class="image-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
</div>
</template>
<script>
import { getToken } from '@/api/common/qiniu'
export default {
name: 'SingleImageUpload2',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
tempUrl: '',
dataObj: { token: '', key: '' }
}
},
computed: {
imageUrl() {
return this.value
}
},
methods: {
rmImage() {
this.emitInput('')
},
emitInput(val) {
this.$emit('input', val)
},
handleImageSuccess() {
this.emitInput(this.tempUrl)
},
beforeUpload() {
const _self = this
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key
const token = response.data.qiniu_token
_self._data.dataObj.token = token
_self._data.dataObj.key = key
this.tempUrl = response.data.qiniu_url
resolve(true)
}).catch(() => {
reject(false)
})
})
}
}
}
</script>
<style lang="scss" scoped>
.upload-container {
width: 100%;
height: 100%;
position: relative;
.image-uploader {
height: 100%;
}
.image-preview {
width: 100%;
height: 100%;
position: absolute;
left: 0px;
top: 0px;
border: 1px dashed #d9d9d9;
.image-preview-wrapper {
position: relative;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.image-preview-action {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
cursor: pointer;
text-align: center;
line-height: 200px;
.el-icon-delete {
font-size: 36px;
}
}
&:hover {
.image-preview-action {
opacity: 1;
}
}
}
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<div class="upload-container">
<el-upload
:data="dataObj"
:multiple="false"
:show-file-list="false"
:on-success="handleImageSuccess"
class="image-uploader"
drag
action="https://httpbin.org/post"
>
<i class="el-icon-upload" />
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
</el-upload>
<div class="image-preview image-app-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
<div class="image-preview">
<div v-show="imageUrl.length>1" class="image-preview-wrapper">
<img :src="imageUrl">
<div class="image-preview-action">
<i class="el-icon-delete" @click="rmImage" />
</div>
</div>
</div>
</div>
</template>
<script>
import { getToken } from '@/api/common/qiniu'
export default {
name: 'SingleImageUpload3',
props: {
value: {
type: String,
default: ''
}
},
data() {
return {
tempUrl: '',
dataObj: { token: '', key: '' }
}
},
computed: {
imageUrl() {
return this.value
}
},
methods: {
rmImage() {
this.emitInput('')
},
emitInput(val) {
this.$emit('input', val)
},
handleImageSuccess(file) {
this.emitInput(file.files.file)
},
beforeUpload() {
const _self = this
return new Promise((resolve, reject) => {
getToken().then(response => {
const key = response.data.qiniu_key
const token = response.data.qiniu_token
_self._data.dataObj.token = token
_self._data.dataObj.key = key
this.tempUrl = response.data.qiniu_url
resolve(true)
}).catch(err => {
console.log(err)
reject(false)
})
})
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/mixin.scss";
.upload-container {
width: 100%;
position: relative;
@include clearfix;
.image-uploader {
width: 35%;
float: left;
}
.image-preview {
width: 200px;
height: 200px;
position: relative;
border: 1px dashed #d9d9d9;
float: left;
margin-left: 50px;
.image-preview-wrapper {
position: relative;
width: 100%;
height: 100%;
img {
width: 100%;
height: 100%;
}
}
.image-preview-action {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
cursor: default;
text-align: center;
color: #fff;
opacity: 0;
font-size: 20px;
background-color: rgba(0, 0, 0, .5);
transition: opacity .3s;
cursor: pointer;
text-align: center;
line-height: 200px;
.el-icon-delete {
font-size: 36px;
}
}
&:hover {
.image-preview-action {
opacity: 1;
}
}
}
.image-app-preview {
width: 320px;
height: 180px;
position: relative;
border: 1px dashed #d9d9d9;
float: left;
margin-left: 50px;
.app-fake-conver {
height: 44px;
position: absolute;
width: 100%; // background: rgba(0, 0, 0, .1);
text-align: center;
line-height: 64px;
color: #fff;
}
}
}
</style>

View File

@ -0,0 +1,87 @@
<template>
<!-- 添加键值对 -->
<div>
<el-form-item
v-for="(tag, index) in editTagForm"
:key="'tag'+index"
label=""
>
<el-row :gutter="10">
<el-col :span="8"><el-input v-model.trim="tag.key" placeholder="键" @blur="blurInput" /></el-col>
<el-col :span="8"><el-input v-model="tag.value" placeholder="值" @blur="blurInput" /></el-col>
<el-col :span="4"><el-button icon="el-icon-delete" circle @click.prevent="removeTag(tag,index)" /></el-col>
</el-row>
</el-form-item>
<el-button type="primary" class="addBtn" plain round :disabled="addTagNumCheck" @click="addTag">{{ btnName }}</el-button>
</div>
</template>
<script>
export default {
props: {
btnName: {
type: String,
default: '添加标签'
},
value: {
type: Object,
default: () => {}
}
},
data() {
return {
editTagForm: []
}
},
computed: {
addTagNumCheck() {
let flag = false
this.editTagForm.forEach(e => {
if (e.key === '') {
flag = true
}
})
return flag
}
},
watch: {
value(val) {
this.setValue(val)
}
},
created() {
this.setValue(this.value)
},
methods: {
setValue(val) {
const arr = []
for (const i in val) {
arr.push({ key: i, value: val[i] })
}
this.editTagForm = arr
},
addTag() {
this.editTagForm.push({ key: '', value: '' })
},
removeTag(tag, index) {
this.editTagForm.splice(index, 1)
this.blurInput()
},
blurInput() {
const obj = {}
this.editTagForm.forEach(e => {
if (e.key !== '') {
obj[e.key] = e.value
}
})
this.$emit('input', obj)
}
}
}
</script>
<style lang="scss" scoped>
.addBtn{
margin-top: 10px;
}
</style>

338
src/components/list.vue Normal file
View File

@ -0,0 +1,338 @@
<template>
<div>
<div class="list-btns">
<el-form v-if="filterMap" :inline="true" class="demo-form-inline">
<el-form-item
v-for="(item, key) in filterMap"
:key="key"
:label="item.label+':'"
>
<RenderCell
v-if="item.render"
:render="item.render"
:item-key="key"
:filter-data="filterData"
/>
<template v-else>
<el-select
v-if="item.type === 'select'"
v-model="filterData[key]"
size="medium"
>
<el-option v-for="(it,idx) in item.dataSource" :key="key+idx" :label="it.label" :value="it.value" />
</el-select>
<el-datepicker
v-else-if="item.type === 'date'"
v-model="filterData[key]"
type="date"
/>
<template
v-else-if="item.type === 'dateRange'"
>
<el-date-picker
v-model="filterData[key]"
type="daterange"
:range-separator="$t('message.to')"
:start-placeholder="$t('message.startDate')"
:end-placeholder="$t('message.endDate')"
/>
<!-- <el-datepicker
v-model="filterData[item.key1]"
type="date"
:clearable="false"
:format='item.format'>
</el-datepicker>
-
<el-datepicker
v-model="filterData[item.key2]"
type="date"
:clearable="false"
:format='item.format'>
</el-datepicker> -->
</template>
<el-input
v-else
v-model="filterData[key]"
size="medium"
@enter="onSearchClick"
/>
</template>
</el-form-item>
<el-form-item>
<el-button
size="medium"
type="info"
@click="onSearchClick"
>
{{ $t('message.search') }}
</el-button>
<el-button
size="medium"
@click="resetSearch"
>
{{ $t('message.reset') }}
</el-button>
</el-form-item>
</el-form>
<!-- {{tableData}}-->
<div class="btn-right"><slot name="filterBtns" class="slot" /></div>
</div>
<el-table
v-if="tableData.length>0"
ref="multipleTable"
class="multipleTable"
:data="paginationAuto ? (tableData.slice((page - 1) * 10, page * 10)) : tableData"
v-bind="$attrs"
:max-height="height"
:border="border"
:highlight-current-row="listColumns.length>0 && listColumns[0].type !== 'selection'"
tooltip-effect="dark"
style="width: 100%"
size="medium"
v-on="$listeners"
>
<el-table-column
v-for="(item, index) in listColumns"
:key="index"
:prop="item.prop"
:label="item.label"
:width="item.width"
:formatter="item.formatter"
:sortable="item.sortable"
:show-overflow-tooltip="item.showOverflowTooltip"
:type="item.type||''"
:selectable="item.selectable"
>
<!-- <template slot="header" slot-scope="scope">
<RenderCell
v-if="typeof item.label === 'function'"
:render="item.label"
:index="scope.$index"
:row="scope.row" />
<span v-else>
{{item.label}}
</span>
</template>
<template slot-scope="scope" >
<RenderCell
v-if="item.render"
:render="item.render"
:index="scope.$index"
:row="scope.row" />
<span v-else>
{{
item.formatter? item.formatter(scope.row, scope.row[item.prop]): scope.row[item.prop]
}}
</span>
</template> -->
</el-table-column>
</el-table>
<el-pagination
v-if="pagination"
background
:hide-on-single-page="true"
:current-page="page"
:pager-count="5"
:layout=" small ? 'prev, pager, next' :'total, prev, pager, next, jumper'"
:small="small"
:total="paginationAuto ? tableData.length :total"
@current-change="currentChange"
/>
</div>
</template>
<script>
import RenderCell from './renderCell'
export default {
name: 'List',
components: { RenderCell },
props: {
getListAction: {
type: Function,
default: undefined
},
map: {
type: Object,
default: undefined
},
columns: {
type: Array,
default: () => []
},
pagination: {
type: Boolean,
default: false
},
paginationAuto: {
type: Boolean,
default: false
},
query: {
type: Object,
default: undefined
}, // api query
filterMap: {
type: Object,
default: () => {}
},
defaultFilterData: {
type: Object,
default: () => {
}
},
funcName: {
type: String,
default: ''
},
isRefresh: {
type: Boolean,
default: false
},
tableListData: {
type: Array,
default: () => []
},
clusterName: {
type: String,
default: ''
},
listKey: {
type: String,
default: 'list'
},
totalKey: {
type: String,
default: 'total'
},
pageKey: {
type: String,
default: 'page'
},
limitKey: {
type: String,
default: 'limit'
},
small: {
type: Boolean,
default: false
},
height: {
type: String,
default: 'auto'
},
border: {
type: Boolean,
default: false
}
},
data() {
const filterData = { ...this.defaultFilterData }
return {
page: 1,
limit: 10,
total: 0,
tableData: [],
listColumns: [],
filterData
}
},
watch: {
tableListData(val) {
this.tableData = val
}
},
mounted() {
if (this.funcName || this.getListAction) {
this.getList()
} else {
this.tableData = this.tableListData
}
this.listColumns = this.columns || this.mapToColums(this.map)
},
beforeDestroy() {
clearInterval(this.timer)
this.timer = null
},
methods: {
setCurrent(row) {
this.$refs.multipleTable.setCurrentRow(row)
},
mapToColums(map) {
const column = []
Object.keys(map).forEach((e) => {
if (e === 'selection' || e === 'index') {
column.push({ type: e, key: map[e] })
} else {
column.push({ prop: e, label: map[e] })
}
})
return column
},
currentChange(page) {
this.page = page
if (!this.paginationAuto) {
this.getList()
}
},
onSearchClick() {
this.page = 1
this.getList()
},
resetSearch() {
this.filterData = {}
this.getList()
},
async getList() {
const params = {
[this.pageKey]: this.pagination ? this.page : undefined,
[this.limitKey]: this.pagination ? this.limit : undefined,
...this.query,
...this.filterData
}
if (this.getListAction) {
const ListData = await this.getListAction(params)
this.total = Number(ListData?.data?.[this.totalKey]) || Number(ListData[this.totalKey]) || 0
this.tableData = ListData?.data?.[this.listKey] || ListData[this.listKey] || []
} else {
const clusterName = this.clusterName
const ListData = await this.$HandleFunc[this.funcName](clusterName, params)
this.total = ListData.total
this.tableData = ListData.rows
}
},
setCurrentRow(row) {
this.$refs.multipleTable.setCurrentRow(row)
}
}
}
</script>
<style lang="scss" scoped>
.filter-field{
margin-bottom: 20px;
// font-size: 0.9rem;
.filter-item{
margin-right: 20px;
margin-bottom: 20px;
.el-input{
width: 100px;
}
float: left;
}
}
.multipleTable{
// margin-top: 20px;
}
.el-pagination{
margin-top: 15px;
float: right;
margin-bottom: 15px;
}
</style>

View File

@ -0,0 +1,8 @@
export default {
name: 'render-cell',
functional: true,
props: {
render: Function
},
render: (h, ctx) => ctx.props.render(h, ctx.data.attrs)
}

68
src/filters/index.js Normal file
View File

@ -0,0 +1,68 @@
// import parseTime, formatTime and set to filter
export { parseTime, formatTime } from '@/utils'
/**
* Show plural label if time is plural number
* @param {number} time
* @param {string} label
* @return {string}
*/
function pluralize(time, label) {
if (time === 1) {
return time + label
}
return time + label + 's'
}
/**
* @param {number} time
*/
export function timeAgo(time) {
const between = Date.now() / 1000 - Number(time)
if (between < 3600) {
return pluralize(~~(between / 60), ' minute')
} else if (between < 86400) {
return pluralize(~~(between / 3600), ' hour')
} else {
return pluralize(~~(between / 86400), ' day')
}
}
/**
* Number formatting
* like 10000 => 10k
* @param {number} num
* @param {number} digits
*/
export function numberFormatter(num, digits) {
const si = [
{ value: 1E18, symbol: 'E' },
{ value: 1E15, symbol: 'P' },
{ value: 1E12, symbol: 'T' },
{ value: 1E9, symbol: 'G' },
{ value: 1E6, symbol: 'M' },
{ value: 1E3, symbol: 'k' }
]
for (let i = 0; i < si.length; i++) {
if (num >= si[i].value) {
return (num / si[i].value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, '$1') + si[i].symbol
}
}
return num.toString()
}
/**
* 10000 => "10,000"
* @param {number} num
*/
export function toThousandFilter(num) {
return (+num || 0).toString().replace(/^-?\d+/g, m => m.replace(/(?=(?!\b)(\d{3})+$)/g, ','))
}
/**
* Upper case first char
* @param {String} string
*/
export function uppercaseFirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}

1
src/icons/iconfont.js Normal file

File diff suppressed because one or more lines are too long

9
src/icons/index.js Normal file
View File

@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

1
src/icons/svg/404.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M121.718 73.272v9.953c3.957-7.584 6.199-16.05 6.199-24.995C127.917 26.079 99.273 0 63.958 0 28.644 0 0 26.079 0 58.23c0 .403.028.806.028 1.21l22.97-25.953h13.34l-19.76 27.187h6.42V53.77l13.728-19.477v49.361H22.998V73.272H2.158c5.951 20.284 23.608 36.208 45.998 41.399-1.44 3.3-5.618 11.263-12.565 12.674-8.607 1.764 23.358.428 46.163-13.178 17.519-4.611 31.938-15.849 39.77-30.513h-13.506V73.272H85.02V59.464l22.998-25.977h13.008l-19.429 27.187h6.421v-7.433l13.727-19.402v39.433h-.027zm-78.24 2.822a10.516 10.516 0 0 1-.996-4.535V44.548c0-1.613.332-3.124.996-4.535a11.66 11.66 0 0 1 2.713-3.68c1.134-1.032 2.49-1.864 4.04-2.468 1.55-.605 3.21-.908 4.982-.908h11.292c1.77 0 3.431.303 4.981.908 1.522.604 2.85 1.41 3.986 2.418l-12.26 16.303v-2.898a1.96 1.96 0 0 0-.665-1.512c-.443-.403-.996-.604-1.66-.604-.665 0-1.218.201-1.661.604a1.96 1.96 0 0 0-.664 1.512v9.071L44.364 77.606a10.556 10.556 0 0 1-.886-1.512zm35.73-4.535c0 1.613-.332 3.124-.997 4.535a11.66 11.66 0 0 1-2.712 3.68c-1.134 1.032-2.49 1.864-4.04 2.469-1.55.604-3.21.907-4.982.907H55.185c-1.77 0-3.431-.303-4.981-.907-1.55-.605-2.906-1.437-4.041-2.47a12.49 12.49 0 0 1-1.384-1.512l13.727-18.217v6.375c0 .605.222 1.109.665 1.512.442.403.996.604 1.66.604.664 0 1.218-.201 1.66-.604a1.96 1.96 0 0 0 .665-1.512V53.87L75.97 36.838c.913.932 1.66 1.99 2.214 3.175.664 1.41.996 2.922.996 4.535v27.011h.028z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.7 KiB

1
src/icons/svg/bug.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M127.88 73.143c0 1.412-.506 2.635-1.518 3.669-1.011 1.033-2.209 1.55-3.592 1.55h-17.887c0 9.296-1.783 17.178-5.35 23.645l16.609 17.044c1.011 1.034 1.517 2.257 1.517 3.67 0 1.412-.506 2.635-1.517 3.668-.958 1.033-2.155 1.55-3.593 1.55-1.438 0-2.635-.517-3.593-1.55l-15.811-16.063a15.49 15.49 0 0 1-1.196 1.06c-.532.434-1.65 1.208-3.353 2.322a50.104 50.104 0 0 1-5.192 2.974c-1.758.87-3.94 1.658-6.546 2.364-2.607.706-5.189 1.06-7.748 1.06V47.044H58.89v73.062c-2.716 0-5.417-.367-8.106-1.102-2.688-.734-5.003-1.631-6.945-2.692a66.769 66.769 0 0 1-5.268-3.179c-1.571-1.057-2.73-1.94-3.476-2.65L33.9 109.34l-14.611 16.877c-1.066 1.14-2.344 1.711-3.833 1.711-1.277 0-2.422-.434-3.434-1.304-1.012-.978-1.557-2.187-1.635-3.627-.079-1.44.333-2.705 1.236-3.794l16.129-18.51c-3.087-6.197-4.63-13.644-4.63-22.342H5.235c-1.383 0-2.58-.517-3.592-1.55S.125 74.545.125 73.132c0-1.412.506-2.635 1.518-3.668 1.012-1.034 2.21-1.55 3.592-1.55h17.887V43.939L9.308 29.833c-1.012-1.033-1.517-2.256-1.517-3.669 0-1.412.505-2.635 1.517-3.668 1.012-1.034 2.21-1.55 3.593-1.55s2.58.516 3.593 1.55l13.813 14.106h67.396l13.814-14.106c1.012-1.034 2.21-1.55 3.592-1.55 1.384 0 2.581.516 3.593 1.55 1.012 1.033 1.518 2.256 1.518 3.668 0 1.413-.506 2.636-1.518 3.67l-13.814 14.105v23.975h17.887c1.383 0 2.58.516 3.593 1.55 1.011 1.033 1.517 2.256 1.517 3.668l-.005.01zM89.552 26.175H38.448c0-7.23 2.489-13.386 7.466-18.469C50.892 2.623 56.92.082 64 .082c7.08 0 13.108 2.541 18.086 7.624 4.977 5.083 7.466 11.24 7.466 18.469z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

1
src/icons/svg/chart.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 54.857h36.571V128H0V54.857zM91.429 27.43H128V128H91.429V27.429zM45.714 0h36.572v128H45.714V0z"/></svg>

After

Width:  |  Height:  |  Size: 179 B

35
src/icons/svg/china.svg Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 833 833" style="enable-background:new 0 0 833 833;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;stroke:#000000;stroke-width:3;stroke-linejoin:round;}
.st1{fill-rule:evenodd;clip-rule:evenodd;}
</style>
<path d="M417.2,61.3C221.4,61.3,62.7,220,62.7,415.8s158.7,354.5,354.5,354.5s354.5-158.7,354.5-354.5S613,61.3,417.2,61.3z
M417.2,732.5c-173.4,0-314-140.6-314-314s140.6-314,314-314s314,140.6,314,314S590.6,732.5,417.2,732.5z"/>
<path id="形状_1" class="st0" d="M591.2,252.4l-0.9-7.3l-1.8-4.6l-2.7-6.4l-2.7-7.3l-2.7-8.2l-5.5-10.1l-3.7-1.8l-4.6,0.9L561,203
h-11l-10.1,2.7l-5.5,4.6l-3.7,2.7l2.7,2.7l2.7,6.4l-3.7,3.7l-9.1,17.4l0.9,3.7c-2.5,0.6-5,1.5-7.3,2.7c-2.7,1.7-6.4,6.4-6.4,6.4
l-6.4-4.6l-11,22l1.8,2.7c0,0,3.6-3.4,7.3-2.7c2.2,0.4,4.2,1.8,5.5,3.7c0,0,5.7-4.3,8.2-3.7s8.2,9.1,8.2,9.1l2.7,5.5l-6.4,0.9
l-6.4,0.9l-5.5,2.7l-6.4-0.9l-3.7,6.4l-6.4,6.4l-2.7-1.8l-9.1,8.2h-8.2l-7.3-2.7l-4.6,7.3l2.7,5.5l-11.9,12.8l-9.1,3.7h-14.6
l-21,8.2l-4.6-1.8h-4.6l-14.6-3.7l-3.7-4.6h-41.2l-7.3-19.2l-6.4,0.9l-7.3-7.3h-6.4l-12.8-2.7l-6.4-5.5l3.7-11l-6.4-14.6l-11-3.7
l-8.2-6.4l-1.8-5.5l-5.5-1.8l-1.8,4.6l-7.3,5.5l-1.8,13.7l-10.1,1.8l-8.2-2.7l-9.1,16.5l1.8,7.3l-8.2-1.8l-12.8,5.5l8.2,18.3
l-5.5,7.3v4.6l-1.8,2.7l-18.3,9.1h-5.5l-4.6,10.1l-9.1-5.5l-5.5,5.5h-5.5l-1.8,6.4l-2.7,4.6l10.1,17.4l6.4,7.3l3.7,3.7l2.8,6.4
l5.5,2.7l8.2,0.9l1.8,6.4l8.2,3.7l-4.6,4.6l2.7,6.4l1.8,5.5l-6.4,1.8l2.7,4.6v7.3l2.7,1.8l5.5,2.7l3.7,0.9l5.5,7.3l4.6-5.5
l17.4,11.9l2.7-2.7l13.7,14.6l8.2-0.9l5.5,2.7l10.1-3.7l1.8,8.2l8.2-8.2l6.4,1.8l8.2,1.8l0.9,2.7l1.8,4.6h8.2l8.2-3.7l5.5-0.9
l10.1-8.2l10.1,7.3l3.7-9.1l7.3,6.4l-1.8,4.6l5.5,11.9l-11,9.1l-0.9,10.1l12.8-2.7l-2.7,5.5l4.6,8.2l-0.9,4.6l5.5,1.8l3.7,4.6
l4.6-2.7l7.3,5.5l-1.8-10.1l3.7-1.8l5.5-2.7h8.2l0.9,4.6l11-11l11.9,6.4l-1.8,5.5l11.9,4.6h11l4.6,5.5h4.6l4.6-4.6l7.3-0.9l3.7,2.7
l9.1-6.4l1.8-7.3l4.6,7.3l5.5-5.5h7.3l11.9-9.1l10.1-6.4l5.5-8.2l4.6-9.1l9.1-8.2l4.6-7.3l2.7-10.1l-0.9-7.3h6.4l-6.4-10.1v-6.4
l-2.7-8.2l-3.7-2.7l-2.7-11.9l-11-8.2l4.6-8.2l7.3-3.7l6.4-7.3l6.4-1.8l-6.4-6.4l-7.3-0.9l-7.3,3.7l-5.5-2.7l-0.9-5.5l-9.1-4.6
l0.9-5.5l6.4,1.8l19.2-18.3l6.4-0.9l1.8,2.7l-3.7,8.2l-0.9,7.3l11-6.4l2.7,6.4l2.7-7.3l8.2-4.6l10.1-7.3l8.2-8.2l10.1,2.7L593,343
l5.5-0.9l6.4-4.6l4.6-5.5l5.5,5.5l2.7-6.4l3.7-4.6l-1.8-15.6l6.4-5.5l8.2,3.7l9.2-12.8l-3.7-4.6l3.7-6.4l-0.9-2.7l7.3-4.6l-1.8-4.6
l3.7-4.6l-24.7,11l-8.2-2.7l-3.7-13.7l-14.6-9.1L591.2,252.4z"/>
<path id="圆角矩形_1_拷贝" class="st1" d="M434.7,559.8v5.5l11,6.4l10.1-8.2l5.5-9.1c0,0-8.5-6.4-9.1-6.4s-7.1,0.4-7.3,0.9
s-11,8.2-11,8.2L434.7,559.8z M539.9,503.1c0,0-11.6,12.6-12.8,18.3s4.2,18.5,7.3,18.3s8.9-13.8,11-21s3.5-13,0-14.6
S539.9,503.1,539.9,503.1L539.9,503.1z M545.4,536l-9.1,10.1l1.8,3.7l10.1-10.1L545.4,536z M528.2,558.5l-4.3,12.9l3.2,2.6l5.1-13.3
L528.2,558.5z M519.7,617.6l-4.8,12.7l3.1,2.7l5.6-13.1L519.7,617.6z M497.7,649l-7.6,11.2l2.3,3.4l8.5-11.4L497.7,649z M473.4,685
l-12.9,4.4l-0.2,4.1l13.7-3.9L473.4,685z M449.8,615.7l-3.1,13.3l3.4,2.3l3.9-13.7L449.8,615.7z M439.7,584.6l6.4,12l4.1-0.5
l-6.1-12.9L439.7,584.6z M521.2,585.4c1.3,0,2.3,1,2.3,2.3v10.1c0,1.3-1.1,2.3-2.3,2.2c-1.2,0-2.2-1-2.2-2.2v-10
C518.9,586.4,519.9,585.4,521.2,585.4L521.2,585.4L521.2,585.4z M434.2,658.6c1.3,0,2.3,1,2.3,2.3l0,0v10.1c0,1.3-1,2.3-2.3,2.3
s-2.3-1-2.3-2.3l0,0v-10.1C432,659.6,433,658.6,434.2,658.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M54.857 118.857h64V73.143H89.143c-1.902 0-3.52-.668-4.855-2.002-1.335-1.335-2.002-2.954-2.002-4.855V36.57H54.857v82.286zM73.143 16v-4.571a2.2 2.2 0 0 0-.677-1.61 2.198 2.198 0 0 0-1.609-.676H20.571c-.621 0-1.158.225-1.609.676a2.198 2.198 0 0 0-.676 1.61V16a2.2 2.2 0 0 0 .676 1.61c.451.45.988.676 1.61.676h50.285c.622 0 1.158-.226 1.61-.677.45-.45.676-.987.676-1.609zm18.286 48h21.357L91.43 42.642V64zM128 73.143v48c0 1.902-.667 3.52-2.002 4.855-1.335 1.335-2.953 2.002-4.855 2.002H52.57c-1.901 0-3.52-.667-4.854-2.002-1.335-1.335-2.003-2.953-2.003-4.855v-11.429H6.857c-1.902 0-3.52-.667-4.855-2.002C.667 106.377 0 104.759 0 102.857v-96c0-1.902.667-3.52 2.002-4.855C3.337.667 4.955 0 6.857 0h77.714c1.902 0 3.52.667 4.855 2.002 1.335 1.335 2.003 2.953 2.003 4.855V30.29c1 .622 1.856 1.29 2.569 2.003l29.147 29.147c1.335 1.335 2.478 3.145 3.429 5.43.95 2.287 1.426 4.383 1.426 6.291v-.018z"/></svg>

After

Width:  |  Height:  |  Size: 971 B

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h54.857v54.857H0V0zm0 73.143h54.857V128H0V73.143zm73.143 0H128V128H73.143V73.143zm27.428-18.286C115.72 54.857 128 42.577 128 27.43 128 12.28 115.72 0 100.571 0 85.423 0 73.143 12.28 73.143 27.429c0 15.148 12.28 27.428 27.428 27.428z"/></svg>

After

Width:  |  Height:  |  Size: 319 B

11
src/icons/svg/cs.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 24.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 200 200" style="enable-background:new 0 0 200 200;" xml:space="preserve">
<path d="M131.2,28.5c1.4,0,2.4,1.1,2.4,2.4V169c0,1.4-1.1,2.4-2.4,2.4H68.8c-1.4,0-2.4-1.1-2.4-2.4V31c0-1.4,1.1-2.4,2.4-2.4H131.2
M131.2,18.8H68.8c-6.7,0-12.2,5.4-12.2,12.2V169c0,6.7,5.4,12.2,12.2,12.2h62.4c6.7,0,12.2-5.4,12.2-12.2V31
C143.3,24.2,137.9,18.8,131.2,18.8z"/>
<path d="M79,54.8h42.9v9.7H79V54.8z M79,75.9h42.9v9.7H79V75.9z M94.4,128.3h10.2v10.2H94.4V128.3z M171.6,33.2h-20.6V43h20.6v118.3
h-20.6v9.7h20.6c5.4,0,9.7-4.4,9.7-9.7V43C181.4,37.6,177,33.2,171.6,33.2z M29.7,33.2h20.6V43H29.7v118.3h20.6v9.7H29.7
c-5.4,0-9.7-4.4-9.7-9.7V43C20,37.6,24.3,33.2,29.7,33.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 938 B

View File

@ -0,0 +1 @@
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M71.984 44.815H115.9L71.984 9.642v35.173zM16.094.05h63.875l47.906 38.37v76.74c0 3.392-1.682 6.645-4.677 9.044-2.995 2.399-7.056 3.746-11.292 3.746H16.094c-4.236 0-8.297-1.347-11.292-3.746-2.995-2.399-4.677-5.652-4.677-9.044V12.84C.125 5.742 7.23.05 16.094.05zm71.86 102.32V89.58h-71.86v12.79h71.86zm23.952-25.58V64H16.094v12.79h95.812z"/></svg>

After

Width:  |  Height:  |  Size: 418 B

1
src/icons/svg/drag.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M73.137 29.08h-9.209 29.7L63.886.093 34.373 29.08h20.49v27.035H27.238v17.948h27.625v27.133h18.274V74.063h27.41V56.115h-27.41V29.08zm-9.245 98.827l27.518-26.711H36.59l27.302 26.71zM.042 64.982l27.196 27.029V38.167L.042 64.982zm100.505-26.815V92.01l27.41-27.029-27.41-26.815z"/></svg>

After

Width:  |  Height:  |  Size: 356 B

1
src/icons/svg/dsxs.svg Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><svg id="_层_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.44 12.44"><defs><style>.cls-1{fill:#fff;}</style></defs><g id="_层_3"><path class="cls-1" d="M6.22,12.44C2.79,12.44,0,9.65,0,6.22S2.79,0,6.22,0s6.22,2.79,6.22,6.22-2.79,6.22-6.22,6.22Zm0-8.91c-1.48,0-2.68,1.2-2.68,2.68s1.2,2.69,2.68,2.69,2.68-1.21,2.68-2.69-1.2-2.68-2.68-2.68Z"/></g></svg>

After

Width:  |  Height:  |  Size: 397 B

1
src/icons/svg/edit.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M106.133 67.2a4.797 4.797 0 0 0-4.8 4.8c0 .187.014.36.027.533h-.027V118.4H9.6V26.667h50.133c2.654 0 4.8-2.147 4.8-4.8 0-2.654-2.146-4.8-4.8-4.8H9.6a9.594 9.594 0 0 0-9.6 9.6V118.4c0 5.307 4.293 9.6 9.6 9.6h91.733c5.307 0 9.6-4.293 9.6-9.6V72.533h-.026c.013-.173.026-.346.026-.533 0-2.653-2.146-4.8-4.8-4.8z"/><path d="M125.16 13.373L114.587 2.8c-3.747-3.747-9.854-3.72-13.6.027l-52.96 52.96a4.264 4.264 0 0 0-.907 1.36L33.813 88.533c-.746 1.76-.226 3.534.907 4.68 1.133 1.147 2.92 1.667 4.693.92l31.4-13.293c.507-.213.96-.52 1.36-.907l52.96-52.96c3.747-3.746 3.774-9.853.027-13.6zM66.107 72.4l-18.32 7.76 7.76-18.32L92.72 24.667l10.56 10.56L66.107 72.4zm52.226-52.227l-8.266 8.267-10.56-10.56 8.266-8.267.027-.026 10.56 10.56-.027.026z"/></svg>

After

Width:  |  Height:  |  Size: 818 B

View File

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M88.883 119.565c-7.284 0-19.434 2.495-21.333 8.25v.127c-4.232.13-5.222 0-7.108 0-1.895-5.76-14.045-8.256-21.333-8.256H0V0h42.523c9.179 0 17.109 5.47 21.47 13.551C68.352 5.475 76.295 0 85.478 0H128v119.57l-39.113-.005h-.004zM60.442 24.763c0-9.651-8.978-16.507-17.777-16.507H7.108V111.43H39.11c7.054-.14 18.177.082 21.333 6.12v-4.628c-.134-5.722-.004-13.522 0-13.832V27.413l.004-2.655-.004.005zm60.442-16.517h-35.55c-8.802 0-17.78 6.856-17.78 16.493v74.259c.004.32.138 8.115 0 13.813v4.627c3.155-6.022 14.279-6.26 21.333-6.114h32V8.25l-.003-.005z"/></svg>

After

Width:  |  Height:  |  Size: 627 B

1
src/icons/svg/email.svg Normal file
View File

@ -0,0 +1 @@
<svg width="128" height="96" xmlns="http://www.w3.org/2000/svg"><path d="M64.125 56.975L120.188.912A12.476 12.476 0 0 0 115.5 0h-103c-1.588 0-3.113.3-4.513.838l56.138 56.137z"/><path d="M64.125 68.287l-62.3-62.3A12.42 12.42 0 0 0 0 12.5v71C0 90.4 5.6 96 12.5 96h103c6.9 0 12.5-5.6 12.5-12.5v-71a12.47 12.47 0 0 0-1.737-6.35L64.125 68.287z"/></svg>

After

Width:  |  Height:  |  Size: 347 B

Some files were not shown because too many files have changed in this diff Show More