ui库和web端产品库合并版本(还需修复细节)
This commit is contained in:
675
ui/packages/tools/AiCodeEditor.vue
Normal file
675
ui/packages/tools/AiCodeEditor.vue
Normal file
@@ -0,0 +1,675 @@
|
||||
<template>
|
||||
<section class="AiCodeEditor ">
|
||||
<div class="dark" ref="editorContent"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from "codemirror"
|
||||
// modes
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/mode/css/css.js'
|
||||
import 'codemirror/mode/htmlmixed/htmlmixed.js'
|
||||
|
||||
// addons
|
||||
import 'codemirror/addon/edit/closebrackets.js'
|
||||
import 'codemirror/addon/edit/closetag.js'
|
||||
import 'codemirror/addon/comment/comment.js'
|
||||
import 'codemirror/addon/fold/foldcode.js'
|
||||
import 'codemirror/addon/fold/foldgutter.js'
|
||||
import 'codemirror/addon/fold/brace-fold.js'
|
||||
import 'codemirror/addon/fold/indent-fold.js'
|
||||
import 'codemirror/addon/fold/comment-fold.js'
|
||||
|
||||
export default {
|
||||
name: "AiCodeEditor",
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: {default: ""},
|
||||
mode: {default: "htmlmixed"},
|
||||
readonly: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
if (v != this.editor?.getValue()) {
|
||||
this.editor?.setValue(v)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initEditor() {
|
||||
const options = {
|
||||
...this.$props,
|
||||
tabSize: 2,
|
||||
autoCloseBrackets: true,
|
||||
autoCloseTags: true,
|
||||
foldGutter: true,
|
||||
lineWrapping: true,
|
||||
lineNumbers: true,
|
||||
// theme: 'idea',
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
}
|
||||
this.editor = new CodeMirror(this.$refs.editorContent, options)
|
||||
this.editor.on('change', () => {
|
||||
this.$emit("change", this.editor.getValue())
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initEditor()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.AiCodeEditor {
|
||||
background-color: #242424;
|
||||
|
||||
.CodeMirror {
|
||||
color: var(--symbols);
|
||||
--symbols: #777;
|
||||
--base: #545281;
|
||||
--comment: hsl(210, 25%, 60%);
|
||||
--keyword: #af4ab1;
|
||||
--variable: var(--base);
|
||||
--function: #c25205;
|
||||
--string: #2ba46d;
|
||||
--number: #c25205;
|
||||
--tags: #dd0000;
|
||||
--brackets: var(--comment);
|
||||
--qualifier: #ff6032;
|
||||
--important: var(--string);
|
||||
--attribute: #9c3eda;
|
||||
--property: #6182b8;
|
||||
|
||||
--selected-bg: #d7d4f0;
|
||||
--selected-bg-non-focus: #d9d9d9;
|
||||
--cursor: #000;
|
||||
|
||||
direction: ltr;
|
||||
font-family: var(--font-code);
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.dark .CodeMirror {
|
||||
color: var(--symbols);
|
||||
--symbols: #89ddff;
|
||||
--base: #a6accd;
|
||||
--comment: #6d6d6d;
|
||||
--keyword: #89ddff;
|
||||
--string: #c3e88d;
|
||||
--variable: #82aaff;
|
||||
--number: #f78c6c;
|
||||
--tags: #f07178;
|
||||
--brackets: var(--symbols);
|
||||
--property: #f07178;
|
||||
--attribute: #c792ea;
|
||||
--cursor: #fff;
|
||||
|
||||
--selected-bg: rgba(255, 255, 255, 0.1);
|
||||
--selected-bg-non-focus: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid var(--border);
|
||||
background-color: transparent;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: var(--comment);
|
||||
white-space: nowrap;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker-subtle {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* FOLD GUTTER */
|
||||
|
||||
.CodeMirror-foldmarker {
|
||||
color: #414141;
|
||||
text-shadow: #ff9966 1px 1px 2px, #ff9966 -1px -1px 2px, #ff9966 1px -1px 2px,
|
||||
#ff9966 -1px 1px 2px;
|
||||
font-family: arial;
|
||||
line-height: 0.3;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.CodeMirror-foldgutter {
|
||||
width: 0.7em;
|
||||
}
|
||||
|
||||
.CodeMirror-foldgutter-open,
|
||||
.CodeMirror-foldgutter-folded {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.CodeMirror-foldgutter-open:after,
|
||||
.CodeMirror-foldgutter-folded:after {
|
||||
content: '>';
|
||||
font-size: 0.8em;
|
||||
opacity: 0.8;
|
||||
transition: transform 0.2s;
|
||||
display: inline-block;
|
||||
top: -0.1em;
|
||||
position: relative;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.CodeMirror-foldgutter-folded:after {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid var(--cursor);
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
|
||||
@-moz-keyframes blink {
|
||||
0% {
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
}
|
||||
}
|
||||
|
||||
.cm-tab {
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -50px;
|
||||
bottom: -20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
.cm-s-default.CodeMirror {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-header {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-quote {
|
||||
color: #090;
|
||||
}
|
||||
|
||||
.cm-negative {
|
||||
color: #d44;
|
||||
}
|
||||
|
||||
.cm-positive {
|
||||
color: #292;
|
||||
}
|
||||
|
||||
.cm-header,
|
||||
.cm-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cm-em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cm-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-atom,
|
||||
.cm-s-default .cm-def,
|
||||
.cm-s-default .cm-variable-2,
|
||||
.cm-s-default .cm-variable-3,
|
||||
.cm-s-default .cm-punctuation {
|
||||
color: var(--base);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-property {
|
||||
color: var(--property);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-hr,
|
||||
.cm-s-default .cm-comment {
|
||||
color: var(--comment);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-attribute {
|
||||
color: var(--attribute);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-keyword {
|
||||
color: var(--keyword);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-variable {
|
||||
color: var(--variable);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-tag {
|
||||
color: var(--tags);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-bracket {
|
||||
color: var(--brackets);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-number {
|
||||
color: var(--number);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-string,
|
||||
.cm-s-default .cm-string-2 {
|
||||
color: var(--string);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-type {
|
||||
color: #085;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-meta {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-qualifier {
|
||||
color: var(--qualifier);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-builtin {
|
||||
color: #7539ff;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-link {
|
||||
color: var(--flash);
|
||||
}
|
||||
|
||||
.cm-s-default .cm-error {
|
||||
color: #ff008c;
|
||||
}
|
||||
|
||||
.cm-invalidchar {
|
||||
color: #ff008c;
|
||||
}
|
||||
|
||||
.CodeMirror-composing {
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {
|
||||
color: #0b0;
|
||||
}
|
||||
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {
|
||||
color: #a22;
|
||||
}
|
||||
|
||||
.CodeMirror-matchingtag {
|
||||
background: rgba(255, 150, 0, 0.3);
|
||||
}
|
||||
|
||||
.CodeMirror-activeline-background {
|
||||
background: #e8f2ff;
|
||||
}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px;
|
||||
margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar,
|
||||
.CodeMirror-hscrollbar,
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0;
|
||||
top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter-wrapper ::selection {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-rtl pre {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.CodeMirror-measure pre {
|
||||
position: static;
|
||||
}
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected {
|
||||
background: var(--selected-bg-non-focus);
|
||||
}
|
||||
|
||||
.CodeMirror-focused .CodeMirror-selected {
|
||||
background: var(--selected-bg);
|
||||
}
|
||||
|
||||
.CodeMirror-crosshair {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.CodeMirror-line::selection,
|
||||
.CodeMirror-line > span::selection,
|
||||
.CodeMirror-line > span > span::selection {
|
||||
background: var(--selected-bg);
|
||||
}
|
||||
|
||||
.CodeMirror-line::-moz-selection,
|
||||
.CodeMirror-line > span::-moz-selection,
|
||||
.CodeMirror-line > span > span::-moz-selection {
|
||||
background: var(--selected-bg);
|
||||
}
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border {
|
||||
padding-right: 0.1px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
ui/packages/tools/AiDownload.vue
Normal file
98
ui/packages/tools/AiDownload.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="ai-download">
|
||||
<a @click="onExport">
|
||||
<slot slot v-if="isHasSlot"></slot>
|
||||
</a>
|
||||
<template v-if="!isHasSlot">
|
||||
<el-button :disabled="disabled" icon="iconfont iconExported" @click="onExport">导出</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiDownload',
|
||||
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true
|
||||
},
|
||||
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 80000
|
||||
},
|
||||
|
||||
instance: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
params: {
|
||||
type: Object
|
||||
},
|
||||
|
||||
fileName: {
|
||||
type: String,
|
||||
default: '文件'
|
||||
},
|
||||
|
||||
suffixName: {
|
||||
type: String,
|
||||
default: 'xls'
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isHasSlot() {
|
||||
return this.$slots.default
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onExport() {
|
||||
if (this.disabled) {
|
||||
return this.$message.error('暂无数据')
|
||||
}
|
||||
this.instance.post(this.url, this.params, {
|
||||
responseType: 'blob',
|
||||
params: this.params,
|
||||
timeout: this.timeout
|
||||
}).then(res => {
|
||||
if (res?.type == "application/json") {
|
||||
let reader = new FileReader()
|
||||
reader.readAsText(res, "utf-8")
|
||||
reader.onload = e => {
|
||||
if (e.target.readyState === 2) {
|
||||
let ret = JSON.parse(e.target.result)
|
||||
if (ret?.code == 0) {
|
||||
this.$message.success(ret.msg)
|
||||
} else this.$message.error(ret.msg)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const link = document.createElement('a')
|
||||
let blob = new Blob([res], {type: res.type})
|
||||
link.style.display = 'none'
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.setAttribute('download', this.fileName + '.' + this.suffixName)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
183
ui/packages/tools/AiEchart.vue
Normal file
183
ui/packages/tools/AiEchart.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<section class="AiEchart">
|
||||
<div ref="AiEchart" class="chart" :style="{minWidth:grid.width,minHeight:grid.height}"/>
|
||||
<slot v-if="$slots.default"/>
|
||||
<render-component v-else-if="ops.render" :render="ops.render" :options="chartOptions" :data="data"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
name: "AiEchart",
|
||||
props: {
|
||||
data: {default: () => []},
|
||||
ops: {default: () => ({})},
|
||||
type: {default: "line"},
|
||||
series: Object,
|
||||
theme: {
|
||||
default: '0'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
renderComponent: {
|
||||
functional: true,
|
||||
props: {
|
||||
render: Function,
|
||||
options: {default: () => ({})},
|
||||
data: {default: () => []}
|
||||
},
|
||||
render(h, ins) {
|
||||
let {options = {}, data} = ins.props
|
||||
return ins.props.render(h, {...options, data})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
chart: null,
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
colors () {
|
||||
if (this.theme === '0') {
|
||||
return ['#2896FF', '#09DBFE', '#61FDB9', '#FFBB69', '#8429FF', '#ea7ccc']
|
||||
}
|
||||
|
||||
return ['#D4380D', '#CF1322', '#D55800', '#FA8C16', '#FFC53D', '#FFA940', '#FFC53D', '#780000']
|
||||
},
|
||||
|
||||
chartOptions() {
|
||||
let {type, data, ops: options = {}} = this,
|
||||
style = this.series ? this.series : this.ops.daemon ? this.ops.daemon : {},
|
||||
colors = this.theme === '1' ? this.colors : (options.color || this.colors),
|
||||
legend = {textStyle: {color: '#fff', padding: [0, 0, 0, 8], fontSize: 14}},
|
||||
series = data?.[0] ? Array(Object.keys(data?.[0]).length - 1).fill(1)
|
||||
.map((e, i) => ({type, ...(typeof style == 'object' ? style : style(colors[i]))})) : []
|
||||
let ops = {
|
||||
tooltip: {},
|
||||
xAxis: {
|
||||
type: 'category', nameGap: 20, axisTick: false,
|
||||
axisLabel: {
|
||||
color: '#fff',
|
||||
interval: 0,
|
||||
rotate: this.data.length > 10 ? 40 : 0
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: this.theme === '1' ? 'rgba(239, 163, 51, 0.8)' : 'rgba(255,255,255,.5)'
|
||||
}
|
||||
}
|
||||
},
|
||||
// 声明一个 Y 轴,数值轴。
|
||||
yAxis: {
|
||||
nameGap: 23, minInterval: 1,
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: this.theme === '1' ? 'rgba(255, 197, 52, 0.4)' : 'rgba(255, 255, 255, .2)',
|
||||
type: 'dashed'
|
||||
}
|
||||
},
|
||||
axisLabel: {color: '#fff'}
|
||||
},
|
||||
legend, series, ...options,
|
||||
color: colors,
|
||||
grid: {
|
||||
left: '0%',
|
||||
right: '0%',
|
||||
bottom: '0%',
|
||||
top: '40px',
|
||||
containLabel: true
|
||||
},
|
||||
}
|
||||
if (JSON.stringify(this.ops) != JSON.stringify(ops)) this.$emit("update:ops", ops)
|
||||
return ops
|
||||
},
|
||||
grid() {
|
||||
let {width, height} = this.chartOptions.grid || {}
|
||||
return {
|
||||
width: width ? width + 'px' : 'auto',
|
||||
height: height ? height + 'px' : 'auto'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
deep: true, handler(v, old) {
|
||||
let oldDims = Object.keys(old?.[0] || {})?.toString(),
|
||||
current = Object.keys(v?.[0] || {})?.toString()
|
||||
this.getChartData(oldDims != current)
|
||||
}
|
||||
},
|
||||
|
||||
theme () {
|
||||
this.refresh()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getChartData(render) {
|
||||
if (!render) {
|
||||
this.chart?.setOption({
|
||||
dataset: {
|
||||
source: this.data || []
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.chart?.setOption({
|
||||
...this.chartOptions,
|
||||
dataset: {
|
||||
source: this.data || []
|
||||
}
|
||||
}, true)
|
||||
}
|
||||
},
|
||||
initChart() {
|
||||
this.chart = echarts.init(this.$refs.AiEchart)
|
||||
this.chart.setOption(this.chartOptions || {})
|
||||
},
|
||||
watchResize() {
|
||||
this.timer && clearInterval(this.timer)
|
||||
this.timer = setInterval(() => {
|
||||
if (this.chart?.getHeight() != this.$refs.AiEchart?.clientHeight ||
|
||||
this.chart?.getWidth() != this.$refs.AiEchart?.clientWidth) {
|
||||
this.chart?.resize()
|
||||
}
|
||||
}, 1000)
|
||||
//5分钟后停止监听
|
||||
setTimeout(() => this.timer && clearInterval(this.timer), 5 * 60 * 1000)
|
||||
},
|
||||
refresh() {
|
||||
this.chart.setOption(this.chartOptions || {})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.watchResize()
|
||||
this.initChart()
|
||||
this.getChartData()
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.timer && clearInterval(this.timer)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiEchart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-width: 100px;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
|
||||
.chart {
|
||||
height: 100%;
|
||||
min-height: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
205
ui/packages/tools/AiFlow.vue
Normal file
205
ui/packages/tools/AiFlow.vue
Normal file
@@ -0,0 +1,205 @@
|
||||
<template>
|
||||
<section class="AiFlow">
|
||||
<div ref="lfIns" :style="{height}"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "AiFlow",
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: String,
|
||||
config: Object,
|
||||
height: {default: '400px'},
|
||||
readonly: Boolean,
|
||||
process: {default: null}
|
||||
},
|
||||
computed: {
|
||||
dndPanelConfigs: () => [
|
||||
{
|
||||
type: 'start',
|
||||
text: '开始',
|
||||
label: '开始',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAAnBJREFUOBGdVL1rU1EcPfdGBddmaZLiEhdx1MHZQXApraCzQ7GKLgoRBxMfcRELuihWKcXFRcEWF8HBf0DdDCKYRZpnl7p0svLe9Zzbd29eQhTbC8nv+9zf130AT63jvooOGS8Vf9Nt5zxba7sXQwODfkWpkbjTQfCGUd9gIp3uuPP8bZ946g56dYQvnBg+b1HB8VIQmMFrazKcKSvFW2dQTxJnJdQ77urmXWOMBCmXM2Rke4S7UAW+/8ywwFoewmBps2tu7mbTdp8VMOkIRAkKfrVawalJTtIliclFbaOBqa0M2xImHeVIfd/nKAfVq/LGnPss5Kh00VEdSzfwnBXPUpmykNss4lUI9C1ga+8PNrBD5YeqRY2Zz8PhjooIbfJXjowvQJBqkmEkVnktWhwu2SM7SMx7Cj0N9IC0oQXRo8xwAGzQms+xrB/nNSUWVveI48ayrFGyC2+E2C+aWrZHXvOuz+CiV6iycWe1Rd1Q6+QUG07nb5SbPrL4426d+9E1axKjY3AoRrlEeSQo2Eu0T6BWAAr6COhTcWjRaYfKG5csnvytvUr/WY4rrPMB53Uo7jZRjXaG6/CFfNMaXEu75nG47X+oepU7PKJvvzGDY1YLSKHJrK7vFUwXKkaxwhCW3u+sDFMVrIju54RYYbFKpALZAo7sB6wcKyyrd+aBMryMT2gPyD6GsQoRFkGHr14TthZni9ck0z+Pnmee460mHXbRAypKNy3nuMdrWgVKj8YVV8E7PSzp1BZ9SJnJAsXdryw/h5ctboUVi4AFiCd+lQaYMw5z3LGTBKjLQOeUF35k89f58Vv/tGh+l+PE/wG0rgfIUbZK5AAAAABJRU5ErkJggg==',
|
||||
},
|
||||
{
|
||||
type: 'task',
|
||||
label: '流程节点',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAEFVwZaAAAABGdBTUEAALGPC/xhBQAAAqlJREFUOBF9VM9rE0EUfrMJNUKLihGbpLGtaCOIR8VjQMGDePCgCCIiCNqzCAp2MyYUCXhUtF5E0D+g1t48qAd7CCLqQUQKEWkStcEfVGlLdp/fm3aW2QQdyLzf33zz5m2IsAZ9XhDpyaaIZkTS4ASzK41TFao88GuJ3hsr2pAbipHxuSYyKRugagICGANkfFnNh3HeE2N0b3nN2cgnpcictw5veJIzxmDamSlxxQZicq/mflxhbaH8BLRbuRwNtZp0JAhoplVRUdzmCe/vO27wFuuA3S5qXruGdboy5/PRGFsbFGKo/haRtQHIrM83bVeTrOgNhZReWaYGnE4aUQgTJNvijJFF4jQ8BxJE5xfKatZWmZcTQ+BVgh7s8SgPlCkcec4mGTmieTP4xd7PcpIEg1TX6gdeLW8rTVMVLVvb7ctXoH0Cydl2QOPJBG21STE5OsnbweVYzAnD3A7PVILuY0yiiyDwSm2g441r6rMSgp6iK42yqroI2QoXeJVeA+YeZSa47gZdXaZWQKTrG93rukk/l2Al6Kzh5AZEl7dDQy+JjgFahQjRopSxPbrbvK7GRe9ePWBo1wcU7sYrFZtavXALwGw/7Dnc50urrHJuTPSoO2IMV3gUQGNg87IbSOIY9BpiT9HV7FCZ94nPXb3MSnwHn/FFFE1vG6DTby+r31KAkUktB3Qf6ikUPWxW1BkXSPQeMHHiW0+HAd2GelJsZz1OJegCxqzl+CLVHa/IibuHeJ1HAKzhuDR+ymNaRFM+4jU6UWKXorRmbyqkq/D76FffevwdCp+jN3UAN/C9JRVTDuOxC/oh+EdMnqIOrlYteKSfadVRGLJFJPSB/ti/6K8f0CNymg/iH2gO/f0DwE0yjAFO6l8JaR5j0VPwPwfaYHqOqrCI319WzwhwzNW/aQAAAABJRU5ErkJggg==',
|
||||
},
|
||||
{
|
||||
type: 'gateway',
|
||||
label: '条件判断',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAVCAYAAAHeEJUAAAAABGdBTUEAALGPC/xhBQAAAvVJREFUOBGNVEFrE0EU/mY3bQoiFlOkaUJrQUQoWMGePLX24EH0IIoHKQiCV0G8iE1covgLiqA/QTzVm1JPogc9tIJYFaQtlhQxqYjSpunu+L7JvmUTU3AgmTfvffPNN++9WSA1DO182f6xwILzD5btfAoQmwL5KJEwiQyVbSVZ0IgRyV6PTpIJ81E5ZvqfHQR0HUOBHW4L5Et2kQ6Zf7iAOhTFAA8s0pEP7AXO1uAA52SbqGk6h/6J45LaLhO64ByfcUzM39V7ZiAdS2yCePPEIQYvTUHqM/n7dgQNfBKWPjpF4ISk8q3J4nB11qw6X8l+FsF3EhlkEMfrjIer3wJTLwS2aCNcj4DbGxXTw00JmAuO+Ni6bBxVUCvS5d9aa04+so4pHW5jLTywuXAL7jJ+D06sl82Sgl2JuVBQn498zkc2bGKxULHjCnSMadBKYDYYHAtsby1EQ5lNGrQd4Y3v4Zo0XdGEmDno46yCM9Tk+RiJmUYHS/aXHPNTcjxcbTFna000PFJHIVZ5lFRqRpJWk9/+QtlOUYJj9HG5pVFEU7zqIYDVsw2s+AJaD8wTd2umgSCCyUxgGsS1Y6TBwXQQTFuZaHcd8gAGioE90hlsY+wMcs30RduYtxanjMGal8H5dMW67dmT1JFtYUEe8LiQLRsPZ6IIc7A4J5tqco3T0pnv/4u0kyzrYUq7gASuEyI8VXKvB9Odytv6jS/PNaZBln0nioJG/AVQRZvApOdhjj3Jt8QC8Im09SafwdBdvIpztpxWxpeKCC+EsFdS8DCyuCn2munFpL7ctHKp+Xc5cMybeIyMAN33SPL3ZR9QV1XVwLyzHm6Iv0/yeUuUb7PPlZC4D4HZkeu6dpF4v9j9MreGtMbxMMRLIcjJic9yHi7WQ3yVKzZVWUr5UrViJvn1FfUlwe/KYVfYyWRLSGNu16hR01U9IacajXPei0wx/5BqgInvJN+MMNtNme7ReU9SBbgntovn0kKHpFg7UogZvaZiOue/q1SBo9ktHzQAAAAASUVORK5CYII=',
|
||||
},
|
||||
{
|
||||
type: 'end',
|
||||
text: '结束',
|
||||
label: '结束',
|
||||
icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAAH6ji2bAAAABGdBTUEAALGPC/xhBQAAA1BJREFUOBFtVE1IVUEYPXOf+tq40Y3vPcmFIdSjIorWoRG0ERWUgnb5FwVhYQSl72oUoZAboxKNFtWiwKRN0M+jpfSzqJAQclHo001tKkjl3emc8V69igP3znzfnO/M9zcDcKT67azmjYWTwl9Vn7Vumeqzj1DVb6cleQY4oAVnIOPb+mKAGxQmKI5CWNJ2aLPatxWa3aB9K7/fB+/Z0jUF6TmMlFLQqrkECWQzOZxYGjTlOl8eeKaIY5yHnFn486xBustDjWT6dG7pmjHOJd+33t0iitTPkK6tEvjxq4h2MozQ6WFSX/LkDUGfFwfhEZj1Auz/U4pyAi5Sznd7uKzznXeVHlI/Aywmk6j7fsUsEuCGADrWARXXwjxWQsUbIupDHJI7kF5dRktg0eN81IbiZXiTESic50iwS+t1oJgL83jAiBupLDCQqwziaWSoAFSeIR3P5Xv5az00wyIn35QRYTwdSYbz8pH8fxUUAtxnFvYmEmgI0wYXUXcCCSpeEVpXlsRhBnCEATxWylL9+EKCAYhe1NGstUa6356kS9NVvt3DU2fd+Wtbm/+lSbylJqsqkSm9CRhvoJVlvKPvF1RKY/FcPn5j4UfIMLn8D4UYb54BNsilTDXKnF4CfTobA0FpoW/LSp306wkXM+XaOJhZaFkcNM82ASNAWMrhrUbRfmyeI1FvRBTpN06WKxa9BK0o2E4Pd3zfBBEwPsv9sQBnmLVbLEIZ/Xe9LYwJu/Er17W6HYVBc7vmuk0xUQ+pqxdom5Fnp55SiytXLPYoMXNM4u4SNSCFWnrVIzKG3EGyMXo6n/BQOe+bX3FClY4PwydVhthOZ9NnS+ntiLh0fxtlUJHAuGaFoVmttpVMeum0p3WEXbcll94l1wM/gZ0Ccczop77VvN2I7TlsZCsuXf1WHvWEhjO8DPtyOVg2/mvK9QqboEth+7pD6NUQC1HN/TwvydGBARi9MZSzLE4b8Ru3XhX2PBxf8E1er2A6516o0w4sIA+lwURhAON82Kwe2iDAC1Watq4XHaGQ7skLcFOtI5lDxuM2gZe6WFIotPAhbaeYlU4to5cuarF1QrcZ/lwrLaCJl66JBocYZnrNlvm2+MBCTmUymPrYZVbjdlr/BxlMjmNmNI3SAAAAAElFTkSuQmCC',
|
||||
}
|
||||
],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
flow: null,
|
||||
configWatch: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadLib() {
|
||||
this.$injectCss("https://cdn.cunwuyun.cn/logicflow/index.css")
|
||||
const load = url => new Promise(resolve => this.$injectLib(url, () => resolve()))
|
||||
let libs = ["https://cdn.cunwuyun.cn/logicflow/logic-flow.js", "https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/BpmnElement.js"]
|
||||
if (!this.readonly) {
|
||||
this.$injectCss("https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/style/index.css")
|
||||
libs = [
|
||||
libs,
|
||||
"https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/Menu.js",
|
||||
"https://cdn.jsdelivr.net/npm/@logicflow/extension/lib/DndPanel.js"
|
||||
].flat()
|
||||
}
|
||||
return Promise.all(libs.map(e => load(e)))
|
||||
},
|
||||
initModels(lf) {
|
||||
const {StartEventModel, StartEventView, UserTaskView, UserTaskModel, EndEventModel, EndEventView, ExclusiveGatewayModel, ExclusiveGatewayView} = window
|
||||
|
||||
class CustomTaskModel extends UserTaskModel {
|
||||
getNodeStyle() {
|
||||
const style = super.getNodeStyle();
|
||||
const props = this.properties
|
||||
if (props.status == 'pass') {
|
||||
style.stroke = '#2ea222'
|
||||
} else if (props.status == 'current') {
|
||||
style.stroke = '#46f'
|
||||
} else if (props.status == 'reject') {
|
||||
style.stroke = '#f46'
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
getTextStyle() {
|
||||
const style = super.getTextStyle();
|
||||
const props = this.properties
|
||||
if (props.status == 'current') {
|
||||
style.fontSize = 14;
|
||||
style.fontWeight = 'bold'
|
||||
} else if (props.status == 'pass') {
|
||||
style.color = '#2ea222'
|
||||
} else if (props.status == 'reject') {
|
||||
style.color = '#f46'
|
||||
style.fontWeight = 'bold'
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
class StartModel extends StartEventModel {
|
||||
getNodeStyle() {
|
||||
const style = super.getNodeStyle();
|
||||
const props = this.properties
|
||||
if (!!props.isStart) {
|
||||
style.stroke = '#2ea222'
|
||||
style.fill = 'rgb(46,162,34,.6)'
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
class EndModel extends EndEventModel {
|
||||
getNodeStyle() {
|
||||
const style = super.getNodeStyle();
|
||||
const props = this.properties
|
||||
if (!!props.isFinished) {
|
||||
style.stroke = '#2ea222'
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
class GatewayModel extends ExclusiveGatewayModel {
|
||||
getNodeStyle() {
|
||||
const style = super.getNodeStyle();
|
||||
const props = this.properties
|
||||
if (props.status == 'pass') {
|
||||
style.stroke = '#2ea222'
|
||||
} else if (props.status == 'current') {
|
||||
style.stroke = '#46f'
|
||||
} else if (props.status == 'reject') {
|
||||
style.stroke = '#f46'
|
||||
}
|
||||
return style;
|
||||
}
|
||||
}
|
||||
|
||||
lf?.batchRegister([
|
||||
{type: 'task', view: UserTaskView, model: CustomTaskModel},
|
||||
{type: 'start', view: StartEventView, model: StartModel},
|
||||
{type: 'end', view: EndEventView, model: EndModel},
|
||||
{type: 'gateway', view: ExclusiveGatewayView, model: GatewayModel},
|
||||
])
|
||||
},
|
||||
initFlow() {
|
||||
const {LogicFlow, Menu, DndPanel, BpmnElement} = window
|
||||
let plugins = [BpmnElement, this.readonly ? [] : [Menu, DndPanel]].flat()
|
||||
this.$load(!!LogicFlow && this.$refs.lfIns && plugins.reduce((r, e) => r && !!e, true), 200, "logicFlow").then(() => {
|
||||
this.flow = new LogicFlow({
|
||||
container: this.$refs.lfIns, plugins,
|
||||
animation: true,
|
||||
isSilentMode: this.readonly,
|
||||
stopScrollGraph: this.readonly,
|
||||
stopZoomGraph: this.readonly,
|
||||
stopMoveGraph: this.readonly,
|
||||
hoverOutline: !this.readonly,
|
||||
edgeSelectedOutline: !this.readonly,
|
||||
style: {
|
||||
outline: {
|
||||
fill: 'transparent',
|
||||
stroke: '#26f',
|
||||
},
|
||||
},
|
||||
})
|
||||
this.$emit("update:process", this.flow);
|
||||
this.initModels(this.flow)
|
||||
this.flow.extension.dndPanel?.setPatternItems(this.dndPanelConfigs)
|
||||
this.flow.render()
|
||||
this.initValue()
|
||||
this.flow.on('history:change', evt => {
|
||||
this.configWatch?.()
|
||||
const conf = this.$copy(evt.data?.undos || null).pop()
|
||||
this.$emit("update:config", conf)
|
||||
})
|
||||
})
|
||||
},
|
||||
initValue() {
|
||||
this.configWatch = this.$watch('config', v => {
|
||||
if (v?.nodes?.length > 0) {
|
||||
this.flow?.render(v || {})
|
||||
this.initCurrent()
|
||||
this.configWatch?.()
|
||||
}
|
||||
}, {immediate: true, deep: true})
|
||||
},
|
||||
initCurrent() {
|
||||
if (this.value) {
|
||||
const curs = this.value?.split(",")
|
||||
curs.map(id => this.flow?.setProperties(id, {status: 'current'}))
|
||||
this.flow?.focusOn({id: curs?.[0]})
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => this.loadLib().then(() => this.initFlow()))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiFlow {
|
||||
}
|
||||
</style>
|
||||
257
ui/packages/tools/AiMap.vue
Normal file
257
ui/packages/tools/AiMap.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<section class="AiMap" :class="{mask}">
|
||||
<div ref="amap" class="map"/>
|
||||
<div v-if="mask" class="mask"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AMapLoader from "@amap/amap-jsapi-loader";
|
||||
|
||||
export default {
|
||||
name: "AiMap",
|
||||
props: {
|
||||
plugins: {default: () => ['AMap.DistrictSearch', 'AMap.LineSearch']},
|
||||
map: Object,
|
||||
lib: Object,
|
||||
mapStyle: String,
|
||||
areaId: String,
|
||||
is3d: Boolean,
|
||||
ops: {default: () => ({})},
|
||||
markers: {default: () => []},
|
||||
mask: Boolean,
|
||||
searchBus: {default: "2"},
|
||||
pulseLines: Boolean,
|
||||
onlyShowArea: Boolean
|
||||
},
|
||||
computed: {
|
||||
viewMode() {
|
||||
return this.is3d ? '3D' : '2D'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
amap: null,
|
||||
mapLib: null,
|
||||
loca: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
markers: {
|
||||
deep: true, handler() {
|
||||
this.addMarkers()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initMap() {
|
||||
let {plugins, viewMode, mapStyle} = this
|
||||
AMapLoader.load({
|
||||
key: '54a02a43d9828a8f9cd4f26fe281e74e',
|
||||
version: '2.0',
|
||||
plugins,
|
||||
Loca: {version: '2.0.0'}
|
||||
}).then(AMap => {
|
||||
this.mapLib = AMap
|
||||
this.amap = new AMap.Map(this.$refs.amap, {
|
||||
mapStyle,
|
||||
viewMode,
|
||||
terrain: true,
|
||||
resizeEnable: true,
|
||||
skyColor: "#082243",
|
||||
zoom: 11,
|
||||
...this.ops
|
||||
})
|
||||
this.amap.on('complete', () => {
|
||||
this.amap.setFitView();//视口自适应
|
||||
})
|
||||
this.$emit('update:lib', AMap)
|
||||
this.$emit('update:map', this.amap)
|
||||
this.$emit("loaded")
|
||||
this.mapLoaded()
|
||||
})
|
||||
},
|
||||
getMapArea() {
|
||||
const {mapLib: AMap} = this
|
||||
if (!!AMap) {
|
||||
new AMap.DistrictSearch({
|
||||
subdistrict: 0, //获取边界不需要返回下级行政区
|
||||
extensions: 'all', //返回行政区边界坐标组等具体信息
|
||||
level: 'district' //查询行政级别为 市
|
||||
}).search(this.areaId.substring(0, 6), (status, result) => {
|
||||
const area = result?.districtList?.[0],
|
||||
bounds = area?.boundaries || [];
|
||||
let polygons = []
|
||||
if (this.onlyShowArea) {
|
||||
const mask = bounds.map(e => [e])
|
||||
polygons = bounds.map(path => new AMap.Polygon({
|
||||
path: path.map(e => [e.lng, e.lat]),
|
||||
strokeWeight: 1,
|
||||
fillOpacity: 0,
|
||||
strokeStyle: 'dashed',
|
||||
strokeColor: '#0091ea'
|
||||
}))
|
||||
this.amap.setMask(mask)
|
||||
this.amap.setPitch(65)
|
||||
} else {
|
||||
polygons = bounds.map(path => new AMap.Polygon({
|
||||
strokeWeight: 1,
|
||||
path: path.map(e => [e.lng, e.lat]),
|
||||
strokeStyle: 'dashed',
|
||||
fillOpacity: 0.1,
|
||||
fillColor: '#80d8ff',
|
||||
strokeColor: '#0091ea'
|
||||
}))
|
||||
}
|
||||
this.amap.add(polygons)
|
||||
this.amap.setCenter(area.center, true)
|
||||
})
|
||||
}
|
||||
},
|
||||
mapLoaded() {
|
||||
this.areaId && this.getMapArea()
|
||||
this.addPulseLines(this.areaId?.substring(0, 6))
|
||||
this.addMarkers()
|
||||
},
|
||||
addMarkers() {
|
||||
if (this.markers.length > 0 && this.mapLib && this.amap) {
|
||||
let markers = this.markers.map(e => {
|
||||
let {label, icon = "https://cdn.cunwuyun.cn/dvcp/h5/Location2.png"} = e
|
||||
return new this.mapLib.Marker({
|
||||
content: e.content || `<div class="marker">
|
||||
<img src="${icon}"/>
|
||||
<span>${label}</span>
|
||||
</div>`,
|
||||
position: [e.lng, e.lat]
|
||||
})
|
||||
})
|
||||
this.amap.add(markers)
|
||||
}
|
||||
},
|
||||
addPulseLines(city) {
|
||||
let {amap: map, mapLib: lib, pulseLines} = this
|
||||
if (pulseLines && lib && map) {
|
||||
this.loca = new Loca.Container({map: this.amap})
|
||||
let ls = new lib.LineSearch({pageSize: 1, pageNum: 1, city}), lines = {
|
||||
type: "FeatureCollection",
|
||||
features: []
|
||||
}
|
||||
Promise.all(Array.from("0123456789").map(i => new Promise((resolve) => {
|
||||
ls.search(i, (e, res) => {
|
||||
if (e == "complete" && res.info == "OK") {
|
||||
res.lineInfo?.map(line => {
|
||||
lines.features.push({
|
||||
type: "Feature",
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: "LineString",
|
||||
coordinates: line.path?.map(p => [p.lng, p.lat]) || []
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}))).then(() => {
|
||||
let layer = new Loca.PulseLineLayer({
|
||||
zIndex: 10,
|
||||
opacity: 1,
|
||||
visible: true,
|
||||
zooms: [2, 22],
|
||||
})
|
||||
let geo = new Loca.GeoJSONSource({data: lines})
|
||||
layer.setSource(geo)
|
||||
layer.setStyle({
|
||||
altitude: 0,
|
||||
lineWidth: 2,
|
||||
// 脉冲头颜色
|
||||
headColor: "#B5FBFF",
|
||||
// 脉冲尾颜色
|
||||
trailColor: 'rgba(0,0,0,0)',
|
||||
// 脉冲长度,0.25 表示一段脉冲占整条路的 1/4
|
||||
interval: 0.25,
|
||||
// 脉冲线的速度,几秒钟跑完整段路
|
||||
duration: 15000,
|
||||
})
|
||||
this.loca.add(layer)
|
||||
this.loca.animate.start()
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initMap()
|
||||
},
|
||||
destroyed() {
|
||||
this.amap?.destroy()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiMap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
position: relative;
|
||||
|
||||
&.mask {
|
||||
box-shadow: 0 0 40px 20px rgba(#000, .8);
|
||||
}
|
||||
|
||||
.map {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mask {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 8;
|
||||
background: radial-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .6) 40%, #000 100%);
|
||||
}
|
||||
|
||||
::v-deep .marker {
|
||||
position: relative;
|
||||
|
||||
& > img {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
& > span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover > span {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translate(-50%, 100%);
|
||||
display: block;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .amap-logo, ::v-deep .amap-copyright {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::v-deep .amap-icon {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
178
ui/packages/tools/AiProcess.vue
Normal file
178
ui/packages/tools/AiProcess.vue
Normal file
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<section class="AiProcess">
|
||||
<ai-flow ref="AiFlow" v-model="detail.nowNodeId" :config="detail.workflowConfig" readonly :process.sync="process" height="200px"/>
|
||||
<ai-wrapper border :title="currentNodeName" v-if="!!detail.id &&!isFinished">
|
||||
<template v-if="currentNode.type=='task'">
|
||||
<ai-info-item label="当前审批" :value="currentNode.properties.userName"/>
|
||||
<ai-info-item label="操作时间" :value="currentNode.properties.updateTime"/>
|
||||
<ai-info-item label="审批意见" isLine>
|
||||
<el-input v-if="canAudit" type="textarea" rows="3" v-model="currentNode.properties.remark"/>
|
||||
<span v-else v-text="currentNode.properties.remark"/>
|
||||
</ai-info-item>
|
||||
</template>
|
||||
<template v-else-if="currentNode.type=='start'">
|
||||
<ai-info-item label="开始时间" :value="currentNode.properties.updateTime" isLine/>
|
||||
</template>
|
||||
<template v-else-if="currentNode.type=='end'">
|
||||
<ai-info-item label="结束时间" :value="currentNode.properties.updateTime" isLine/>
|
||||
</template>
|
||||
</ai-wrapper>
|
||||
<el-row class="processOptions mar-t16" type="flex" justify="center" v-if="canAudit">
|
||||
<el-button @click="handleReject" type="danger">拒绝</el-button>
|
||||
<el-button @click="handleReturn" v-if="!incomingIsStart">驳回</el-button>
|
||||
<el-button type="primary" @click="handlePass">同意</el-button>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiFlow from "./AiFlow";
|
||||
import request from "../../meta/js/request";
|
||||
import AiWrapper from "../basic/AiWrapper";
|
||||
import AiInfoItem from "../basic/AiInfoItem";
|
||||
import {mapState, mapActions} from "vuex"
|
||||
|
||||
export default {
|
||||
name: "AiProcess",
|
||||
props: {
|
||||
bid: String
|
||||
},
|
||||
components: {AiInfoItem, AiWrapper, AiFlow},
|
||||
data() {
|
||||
return {
|
||||
instance: request,
|
||||
detail: {},
|
||||
currentNode: {properties: {}},
|
||||
process: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
currentNodeName: v => v.currentNode?.text?.value || "当前节点",
|
||||
canAudit: v => v.user.info.id == v.currentNode?.properties.userId && v.currentNode?.id == v.detail.nowNodeId && !v.isFinished,
|
||||
incomingIsStart: v => v.process?.getNodeIncomingNode(v.detail.nowNodeId)?.[0]?.type == "start",
|
||||
isFinished: v => v.detail.nowNodeId == "finished"
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['endFlow']),
|
||||
getProcess() {
|
||||
const {bid: id} = this.$props
|
||||
id ? this.instance.post("/app/appworkflowlog/queryDetailById", null, {
|
||||
params: {id}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
const {workflowConfig = null, nowNodeId} = res.data
|
||||
this.detail = {...res.data, workflowConfig: JSON.parse(workflowConfig)}
|
||||
this.currentNode = this.detail.workflowConfig.nodes.find(e => e.id == nowNodeId) || this.currentNode
|
||||
}
|
||||
}) : console.error("无法获取bid")
|
||||
},
|
||||
initEvents() {
|
||||
this.process.on("node:click", res => {
|
||||
if (res?.data) {
|
||||
this.currentNode = res.data
|
||||
}
|
||||
})
|
||||
this.process.on("blank:click", () => {
|
||||
this.currentNode = this.process.getNodeModelById(this.detail.nowNodeId)
|
||||
})
|
||||
},
|
||||
handleReject() {
|
||||
this.$confirm("是否要拒绝该申请?").then(() => {
|
||||
this.currentNode.properties = {
|
||||
remark: "拒绝申请", ...this.currentNode.properties,
|
||||
status: "reject",
|
||||
updateTime: this.$moment().format("YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
this.process.setProperties(this.detail.nowNodeId, {
|
||||
...this.currentNode.properties,
|
||||
})
|
||||
const form = {...this.detail, workflowConfig: this.process.getGraphData()}
|
||||
this.saveProcess(form)
|
||||
}).catch(() => 0)
|
||||
},
|
||||
handleReturn() {
|
||||
this.$confirm("是否要驳回该申请返回到前次审批?").then(() => {
|
||||
this.currentNode.properties = {
|
||||
remark: "驳回申请", ...this.currentNode.properties,
|
||||
status: "reject",
|
||||
updateTime: this.$moment().format("YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
let {nowNodeId} = this.detail
|
||||
this.process.setProperties(nowNodeId, {
|
||||
...this.currentNode.properties,
|
||||
})
|
||||
const next = this.process.getNodeIncomingNode(nowNodeId)
|
||||
const form = {...this.detail, workflowConfig: this.process.getGraphData()}
|
||||
nowNodeId = []
|
||||
next?.map(e => {
|
||||
nowNodeId.push(e.id)
|
||||
this.process.setProperties(e.id, {status: 'current'})
|
||||
})
|
||||
this.saveProcess({...form, nowNodeId: nowNodeId.toString()})
|
||||
}).catch(() => 0)
|
||||
},
|
||||
handlePass() {
|
||||
this.$confirm("是否要同意该申请?").then(() => {
|
||||
this.currentNode.properties = {
|
||||
remark: "同意申请", ...this.currentNode.properties,
|
||||
status: "pass",
|
||||
updateTime: this.$moment().format("YYYY-MM-DD HH:mm:ss")
|
||||
}
|
||||
let {nowNodeId} = this.detail
|
||||
this.process.setProperties(nowNodeId, {
|
||||
...this.currentNode.properties,
|
||||
})
|
||||
const next = this.process.getNodeOutgoingNode(nowNodeId)
|
||||
const form = {...this.detail, workflowConfig: this.process.getGraphData()}
|
||||
if (next?.[0].type == "end") this.endFlow(form).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.$message.success("提交成功!")
|
||||
this.getProcess()
|
||||
}
|
||||
})
|
||||
else {
|
||||
nowNodeId = []
|
||||
next?.map(e => {
|
||||
nowNodeId.push(e.id)
|
||||
this.process.setProperties(e.id, {status: 'current'})
|
||||
})
|
||||
this.saveProcess({...form, nowNodeId: nowNodeId.toString()})
|
||||
}
|
||||
}).catch(() => 0)
|
||||
},
|
||||
saveProcess(form) {
|
||||
const {workflowConfig} = form
|
||||
this.instance.post("/app/appworkflowlog/addOrUpdate", {...form, workflowConfig: JSON.stringify(workflowConfig)}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
this.$message.success("提交成功!")
|
||||
this.$emit("submit")
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
process: {
|
||||
immediate: true,
|
||||
handler(v) {
|
||||
if (!!v) {
|
||||
this.initEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getProcess()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiProcess {
|
||||
.processOptions {
|
||||
.el-button {
|
||||
min-width: 72px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
313
ui/packages/tools/AiQMap.vue
Normal file
313
ui/packages/tools/AiQMap.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<section class="AiTMap">
|
||||
<div ref="tmap" class="map"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiQMap',
|
||||
props: {
|
||||
areaId: String,
|
||||
ops: {
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
libraries: {
|
||||
default: () => ['service']
|
||||
},
|
||||
markerIcon: {
|
||||
type: String
|
||||
},
|
||||
is3dAround: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
is3d: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
limitArea: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
map: Object,
|
||||
lib: Object,
|
||||
markers: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
markers: {
|
||||
handler (v) {
|
||||
if (window.TMap && v.length) {
|
||||
this.addMarkers(v)
|
||||
}
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
tmap: null,
|
||||
mapLib: null,
|
||||
marker: null,
|
||||
keyframe: [
|
||||
{
|
||||
percentage: 0,
|
||||
rotation: 0
|
||||
},
|
||||
{
|
||||
percentage: 0.2,
|
||||
rotation: 72
|
||||
},
|
||||
{
|
||||
percentage: 0.4,
|
||||
rotation: 144
|
||||
},
|
||||
{
|
||||
percentage: 0.6,
|
||||
rotation: 216
|
||||
},
|
||||
{
|
||||
percentage: 0.8,
|
||||
rotation: 288
|
||||
},
|
||||
{
|
||||
percentage: 1,
|
||||
rotation: 360
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
key () {
|
||||
return process.env.NODE_ENV == 'production' ? 'RWWBZ-64BEJ-MVLFJ-FTHLQ-JTR6J-SAB2S' : '3RZBZ-LZUCF-CT6J5-NWKZH-FCWOQ-UUFKY'
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
if (!window.TMap) {
|
||||
window.initTMap = this.initTMap
|
||||
this.injectLib(`https://map.qq.com/api/gljs?v=1.exp&key=${this.key}&libraries=${this.libraries.toString()}&callback=initTMap`)
|
||||
} else {
|
||||
this.initTMap()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
injectLib (url, cb) {
|
||||
const script = document.createElement('script')
|
||||
script.type = 'text/javascript'
|
||||
script.id = 'aiqmap'
|
||||
script.src = url
|
||||
script.addEventListener('load', () => {
|
||||
cb && cb()
|
||||
})
|
||||
document.body.appendChild(script)
|
||||
},
|
||||
|
||||
setMarkersCenter () {
|
||||
var bounds = new TMap.LatLngBounds()
|
||||
this.markers.forEach(item => {
|
||||
if (bounds.isEmpty() || !bounds.contains(new TMap.LatLng(item.lat, item.lng))) {
|
||||
bounds.extend(new TMap.LatLng(item.lat, item.lng))
|
||||
}
|
||||
})
|
||||
|
||||
this.tmap.fitBounds(bounds, {
|
||||
padding: 100
|
||||
})
|
||||
},
|
||||
|
||||
addMarkers (v) {
|
||||
if (this.marker) {
|
||||
this.marker.setMap(null)
|
||||
}
|
||||
|
||||
this.marker = new TMap.MultiMarker({
|
||||
id: 'markers',
|
||||
map: this.tmap,
|
||||
styles: {
|
||||
marker: new TMap.MarkerStyle({
|
||||
width: 25,
|
||||
height: 35,
|
||||
color: '#333',
|
||||
size: 16,
|
||||
offset: { x: 0, y: 8 },
|
||||
anchor: { x: 17, y: 23 },
|
||||
direction: 'bottom',
|
||||
strokeWidth: 2,
|
||||
src: this.markerIcon
|
||||
})
|
||||
},
|
||||
geometries: v.map(v => {
|
||||
return {
|
||||
...v,
|
||||
styleId: 'marker',
|
||||
position: new TMap.LatLng(v.lat, v.lng)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.setMarkersCenter()
|
||||
this.marker.on('click', e => {
|
||||
this.$emit('markerClick', e.geometry)
|
||||
})
|
||||
},
|
||||
|
||||
initTMap () {
|
||||
this.mapLib = TMap
|
||||
this.tmap = new TMap.Map(this.$refs.tmap, {
|
||||
zoom: 11,
|
||||
...this.ops,
|
||||
viewMode: this.is3d ? '3d' : '2d',
|
||||
pitch: this.is3d ? 40 : 0,
|
||||
// mapStyleId: 'style1',
|
||||
baseMap: this.limitArea ? [
|
||||
{ type: 'vector', features: ['base'] },
|
||||
{
|
||||
type: 'traffic',
|
||||
opacity: 1,
|
||||
}
|
||||
] : [
|
||||
{ type: 'vector' },
|
||||
],
|
||||
renderOptions: {
|
||||
enableBloom: true, // 是否启用泛光效果 注:为true才会有效果
|
||||
}
|
||||
})
|
||||
|
||||
this.tmap.on('click', e => {
|
||||
this.$emit('click', e)
|
||||
})
|
||||
|
||||
if (this.markers.length) {
|
||||
this.addMarkers(this.markers)
|
||||
}
|
||||
this.$emit('loaded')
|
||||
this.$emit('update:lib', TMap)
|
||||
this.$emit('update:map', this.tmap)
|
||||
this.areaId && this.getMapArea()
|
||||
},
|
||||
|
||||
getMapArea () {
|
||||
let {mapLib, areaId, tmap: map} = this, keyword = areaId.substring(0, 6)
|
||||
const fitBounds = latLngList => {
|
||||
if (latLngList.length === 0) {
|
||||
return null
|
||||
}
|
||||
let boundsN = latLngList[0].getLat()
|
||||
let boundsS = boundsN
|
||||
let boundsW = latLngList[0].getLng()
|
||||
let boundsE = boundsW
|
||||
latLngList.forEach((point) => {
|
||||
point.getLat() > boundsN && (boundsN = point.getLat())
|
||||
point.getLat() < boundsS && (boundsS = point.getLat())
|
||||
point.getLng() > boundsE && (boundsE = point.getLng())
|
||||
point.getLng() < boundsW && (boundsW = point.getLng())
|
||||
})
|
||||
return new TMap.LatLngBounds(
|
||||
new TMap.LatLng(boundsS, boundsW),
|
||||
new TMap.LatLng(boundsN, boundsE)
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.limitArea) return
|
||||
let polygons = new TMap.MultiPolygon({map, geometries: []})
|
||||
new mapLib.service.District({
|
||||
polygon: 2,
|
||||
maxOffset: 100
|
||||
}).search({keyword}).then(res => {
|
||||
if (res?.result) {
|
||||
console.log(res.result[0][0].polygon)
|
||||
let center = res.result[0][0].location
|
||||
this.tmap.enableAreaHighlight({
|
||||
paths: res.result[0][0].polygon,
|
||||
highlightColor: 'rgba(0,0,0,0)',
|
||||
shadeColor: 'rgba(0,0,0,1)'
|
||||
})
|
||||
this.tmap.setCenter(center)
|
||||
new TMap.MultiPolyline({
|
||||
map: this.tmap,
|
||||
styles: {
|
||||
polyline: new TMap.PolylineStyle({
|
||||
color: '#017cf7', // 线条填充色,
|
||||
width: 8,
|
||||
lineCap: 'round',
|
||||
enableBloom: true, // 是否启用泛光 注:为true才会有效果
|
||||
}),
|
||||
},
|
||||
geometries: [
|
||||
{
|
||||
styleId: 'polyline', // 样式id
|
||||
paths: res.result[0][0].polygon
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
res.result.forEach((level) => {
|
||||
level.forEach((place) => {
|
||||
let bounds = []
|
||||
let newGeometries = place.polygon.map((polygon, index) => {
|
||||
bounds.push(fitBounds(polygon))
|
||||
return {
|
||||
id: `${place.id}_${index}`,
|
||||
paths: polygon
|
||||
}
|
||||
})
|
||||
bounds = bounds.reduce((a, b) => {
|
||||
return fitBounds([
|
||||
a.getNorthEast(),
|
||||
a.getSouthWest(),
|
||||
b.getNorthEast(),
|
||||
b.getSouthWest()
|
||||
])
|
||||
})
|
||||
// polygons.updateGeometries(newGeometries)
|
||||
this.tmap.fitBounds(bounds)
|
||||
})
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.is3dAround && this.tmap.startAnimation(this.keyframe, {
|
||||
duration: 16000,
|
||||
loop: Infinity
|
||||
})
|
||||
}, 600)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
destroyed () {
|
||||
this.tmap.destroy()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTMap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
::v-deep.map {
|
||||
height: 100%;
|
||||
|
||||
& > div > div {
|
||||
&:nth-of-type(2), &:nth-of-type(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
156
ui/packages/tools/AiTMap.vue
Normal file
156
ui/packages/tools/AiTMap.vue
Normal file
@@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<section class="AiTMap">
|
||||
<div ref="tmap" class="map"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "AiTMap",
|
||||
props: {
|
||||
/**
|
||||
* 地区id
|
||||
*/
|
||||
areaId: String,
|
||||
/**
|
||||
* 地图参数,详情见https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap
|
||||
*/
|
||||
ops: {default: () => ({})},
|
||||
/**
|
||||
* 加载三方库,以腾讯地图文档要求进行添加
|
||||
*/
|
||||
libraries: {default: () => ["service"]},
|
||||
/**
|
||||
* 地图实例,支持用.sync绑定获取
|
||||
*/
|
||||
map: Object,
|
||||
/**
|
||||
* 地图库,支持用.sync绑定获取
|
||||
*/
|
||||
lib: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tmap: null,
|
||||
mapLib: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
key() {
|
||||
return process.env.NODE_ENV == "production" ?
|
||||
"RWWBZ-64BEJ-MVLFJ-FTHLQ-JTR6J-SAB2S" :
|
||||
"3RZBZ-LZUCF-CT6J5-NWKZH-FCWOQ-UUFKY"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
injectLib(url, cb) {
|
||||
const script = document.createElement('script')
|
||||
script.type = 'text/javascript';
|
||||
script.id = "aitmap";
|
||||
script.src = url;
|
||||
script.addEventListener('load', () => {
|
||||
cb && cb()
|
||||
})
|
||||
document.body.appendChild(script);
|
||||
},
|
||||
initTMap() {
|
||||
this.mapLib = TMap
|
||||
this.tmap = new TMap.Map(this.$refs.tmap, {
|
||||
zoom: 11,
|
||||
...this.ops
|
||||
})
|
||||
this.$emit('loaded')
|
||||
this.$emit('update:lib', TMap)
|
||||
this.$emit('update:map', this.tmap)
|
||||
this.areaId && this.getMapArea()
|
||||
},
|
||||
getMapArea() {
|
||||
let {mapLib, areaId, tmap: map} = this, keyword = areaId.substring(0, 6)
|
||||
const fitBounds = (latLngList) => {
|
||||
// 由多边形顶点坐标数组计算能完整呈现该多边形的最小矩形范围
|
||||
if (latLngList.length === 0) {
|
||||
return null;
|
||||
}
|
||||
let boundsN = latLngList[0].getLat();
|
||||
let boundsS = boundsN;
|
||||
let boundsW = latLngList[0].getLng();
|
||||
let boundsE = boundsW;
|
||||
latLngList.forEach((point) => {
|
||||
point.getLat() > boundsN && (boundsN = point.getLat());
|
||||
point.getLat() < boundsS && (boundsS = point.getLat());
|
||||
point.getLng() > boundsE && (boundsE = point.getLng());
|
||||
point.getLng() < boundsW && (boundsW = point.getLng());
|
||||
});
|
||||
return new TMap.LatLngBounds(
|
||||
new TMap.LatLng(boundsS, boundsW),
|
||||
new TMap.LatLng(boundsN, boundsE)
|
||||
);
|
||||
}
|
||||
let polygons = new TMap.MultiPolygon({map, geometries: []});
|
||||
new mapLib.service.District({
|
||||
polygon: 2,
|
||||
maxOffset: 100
|
||||
}).search({keyword}).then(res => {
|
||||
if (res?.result) {
|
||||
let center = res.result?.[0]?.[0]?.location
|
||||
this.tmap.setCenter(center)
|
||||
res.result.forEach((level) => {
|
||||
level.forEach((place) => {
|
||||
let bounds = [];
|
||||
let newGeometries = place.polygon.map((polygon, index) => {
|
||||
bounds.push(fitBounds(polygon)); // 计算能完整呈现行政区边界的最小矩形范围
|
||||
return {
|
||||
id: `${place.id}_${index}`,
|
||||
paths: polygon, // 将得到的行政区划边界用多边形标注在地图上
|
||||
};
|
||||
});
|
||||
bounds = bounds.reduce((a, b) => {
|
||||
return fitBounds([
|
||||
a.getNorthEast(),
|
||||
a.getSouthWest(),
|
||||
b.getNorthEast(),
|
||||
b.getSouthWest(),
|
||||
]);
|
||||
}); // 若一行政区有多个多边形边界,应计算能包含所有多边形边界的范围。
|
||||
polygons.updateGeometries(newGeometries);
|
||||
this.tmap.fitBounds(bounds);
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!window?.TMap) {
|
||||
window.initTMap = this.initTMap
|
||||
this.injectLib(`https://map.qq.com/api/gljs?v=1.exp&key=${this.key}&libraries=${this.libraries.toString()}&callback=initTMap`)
|
||||
} else {
|
||||
this.initTMap()
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
this.map?.destroy()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiTMap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
|
||||
::v-deep.map {
|
||||
height: 100%;
|
||||
|
||||
& > div > div {
|
||||
&:nth-of-type(2), &:nth-of-type(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user