This commit is contained in:
2025-12-19 14:26:09 +08:00
parent 4c849c2c3d
commit 2f4b5d0870
166 changed files with 43871 additions and 1 deletions

3
.browserslistrc Normal file
View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

14
.env.development Normal file
View File

@@ -0,0 +1,14 @@
# just a flag
ENV = 'development'
# base api
# VUE_APP_BASE_API = '/mdmds-bs/mdmds/v1'
# VUE_APP_KEYCLOAK_AUTH_SERVER_URL = 'https://idp.qs.cloudidp.vgcserv.com.cn/auth'
# VUE_APP_KEYCLOAK_REALM = 'vgcmfa'
# VUE_APP_KEYCLOAK_CLIENT_ID = 'EHR_FRONTEND_QS'
VUE_APP_BASE_API = 'mdmds/v1'
VUE_APP_KEYCLOAK_AUTH_SERVER_URL = 'http://auth.haramasu.com/auth'
VUE_APP_KEYCLOAK_REALM = 'testmdmds'
VUE_APP_KEYCLOAK_CLIENT_ID = 'EHR_FRONTEND_PROD'

8
.env.production Normal file
View File

@@ -0,0 +1,8 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/mdmds-bs/mdmds/v1'
VUE_APP_KEYCLOAK_AUTH_SERVER_URL = 'https://idp.cloudidp.vgcserv.com.cn/auth'
VUE_APP_KEYCLOAK_REALM = 'vgcmfa2'
VUE_APP_KEYCLOAK_CLIENT_ID = 'EHR_FRONTEND_PROD'

8
.env.qa Normal file
View File

@@ -0,0 +1,8 @@
# just a flag
ENV = 'qa'
# base api
VUE_APP_BASE_API = '/mdmds-bs/mdmds/v1'
VUE_APP_KEYCLOAK_AUTH_SERVER_URL = 'https://idp.qs.cloudidp.vgcserv.com.cn/auth'
VUE_APP_KEYCLOAK_REALM = 'vgcmfa'
VUE_APP_KEYCLOAK_CLIENT_ID = 'EHR_FRONTEND_QS'

8
.env.test Normal file
View File

@@ -0,0 +1,8 @@
# just a flag
ENV = 'test'
# base api
VUE_APP_BASE_API = '/mdmds/v1'
VUE_APP_KEYCLOAK_AUTH_SERVER_URL = 'http://81.70.254.22:8082/auth'
VUE_APP_KEYCLOAK_REALM = 'mdmds'
VUE_APP_KEYCLOAK_CLIENT_ID = 'mdmds-front'

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# 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?

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm run build
# production stage
FROM nginx as production-stage
COPY --from=build-stage /app /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,2 +1,19 @@
# mdmdms_fs_prod # fileupload
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

115
bamboo-specs/bamboo.yaml Normal file
View File

@@ -0,0 +1,115 @@
---
version: 2
plan:
project-key: EHR
key: MFR
name: MDMDS FRONTEND CI
stages:
- app-ci:
manual: false
final: false
jobs:
- helmchart-ci
- docker-ci
helmchart-ci:
key: HEL
docker:
image: jie3324/azure-cli
volumes:
${bamboo.working.directory}: ${bamboo.working.directory}
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
docker-run-arguments: []
tasks:
- checkout:
force-clean-build: 'false'
- script:
interpreter: SHELL
scripts:
- |-
image_name=${bamboo.appname}
image_tag=0.${bamboo.buildNumber}
ls -l
az version
az cloud set -n AzureChinaCloud
az login --service-principal -u ${bamboo.client_id} -p ${bamboo.client_secret} --tenant ${bamboo.tenant_id}
az acr helm repo add -n ${bamboo.acr_repo}
az acr helm list -n ${bamboo.acr_repo} --query 'keys(@)' -o tsv
#helm package
helm package helmcharts --version $image_tag
ls -l
#helm push
az acr helm push --name ${bamboo.acr_repo} ${image_name}-${image_tag}.tgz
az acr helm list -n ${bamboo.acr_repo} --query 'keys(@)' -o tsv
az acr helm list -n ${bamboo.acr_repo} --query '"'${image_name}'"[].version' -o tsv
docker-ci:
key: JOB1
docker:
image: jie3324/docker-agent-ci:npm
volumes:
${bamboo.working.directory}: ${bamboo.working.directory}
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
/var/run: /var/run
docker-run-arguments:
- --privileged
tasks:
- checkout:
force-clean-build: 'false'
- script:
interpreter: SHELL
scripts:
- |-
image_name=${bamboo.appname}
image_tag=0.${bamboo.buildNumber}
npm install
npm run build
cat <<EOF > default.conf
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 /html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
EOF
cat <<EOF > Dockerfile
FROM nginx:alpine
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
COPY dist html
COPY default.conf /etc/nginx/conf.d/default.conf
EOF
pwd && ls -l
# Get acr password from keyvault
tenant_id=${bamboo.tenant_id}
client_id=${bamboo.client_id}
client_secret=${bamboo.client_secret}
jwt=`curl --location --request POST "https://login.chinacloudapi.cn/$tenant_id/oauth2/token?api-version=1.0" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "resource=https://vault.azure.cn" \
--data-urlencode "client_id=$client_id" \
--data-urlencode "client_secret=$client_secret" |awk -F 'access_token' '{print $2}'|awk -F '"' '{print $3}'`
acr_password=`curl -k --request GET -H "Content-type: application/json;charset=UTF-8" -H "Authorization: Bearer $jwt" -s https://${bamboo.keyvault}.vault.azure.cn/secrets/acr-passwd?api-version=7.2 |awk -F '"' '{print $4}'`
docker build -t ${bamboo.acr_repo}.azurecr.cn/${image_name}:${image_tag} .
docker login ${bamboo.acr_repo}.azurecr.cn -u ${bamboo.acr_user} -p ${acr_password}
docker push ${bamboo.acr_repo}.azurecr.cn/${image_name}:${image_tag}
variables:
appname: mdmds-frontend

23
helmcharts/.helmignore Normal file
View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

23
helmcharts/Chart.yaml Normal file
View File

@@ -0,0 +1,23 @@
apiVersion: v2
name: mdmds-frontend
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 1.16.0

View File

@@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mdmds-backend.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "mdmds-backend.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mdmds-backend.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mdmds-backend.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mdmds-backend.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mdmds-backend.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mdmds-backend.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "mdmds-backend.labels" -}}
helm.sh/chart: {{ include "mdmds-backend.chart" . }}
{{ include "mdmds-backend.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "mdmds-backend.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mdmds-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "mdmds-backend.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mdmds-backend.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mdmds-backend.fullname" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mdmds-backend.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mdmds-backend.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "mdmds-backend.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
## livenessProbe:
## httpGet:
## path: /
## port: http
## readinessProbe:
## httpGet:
## path: /
## port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,28 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "mdmds-backend.fullname" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "mdmds-backend.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,48 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "mdmds-backend.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{ $svcName := .backend.service.name }}
{{ $svcPort := .backend.service.port.number }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $svcName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mdmds-backend.fullname" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "mdmds-backend.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "mdmds-backend.serviceAccountName" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mdmds-backend.fullname" . }}-test-connection"
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mdmds-backend.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

88
helmcharts/values.yaml Normal file
View File

@@ -0,0 +1,88 @@
# Default values for mdmds-backend.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: ehrapprepo.azurecr.cn/mdmds-frontend
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/use-regex: "true"
# kubernetes.io/tls-acme: "true"
hosts:
- host: ehrapptest.chinanorth2.cloudapp.chinacloudapi.cn
paths:
- path: /mdmds-frontend/(.*)$
pathType: Prefix
backend:
service:
name: mdmds-frontend
port:
number: 80
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}

3
mdmds/.browserslistrc Normal file
View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

16
mdmds/.env.development Normal file
View File

@@ -0,0 +1,16 @@
# just a flag
ENV = 'development'
# base api
# VUE_APP_BASE_API = 'http://52.131.81.121:15814'
VUE_APP_BASE_API = 'http://mdmds.haramasu.com/mdmds/v1/'
# vue-cli uses the VUE_CLI_BABEL_TRANSPILE_MODULES environment variable,
# to control whether the babel-plugin-dynamic-import-node plugin is enabled.
# It only does one thing by converting all import() to require().
# This configuration can significantly increase the speed of hot updates,
# when you have a large number of pages.
# Detail: https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/babel-preset-app/index.js
VUE_CLI_BABEL_TRANSPILE_MODULES = true

7
mdmds/.env.production Normal file
View File

@@ -0,0 +1,7 @@
# just a flag
ENV = 'production'
# base api
#VUE_APP_BASE_API = '/mdmds-backend'
VUE_APP_BASE_API = 'http://mdmds-backend-service/'
# VUE_APP_BASE_API = 'localhost:8099'

6
mdmds/.env.test Normal file
View File

@@ -0,0 +1,6 @@
# just a flag
ENV = 'production'
# base api
VUE_APP_BASE_API = '/webportal-api'
VUE_APP_BUILD = 'QA'

23
mdmds/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# 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?

14
mdmds/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# build stage
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm run build
COPY dist ./
# production stage
FROM nginx as production-stage
COPY --from=build-stage /app /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

19
mdmds/README.md Normal file
View File

@@ -0,0 +1,19 @@
# fileupload
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
mdmds/babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,115 @@
---
version: 2
plan:
project-key: EHR
key: MFR
name: MDMDS FRONTEND CI
stages:
- app-ci:
manual: false
final: false
jobs:
- helmchart-ci
- docker-ci
helmchart-ci:
key: HEL
docker:
image: jie3324/azure-cli
volumes:
${bamboo.working.directory}: ${bamboo.working.directory}
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
docker-run-arguments: []
tasks:
- checkout:
force-clean-build: 'false'
- script:
interpreter: SHELL
scripts:
- |-
image_name=${bamboo.appname}
image_tag=0.${bamboo.buildNumber}
ls -l
az version
az cloud set -n AzureChinaCloud
az login --service-principal -u ${bamboo.client_id} -p ${bamboo.client_secret} --tenant ${bamboo.tenant_id}
az acr helm repo add -n ${bamboo.acr_repo}
az acr helm list -n ${bamboo.acr_repo} --query 'keys(@)' -o tsv
#helm package
helm package helmcharts --version $image_tag
ls -l
#helm push
az acr helm push --name ${bamboo.acr_repo} ${image_name}-${image_tag}.tgz
az acr helm list -n ${bamboo.acr_repo} --query 'keys(@)' -o tsv
az acr helm list -n ${bamboo.acr_repo} --query '"'${image_name}'"[].version' -o tsv
docker-ci:
key: JOB1
docker:
image: jie3324/docker-agent-ci:npm
volumes:
${bamboo.working.directory}: ${bamboo.working.directory}
${bamboo.tmp.directory}: ${bamboo.tmp.directory}
/var/run: /var/run
docker-run-arguments:
- --privileged
tasks:
- checkout:
force-clean-build: 'false'
- script:
interpreter: SHELL
scripts:
- |-
image_name=${bamboo.appname}
image_tag=0.${bamboo.buildNumber}
npm install
npm run build
cat <<EOF > default.conf
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 /html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
EOF
cat <<EOF > Dockerfile
FROM nginx:alpine
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
COPY dist html
COPY default.conf /etc/nginx/conf.d/default.conf
EOF
pwd && ls -l
# Get acr password from keyvault
tenant_id=${bamboo.tenant_id}
client_id=${bamboo.client_id}
client_secret=${bamboo.client_secret}
jwt=`curl --location --request POST "https://login.chinacloudapi.cn/$tenant_id/oauth2/token?api-version=1.0" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "resource=https://vault.azure.cn" \
--data-urlencode "client_id=$client_id" \
--data-urlencode "client_secret=$client_secret" |awk -F 'access_token' '{print $2}'|awk -F '"' '{print $3}'`
acr_password=`curl -k --request GET -H "Content-type: application/json;charset=UTF-8" -H "Authorization: Bearer $jwt" -s https://${bamboo.keyvault}.vault.azure.cn/secrets/acr-passwd?api-version=7.2 |awk -F '"' '{print $4}'`
docker build -t ${bamboo.acr_repo}.azurecr.cn/${image_name}:${image_tag} .
docker login ${bamboo.acr_repo}.azurecr.cn -u ${bamboo.acr_user} -p ${acr_password}
docker push ${bamboo.acr_repo}.azurecr.cn/${image_name}:${image_tag}
variables:
appname: mdmds-frontend

View File

@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@@ -0,0 +1,23 @@
apiVersion: v2
name: mdmds-frontend
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 1.16.0

View File

@@ -0,0 +1,22 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mdmds-backend.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "mdmds-backend.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mdmds-backend.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mdmds-backend.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@@ -0,0 +1,62 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "mdmds-backend.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "mdmds-backend.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "mdmds-backend.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "mdmds-backend.labels" -}}
helm.sh/chart: {{ include "mdmds-backend.chart" . }}
{{ include "mdmds-backend.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "mdmds-backend.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mdmds-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "mdmds-backend.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "mdmds-backend.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,61 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mdmds-backend.fullname" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mdmds-backend.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "mdmds-backend.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "mdmds-backend.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: 80
protocol: TCP
## livenessProbe:
## httpGet:
## path: /
## port: http
## readinessProbe:
## httpGet:
## path: /
## port: http
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@@ -0,0 +1,28 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "mdmds-backend.fullname" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "mdmds-backend.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,48 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "mdmds-backend.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{ $svcName := .backend.service.name }}
{{ $svcPort := .backend.service.port.number }}
pathType: {{ .pathType }}
backend:
service:
name: {{ $svcName }}
port:
number: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "mdmds-backend.fullname" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "mdmds-backend.selectorLabels" . | nindent 4 }}

View File

@@ -0,0 +1,12 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "mdmds-backend.serviceAccountName" . }}
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -0,0 +1,15 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mdmds-backend.fullname" . }}-test-connection"
labels:
{{- include "mdmds-backend.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "mdmds-backend.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@@ -0,0 +1,88 @@
# Default values for mdmds-backend.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: ehrapprepo.azurecr.cn/mdmds-frontend
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/use-regex: "true"
# kubernetes.io/tls-acme: "true"
hosts:
- host: ehrapptest.chinanorth2.cloudapp.chinacloudapi.cn
paths:
- path: /mdmds-frontend/(.*)$
pathType: Prefix
backend:
service:
name: mdmds-frontend
port:
number: 80
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}

25826
mdmds/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

26
mdmds/package.json Normal file
View File

@@ -0,0 +1,26 @@
{
"name": "EHR-POC",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve --open",
"build": "export NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build --report"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"echarts": "^5.1.2",
"element-ui": "^2.15.1",
"keycloak-js": "^15.0.2",
"vue": "^2.6.11",
"vue-router": "^3.2.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.11"
}
}

BIN
mdmds/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

539
mdmds/public/font/demo.css Normal file
View File

@@ -0,0 +1,539 @@
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

View File

@@ -0,0 +1,326 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>iconfont Demo</title>
<link rel="shortcut icon" href="//img.alicdn.com/imgextra/i2/O1CN01ZyAlrn1MwaMhqz36G_!!6000000001499-73-tps-64-64.ico" type="image/x-icon"/>
<link rel="icon" type="image/svg+xml" href="//img.alicdn.com/imgextra/i4/O1CN01EYTRnJ297D6vehehJ_!!6000000008020-55-tps-64-64.svg"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
<style>
.main .logo {
margin-top: 0;
height: auto;
}
.main .logo a {
display: flex;
align-items: center;
}
.main .logo .sub-title {
margin-left: 0.5em;
font-size: 22px;
color: #fff;
background: linear-gradient(-45deg, #3967FF, #B500FE);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
</style>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">
<img width="200" src="https://img.alicdn.com/imgextra/i3/O1CN01Mn65HV1FfSEzR6DKv_!!6000000000514-55-tps-228-59.svg">
</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=2894474" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe650;</span>
<div class="name">user-image</div>
<div class="code-name">&amp;#xe650;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe64f;</span>
<div class="name">退出 (4)</div>
<div class="code-name">&amp;#xe64f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe62f;</span>
<div class="name">购物车_1</div>
<div class="code-name">&amp;#xe62f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe61f;</span>
<div class="name">消息</div>
<div class="code-name">&amp;#xe61f;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60d;</span>
<div class="name">成功</div>
<div class="code-name">&amp;#xe60d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe635;</span>
<div class="name">下拉</div>
<div class="code-name">&amp;#xe635;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>默认情况下不支持多色,直接添加多色图标会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持两种方式引用多色图标SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.woff2?t=1636961535521') format('woff2'),
url('iconfont.woff?t=1636961535521') format('woff'),
url('iconfont.ttf?t=1636961535521') format('truetype');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-user"></span>
<div class="name">
user-image
</div>
<div class="code-name">.icon-user
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-tuichu"></span>
<div class="name">
退出 (4)
</div>
<div class="code-name">.icon-tuichu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-car"></span>
<div class="name">
购物车_1
</div>
<div class="code-name">.icon-car
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiaoxi"></span>
<div class="name">
消息
</div>
<div class="code-name">.icon-xiaoxi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-chenggong"></span>
<div class="name">
成功
</div>
<div class="code-name">.icon-chenggong
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-xiala"></span>
<div class="name">
下拉
</div>
<div class="code-name">.icon-xiala
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-user"></use>
</svg>
<div class="name">user-image</div>
<div class="code-name">#icon-user</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-tuichu"></use>
</svg>
<div class="name">退出 (4)</div>
<div class="code-name">#icon-tuichu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-car"></use>
</svg>
<div class="name">购物车_1</div>
<div class="code-name">#icon-car</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiaoxi"></use>
</svg>
<div class="name">消息</div>
<div class="code-name">#icon-xiaoxi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-chenggong"></use>
</svg>
<div class="name">成功</div>
<div class="code-name">#icon-chenggong</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-xiala"></use>
</svg>
<div class="name">下拉</div>
<div class="code-name">#icon-xiala</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>

View File

@@ -0,0 +1,83 @@
@font-face {
font-family: "iconfont"; /* Project id 2998284 */
src: url('iconfont.woff2?t=1639552590060') format('woff2'),
url('iconfont.woff?t=1639552590060') format('woff'),
url('iconfont.ttf?t=1639552590060') format('truetype');
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-tuichudenglu:before {
content: "\e608";
}
.icon-edit:before {
content: "\e606";
}
.icon-guanlian:before {
content: "\e639";
}
.icon-changyonggoupiaorenshanchu:before {
content: "\e645";
}
.icon-gongdan:before {
content: "\ec37";
}
.icon-fasong:before {
content: "\e62f";
}
.icon-tianjiadaozuhe:before {
content: "\e700";
}
.icon-bianji:before {
content: "\e8ac";
}
.icon-sousuo:before {
content: "\e600";
}
.icon-tianjiaqunzu:before {
content: "\e63e";
}
.icon-xinzengruku:before {
content: "\e616";
}
.icon-shanchu:before {
content: "\ec7b";
}
.icon-zhongzhi:before {
content: "\e657";
}
.icon-view:before {
content: "\e6ad";
}
.icon-sharpicons_tickets:before {
content: "\e831";
}
.icon-xinzenghuopin:before {
content: "\e622";
}
.icon-gengxin:before {
content: "\e7cb";
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,128 @@
{
"id": "2998284",
"name": "mdmds",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "4735599",
"name": "退出登录",
"font_class": "tuichudenglu",
"unicode": "e608",
"unicode_decimal": 58888
},
{
"icon_id": "1327475",
"name": "edit",
"font_class": "edit",
"unicode": "e606",
"unicode_decimal": 58886
},
{
"icon_id": "2077526",
"name": "关联",
"font_class": "guanlian",
"unicode": "e639",
"unicode_decimal": 58937
},
{
"icon_id": "2892818",
"name": "删除",
"font_class": "changyonggoupiaorenshanchu",
"unicode": "e645",
"unicode_decimal": 58949
},
{
"icon_id": "5769243",
"name": "工单",
"font_class": "gongdan",
"unicode": "ec37",
"unicode_decimal": 60471
},
{
"icon_id": "7048499",
"name": "发送",
"font_class": "fasong",
"unicode": "e62f",
"unicode_decimal": 58927
},
{
"icon_id": "8412375",
"name": "添加到组合",
"font_class": "tianjiadaozuhe",
"unicode": "e700",
"unicode_decimal": 59136
},
{
"icon_id": "11372640",
"name": "编辑",
"font_class": "bianji",
"unicode": "e8ac",
"unicode_decimal": 59564
},
{
"icon_id": "76992",
"name": "搜索",
"font_class": "sousuo",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "963254",
"name": "add Groups",
"font_class": "tianjiaqunzu",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "1122226",
"name": "新增入库",
"font_class": "xinzengruku",
"unicode": "e616",
"unicode_decimal": 58902
},
{
"icon_id": "6061533",
"name": "删除",
"font_class": "shanchu",
"unicode": "ec7b",
"unicode_decimal": 60539
},
{
"icon_id": "8171733",
"name": "重置",
"font_class": "zhongzhi",
"unicode": "e657",
"unicode_decimal": 58967
},
{
"icon_id": "10140082",
"name": "view",
"font_class": "view",
"unicode": "e6ad",
"unicode_decimal": 59053
},
{
"icon_id": "10574000",
"name": "tickets",
"font_class": "sharpicons_tickets",
"unicode": "e831",
"unicode_decimal": 59441
},
{
"icon_id": "11641896",
"name": "新增货品",
"font_class": "xinzenghuopin",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "25537526",
"name": "更新",
"font_class": "gengxin",
"unicode": "e7cb",
"unicode_decimal": 59339
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

19
mdmds/public/index.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="">
<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="stylesheet" href="./font/iconfont.css">
<!-- <link rel="stylesheet" href="//at.alicdn.com/t/font_2894474_cuqmmq434d.css">-->
<title><%= htmlWebpackPlugin.options.title %></title>
</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>

35
mdmds/src/App.vue Normal file
View File

@@ -0,0 +1,35 @@
<template>
<div id="app">
<router-view/>
</div>
</template>
<style lang="scss">
html, body, div, object, iframe, applet, object, h1, h2, h3, h4, h5, h6, p, blockquote, pre, address, dl, dt, dd, ol, ul, li, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, menu, nav, output, ruby, section, summary, time, mark, audio, video, progress, span {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
margin: 0;
padding: 0;
border: 0;
font-size: 16px;
vertical-align: baseline;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
li {
list-style: none;
}
a {
background-color: transparent;
text-decoration: none;
}
body .el-table th.gutter{
display: table-cell!important;
}
.el-table-column--selection .cell {
padding-left: 10px !important;
}
</style>

55
mdmds/src/api/vehicle.js Normal file
View File

@@ -0,0 +1,55 @@
import { GET, POST, Delete } from "@/utils/request";
const GetModel = () => {
return GET("/vehicle/getModel");
};
const GetVehicleList = (params) => {
return POST("/vehicle/getVehicleList", {}, true, params);
};
const PostcreateGroup = (data) => {
return POST("/group/createGroup", data, true);
};
const PostaddTicket = (data) => {
return POST("/issueTicket/addTicket/" + data.ticketName, data, true);
};
const GetTickets = (params) => {
return GET("/issueTicket/getTickets", params);
};
const GetTicketDetial = (params) => {
return GET(`/issueTicket/getTicket/${params.ticketNo}`, params);
};
const DeleteTickets = (params) => {
return Delete("/issueTicket/delTicket", params);
};
const GetGroups = (params) => {
return GET("/group/getGroups", params);
};
const DeleteGroup = (params) => {
return Delete(`/group/deleteGroup/${params.groupId}`, params);
};
const GetGroupsDetail = (params) => {
return GET(`/group/getGroup/${params.groupId}`, params);
};
export {
GetModel,
GetVehicleList,
PostcreateGroup,
PostaddTicket,
GetTickets,
GetTicketDetial,
DeleteTickets,
GetGroups,
DeleteGroup,
GetGroupsDetail
};

View File

@@ -0,0 +1,334 @@
@font-face {
font-family: "VWAGTheSans_Regular";
src: url("../fonts/VWAGTheSans_Regular.ttf");
}
@font-face {
font-family: "VWAGTheSans_Bold";
src: url("../fonts/VWAGTheSans_Bold.ttf");
}
html,
body,
div,
object,
iframe,
applet,
object,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
address,
dl,
dt,
dd,
ol,
ul,
li,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video,
progress {
font-family: VWAGTheSans_Regular !important;
margin: 0;
padding: 0;
border: 0;
vertical-align: baseline;
box-sizing: border-box;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
.content {
height: 100%;
padding: 20px 35px;
background: #f1f1f1;
overflow: auto;
.search {
padding: 20px;
margin-bottom: 20px;
background: #fff;
box-shadow: 4px 4px 6px 2px rgba(0, 0, 0, 0.03);
}
.table-wrapper {
padding: 30px;
background: #fff;
box-shadow: 4px 4px 6px 2px rgba(0, 0, 0, 0.03);
.title {
display: flex;
margin-bottom: 20px;
justify-content: space-between;
h6 {
font-family: VWAGTheSans_Bold !important;
font-size: 16px;
color: #333;
line-height: 36px;
}
}
}
}
span {
font-size: 14px;
}
.el-table {
border: 1px solid #ebeef5;
thead {
th {
padding: 12px 0;
color: #333;
&:first-child {
padding-left: 15px;
}
.cell {
font-family: VWAGTheSans_Bold !important;
font-size: 14px !important;
line-height: 20px !important;
}
}
}
tbody {
tr {
td {
padding: 12px 0;
color: #4a4a4c;
&:first-child {
padding-left: 15px;
}
.cell {
font-size: 14px !important;
.red-circle {
display: inline-block;
width: 18px;
height: 18px;
font-size: 12px;
color: #fff;
line-height: 18px;
text-align: center;
background: #e11508;
border-radius: 50%;
}
.table-row-btn {
display: inline-block;
margin-right: 20px;
font-size: 14px !important;
color: #0241a5;
cursor: pointer;
}
}
}
&:nth-child(2n) {
background: #f5f5f5;
}
}
}
}
.el-pagination {
padding: 15px 0 0;
text-align: right;
margin-right: -5px;
.el-pagination__sizes{
.el-input__inner{
height: 28px !important;;
line-height: 26px !important;;
}
}
.el-pager {
.number {
background: #fff !important;
border: 1px solid #e4e4e4;
&:hover {
color: #0241a5 !important;
border: 1px solid #0241a5 !important;
}
}
.active {
color: #0241a5 !important;
border: 1px solid #0241a5 !important;
}
}
.btn-prev,
.btn-next {
background: #fff !important;
}
}
.el-row {
width: 100%;
}
.el-dialog__header {
padding: 30px 40px;
}
.el-dialog__title {
font-family: VWAGTheSans_Bold !important;
font-size: 20px;
}
.el-dialog__body {
padding: 0 40px;
}
.el-dialog__footer {
padding: 20px 40px 30px;
}
.el-button {
span {
font-size: 14px;
}
.iconfont {
margin-right: 5px;
font-size: 20px;
vertical-align: middle;
}
}
.el-input__inner {
height: 36px !important;
line-height: 36px !important;
}
.el-input__icon {
line-height: 36px !important;
}
.el-select {
width: 100%;
}
.el-message-box {
width: 500px;
}
.row-margin {
margin-bottom: 20px;
}
.lable-title {
margin-right: 20px;
color: #646464;
text-align: right;
}
.btn-group {
text-align: right;
}
.el-tabs__content {
overflow: visible !important;
}
.timers {
display: flex;
align-items: center;
justify-content: space-between;
.el-date-editor.el-input,
.el-date-editor.el-input__inner {
width: 100%;
}
}
.dialog-content {
.detail {
padding: 20px 50px 5px;
margin-bottom: 20px;
background: #f5f5f5;
border-radius: 3px;
p {
margin-bottom: 15px;
font-size: 14px;
color: #353434;
span {
display: inline-block;
width: 130px;
font-size: 14px;
color: #646464;
}
}
}
.form-item {
display: flex;
margin-bottom: 20px;
label {
width: 110px;
font-size: 15px;
line-height: 36px;
}
.el-input,
.el-textarea {
flex: 1;
}
}
.el-checkbox {
padding-left: 110px;
}
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-row-center {
display: flex;
align-items: center;
flex-direction: row;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-column-center {
display: flex;
align-items: center;
flex-direction: column;
}
.cursor {
cursor: pointer;
}
.table-acion {
margin-right: 10px;
padding: 5px;
}

View File

@@ -0,0 +1,6 @@
/* 改变主题色变量 */
//$--color-primary: #08427E;
$--color-primary: #0B2C55;
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";

View File

@@ -0,0 +1,21 @@
.flex-layout {
display: flex;
.table-wrapper {
flex: 1;
}
.chart-wrapper {
width: 520px;
padding-left: 20px;
.chart {
padding: 30px;
background: #fff;
box-shadow: 4px 4px 6px 2px rgba(0, 0, 0, 0.03);
h6 {
font-size: 16px;
color: #333;
line-height: 36px;
}
}
}
}

View File

@@ -0,0 +1,82 @@
.layout {
.header {
display: flex;
height: 74px;
padding: 15px 35px;
justify-content: space-between;
.logout {
line-height: 50px;
color: #0A1629;
span {
display: inline-block;
margin: 0 20px 0 5px;
font-size: 15px;
cursor: pointer;
}
i {
display: inline-block;
font-size: 22px;
vertical-align: top;
cursor: pointer;
}
.icon-user {
margin-left: 20px;
font-size: 24px;
color: #999;
}
}
}
.menus {
.el-menu {
border: none !important;
.el-menu-item {
height: 66px;
padding: 0 0 0 35px;
line-height: 66px;
font-size: 17px;
color: rgba(255, 255, 255, 0.5) !important;
background: transparent !important;
&:hover {
background: transparent !important;
}
}
.is-active {
color: #fff !important;
border-bottom-color: transparent !important;
background: transparent !important;
&:after {
display: none;
}
}
}
}
.main {
height: calc(100vh - 140px);
}
}
.popper-class {
width: 292px;
top: 95px !important;
.el-menu {
.el-menu-item {
padding: 0 20px;
height: 50px;
line-height: 50px;
font-size: 18px;
}
.is-active {
background: #063565 !important;
}
}
}

View File

@@ -0,0 +1,19 @@
.login {
display: flex;
height: 100vh;
justify-content: center;
align-items: center;
.inner {
width: 37%;
min-width: 500px;
max-width: 700px;
margin-top: -10vh;
h3 {
margin-bottom: 20px;
font-size: 28px;
color: #454545;
font-weight: 500;
text-align: center;
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -0,0 +1,106 @@
<template>
<div class="layout">
<div class="header">
<div class="logo">
<img class="logo" src="../../assets/imgs/logo.png" alt="logo" />
</div>
<div class="logout">
<i class="iconfont icon-xiaoxi"></i>
<i class="iconfont icon-user"></i>
<span>{{ userName }}</span>
<i class="icon iconfont icon-tuichudenglu" @click="logout()"></i>
</div>
</div>
<div class="menus">
<el-menu
:default-active="currentMenu"
mode="horizontal"
background-color="#0B2C55"
text-color="#fff"
active-text-color="#fff"
@select="handleSelect"
>
<el-menu-item
index="1"
@click="$router.push({ path: '/vehicleManagement' })"
>Vehicle Management</el-menu-item
>
<el-menu-item
index="2"
@click="$router.push({ path: '/groupManagement' })"
>Group Management</el-menu-item
>
<el-menu-item
index="4"
@click="$router.push({ path: '/configurationManagement' })"
>Configuration Management</el-menu-item
>
<el-menu-item index="9" @click="$router.push({ path: '/permissionManagement' })"
>Permission</el-menu-item
>
<el-menu-item index="10" @click="$router.push({ path: '/datasourceManagement' })"
>Data Source Management</el-menu-item
>
</el-menu>
<el-menu-item
index="3"
@click="$router.push({ path: '/issueManagement' })"
>Issue Management</el-menu-item
>
<!-- <el-menu-item index="4" @click="$router.push({ path: '/dateTask' })"
>Date Task</el-menu-item
> -->
<!-- <el-menu-item index="5" @click="$router.push({ path: '/sendCommand' })"
>Send Command</el-menu-item
> -->
<!-- <el-menu-item index="7">Event Logs</el-menu-item>
<el-menu-item index="8">Audit</el-menu-item> -->
</div>
<div class="main">
<router-view />
</div>
</div>
</template>
<script>
import { removeToken, removeCookie } from "@/utils/auth";
export default {
name: "Layout",
data() {
return {
currentMenu: "1",
userName: "Evan Yates",
email: "No email!",
};
},
created() {
// this.getUserName();
this.getCurrentMenu();
},
mounted() {
this.userName = this.$keycloak.tokenParsed.given_name;
},
methods: {
getUserName() {
const userInfo = localStorage.getItem("user");
if (userInfo) {
this.userName = JSON.parse(userInfo)["username"] || "Visitor";
this.email = JSON.parse(userInfo)["email"] || "No email!";
}
},
logout() {
this.$keycloak.logout();
},
getCurrentMenu() {
const currentMenu = sessionStorage.getItem("currentMenu");
this.currentMenu = currentMenu ? currentMenu : this.currentMenu;
},
handleSelect(key) {
sessionStorage.setItem("currentMenu", key);
},
},
};
</script>
<style lang="scss" src="../../assets/css/layout.scss" />

117
mdmds/src/main.js Normal file
View File

@@ -0,0 +1,117 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import 'element-ui/lib/theme-chalk/index.css';
import './assets/css/element-variables.scss'
import './assets/css/common.scss'
import echarts from './utils/echartsUi'
import Keycloak from './utils/keycloak/keycloak'
// import Keycloak from 'keycloak-js'
import {
Menu,
Submenu,
MenuItem,
Table,
TableColumn,
Input,
Button,
Pagination,
DatePicker,
Row,
Col,
Select,
Option,
Dialog,
Message,
MessageBox,
Tabs,
TabPane,
Tooltip,
Upload,
Checkbox,
CheckboxGroup,
Popover,
Tag,
Popconfirm,
Alert,
Form,
FormItem
} from 'element-ui'
import * as filters from './utils/filters.js'
Object.keys(filters).forEach(key=>{
Vue.filter(key,filters[key])//插入过滤器名和对应方法
})
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Tabs);
Vue.use(TabPane);
Vue.use(Dialog);
Vue.use(Menu)
Vue.use(Submenu)
Vue.use(MenuItem)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Input)
Vue.use(Button)
Vue.use(Pagination)
Vue.use(DatePicker)
Vue.use(Row);
Vue.use(Col);
Vue.use(Select);
Vue.use(Option);
Vue.use(Tooltip);
Vue.use(Upload);
Vue.use(Checkbox);
Vue.use(CheckboxGroup);
Vue.use(Popover);
Vue.use(Tag);
Vue.use(Popconfirm);
Vue.use(Alert);
Vue.config.productionTip = false
Vue.prototype.$message = Message;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$echarts = echarts;
// keycloak init options
// const initOptions = {
// url: 'https://idp.qs.cloudidp.vgcserv.com.cn/auth',
// realm: 'vgcmfa',
// clientId: 'EHR-FRONT-QS',
// onLoad:'login-required'
// }
const initOptions = {
url: 'http://139.217.94.179:8080/auth',
realm: 'demo',
clientId: 'vue-demo',
onLoad:'login-required'
}
new Vue({
router,
render: h => h(App)
}).$mount('#app')
// const keycloak = Keycloak(initOptions);
// keycloak.init({ onLoad: initOptions.onLoad,pkceMethod:'S256', promiseType: 'native' }).then((authenticated) =>{
// if(!authenticated) {
// window.location.reload();
// } else {
// Vue.prototype.$keycloak = keycloak;
// }
// let token = "Bearer "+ keycloak.token
// localStorage.setItem('token', token)
// setTimeout(() => {
// }, 2000);
// }).catch(error => {
// console.log('Authenticated Failed', error)
// })

80
mdmds/src/router/index.js Normal file
View File

@@ -0,0 +1,80 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index')
},
{
path: '/',
name: 'Layout',
component: () => import('@/components/layout/index'),
children: [{
path: 'vehicleManagement',
component: () => import('@/views/vehicleManagement')
}, {
path: 'groupManagement',
component: () => import('@/views/groupManagement')
}, {
path: 'groupManagement/detail',
component: () => import('@/views/groupManagement/detail')
}, {
path: 'issueManagement',
component: () => import('@/views/issueManagement')
}, {
path: 'issueManagement/detail',
component: () => import('@/views/issueManagement/detail')
}, {
path: 'dateTask',
component: () => import('@/views/dateTask')
}, {
path: 'sendCommand',
component: () => import('@/views/sendCommand')
}, {
path: 'configurationManagement',
component: () => import('@/views/configurationManagement')
},
{
path: 'permission',
component: () => import('@/views/permission'),
redirect:'/user',
children:[
{
path: '/user',
component: () => import('@/views/userList'),
},{
path: '/role',
component: () => import('@/views/roleList'),
},{
path: '/roleDetail',
component: () => import('@/views/roleDetail'),
}
],
},
{
path: '*',
redirect: '/vehicleManagement'
}, {
path: '/',
redirect: '/vehicleManagement'
}]
}
]
const router = new VueRouter({
mode: 'hash',
base: process.env.BASE_URL,
routes
})
// 解决ElementUI导航栏中的vue-router在3.0版本以上重复点菜单报错问题
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
export default router

19
mdmds/src/utils/auth.js Normal file
View File

@@ -0,0 +1,19 @@
const TokenKey = 'token'
export function getToken() {
return localStorage.getItem(TokenKey);
}
export function setToken(token) {
return localStorage.setItem(TokenKey, token);
}
export function removeToken() {
return localStorage.clear();
}
export function removeCookie () {
var date = new Date();
date.setTime(date.getTime() + 24*60*60*1000*-1);
window.document.cookie = "JSESSIONID=;path=/;expires="+date.toGMTString();
}

View File

@@ -0,0 +1,27 @@
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from "echarts/core";
// 引入各种图表,图表后缀都为 Chart
import { BarChart, LineChart, PieChart } from "echarts/charts"; //这里我引用两个类型的图表
// 引入提示框,标题,直角坐标系等组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent
} from "echarts/components";
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from "echarts/renderers";
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
BarChart,
LineChart,
PieChart,
CanvasRenderer,
]);
export default echarts;

148
mdmds/src/utils/filters.js Normal file
View File

@@ -0,0 +1,148 @@
// 数字添加千分号
const formatNum = function(n) {
let num1 = n.toString(),
num2 = num1;
let len = num1.length;
// 判断是否有小数
if (num1.indexOf("-") > -1 && num1.indexOf(".") === -1) {
len = num1.split("-")[1].length;
num2 = num1.split("-")[1];
if (!num2) return num1;
if (len <= 3) {
return num1;
} else {
let remainder = len % 3;
if (remainder > 0) {
// 不是3的整数倍
return (
"-" +
num2.slice(0, remainder) +
"," +
num2
.slice(remainder, len)
.match(/\d{3}/g)
.join(",")
);
} else {
// 是3的整数倍
return (
"-" +
num2
.slice(0, len)
.match(/\d{3}/g)
.join(",")
);
}
}
} else if (num1.indexOf("-") === -1 && num1.indexOf(".") > -1) {
len = num1.split(".")[0].length;
let decimals = "." + num1.split(".")[1];
if (len <= 3) {
return num1;
} else {
let remainder = len % 3;
if (remainder > 0) {
// 不是3的整数倍
return (
num1.slice(0, remainder) +
"," +
num1
.slice(remainder, len)
.match(/\d{3}/g)
.join(",") +
decimals
);
} else {
// 是3的整数倍
return (
num1
.slice(0, len)
.match(/\d{3}/g)
.join(",") + decimals
);
}
}
} else if (num1.indexOf("-") > -1 && num1.indexOf(".") > -1) {
len = num1.split("-")[1].split(".")[0].length;
let decimals = "." + num1.split(".")[1];
num2 = num1.split("-")[1];
if (len <= 3) {
return num1;
} else {
let remainder = len % 3;
if (remainder > 0) {
// 不是3的整数倍
return (
"-" +
num2.slice(0, remainder) +
"," +
num2
.slice(remainder, len)
.match(/\d{3}/g)
.join(",") +
decimals
);
} else {
// 是3的整数倍
return (
"-" +
num2
.slice(0, len)
.match(/\d{3}/g)
.join(",") +
decimals
);
}
}
} else {
if (len <= 3) {
return num1;
} else {
let remainder = len % 3;
if (remainder > 0) {
// 不是3的整数倍
return (
num1.slice(0, remainder) +
"," +
num1
.slice(remainder, len)
.match(/\d{3}/g)
.join(",")
);
} else {
// 是3的整数倍
return num1
.slice(0, len)
.match(/\d{3}/g)
.join(",");
}
}
}
};
const formatTime = function(date, fmt) {
if (!fmt) {
fmt = "YYYY-mm-dd HH:MM";
}
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"H+": date.getHours().toString(), // 时
"M+": date.getMinutes().toString(), // 分
"S+": date.getSeconds().toString(), // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(
ret[1],
ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
);
}
}
return fmt
};
export { formatNum, formatTime };

View File

@@ -0,0 +1 @@
(function(r){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=r()}else if(typeof define==="function"&&define.amd){define([],r)}else{var e;if(typeof window!=="undefined"){e=window}else if(typeof global!=="undefined"){e=global}else if(typeof self!=="undefined"){e=self}else{e=this}e.base64js=r()}})(function(){var r,e,n;return function(){function d(a,f,i){function u(n,r){if(!f[n]){if(!a[n]){var e="function"==typeof require&&require;if(!r&&e)return e(n,!0);if(v)return v(n,!0);var t=new Error("Cannot find module '"+n+"'");throw t.code="MODULE_NOT_FOUND",t}var o=f[n]={exports:{}};a[n][0].call(o.exports,function(r){var e=a[n][1][r];return u(e||r)},o,o.exports,d,a,f,i)}return f[n].exports}for(var v="function"==typeof require&&require,r=0;r<i.length;r++)u(i[r]);return u}return d}()({"/":[function(r,e,n){"use strict";n.byteLength=f;n.toByteArray=i;n.fromByteArray=p;var u=[];var v=[];var d=typeof Uint8Array!=="undefined"?Uint8Array:Array;var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";for(var o=0,a=t.length;o<a;++o){u[o]=t[o];v[t.charCodeAt(o)]=o}v["-".charCodeAt(0)]=62;v["_".charCodeAt(0)]=63;function c(r){var e=r.length;if(e%4>0){throw new Error("Invalid string. Length must be a multiple of 4")}var n=r.indexOf("=");if(n===-1)n=e;var t=n===e?0:4-n%4;return[n,t]}function f(r){var e=c(r);var n=e[0];var t=e[1];return(n+t)*3/4-t}function h(r,e,n){return(e+n)*3/4-n}function i(r){var e;var n=c(r);var t=n[0];var o=n[1];var a=new d(h(r,t,o));var f=0;var i=o>0?t-4:t;var u;for(u=0;u<i;u+=4){e=v[r.charCodeAt(u)]<<18|v[r.charCodeAt(u+1)]<<12|v[r.charCodeAt(u+2)]<<6|v[r.charCodeAt(u+3)];a[f++]=e>>16&255;a[f++]=e>>8&255;a[f++]=e&255}if(o===2){e=v[r.charCodeAt(u)]<<2|v[r.charCodeAt(u+1)]>>4;a[f++]=e&255}if(o===1){e=v[r.charCodeAt(u)]<<10|v[r.charCodeAt(u+1)]<<4|v[r.charCodeAt(u+2)]>>2;a[f++]=e>>8&255;a[f++]=e&255}return a}function s(r){return u[r>>18&63]+u[r>>12&63]+u[r>>6&63]+u[r&63]}function l(r,e,n){var t;var o=[];for(var a=e;a<n;a+=3){t=(r[a]<<16&16711680)+(r[a+1]<<8&65280)+(r[a+2]&255);o.push(s(t))}return o.join("")}function p(r){var e;var n=r.length;var t=n%3;var o=[];var a=16383;for(var f=0,i=n-t;f<i;f+=a){o.push(l(r,f,f+a>i?i:f+a))}if(t===1){e=r[n-1];o.push(u[e>>2]+u[e<<4&63]+"==")}else if(t===2){e=(r[n-2]<<8)+r[n-1];o.push(u[e>>10]+u[e>>4&63]+u[e<<2&63]+"=")}return o.join("")}},{}]},{},[])("/")});

View File

@@ -0,0 +1,152 @@
'use strict'
exports.byteLength = byteLength
exports.toByteArray = toByteArray
exports.fromByteArray = fromByteArray
var lookup = []
var revLookup = []
var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array
var code = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
for (var i = 0, len = code.length; i < len; ++i) {
lookup[i] = code[i]
revLookup[code.charCodeAt(i)] = i
}
// Support decoding URL-safe base64 strings, as Node.js does.
// See: https://en.wikipedia.org/wiki/Base64#URL_applications
revLookup['-'.charCodeAt(0)] = 62
revLookup['_'.charCodeAt(0)] = 63
function getLens (b64) {
var len = b64.length
if (len % 4 > 0) {
throw new Error('Invalid string. Length must be a multiple of 4')
}
// Trim off extra bytes after placeholder bytes are found
// See: https://github.com/beatgammit/base64-js/issues/42
var validLen = b64.indexOf('=')
if (validLen === -1) validLen = len
var placeHoldersLen = validLen === len
? 0
: 4 - (validLen % 4)
return [validLen, placeHoldersLen]
}
// base64 is 4/3 + up to two characters of the original data
function byteLength (b64) {
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
function _byteLength (b64, validLen, placeHoldersLen) {
return ((validLen + placeHoldersLen) * 3 / 4) - placeHoldersLen
}
function toByteArray (b64) {
var tmp
var lens = getLens(b64)
var validLen = lens[0]
var placeHoldersLen = lens[1]
var arr = new Arr(_byteLength(b64, validLen, placeHoldersLen))
var curByte = 0
// if there are placeholders, only get up to the last complete 4 chars
var len = placeHoldersLen > 0
? validLen - 4
: validLen
var i
for (i = 0; i < len; i += 4) {
tmp =
(revLookup[b64.charCodeAt(i)] << 18) |
(revLookup[b64.charCodeAt(i + 1)] << 12) |
(revLookup[b64.charCodeAt(i + 2)] << 6) |
revLookup[b64.charCodeAt(i + 3)]
arr[curByte++] = (tmp >> 16) & 0xFF
arr[curByte++] = (tmp >> 8) & 0xFF
arr[curByte++] = tmp & 0xFF
}
if (placeHoldersLen === 2) {
tmp =
(revLookup[b64.charCodeAt(i)] << 2) |
(revLookup[b64.charCodeAt(i + 1)] >> 4)
arr[curByte++] = tmp & 0xFF
}
if (placeHoldersLen === 1) {
tmp =
(revLookup[b64.charCodeAt(i)] << 10) |
(revLookup[b64.charCodeAt(i + 1)] << 4) |
(revLookup[b64.charCodeAt(i + 2)] >> 2)
arr[curByte++] = (tmp >> 8) & 0xFF
arr[curByte++] = tmp & 0xFF
}
return arr
}
function tripletToBase64 (num) {
return lookup[num >> 18 & 0x3F] +
lookup[num >> 12 & 0x3F] +
lookup[num >> 6 & 0x3F] +
lookup[num & 0x3F]
}
function encodeChunk (uint8, start, end) {
var tmp
var output = []
for (var i = start; i < end; i += 3) {
tmp =
((uint8[i] << 16) & 0xFF0000) +
((uint8[i + 1] << 8) & 0xFF00) +
(uint8[i + 2] & 0xFF)
output.push(tripletToBase64(tmp))
}
return output.join('')
}
function fromByteArray (uint8) {
var tmp
var len = uint8.length
var extraBytes = len % 3 // if we have 1 byte left, pad 2 bytes
var parts = []
var maxChunkLength = 16383 // must be multiple of 3
// go through the array every three bytes, we'll deal with trailing stuff later
for (var i = 0, len2 = len - extraBytes; i < len2; i += maxChunkLength) {
parts.push(encodeChunk(
uint8, i, (i + maxChunkLength) > len2 ? len2 : (i + maxChunkLength)
))
}
// pad the end with zeros, but make sure to not forget the extra bytes
if (extraBytes === 1) {
tmp = uint8[len - 1]
parts.push(
lookup[tmp >> 2] +
lookup[(tmp << 4) & 0x3F] +
'=='
)
} else if (extraBytes === 2) {
tmp = (uint8[len - 2] << 8) + uint8[len - 1]
parts.push(
lookup[tmp >> 10] +
lookup[(tmp >> 4) & 0x3F] +
lookup[(tmp << 2) & 0x3F] +
'='
)
}
return parts.join('')
}

View File

@@ -0,0 +1,122 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permissionManagement notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import * as Keycloak from './keycloak';
export as namespace KeycloakAuthorization;
export = KeycloakAuthorization;
/**
* Creates a new Keycloak client instance.
* @param config Path to a JSON config file or a plain config object.
*/
declare function KeycloakAuthorization(keycloak: Keycloak.KeycloakInstance): KeycloakAuthorization.KeycloakAuthorizationInstance;
declare namespace KeycloakAuthorization {
interface KeycloakAuthorizationPromise {
then(onGrant: (rpt: string) => void, onDeny: () => void, onError: () => void): void;
}
interface AuthorizationRequest {
/**
* An array of objects representing the resource and scopes.
*/
permissions?:ResourcePermission[],
/**
* A permissionManagement ticket obtained from a resource server when using UMA authorization protocol.
*/
ticket?:string,
/**
* A boolean value indicating whether the server should create permissionManagement requests to the resources
* and scopes referenced by a permissionManagement ticket. This parameter will only take effect when used together
* with the ticket parameter as part of a UMA authorization process.
*/
submitRequest?:boolean,
/**
* Defines additional information about this authorization request in order to specify how it should be processed
* by the server.
*/
metadata?:AuthorizationRequestMetadata,
/**
* Defines whether or not this authorization request should include the current RPT. If set to true, the RPT will
* be sent and permissions in the current RPT will be included in the new RPT. Otherwise, only the permissions referenced in this
* authorization request will be granted in the new RPT.
*/
incrementalAuthorization?:boolean
}
interface AuthorizationRequestMetadata {
/**
* A boolean value indicating to the server if resource names should be included in the RPTs permissions.
* If false, only the resource identifier is included.
*/
responseIncludeResourceName?:any,
/**
* An integer N that defines a limit for the amount of permissions an RPT can have. When used together with
* rpt parameter, only the last N requested permissions will be kept in the RPT.
*/
response_permissions_limit?:number
}
interface ResourcePermission {
/**
* The id or name of a resource.
*/
id:string,
/**
* An array of strings where each value is the name of a scope associated with the resource.
*/
scopes?:string[]
}
interface KeycloakAuthorizationInstance {
rpt: any;
config: { rpt_endpoint: string };
init(): void;
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer using UMA protocol.
*
* The authorization request must be provided with a ticket.
*
* @param authorizationRequest An AuthorizationRequest instance with a valid permissionManagement ticket set.
* @returns A promise to set functions to be invoked on grant, deny or error.
*/
authorize(authorizationRequest: AuthorizationRequest): KeycloakAuthorizationPromise;
/**
* Obtains all entitlements from a Keycloak server based on a given resourceServerId.
*
* @param resourceServerId The id (client id) of the resource server to obtain permissions from.
* @param authorizationRequest An AuthorizationRequest instance.
* @returns A promise to set functions to be invoked on grant, deny or error.
*/
entitlement(resourceServerId: string, authorizationRequest?: AuthorizationRequest): KeycloakAuthorizationPromise;
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
(function( window, undefined ) {
var KeycloakAuthorization = function (keycloak, options) {
var _instance = this;
this.rpt = null;
var resolve = function () {};
var reject = function () {};
// detects if browser supports promises
if (typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1) {
this.ready = new Promise(function (res, rej) {
resolve = res;
reject = rej;
});
}
this.init = function () {
var request = new XMLHttpRequest();
request.open('GET', keycloak.authServerUrl + '/realms/' + keycloak.realm + '/.well-known/uma2-configuration');
request.onreadystatechange = function () {
if (request.readyState == 4) {
if (request.status == 200) {
_instance.config = JSON.parse(request.responseText);
resolve();
} else {
console.error('Could not obtain configuration from server.');
reject();
}
}
}
request.send(null);
};
/**
* This method enables client applications to better integrate with resource servers protected by a Keycloak
* policy enforcer using UMA protocol.
*
* The authorization request must be provided with a ticket.
*/
this.authorize = function (authorizationRequest) {
this.then = function (onGrant, onDeny, onError) {
if (authorizationRequest && authorizationRequest.ticket) {
var request = new XMLHttpRequest();
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
};
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId + "&ticket=" + authorizationRequest.ticket;
if (authorizationRequest.submitRequest != undefined) {
params += "&submit_request=" + authorizationRequest.submitRequest;
}
var metadata = authorizationRequest.metadata;
if (metadata) {
if (metadata.responseIncludeResourceName) {
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
}
if (metadata.responsePermissionsLimit) {
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
}
}
if (_instance.rpt && (authorizationRequest.incrementalAuthorization == undefined || authorizationRequest.incrementalAuthorization)) {
params += "&rpt=" + _instance.rpt;
}
request.send(params);
}
};
return this;
};
/**
* Obtains all entitlements from a Keycloak Server based on a given resourceServerId.
*/
this.entitlement = function (resourceServerId, authorizationRequest) {
this.then = function (onGrant, onDeny, onError) {
var request = new XMLHttpRequest();
request.open('POST', _instance.config.token_endpoint, true);
request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
request.setRequestHeader('Authorization', 'Bearer ' + keycloak.token);
request.onreadystatechange = function () {
if (request.readyState == 4) {
var status = request.status;
if (status >= 200 && status < 300) {
var rpt = JSON.parse(request.responseText).access_token;
_instance.rpt = rpt;
onGrant(rpt);
} else if (status == 403) {
if (onDeny) {
onDeny();
} else {
console.error('Authorization request was denied by the server.');
}
} else {
if (onError) {
onError();
} else {
console.error('Could not obtain authorization data from server.');
}
}
}
};
if (!authorizationRequest) {
authorizationRequest = {};
}
var params = "grant_type=urn:ietf:params:oauth:grant-type:uma-ticket&client_id=" + keycloak.clientId;
if (authorizationRequest.claimToken) {
params += "&claim_token=" + authorizationRequest.claimToken;
if (authorizationRequest.claimTokenFormat) {
params += "&claim_token_format=" + authorizationRequest.claimTokenFormat;
}
}
params += "&audience=" + resourceServerId;
var permissions = authorizationRequest.permissions;
if (!permissions) {
permissions = [];
}
for (i = 0; i < permissions.length; i++) {
var resource = permissions[i];
var permission = resource.id;
if (resource.scopes && resource.scopes.length > 0) {
permission += "#";
for (j = 0; j < resource.scopes.length; j++) {
var scope = resource.scopes[j];
if (permission.indexOf('#') != permission.length - 1) {
permission += ",";
}
permission += scope;
}
}
params += "&permissionManagement=" + permission;
}
var metadata = authorizationRequest.metadata;
if (metadata) {
if (metadata.responseIncludeResourceName) {
params += "&response_include_resource_name=" + metadata.responseIncludeResourceName;
}
if (metadata.responsePermissionsLimit) {
params += "&response_permissions_limit=" + metadata.responsePermissionsLimit;
}
}
if (_instance.rpt) {
params += "&rpt=" + _instance.rpt;
}
request.send(params);
};
return this;
};
this.init(this);
return this;
};
if ( typeof module === "object" && module && typeof module.exports === "object" ) {
module.exports = KeycloakAuthorization;
} else {
window.KeycloakAuthorization = KeycloakAuthorization;
if ( typeof define === "function" && define.amd ) {
define( "keycloak-authorization", [], function () { return KeycloakAuthorization; } );
}
}
})( window );

562
mdmds/src/utils/keycloak/keycloak.d.ts vendored Normal file
View File

@@ -0,0 +1,562 @@
/*
* MIT License
*
* Copyright 2017 Brett Epps <https://github.com/eppsilon>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permissionManagement notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
export as namespace Keycloak;
export = Keycloak;
/**
* Creates a new Keycloak client instance.
* @param config A configuration object or path to a JSON config file.
*/
declare function Keycloak(config?: Keycloak.KeycloakConfig | string): Keycloak.KeycloakInstance;
declare namespace Keycloak {
type KeycloakAdapterName = 'cordova' | 'cordova-native' |'default' | any;
type KeycloakOnLoad = 'login-required'|'check-sso';
type KeycloakResponseMode = 'query'|'fragment';
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
type KeycloakPkceMethod = 'S256';
interface KeycloakConfig {
/**
* URL to the Keycloak server, for example: http://keycloak-server/auth
*/
url?: string;
/**
* Name of the realm, for example: 'myrealm'
*/
realm: string;
/**
* Client identifier, example: 'myapp'
*/
clientId: string;
}
interface KeycloakInitOptions {
/**
* Adds a [cryptographic nonce](https://en.wikipedia.org/wiki/Cryptographic_nonce)
* to verify that the authentication response matches the request.
* @default true
*/
useNonce?: boolean;
/**
* Allows to use different adapter:
*
* - {string} default - using browser api for redirects
* - {string} cordova - using cordova plugins
* - {function} - allows to provide custom function as adapter.
*/
adapter?: KeycloakAdapterName;
/**
* Specifies an action to do on load.
*/
onLoad?: KeycloakOnLoad;
/**
* Set an initial value for the token.
*/
token?: string;
/**
* Set an initial value for the refresh token.
*/
refreshToken?: string;
/**
* Set an initial value for the id token (only together with `token` or
* `refreshToken`).
*/
idToken?: string;
/**
* Set an initial value for skew between local time and Keycloak server in
* seconds (only together with `token` or `refreshToken`).
*/
timeSkew?: number;
/**
* Set to enable/disable monitoring login state.
* @default true
*/
checkLoginIframe?: boolean;
/**
* Set the interval to check login state (in seconds).
* @default 5
*/
checkLoginIframeInterval?: number;
/**
* Set the OpenID Connect response mode to send to Keycloak upon login.
* @default fragment After successful authentication Keycloak will redirect
* to JavaScript application with OpenID Connect parameters
* added in URL fragment. This is generally safer and
* recommended over query.
*/
responseMode?: KeycloakResponseMode;
/**
* Specifies a default uri to redirect to after login or logout.
* This is currently supported for adapter 'cordova-native' and 'default'
*/
redirectUri?: string;
/**
* Specifies an uri to redirect to after silent check-sso.
* Silent check-sso will only happen, when this redirect uri is given and
* the specified uri is available whithin the application.
*/
silentCheckSsoRedirectUri?: string;
/**
* Set the OpenID Connect flow.
* @default standard
*/
flow?: KeycloakFlow;
/**
* Configures the Proof Key for Code Exchange (PKCE) method to use.
* The currently allowed method is 'S256'.
* If not configured, PKCE will not be used.
*/
pkceMethod?: KeycloakPkceMethod;
/**
* Enables logging messages from Keycloak to the console.
* @default false
*/
enableLogging?: boolean
}
interface KeycloakLoginOptions {
/**
* @private Undocumented.
*/
scope?: string;
/**
* Specifies the uri to redirect to after login.
*/
redirectUri?: string;
/**
* By default the login screen is displayed if the user is not logged into
* Keycloak. To only authenticate to the application if the user is already
* logged in and not display the login page if the user is not logged in, set
* this option to `'none'`. To always require re-authentication and ignore
* SSO, set this option to `'login'`.
*/
prompt?: 'none'|'login';
/**
* If value is `'register'` then user is redirected to registration page,
* otherwise to login page.
*/
action?: string;
/**
* Used just if user is already authenticated. Specifies maximum time since
* the authentication of user happened. If user is already authenticated for
* longer time than `'maxAge'`, the SSO is ignored and he will need to
* authenticate again.
*/
maxAge?: number;
/**
* Used to pre-fill the username/email field on the login form.
*/
loginHint?: string;
/**
* Used to tell Keycloak which IDP the user wants to authenticate with.
*/
idpHint?: string;
/**
* Sets the 'ui_locales' query param in compliance with section 3.1.2.1
* of the OIDC 1.0 specification.
*/
locale?: string;
/**
* Specifies arguments that are passed to the Cordova in-app-browser (if applicable).
* Options 'hidden' and 'location' are not affected by these arguments.
* All available options are defined at https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-inappbrowser/.
* Example of use: { zoom: "no", hardwareback: "yes" }
*/
cordovaOptions?: { [optionName: string]: string };
}
interface KeycloakLogoutOptions {
/**
* Specifies the uri to redirect to after logout.
*/
redirectUri?: string;
}
type KeycloakPromiseCallback<T> = (result: T) => void;
class KeycloakPromise<TSuccess, TError> extends Promise<TSuccess> {
/**
* Function to call if the promised action succeeds.
*
* @deprecated Use `.then()` instead.
*/
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
/**
* Function to call if the promised action throws an error.
*
* @deprecated Use `.catch()` instead.
*/
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
}
interface KeycloakError {
error: string;
error_description: string;
}
interface KeycloakAdapter {
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>;
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
accountManagement(): KeycloakPromise<void, void>;
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
}
interface KeycloakProfile {
id?: string;
username?: string;
email?: string;
firstName?: string;
lastName?: string;
enabled?: boolean;
emailVerified?: boolean;
totp?: boolean;
createdTimestamp?: number;
}
interface KeycloakTokenParsed {
exp?: number;
iat?: number;
nonce?: string;
sub?: string;
session_state?: string;
realm_access?: KeycloakRoles;
resource_access?: KeycloakResourceAccess;
}
interface KeycloakResourceAccess {
[key: string]: KeycloakRoles
}
interface KeycloakRoles {
roles: string[];
}
/**
* A client for the Keycloak authentication server.
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
*/
interface KeycloakInstance {
/**
* Is true if the user is authenticated, false otherwise.
*/
authenticated?: boolean;
/**
* The user id.
*/
subject?: string;
/**
* Response mode passed in init (default value is `'fragment'`).
*/
responseMode?: KeycloakResponseMode;
/**
* Response type sent to Keycloak with login requests. This is determined
* based on the flow value used during initialization, but can be overridden
* by setting this value.
*/
responseType?: KeycloakResponseType;
/**
* Flow passed in init.
*/
flow?: KeycloakFlow;
/**
* The realm roles associated with the token.
*/
realmAccess?: KeycloakRoles;
/**
* The resource roles associated with the token.
*/
resourceAccess?: KeycloakResourceAccess;
/**
* The base64 encoded token that can be sent in the Authorization header in
* requests to services.
*/
token?: string;
/**
* The parsed token as a JavaScript object.
*/
tokenParsed?: KeycloakTokenParsed;
/**
* The base64 encoded refresh token that can be used to retrieve a new token.
*/
refreshToken?: string;
/**
* The parsed refresh token as a JavaScript object.
*/
refreshTokenParsed?: KeycloakTokenParsed;
/**
* The base64 encoded ID token.
*/
idToken?: string;
/**
* The parsed id token as a JavaScript object.
*/
idTokenParsed?: KeycloakTokenParsed;
/**
* The estimated time difference between the browser time and the Keycloak
* server in seconds. This value is just an estimation, but is accurate
* enough when determining if a token is expired or not.
*/
timeSkew?: number;
/**
* @private Undocumented.
*/
loginRequired?: boolean;
/**
* @private Undocumented.
*/
authServerUrl?: string;
/**
* @private Undocumented.
*/
realm?: string;
/**
* @private Undocumented.
*/
clientId?: string;
/**
* @private Undocumented.
*/
clientSecret?: string;
/**
* @private Undocumented.
*/
redirectUri?: string;
/**
* @private Undocumented.
*/
sessionId?: string;
/**
* @private Undocumented.
*/
profile?: KeycloakProfile;
/**
* @private Undocumented.
*/
userInfo?: {}; // KeycloakUserInfo;
/**
* Called when the adapter is initialized.
*/
onReady?(authenticated?: boolean): void;
/**
* Called when a user is successfully authenticated.
*/
onAuthSuccess?(): void;
/**
* Called if there was an error during authentication.
*/
onAuthError?(errorData: KeycloakError): void;
/**
* Called when the token is refreshed.
*/
onAuthRefreshSuccess?(): void;
/**
* Called if there was an error while trying to refresh the token.
*/
onAuthRefreshError?(): void;
/**
* Called if the user is logged out (will only be called if the session
* status iframe is enabled, or in Cordova mode).
*/
onAuthLogout?(): void;
/**
* Called when the access token is expired. If a refresh token is available
* the token can be refreshed with Keycloak#updateToken, or in cases where
* it's not (ie. with implicit flow) you can redirect to login screen to
* obtain a new access token.
*/
onTokenExpired?(): void;
/**
* Called when a AIA has been requested by the application.
*/
onActionUpdate?(status: 'success'|'cancelled'|'error'): void;
/**
* Called to initialize the adapter.
* @param initOptions Initialization options.
* @returns A promise to set functions to be invoked on success or error.
*/
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
/**
* Redirects to login form.
* @param options Login options.
*/
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
/**
* Redirects to logout.
* @param options Logout options.
*/
logout(options?: KeycloakLogoutOptions): KeycloakPromise<void, void>;
/**
* Redirects to registration form.
* @param options Supports same options as Keycloak#login but `action` is
* set to `'register'`.
*/
register(options?: any): KeycloakPromise<void, void>;
/**
* Redirects to the Account Management Console.
*/
accountManagement(): KeycloakPromise<void, void>;
/**
* Returns the URL to login form.
* @param options Supports same options as Keycloak#login.
*/
createLoginUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to logout the user.
* @param options Logout options.
*/
createLogoutUrl(options?: KeycloakLogoutOptions): string;
/**
* Returns the URL to registration page.
* @param options Supports same options as Keycloak#createLoginUrl but
* `action` is set to `'register'`.
*/
createRegisterUrl(options?: KeycloakLoginOptions): string;
/**
* Returns the URL to the Account Management Console.
*/
createAccountUrl(): string;
/**
* Returns true if the token has less than `minValidity` seconds left before
* it expires.
* @param minValidity If not specified, `0` is used.
*/
isTokenExpired(minValidity?: number): boolean;
/**
* If the token expires within `minValidity` seconds, the token is refreshed.
* If the session status iframe is enabled, the session status is also
* checked.
* @returns A promise to set functions that can be invoked if the token is
* still valid, or if the token is no longer valid.
* @example
* ```js
* keycloak.updateToken(5).success(function(refreshed) {
* if (refreshed) {
* alert('Token was successfully refreshed');
* } else {
* alert('Token is still valid');
* }
* }).error(function() {
* alert('Failed to refresh the token, or the session has expired');
* });
*/
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
/**
* Clears authentication state, including tokens. This can be useful if
* the application has detected the session was expired, for example if
* updating token fails. Invoking this results in Keycloak#onAuthLogout
* callback listener being invoked.
*/
clearToken(): void;
/**
* Returns true if the token has the given realm role.
* @param role A realm role name.
*/
hasRealmRole(role: string): boolean;
/**
* Returns true if the token has the given role for the resource.
* @param role A role name.
* @param resource If not specified, `clientId` is used.
*/
hasResourceRole(role: string, resource?: string): boolean;
/**
* Loads the user's profile.
* @returns A promise to set functions to be invoked on success or error.
*/
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
/**
* @private Undocumented.
*/
loadUserInfo(): KeycloakPromise<{}, void>;
}
}

File diff suppressed because one or more lines are too long

217
mdmds/src/utils/request.js Normal file
View File

@@ -0,0 +1,217 @@
import axios from "axios";
import qs from "qs";
import { Message } from "element-ui";
import { getToken, setToken, removeToken } from "@/utils/auth";
axios.defaults.responsetype = "json";
axios.defaults.baseURL = process.env.VUE_APP_BASE_API;
let timeout = 5 * 60 * 1000;
let fileName = "",
type = ""; //下载的fileName, suffix
// 请求时的拦截器
axios.interceptors.request.use(
(config) => {
if (getToken() != undefined) {
config.headers["Authorization"] = getToken();
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 请求完成后的拦截器
axios.interceptors.response.use(
(response) => {
if (
!(
response.headers.Authorization === undefined ||
response.headers.Authorization == null
)
) {
setToken(response.headers.Authorization);
}
if (response.headers["filename"]) {
fileName = response.headers["filename"];
let typeArr = fileName.split(".");
type = typeArr[typeArr.length - 1];
}
const res = response.data;
if (!res.httpCode) {
//下载
return res;
} else if (res.httpCode !== 200) {
Message({
message: res.msg,
type: "error",
duration: 5 * 1000,
});
return Promise.reject(res.msg);
} else {
return res;
}
},
(error) => {
if (error.response.status === 401) {
window.location.reload();
}
return Promise.reject(error);
}
);
const GET = (url, data) => {
let requestHeaders = {
"X-Requested-With": "XMLHttpRequest",
Accept: "application/json",
"Content-Type": "application/json; charset=UTF-8",
Authorization: localStorage.getItem("token"),
};
// http默认配置
let httpDefaultOpts = {
headers: requestHeaders,
method: "get",
url: url,
timeout: timeout,
params: data,
};
let promise = new Promise(function(resolve, reject) {
axios(httpDefaultOpts).then((res) => {
resolve(res);
});
});
return promise;
};
const POST = (url, data, requestBody = false, params = {}) => {
let requestHeaders;
if (requestBody) {
requestHeaders = {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/json;charset=UTF-8",
Authorization: localStorage.getItem("token"),
};
} else {
requestHeaders = {
"X-Requested-With": "XMLHttpRequest",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
Authorization: localStorage.getItem("token"),
};
}
// http默认配置
let httpDefaultOpts = {
method: "post",
url: url,
timeout: timeout,
params: params,
data: requestBody ? JSON.stringify(data) : qs.stringify(data),
headers: requestHeaders,
};
if (!requestBody) {
delete httpDefaultOpts.params;
}
let promise = new Promise(function(resolve, reject) {
axios(httpDefaultOpts).then((res) => {
resolve(res);
});
});
return promise;
};
const Delete = (url, data) => {
let requestHeaders = {
"X-Requested-With": "XMLHttpRequest",
Accept: "application/json",
"Content-Type": "application/json; charset=UTF-8",
Authorization: localStorage.getItem("token"),
};
// http默认配置
let httpDefaultOpts = {
headers: requestHeaders,
method: "delete",
url: url,
timeout: timeout,
params: data,
};
let promise = new Promise(function(resolve, reject) {
axios(httpDefaultOpts).then((res) => {
resolve(res);
});
});
return promise;
};
const UploadFile = (url, data) => {
let params = new FormData();
for (const key in data) {
params.append(key, data[key]);
}
return new Promise(function(resolve, reject) {
axios
.post(url, params, {
timeout: 2 * 60 * 60 * 1000,
headers: {
"Content-Type": "multipart/form-dataUpload",
},
})
.then((res) => {
resolve(res);
});
});
};
const DownloadExcel = (url, data) => {
Download(url, data).then((res) => {
const blob = new Blob([res], {
type: `application/${type || "ynd.ms-excel"}`,
});
if (window.navigator && window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveOrOpenBlob(blob, fileName);
} else {
const aEle = document.createElement("a"); // 创建a标签
const href = window.URL.createObjectURL(blob); // 创建下载的链接
aEle.href = href;
aEle.download = fileName; // 下载后文件名
document.body.appendChild(aEle);
aEle.dispatchEvent(
new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
})
);
document.body.removeChild(aEle); // 下载完成移除元素
window.URL.revokeObjectURL(href); // 释放掉blob对象
}
});
};
const Download = (url, data) => {
let httpDefaultOpts = {
method: "get",
url: url,
timeout: timeout,
params: data,
responseType: "blob",
};
let promise = new Promise(function(resolve, reject) {
axios(httpDefaultOpts).then((res) => {
resolve(res);
});
});
return promise;
};
export { POST, GET, Delete, UploadFile, DownloadExcel };

3
mdmds/src/utils/utils.js Normal file
View File

@@ -0,0 +1,3 @@
export function removeNull (list) {
return list.filter(n => n);
}

View File

@@ -0,0 +1,97 @@
<template>
<div class="content">
<div class="table-wrapper">
<div class="title">
<h6>Configurations</h6>
</div>
<div class="flex-row" style="align-items: baseline">
<h6>Configuration ID</h6>
<h4 class="top-info">CONF_{{ item.id }}</h4>
<h6 style="margin-left: 30px">Subscription</h6>
<h4 class="top-info">{{ item.Subscription }}</h4>
</div>
<div class="detail-box">
<pre>{{ JSON.stringify(item.detail, null, 4) }}</pre>
</div>
<div class="flex-row" style="justify-content: flex-end">
<el-button size="small" type="primary" @click="back()">back</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
item: {},
};
},
methods: {
showItem(item) {
item.detail = JSON.parse(item.detail)
this.item = item;
},
back() {
this.$emit("back");
},
syntaxHighlight(json) {
if (typeof json != "string") {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
return json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
function (match) {
var cls = "number";
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = "key";
} else {
cls = "string";
}
} else if (/true|false/.test(match)) {
cls = "boolean";
} else if (/null/.test(match)) {
cls = "null";
}
return '<span class="' + cls + '">' + match + "</span>";
}
);
},
},
};
</script>
<style >
.top-info {
margin-left: 15px;
font-size: 30px;
}
.detail-box {
margin: 20px 0px;
padding: 10px;
border: solid 1px #666;
word-break: break-all;
}
.json-text {
outline: 1px solid #ccc;
padding: 5px;
margin: 5px;
}
.string {
color: green;
}
.number {
color: darkorange;
}
.boolean {
color: blue;
}
.null {
color: magenta;
}
.key {
color: red;
}
</style>

View File

@@ -0,0 +1,92 @@
<template>
<div class="content">
<div class="table-wrapper" v-show='!isshowDetial'>
<div class="title">
<h6>Configurations</h6>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="Subscription" label="Subscription"></el-table-column>
<el-table-column prop="Comments" label="Comments"></el-table-column>
<el-table-column prop="adsVersion" label="adsVersion"></el-table-column>
<el-table-column prop="detail" label="detail">
<template slot-scope="scope">
<div class="detail">{{scope.row.detail}}</div>
</template>
</el-table-column>
<el-table-column prop="operate" label="operate">
<template slot-scope="scope">
<span class="table-row-btn" @click="showDetail(scope.row)">view</span>
</template>
</el-table-column>
</el-table>
<el-pagination v-if="totalPage>1" :page-size="limit" :total="total" @current-change="handleCurrentChange" background layout="prev, pager, next"></el-pagination>
</div>
<div v-show='isshowDetial'>
<detial-item ref="detailItem" @back='back'/>
</div>
</div>
</template>
<script>
import detialItem from './comp/detialItem.vue';
export default {
components: { detialItem },
data(){
return {
tableData: [
{id: '0001', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'},
{id: '0002', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'},
{id: '0003', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'
},
{id: '0004', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'},
{id: '0005', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'},
{id: '0006', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'},
{id: '0007', Subscription: 'Events 1-5', Comments: 'All the events may',adsVersion:'1.0.0',detail:'{"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"获取信息成功"}'},
],
maxHeight: 0,
page: 1,
limit: 10,
total: 300,
totalPage: 10,
isshowDetial:false,
}
},
created() {
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData(){
},
handleCurrentChange(val){
this.page = val;
this.getTableData();
},
getMaxHeight(){
let bodyHeight = document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
},
showDetail(item){
this.$refs.detailItem.showItem(item)
this.isshowDetial = true;
},
back(){
this.isshowDetial = false
}
}
}
</script>
<style >
.detail{
width: 170px;
overflow: hidden;
white-space:nowrap;
text-overflow: ellipsis;
}
</style>

View File

@@ -0,0 +1,70 @@
<template>
<div class="content">
<div class="table-wrapper">
<div class="title">
<h6>Task List</h6>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="taskName" label="Task Name"></el-table-column>
<el-table-column prop="runningState" label="Running State"></el-table-column>
<el-table-column prop="lastRunTime" label="Last run time"></el-table-column>
<el-table-column prop="executeNow" label="Execute Now">
<template slot-scope="scope">
<span class="table-row-btn">Sync Now</span>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination v-if="totalPage>1" :page-size="limit" :total="total" @current-change="handleCurrentChange" background layout="prev, pager, next"></el-pagination>
</div>
</div>
</template>
<script>
export default {
data(){
return {
tableData: [
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'},
{taskName: 'Task Name', runningState: 'Done', lastRunTime: '2021.09.23 12:34:21'}
],
maxHeight: 0,
page: 1,
limit: 10,
total: 300,
totalPage: 10,
}
},
created() {
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData(){
},
handleCurrentChange(val){
this.page = val;
this.getTableData();
},
getMaxHeight(){
let bodyHeight = document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
}
}
}
</script>

View File

@@ -0,0 +1,87 @@
<template>
<div id="bar-chart" style="height: 300px"></div>
</template>
<script>
export default {
data(){
return {
myChart: null,
data: [
{name: 'Succeed', value: '10532'},
{name: 'Failed', value: '1529'},
{name: 'Ready to send', value: '2107'},
]
}
},
mounted() {
this.getMyChart();
},
methods: {
getMyChart() {
if(!this.myChart){
this.myChart = this.$echarts.init(document.getElementById('bar-chart'));
}
let { data } = this;
let colors = ['#30A706', '#E11508', '#FF8A00'];
this.myChart.setOption({
grid: {
top: 30,
right: 0,
bottom: 5,
left: 0,
containLabel: true
},
xAxis: {
type: 'category',
axisLine: {
show: true,
lineStyle: {
color: '#D8E0F0'
}
},
axisLabel: {
color: '#000000'
},
data: data.map(item => item.name)
},
yAxis: {
type: 'value',
splitLine: {
show: false,
},
axisLine: {
show: true,
lineStyle: {
color: '#D8E0F0'
}
},
axisLabel: {
color: '#555353'
},
},
series: [ {
type: 'bar',
barWidth: 40,
label: {
show: true,
position: 'top',
itemStyle: {
color: '#373737'
}
},
data: data.map((item, i) => {
return {
itemStyle: {
color: colors[i]
},
value: item.value
}
})
}]
})
},
}
}
</script>

View File

@@ -0,0 +1,88 @@
<template>
<div id="pie-chart" style="height: 300px"></div>
</template>
<script>
export default {
data(){
return {
myChart: null,
data: [
{name: 'Succeed', value: '10532'},
{name: 'Failed', value: '1529'},
{name: 'Ready to send', value: '2107'},
]
}
},
mounted() {
this.getMyChart();
},
methods: {
getMyChart() {
if(!this.myChart){
this.myChart = this.$echarts.init(document.getElementById('pie-chart'));
}
let { data } = this;
this.myChart.setOption({
title: {
left: 'center',
top: 'center',
text: [
'{a|74.34%}',
'{b|Success rate}'
].join('\n'),
textStyle: {
rich: {
a: {
fontSize: 18,
color: '#3D3B3B',
fontWeight: 'bold',
lineHeight: 30
},
b: {
fontSize: 12,
color: '#3D3B3B'
}
}
}
},
color: ['#30A706', '#E11508', '#FF8A00'],
legend: {
orient: 'vertical',
top: 'bottom',
left: 'right',
icon: 'circle',
itemWidth: 10,
itemHeight: 10,
itemGap: 10,
textStyle:{
color: '#000'
},
},
series: [
{
type: 'pie',
radius: ['30%', '75%'],
label: {
position: 'inside',
formatter:'{d}%',
textStyle : {
fontSize : 12,
color: '#fff'
},
},
labelLine: {
show: false
},
itemStyle:{
borderWidth: 3,
borderColor: '#fff',
},
data
}
]
})
},
}
}
</script>

View File

@@ -0,0 +1,391 @@
<template>
<div class="content">
<div class="group-msg">
<p>Group ID: {{ group.groupId }}</p>
<p>Group Name: {{ group.groupName }}</p>
</div>
<div class="search">
<el-row :gutter="10">
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">Brand:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="brand"
placeholder="Please choose Brand!"
clearable
>
<el-option
v-for="item in brandList"
:key="item.value"
:label="item.name"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">Model Code:</div>
</el-col>
<el-col :span="16" class="timers">
<el-select
v-model="modelCode"
placeholder="Please choose Model Code!"
clearable
>
<el-option
v-for="item in modelCodeList"
:key="item.value"
:label="item.name"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">Model Year:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="modelYear"
placeholder="Please choose Model Year!"
clearable
>
<el-option
v-for="item in modelYearList"
:key="item.value"
:label="item.name"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">ADS Version:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="adsVersion"
placeholder="Please choose ADS Version!"
clearable
>
<el-option
v-for="item in adsVersionList"
:key="item.value"
:label="item.name"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">VIN:</div>
</el-col>
<el-col :span="16">
<el-input
v-model="vin"
placeholder="Please input VIN!"
clearable
/>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">Existing Group:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="existingGroup"
placeholder="Please choose Existing Group!"
clearable
>
<el-option
v-for="item in existingGroup"
:key="item.value"
:label="item.name"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">Online Status:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="onlineStatus"
placeholder="Please choose Online Status!"
clearable
>
<el-option
v-for="item in onlineStatusList"
:key="item.value"
:label="item.name"
:value="item.value"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6" class="btn-group">
<el-button type="primary" size="small">Search</el-button>
</el-col>
</el-row>
</div>
<div class="table-wrapper">
<div class="title">
<h6>Vehicle List</h6>
<div>
<el-button type="primary" size="small">Add all to group</el-button>
<el-button type="primary" size="small">Update</el-button>
</div>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column type="selection" width="60px"></el-table-column>
<el-table-column prop="vin" label="VIN" width="220px"></el-table-column>
<el-table-column prop="vid" label="VID" width="120px"></el-table-column>
<el-table-column
prop="brand"
label="Brand"
width="120px"
></el-table-column>
<el-table-column
prop="modelCode"
label="Model Code"
width="120px"
></el-table-column>
<el-table-column
prop="modelYear"
label="Model Year"
width="120px"
></el-table-column>
<el-table-column
prop="modelFamily"
label="Model Family"
width="150px"
></el-table-column>
<el-table-column
prop="adsVersion"
label="ADS Version"
width="150px"
></el-table-column>
<el-table-column
prop="lifecycle"
label="LifeCycle"
width="120px"
></el-table-column>
<el-table-column
prop="onlineStatus"
label="Online Status"
width="150px"
></el-table-column>
<el-table-column
prop="shadowVersion"
label="Shadow Version"
width="150px"
></el-table-column>
<el-table-column prop="existGroup" label="Exit Groups" width="150px">
<template slot-scope="scope">
<span>{{ getGroup(scope.row.existGroup) }}</span>
</template>
</el-table-column>
<el-table-column prop="existIssue" label="Exit Groups" width="150px">
</el-table-column>
//null
<el-table-column prop="warning" label="Warning" width="120px">
<template slot-scope="scope">
<span class="red-circle">{{ scope.row.warning }}</span>
</template>
</el-table-column>
<el-table-column
prop="isDisabled"
label="Is Disabled"
width="120px"
></el-table-column>
<el-table-column
prop="inGroup"
label="In Group"
width="120px"
></el-table-column>
<el-table-column prop="action" label="Action" width="220px" fixed="right">
<template slot-scope="scope">
<span class="table-row-btn">Add to group</span>
<span class="table-row-btn">Remove</span>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="total > 0"
:page-size="limit"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 20, 50, 100]"
background
layout="prev,sizes, pager, next"
></el-pagination>
</div>
</div>
</template>
<script>
import { GetGroupsDetail } from "@/api/vehicle";
export default {
data() {
return {
groupId: "",
brandList: [],
modelCodeList: [],
modelYearList: [],
adsVersionList: [],
existingGroupList: [],
onlineStatusList: [
{ value: "0", name: "Off" },
{ value: "1", name: "On" },
],
brand: "",
modelCode: "",
modelYear: "",
adsVersion: "",
vin: "",
existingGroup: "",
onlineStatus: "",
tableData: [],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
group: {},
};
},
created() {
this.groupId = this.$route.query.groupId;
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData() {
const { page, limit } = this;
GetGroupsDetail({
currentPage: page,
pageSize: limit,
groupId: this.groupId,
}).then((res) => {
const { data } = res;
this.group = data.group;
if (data.group.groupFilterConditions) {
let groupFilterConditions = JSON.parse(
data.group.groupFilterConditions
);
this.brandList = [
{
value: groupFilterConditions.brand,
name: groupFilterConditions.brand,
},
];
this.modelCodeList = [
{
value: groupFilterConditions.modeCode,
name: groupFilterConditions.modeCode,
},
];
this.adsVersionList = [
{
value: groupFilterConditions.adsVersion,
name: groupFilterConditions.adsVersion,
},
];
this.adsVersionList = [
{
value: groupFilterConditions.adsVersion,
name: groupFilterConditions.adsVersion,
},
];
this.modelYearList = [
{
value: groupFilterConditions.modeYear,
name: groupFilterConditions.modeYear,
},
];
this.existingGroupList = [
{
value: groupFilterConditions.existingGroup,
name: groupFilterConditions.existingGroup,
},
];
this.brand = groupFilterConditions.brand;
this.modelCode = groupFilterConditions.modeCode;
this.modelYear = groupFilterConditions.modeYear;
this.adsVersion = groupFilterConditions.adsVersion;
this.existingGroup = groupFilterConditions.existingGroup;
this.onlineStatus = groupFilterConditions.onlineStatus;
}
this.tableData = data.vehicles.records;
this.total = data.vehicles.total;
});
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1)
},
getMaxHeight() {
let bodyHeight =
document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
},
getGroup(group) {
return group;
},
},
};
</script>
<style scoped lang="scss">
.group-msg {
display: flex;
margin-bottom: 20px;
p {
margin-right: 50px;
font-size: 16px;
font-weight: 600;
color: #212020;
line-height: 30px;
}
}
</style>

View File

@@ -0,0 +1,160 @@
<template>
<div class="content">
<div class="flex-layout">
<div class="table-wrapper">
<div class="title">
<h6>Group List</h6>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="groupId" label="Group ID"></el-table-column>
<el-table-column
prop="groupName"
label="Group Name"
></el-table-column>
<el-table-column prop="autoUpdate" label="Automatic updates">
<template slot-scope="scope">
<span v-if="scope.row.autoUpdate == '0'">true</span>
<span v-else>false</span>
</template>
</el-table-column>
<el-table-column prop="createBy" label="createBy"></el-table-column>
<el-table-column prop="action" label="Action" width="250px">
<template slot-scope="scope">
<span
class="icon iconfont icon-edit cursor table-acion"
@click="detail(scope.row)"
></span>
<el-popconfirm
confirm-button-text="Yes"
cancel-button-text="No"
icon="el-icon-info"
icon-color="red"
title="Are you sure to delete this?"
@confirm="toDelete(scope.row.groupId)"
>
<span
class="icon iconfont icon-shanchu cursor table-acion"
slot="reference"
>
</span>
</el-popconfirm>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="total > 0"
:page-size="limit"
:current-page.sync="page"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:page-sizes="[10, 20, 50, 100]"
layout="prev, sizes, pager, next"
></el-pagination>
</div>
<div class="chart-wrapper flex-column">
<div class="flex-row">
<div class="chart">
<h6>Command sending success rate</h6>
<pie-chart />
</div>
<div class="chart">
<h6>Statistics of single group Command Send Status</h6>
<bar-chart />
</div>
</div>
<div class="chart">
<h6>Logs</h6>
<el-table :data="logData" style="width: 100%" height="250">
<el-table-column prop="Time" label="Time"></el-table-column>
<el-table-column prop="Operator" label="Operator"></el-table-column>
<el-table-column prop="logs" label="logs"></el-table-column>
</el-table>
</div>
</div>
</div>
</div>
</template>
<script>
import pieChart from "./components/pieChart";
import barChart from "./components/barChart";
import { GetGroups, DeleteGroup } from "@/api/vehicle";
export default {
components: { pieChart, barChart },
data() {
return {
tableData: [],
logData:[
{Time:'2022-01-01 08:15:2',Operator:'Ding shuo',logs:'Send to subscription'},
{Time:'2022-01-01 08:15:2',Operator:'Ding shuo',logs:'Send to subscription'},
{Time:'2022-01-01 08:15:2',Operator:'Ding shuo',logs:'Send to subscription'},
{Time:'2022-01-01 08:15:2',Operator:'Ding shuo',logs:'Send to subscription'},
{Time:'2022-01-01 08:15:2',Operator:'Ding shuo',logs:'Send to subscription'},
{Time:'2022-01-01 08:15:2',Operator:'Ding shuo',logs:'Send to subscription'},
],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
};
},
created() {
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData() {
const { page, limit } = this;
GetGroups({ currentPage: page, pageSize: limit }).then((res) => {
const { data } = res;
this.tableData = data.records;
this.total = data.total;
if (page > 0 && data.records.length == 0) {
this.handleCurrentChange(page - 1);
}
});
},
toDelete(id) {
DeleteGroup({ groupId: id }).then((res) => {
if (res.httpCode == 200) {
this.$message.success("delete success");
this.handleCurrentChange(this.page);
} else {
this.$message.error(res.msg);
}
});
},
detail(item) {
this.$router.push({
path: "/groupManagement/detail",
query: {
groupId: item.groupId,
},
});
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1);
},
getMaxHeight() {
let bodyHeight =
document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
},
},
};
</script>
<style lang="scss" scoped src="../../assets/css/group-management.scss" />

View File

@@ -0,0 +1,143 @@
<template>
<div class="content">
<div class="table-wrapper">
<div class="ticket-msg">
<div>
<p>Ticket ID: {{ ticketNo }}</p>
<p>Ticket Name: {{ ticketName }}</p>
<p>Operator: {{ Operator }}</p>
<p>Comment: {{ comments }}</p>
</div>
<div>
<el-button type="primary" size="small">Delete Ticket</el-button>
</div>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="vin" label="VIN" width="220px"></el-table-column>
<el-table-column prop="vid" label="VID"></el-table-column>
<el-table-column prop="brand" label="Brand"></el-table-column>
<el-table-column prop="modelCode" label="Model Code"></el-table-column>
<el-table-column prop="modelYear" label="Model Year"></el-table-column>
<el-table-column
prop="modelFamily"
label="Model Family"
></el-table-column>
<el-table-column
prop="adsVersion"
label="ADS Version"
></el-table-column>
<el-table-column prop="lifeCycle" label="LifeCycle"></el-table-column>
<el-table-column prop="brand" label="Brand"></el-table-column>
<el-table-column
prop="onlineStatus"
label="Online Status"
></el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="total > 0"
:page-size="limit"
:total="total"
:page-sizes="[10, 20, 50, 100]"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
layout="prev,sizes, pager, next"
></el-pagination>
</div>
</div>
</template>
<script>
import { GetTicketDetial } from "@/api/vehicle";
export default {
data() {
return {
ticketNo: "",
ticketName: "",
Operator: "",
comments: "",
brandList: [{ code: "1", name: "Audi" }],
modelCodeList: [{ code: "1", name: "MC_0001" }],
modelYearList: [{ code: "2021", name: "2021" }],
adsVersionList: [{ code: "1", name: "1.0" }],
existingGroupList: [{ code: "1", name: "AAA" }],
onlineStatusList: [
{ code: "0", name: "Off" },
{ code: "1", name: "On" },
],
brand: "",
modelCode: "",
modelYear: "",
adsVersion: "",
vin: "",
existingGroup: "",
onlineStatus: "",
tableData: [],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
};
},
created() {
const { ticketNo } = this.$route.query;
this.ticketNo = ticketNo;
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData() {
const { page, limit } = this;
GetTicketDetial({
currentPage: page,
pageSize: limit,
ticketNo: this.ticketNo,
}).then((res) => {
const { data } = res;
this.ticketName = data.issueTicket.ticketName;
this.Operator = data.issueTicket.createBy;
this.comments = data.issueTicket.comments;
this.tableData = data.vehicles.records;
this.total = data.vehicles.total;
});
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1)
},
getMaxHeight() {
let bodyHeight =
document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
},
},
};
</script>
<style scoped lang="scss">
.ticket-msg {
display: flex;
margin-bottom: 20px;
justify-content: space-between;
div {
display: flex;
}
p {
margin-right: 50px;
font-size: 16px;
font-weight: 600;
color: #212020;
line-height: 30px;
}
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<div class="content">
<div class="table-wrapper">
<div class="title">
<h6>Ticket List</h6>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="id" label="Ticket ID"></el-table-column>
<el-table-column
prop="ticketName"
label="Ticket Name"
></el-table-column>
<el-table-column prop="createBy" label="createBy"></el-table-column>
<el-table-column
prop="anomalyFlag"
label="anomalyFlag"
></el-table-column>
<el-table-column prop="createDate" label="Time">
<template slot-scope="scope">
<span>{{ new Date(scope.row.createDate) | formatTime }} </span>
</template>
</el-table-column>
<el-table-column prop="action" label="Action">
<template slot-scope="scope">
<span
class="icon iconfont icon-view cursor table-acion"
@click="detail(scope.row)"
></span>
<el-popconfirm
confirm-button-text="Yes"
cancel-button-text="No"
icon="el-icon-info"
icon-color="red"
title="Are you sure to delete this?"
@confirm="toDelete(scope.row.id)"
>
<span
class="icon iconfont icon-shanchu cursor table-acion"
slot="reference"
>
</span>
</el-popconfirm>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="totalPage > 1"
:page-size="limit"
:total="total"
:current-page.sync="page"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:page-sizes="[10, 20, 50, 100]"
background
layout="prev,sizes, pager, next"
></el-pagination>
</div>
</div>
</template>
<script>
import { GetTickets, DeleteTickets } from "@/api/vehicle";
export default {
data() {
return {
tableData: [],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
totalPage: 10,
};
},
created() {
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData() {
const { page, limit } = this;
GetTickets({ currentPage: page, totalPage: limit }).then((res) => {
const { data } = res;
this.tableData = data.records;
this.total = data.total;
if (page > 0 && data.records.length == 0) {
this.handleCurrentChange(page - 1);
}
});
},
detail(item) {
this.$router.push({
path: "/issueManagement/detail",
query: {
ticketNo: item.id,
},
});
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1)
},
toDelete(id) {
DeleteTickets({ ticketId: id }).then((res) => {
if (res.httpCode == 200) {
this.$message.success("delete success");
this.handleCurrentChange(this.page);
} else {
this.$message.error(res.msg);
}
});
},
getMaxHeight() {
let bodyHeight =
document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
},
},
};
</script>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,39 @@
<template>
<div class="content flex-layout">
<div class="flex-row all-box">
<div>
<el-menu
:router="true"
default-active="/userList"
class="el-menu-vertical-demo"
>
<el-menu-item index="/userList">
<i class="el-icon-menu"></i>
<span slot="title">User List</span>
</el-menu-item>
<el-menu-item index="/roleList">
<i class="el-icon-document"></i>
<span slot="title">Role List</span>
</el-menu-item>
</el-menu>
</div>
<div style="flex: 1;padding :20px">
<router-view />
</div>
</div>
</div>
</template>
<style scoped>
.all-box {
padding: 30px;
background: #fff;
box-shadow: 4px 4px 6px 2px rgb(0 0 0 / 3%);
height: 100%;
}
.el-menu-vertical-demo {
border-right: 0px;
}
.left {
width: 240px;
}
</style>

View File

@@ -0,0 +1,129 @@
<template>
<div>
<div class="flex-row-center top-box">
<div class="flex-row-center" style="flex: 1; align-items: flex-end">
<span class="title">{{ name }}</span>
<i class="el-icon-edit" style="cursor: pointer" @click="editRole()"></i>
</div>
<span style="font-wight: bold; margin-right: 5px">Search User</span>
<el-input v-model="userName" style="width: 300px" />
<el-button
style="margin-left: 10px"
icon="el-icon-search"
type="primary"
@click="getTableData()"
></el-button>
</div>
<el-table
:data="tableData"
style="width: 100%; margin-top: 10px"
height="600"
>
<el-table-column prop="userName" label="User Name"></el-table-column>
<el-table-column prop="fullName" label="Full Name"></el-table-column>
<el-table-column prop="eamil" label="Eamil"> </el-table-column>
<el-table-column prop="action" label="Operate" width="250px">
<template slot-scope="scope">
<span class="role-text" @click="toDelete(scope.row)">delete</span>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="total > 0"
:page-size="limit"
:current-page.sync="page"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:page-sizes="[10, 20, 50, 100]"
layout="prev, sizes, pager, next"
></el-pagination>
<edit-item ref="editItem" />
</div>
</template>
<script>
import EditItem from "../roleList/components/editItem.vue";
export default {
components: { EditItem },
data() {
return {
userName: "",
name: "asdf",
tableData: [
{
userName: "Yangjinkang",
fullName: "Yangjinkang",
eamil: "Jinkang.Yang@t-systems.com",
},
],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
};
},
created() {
const { id, name } = this.$route.query;
// this.getTableData();
},
methods: {
getTableData() {
const { page, limit } = this;
GetGroups({ currentPage: page, pageSize: limit }).then((res) => {
const { data } = res;
this.tableData = data.records;
this.total = data.total;
if (page > 0 && data.records.length == 0) {
this.handleCurrentChange(page - 1);
}
});
},
toDelete(id) {
DeleteGroup({ groupId: id }).then((res) => {
if (res.httpCode == 200) {
this.$message.success("delete success");
this.handleCurrentChange(this.page);
this.$message.error(res.msg);
}
});
},
editRole() {
this.$refs.editItem.show({ id: 123, roleName: this.name });
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1);
},
},
};
</script>
<style scoped>
.title {
font-size: 30px;
font-weight: bold;
margin-right: 20px;
line-height: 30px;
}
.acition-btn {
cursor: pointer;
border-bottom: solid 1px rgb(91, 91, 212);
color: rgb(91, 91, 212);
}
.top-box {
border-bottom: dashed 1px #f3f3f3;
padding-bottom: 8px;
}
.role-text {
cursor: pointer;
color: rgb(11, 44, 85);
border-bottom: solid 1px rgb(11, 44, 85);
}
</style>

View File

@@ -0,0 +1,46 @@
<template>
<div>
<el-dialog
:title="changeItem ? 'Edit Role' : 'Add Role'"
:visible.sync="isShow"
width="40%"
>
<div class="flex-row-center">
<div style="width:180px">Role Name</div>
<el-input v-model="roleName" />
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="isShow = false"> </el-button>
<el-button type="primary" @click="submit()"
> </el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
roleName: "",
changeItem: {},
};
},
methods: {
show(item) {
this.isShow = true;
this.changeItem = item;
if (item) {
this.roleName = item.roleName;
}
},
submit(){
if (!this.roleName) {
this.$elmessage.show('please input role name')
return
}
}
},
};
</script>

View File

@@ -0,0 +1,128 @@
<template>
<div>
<div class="flex-row">
<div style="flex: 1"></div>
<el-button @click="showEdit()">add Role</el-button>
</div>
<el-table
:data="tableData"
style="width: 100%; margin-top: 10px"
height="600"
>
<el-table-column prop="roleName" label="Role Name"></el-table-column>
<el-table-column prop="number" label="Number"></el-table-column>
<el-table-column prop="createTime" label="Create Time"> </el-table-column>
<el-table-column prop="action" label="Operate" width="250px">
<template slot-scope="scope">
<span class="role-text" @click="showEdit(scope.row)">edit</span>
<span class="role-text" @click="jumDetail(scope.row)">detail</span>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="total > 0"
:page-size="limit"
:current-page.sync="page"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:page-sizes="[10, 20, 50, 100]"
layout="prev, sizes, pager, next"
></el-pagination>
<edit-item ref="editItem" />
</div>
</template>
<script>
import editItem from "./components/editItem.vue";
export default {
components: { editItem },
data() {
return {
tableData: [
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
{ roleName: "Admin", number: 1, createTime: "2022-2-22" },
],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
};
},
created() {
// this.getTableData();
},
methods: {
getTableData() {
const { page, limit } = this;
GetGroups({ currentPage: page, pageSize: limit }).then((res) => {
const { data } = res;
this.tableData = data.records;
this.total = data.total;
if (page > 0 && data.records.length == 0) {
this.handleCurrentChange(page - 1);
}
});
},
toDelete(id) {
DeleteGroup({ groupId: id }).then((res) => {
if (res.httpCode == 200) {
this.$message.success("delete success");
this.handleCurrentChange(this.page);
} else {
this.$message.error(res.msg);
}
});
},
detail(item) {
this.$router.push({
path: "/groupManagement/detail",
query: {
groupId: item.groupId,
},
});
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1);
},
showEdit(item) {
this.$refs.editItem.show(item);
},
jumDetail(item) {
this.$router.push({
path: "/roleDetail",
query: {
id: item,
name: item,
},
});
},
},
};
</script>
<style scoped>
.acition-btn {
cursor: pointer;
border-bottom: solid 1px rgb(91, 91, 212);
color: rgb(91, 91, 212);
}
.role-text {
margin-right: 10px;
cursor: pointer;
color: rgb(11, 44, 85);
border-bottom: solid 1px rgb(11, 44, 85);
}
</style>

View File

@@ -0,0 +1,168 @@
<template>
<div class="content">
<div class="search">
<el-row :gutter="10">
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">Vehicle Group:</div>
</el-col>
<el-col :span="16">
<el-select v-model="vehicleGroup" placeholder="Please choose Vehicle Group!" clearable>
<el-option v-for="item in vehicleGroupList" :key="item.code"
:label="item.name"
:value="item.code"></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="16">
<el-button type="primary" size="small">Search</el-button>
</el-col>
</el-row>
</div>
<div class="table-wrapper" style="margin-bottom: 20px;">
<div class="title">
<h6>Select Vehicle Group</h6>
</div>
<el-table :data="tableData1" style="width: 100%">
<el-table-column prop="configurationId" label="Configuration ID"></el-table-column>
<el-table-column prop="configurationMessage" label="Configuration Message"></el-table-column>
<el-table-column prop="description" label="Description"></el-table-column>
<el-table-column prop="linkGroup" label="Link Group">
<template slot-scope="scope">
<span class="table-row-btn">Link</span>
</template>
</el-table-column>
<template slot="empty" v-if="tableData1.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination v-if="pager1.totalPage>1" :page-size="pager1.limit" :total="pager1.total" @current-change="handleCurrentChange1" background layout="prev, pager, next"></el-pagination>
</div>
<div class="table-wrapper">
<div class="title">
<h6>Issue Command</h6>
<div>
<el-button type="primary" size="small" @click="dialogVisible = true">Send All Commands</el-button>
</div>
</div>
<el-table :data="tableData2" style="width: 100%">
<el-table-column prop="commandId" label="Command ID"></el-table-column>
<el-table-column prop="groupName" label="Group Name"></el-table-column>
<el-table-column prop="operate" label="Operate">
<template slot-scope="scope">
<span class="table-row-btn">Send to Vehicle</span>
</template>
</el-table-column>
<template slot="empty" v-if="tableData2.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination v-if="pager2.totalPage>1" :page-size="pager2.limit" :total="pager2.total" @current-change="handleCurrentChange2" background layout="prev, pager, next"></el-pagination>
</div>
<el-dialog
title="Group List"
:visible.sync="dialogVisible"
width="30%"
:before-close="handleClose">
<el-table :data="dialogTableData">
<el-table-column property="groupName" label="Group Name"></el-table-column>
<el-table-column property="details" label="Details"></el-table-column>
<el-table-column prop="link" label="Link">
<template slot-scope="scope">
<el-checkbox :value="scope.row.link"></el-checkbox>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="submit">Submit</el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data(){
return {
vehicleGroupList: [
{code: 1, name: 'Group_001'},
{code: 2, name: 'Group_002'},
{code: 3, name: 'Group_003'},
],
vehicleGroup: '',
tableData1: [
{configurationId: 'Id_0001', configurationMessage: 'Config_******', description: '-'},
{configurationId: 'Id_0001', configurationMessage: 'Config_******', description: '-'},
{configurationId: 'Id_0001', configurationMessage: 'Config_******', description: '-'},
{configurationId: 'Id_0001', configurationMessage: 'Config_******', description: '-'},
{configurationId: 'Id_0001', configurationMessage: 'Config_******', description: '-'}
],
tableData2: [
{commandId: 'CMD_0001', groupName: 'Group_Name_0001'},
{commandId: 'CMD_0001', groupName: 'Group_Name_0001'},
{commandId: 'CMD_0001', groupName: 'Group_Name_0001'},
{commandId: 'CMD_0001', groupName: 'Group_Name_0001'},
{commandId: 'CMD_0001', groupName: 'Group_Name_0001'}
],
dialogVisible: false,
dialogTableData: [
{groupName: 'Group_0001', details: '-', link: true},
{groupName: 'Group_0001', details: '-', link: false},
{groupName: 'Group_0001', details: '-', link: true},
{groupName: 'Group_0001', details: '-', link: false},
],
maxHeight: 0,
pager1: {
page: 1,
limit: 10,
total: 300,
totalPage: 10,
},
pager2: {
page: 1,
limit: 10,
total: 300,
totalPage: 10,
}
}
},
created() {
this.getTableData1();
this.getTableData2();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getTableData1(){
},
getTableData2(){
},
submit(){
this.dialogVisible = false;
this.$message.success('Group and configuration have been completed. Waiting for the command to be sent manually.');
},
handleClose(){
this.dialogVisible = false;
},
handleCurrentChange1(val){
this.page = val;
this.getTableData1();
},
handleCurrentChange2(val){
this.page = val;
this.getTableData2();
},
getMaxHeight(){
let bodyHeight = document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
}
}
}
</script>

View File

@@ -0,0 +1,49 @@
<template>
<div>
<el-dialog title="Change Role" :visible.sync="isShow" width="40%">
<div class="flex-row-center">
<div style="width: 180px">select</div>
<el-select v-model="changeItem.role">
<el-option
v-for="item in options"
:key="item.id"
:label="item.name"
:value="item.id"
>
</el-option>
</el-select>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="isShow = false"> </el-button>
<el-button type="primary" @click="submit()"> </el-button>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
isShow: false,
changeItem: {},
options: [
{
id: "1",
name: "Role Admin",
},
{
id: "2",
name: "Role Admin2",
},
],
};
},
methods: {
show(item) {
this.isShow = true;
this.changeItem = item;
},
submit() {},
},
};
</script>

View File

@@ -0,0 +1,113 @@
<template>
<div>
<el-table :data="tableData" style="width: 100%" height="600">
<el-table-column prop="userName" label="user Name"></el-table-column>
<el-table-column prop="fullName" label="full Name"></el-table-column>
<el-table-column prop="eamil" label="Eamil">
<!-- <template slot-scope="scope">
<span v-if="scope.row.autoUpdate == '0'">true</span>
<span v-else>false</span>
</template> -->
</el-table-column>
<el-table-column
prop="lastloginTime"
label="Last Login Time"
></el-table-column>
<el-table-column prop="role" label="role">
<template slot-scope="scope">
<span class="role-text" @click="editRole(scope.row)">
{{ scope.row.role }}
</span>
</template>
</el-table-column>
<el-table-column prop="action" label="Action" width="250px">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.isCheck" />
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="total > 0"
:page-size="limit"
:current-page.sync="page"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:page-sizes="[10, 20, 50, 100]"
layout="prev, sizes, pager, next"
></el-pagination>
<edit-role ref="editRole" />
</div>
</template>
<script>
import editRole from "./components/editRole.vue";
export default {
components: { editRole },
data() {
return {
tableData: [
{
eamil: "Jinkang.Yang@t-systems.com",
userName: "Yangjinkang",
fullName: "Yangjinkang",
role: "Admin",
isCheck: true,
lastloginTime: "2022-2-22",
},
],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
};
},
created() {
// this.getTableData();
},
methods: {
getTableData() {
const { page, limit } = this;
GetGroups({ currentPage: page, pageSize: limit }).then((res) => {
const { data } = res;
this.tableData = data.records;
this.total = data.total;
if (page > 0 && data.records.length == 0) {
this.handleCurrentChange(page - 1);
}
});
},
toDelete(id) {
DeleteGroup({ groupId: id }).then((res) => {
if (res.httpCode == 200) {
this.$message.success("delete success");
this.handleCurrentChange(this.page);
} else {
this.$message.error(res.msg);
}
});
},
editRole(item) {
this.$refs.editRole.show(item);
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1);
},
},
};
</script>
<style scoped>
.role-text {
cursor: pointer;
color: rgb(11, 44, 85);
border-bottom: solid 1px rgb(11, 44, 85);
}
</style>

View File

@@ -0,0 +1,551 @@
<template>
<div class="content">
<div class="search">
<el-row :gutter="10">
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">Brand:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="brand"
placeholder="Please choose Brand!"
clearable
filterable
>
<el-option
v-for="(item, i) in brandList"
:key="i"
:label="item"
:value="item"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">Model Code:</div>
</el-col>
<el-col :span="16" class="timers">
<el-select
v-model="modelCode"
placeholder="Please choose Model Code!"
clearable
filterable
>
<el-option
v-for="(item, i) in modelCodeList"
:key="i"
:label="item"
:value="item"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">Model Year:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="modelYear"
placeholder="Please choose Model Year!"
clearable
filterable
>
<el-option
v-for="(item, i) in modelYearList"
:key="i"
:label="item"
:value="item"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle" class="row-margin">
<el-col :span="8">
<div class="lable-title">ADS Version:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="adsVersion"
placeholder="Please choose ADS Version!"
clearable
filterable
>
<el-option
v-for="(item, i) in adsVersionList"
:key="i"
:label="item"
:value="item"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">VIN:</div>
</el-col>
<el-col :span="16">
<el-input
v-model="vin"
placeholder="Please input VIN!"
clearable
/>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">Existing Group:</div>
</el-col>
<el-col :span="16">
<el-input
v-model="existGroup"
placeholder="Please input Existing Group!"
clearable
/>
</el-col>
</el-row>
</el-col>
<el-col :span="6">
<el-row type="flex" align="middle">
<el-col :span="8">
<div class="lable-title">Online Status:</div>
</el-col>
<el-col :span="16">
<el-select
v-model="onlineStatus"
placeholder="Please choose Online Status!"
clearable
filterable
>
<el-option
v-for="item in onlineStatusList"
:key="item.code"
:label="item.name"
:value="item.code"
></el-option>
</el-select>
</el-col>
</el-row>
</el-col>
<el-col :span="6" class="btn-group">
<el-popconfirm
v-if="groupList.length > 0 || ticketList.length > 0"
confirm-button-text="confirm"
cancel-button-text="cancel"
title="There are unsaved vehicles with groups at present, do you want to confirm the operation?"
@confirm="search"
>
<el-button slot="reference" type="primary" size="small">
<div class="flex-row-center">
Search
<div
class="icon iconfont icon-sousuo"
style="margin-left:5px"
></div>
</div>
</el-button>
</el-popconfirm>
<el-button v-else type="primary" size="small" @click="search">
<div class="flex-row-center">
Search
<div
class="icon iconfont icon-sousuo"
style="margin-left:5px"
></div>
</div>
</el-button>
<el-button
style="margin-left:10px"
type="primary"
size="small"
@click="reset"
>
<div class="flex-row-center">
Reset
<div
class="icon iconfont icon-zhongzhi"
style="margin-left:5px"
></div>
</div>
</el-button>
</el-col>
</el-row>
</div>
<div class="table-wrapper">
<div class="title">
<h6>Vehicle List</h6>
<div>
<el-button type="primary" size="small">Add all to ticket</el-button>
<el-button :disabled="!isaddGroup" type="primary" size="small"
>Add all to group</el-button
>
<el-button @click="showTickDialog()" type="primary" size="small"
>Generate New Ticket ({{ ticketList.length }})</el-button
>
<el-popover
placement="top"
width="400"
trigger="hover"
v-if="!isaddGroup"
>
<el-alert
:closable="false"
title="info"
type="warning"
description="You cannot create a group until the criteria filter is complete."
show-icon
>
</el-alert>
<button
slot="reference"
style="margin-left:10px"
class="el-button el-button--primary el-button--small is-disabled el-popover__reference"
>
<span> Generate New Group ({{ groupList.length }})</span>
</button>
</el-popover>
<el-button
type="primary"
size="small"
v-else
@click="showGroupDialog()"
>Generate New Group ({{ groupList.length }})</el-button
>
</div>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="vin" label="VIN" width="220px"></el-table-column>
<el-table-column prop="vid" label="VID" width="120px"></el-table-column>
<el-table-column
prop="brand"
label="Brand"
width="120px"
></el-table-column>
<el-table-column
prop="modelCode"
label="Model Code"
width="120px"
></el-table-column>
<el-table-column
prop="modelYear"
label="Model Year"
width="120px"
></el-table-column>
<el-table-column
prop="modelFamily"
label="Model Family"
width="150px"
></el-table-column>
<el-table-column
prop="adsVersion"
label="ADS Version"
width="150px"
></el-table-column>
<el-table-column
prop="lifeCycle"
label="LifeCycle"
width="120px"
></el-table-column>
<el-table-column
prop="onlineStatus"
label="Online Status"
width="150px"
></el-table-column>
<el-table-column
prop="shadowVersion"
label="Shadow Version"
width="150px"
></el-table-column>
<el-table-column
prop="existGroup"
label="Exit Groups"
width="150px"
></el-table-column>
<el-table-column prop="existIssue" label="Warning" width="120px">
<template slot-scope="scope">
<span class="red-circle">{{ scope.row.existIssue }}</span>
</template>
</el-table-column>
<el-table-column
prop="action"
label="Action"
width="150px"
fixed="right"
>
<template slot-scope="scope">
<el-tooltip
content="Add to issue ticket"
placement="top"
effect="light"
>
<span
:style="{ color: getIsColor(scope.row.vin, ticketList) }"
class="icon iconfont icon-xinzengruku cursor table-acion"
@click="addTicket(scope.row)"
>
</span>
</el-tooltip>
<el-tooltip
v-if="isaddGroup"
content="Add to group"
placement="top"
effect="light"
>
<span
:style="{ color: getIsColor(scope.row.vin, groupList) }"
class="icon iconfont icon-tianjiaqunzu cursor table-acion"
@click="addGroup(scope.row)"
>
</span>
</el-tooltip>
</template>
</el-table-column>
<template slot="empty" v-if="tableData.length == 0">
<span>No data available</span>
</template>
</el-table>
<el-pagination
v-if="totalPage > 1"
:current-page.sync="page"
:page-size="limit"
:total="total"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
background
:page-sizes="[10, 20, 50, 100]"
layout="prev,sizes, pager, next"
></el-pagination>
</div>
<newGroupDialog
ref="newGroupDialog"
@addSuccess="groupList = []"
></newGroupDialog>
<newTicketDialog
ref="newTicketDialog"
@addSuccess="ticketList = []"
></newTicketDialog>
</div>
</template>
<script>
import { GetModel, GetVehicleList } from "@/api/vehicle";
import { removeNull } from "@/utils/utils";
import newGroupDialog from "./newGroupDialog.vue";
import newTicketDialog from "./newTicketDialog.vue";
export default {
data() {
return {
brandList: [],
modelCodeList: [],
modelYearList: [],
adsVersionList: [],
onlineStatusList: [
{ code: "1", name: "Off" },
{ code: "0", name: "On" },
],
brand: "",
modelCode: "",
modelYear: "",
adsVersion: "",
vin: "",
existGroup: "",
onlineStatus: "",
tableData: [],
maxHeight: 0,
page: 1,
limit: 10,
total: 0,
totalPage: 10,
isaddGroup: false,
groupList: [],
ticketList: [],
};
},
components: { newGroupDialog, newTicketDialog },
created() {
this.getModel();
this.getTableData();
// this.getMaxHeight();
// window.addEventListener('resize', () => {
// this.getMaxHeight();
// })
},
methods: {
getModel() {
GetModel().then((res) => {
this.modelCodeList = removeNull(res.data.modelCode);
this.modelYearList = removeNull(res.data.modelYear);
this.adsVersionList = removeNull(res.data.adsVersion);
this.brandList = removeNull(res.data.brand);
});
},
getTableData() {
let {
brand,
modelCode,
modelYear,
adsVersion,
vin,
existGroup,
onlineStatus,
page,
limit,
} = this;
GetVehicleList({
brand,
modelCode,
modelYear,
adsVersion,
vin,
existGroup,
onlineStatus,
currentPage: page,
pageSize: limit,
}).then((res) => {
this.tableData = res.data.records;
this.total = res.data.total;
this.totalPage = res.data.pages;
});
},
addTicket(additem) {
let isadd = true;
this.ticketList.map((item, index) => {
if (item.vin == additem.vin) {
isadd = false;
console.log("index", index);
this.ticketList.splice(index, 1);
// this.removeItem()
}
});
if (isadd) {
this.ticketList.push(additem);
}
},
addGroup(additem) {
let isadd = true;
this.groupList.map((item, index) => {
if (item.vin == additem.vin) {
isadd = false;
this.groupList.splice(index, 1);
}
});
if (isadd) {
this.groupList.push(additem);
}
},
showGroupDialog() {
let {
brand,
modelCode,
modelYear,
adsVersion,
vin,
existGroup,
onlineStatus,
groupList,
} = this;
let data = {
brand,
modelCode,
modelYear,
adsVersion,
vin,
existGroup,
onlineStatus,
groupList,
};
this.$refs.newGroupDialog.show(data);
},
showTickDialog() {
if (this.ticketList.length == 0) {
this.$message.info("please add frist Vehile");
return;
}
let data = { ticketList: this.ticketList };
this.$refs.newTicketDialog.show(data);
},
search() {
let {
brand,
modelCode,
modelYear,
adsVersion,
vin,
existGroup,
onlineStatus,
page,
limit,
} = this;
if (
brand ||
modelCode ||
modelYear ||
adsVersion ||
vin ||
existGroup ||
onlineStatus
) {
this.isaddGroup = true;
}
this.ticketList = [];
this.groupList = [];
this.page = 1;
this.getTableData();
},
reset() {
this.brand = "";
this.modelCode = "";
this.modelYear = "";
this.adsVersion = "";
this.vin = "";
this.existGroup = "";
this.onlineStatus = "";
this.page = 1;
this.ticketList = [];
this.groupList = [];
this.isaddGroup = false;
this.getTableData();
},
handleCurrentChange(val) {
this.page = val;
this.getTableData();
},
handleSizeChange(val) {
this.limit = val;
this.handleCurrentChange(1);
},
getMaxHeight() {
let bodyHeight =
document.documentElement.clientHeight || document.body.clientHeight;
this.maxHeight = bodyHeight - 194;
},
getIsColor(vin, list) {
let isin = false;
list.map((item) => {
if (item.vin == vin) {
isin = true;
}
});
if (isin) {
return "#0070fb";
} else {
return "#4a4a4c";
}
},
},
};
</script>

View File

@@ -0,0 +1,82 @@
<template>
<el-dialog title="New Group" :visible.sync="groupDialogVisible" width="30%">
<div class="dialog-content">
<div class="detail">
<p><span>Brand:</span>{{ data.brand }}</p>
<p><span>Model COde:</span>{{ data.modelCode }}</p>
<p><span>Model Year:</span>{{ data.modelYear }}</p>
<p><span>ADS Version:</span> {{ data.adsVersion }}</p>
<p>
<span>Online status:</span>
{{ data.onlineStatus == "0" ? "On" : "Off" }}
</p>
<p>
<span>Vehicle Length:</span>{{ data.groupList ? data.groupList.length : "0" }}
</p>
</div>
<div class="form-item">
<label>Group Name:</label>
<el-input v-model="groupName" clearable />
</div>
<el-checkbox v-model="isAutomatic">Automatic updates</el-checkbox>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="groupDialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="submit()">Submit</el-button>
</span>
</el-dialog>
</template>
<script>
import { PostcreateGroup } from "@/api/vehicle";
import { removeNull } from "@/utils/utils";
export default {
data() {
return {
groupDialogVisible: false,
groupName: "",
isAutomatic: false,
data: {},
};
},
methods: {
show(data) {
this.groupName = "";
this.data = data;
this.groupDialogVisible = true;
},
submit() {
let vehicles = this.data.groupList.map((item) => {
return item.vin;
});
if (!this.groupName) {
this.$message.info("plesase input groupName");
}
let data = {
groupName: this.groupName,
autoUpdate: this.isAutomatic ? "0" : "1",
comments: "",
vehicles: vehicles,
additionalProperties: {
brand: this.data.brand,
modelCode: this.data.modelCode,
modelYear: this.data.modelYear,
adsVersion: this.data.adsVersion,
existGroup: this.data.existGroup,
onlineStatus: this.data.onlineStatus,
vin: this.data.vin,
},
};
PostcreateGroup(data).then((res) => {
if (res.httpCode == 200) {
this.groupDialogVisible = false;
this.$message.success("add success.");
this.$emit('addSuccess')
} else {
this.$message.success(res.msg);
}
});
},
},
};
</script>

View File

@@ -0,0 +1,66 @@
<template>
<el-dialog title="New Ticket" :visible.sync="ticketDialogVisible" width="30%">
<div class="dialog-content">
<div class="form-item">
<label>Ticket Name:</label>
<el-input v-model="ticketName" clearable />
</div>
<div class="form-item">
<label>comment:</label>
<el-input v-model="comment" type="textarea" clearable />
</div>
<div class="form-item flex-row-center">
<label>Vehicle Length:</label>
{{ data.ticketList ? data.ticketList.length :'' }}
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="ticketDialogVisible = false">Cancel</el-button>
<el-button type="primary" @click="submit()">Submit</el-button>
</span>
</el-dialog>
</template>
<script>
import { PostaddTicket } from "@/api/vehicle";
import { removeNull } from "@/utils/utils";
export default {
data() {
return {
ticketDialogVisible: false,
ticketName: "",
comment: "",
data: {},
};
},
methods: {
show(data) {
this.ticketName = "";
this.comment = "";
this.data = data;
this.ticketDialogVisible = true;
},
submit() {
let vehicles = this.data.ticketList.map((item) => {
return item.vin;
});
if (!this.ticketName) {
this.$message.info("plesase input ticketName");
}
let data = {
ticketName: this.ticketName,
vinList: vehicles,
comments: this.comment,
};
PostaddTicket(data).then((res) => {
if (res.httpCode == 200) {
this.ticketDialogVisible = false;
this.$message.success("add success.");
this.$emit("addSuccess");
} else {
this.$message.success(res.msg);
}
});
},
},
};
</script>

3
mdmds/vue.config.js Normal file
View File

@@ -0,0 +1,3 @@
module.exports = {
publicPath: './'
}

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