Initial commit

main
2025 2 weeks ago
commit 250497377b
  1. 25
      .gitignore
  2. 11
      folder-alias.json
  3. 20
      package.json
  4. 4
      server/.babelrc
  5. 118
      server/.gitignore
  6. 99
      server/app.js
  7. 89
      server/bin/www
  8. 17
      server/build/package.json
  9. 30
      server/config.js
  10. 50
      server/doc/demo.bpmn
  11. 51682
      server/doc/openapi.json
  12. 25
      server/mysql/index.js
  13. 57
      server/package.json
  14. 1
      server/public/client_secret_65397196788-vap9su79jhke0036bkes9dso2hto1uk5.apps.googleusercontent.com.json
  15. 8
      server/public/stylesheets/style.css
  16. 501
      server/routes/camunda.js
  17. 38
      server/routes/center.js
  18. 118
      server/routes/client.js
  19. 74
      server/routes/client.ts
  20. 334
      server/routes/form.js
  21. 6
      server/views/error.jade
  22. 5
      server/views/index.jade
  23. 7
      server/views/layout.jade
  24. 60
      server/webpack.config.js
  25. 10
      web/.env.development
  26. 14
      web/.env.production
  27. 12
      web/.env.staging
  28. 4
      web/README.md
  29. 31
      web/docs/.vitepress/cache/deps/_metadata.json
  30. 9510
      web/docs/.vitepress/cache/deps/chunk-MWNQXFLB.js
  31. 7
      web/docs/.vitepress/cache/deps/chunk-MWNQXFLB.js.map
  32. 3
      web/docs/.vitepress/cache/deps/package.json
  33. 4512
      web/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js
  34. 7
      web/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map
  35. 9370
      web/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js
  36. 7
      web/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map
  37. 295
      web/docs/.vitepress/cache/deps/vue.js
  38. 7
      web/docs/.vitepress/cache/deps/vue.js.map
  39. 76
      web/docs/.vitepress/config.js
  40. 16
      web/docs/.vitepress/theme/index.js
  41. BIN
      web/docs/assets/build.jpg
  42. BIN
      web/docs/assets/process.png
  43. BIN
      web/docs/assets/server.jpg
  44. 105
      web/docs/components/demo.md
  45. 19
      web/docs/components/form/editor.md
  46. 69
      web/docs/components/form/introduce.md
  47. 86
      web/docs/components/form/normal.md
  48. 21
      web/docs/components/form/radio.md
  49. 49
      web/docs/components/form/select.md
  50. 21
      web/docs/components/form/tag-input.md
  51. 27
      web/docs/components/form/tree.md
  52. 159
      web/docs/components/form/upload.md
  53. 32
      web/docs/components/method/dateformatter.md
  54. 8
      web/docs/components/method/getOptionByKey.md
  55. 8
      web/docs/components/method/getUuid.md
  56. 8
      web/docs/components/method/hasEmoji.md
  57. 8
      web/docs/components/method/introduce.md
  58. 15
      web/docs/components/method/setObjectValue.md
  59. 30
      web/docs/components/method/setOptions.md
  60. 8
      web/docs/components/method/setValueByKey.md
  61. 5
      web/docs/components/module/child-page.md
  62. 19
      web/docs/components/module/control.md
  63. 49
      web/docs/components/module/dialog.md
  64. 37
      web/docs/components/module/filter.md
  65. 201
      web/docs/components/module/page.md
  66. 28
      web/docs/components/module/table.md
  67. 34
      web/docs/components/normal/bpmn.md
  68. 124
      web/docs/components/options.md
  69. 105
      web/docs/demo-doc/demo.md
  70. 114
      web/docs/index.md
  71. 296
      web/docs/workflow/camunda.md
  72. 46
      web/html/ie.html
  73. 215
      web/index.html
  74. 62
      web/package.json
  75. BIN
      web/public/favicon.ico
  76. 33
      web/src/App.vue
  77. 59
      web/src/api/login.js
  78. 9
      web/src/api/menu.js
  79. 57
      web/src/api/monitor/cache.js
  80. 71
      web/src/api/monitor/job.js
  81. 26
      web/src/api/monitor/jobLog.js
  82. 34
      web/src/api/monitor/logininfor.js
  83. 18
      web/src/api/monitor/online.js
  84. 26
      web/src/api/monitor/operlog.js
  85. 9
      web/src/api/monitor/server.js
  86. 60
      web/src/api/system/config.js
  87. 55
      web/src/api/system/dept.js
  88. 52
      web/src/api/system/dict/data.js
  89. 60
      web/src/api/system/dict/type.js
  90. 63
      web/src/api/system/menu.js
  91. 44
      web/src/api/system/notice.js
  92. 48
      web/src/api/system/post.js
  93. 122
      web/src/api/system/role.js
  94. 137
      web/src/api/system/user.js
  95. 76
      web/src/api/tool/gen.js
  96. 433
      web/src/api/workflow.js
  97. BIN
      web/src/assets/401_images/401.gif
  98. BIN
      web/src/assets/404_images/404.png
  99. BIN
      web/src/assets/404_images/404_cloud.png
  100. 1
      web/src/assets/icons/svg/404.svg
  101. Some files were not shown because too many files have changed in this diff Show More

25
.gitignore vendored

@ -0,0 +1,25 @@
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
.history
/server/form
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock

@ -0,0 +1,11 @@
{
"server/config.js": {
"description": "配置文件"
},
"server/build": {
"description": "构建配置"
},
"server/form": {
"description": "流程表单配置"
}
}

@ -0,0 +1,20 @@
{
"name": "zilber-admin",
"version": "1.0.0",
"description": "致博后台管理系统",
"author": "ChengYu",
"license": "MIT",
"scripts": {
"dev": "concurrently \"npm run dev --prefix web\" \"npm run start --prefix server\"",
"preinstall": "npm install --prefix web && npm install --prefix server",
"build:web": "npm run build:prod --prefix web",
"build:server": "npm run build --prefix server",
"build": "npm-run-all build:web build:server",
"docs:dev": "cd web && vitepress dev docs",
"docs:build": "cd web && vitepress build docs"
},
"devDependencies": {
"concurrently": "^9.1.1",
"npm-run-all": "^4.1.5"
}
}

@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-proposal-optional-chaining"]
}

118
server/.gitignore vendored

@ -0,0 +1,118 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
front-dist
backend-dist
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

@ -0,0 +1,99 @@
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var bodyParser = require('body-parser');
const fs = require("fs");
var formRouter = require('./routes/form');
var processRouter = require('./routes/camunda');
var proxy = require('express-http-proxy');
let config = require('./config');
var app = express();
app.all('*', function(req, res, next) {
  //设置允许跨域的域名,*代表允许任意域名跨域
res.header("Access-Control-Allow-Origin", "*");
// res.header("Access-Control-Allow-Origin", "http://www.zilber.cn");
  //允许的header类型
res.header('Access-Control-Allow-Headers', 'Content-type, Authorization');
  //跨域允许的请求方式
res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS,PATCH");
  //可选,用来指定本次预检请求的有效期,单位为秒。在此期间,不用发出另一条预检请求。
res.header('Access-Control-Max-Age',1728000);//预请求缓存20天
if (req.method === 'OPTIONS') {
res.sendStatus(200); // 对于 OPTIONS 预检请求,返回200状态码并结束响应
return;
}
next();
});
app.use(bodyParser.json({limit: '50000mb'}));
app.use(bodyParser.urlencoded({limit: '50000mb', extended: true}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use('/resource',express.static(path.join(__dirname, './resource')));
app.use('/front',express.static(path.join(__dirname, './dist')));
// app.use('/resource',express.static(path.join(__dirname, 'resource')));
app.use('/process/form', formRouter);
app.use('/process', processRouter);
app.use('/ry', proxy(config.server.ryRedirect));
app.get('/',function(req,res){
var url = req.url;
//res.send("Hello word");
// const filePath = path.join(__dirname,'./front-dist/index.html')
const filePath = path.join(__dirname, '.', 'dist', 'index.html');
console.log(filePath);
fs.readFile( filePath , function(err,data){
/*
一参为文件路径
二参为回调函数
回调函数的一参为读取错误返回的信息返回空就没有错误
二参为读取成功返回的文本内容
*/
if(err){
res.writeHeader(404,{
'content-type' : 'text/html;charset="utf-8"'
});
res.write('<h1>404错误</h1><p>你要找的页面不存在</p>');
res.end();
}else{
res.writeHeader(200,{
'content-type' : 'text/html;charset="utf-8"'
});
res.write(data);//将index.html显示在客户端
res.end();
}
});
})
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
// init();
module.exports = app;

@ -0,0 +1,89 @@
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('medicine-serve:server');
var http = require('http');
var config = require('../config');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(config.port || process.env.PORT || '3095');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

@ -0,0 +1,17 @@
{
"name": "server",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./server"
},
"dependencies": {
},
"devDependencies": {
"axios": "^1.7.9",
"jade": "~1.11.0",
"mysql": "^2.18.1"
}
}

@ -0,0 +1,30 @@
module.exports = {
env: {
development: {
formBaseURL: 'dev-api',
// 其他开发环境配置
},
test: {
baseURL: 'http://test.example.com/api',
// 其他测试环境配置
},
production: {
baseURL: 'http://example.com/api',
// 其他生产环境配置
}
},
database: {//数据库配置
host: '39.100.74.100',
port: '3306',
user: 'root',
password: 'root@123456',
database: 'zilberboot'
},
server: {
port: 3095,
ryRedirect: 'http://39.100.74.100:8080' //重定向java服务地址
},
camunda: {
base: `http://39.100.90.227:1111` //camunda服务地址
},
};

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="Definitions_0001" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:process id="test_form" name="测试流程带表单" isExecutable="true" camunda:historyTimeToLive="30">
<bpmn:startEvent id="Event_0qg2uvh">
<bpmn:outgoing>Flow_0j4azlb</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="Task_1" name="用户任务" camunda:candidateUsers="user1,user2" camunda:formKey="testForm1">
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_0j4azlb" sourceRef="Event_0qg2uvh" targetRef="Task_1" />
<bpmn:userTask id="Task_2" name="用户组任务" camunda:candidateGroups="group1,group2" camunda:formKey="testForm2">
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_12flmok" sourceRef="Task_1" targetRef="Task_2" />
<bpmn:endEvent id="Event_0ap0ojz">
<bpmn:incoming>Flow_1hcelfi</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1hcelfi" sourceRef="Task_2" targetRef="Event_0ap0ojz" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1705995903054">
<bpmndi:BPMNShape id="Event_0qg2uvh_di" bpmnElement="Event_0qg2uvh">
<dc:Bounds x="322" y="182" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="458" y="140" width="120" height="120" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_2_di" bpmnElement="Task_2">
<dc:Bounds x="678" y="140" width="120" height="120" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ap0ojz_di" bpmnElement="Event_0ap0ojz">
<dc:Bounds x="898" y="182" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0j4azlb_di" bpmnElement="Flow_0j4azlb">
<di:waypoint x="358" y="200" />
<di:waypoint x="458" y="200" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_12flmok_di" bpmnElement="Flow_12flmok">
<di:waypoint x="578" y="200" />
<di:waypoint x="678" y="200" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1hcelfi_di" bpmnElement="Flow_1hcelfi">
<di:waypoint x="798" y="200" />
<di:waypoint x="898" y="200" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,25 @@
let mysql = require('mysql')
var config = require('../config');
let pool = mysql.createPool(config.database)
function doSQL (sql, params = []) {
// console.log('执行sql语句:'+sql);
return new Promise((resolve, reject) => {
pool.getConnection(function (error, connection) {
if (error) {
reject(error)
} else {
connection.query(sql, params, function (err, data, fields) {
connection.release()
resolve({ err, data, fields })
})
}
})
})
}
let MYSQL = {
pool,
doSQL
}
module.exports = MYSQL;

@ -0,0 +1,57 @@
{
"name": "server",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "cross-env NODE_ENV=development node ./bin/www",
"build": "cross-env NODE_ENV=production npx -y webpack"
},
"dependencies": {
"@alicloud/dm20151123": "1.0.5",
"@alicloud/openapi-client": "^0.4.4",
"@alicloud/tea-console": "^1.0.0",
"@alicloud/tea-typescript": "^1.7.1",
"@alicloud/tea-util": "^1.4.5",
"@elastic/elasticsearch": "^8.0.0",
"archiver": "^5.3.0",
"axios": "^1.7.9",
"body-parser": "^1.19.0",
"cookie-parser": "~1.4.4",
"date-formatter-cy": "^1.0.0",
"debug": "~2.6.9",
"express": "~4.16.1",
"express-formidable": "^1.2.0",
"express-http-proxy": "^2.1.1",
"formidable": "^2.0.1",
"google-auth-library": "^8.8.0",
"http-errors": "~1.6.3",
"http-proxy-middleware": "^3.0.3",
"jade": "~1.11.0",
"jszip": "^3.7.1",
"leven": "^4.0.0",
"morgan": "~1.9.1",
"multiparty": "^4.2.2",
"mysql": "^2.18.1",
"node-cache": "^5.1.2",
"node-zip": "^1.1.1",
"nodemailer": "^6.9.2",
"request": "^2.88.2",
"string-similarity": "^4.0.4"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/preset-env": "^7.26.0",
"babel-loader": "^8.4.1",
"browserify-zlib": "^0.2.0",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3",
"path-browserify": "^1.0.1",
"querystring-es3": "^0.2.1",
"stream-browserify": "^3.0.0",
"url": "^0.11.4",
"webpack": "^4.47.0",
"webpack-cli": "^3.3.12"
}
}

@ -0,0 +1 @@
{"web":{"client_id":"65397196788-vap9su79jhke0036bkes9dso2hto1uk5.apps.googleusercontent.com","project_id":"shuxinxiaozhushou","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"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","javascript_origins":["https://www.zilber.cn"]}}

@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

@ -0,0 +1,501 @@
var express = require('express');
var router = express.Router();
var doSQL = require('../mysql/index').doSQL;
const axios = require('axios');
const { createProxyMiddleware, responseInterceptor } = require('http-proxy-middleware');
const fs = require("fs");
var path = require("path");
var config = require('../config');
const baseURL = config.camunda.base;
//1.获取最新流程定义列表
router.get('/engine-rest/process-definition', function(request, response) {
//查询流程总数量
axios.get(baseURL + '/engine-rest/process-definition/count?latestVersion=true').then(res=>{
let count = res.data.count;
doRequest(request).then(data=>{
response.send({
code: 200,
data: {
list: data,
total: count
}
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
})
});
//2.获取流程实例列表
router.get('/engine-rest/history/process-instance', function(request, response) {
let processDefinitionKey = request.query.processDefinitionKey;
let finished = request.query.finished;
//查询实例总数量
axios.get(baseURL + `/engine-rest/history/process-instance/count`,{
params: {
processDefinitionKey,
finished
}
}).then(res=>{
let count = res.data.count;
doRequest(request).then(data=>{
response.send({
code: 200,
data: {
list: data,
total: count
}
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
})
});
//3.获取流程实例任务列表
router.get('/engine-rest/history/task', function(request, response) {
let processInstanceId = request.query.processInstanceId;
//查询实例任务总数量
axios.get(baseURL + `/engine-rest/history/task/count?processInstanceId=${processInstanceId}`).then(res=>{
let count = res.data.count;
doRequest(request).then(data=>{
//如果任务已完成,获取任务评论
let promiseArr = [];
data.forEach(item=> {
promiseArr.push(axios.get(baseURL + `/engine-rest/task/${item.id}/comment`))
})
Promise.all(promiseArr).then(res=>{
res.forEach((item,index)=> {
data[index].comment = item.data;
})
response.send({
code: 200,
data: {
list: data,
total: count
}
});
})
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
})
});
//4.添加候选人
router.post('/engine-rest/task/:taskId/identity-links', function(request, response) {
doRequest(request).then(data=>{
response.send({
code: 200
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
});
//5.移除候选人
router.post('/engine-rest/task/:taskId/identity-links/delete', function(request, response) {
doRequest(request).then(data=>{
response.send({
code: 200
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
});
function getProcessInstanceDetail(taskInfo,id) {
return new Promise((resolve, reject) => {
const query = `
SELECT wi.form_id, wi.process_key, wi.form_values, wi.process_instance_id, wi.status, wf.form_name, wf.form_desc, wf.form_path
FROM workflow_instance wi
LEFT JOIN workflow_form wf ON wf.id = wi.form_id
WHERE process_instance_id = ?
`;
const params = [id];
doSQL(query, params).then(res=>{
let data = res.data[0];
axios.get(baseURL + `/engine-rest/process-definition/${taskInfo.processDefinitionId}`)
.then(res=>{
let processInfo = res.data;
if(data){
try {
let form = require(path.join(__dirname, '../' + data.form_path));
resolve({
processName: processInfo.name,
...taskInfo,
...data,
form: form.formFields
})
} catch (error) {
resolve({
processName: processInfo.name,
...taskInfo,
...data
})
}
// fs.readFile(path.join(__dirname, '../' + data.form_path), 'utf-8', function(err, form) {
// if (err) {
// resolve({
// processName: processInfo.name,
// ...taskInfo,
// ...data
// })
// } else {
// resolve({
// processName: processInfo.name,
// ...taskInfo,
// ...data,
// form: JSON.parse(form)
// })
// }
// });
} else {
resolve({
processName: processInfo.name,
...taskInfo
})
}
})
.catch(error => {
// 处理 axios 错误
resolve({
...taskInfo
})
});
}).catch(error => {
// 处理 doSQL 错误
reject(error);
});
});
}
//6.查询候选任务
router.get('/engine-rest/task', async function(request, response) {
let candidateUser = request.query.candidateUser;
let assignee = request.query.assignee;
let candidateGroup = request.query.candidateGroup;
try {
let count = 0;
let primiseArr = [];
if(!candidateUser && !assignee && !candidateGroup){
res = await axios.get(baseURL + '/engine-rest/task/count');
count = res.data.count;
const data = await doRequest(request);
// 按照 created 字段倒序排序
data.sort((a, b) => new Date(b.created) - new Date(a.created));
data.forEach(item => {
primiseArr.push(getProcessInstanceDetail(item, item.processInstanceId));
});
}else{
// 如果传入了参数,分别查询
let requests = [];
if (candidateUser) {
requests.push(axios.get(baseURL + '/engine-rest/task', {
params: { candidateUser: candidateUser }
}));
}
if (assignee) {
requests.push(axios.get(baseURL + '/engine-rest/task', {
params: { assignee: assignee }
}));
}
if (candidateGroup) {
requests.push(axios.get(baseURL + '/engine-rest/task', {
params: { candidateGroup: candidateGroup }
}));
}
// 等待所有请求完成
const responses = await Promise.all(requests);
// 合并所有请求的结果
let tasks = [];
responses.forEach(response => {
tasks = tasks.concat(response.data);
});
// 去重
const uniqueTasks = tasks.filter((task, index, self) =>
index === self.findIndex((t) => (
t.id === task.id
))
);
// 按照 created 字段倒序排序
uniqueTasks.sort((a, b) => new Date(b.created) - new Date(a.created));
// 分页处理
let firstResult = request.query.firstResult || 0;
let maxResults = request.query.maxResults || 10;
const paginatedTasks = uniqueTasks.slice(firstResult, firstResult + maxResults);
// 获取任务总数
count = uniqueTasks.length;
// 获取任务详情
paginatedTasks.forEach(item => {
primiseArr.push(getProcessInstanceDetail(item, item.processInstanceId));
});
}
const results = await Promise.all(primiseArr);
response.send({
code: 200,
data: {
list: results,
total: count
}
});
} catch (err) {
console.error('Error:', err);
response.send({
code: 600,
message: err.message || '处理请求时发生错误'
});
}
});
//7.领取任务
router.post('/engine-rest/task/:taskId/claim', function(request, response) {
doRequest(request).then(data=>{
response.send({
code: 200
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
});
//8.取消执行人
router.post('/engine-rest/task/:taskId/unclaim', function(request, response) {
doRequest(request).then(data=>{
response.send({
code: 200
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
});
//9.执行任务
router.post('/engine-rest/task/:taskId/complete', function(request, response) {
doRequest(request).then(data=>{
response.send({
code: 200
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
});
//10.设置任务评论
router.post('/engine-rest/task/:taskId/comment/create', function(request, response) {
doRequest(request).then(data=>{
response.send({
code: 200
});
}).catch(err=>{
response.send(JSON.stringify({
code: 600,
message: err
}));
})
});
/**
* 创建自定义流程实例
* @description 本系统内生成流程实例并启动camunda流程--------标准需要开事务
* @
*/
router.post('/start', function(request, response) {
//1. 启动camunda流程
let processKey = request.body.processKey;
let createUser = request.body.createUser;
//1.本系统内生成流程实例
let formId = request.body.formId;
let formValues = request.body.formValues;
const query = `
INSERT INTO workflow_instance (form_id, process_key, process_instance_id, form_values, status, create_user)
VALUES (?, ?, NULL, ?, 1, ?)
`;
const params = [formId, processKey, formValues, createUser];
doSQL(query,params).then(res=>{
if(!res.error){
//获取刚刚插入的id
const insertId = res.data.insertId;
axios.post(baseURL + `/engine-rest/process-definition/key/${processKey}/start`, {
businessKey: insertId
}).then(res=>{
let processInstanceId = res.data.id;
if( processInstanceId ){
//2. 创建本系统内的流程实例包含form_id,process_instance_id,status
const query = `UPDATE workflow_instance SET process_instance_id = ? WHERE id = ?`;
const params = [processInstanceId, insertId];
doSQL(query, params).then(res=>{
if(!res.error){
response.send({
code: 200,
message: '启动流程成功'
});
}else{
const query = `delete from workflow_instance where id = ?`;
const params = [insertId];
doSQL(query, params).then(res=>{
response.send({
code: 600,
message: '启动流程失败'
});
})
}
})
}else {
response.send({
code: 600,
message: '启动流程失败'
});
}
})
}else{
response.send({
code: 600,
message: '启动流程失败'
});
}
})
});
router.use('/', createProxyMiddleware({
target: baseURL,
changeOrigin: true, // 确保请求头正确
pathRewrite: {
'^/process': '', // 去掉路径前缀
},
timeout: 60000, // 设置超时为60秒
selfHandleResponse: true,
on: {
// proxyReq: (proxyReq, req, res) => {
// console.log('代理请求发送到目标服务器:', proxyReq.path);
// console.log('请求头:', proxyReq.getHeaders());
// },
proxyRes: responseInterceptor(async (responseBuffer, proxyRes, req, res) => {
const response = responseBuffer.toString('utf8'); // convert buffer to string
let originalData;
try {
originalData = response ? JSON.parse(response) : {}; // parse JSON
} catch (error) {
originalData = {
code: 500,
message: 'Error parsing response',
data: response
};
}
//异常特殊处理
if(originalData?.message?.includes('ENGINE-03076')){
return JSON.stringify({
code: 503,
msg: '流程存在启用的实例,无法删除!'
});
}else{
let statusCode = proxyRes.statusCode == 204 ? 200 : proxyRes.statusCode;
const wrappedData = {
code: originalData.code || statusCode, // 包含 HTTP 状态码
data: originalData.data || originalData, // 返回的实际数据
timestamp: new Date().toISOString(), // 添加时间戳
};
return JSON.stringify(wrappedData);
}
}),
error: (err, req, res) => {
console.error('Proxy error:', err);
res.status(500).json({
code: 500,
message: 'Proxy request failed',
error: err.message
});
}
},
}));
// function proxyDataResolver(fn) {
// return proxy(baseURL + '', {
// proxyReqPathResolver: function (req) {
// // 移除请求路径中的开头 /process
// return req.url.replace(/^\/process/, '');
// },
// proxyReqOptDecorator: function(proxyReqOpts, req) {
// // 确保请求头中包含正确的 Content-Type
// if (req.headers['content-type']) {
// proxyReqOpts.headers['Content-Type'] = req.headers['content-type'];
// }
// return proxyReqOpts;
// },
// userResDecorator: function (proxyRes, proxyResData, req, res) {
// try {
// // 处理返回的结果
// const originalData = JSON.parse(proxyResData.toString('utf8')); // 解析 JSON
// const wrappedData = {
// code: originalData.code || proxyRes.statusCode, // 包含 HTTP 状态码
// data: fn ? fn(originalData) : (originalData.data || originalData), // 返回的实际数据
// timestamp: new Date().toISOString(), // 添加时间戳
// };
// return JSON.stringify(wrappedData); // 转换为字符串返回
// } catch (error) {
// console.error('Error processing response:', error);
// return proxyResData; // 如果解析失败,直接返回原始数据
// }
// },
// })
// }
function doRequest(req) {
return new Promise((resolve, reject) => {
// 构建请求选项
const url = baseURL + `${req.url.replace(/^\/process/, '')}`;
axios({
url: url,
method: req.method,
data: req.body,
})
.then(response => {
resolve(response.data,response)
})
.catch(error => {
reject(error)
});
});
}
module.exports = router;

@ -0,0 +1,38 @@
const https = require('https');
let accessToken = null;
function requestAccessToken(){
// wxb9db86cc1e1b8ce7
// 7b693cd53613d50b2fa6a1faaa39b658
return new Promise((resolve,reject)=>{
https.get('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wxb9db86cc1e1b8ce7&secret=7b693cd53613d50b2fa6a1faaa39b658',function(res){
var html = '';
res.on('data',function(data){
html += data;
});
res.on('end',function(){
resolve(JSON.parse(html));
});
});
})
}
function init(){
requestAccessToken().then((res)=>{
accessToken = res.access_token;
console.log('accessToken',accessToken);
setTimeout(()=>{
init();
},(res.expires_in-200)*1000)
})
}
function getAccessToken(){
return accessToken;
}
module.exports = {
init,
getAccessToken
};

@ -0,0 +1,118 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
// This file is auto-generated, don't edit it
// 依赖的模块可通过下载工程中的模块依赖文件或右上角的获取 SDK 依赖信息查看
var dm20151123_1 = require("@alicloud/dm20151123"), $Dm20151123 = dm20151123_1;
var $OpenApi = require("@alicloud/openapi-client");
var tea_console_1 = require("@alicloud/tea-console");
var tea_util_1 = require("@alicloud/tea-util"), $Util = tea_util_1;
var Client = /** @class */ (function () {
function Client() {
}
/**
* 使用AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/
Client.createClient = function (accessKeyId, accessKeySecret) {
var config = new $OpenApi.Config({
// 必填,您的 AccessKey ID
accessKeyId: accessKeyId,
// 必填,您的 AccessKey Secret
accessKeySecret: accessKeySecret,
});
// 访问的域名
config.endpoint = "dm.aliyuncs.com";
return new dm20151123_1.default(config);
};
/**
* 使用STS鉴权方式初始化账号Client推荐此方式
* @param accessKeyId
* @param accessKeySecret
* @param securityToken
* @return Client
* @throws Exception
*/
Client.createClientWithSTS = function (accessKeyId, accessKeySecret, securityToken) {
var config = new $OpenApi.Config({
// 必填,您的 AccessKey ID
accessKeyId: accessKeyId,
// 必填,您的 AccessKey Secret
accessKeySecret: accessKeySecret,
// 必填,您的 Security Token
securityToken: securityToken,
// 必填,表明使用 STS 方式
type: "sts",
});
// 访问的域名
config.endpoint = "dm.aliyuncs.com";
return new dm20151123_1.default(config);
};
Client.main = function (type, content) {
return __awaiter(this, void 0, void 0, function () {
var client, singleSendMailRequest, runtime, resp;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
client = Client.createClient("LTAI5tEzKa1MzVZ1QAQPayJX", "77mXtGCkWYPWLyCBc0fyOQMLYjn7Dj");
singleSendMailRequest = new $Dm20151123.SingleSendMailRequest({
accountName: "sxxzs@email.zilber.cn",
addressType: 1,
replyToAddress: false,
toAddress: "529315546@qq.com",
// toAddress: "liujunhong@zilber.cn",
subject: type,
textBody: content,
});
runtime = new $Util.RuntimeOptions({});
return [4 /*yield*/, client.singleSendMailWithOptions(singleSendMailRequest, runtime)];
case 1:
resp = _a.sent();
tea_console_1.default.log(tea_util_1.default.toJSONString(resp));
return [2 /*return*/];
}
});
});
};
return Client;
}());
exports.default = Client;
// Client.main(process.argv.slice(2));

@ -0,0 +1,74 @@
// This file is auto-generated, don't edit it
// 依赖的模块可通过下载工程中的模块依赖文件或右上角的获取 SDK 依赖信息查看
import Dm20151123, * as $Dm20151123 from '@alicloud/dm20151123';
import OpenApi, * as $OpenApi from '@alicloud/openapi-client';
import Console from '@alicloud/tea-console';
import Util, * as $Util from '@alicloud/tea-util';
import * as $tea from '@alicloud/tea-typescript';
export default class Client {
/**
* 使AK&SK初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @return Client
* @throws Exception
*/
static createClient(accessKeyId: string, accessKeySecret: string): Dm20151123 {
let config = new $OpenApi.Config({
// 必填,您的 AccessKey ID
accessKeyId: accessKeyId,
// 必填,您的 AccessKey Secret
accessKeySecret: accessKeySecret,
});
// 访问的域名
config.endpoint = `dm.aliyuncs.com`;
return new Dm20151123(config);
}
/**
* 使STS鉴权方式初始化账号Client
* @param accessKeyId
* @param accessKeySecret
* @param securityToken
* @return Client
* @throws Exception
*/
static createClientWithSTS(accessKeyId: string, accessKeySecret: string, securityToken: string): Dm20151123 {
let config = new $OpenApi.Config({
// 必填,您的 AccessKey ID
accessKeyId: accessKeyId,
// 必填,您的 AccessKey Secret
accessKeySecret: accessKeySecret,
// 必填,您的 Security Token
securityToken: securityToken,
// 必填,表明使用 STS 方式
type: "sts",
});
// 访问的域名
config.endpoint = `dm.aliyuncs.com`;
return new Dm20151123(config);
}
static async main(type:String,content:String): Promise<void> {
// 工程代码泄露可能会导致AccessKey泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378664.html
let client = Client.createClient("LTAI5tEzKa1MzVZ1QAQPayJX", "77mXtGCkWYPWLyCBc0fyOQMLYjn7Dj");
let singleSendMailRequest = new $Dm20151123.SingleSendMailRequest({
accountName: "sxxzs@email.zilber.cn",
addressType: 1,
replyToAddress: false,
toAddress: "529315546@qq.com",
// toAddress: "liujunhong@zilber.cn",
subject: type,
textBody: content,
});
let runtime = new $Util.RuntimeOptions({ });
let resp = await client.singleSendMailWithOptions(singleSendMailRequest, runtime);
Console.log(Util.toJSONString(resp));
}
}
// Client.main(process.argv.slice(2));

@ -0,0 +1,334 @@
var express = require('express');
var router = express.Router();
var doSQL = require('../mysql/index').doSQL;
const fs = require("fs");
var path = require("path");
router.get('/', function(request, response, next) {
let page = request.query.page;
let limit = request.query.limit;
let formName = request.query.formName;
let sql = `
select id,form_name formName,original_type originalType,form_desc formDesc,form_path formPath,DATE_FORMAT(workflow_form.create_time,'%Y-%m-%d') createTime,create_user createUser,sys_user.nick_name createUserName
from workflow_form
left join sys_user on workflow_form.create_user = sys_user.user_id
`;
const params = [];
if (formName) {
sql += ` where form_name like ?`;
params.push(`%${formName}%`);
}
if (page) {
sql += ` limit ?, ?`;
params.push(+(page - 1) * limit, +limit);
}
getCount(sql, params).then(count => {
doSQL(sql, params).then(res => {
if(!res.error){
response.send(JSON.stringify({
code: 200,
data: {
list: res.data,
count: count
}
}));
}else{
response.send(JSON.stringify({
code: 600,
message: res.error
}));
}
})
})
});
// router.get('/fields', function(request, response, next) {
// let formPath = request.query.path;
// //读取文件
// try {
// let form = require(path.join(__dirname, '../' + formPath));
// response.send(JSON.stringify({
// code: 200,
// data: form.formFields
// }));
// } catch (error) {
// response.send(JSON.stringify({
// code: 500,
// msg: `未找到文件${formPath}`
// }));
// }
// // fs.readFile( path.join(__dirname, '../' + formPath), 'utf-8', function(err, data) {
// // if (err) {
// // response.send(JSON.stringify({
// // code: 500,
// // msg: `未找到文件${formPath}`
// // }));
// // } else {
// // let json = JSON.stringify(eval(data));
// // response.send(JSON.stringify({
// // code: 200,
// // data: json
// // }));
// // }
// // });
// });
router.get('/:id', function(request, response, next) {
let id = request.params.id;
let sql = `
select id,form_name formName,original_type originalType,form_desc formDesc,form_path formPath,DATE_FORMAT(workflow_form.create_time,'%Y-%m-%d') createTime,create_user createUser,sys_user.nick_name createUserName
from workflow_form
left join sys_user on workflow_form.create_user = sys_user.user_id
where id = ?
`;
const params = [id];
doSQL(sql, params).then(res=>{
if(!res.error){
let data = res.data[0] || {};
let formPath = data.formPath;
//读取文件
const fullPath = path.join(__dirname, '../' + formPath);
fs.readFile(fullPath, 'utf8',function(err,res){
if (err) {
data.content = [];
} else {
data.content = JSON.parse(res);
}
response.send(JSON.stringify({
code: 200,
data
}));
});
}else{
response.send(JSON.stringify({
code: 600,
message: res.error
}));
}
})
});
function addForm(fileName, content){
return new Promise((resolve, reject) => {
//检测文件是否存在
let isExist = fs.existsSync(path.join(__dirname, '../form', fileName + '.js'));
if(isExist){
reject('文件已存在');
}else{
fileName = fileName.indexOf('.js') > -1 ? fileName : fileName + '.js';
const filePath = path.join(__dirname, '../form', fileName );
fs.writeFile(filePath, content, 'utf8', (err) => {
if (err) {
reject(err);
}else{
resolve();
}
});
}
});
}
router.post('/', async function(request, response, next) {
let { formName, formDesc, userId,formPath = '',fileName,content,type = 1 } = request.body;
fileName = fileName || formName;
let errMsg = '';
if(!userId){
errMsg = '创建人不能为空'
}else if(!formName){
errMsg = '表单名称不能为空'
}else if(!formPath && ( !fileName || !content)){
errMsg = '文件名和表单内容不能为空'
}
if(errMsg){
response.send(JSON.stringify({
code: 600,
message: errMsg
}));
return;
}
if( !formPath ){
type = 2;
try {
await addForm(fileName, content)
formPath = `/form/${fileName}.js`;
} catch (err) {
response.send(JSON.stringify({
code: 600,
message: err
}));
return;
}
}
let sql = `
insert into workflow_form (form_name, form_desc, form_path, create_time, create_user, original_type)
values (?, ?, ?, now(), ?, ?)
`;
const params = [formName, formDesc, formPath, userId,type];
doSQL(sql, params).then(res=>{
if(!res.error){
response.send(JSON.stringify({
code: 200,
message: '新增成功'
}));
}else{
response.send(JSON.stringify({
code: 600,
message: res.error
}));
}
})
});
router.delete('/:id', function(request, response, next) {
let id = request.params.id;
let sql = `delete from workflow_form where id in (?)`;
const params = [id];
doSQL(sql, params).then(res=>{
if(!res.error){
response.send(JSON.stringify({
code: 200,
message: '删除成功'
}));
}else{
response.send(JSON.stringify({
code: 600,
message: res.error
}));
}
})
});
// router.put('/', function(request, response, next) {
// let { id, formName, formDesc, formPath, userId,content } = request.body;
// let sql = `
// update workflow_form
// set form_name = ?, form_desc = ?, form_path = ?
// where id = ?
// `;
// const params = [formName, formDesc, formPath, id];
// doSQL(sql, params).then(res => {
// if(!res.error) {
// response.send(JSON.stringify({
// code: 200,
// message: '修改成功'
// }));
// } else {
// response.send(JSON.stringify({
// code: 600,
// message: res.error
// }));
// }
// })
// });
router.put('/', async function(request, response, next) {
let { id, formName, formDesc, userId, content ,formPath} = request.body;
let errMsg = '';
if (!id) {
errMsg = 'ID 不能为空';
} else if (!userId) {
errMsg = '修改人不能为空';
} else if (!formName) {
errMsg = '表单名称不能为空';
}
if (errMsg) {
return response.status(400).send({
code: 400,
message: errMsg
});
}
try {
// 根据 id 查询原数据
const originalData = await doSQL('SELECT * FROM workflow_form WHERE id = ?', [id]);
if (originalData.data.length === 0) {
return response.status(404).send({
code: 404,
message: '未找到对应的表单'
});
}
const originalFormPath = originalData.data[0].form_path;
let newFormPath;
if( content && content.length > 0){
newFormPath = `/form/${formName}.js`;
if (originalFormPath !== newFormPath) {
// 检查新文件是否存在
const newFilePath = path.join(__dirname, '../', newFormPath);
if (fs.existsSync(newFilePath)) {
return response.status(400).send({
code: 400,
message: '表单名已存在'
});
}
}
// 删除原文件
if (originalFormPath) {
const filePath = path.join(__dirname, '../', originalFormPath);
fs.unlinkSync(filePath);
}
try {
await addForm(formName, content)
} catch (err) {
response.send(JSON.stringify({
code: 600,
message: err
}));
return;
}
}else{
newFormPath = formPath || originalFormPath;
}
// 更新数据库中的记录
const sql = `
update workflow_form
set form_name = ?, form_desc = ?, form_path = ?, create_time = ?, create_user = ?
where id = ?
`;
const params = [formName, formDesc, newFormPath, new Date(), userId, id];
const res = await doSQL(sql, params);
if (!res.error) {
response.send({
code: 200,
message: '修改成功'
});
} else {
response.send({
code: 600,
message: res.error
});
}
} catch (error) {
console.error('Failed to update form:', error);
response.status(500).send({
code: 500,
message: 'Failed to update form'
});
}
});
function getCount(sql, params) {
let countSql = `select count(1) count from (${sql.split('limit')[0]}) a`;
return new Promise((resolve, reject) => {
doSQL(countSql, params).then(res => {
resolve(res.data[0].count);
}).catch(err => {
reject(err);
});
});
}
module.exports = router;

@ -0,0 +1,6 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}

@ -0,0 +1,5 @@
extends layout
block content
h1= title
p Welcome to #{title}

@ -0,0 +1,7 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content

@ -0,0 +1,60 @@
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
// 入口文件,指定项目的起点
entry: './bin/www',
// 输出配置
output: {
// 输出文件的路径
path: path.resolve(__dirname, '../dist'),
// 输出文件名
filename: 'server.js'
},
externals: {
mysql: 'commonjs mysql',
axios: 'commonjs axios',
},
target: 'node',
node: {
__dirname: false,
fs: 'empty',
net: 'empty',
tls: 'empty',
},
// 模块规则,用于处理各种类型的文件
module: {
rules: [
{
// 处理JavaScript文件
test: /\.js$/,
// 排除node_modules目录下的文件
exclude: /node_modules\/(?!http-proxy-middleware)/, // 处理 http-proxy-middleware 包
// 使用babel-loader进行转译
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-optional-chaining']
}
}
},
{
// 处理CSS文件
test: /\.css$/,
// 使用style-loader和css-loader加载CSS
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin({
patterns: [
{ from: path.resolve(__dirname, '../web/dist'), to: path.resolve(__dirname, '../dist/dist') },
{ from: path.resolve(__dirname, './build'), to: path.resolve(__dirname, '../dist') }
]
})
]
};

@ -0,0 +1,10 @@
# 页面标题
VITE_APP_TITLE = 致博管理系统
# 开发环境配置
VITE_APP_ENV = 'development'
# 致博管理系统/开发环境
VITE_APP_BASE_API = '/dev-api'
# 开发环境-NODE-工作流接口
VITE_APP_BASE_PROCESS_API = 'http://127.0.0.1:3095/process'

@ -0,0 +1,14 @@
# 页面标题
VITE_APP_TITLE = 致博管理系统
# 生产环境配置
VITE_APP_ENV = 'production'
# 致博管理系统/生产环境
#VITE_APP_BASE_API = 'http://39.100.74.100:8080'
VITE_APP_BASE_API = 'http://127.0.0.1:3095/ry'
# 生产环境-NODE-工作流接口
VITE_APP_BASE_PROCESS_API = 'http://127.0.0.1:3095/process'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

@ -0,0 +1,12 @@
# 页面标题
VITE_APP_TITLE = 致博管理系统
# 生产环境配置
VITE_APP_ENV = 'staging'
# 致博管理系统/生产环境
VITE_APP_BASE_API = '/stage-api'
# 生产环境-NODE-工作流接口
VITE_APP_BASE_PROCESS_API = 'http://127.0.0.1:3095/process'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

@ -0,0 +1,4 @@
## 说明
基于致博的致博后台管理系统模板
## 使用文档
[文档地址](https://www.zilber.cn/doc/rp/)

@ -0,0 +1,31 @@
{
"hash": "9a4f7a36",
"configHash": "77c0909b",
"lockfileHash": "90b8159b",
"browserHash": "5e17b1da",
"optimized": {
"vue": {
"src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "9e0769ad",
"needsInterop": false
},
"vitepress > @vue/devtools-api": {
"src": "../../../../node_modules/vitepress/node_modules/@vue/devtools-api/dist/index.js",
"file": "vitepress___@vue_devtools-api.js",
"fileHash": "0b965065",
"needsInterop": false
},
"vitepress > @vueuse/core": {
"src": "../../../../node_modules/vitepress/node_modules/@vueuse/core/index.mjs",
"file": "vitepress___@vueuse_core.js",
"fileHash": "5204427c",
"needsInterop": false
}
},
"chunks": {
"chunk-MWNQXFLB": {
"file": "chunk-MWNQXFLB.js"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,3 @@
{
"type": "module"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,295 @@
import {
BaseTransition,
Comment,
EffectScope,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
Transition,
TransitionGroup,
VueElement,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineProps,
defineSSRCustomElement,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hydrate,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useSSRContext,
useSlots,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-MWNQXFLB.js";
export {
BaseTransition,
Comment,
EffectScope,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
Transition,
TransitionGroup,
VueElement,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineProps,
defineSSRCustomElement,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hydrate,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useSSRContext,
useSlots,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

@ -0,0 +1,7 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

@ -0,0 +1,76 @@
module.exports = {
base: '/doc/rp/',
title: 'zbn-admin文档',
description: '基于Vue3+Element-plus封装',
markdown: {
lineNumbers: true,
doc: {
includeLevel: [1,2,3]
}
},
themeConfig: {
search: true,
nav: [
{ text: '首页', link: '/' },
{ text: '组件', link: '/components/form/introduce' },
{ text: '工作流', link: '/workflow/camunda' },
{ text: '文件示例', link: '/demo-doc/demo' }
],
sidebar: {
'/components/': [
{
text: '表单组件',
items: [
{ text: '介绍', link: '/components/form/introduce' },
{ text: '动态form配置', link: '/components/form/normal' },
{ text: '下拉选', link: '/components/form/select' },
{ text: '单选', link: '/components/form/radio' },
{ text: '树形控件', link: '/components/form/tree' },
{ text: '标签', link: '/components/form/tag-input' },
{ text: '富文本', link: '/components/form/editor' },
{ text: '上传', link: '/components/form/upload' },
]
},
{
text: '常规组件',
items: [
{ text: '流程设计', link: '/components/normal/bpmn' },
]
},
{
text: '模块组件',
items: [
{ text: '弹出框', link: '/components/module/dialog' },
{ text: '搜索区域', link: '/components/module/filter' },
{ text: '页面操作区域', link: '/components/module/control' },
{ text: '表格', link: '/components/module/table' },
{ text: 'table页面', link: '/components/module/page' },
{ text: '树形-table', link: '/components/module/child-page' },
]
},
{
text: '公共方法',
items: [
{ text: '使用方式', link: '/components/method/introduce' },
{ text: '日期格式化', link: '/components/method/dateformatter' },
{ text: '获取选项', link: '/components/method/getOptionByKey' },
{ text: '选项赋值', link: '/components/method/setOptions' },
// { text: '动态数组赋值-object', link: '/components/method/setObjectValue' },
// { text: '动态数组赋值-key', link: '/components/method/setValueByKey' },
{ text: '获取uuid', link: '/components/method/getUuid' },
{ text: 'emoji判断', link: '/components/method/hasEmoji' },
]
},
],
'/demo-doc/': [
{
text: '文件示例',
items: [
{ text: '表单配置文件', link: '/demo-doc/demo#option-js' },
{ text: 'vue页面文件', link: '/demo-doc/demo#role-vue' },
]
},
],
}
}
}

@ -0,0 +1,16 @@
import DefaultTheme from "vitepress/theme";
// import "element-plus/dist/index.css";
// import '@vueup/vue-quill/dist/vue-quill.snow.css';
export default {
...DefaultTheme,
// enhanceApp: async ({ app, router, siteData, isServer }) => {
// import("element-plus").then((module) => {
// app.use(module);
// });
// import("@element-plus/icons-vue").then((module) => {
// app.use(module);
// });
// },
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@ -0,0 +1,105 @@
# 示例
## option.js
```javascript
export const baseModelOptions = [
{
tag: 'el-input',
label: '角色名称:',
key: 'name',
value: '',
default: '',
attribute: {//属性
type: 'text',
placeholder: '请输入角色名称',
},
},
{
tag: 'el-input',
label: '备注:',
key: 'remark',
value: '',
default: '',
attribute: {//属性
type: 'text',
placeholder: '请输入备注',
},
},
{
tag: 'BaseTree',
label: '菜单:',
key: 'menuIdList',
value: [],
default: [],
attribute: {//属性
defaultCheckedKey: [],
nodeKey: "id",
showCheckbox: true,
props: {label:'name'},
data: []
}
},
]
export const baseFilterOptions = [
{
tag: 'el-input',
label: '角色名称:',
key: 'name',
value: '',
attribute: {//属性
type: 'text',
placeholder: '请输入角色名称'
},
},
]
```
## role.vue
```html
<script setup>
import { reactive,ref,getCurrentInstance } from 'vue'
import {getRole,addRole,updateRole,deleteRole,getRoleDetail,getMenuList} from '@/api/data'
import {setOptions} from '@/utils'
import {baseModelOptions,baseFilterOptions} from './options'
const { proxy } = getCurrentInstance();
const state = reactive({
baseModelOptions: baseModelOptions,
baseFilterOptions: baseFilterOptions,
title: "角色管理",
baseModelName: '角色信息',
addBtnName: '添加角色',
primaryKey: 'id',
getTableFn: getRole,
addFn: addRole,
editFn: updateRole,
deleteFn: deleteRole,
detailFn: getRoleDetail,
multipleDelete: true,
pageInfo: { total: 0, base:{limit: 8,current: 1} }
})
getMenuList().then(res=>{
state.baseModelOptions = proxy.$util.setOptions({
data: state.baseModelOptions,//待赋值数据源
key:'station',//配置项的key
res//返回结果
})
})
</script>
<template>
<BaseTablePage
:tableOptions="state"
>
<template v-slot:column>
<el-table-column align="center" prop="name" label="名称" />
<el-table-column align="center" prop="remark" label="备注" />
</template>
</BaseTablePage>
</template>
<style scoped>
</style>
```

@ -0,0 +1,19 @@
# Editor - 富文本编辑器
基于 QuillEditor 封装,配置项参考 [quill](https://quilljs.com/docs/quickstart/ "https://quilljs.com/docs/quickstart/")<br>
<br/>
```javascript
动态组件配置示例:
{
tag: 'Editor',
label: '富文本:',
className: 'className',
value: '',
default: '',
contentType: 'html',//默认值,可不填写
uploadFn: (file,callback)=>{//自行实现上传图片,上传后调用callback(url)}
//toolbar: [],//工具栏,默认不需要传递
//modules: [],//模块,默认不需要传递
}
```

@ -0,0 +1,69 @@
# 动态Form
以配置数组的方式,动态生成相应form表单。
常用在模块组件中的BaseModel和BaseFilter。<br/>
通常情况,上述2个组件被封装在[BaseTablePage](../module/page.md)中。示例如下:
```javascript
//新增、修改弹窗配置项
export const baseModelOptions = [
{
tag: 'el-input',
label: '标题:',
key: 'name',
value: '',
default: '',
rules: [
{ required: true, message: '请输入标题', trigger: 'blur' },
],
attribute: {//属性
type: 'text',
placeholder: '请输入标题',
},
},
{
tag: 'BaseUpload',
label: '视频:',
key: 'videoUrl',
baseUrl,
value: [],
default: [],
width: '60%',
rules: [
{ required: true, message: '请上传视频', trigger: 'change' },
],
attribute: {//属性
limit: 1,
ref: 'uploadRef',
accept: '.wmv,.asf,.asx,.mp4,.m4v',
headers: {token: localGet('token')},
baseUrl: baseUrl+'/base/upload'//图片提交接口
},
}
]
//搜索区域配置项
export const baseFilterOptions = [
{
tag: 'el-input',
label: '标题:',
key: 'title',
value: '',
default: '',
attribute: {//属性
type: 'text',
placeholder: '请输入标题',
},
},
{
tag: 'BaseSelect',
label: '提案类别:',
key: 'type',
value: '',
default: '',
attribute: {//属性
placeholder: '请选择提案类别',
options: []
},
},
]
```

@ -0,0 +1,86 @@
# 通用配置
```javascript
// 通用配置
{
tag: '组件名称',
label: '示例:',
key: 'videoUrl',
value: '当前值',
default: '默认值',
rules: [//校验规则
{ required: true, message: '示例规则', trigger: 'change' },
],
attribute: {//属性
limit: 1,
},
event: {
'change': (data)=>{//参数内容,见使用的组件的事件
//doSomething
}
},
customFormatter: function(data){
if(data.length > 0) return data[0].response.url
}
}
```
## 属性
| 属性 | 描述 | 是否必填 | 类型 | 默认值 |
| :-------: | :----------------------------------------------------------------------------: | :------: | :----: | :----: |
| tag | 组件名称 | 是 | String | 无 |
| label | 标签描述文本 | 是 | String | 无 |
| key | 键名(取值/赋值使用) | 是 | String | 无 |
| value | 当前值(v-model 绑定值) | 是 | Any | '' |
| default | 默认值(取值时,若 value 为空取 default) | 是 | Any | '' |
| width | 组件宽度 | 否 | String | 80% |
| formItemWidth | formItem行宽度 | 否 | String | 50% |
| labelWidth | label宽度 | 否 | String | 30% |
| hide | 隐藏控件 | 否 | Boolean | false |
| disabled | 禁用控件 | 否 | Boolean | false |
| disEdit | 不进行修改([modelType](../module/dialog.md)=edit &&disEdit=true)自动进行隐藏 | 否 | Boolean | false |
| rules | form表单校验规则(提交表单时校验) [规则](https://element-plus.gitee.io/zh-CN/component/form.html#%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C "https://element-plus.gitee.io/zh-CN/component/form.html#%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C") | 否 | Array | '' |
| attribute | 组件属性(绑定到组件上的属性,等同于:attr="父子传值") | 否 | Object | 无 |
| event | 组件事件(绑定到组件上的属性,若为 event:{click:()=>{}}等同于@click="function") | 否 | Object | 无 |
| customFormatter | 自定义格式化(点击确定按钮后-调用提交接口前,触发) | 否 | Object | 无 |
## event
为组件绑定事件
```javascript
{
tag: 'BaseSelect',
label: '提案类别:',
key: 'type',
value: '',
default: '',
attribute: {//属性
placeholder: '请选择提案类别',
options: []
},
event: {
'change': (data)=>{//参数内容,见使用的组件的事件
//doSomething
}
}
}
```
## customFormatter
自定义格式化函数
```javascript
{
tag: 'BaseSelect',
label: '提案类别:',
key: 'type',
value: '',
default: '',
attribute: {//属性
placeholder: '请选择提案类别',
options: []
},
customFormatter: function(data){
//data 为v-model的值
//this 指向当前vue组件
return data[0].response.url
}
}
```

@ -0,0 +1,21 @@
# BaseRadioGroup - 单选
配置项参考 [el-radio-group](https://element-plus.gitee.io/zh-CN/component/radio.html "https://element-plus.gitee.io/zh-CN/component/radio.html")
```javascript
动态组件配置示例:
{
tag: 'BaseRadioGroup',
label: '性别:',
key: 'gender',
value: 1,
default: 1,
attribute: {//配置项内容
options:[
{label: '男',value: 1},
{label: '女',value: 2},
{label: '未知',value: 3},
]
}
}
```

@ -0,0 +1,49 @@
# BaseSelect - 下拉选
配置项参考 [el-select](https://element-plus.gitee.io/zh-CN/component/select.html "https://element-plus.gitee.io/zh-CN/component/select.html")
<br/>
BaseSelect/BaseTree 选项赋值,参考 util.js -> [setOptions](./../method/setOptions.md)
```javascript
//动态选项配置示例:
{
tag: 'BaseSelect',
label: '申请人:',
key: 'user',
value: '',
default: '',
attribute: {//配置项内容
placeholder: '请选择申请人',
fetchOptions: {
baseURL: '', //axios.request请求的基础路径
url: `/system/user/list`,
method: "get",
data: {
page: 1,
limit: 999,
},
dataType: "json",
dataPath: "data.list",//可不指定,默认值-data.list,返回值中的数据路径
labelKey: "nickName",//可不指定,默认值-name,返回值中的字段名
valueKey: "userId",//可不指定,默认值-id,返回值中的字段名
}
}
}
//静态选项配置示例:
{
tag: 'BaseSelect',
label: '部门:',
key: 'deptId',
value: '',
default: '',
attribute: {//配置项内容
placeholder: '请选择公司',
options: [
{
label: '产品部',
value: 1
}
]
}
}
```

@ -0,0 +1,21 @@
## TagInput - 输入标签
配置项参考 [el-tag](https://element-plus.gitee.io/zh-CN/component/tag.html "https://element-plus.gitee.io/zh-CN/component/tag.html")<br>
<br/>
```javascript
动态组件配置示例:
{
tag: 'TagInput',
label: '标签:',
keys: {//示例为默认值,可不填写
value: 'id',
label: 'name'
},
value: [{id: 1,name: '张三'},{id: 2,name: '李四'}],
default: [],
size: 'large',//示例为默认值,可不填写
type: '',//示例为默认值,可不填写
closable: 'true',//示例为默认值,可不填写
}
```

@ -0,0 +1,27 @@
# BaseTree - 树形控件
配置项参考 [el-tree](https://element-plus.gitee.io/zh-CN/component/tree.html "https://element-plus.gitee.io/zh-CN/component/tree.html")
<br/>
获取结果时,默认携带了父级 id(即使 children 并未全部选中)
<br/>
回显时,默认进行了匹配过滤,即便默认选中值中包含了父级 id,但其 children 的 id 若未全部包含,父节点仍不会被选中
<br/>
BaseSelect/BaseTree 选项赋值,参考 util.js -> [setOptions](./../method/setOptions.md)
```javascript
动态组件配置示例:
{
tag: 'BaseTree',
label: '菜单:',
key: 'menuIdList',
value: [],
default: [],
attribute: {//配置项内容
defaultCheckedKey: [],
nodeKey: "id",
showCheckbox: true,
props: {label:'name'},
data: []
}
}
```

@ -0,0 +1,159 @@
# BaseUpload - 上传
配置项参考 [el-upload](https://element-plus.gitee.io/zh-CN/component/upload.html "https://element-plus.gitee.io/zh-CN/component/upload.html")<br>
默认 list-type 为 text
## 普通上传配置示例:
```javascript
{
tag: 'BaseUpload',
label: '图片:',
key: 'videoUrl',
value: [],
default: [],
width: '60%',
rules: [
{ required: true, message: '请上传图片', trigger: 'change' },
],
attribute: {//属性
limit: 1,
ref: 'uploadRef', //仅支持命名uploadRef,可通过table的ref调用, table.value.uploadRef
accept: '.wmv,.asf,.asx,.mp4,.m4v', //支持的格式
headers: {token: 'something'}, //定义请求头
baseUrl: baseUrl+'/base/upload',//图片提交接口
resKey: {//当前value数组[{}]中,对应的url/name
urlKey: 'url',//默认值url,相同则可不传
nameKey: 'name'//默认值name,相同则可不传
},
},
customFormatter: function(data){//自定义提交表单时,格式化videoUrl字段
if(data.length > 0) return data[0].response.url
}
}
```
## 切片上传配置示例:
```javascript
{
tag: 'BaseUpload',
label: '视频:',
key: 'videoUrl',
value: [],
default: [],
width: '60%',
rules: [
{ required: true, message: '请上传视频', trigger: 'change' },
],
attribute: {//属性
limit: 1,
ref: 'uploadRef', //仅支持命名uploadRef,可通过table的ref调用, table.value.uploadRef
accept: '.wmv,.asf,.asx,.mp4,.m4v', //支持的格式
resKey: {//当前文件数组[{}]中,对应的url/name
urlKey: 'url',//默认值,url,name相同则可不传
nameKey: 'name'
},
pieceUpload: {//切片上传, 不传则不切片
unique: 'userId or others',//唯一id,
checkFile:{//检查文件接口
// method: 'post', //默认post
url: 'http://localhost:3000/api/base/check',
// headers: {//选填参数
// token: localGet('token'),
// },
},
upload:{//上传文件接口,同检查接口
url: 'http://localhost:3000/api/base/slice',
},
mergetFile:{//合并文件接口,同检查接口
url: 'http://localhost:3000/api/base/compose',
},
autoShard: true,//是否自动进行分片大小计算,默认true
size: 20,//autoShard=false,生效
},
},
customFormatter: function(data){
if(data.length > 0) return data[0].response.url
}
}
```
## 上传视频,获取视频长度demo
```javascript
const table = ref();
//监听Upload组件的值
watch(()=>state.baseModelOptions[6].value,(newVal)=>{
if(newVal.length > 0 && (newVal[0].percentage === undefined || newVal[0].percentage === 0 || newVal[0].percentage === 100)){
var audioElement;
if(newVal[0]?.response){//后台返回的值
audioElement = new Audio(baseUrl + newVal[0]?.response.url);
}else{
var url = URL.createObjectURL(newVal[0].raw);//获取录音时长
audioElement = new Audio(url);//audio也可获取视频的时长
}
setTimeout(()=>{
table.value.modelRef.loading = true;
})
getAudioLength(audioElement).then(res=>{
state.baseAdd.videoLength = res;
state.baseUpdate.videoLength = res;
if(newVal[0]?.response) table.value.modelRef.loading = false;
}).catch(error=>{
console.log(55555555,error);
ElMessage.error('获取音频信息失败');
table.value.modelRef.loading = false;
})
}else if(newVal.length == 0){
table.value.modelRef.loading = false;
}
},{deep:true})
```
## upload组件的赋值过程
```javascript
//修改操作时,upload组件的赋值过程,以确保upload组件回显正确
if (value instanceof Array) {
//如果value为数组,自动格式化,并返回数组
//resKey:{urlKey,nameKey} 对应了取值的key,默认为value[index].url
item.value = value.map((item1) => {
let url = item1[item.resKey ? item.resKey[urlKey] : "url"];
let name = item1[item.resKey ? item.resKey[nameKey] : "name"];
return {
name: name,
url: item.baseUrl + url,
status: "success",
uid: url,
response: {
url,
name,
},
};
});
} else if (value instanceof Object) {
//如果是object,格式化并返回单记录数组
let url = value[item.resKey ? item.resKey[urlKey] : "url"];
let name = value[item.resKey ? item.resKey[nameKey] : "name"];
item.value = [
{
name: name,
url: item.baseUrl + url,
status: "success",
uid: url,
response: {
url,
name,
},
},
];
} else {
//如果是字符串,格式化并返回单记录数组
item.value = [
{
name: data[item.aliasKey || item.key],
url: item.baseUrl + data[item.aliasKey || item.key],
status: "success",
uid: data[item.aliasKey || item.key],
response: {
url: data[item.aliasKey || item.key],
name: data[item.aliasKey || item.key],
},
},
];
}
```

@ -0,0 +1,32 @@
# dateFormatter 日期格式化
```javascript
//日期格式化
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
proxy.$util.dateFormatter.formate("2022/09/08", "yyyy-mm-dd hh:mm");
```
## formateType
| 名称 | 示例 |
| :------------------ | :------------------ |
| yyyy-m | 2022-1 |
| yyyy-m-d | 2022-1-1 |
| yyyy-m-d h:m | 2022-01-01 1:1 |
| yyyy-m-d h:m:s | 2022-01-01 1:1:1 |
| yyyy-mm | 2022-01 |
| yyyy-mm-dd | 2022-01-01 |
| yyyy-mm-dd hh:mm | 2022-01-01 01:01 |
| yyyy-mm-dd hh:mm:ss | 2022-01-01 01:01:01 |
| date | 2022-01-01 |
| datetime | 2022-01-01 01:01:01 |
## methods
| 名称 | 描述 | 参数 |
| :------------: | :------------------: | :-----------------------------------------------------: |
| formate | 格式化时间 | (日期,formateType) |
| now | 获取当前时间并格式化 | formateType |
| getMonthLength | 获取月份天数 | (year,month) |
| calculateDate | 计算日期 | (日期, type 增减类型 年:1,月:2,日:3, 数量 ,formateType) |

@ -0,0 +1,8 @@
# getOptionByKey 通过Key获取选项
```javascript
//向动态组件中填充选项
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
console.log(proxy.$util.getOptionByKey(state.baseModelOptions,'station'));
```

@ -0,0 +1,8 @@
# getUUID 获取 UUID
```javascript
//生成UUID
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
let uuid = proxy.$util.getUUID();
```

@ -0,0 +1,8 @@
# hasEmoji Emoji 判断
```javascript
//判断是否包含emoji表情
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
let hasEmoji = proxy.$util.hasEmoji(String);
```

@ -0,0 +1,8 @@
# 使用方式
插件在app.use 注入后,会自动注入到 vue3 全局方法中
```javascript
//使用示例:
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
console.log(proxy.$util);
```

@ -0,0 +1,15 @@
# setObjectValue 动态数组赋值(object)
```javascript
//向动态组件中填充值
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
proxy.$util.setObjectValue(source, arg);
```
## 参数
| 名称 | 描述 | 必填 | 默认值 | 类型 |
| :----: | :------------: | ---- | :----: | ------ |
| source | 待赋值的数据源 | 是 | 无 | Array |
| arg | 取值的数据源 | 是 | 无 | Object |

@ -0,0 +1,30 @@
# setOptions 选项赋值
```javascript
//向动态组件中填充选项
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
state.baseModelOptions = proxy.$util.setOptions({
//attrName: 下拉选?'options':'data' 默认为options
data: state.baseModelOptions, //待赋值数据源
key: "station", //配置项的key
res, //返回结果
});
```
## 参数
| 名称 | 描述 | 必填 | 默认值 | 类型 |
| :---------: | :-------------------------------------------------------------------------------------------------------------------------: | ---- | :-----------------------------------------------------: | ------- |
| baseOptions | 默认选项 | 否 | [] | Array |
| attrName | 数据格式化后,赋值的属性名(select: options,tree: data) | 否 | 'options' | String |
| data | 目标(待赋值)数据 | 是 | 无 | Object |
| res | 数据源 | 是 | 无 | Object |
| path | 取值路径 | 否 | "res.data.data" | String |
| key | data 中对应的 key | 是 | 无 | String |
| relation | \{key:'格式化后值对应的 key',name:'格式化后名称对应的 key',resKey:'格式化前值对应的 key',resName:'格式化前名称对应的 key'\} | 否 | \{key:'value',name:'label',resKey:'id',resName:'name'\} | Object |
| hasChildren | 是否需要格式化 children 内容 | 否 | null | Boolean |
| event | 自定义 event | 否 | null | Object |
| everClear | 清空已有值 | 否 | false | Boolean |
| setDefault | 取第一项的值为默认值 | 否 | data 配置项的 value | Boolean |
| query | 查询数据源(state.baseQuery) ,将选项的第一个值赋给查询 object | 否 | null | Object |

@ -0,0 +1,8 @@
# setValueByKey 动态数组赋值(key)
```javascript
//向动态数组赋值
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
proxy.$util.setValueByKey(state.baseModelOptions, "key", "newValue");
```

@ -0,0 +1,5 @@
# BaseChildrenPage-树形 table(已弃用)
<!-- 配置参看BaseTablePage,除data参数格式不同以外,均相同 -->
BaseTablePage 中配置即可实现

@ -0,0 +1,19 @@
# BaseControl-页面操作区域
默认被包含在BaseTable中,可单独使用
```html
<BaseControl
:single="single"
:multiple="multiple"
:showAdd="props.tableOptions.showAddBtn"
:showEdit="props.tableOptions.showEditBtn"
:showDelete="props.tableOptions.showDeleteBtn"
:showExport="props.tableOptions.showExportBtn"
:showImport="props.tableOptions.showImportBtn"
:permission="props.tableOptions.permission"
@add="handleAdd"
@edit="handleEdit"
@delete="deleteTableData"
></BaseControl>
```

@ -0,0 +1,49 @@
# BaseModel-弹出框
```html
<BaseModel
:title="'查看资料'"
:modelType="'view'"
:showModel="state.showModel"
:width="'35%'"
:modelOptions="state.baseModelOptions"
@dialogClose="state.showModel=false"
@dialogSubmit="editFile"
>
</BaseModel>
```
## 属性
| 名称 | 描述 | 参数类型 | 默认值 |
| ------------ | :----------: | --------------------: | --------------------:|
| title | 标题 | String | ''|
| width | 窗口宽度 | String | 35%|
| columnCount | 列数量 | Number | 2 |
| labelWidth | label宽度([行内可单独配置](./../form/normal.md)) | String |30%|
| modelType | 查看类型view(只读)/edit(编辑) | String |edit
| showModel | 是否显示 | Boolean |false
| modelOptions | 动态组件内容 | Array | []
## 方法
| 名称 | 描述 | 参数类型 |
| ------------ | :------: | -------------: |
| setValueByKey | 通过key赋值 | (key,value) |
| setObjectValue | 通过Object赋值(根据key进行取值) | Object |
| clearForm | 清空表单值(取modelOptions中default值) | null |
```javascript
//添加ref
<BaseModel ref="bm"></BaseModel>
//js
const bm = ref()
bm.value.setValueByKey('name','张三')
bm.value.setObjectValue({name:'张三',age:18})
```
## 事件
| 名称 | 描述 | 可选值 |
| ------------ | :------: | -------------: |
| dialogClose | 关闭弹窗 | any |
| dialogSubmit | 点击确认按钮 | Function(data:Object,callback:Function) 调用callback(),主动将loading状态改为false |

@ -0,0 +1,37 @@
# BaseFilter-搜索区域
默认被包含在BaseTable中,可单独使用
```html
<BaseFilter
:filterOptions="props.tableOptions.baseFilterOptions"
:showFilter="true"
@doFilter="doFilter"
></BaseFilter>
```
## 属性
| 名称 | 描述 | 可选值 | 默认值 |
| ------------ | :----------: | --------------------: | --------------------------: |
| filterOptions | 动态组件内容 | [组件数组] | [] |
## 方法
| 名称 | 描述 | 参数类型 |
| ------------ | :------: | -------------: |
| doFilter | 执行搜索 | null |
| clearFilter | 清空搜索条件 | null |
```javascript
//添加ref
<BaseFilter ref="filter"></BaseFilter>
//js
const filter = ref()
filter.value.doFilter()
filter.value.clearFilter()
```
## 事件
| 名称 | 描述 | 可选值 |
| -------- | :----------: | -------------: |
| doFilter | 点击搜索触发 | Function(data) |

@ -0,0 +1,201 @@
# BaseTablePage-table 页面
column 配置项参考 [el-table](https://element-plus.gitee.io/zh-CN/component/table.html "https://element-plus.gitee.io/zh-CN/component/table.html")
```javascript
const state = reactive({
baseModelOptions: baseModelOptions, //新增、修改弹窗 动态组件数组
baseFilterOptions: baseFilterOptions, //上方搜索 动态组件数组
title: "用户管理", //页面标题,不设置则隐藏标题区域
getTableFn: getUser, //查询接口 --Promise
pageInfo: { total: 0, base: { limit: 8, current: 1 } }, //分页信息
});
```
```html
<BaseTablePage ref="table" :tableOptions="state">
<!-- 表格column插槽,不包含操作按钮 -->
<template #column>
<el-table-column align="center" prop="username" label="用户名" />
<el-table-column align="center" prop="gender" label="性别">
<template #default="scope">
<span style="margin-left: 10px"
>{{ scope.row.gender==1?'男':scope.row.gender==2?'女':'未知' }}</span
>
</template>
</el-table-column>
</template>
<!-- type1: 编辑按钮前置插槽,可在编辑按钮前添加内容 -->
<template #before="scope">
<el-button type="primary">新增</el-button>
</template>
<!-- type2: 删除按钮后置插槽,可在删除按钮后添加内容 -->
<template #after="scope">
<el-button type="primary">详情</el-button>
</template>
<!-- type3: 表格control插槽,重写操作按钮区域 -->
<template #control="baseScope">
<el-table-column label="操作" width="350">
<template #default="scope">
<el-button type="warning" @click="baseScope.handleEdit(scope.$index, scope.row)"
>修改</el-button
>
<el-popconfirm @confirm="baseScope.deleteTableData(scope.$index, scope.row)" title="确定删除该条数据?">
<template #reference>
<el-button
type="danger"
>删除</el-button
>
</template>
</el-popconfirm>
</template>
</el-table-column>
</template>
</BaseTablePage>
```
## 调用组件方法
```html
<BaseTablePage :tableOptions="state" ref="table"></BaseTablePage>
```
```javascript
const table = ref();
onMounted(() => {
const {
doFilter, //点击搜索后执行的函数
getTableData, //查询表格数据执行的函数
editTableData, //修改弹窗,点击确定执行的函数
deleteTableData, //删除弹窗,点击确定执行的函数
} = table.value;
//示例:3s后主动执行一次查询
setTimeout(() => {
getTableData();
}, 3000);
});
```
## 事件
| 名称 | 描述 | 参数 |
| ----------------- | :-------------------------: | -----: |
| import | 点击顶部导入按钮触发 | null |
| export | 点击顶部导出按钮触发 | null |
| doSelection| 行选中change事件 | \{data:Array,ids:Array\} |
## tableOptions 属性
### function相关
| 名称 | 描述 | 必填 | 参数类型 | 默认值 |
| ----------------- | :-------------------------: | ---: | -------: | -----: |
| handleAdd | 添加按钮执行的 func(覆盖默认的调用 function) | 否 | Function | null |
| handleAdd | 添加按钮执行的 func(覆盖默认的调用 function) | 否 | Function | null |
| handleEdit | 点击编辑按钮执行的 func(覆盖默认的调用 function) | 否 | Function(index,row) | null |
| handleDelete | 点击删除按钮执行的 func(覆盖默认的调用 function) | 否 | Function(index,row) | null |
| beforeSubmit | 提交修改前事件,F(params) | 是 | Function | null |
| beforeShowEdit | 点击编辑按钮事件,弹窗显示前执行(同步) F(params) | 否 | Function | null |
| beforeEdit | 与 beforeShowEdit 执行时机一致(异步) | 否 | (Promise)Function(row) | null |
| beforeDelete | 执行删除前调用 | 否 | Function(row) | null |
| [beforeFilter](./page.md#示例1) | 执行搜索前调用 | 否 | Function(params) | null |
| [beforeReset](./page.md#示例1) | 执行重置前调用 | 否 | Function(params) | null |
| afterSubmit | 表单提交后调用(支持普通 fn 与 return Promise) | 否 | (Promise)Function(row) | null |
| afterDelete | 数据删除后调用(支持普通 fn 与 return Promise) | 否 | (Promise)Function(row) | null |
### 按钮相关
| 名称 | 描述 | 必填 | 参数类型 | 默认值 |
| ----------------- | :-------------------------: | ---: | -------: | -----: |
| showAddBtn | 是否显示添加按钮 | 否 | Boolean | true |
| showEditBtn | 是否显示顶部修改按钮 | 否 | Boolean | false |
| showDeleteBtn | 是否显示顶部删除按钮 | 否 | Boolean | false |
| showImport | 是否显示顶部导入按钮 | 否 | Boolean | false |
| showExport | 是否显示顶部导出按钮 | 否 | Boolean | false |
| editBtnName | 修改操作 按钮名称 | 否 | String | null |
| delBtnName | 删除操作 按钮名称 | 否 | String | null |
| rowControl | 行内操作按钮控制\{showEditBtn:false,showDelBtn:false\} | 否 | Object | \{showEditBtn:true,showDelBtn:true\} |
| permission | 页面内增删改查权限,permission:\{add:'菜单权限标识',edit:'菜单权限标识',delete:'菜单权限标识'\} | 否 | Object | null |
### 配置属性
| 名称 | 描述 | 必填 | 参数类型 | 默认值 |
| ----------------- | :-------------------------: | ---: | -------: | -----: |
| baseModelOptions | 新增、修改弹窗 动态组件数组 | 是 | Array | null |
| baseFilterOptions | 上方搜索 动态组件数组 | 是 | Array | null |
| pageSelection | 是否开启分页行选中(不开启只计算当前page选中) | 否 | Boolean | false |
| loadingText | 加载中的文本 | 否 | String | Loading... |
| loadingBackground | 加载中背景色 | 否 | String | rgb(34, 58, 106, 0.5) |
| style | BaseTablePage组件的样式 | 否 | StyleObject | null |
| tableStyle | el-table组件的style | 否 | StyleObject | null |
| autoQuery | 是否页面初始化自动进行查询 table | 是 | Boolean | true |
| showLoading | 是否显示加载动画,默认 false,且只有值为 true 显示 | 否 | Boolan | true |
| rowKey | 当 row 中包含 children 字段时,被视为树形数据,指定 row-key,开启树形数据展示 | 否 | String | null |
| baseModelName | 弹窗标题 | 否 | String | null |
| hideControl | 隐藏操作列 | 否 | Boolean | null |
| primaryKey | 修改操作 参数名称 | 否 | String | null |
| deleteKey | 删除操作 参数名称 | 否 | String | null |
| baseQuery | 查询接口默认参数 | 否 | Object | null |
| baseAdd | 添加接口默认参数 | 否 | Object | null |
| baseUpdate | 修改接口默认参数 | 否 | Object | null |
| getTableFn | 查询接口 | 是 | (Promise) Function | null |
| addFn | 新增接口 | 否 | (Promise) Function | null |
| editFn | 修改接口 | 否 | (Promise) Function | null |
| detailFn | 查询详情接口,点击修改触发,为 null 时,使用 table-row 的数据 | 否 | (Promise) Function | null |
| deleteFn | 删除接口 | 否 | (Promise) Function | null |
| pageInfo | 分页信息 | 否 | Object | { total: 0, base:{limit: 10,current: 1} } |
| pageKeys | 查询时分页key | 否 | Object | \{ limit: 'limit',page: 'current' \} |
## 插槽
| 名称 | 描述 | scope |
| --------- | :--------------------: | ---------------------------------: |
| addButton | 顶部添加按钮的内部插槽 | none |
| column | table-column 内容 | |
| control | 操作(操作按钮) | scope.'table 相关 [methods](./page.md#control插槽属性)' |
| before | 编辑按钮前置插槽 | scope.row |
| after | 删除按钮后置插槽 | scope.row |
## control插槽属性
| 名称 | 描述 |
| --------- | :--------------------: |
| showModel | 显示添加/编辑弹窗 |
| doFilter | 触发查询按钮 |
| getTableData | 查询列表 |
| pageChange | 触发页面改变 |
| tableData | 当前tableData |
| handleAdd | 触发新增按钮点击 |
| handleEdit | 触发修改按钮点击 |
| editTableData | 触发提交修改事件 |
| deleteTableData | 触发删除点击事件 |
## 其他配置
### 数值来源:
vue全局变量$autoScrollTop|$autoScrollHeight
| 名称 | 描述 | defasult |
| --------- | :--------------------: | :--------------------: |
| $autoScrollTop | 页码变化时是否滚动到顶部 | false |
| $autoScrollHeight | table高度,autoScrollTop为true生效 | '' |
## 示例1
```javascript
const state = reactive({
beforeReset: ()=>{
//1. 使用异步的方式
return new Promise((resolve)=>{
setTimeout(()=>{
//dosomething...
resolve()
},2000)
})
//2.使用同步的方式
//dosomething...
},
beforeFilter: (params)=>{
//1. 使用异步的方式
return new Promise((resolve)=>{
setTimeout(()=>{
//dosomething...
resolve({a:'重新设置的值'})
},2000)
})
//2.使用同步的方式
//dosomething...
// return {a:'重新设置的值'} || 不返回,则使用当前搜索框内的值
}
})
```

@ -0,0 +1,28 @@
# BaseTable-表格
默认被包含在BaseTable中,可单独使用
```html
<!-- 封装了el-table + pagination -->
<BaseTable
@pageChange="pageChange"
:data="tableData"
:hidePagenation="props.tableOptions.hidePagenation"
:pageInfo="props.tableOptions.pageInfo"
>
<el-table-column align="center" prop="dictName" label="字典名称" />
<el-table-column align="center" prop="dictType" label="字典类型" />
</BaseTable>
```
## 属性
| 名称 | 描述 | 可选值 |
| -------------- | :---------------: | ---------------------------------------: |
| data | 列表值 | [列表值] |
| pageInfo | 分页信息对象 | { total: 0, base:{limit: 8,current: 1} } |
| hidePagenation | 是否隐藏分页 | true/false |
## 事件
| 名称 | 描述 | 可选值 |
| ---------- | :----------: | -----------------: |
| pageChange | 页码发生改变 | Function(pageInfo) |

@ -0,0 +1,34 @@
# Bpmn - 流程设计
基于 [bpmn-js](https://bpmn.io/toolkit/bpmn-js/ "https://bpmn.io/toolkit/bpmn-js/") 实现的流程设计器,通过绘制流程图,快速设计流程。
## 使用示例:
```html
<Bpmn :users="users" :roles="roles" @save="save"></Bpmn>
```
```javascript
const users = [
{
value: "zhangsan",
label: "张三"
},
{
value: "lisi",
label: "李四"
}
]
const roles = [
{
value: "manager",
label: "经理"
},
{
value: "personnel",
label: "人事"
}
]
const save = (res)=>{//保存流程图xml
console.log(res)
}
```

@ -0,0 +1,124 @@
# 动态Form
以配置数组的方式,动态生成相应form表单。
常用在模块组件中的BaseModel和BaseFilter。
通常情况,上述2个组件被封装在BaseTablePage中
```javascript
//新增、修改弹窗配置项
export const baseModelOptions = [
{
tag: 'el-input',
label: '标题:',
key: 'name',
value: '',
default: '',
rules: [
{ required: true, message: '请输入标题', trigger: 'blur' },
],
attribute: {//属性
type: 'text',
placeholder: '请输入标题',
},
},
{
tag: 'BaseUpload',
label: '视频:',
key: 'videoUrl',
baseUrl,
value: [],
default: [],
width: '60%',
rules: [
{ required: true, message: '请上传视频', trigger: 'change' },
],
attribute: {//属性
limit: 1,
ref: 'uploadRef',
accept: '.wmv,.asf,.asx,.mp4,.m4v',
headers: {token: localGet('token')},
baseUrl: baseUrl+'/base/upload'//图片提交接口
},
}
]
//搜索区域配置项
export const baseFilterOptions = [
{
tag: 'el-input',
label: '标题:',
key: 'title',
value: '',
default: '',
attribute: {//属性
type: 'text',
placeholder: '请输入标题',
},
},
{
tag: 'BaseSelect',
label: '提案类别:',
key: 'type',
value: '',
default: '',
attribute: {//属性
placeholder: '请选择提案类别',
options: []
},
},
]
```
### 属性
| 名称 | 描述 | 必填 | 参数类型 | 默认值 |
| ----------------- | :--------------------------------------------------------------------------: | ---: | ------: | ----: |
| tag | 组件名称 | 是 | String | 无 |
| label | 标签名称 | 是 | String | 无 |
| key | 提交的key值 | 是 | String | 无 |
| value | 当前值 | 是 | String | 无 |
| default | 默认值,取值时,若value为空,则取default的值 | 是 | String | 无 |
| attribute | 组件的属性,等同于直接组件中<组件 :属性="something" /> | 否 | Object | {} |
| rules | form表单校验规则,仅支持默认校验,详情参考element-plus | 否 | Array | 无 |
| event | 组件的事件,等同于直接组件中<组件 @事件="something" />,见下方示例 | 否 | Object | null |
| customFormatter | 自定义格式化,在form表单点击提交取值时会触发,见下方示例 | 否 | Object | null |
### event
为组件绑定事件
```javascript
{
tag: 'BaseSelect',
label: '提案类别:',
key: 'type',
value: '',
default: '',
attribute: {//属性
placeholder: '请选择提案类别',
options: []
},
event: {
'change': (data)=>{//参数内容,见使用的组件的事件
//doSomething
}
}
}
```
### customFormatter
自定义格式化函数
```javascript
{
tag: 'BaseSelect',
label: '提案类别:',
key: 'type',
value: '',
default: '',
attribute: {//属性
placeholder: '请选择提案类别',
options: []
},
customFormatter: function(data){
//data 为v-model的值
//this 指向当前vue组件
return data[0].response.url
}
}
```

@ -0,0 +1,105 @@
# 示例
## option.js
```javascript
export const baseModelOptions = [
{
tag: 'el-input',
label: '角色名称:',
key: 'name',
value: '',
default: '',
attribute: {//属性
type: 'text',
placeholder: '请输入角色名称',
},
},
{
tag: 'el-input',
label: '备注:',
key: 'remark',
value: '',
default: '',
attribute: {//属性
type: 'text',
placeholder: '请输入备注',
},
},
{
tag: 'BaseTree',
label: '菜单:',
key: 'menuIdList',
value: [],
default: [],
attribute: {//属性
defaultCheckedKey: [],
nodeKey: "id",
showCheckbox: true,
props: {label:'name'},
data: []
}
},
]
export const baseFilterOptions = [
{
tag: 'el-input',
label: '角色名称:',
key: 'name',
value: '',
attribute: {//属性
type: 'text',
placeholder: '请输入角色名称'
},
},
]
```
## role.vue
```html
<script setup>
import { reactive,ref,getCurrentInstance } from 'vue'
import {getRole,addRole,updateRole,deleteRole,getRoleDetail,getMenuList} from '@/api/data'
import {setOptions} from '@/utils'
import {baseModelOptions,baseFilterOptions} from './options'
const { proxy } = getCurrentInstance();
const state = reactive({
baseModelOptions: baseModelOptions,
baseFilterOptions: baseFilterOptions,
title: "角色管理",
baseModelName: '角色信息',
addBtnName: '添加角色',
primaryKey: 'id',
getTableFn: getRole,
addFn: addRole,
editFn: updateRole,
deleteFn: deleteRole,
detailFn: getRoleDetail,
multipleDelete: true,
pageInfo: { total: 0, base:{limit: 8,current: 1} }
})
getMenuList().then(res=>{
state.baseModelOptions = proxy.$util.setOptions({
data: state.baseModelOptions,//待赋值数据源
key:'station',//配置项的key
res//返回结果
})
})
</script>
<template>
<BaseTablePage
:tableOptions="state"
>
<template v-slot:column>
<el-table-column align="center" prop="name" label="名称" />
<el-table-column align="center" prop="remark" label="备注" />
</template>
</BaseTablePage>
</template>
<style scoped>
</style>
```

@ -0,0 +1,114 @@
# 介绍
zbn-admin后台管理系统,现已支持前端server模式(非必选)
- server部分使用nodejs的express框架
- web部分使用Vue3 + ElementPlus封装,搭配java若依后端框架
## server
基于nodejs的express框架,主要实现了以下功能:
- 1. 支持mysql数据库直连,前端可直接进行业务扩展
- 2. 定向转发了java端的若依框架接口,不影响原业务结构
- 3. 集成了camunda rest api,支持自定义审批流
- 4. 可直接进行部署,不再依赖nginx转发
- 5. 前端可自行扩展vue-router history模式
#### 目录结构
![目录结构](/assets/server.jpg)
## web
web后台管理系统,有以下特点:
- 1. 基于Vue3 + ElementPlus封装,搭配java若依后端框架。
- 2. 封装了部分无法进行v-model的form表单组件,并实现了以配置的方式,动态绘制表单
- 3. 封装了弹出框、table页等常规表格组件
- 4. 封装了常用的util方法,注册到全局方法中
- 5. 搭配vscode插件-zeQick,可以快速生成页面代码,提高开发效率。
- 6. 支持camunda集成-自定义审批流
### 调整页面组件的默认行为
```
app.config.globalProperties.$resPath = {//自定义tablePage接口的配置
tableResult: {//获取table数据的配置
successCode: 0,
codePath: 'data.code',
dataPath: 'data.data.list',
totalPath: 'data.data.total',
messagePath: 'data.msg',
},
rowResult: {//点击详情,获取行数据的配置
successCode: 0,
codePath: 'data.code',
dataPath: 'data.data',
messagePath: 'data.msg',
},
controlResult: {//修改/新增/删除的配置
successCode: 0,
codePath: 'data.code',
messagePath: 'data.msg'
}
};
app.config.globalProperties.$pageKeys = {
page: 'page',
limit: 'limit'
};
```
### resPath
| 名称 | 描述 | 参数类型 | 默认值 |
| ------------ | :----------: | --------------------: | --------------------:|
| [tableResult](./index.md#Result) | 获取table数据的配置 | Object | null |
| [rowResult](./index.md#Result) | 点击详情,获取行数据的配置 | Object | null|
| [controlResult](./index.md#Result) | 修改/新增/删除的配置 | Object | null|
### Result
path:从res的读取的属性,codePath='code',则读取res.code
| 名称 | 描述 | 参数类型 | 默认值 |
| ------------ | :----------: | --------------------: | --------------------:|
| successCode | 成功状态码 | Number | 200 |
| codePath | 读取状态码的路径 | String | 'code'|
| dataPath | 读取tableData的路径 | String | 'data.list'|
| totalPath | 读取总条数的路径 | String | 'data.total'|
| messagePath | 读取消息的路径 | String | 'data.msg'|
## 启动方式
### 单独启动
```shell
# server
cd server
npm install
npm run start
# web
cd web
npm install
npm run dev
```
### 整体启动
```shell
npm install
npm run dev
```
## 打包方式
### 单独打包
```shell
# server
cd server
npm run build
# web
cd web
npm run build:prod
```
### 整体打包
```shell
npm run build
```
#### 输出目录-整体打包
根目录下会生成dist文件夹,
- 内部dist文件夹为web打包后的文件
- server.js为server打包后的文件
####
![输出目录](/assets/build.jpg)
#### 部署方式-整体打包
将dist文件夹放到服务器上,执行以下命令
```shell
npm install
# 当前窗口启动
npm run start
# 后台启动-方式1
nohup npm run start &
# 后台启动-方式2
forever start server.js
```

@ -0,0 +1,296 @@
# Workflow API 文档
## 流程示意
![流程示意图](/assets/process.png)
## 目录
- [流程图管理](#流程图管理)
- [流程实例管理](#流程实例管理)
- [任务管理](#任务管理)
- [表单管理](#表单管理)
- [Camunda部署-Docker](#Camunda部署-Docker)
## 项目导入
```javascript
import {
getProcessList,
getProcessInfo
//...其他
} from '/api/workflow'
```
## 流程图管理
### 获取流程图列表
```javascript
getProcessList({
firstResult = 0, // 起始索引
maxResults = 8, // 最大结果数
latestVersion = true // 是否最新版本
})
```
### 获取流程图信息
```javascript
getProcessInfo(processId) // processId: 流程图ID
```
### 获取流程图 XML
```javascript
getProcessXml(processId) // processId: 流程图ID
```
### 上传流程图
```javascript
const bpmnContent = '...'; // 流程图内容
const formData = new FormData()
const bpmnBlob = new Blob([bpmnContent], { type: 'application/octet-stream' });
formData.append('content', bpmnBlob, 'process.bpmn'); // 文件名应以 .bpmn 结尾
addModeler(formData)
```
### 删除流程图
```javascript
deleteProcess(processId) // processId: 流程图ID
```
### 创建流程实例
```javascript
start({
processKey: string, // 流程图key
formId: string, // 表单ID
createUser: string, // 创建人
formValues: object // 填写的表单数据
})
```
## 流程实例管理
### 获取流程实例列表
```javascript
getProcessInstanceList({
firstResult = 0, // 起始索引
maxResults = 8, // 最大结果数
processDefinitionKey, // 流程图key
sortOrder = "desc", // 排序方式:asc/desc
sortBy = "startTime" // 排序字段
})
```
### 删除流程实例
```javascript
deleteProcessInstance(processInstanceId) // processInstanceId: 流程实例ID
```
## 任务管理
### 获取流程实例任务列表
```javascript
getInstanceTaskList({
firstResult = 0, // 起始索引
maxResults = 8, // 最大结果数
processInstanceId // 流程实例ID
})
```
### 获取用户任务列表
```javascript
getUserTaskList({
firstResult = 0, // 起始索引
maxResults = 8, // 最大结果数
assignee, // 执行人ID
candidateUser, // 候选人ID
candidateGroup // 候选组ID
})
```
### 任务候选人管理
```javascript
// 查询任务节点候选人/组
getTaskCandidate(taskId)
// 添加节点候选人/组
addTaskCandidate({
taskId, // 任务ID
type, // 类型:candidate/assignee
userId, // 用户ID(与groupId二选一)
groupId // 组ID(与userId二选一)
})
// 删除节点候选人/组
deleteTaskCandidate({
taskId, // 任务ID
type, // 类型:candidate/assignee
userId, // 用户ID(与groupId二选一)
groupId // 组ID(与userId二选一)
})
```
### 任务操作
```javascript
// 认领任务
claim({
taskId, // 任务ID
userId // 用户ID
})
// 取消认领任务
unclaim(taskId)
// 完成任务
complete({
taskId, // 任务ID
variables: {
approvalResult: {
value: "approved" | "rejected", // 审批结果
type: "String"
}
}
})
// 添加任务评论---记录审批结果
setComment({
taskId, // 任务ID
message // 评论内容(JSON字符串) JSON.stringify({message: '执行成功',id: '其他的测试信息'})
})
```
## 表单管理
### 表单查询
```javascript
// 获取表单列表
getFormList({
page, // 页码
start, // 起始索引
limit // 最大结果数
})
// 获取表单详情
getFormDetail(formId)
// 获取表单字段
getFormFields(path)
```
### 表单操作
表单内容,暂不支持上传文件,手动填写在node服务端- /server/form下,写法与动态Form一致
```javascript
var config = require('../config'); //配置动态请求的基础路径
const formFields = [
{
tag: 'el-input',
label: '申请原因:',
key: 'reason',
value: '',
default: '',
attribute: {
clearable: true,
placeholder: '请输入申请原因',
},
},
{
tag: 'BaseSelect',
label: '申请人:',
key: 'user',
value: '',
default: '',
attribute: {//配置项内容
placeholder: '请选择申请人',
fetchOptions: {//动态请求配置
baseURL: config.env[process.env.NODE_ENV].formBaseURL, //请求的基础路径
url: `/system/user/list`,
method: "get",
data: {
page: 1,
limit: 999,
},
dataType: "json",
dataPath: "data.list",//默认值-data.list,可不指定
labelKey: "nickName",//默认值-name,可不指定
valueKey: "userId",//默认值-id,可不指定
}
}
}
];
module.exports = {
formFields
};
```
```javascript
// 添加表单
addForm({
formName: string, // 表单名称
formDesc: string, // 表单描述
formPath: string, // 表单内容--/form/test.js
userId: string // 创建人ID
})
// 更新表单
updateForm({
id: string, // 表单ID
formName: string, // 表单名称
formDesc: string, // 表单描述
formPath: string // 表单内容
})
// 删除表单
deleteForm(formId)
```
注意:所有 API 都返回 Promise 对象,可使用 async/await 处理异步请求。API 的基础 URL 由环境变量 `VITE_APP_BASE_PROCESS_API` 指定。
## Camunda部署-Docker
### 1.更改docker的镜像源
vi /etc/docker/daemon.json
```shell
{
"registry-mirrors": [
"https://docker.registry.cyou",
"https://docker-cf.registry.cyou",
"https://dockercf.jsdelivr.fyi",
"https://docker.jsdelivr.fyi",
"https://dockertest.jsdelivr.fyi",
"https://mirror.aliyuncs.com",
"https://dockerproxy.com",
"https://mirror.baidubce.com",
"https://docker.m.daocloud.io",
"https://docker.nju.edu.cn",
"https://docker.mirrors.sjtug.sjtu.edu.cn",
"https://docker.mirrors.ustc.edu.cn",
"https://mirror.iscas.ac.cn",
"https://docker.rainbond.cc"
]
}
```
```shell
systemctl daemon-reload
systemctl restart docker
```
### 2.拉取镜像
```shell
docker pull camunda/camunda-bpm-platform:latest
```
### 3.添加启动脚本
vi /data/camunda/docker-compose.yml
```shell
# 其中ports 1111为宿主机端口,8080为容器内部端口
version: '3'
services:
camunda:
image: camunda/camunda-bpm-platform:latest
ports:
- "1111:8080"
volumes:
- ./camunda-data:/camunda/data
```
```shell
#启动服务
docker-compose up -d
```

File diff suppressed because one or more lines are too long

@ -0,0 +1,215 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="/favicon.ico">
<title>致博管理系统</title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 150px;
height: 150px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 2s linear infinite;
-ms-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite;
-o-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
z-index: 1001;
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-webkit-animation: spin 3s linear infinite;
-moz-animation: spin 3s linear infinite;
-o-animation: spin 3s linear infinite;
-ms-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #FFF;
-moz-animation: spin 1.5s linear infinite;
-o-animation: spin 1.5s linear infinite;
-ms-animation: spin 1.5s linear infinite;
-webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #7171C6;
z-index: 1000;
-webkit-transform: translateX(0);
-ms-transform: translateX(0);
transform: translateX(0);
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
-ms-transform: translateX(-100%);
transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
-ms-transform: translateX(100%);
transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
-ms-transform: translateY(-100%);
transform: translateY(-100%);
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 19px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #FFF;
opacity: 0.5;
}
</style>
</head>
<body>
<div id="app">
<div id="loader-wrapper">
<!-- <div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">正在加载系统资源,请耐心等待</div> -->
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -0,0 +1,62 @@
{
"name": "zilber-admin",
"version": "1.0.0",
"description": "致博后台管理系统",
"author": "ChengYu",
"license": "MIT",
"scripts": {
"dev": "vite",
"build:prod": "vite build",
"build:stage": "vite build --mode staging",
"docs:dev": "vitepress dev docs",
"docs:build": "vitepress build docs",
"preview": "vite preview"
},
"repository": {
"type": "git",
"url": "http://dev.zilber.cn:3000/root/zbn-admin.git"
},
"dependencies": {
"@element-plus/icons-vue": "1.1.4",
"@vueup/vue-quill": "^1.0.0-beta.10",
"@vueuse/core": "8.5.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"@wangeditor/plugin-upload-attachment": "^1.1.0",
"axios": "0.26.1",
"bpmn-js": "^18.1.1",
"echarts": "5.3.2",
"element-plus": "2.1.8",
"esbuild": "^0.19.9",
"file-saver": "2.0.5",
"fuse.js": "6.5.3",
"image-conversion": "^2.1.1",
"js-cookie": "3.0.1",
"js-md5": "^0.8.3",
"jsencrypt": "3.2.1",
"nprogress": "0.2.0",
"pinia": "2.0.14",
"quill-image-resize-module": "^3.0.0",
"quill-image-uploader": "^1.3.0",
"uuid": "^11.0.4",
"vue": "3.2.37",
"vue-cropper": "1.0.3",
"vue-router": "4.0.14",
"vuedraggable": "^4.1.0",
"xml-js": "^1.6.11"
},
"devDependencies": {
"@rollup/plugin-inject": "^5.0.5",
"@vitejs/plugin-vue": "2.3.3",
"@vue/compiler-sfc": "3.2.36",
"concurrently": "^9.1.0",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "1.52.1",
"unplugin-auto-import": "0.8.5",
"vite": "2.9.14",
"vite-plugin-compression": "0.5.1",
"vite-plugin-svg-icons": "2.0.1",
"vite-plugin-vue-setup-extend": "0.4.0",
"vitepress": "^1.5.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

@ -0,0 +1,33 @@
<template>
<router-view />
</template>
<script setup>
import useSettingsStore from '@/store/modules/settings'
import { handleThemeStyle } from '@/utils/theme'
onMounted(() => {
nextTick(() => {
//
handleThemeStyle(useSettingsStore().theme)
})
})
</script>
<style>
.title{
text-align: left;
padding-top: 15px;
padding-bottom: 15px;
font-size: 16px;
border-bottom: 1px solid #FFFFFF;
}
.title:before{
content: '';
display: inline-block;
width: 4px;
height: 12px;
background: #286AED;
margin: 0 9px;
}
.fliter-box .el-date-editor.el-input, .fliter-box .el-date-editor.el-input__wrapper{ width: 192px;}
</style>

@ -0,0 +1,59 @@
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
const data = {
userName: username,
password,
code,
uuid
}
return request({
url: '/login',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 注册方法
export function register(data) {
return request({
url: '/register',
headers: {
isToken: false
},
method: 'post',
data: data
})
}
// 获取用户详细信息
export function getInfo() {
return request({
url: '/getInfo',
method: 'get'
})
}
// 退出方法
export function logout() {
return request({
url: '/logout',
method: 'post'
})
}
// 获取验证码
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
}

@ -0,0 +1,9 @@
import request from '@/utils/request'
// 获取路由
export const getRouters = () => {
return request({
url: '/getRouters',
method: 'get'
})
}

@ -0,0 +1,57 @@
import request from '@/utils/request'
// 查询缓存详细
export function getCache() {
return request({
url: '/monitor/cache',
method: 'get'
})
}
// 查询缓存名称列表
export function listCacheName() {
return request({
url: '/monitor/cache/getNames',
method: 'get'
})
}
// 查询缓存键名列表
export function listCacheKey(cacheName) {
return request({
url: '/monitor/cache/getKeys/' + cacheName,
method: 'get'
})
}
// 查询缓存内容
export function getCacheValue(cacheName, cacheKey) {
return request({
url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
method: 'get'
})
}
// 清理指定名称缓存
export function clearCacheName(cacheName) {
return request({
url: '/monitor/cache/clearCacheName/' + cacheName,
method: 'delete'
})
}
// 清理指定键名缓存
export function clearCacheKey(cacheKey) {
return request({
url: '/monitor/cache/clearCacheKey/' + cacheKey,
method: 'delete'
})
}
// 清理全部缓存
export function clearCacheAll() {
return request({
url: '/monitor/cache/clearCacheAll',
method: 'delete'
})
}

@ -0,0 +1,71 @@
import request from '@/utils/request'
// 查询定时任务调度列表
export function listJob(query) {
return request({
url: '/monitor/job/list',
method: 'get',
params: query
})
}
// 查询定时任务调度详细
export function getJob(jobId) {
return request({
url: '/monitor/job/' + jobId,
method: 'get'
})
}
// 新增定时任务调度
export function addJob(data) {
return request({
url: '/monitor/job',
method: 'post',
data: data
})
}
// 修改定时任务调度
export function updateJob(data) {
return request({
url: '/monitor/job',
method: 'put',
data: data
})
}
// 删除定时任务调度
export function delJob(jobId) {
return request({
url: '/monitor/job/' + jobId,
method: 'delete'
})
}
// 任务状态修改
export function changeJobStatus(jobId, status) {
const data = {
jobId,
status
}
return request({
url: '/monitor/job/changeStatus',
method: 'put',
data: data
})
}
// 定时任务立即执行一次
export function runJob(jobId, jobGroup) {
const data = {
jobId,
jobGroup
}
return request({
url: '/monitor/job/run',
method: 'put',
data: data
})
}

@ -0,0 +1,26 @@
import request from '@/utils/request'
// 查询调度日志列表
export function listJobLog(query) {
return request({
url: '/monitor/jobLog/list',
method: 'get',
params: query
})
}
// 删除调度日志
export function delJobLog(jobLogId) {
return request({
url: '/monitor/jobLog/' + jobLogId,
method: 'delete'
})
}
// 清空调度日志
export function cleanJobLog() {
return request({
url: '/monitor/jobLog/clean',
method: 'delete'
})
}

@ -0,0 +1,34 @@
import request from '@/utils/request'
// 查询登录日志列表
export function list(query) {
return request({
url: '/monitor/logininfor/list',
method: 'get',
params: query
})
}
// 删除登录日志
export function delLogininfor(infoId) {
return request({
url: '/monitor/logininfor/' + infoId,
method: 'delete'
})
}
// 解锁用户登录状态
export function unlockLogininfor(userName) {
return request({
url: '/monitor/logininfor/unlock/' + userName,
method: 'get'
})
}
// 清空登录日志
export function cleanLogininfor() {
return request({
url: '/monitor/logininfor/clean',
method: 'delete'
})
}

@ -0,0 +1,18 @@
import request from '@/utils/request'
// 查询在线用户列表
export function list(query) {
return request({
url: '/monitor/online/list',
method: 'get',
params: query
})
}
// 强退用户
export function forceLogout(tokenId) {
return request({
url: '/monitor/online/' + tokenId,
method: 'delete'
})
}

@ -0,0 +1,26 @@
import request from '@/utils/request'
// 查询操作日志列表
export function list(query) {
return request({
url: '/monitor/operlog/list',
method: 'get',
params: query
})
}
// 删除操作日志
export function delOperlog(operId) {
return request({
url: '/monitor/operlog/' + operId,
method: 'delete'
})
}
// 清空操作日志
export function cleanOperlog() {
return request({
url: '/monitor/operlog/clean',
method: 'delete'
})
}

@ -0,0 +1,9 @@
import request from '@/utils/request'
// 获取服务信息
export function getServer() {
return request({
url: '/monitor/server',
method: 'get'
})
}

@ -0,0 +1,60 @@
import request from '@/utils/request'
// 查询参数列表
export function listConfig(query) {
return request({
url: '/system/config/list',
method: 'get',
params: query
})
}
// 查询参数详细
export function getConfig(configId) {
return request({
url: '/system/config/' + configId,
method: 'get'
})
}
// 根据参数键名查询参数值
export function getConfigKey(configKey) {
return request({
url: '/system/config/configKey/' + configKey,
method: 'get'
})
}
// 新增参数配置
export function addConfig(data) {
return request({
url: '/system/config',
method: 'post',
data: data
})
}
// 修改参数配置
export function updateConfig(data) {
return request({
url: '/system/config',
method: 'put',
data: data
})
}
// 删除参数配置
export function delConfig(configId) {
return request({
url: '/system/config/' + configId,
method: 'delete'
})
}
// 刷新参数缓存
export function refreshCache() {
return request({
url: '/system/config/refreshCache',
method: 'delete'
})
}

@ -0,0 +1,55 @@
import request from '@/utils/request'
// 查询部门列表
export function listDept(query) {
return request({
url: '/system/dept/list',
method: 'get',
params: query
})
}
// 查询部门列表(排除节点)
export function listDeptExcludeChild(deptId) {
return request({
url: '/system/dept/list/exclude',
params: {deptId: deptId},
method: 'get'
})
}
// 查询部门详细
export function getDept(deptId) {
return request({
url: '/system/dept/getInfo',
params: {deptId: deptId},
method: 'get'
})
}
// 新增部门
export function addDept(data) {
return request({
url: '/system/dept',
method: 'post',
data: data
})
}
// 修改部门
export function updateDept(data) {
return request({
url: '/system/dept/edit',
method: 'put',
data: data
})
}
// 删除部门
export function delDept(deptId) {
return request({
url: '/system/dept/remove',
params: {deptId: deptId},
method: 'delete'
})
}

@ -0,0 +1,52 @@
import request from '@/utils/request'
// 查询字典数据列表
export function listData(query) {
return request({
url: '/system/dict/data/list',
method: 'get',
params: query
})
}
// 查询字典数据详细
export function getData(dictCode) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'get'
})
}
// 根据字典类型查询字典数据信息
export function getDicts(dictType) {
return request({
url: '/system/dict/data/type/' + dictType,
method: 'get'
})
}
// 新增字典数据
export function addData(data) {
return request({
url: '/system/dict/data',
method: 'post',
data: data
})
}
// 修改字典数据
export function updateData(data) {
return request({
url: '/system/dict/data',
method: 'put',
data: data
})
}
// 删除字典数据
export function delData(dictCode) {
return request({
url: '/system/dict/data/' + dictCode,
method: 'delete'
})
}

@ -0,0 +1,60 @@
import request from '@/utils/request'
// 查询字典类型列表
export function listType(query) {
return request({
url: '/system/dict/type/list',
method: 'get',
params: query
})
}
// 查询字典类型详细
export function getType(dictId) {
return request({
url: '/system/dict/type/' + dictId,
method: 'get'
})
}
// 新增字典类型
export function addType(data) {
return request({
url: '/system/dict/type',
method: 'post',
data: data
})
}
// 修改字典类型
export function updateType(data) {
return request({
url: '/system/dict/type',
method: 'put',
data: data
})
}
// 删除字典类型
export function delType(dictId) {
return request({
url: '/system/dict/type/' + dictId,
method: 'delete'
})
}
// 刷新字典缓存
export function refreshCache() {
return request({
url: '/system/dict/type/refreshCache',
method: 'delete'
})
}
// 获取字典选择框列表
export function optionselect() {
return request({
url: '/system/dict/type/optionselect',
method: 'get'
})
}

@ -0,0 +1,63 @@
import request from '@/utils/request'
// 查询菜单列表
export function listMenu(query) {
return request({
url: '/system/menu/list',
method: 'get',
params: query
})
}
// 查询菜单详细
export function getMenu(menuId) {
return request({
url: '/system/menu/getInfo',
params: {menuId},
method: 'get'
})
}
// 查询菜单下拉树结构
export function treeselect() {
return request({
url: '/system/menu/treeselect',
method: 'get'
})
}
// 根据角色ID查询菜单下拉树结构
export function roleMenuTreeselect(roleId) {
return request({
url: '/system/menu/roleMenuTreeselect',
params: {roleId},
method: 'get'
})
}
// 新增菜单
export function addMenu(data) {
return request({
url: '/system/menu/add',
method: 'post',
data: data
})
}
// 修改菜单
export function updateMenu(data) {
return request({
url: '/system/menu/edit',
method: 'put',
data: data
})
}
// 删除菜单
export function delMenu(menuId) {
return request({
url: '/system/menu/remove',
params: {menuId},
method: 'delete'
})
}

@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询公告列表
export function listNotice(query) {
return request({
url: '/system/notice/list',
method: 'get',
params: query
})
}
// 查询公告详细
export function getNotice(noticeId) {
return request({
url: '/system/notice/' + noticeId,
method: 'get'
})
}
// 新增公告
export function addNotice(data) {
return request({
url: '/system/notice',
method: 'post',
data: data
})
}
// 修改公告
export function updateNotice(data) {
return request({
url: '/system/notice',
method: 'put',
data: data
})
}
// 删除公告
export function delNotice(noticeId) {
return request({
url: '/system/notice/' + noticeId,
method: 'delete'
})
}

@ -0,0 +1,48 @@
import request from '@/utils/request'
// 查询岗位列表
export function listPost(query) {
return request({
url: '/system/post/list',
method: 'get',
params: query
})
}
// 查询岗位详细
export function getPost(postId) {
return request({
url: '/system/post/getInfo',
params: {postId},
method: 'get'
})
}
// 新增岗位
export function addPost(data) {
return request({
url: '/system/post/add',
method: 'post',
data: data
})
}
// 修改岗位
export function updatePost(data) {
return request({
url: '/system/post/edit',
method: 'put',
data: data
})
}
// 删除岗位
export function delPost(postId) {
return request({
url: '/system/post/remove',
params: {
postIds: postId instanceof Array ? postId.join(',') : postId
},
method: 'delete'
})
}

@ -0,0 +1,122 @@
import request from '@/utils/request'
// 查询角色列表
export function listRole(query) {
return request({
url: '/system/role/list',
method: 'get',
params: query
})
}
// 查询角色详细
export function getRole(roleId) {
return request({
url: '/system/role/getInfo',
params: {roleId},
method: 'get'
})
}
// 新增角色
export function addRole(data) {
return request({
url: '/system/role/add',
method: 'post',
data: data
})
}
// 修改角色
export function updateRole(data) {
return request({
url: '/system/role/edit',
method: 'put',
data: data
})
}
// 角色数据权限
export function dataScope(data) {
return request({
url: '/system/role/dataScope',
method: 'put',
data: data
})
}
// 角色状态修改
export function changeRoleStatus(roleId, status) {
const data = {
roleId,
status
}
return request({
url: '/system/role/changeStatus',
method: 'put',
data: data
})
}
// 删除角色
export function delRole(roleId) {
return request({
url: '/system/role/',
params: {roleId},
method: 'delete'
})
}
// 查询角色已授权用户列表
export function allocatedUserList(query) {
return request({
url: '/system/role/authUser/allocatedList',
method: 'get',
params: query
})
}
// 查询角色未授权用户列表
export function unallocatedUserList(query) {
return request({
url: '/system/role/authUser/unallocatedList',
method: 'get',
params: query
})
}
// 取消用户授权角色
export function authUserCancel(data) {
return request({
url: '/system/role/authUser/cancel',
method: 'put',
data: data
})
}
// 批量取消用户授权角色
export function authUserCancelAll(data) {
return request({
url: '/system/role/authUser/cancelAll',
method: 'put',
params: data
})
}
// 授权用户选择
export function authUserSelectAll(data) {
return request({
url: '/system/role/authUser/selectAll',
method: 'put',
params: data
})
}
// 根据角色ID查询部门树结构
export function deptTreeSelect(roleId) {
return request({
url: '/system/role/deptTree/',
params: {roleId},
method: 'get'
})
}

@ -0,0 +1,137 @@
import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi";
// 查询用户列表
export function listUser(query) {
return request({
url: '/system/user/list',
method: 'get',
params: query
})
}
// 查询用户详细
export function getUser(userId) {
return request({
url: '/system/user/getInfo',
params: {userId},
method: 'get'
})
}
// 新增用户
export function addUser(data) {
return request({
url: '/system/user/add',
method: 'post',
data: data
})
}
// 修改用户
export function updateUser(data) {
return request({
url: '/system/user',
method: 'put',
data: data
})
}
// 删除用户
export function delUser(userId) {
return request({
url: '/system/user/remove',
params: {userId},
method: 'delete'
})
}
// 用户密码重置
export function resetUserPwd(userId, password) {
const data = {
userId,
password
}
return request({
url: '/system/user/resetPwd',
method: 'put',
data: data
})
}
// 用户状态修改
export function changeUserStatus(userId, status) {
const data = {
userId,
status
}
return request({
url: '/system/user/changeStatus',
method: 'put',
data: data
})
}
// 查询用户个人信息
export function getUserProfile() {
return request({
url: '/system/user/profile',
method: 'get'
})
}
// 修改用户个人信息
export function updateUserProfile(data) {
return request({
url: '/system/user/profile/updateProfile',
method: 'put',
data: data
})
}
// 用户密码重置
export function updateUserPwd(oldPassword, newPassword) {
const data = {
oldPassword,
newPassword
}
return request({
url: '/system/user/profile/updatePwd',
method: 'put',
params: data
})
}
// 用户头像上传
export function uploadAvatar(data) {
return request({
url: '/system/user/profile/avatar',
method: 'post',
data: data
})
}
// 查询授权角色
export function getAuthRole(userId) {
return request({
url: '/system/user/authRole/' + userId,
method: 'get'
})
}
// 保存授权角色
export function updateAuthRole(data) {
return request({
url: '/system/user/authRole',
method: 'put',
params: data
})
}
// 查询部门下拉树结构
export function deptTreeSelect() {
return request({
url: '/system/user/deptTree',
method: 'get'
})
}

@ -0,0 +1,76 @@
import request from '@/utils/request'
// 查询生成表数据
export function listTable(query) {
return request({
url: '/tool/gen/list',
method: 'get',
params: query
})
}
// 查询db数据库列表
export function listDbTable(query) {
return request({
url: '/tool/gen/db/list',
method: 'get',
params: query
})
}
// 查询表详细信息
export function getGenTable(tableId) {
return request({
url: '/tool/gen/' + tableId,
method: 'get'
})
}
// 修改代码生成信息
export function updateGenTable(data) {
return request({
url: '/tool/gen',
method: 'put',
data: data
})
}
// 导入表
export function importTable(data) {
return request({
url: '/tool/gen/importTable',
method: 'post',
params: data
})
}
// 预览生成代码
export function previewTable(tableId) {
return request({
url: '/tool/gen/preview/' + tableId,
method: 'get'
})
}
// 删除表数据
export function delTable(tableId) {
return request({
url: '/tool/gen/' + tableId,
method: 'delete'
})
}
// 生成代码(自定义路径)
export function genCode(tableName) {
return request({
url: '/tool/gen/genCode/' + tableName,
method: 'get'
})
}
// 同步数据库
export function synchDb(tableName) {
return request({
url: '/tool/gen/synchDb/' + tableName,
method: 'get'
})
}

@ -0,0 +1,433 @@
import axios from "@/utils/request";
const baseURL = import.meta.env.VITE_APP_BASE_PROCESS_API;
/**
* @description 获取流程图列表
* @param {Object} data - 查询参数对象
* @param {string} [data.firstResult] - 起始索引
* @param {string} [data.maxResults] - 最大结果数
* @param {boolean} [data.latestVersion] - 是否最新版本
* @returns {Promise} 返回流程图列表数据
*/
export const getProcessList = ({
firstResult = 0,
maxResults = 8,
latestVersion = true,
}) => {
return axios.request({
baseURL: baseURL,
url: "/engine-rest/process-definition",
method: "get",
params: {
firstResult,
maxResults,
latestVersion,
},
dataType: "json",
});
};
/**
* @description 获取流程图信息
* @param {string} processId - 流程图ID
* @returns {Promise} 返回流程图信息数据
*/
export const getProcessInfo = (processId) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/process-definition/${processId}`,
method: "get",
dataType: "json",
});
};
/**
* @description 获取流程图xml
* @param {string} processId - 流程图ID
* @returns {Promise} 返回流程图xml数据
*/
export const getProcessXml = (processId) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/process-definition/${processId}/xml`,
method: "get",
dataType: "json",
});
};
/**
* @description 上传流程图
* @param {FormData} data - 流程图数据
* @param {Blob} [data.content] - 流程图文件 formData.append('content', bpmnBlob, 'process.bpmn'); // 文件名应以 .bpmn 结尾
* @returns {Promise} 返回上传结果
*/
export const addModeler = (data) => {
return axios.request({
baseURL,
url: "/engine-rest/deployment/create",
headers: {
"Content-Type": "multipart/form-data",
},
method: "post",
data: data,
dataType: "json",
});
};
/**
* @description 删除流程图
* @param {string} processId - 流程图ID
* @returns {Promise} 返回删除结果
*/
export const deleteProcess = (processId) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/process-definition/${processId}`,
method: "delete",
dataType: "json",
});
};
/**
* @description 创建流程实例
* @param {object} data
* @param {string} data.processKey - 流程图key
* @param {string} data.formId - 表单ID
* @param {string} data.createUser - 创建人
* @param {object} data.formValues - 填写的表单数据
* @returns {Promise} 返回创建结果
*/
export const start = (data) => {
return axios.request({
baseURL: baseURL,
url: `/start`,
method: "post",
data: data,
dataType: "json",
});
};
//------instance
/**
* @description 获取流程实例列表
* @param {Object} data - 查询参数对象
* @param {string} [data.firstResult] - 起始索引
* @param {string} [data.maxResults] - 最大结果数
* @param {string} [data.processDefinitionKey] - 流程图key
* @param {string} [data.sortOrder] - 排序方式 asc/desc
* @param {string} [data.sortBy] - 排序字段 startTime
* @returns {Promise} 返回流程图实例列表数据
*/
export const getProcessInstanceList = ({
firstResult = 0,
maxResults = 8,
processDefinitionKey,
sortOrder = "desc",
sortBy = "startTime",
}) => {
return axios.request({
baseURL: baseURL,
url: "/engine-rest/history/process-instance",
method: "get",
params: {
firstResult,
maxResults,
processDefinitionKey,
sortOrder,
sortBy,
},
dataType: "json",
});
};
/**
* @description 删除流程实例
* @param {string} processInstanceId - 流程实例ID
* @returns {Promise} 返回删除结果
*/
export const deleteProcessInstance = (processInstanceId) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/process-instance/${processInstanceId}`,
method: "delete",
// data: arg,
dataType: "json",
});
};
///TASK----------
/**
* @description 获取流程实例下的任务
* @param {Object} data - 查询参数对象
* @param {string} [data.firstResult] - 起始索引
* @param {string} [data.maxResults] - 最大结果数
* @param {string} [data.processInstanceId] - 流程实例ID
* @returns {Promise} 返回任务列表数据
*/
export const getInstanceTaskList = ({
firstResult = 0,
maxResults = 8,
processInstanceId,
}) => {
return axios.request({
baseURL: baseURL,
url: "/engine-rest/history/task",
method: "get",
params: {
firstResult,
maxResults,
processInstanceId,
},
dataType: "json",
});
};
/**
* @description 查询任务节点候选人/
* @param {string} taskId - 任务ID
* @returns {Promise} 返回任务候选人/组数据
*/
export const getTaskCandidate = (taskId) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${taskId}/identity-links`,
method: "get",
dataType: "json",
});
};
/**
* @description 添加节点候选人/
* @param {object} data
* @param {string} data.taskId - 任务ID
* @param {string} data.type - 类型 candidateassignee
* @param {string} data.userId - 用户ID二选一
* @param {string} data.groupId - 组ID二选一
* @returns {Promise} 返回添加结果
*/
export const addTaskCandidate = (data) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${taskId}/identity-links`,
method: "post",
data: data,
dataType: "json",
});
};
/**
* @description 删除节点候选人/
* @param {object} data
* @param {string} data.taskId - 任务ID
* @param {string} data.type - 类型 candidateassignee
* @param {string} data.userId - 用户ID二选一
* @param {string} data.groupId - 组ID二选一
* @returns {Promise} 返回删除结果
*/
export const deleteTaskCandidate = (data) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${data.taskId}/identity-links/delete`,
method: "post",
data: data,
dataType: "json",
});
};
/**
* @description 获取任务列表
* @param {Object} data - 查询参数对象
* @param {string} [data.firstResult] - 起始索引
* @param {string} [data.maxResults] - 最大结果数
* @param {string} [data.assignee] - 执行人ID
* @param {string} [data.candidateUser] - 候选人ID二选一
* @param {string} [data.candidateGroup] - 候选组ID二选一
* @returns {Promise} 返回任务列表数据
*/
export const getUserTaskList = ({
firstResult = 0,
maxResults = 8,
assignee,
candidateUser,
candidateGroup,
}) => {
return axios.request({
baseURL: baseURL,
url: "/engine-rest/task",
method: "get",
params: {
firstResult,
maxResults,
assignee,
candidateUser,
candidateGroup,
},
dataType: "json",
});
};
/**
* @description 认领任务
* @param {object} data
* @param {string} data.taskId - 任务ID
* @param {string} data.userId - 用户ID
* @returns {Promise} 返回认领结果
*/
export const claim = (data) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${data.taskId}/claim`,
method: "post",
data: data,
dataType: "json",
});
};
/**
* @description 取消任务执行人
* @param {string} taskId - 任务ID
* @returns {Promise} 返回取消结果
*/
// 取消任务执行人:
export const unclaim = (taskId) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${taskId}/unclaim`,
method: "post",
dataType: "json",
});
};
/**
* @description 完成任务
* @param {Object} data - 包含任务完成相关信息的对象参数具体结构如下
* @param {string} data.taskId - 任务ID用于唯一标识需要完成的具体任务是一个字符串类型的值通常由系统分配并在任务流转过程中传递使用
* @param {Object} data.variables - 任务变量对象用于携带任务执行过程中涉及的各类变量信息其内部包含具体的业务相关变量结构如下
* @param {Object} data.variables.approvalResult - 与审批结果相关的变量对象用于传递审批相关信息包含以下属性
* @param {"approved" | "rejected"} data.variables.approvalResult.value - 审批结果取值为 'approved' 表示审批通过'rejected' 表示审批未通过类型为字符串
* @param {"String"} data.variables.approvalResult.type - 变量类型标识此处固定为 'String'表明审批结果变量的数据类型是字符串类型用于系统在处理变量时进行类型校验等操作
* @returns {Promise} 返回完成结果
*/
export const complete = (data) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${data.taskId}/complete`,
method: "post",
data: {
variables: data.variables,
},
dataType: "json",
});
};
/**
* @description 创建评论--自定义任务执行结果变量
* @param {object} data
* @param {string} data.taskId - 任务ID
* @param {json} data.message - 评论内容-json字符串
* @returns {Promise} 返回创建结果
*/
export const setComment = (data) => {
return axios.request({
baseURL: baseURL,
url: `/engine-rest/task/${data.taskId}/comment/create`,
method: "post",
data: data,
dataType: "json",
});
};
//-Form----------
/**
* @description 获取流程表单列表
* @param {object} data
* @param {string} [data.page] - 页码
* @param {string} [data.start] - 起始索引
* @param {string} [data.limit] - 最大结果数
* @returns {Promise} 返回流程表单列表数据
*/
export const getFormList = (data) => {
return axios.request({
baseURL: baseURL,
url: "/form",
method: "get",
params: data,
dataType: "json",
});
};
/**
* @description 获取流程表单详情
* @param {string} formId
* @returns {Promise} 返回流程表单详情数据
*/
export const getFormDetail = (formId) => {
return axios.request({
baseURL: baseURL,
url: `/form/${formId}`,
method: "get",
dataType: "json",
});
};
/**
* @description 获取流程表单字段
* @param {string} path
* @returns {Promise} 返回流程表单字段数据
*/
export const getFormFields = (path) => {
return axios.request({
baseURL: baseURL,
url: `/form/fields`,
method: "get",
params: {
path,
},
dataType: "json",
});
};
/**
* @description 添加流程表单
* @param {object} data
* @param {string} data.formName - 表单名称
* @param {string} data.formDesc - 表单描述
* @param {string} data.formPath - 表单内容
* @param {string} data.userId - 创建人ID
* @returns {Promise} 返回添加结果
*/
export const addForm = (data) => {
return axios.request({
baseURL: baseURL,
url: "/form",
method: "post",
data: data,
dataType: "json",
});
};
/**
* @description 修改流程表单
* @param {object} data
* @param {string} data.id - 表单ID
* @param {string} data.formName - 表单名称
* @param {string} data.formDesc - 表单描述
* @param {string} data.formPath - 表单内容
* @returns {Promise} 返回修改结果
*/
export const updateForm = (data) => {
return axios.request({
baseURL: baseURL,
url: "/form",
method: "put",
data: data,
dataType: "json",
});
};
/**
* @description 删除流程表单
* @param {string} formId
* @returns {Promise} 返回删除结果
*/
// 删除数据:
export const deleteForm = (formId) => {
return axios.request({
baseURL: baseURL,
url: "/form/" + formId,
method: "delete",
dataType: "json",
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

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

After

Width:  |  Height:  |  Size: 1.4 KiB

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

Loading…
Cancel
Save