customModeler.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  1. <template>
  2. <div class="app-container">
  3. <div class="containers">
  4. <div class="canvas" ref="canvas"></div>
  5. <ul class="buttons">
  6. <li>
  7. <a href="javascript:" class="active" ref="saveXML" title="保存为bpmn"
  8. >保存为bpmn</a
  9. >
  10. </li>
  11. <li>
  12. <a href="javascript:" class="active" ref="saveSvg" title="保存为svg"
  13. >保存为svg</a
  14. >
  15. </li>
  16. <li>
  17. <a
  18. href="javascript:"
  19. class="active"
  20. @click="handlerUndo"
  21. title="撤销操作"
  22. >撤销</a
  23. >
  24. </li>
  25. <li>
  26. <a
  27. href="javascript:"
  28. class="active"
  29. @click="handlerRedo"
  30. title="恢复操作"
  31. >恢复</a
  32. >
  33. </li>
  34. <li>
  35. <a
  36. href="javascript:"
  37. class="active"
  38. @click="handlerZoom(0.1)"
  39. title="放大"
  40. >放大</a
  41. >
  42. </li>
  43. <li>
  44. <a
  45. href="javascript:"
  46. class="active"
  47. @click="handlerZoom(-0.1)"
  48. title="缩小"
  49. >缩小</a
  50. >
  51. </li>
  52. <li>
  53. <a
  54. href="javascript:"
  55. class="active"
  56. @click="handlerZoom(0)"
  57. title="还原"
  58. >还原</a
  59. >
  60. </li>
  61. <li>
  62. <el-button type="primary" @click="submitBpmn" v-if="!loading" title="提交流程图">提 交</el-button>
  63. <el-button :loading="loading" type="primary" @click="submitBpnm" v-else>提交中...</el-button>
  64. </li>
  65. </ul>
  66. </div>
  67. <!-- 添加或修改节点参数配置对话框 -->
  68. <el-dialog title="修改" :visible.sync="serviceTask" width="600px" append-to-body :before-close="cancel" :close-on-click-modal="false">
  69. <el-form ref="form" :model="formData" :rules="rules" label-width="120px" >
  70. <el-row :gutter="20">
  71. <el-col :span="24" >
  72. <el-form-item label="节点名称" prop="name" >
  73. <el-input v-model.trim="formData.name" placeholder="请输入节点名称" @input="onInput" maxlength="30" show-word-limit />
  74. </el-form-item>
  75. </el-col>
  76. <!-- <el-col :span="24">
  77. <el-form-item label="执行顺序" prop="order">
  78. <el-input v-model.trim="formData.order" placeholder="请输入执行顺序" @input="onInput" maxlength="30" show-word-limit />
  79. </el-form-item>
  80. </el-col> -->
  81. <el-col :span="24">
  82. <el-form-item label="执行场景" prop="nodeId">
  83. <treeselect v-model="formData.nodeId" :options="dataOptions" :show-count="true" placeholder="请选择执行场景" />
  84. </el-form-item>
  85. </el-col>
  86. </el-row>
  87. </el-form>
  88. <div slot="footer" class="dialog-footer">
  89. <el-button type="primary" @click="submitForm" v-if="!loading">确 定</el-button>
  90. <el-button :loading="loading" type="primary" @click="submitForm" v-else>提交中...</el-button>
  91. <el-button @click="cancel">取 消</el-button>
  92. </div>
  93. </el-dialog>
  94. <!-- 添加或修改流程图参数配置对话框 -->
  95. <el-dialog title="修改" :visible.sync="openBpmn" width="600px" append-to-body :before-close="cancel" :close-on-click-modal="false">
  96. <el-form ref="form2" :model="formData2" :rules="rules2" label-width="120px" >
  97. <el-row :gutter="20">
  98. <el-col :span="24" >
  99. <el-form-item label="流程图名称" prop="name" >
  100. <el-input v-model.trim="formData2.name" placeholder="请输入流程图名称" @input="onInput" maxlength="30" show-word-limit />
  101. </el-form-item>
  102. </el-col>
  103. <el-col :span="24" >
  104. <el-form-item label="流程图描述" prop="procdefDescribe" >
  105. <el-input type="textarea" v-model.trim="formData2.procdefDescribe" placeholder="请输入流程图描述" @input="onInput" maxlength="60" show-word-limit />
  106. </el-form-item>
  107. </el-col>
  108. </el-row>
  109. </el-form>
  110. <div slot="footer" class="dialog-footer">
  111. <el-button type="primary" @click="submitBpnmForm" v-if="!loading">确 定</el-button>
  112. <el-button :loading="loading" type="primary" @click="submitBpnmForm" v-else>提交中...</el-button>
  113. <el-button @click="cancelBpnm">取 消</el-button>
  114. </div>
  115. </el-dialog>
  116. </div>
  117. </template>
  118. <script>
  119. // 引入相关的依赖
  120. import { xmlStr } from "@/mock/xmlStrPreview2";
  121. import { CustomModeler } from "@/components/customBpmn";
  122. import Treeselect from "@riophae/vue-treeselect";
  123. import '@riophae/vue-treeselect/dist/vue-treeselect.css'
  124. import { getBpmnZkNodeList, getBpmnZkNodeTreeList, addBpmnZkNode, updataBpmnZkNode,
  125. getBpmnZkReNodeProcdefTreeList, getBpmnZkReNodeProcdefCheckedTreeList,
  126. getBpmnZkReProcdefList, addBpmnZkReProcdef, updataBpmnZkReProcdef, delBpmnZkReProcdef, getBpmnZkReNodeProcdefListByPorcedfId} from "@/api/business/Middleware/bpmn";
  127. export default {
  128. name: "",
  129. components: { Treeselect },
  130. data() {
  131. return {
  132. bpmnModeler: null,
  133. container: null,
  134. canvas: null,
  135. scale: 1,
  136. userTask: false,
  137. serviceTask: false,
  138. scriptTask: false,
  139. sequenceFlow: "",
  140. formData: {
  141. name: "",
  142. type: "",
  143. nodeId:undefined,
  144. // order:undefined
  145. },
  146. //节点树选项
  147. dataOptions: [],
  148. data:undefined,
  149. openBpmn:false,
  150. // 节点表单校验
  151. rules: {
  152. name: [
  153. { required: true, message: "节点名称不能为空", trigger: "blur" }
  154. ],
  155. // order: [
  156. // { required: true, message: "执行顺序不能为空", trigger: "blur" }
  157. // ],
  158. nodeId: [
  159. { required: true, message: "场景不能为空", trigger: ["blur",'change'] }
  160. ],
  161. },
  162. formData2: {
  163. name: undefined,
  164. procdefDescribe:undefined,
  165. },
  166. // 流程图表单校验
  167. rules2: {
  168. name: [
  169. { required: true, message: "流程描述名称不能为空", trigger: "blur" }
  170. ],
  171. procdefDescribe: [
  172. { required: true, message: "流程图描述不能为空", trigger: ["blur",'change'] }
  173. ],
  174. },
  175. loading:false,
  176. bpmnData:[], //待提交数据,
  177. bpmnTitle:undefined,//待提交流程图名称
  178. bpmnNodeId:undefined,// 弹框打开时节点Id
  179. xml:undefined,
  180. serviceTaskStatus:false,
  181. checkedKeys:[],//流程图返回节点
  182. bpmnDataFH:[],//流程图返回信息
  183. };
  184. },
  185. created(){
  186. },
  187. mounted() {
  188. this.getNodeTree()
  189. if(this.$route.query.name){
  190. getBpmnZkReProcdefList({name:this.$route.query.name,current: 1,size: 20}).then(res=>{
  191. this.bpmnDataFH = res.data.records[0]
  192. this.init()
  193. })
  194. getBpmnZkReNodeProcdefListByPorcedfId({procdefId:this.$route.query.id}).then(res=>{
  195. this.checkedKeys = res.data
  196. })
  197. }else{
  198. this.init()
  199. }
  200. },
  201. // 方法集合
  202. methods: {
  203. //强制el-input刷新
  204. onInput(){
  205. this.$forceUpdate();
  206. },
  207. init() {
  208. // 获取到属性ref为“canvas”的dom节点
  209. const canvas = this.$refs.canvas;
  210. // 建模
  211. this.bpmnModeler = new CustomModeler({
  212. container: canvas,
  213. additionalModules: [
  214. {
  215. // 禁止编辑label
  216. labelEditingProvider: ["value", ""]
  217. }
  218. ]
  219. });
  220. this.createNewDiagram();
  221. },
  222. async createNewDiagram() {
  223. let that = this
  224. try {
  225. //编辑或默认视图
  226. if(that.bpmnDataFH.resourceName){
  227. var result = await this.bpmnModeler.importXML(that.bpmnDataFH.resourceName);
  228. }else{
  229. var result = await this.bpmnModeler.importXML(xmlStr);
  230. }
  231. // 上传
  232. console.log(result)
  233. const { warnings } = result;
  234. this.data = result.warnings
  235. console.log(warnings);
  236. this.success();
  237. } catch (err) {
  238. console.log(err.message, err.warnings);
  239. }
  240. },
  241. success() {
  242. this.addBpmnListener();
  243. this.addModelerListener();
  244. this.addEventBusListener();
  245. },
  246. addModelerListener() {
  247. const bpmnjs = this.bpmnModeler;
  248. const that = this;
  249. // 这里我是用了一个forEach给modeler上添加要绑定的事件
  250. const events = [
  251. "shape.added",
  252. "shape.move.end",
  253. "shape.removed",
  254. "connect.end",
  255. "connect.move"
  256. ];
  257. events.forEach(function(event) {
  258. that.bpmnModeler.on(event, e => {
  259. console.log(1,event, e,that.data );
  260. var elementRegistry = bpmnjs.get("elementRegistry");
  261. var shape = e.element ? elementRegistry.get(e.element.id) : e.shape;
  262. console.log(2,shape);
  263. });
  264. });
  265. },
  266. // 下载
  267. async addBpmnListener() {
  268. console.log(11111)
  269. const that = this;
  270. const downloadLink = this.$refs.saveXML;
  271. console.log(downloadLink)
  272. const downloadSvgLink = this.$refs.saveSvg;
  273. async function opscoffee() {
  274. try {
  275. const result = await that.saveSVG();
  276. const { svg } = result;
  277. that.setEncoded(downloadSvgLink, "ops-coffee.svg", svg);
  278. } catch (err) {
  279. console.log(err);
  280. }
  281. try {
  282. const result = await that.saveXML();
  283. const { xml } = result;
  284. that.setEncoded(downloadLink, "ops-coffee.bpmn", xml);
  285. } catch (err) {
  286. console.log(err);
  287. }
  288. }
  289. opscoffee();
  290. this.bpmnModeler.on("commandStack.changed", opscoffee);
  291. },
  292. async saveSVG(done) {
  293. try {
  294. const result = await this.bpmnModeler.saveSVG(done);
  295. return result;
  296. } catch (err) {
  297. console.log(err);
  298. }
  299. },
  300. async saveXML(done) {
  301. try {
  302. const result = await this.bpmnModeler.saveXML({ format: true }, done);
  303. this.xml = result
  304. // console.log(this.xml)
  305. return result;
  306. } catch (err) {
  307. console.log(err);
  308. }
  309. },
  310. setEncoded(link, name, data) {
  311. const encodedData = encodeURIComponent(data);
  312. if (data) {
  313. link.href = "data:application/bpmn20-xml;charset=UTF-8," + encodedData;
  314. link.download = name;
  315. }
  316. },
  317. handlerRedo() {
  318. this.bpmnModeler.get("commandStack").redo();
  319. },
  320. handlerUndo() {
  321. this.bpmnModeler.get("commandStack").undo();
  322. },
  323. handlerZoom(radio) {
  324. const newScale = !radio ? 1.0 : this.scale + radio;
  325. this.bpmnModeler.get("canvas").zoom(newScale);
  326. this.scale = newScale;
  327. },
  328. addEventBusListener() {
  329. const that = this;
  330. const eventBus = this.bpmnModeler.get("eventBus");
  331. const modeling = this.bpmnModeler.get("modeling");
  332. const elementRegistry = this.bpmnModeler.get("elementRegistry");
  333. eventBus.on("element.click", function(e) {
  334. // console.log(
  335. // "点击了element",
  336. // e.element.businessObject.id,
  337. // e.element.businessObject.$type,
  338. // e.element.businessObject.name,that.data
  339. // );
  340. e.element.businessObject.nodeId = that.formData.nodeId
  341. // e.element.businessObject.order = that.formData.order
  342. console.log(
  343. "点击了element",
  344. e,e.element.businessObject.name,
  345. e.element.businessObject.nodeId
  346. );
  347. if (e.element.businessObject.$type == "bpmn:SequenceFlow") {
  348. const sourceRef = e.element.businessObject.sourceRef;
  349. if (sourceRef.$type == "bpmn:ExclusiveGateway") {
  350. var targetElement = elementRegistry.get(sourceRef.id);
  351. modeling.updateProperties(targetElement, {
  352. default: e.element.businessObject
  353. });
  354. }
  355. }
  356. });
  357. },
  358. // 节点取消按钮
  359. cancel() {
  360. this.serviceTask = false
  361. // this.reset();
  362. },
  363. // 表单重置
  364. reset() {
  365. this.formData = {
  366. name: undefined,
  367. order: undefined,
  368. deptId: undefined,
  369. type: undefined
  370. };
  371. this.resetForm("form");
  372. },
  373. /** 节点提交按钮 */
  374. submitForm(row) {
  375. let that = this
  376. this.$refs["form"].validate(valid => {
  377. if (valid) {
  378. this.msgSuccess("节点属性保存成功");
  379. this.loading = false
  380. let data = this.dataOptions
  381. for(let a = 0; a<Object.keys(data).length; a++){
  382. if(data[a].id == this.formData.nodeId){
  383. this.formData.param = data[a].params
  384. }
  385. if(data[a].children){
  386. if(Object.keys(data[a].children).length>0){
  387. for(let b = 0; b<Object.keys(data[a].children).length; b++){
  388. if(data[a].children[b].id == this.formData.nodeId){
  389. this.formData.param = data[a].children[b].params
  390. }
  391. if(data[a].children[b].children){
  392. if(Object.keys(data[a].children[b].children).length>0){
  393. for(let c = 0; c<Object.keys(data[a].children[b].children).length; c++){
  394. if(data[a].children[b].children[c].id == this.formData.nodeId){
  395. this.formData.param = data[a].children[b].children[c].params
  396. }
  397. }
  398. }
  399. }
  400. }
  401. }
  402. }
  403. }
  404. this.formData.procedefKey = this.$store.state.bpmn.nodeInfo.businessObject.id
  405. delete this.formData.type
  406. let arr = JSON.parse(JSON.stringify(this.bpmnData))
  407. if(arr.length>0){
  408. let num = 0
  409. for(let i =0;i<arr.length;i++){
  410. if(this.$store.state.bpmn.nodeInfo.businessObject.id == arr[i].procedefKey){
  411. arr[i] = this.formData
  412. }else{
  413. num +=1
  414. if(num == arr.length){
  415. this.bpmnData.push(this.formData)
  416. }
  417. }
  418. }
  419. }else{
  420. this.bpmnData.push(this.formData)
  421. }
  422. //异步避免需要二次关闭
  423. setTimeout(()=>{
  424. that.serviceTask = false
  425. },1)
  426. }
  427. });
  428. },
  429. /** 流程图提交按钮 */
  430. submitBpmn(){
  431. if(this.bpmnDataFH.name){
  432. this.formData2.procdefDescribe = this.bpmnDataFH.procdefDescribe
  433. this.formData2.name = this.bpmnDataFH.name
  434. }
  435. this.openBpmn = true
  436. },
  437. /** 流程图提交数据 */
  438. submitBpnmForm(){
  439. this.$refs["form2"].validate(valid => {
  440. if (valid) {
  441. if(this.bpmnData.length<2 && this.checkedKeys.length<2){
  442. this.msgError("流程图节点数据个数小于2");
  443. }else{
  444. if(this.checkedKeys.length>1){
  445. let arr = {}
  446. arr.name = this.formData2.name
  447. arr.procdefDescribe = this.formData2.procdefDescribe
  448. arr.procedefKey = this.formData2.procedefKey
  449. arr.nodes = []
  450. for(let i = 0; i<this.checkedKeys.length;i++){
  451. arr.nodes[i] = {}
  452. arr.nodes[i].param = this.checkedKeys[i].param
  453. arr.nodes[i].procedefKey = this.checkedKeys[i].procedefKey
  454. arr.nodes[i].nodeId = this.checkedKeys[i].id
  455. if(this.bpmnData.length>0){
  456. for(let n=0;n<this.bpmnData.length;n++){
  457. if(this.bpmnData[n].procedefKey){
  458. if(this.bpmnData[n].procedefKey == arr.nodes[i].procedefKey){
  459. arr.nodes[i].param = this.bpmnData[n].param
  460. arr.nodes[i].nodeId = this.bpmnData[n].nodeId
  461. }
  462. }
  463. }
  464. }
  465. }
  466. arr.resourceName = this.xml.xml;
  467. arr.id = this.bpmnDataFH.id
  468. console.log(arr)
  469. updataBpmnZkReProcdef(arr).then(res=>{
  470. this.msgSuccess("流程图修改成功");
  471. this.loading = false
  472. this.openBpmn = false
  473. this.$router.push('/business/Middleware/bpmn/index')
  474. })
  475. }else if(this.bpmnData.length>1){
  476. let arr = {}
  477. arr.name = this.formData2.name
  478. arr.procdefDescribe = this.formData2.procdefDescribe
  479. arr.procedefKey = this.formData2.procedefKey
  480. arr.nodes = this.bpmnData
  481. arr.resourceName = this.xml.xml;
  482. console.log(arr)
  483. addBpmnZkReProcdef(arr).then(res=>{
  484. this.msgSuccess("流程图保存成功");
  485. this.loading = false
  486. this.openBpmn = false
  487. this.$router.push('/business/Middleware/bpmn/index')
  488. })
  489. }
  490. }
  491. }
  492. });
  493. },
  494. /** 流程图取消按钮 */
  495. cancelBpnm(){
  496. this.openBpmn = false
  497. },
  498. getNodeTree(){
  499. getBpmnZkNodeTreeList().then(res =>{
  500. if(res.data){
  501. for(let a = 0; a<Object.keys(res.data).length; a++){
  502. this.dataOptions[a] = []
  503. this.dataOptions[a].label = res.data[a].name
  504. this.dataOptions[a].id = res.data[a].id
  505. this.dataOptions[a].params = res.data[a].params
  506. if(res.data[a].children){
  507. if(Object.keys(res.data[a].children).length>0){
  508. this.dataOptions[a].children = []
  509. for(let b = 0; b<Object.keys(res.data[a].children).length; b++){
  510. this.dataOptions[a].children[b] = {}
  511. this.dataOptions[a].children[b].label = res.data[a].children[b].name
  512. this.dataOptions[a].children[b].id = res.data[a].children[b].id
  513. this.dataOptions[a].children[b].params = res.data[a].children[b].params
  514. if(res.data[a].children[b].children){
  515. if(Object.keys(res.data[a].children[b].children).length>0){
  516. this.dataOptions[a].children[b].children = []
  517. for(let c = 0; c<Object.keys(res.data[a].children[b].children).length; c++){
  518. this.dataOptions[a].children[b].children[c] = {}
  519. this.dataOptions[a].children[b].children[c].label = res.data[a].children[b].children[c].name
  520. this.dataOptions[a].children[b].children[c].id = res.data[a].children[b].children[c].id
  521. this.dataOptions[a].children[b].children[c].params= res.data[a].children[b].children[c].params
  522. }
  523. }
  524. }
  525. }
  526. }
  527. }
  528. }
  529. // console.log(this.dataOptions)
  530. }
  531. })
  532. },
  533. currDeptChange(val) {
  534. console.log('currDeptChange', val)
  535. },
  536. },
  537. // 计算属性
  538. computed: {
  539. task: {
  540. get: function() {
  541. const that = this;
  542. const element = this.$store.state.bpmn.nodeInfo;
  543. if (element.businessObject) {
  544. // console.log(
  545. // element.businessObject.id,
  546. // element.businessObject.name,
  547. // element.businessObject.$type
  548. // );
  549. // if (element.businessObject.$type === "bpmn:UserTask") {
  550. // that.formData.type = "用户任务1";
  551. // that.formData.name = element.businessObject.name;
  552. // that.userTask = this.$store.state.bpmn.nodeVisible;
  553. // }
  554. if (element.businessObject.$type === "bpmn:ServiceTask") {
  555. that.formData = {
  556. name: "",
  557. type: "",
  558. nodeId:undefined,
  559. }
  560. that.formData.name = element.businessObject.name;
  561. let data = that.bpmnData
  562. if(that.checkedKeys.length>0){
  563. for(let i = 0; i<that.checkedKeys.length;i++){
  564. if(element.businessObject.id == that.checkedKeys[i].procedefKey){
  565. this.formData.nodeId = that.checkedKeys[i].id
  566. }
  567. }
  568. }
  569. if(data.length>0){
  570. for(let i = 0; i<data.length;i++){
  571. if(element.businessObject.id == data[i].procedefKey){
  572. this.formData.nodeId = data[i].nodeId
  573. }
  574. }
  575. }
  576. // if(that.serviceTaskStatus == false){
  577. // that.serviceTask = true
  578. // }else{
  579. // that.serviceTask = false
  580. // }
  581. that.serviceTask = this.$store.state.bpmn.nodeVisible;
  582. console.log(data,this.formData)
  583. }
  584. // if (element.businessObject.$type === "bpmn:ScriptTask") {
  585. // that.formData.type = "脚本任务1";
  586. // that.formData.name = element.businessObject.name;
  587. // that.serviceTask = this.$store.state.bpmn.nodeVisible;
  588. // }
  589. // if (element.businessObject.$type === "bpmn:SequenceFlow") {
  590. // that.sequenceFlow = element.businessObject.name;
  591. // that.scriptTask = this.$store.state.bpmn.nodeVisible;
  592. // }
  593. }
  594. return false;
  595. },
  596. set: function(val) {
  597. this.$store.state.bpmn.nodeVisible = val;
  598. }
  599. }
  600. },
  601. watch: {
  602. 'formData.nodeId': 'currDeptChange',
  603. task(val) {},
  604. userTask(val) {
  605. this.$store.state.bpmn.nodeVisible = val;
  606. },
  607. serviceTask(val) {
  608. this.$store.state.bpmn.nodeVisible = val;
  609. },
  610. scriptTask(val) {
  611. this.$store.state.bpmn.nodeVisible = val;
  612. },
  613. sequenceFlow(val) {
  614. const element = this.$store.state.bpmn.nodeInfo;
  615. const modeling = this.bpmnModeler.get("modeling");
  616. modeling.updateLabel(element, val);
  617. },
  618. "formData.name": {
  619. handler(val, old) {
  620. const element = this.$store.state.bpmn.nodeInfo;
  621. const modeling = this.bpmnModeler.get("modeling");
  622. modeling.updateLabel(element, val);
  623. },
  624. deep: true
  625. },
  626. }
  627. };
  628. </script>
  629. <style scoped>
  630. .containers {
  631. background: white;
  632. overflow: auto;
  633. background-image: linear-gradient(
  634. 90deg,
  635. rgba(220, 220, 220, 0.5) 6%,
  636. transparent 0
  637. ),
  638. linear-gradient(rgba(192, 192, 192, 0.5) 6%, transparent 0);
  639. background-size: 12px 12px;
  640. width: 100%;
  641. height: 85vh;
  642. -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
  643. }
  644. .canvas {
  645. width: 100%;
  646. height: 100%;
  647. }
  648. .panel {
  649. position: absolute;
  650. right: 0;
  651. top: 0;
  652. width: 300px;
  653. }
  654. .buttons {
  655. position: absolute;
  656. left: 80px;
  657. bottom: 20px;
  658. }
  659. .buttons li {
  660. display: inline-block;
  661. margin: 5px;
  662. }
  663. .buttons li a {
  664. color: #999;
  665. background: #eee;
  666. cursor: not-allowed;
  667. padding: 8px;
  668. border: 1px solid #ccc;
  669. text-decoration: none;
  670. }
  671. .buttons li a.active {
  672. color: #333;
  673. background: #fff;
  674. cursor: pointer;
  675. }
  676. .demo-drawer-footer {
  677. width: 100%;
  678. position: absolute;
  679. bottom: 0;
  680. left: 0;
  681. border-top: 1px solid #e8e8e8;
  682. padding: 10px 16px;
  683. text-align: right;
  684. background: #fff;
  685. }
  686. </style>