ui库和web端产品库合并版本(还需修复细节)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ yarn-error.log*
|
||||
/oms/dist/
|
||||
/project/*/index.js
|
||||
/project/*/dist
|
||||
/ui/lib/
|
||||
|
||||
@@ -6,6 +6,7 @@ core/
|
||||
public/
|
||||
project/
|
||||
.idea/
|
||||
ui/lib/
|
||||
|
||||
# 忽略指定文件
|
||||
vue.config.js
|
||||
|
||||
1
.npmrc
1
.npmrc
@@ -1,4 +1,3 @@
|
||||
registry=http://cli.sinoecare.net/
|
||||
email=aixianling@sinoecare.com
|
||||
always-auth=true
|
||||
_auth="YWRtaW46YWRtaW4xMjM="
|
||||
|
||||
5
bin/ui.js
Normal file
5
bin/ui.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const {copyFiles, chalkTag} = require("./tools");
|
||||
const start = () => {
|
||||
copyFiles('lib', 'meta').then(() => chalkTag.done("转移成功!"))
|
||||
}
|
||||
start()
|
||||
@@ -1,4 +1,4 @@
|
||||
import http from "dvcp-ui/lib/js/request";
|
||||
import http from "dui/lib/js/request";
|
||||
import Vue from "vue"
|
||||
|
||||
export default class PartyOrg {
|
||||
|
||||
@@ -4,9 +4,9 @@ import ui from 'element-ui';
|
||||
import router from './router/router';
|
||||
import axios from './router/axios';
|
||||
import utils from './utils';
|
||||
import vcUI from 'dvcp-ui';
|
||||
import 'dvcp-ui/lib/styles/common.scss';
|
||||
import 'dvcp-ui/lib/dvcp-ui.css';
|
||||
import vcUI from 'dui';
|
||||
import 'dui/lib/styles/common.scss';
|
||||
import 'dui/lib/dui.css';
|
||||
import store from './store';
|
||||
import dataV from '@jiaminghi/data-view';
|
||||
import appComps from '../components'
|
||||
@@ -28,9 +28,9 @@ let theme = null
|
||||
store.dispatch('getSystem').then(({colorScheme}) => {
|
||||
theme = JSON.parse(colorScheme || null)
|
||||
Vue.prototype.$theme = theme?.web || "blue"
|
||||
return import(`dvcp-ui/lib/styles/theme.${theme?.web}.scss`).catch(() => 0)
|
||||
return import(`dui/lib/styles/theme.${theme?.web}.scss`).catch(() => 0)
|
||||
}).finally(() => {
|
||||
Vue.prototype.$vm = app
|
||||
!theme ? app.$mount('#app') : import(`dvcp-ui/lib/styles/common.scss`).finally(() => app.$mount('#app'))
|
||||
!theme ? app.$mount('#app') : import(`dui/lib/styles/common.scss`).finally(() => app.$mount('#app'))
|
||||
})
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import instance from 'dvcp-ui/lib/js/request'
|
||||
import instance from 'dui/lib/js/request'
|
||||
import {Message} from 'element-ui'
|
||||
|
||||
let baseURLs = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import preState from 'vuex-persistedstate'
|
||||
import * as modules from "dvcp-ui/lib/js/modules"
|
||||
import * as modules from "dui/lib/js/modules"
|
||||
import xsActions from "../../project/xiushan/actions"
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {MessageBox} from 'element-ui'
|
||||
import store from '../store'
|
||||
import tools from 'dvcp-ui/lib/js/utils'
|
||||
import tools from 'dui/lib/js/utils'
|
||||
|
||||
const addChildParty = (parent, pending) => {
|
||||
let doBeforeCount = pending.length
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"lib": "npm unpublish --force&&npm publish",
|
||||
"ui": "npm i dvcp-ui@latest",
|
||||
"ui": "npm run build -w ui&&npm i dui@latest",
|
||||
"sync": "node bin/appsSync.js"
|
||||
},
|
||||
"workspaces": [
|
||||
"ui"
|
||||
],
|
||||
"files": [
|
||||
"packages",
|
||||
"project",
|
||||
@@ -21,7 +24,7 @@
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
"bin-code-editor": "^0.9.0",
|
||||
"dayjs": "^1.8.35",
|
||||
"dvcp-ui": "^1.42.2",
|
||||
"dui": "file:ui",
|
||||
"echarts": "^5.1.2",
|
||||
"hash.js": "^1.1.7",
|
||||
"mp4box": "^0.4.1",
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import AddVaccination from "./addVaccination";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "AppVaccination",
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "addVaccination",
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<script>
|
||||
import {mapState} from 'vuex';
|
||||
import detail from './detail'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'AppScorePersonal',
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
26
packages/extra/AppModConfig/AppModConfig.vue
Normal file
26
packages/extra/AppModConfig/AppModConfig.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<section class="AppModConfig">
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: "AppModConfig",
|
||||
label: "应用定制配置",
|
||||
props: {
|
||||
instance: Function,
|
||||
dict: Object,
|
||||
permissions: Function
|
||||
},
|
||||
created() {
|
||||
this.dict.load('yesOrNo')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AppModConfig {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -765,7 +765,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "Add",
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "neighbourSetting",
|
||||
|
||||
@@ -434,7 +434,7 @@ import {mapState} from "vuex";
|
||||
import AiEditCard from "./components/AiEditCard";
|
||||
import PersonalAssets from "./components/personalAssets";
|
||||
import TagsManage from "./components/tagsManage";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "residentDetail",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "psList",
|
||||
|
||||
@@ -328,7 +328,7 @@
|
||||
<script>
|
||||
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "hrAdd",
|
||||
|
||||
@@ -497,7 +497,7 @@
|
||||
import {mapState} from "vuex";
|
||||
import HrMeasure from "./detail/hrMeasure";
|
||||
import HrLog from "./detail/hrLog";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "hrDetail",
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "hrList",
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "sealDetail",
|
||||
|
||||
@@ -765,7 +765,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "Add",
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "neighbourSetting",
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils"
|
||||
import {ID} from "dui/lib/js/utils"
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Add',
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'Detail',
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: 'List',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from "dvcp-ui/lib/js/request";
|
||||
import axios from "dui/lib/js/request";
|
||||
|
||||
export default {
|
||||
getFinanceUser({commit}) {
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import LoanSta from "./loanSta";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "loanList",
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "AppLoanSta",
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "sealDetail",
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<script>
|
||||
import {mapState} from "vuex";
|
||||
import DrawInPhone from "./drawInPhone";
|
||||
import {ID} from "dvcp-ui/lib/js/utils";
|
||||
import {ID} from "dui/lib/js/utils";
|
||||
|
||||
export default {
|
||||
name: "AppPersonalSignature",
|
||||
|
||||
18
ui/meta/cdn/aes.js
Normal file
18
ui/meta/cdn/aes.js
Normal file
File diff suppressed because one or more lines are too long
5892
ui/meta/cdn/crypto-js.js
Normal file
5892
ui/meta/cdn/crypto-js.js
Normal file
File diff suppressed because it is too large
Load Diff
7
ui/meta/cdn/mode-ecb.js
Normal file
7
ui/meta/cdn/mode-ecb.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
CryptoJS.mode.ECB = (function () { var a = CryptoJS.lib.BlockCipherMode.extend(); a.Encryptor = a.extend({ processBlock: function (a, b) { this._cipher.encryptBlock(a, b) } }); a.Decryptor = a.extend({ processBlock: function (a, b) { this._cipher.decryptBlock(a, b) } }); return a }())
|
||||
1
ui/meta/cdn/mpfont.js
Normal file
1
ui/meta/cdn/mpfont.js
Normal file
File diff suppressed because one or more lines are too long
25
ui/meta/cdn/pad-zeropadding.js
Normal file
25
ui/meta/cdn/pad-zeropadding.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Zero padding strategy.
|
||||
*/
|
||||
CryptoJS.pad.ZeroPadding = {
|
||||
pad: function (data, blockSize) {
|
||||
// Shortcut
|
||||
var blockSizeBytes = blockSize * 4
|
||||
|
||||
// Pad
|
||||
data.clamp()
|
||||
data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes)
|
||||
},
|
||||
|
||||
unpad: function (data) {
|
||||
// Shortcut
|
||||
var dataWords = data.words
|
||||
|
||||
// Unpad
|
||||
var i = data.sigBytes - 1
|
||||
while (!((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) {
|
||||
i--
|
||||
}
|
||||
data.sigBytes = i + 1
|
||||
}
|
||||
}
|
||||
1
ui/meta/cdn/weappFont.js
Normal file
1
ui/meta/cdn/weappFont.js
Normal file
File diff suppressed because one or more lines are too long
88
ui/meta/js/area.js
Normal file
88
ui/meta/js/area.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import request from "./request";
|
||||
|
||||
|
||||
export default class Area {
|
||||
constructor(code, hash = {}) {
|
||||
this.id = code
|
||||
this.level = Area.getLevelByAreaId(code)
|
||||
this.areaMap = Object.values(this.getAreaInfo(code))
|
||||
if (Object.keys(hash).length > 0) {
|
||||
this.getName(this.areaMap.map(id => hash[id]))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地区的行政等级
|
||||
* @param code 地区编码
|
||||
* @returns {number}
|
||||
*/
|
||||
static getLevelByAreaId(code) {
|
||||
if (code) {
|
||||
if (code.length === 2 || /0{10}$/.test(code)) return 0;
|
||||
else if (/0{8}$/.test(code)) return 1;
|
||||
else if (/0{6}$/.test(code)) return 2;
|
||||
else if (/0{3}$/.test(code)) return 3;
|
||||
else return 4
|
||||
} else return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据地区编码获取指定等级的地区编码
|
||||
* @param value 地区编码
|
||||
* @param level 指定等级
|
||||
* @returns {string|null|*}
|
||||
*/
|
||||
static getAreaCodeByLevel(value, level) {
|
||||
if (value) {
|
||||
const areaNumber = value.toString();
|
||||
switch (level) {
|
||||
case 0:
|
||||
return areaNumber.substring(0, 2) + '0000000000';
|
||||
case 1:
|
||||
return areaNumber.substring(0, 4) + '00000000';
|
||||
case 2:
|
||||
return areaNumber.substring(0, 6) + '000000';
|
||||
case 3:
|
||||
return areaNumber.substring(0, 9) + '000';
|
||||
case 4:
|
||||
return areaNumber
|
||||
}
|
||||
} else return null
|
||||
}
|
||||
|
||||
static createByAction(areaId, ins = request, action = "/admin/area/getAllParentAreaId") {
|
||||
return ins.post(action, null, {params: {areaId}, withoutToken: 1}).then(res => res?.data?.reverse() || [])
|
||||
}
|
||||
|
||||
getAreaInfo(id) {
|
||||
let info = {}
|
||||
const currentLevel = Area.getLevelByAreaId(id);
|
||||
for (let i = 0; i <= currentLevel; i++) {
|
||||
info[i] = Area.getAreaCodeByLevel(id, i);
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
async getAreaName() {
|
||||
const names = await Area.createByAction(this.id);
|
||||
this.getName(names)
|
||||
}
|
||||
|
||||
getName(names) {
|
||||
this.meta = names
|
||||
this.nameMap = names?.map(e => e.name) || []
|
||||
this.name = names?.slice(-1)?.[0]?.name
|
||||
this.fullname = this.nameMap.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步从数据库中获取地区信息
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
static async init(code) {
|
||||
const names = await Area.createByAction(code),
|
||||
area = new Area(code)
|
||||
area.getName(names)
|
||||
return area
|
||||
}
|
||||
}
|
||||
20
ui/meta/js/coin.js
Normal file
20
ui/meta/js/coin.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
cny(money) {
|
||||
if (money) {
|
||||
money.toLocaleString('zh-Hans-CN', {style: 'currency', currency: "CNY"})
|
||||
}
|
||||
return money
|
||||
},
|
||||
cn(money) {
|
||||
let num = parseFloat(money), cnMoney = '',
|
||||
units = '仟佰拾亿仟佰拾万仟佰拾元角分',
|
||||
cnNum = '零壹贰叁肆伍陆柒捌玖'
|
||||
num = num.toFixed(2).replace(/\./g,'')
|
||||
units = units.substring(units.length - num.length)
|
||||
console.log(num, units.length, num.length)
|
||||
Array.from(num).map((e, i) => {
|
||||
cnMoney += cnNum.charAt(e) + units.charAt(i)
|
||||
})
|
||||
return cnMoney.replace(/零角零分$/, '整').replace(/零[仟佰拾]/g, '零').replace(/零{2,}/g, '零').replace(/零([亿|万])/g, '$1').replace(/零+元/, '元').replace(/亿零{0,3}万/, '亿').replace(/^元/, "零元")
|
||||
}
|
||||
}
|
||||
77
ui/meta/js/dict.js
Normal file
77
ui/meta/js/dict.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import request from "./request";
|
||||
|
||||
/**
|
||||
* 封装字典工具类
|
||||
*/
|
||||
const $dict = {
|
||||
url: "/admin/dictionary/queryValsByCodeList",
|
||||
loading: [],
|
||||
resolves: [],
|
||||
getStorage() {
|
||||
const dicts = JSON.parse(localStorage.getItem('dicts') || null)
|
||||
return dicts?.data || dicts || [];
|
||||
},
|
||||
setUrl(v) {
|
||||
this.url = v
|
||||
},
|
||||
getData(codeList) {
|
||||
codeList = [codeList].flat().filter(Boolean).toString()
|
||||
return request.post(this.url, null, {
|
||||
withoutToken: true, params: {codeList}
|
||||
}).then(res => res?.data && this.setStorage(res.data))
|
||||
},
|
||||
load(...code) {
|
||||
return new Promise(resolve => {
|
||||
this.resolves.push(resolve)
|
||||
if (this.loading.length == 2) {
|
||||
const [timer, codes] = this.loading;
|
||||
clearTimeout(timer)
|
||||
code = Array.from(new Set([codes, code].flat()))
|
||||
}
|
||||
const timer = setTimeout(() => {
|
||||
this.getData(code).then(() => Promise.all(this.resolves.map(e => e())).then(() => this.resolves = []))
|
||||
}, 500)
|
||||
this.loading = [timer, code]
|
||||
})
|
||||
},
|
||||
setStorage(data) {
|
||||
let ds = this.getStorage()
|
||||
data.map(p => {
|
||||
if (ds.some(d => d.key == p.key)) {
|
||||
const index = ds.findIndex(d => d.key == p.key)
|
||||
ds.splice(index, 1, p)
|
||||
} else {
|
||||
ds.push(p)
|
||||
}
|
||||
})
|
||||
localStorage.setItem("dicts", JSON.stringify([ds].flat()))
|
||||
},
|
||||
getDict(key) {
|
||||
let dict = this.getStorage().find(e => e.key == key)
|
||||
!dict && console.warn("字典%s缺少加载...", key)
|
||||
return dict ? dict.values : []
|
||||
},
|
||||
getValue(key, label) {
|
||||
let dict = this.getDict(key)
|
||||
if (dict) {
|
||||
let item = dict.find(v => v.dictName == label)
|
||||
return item ? item.dictValue : label
|
||||
} else return label
|
||||
},
|
||||
getLabel(key, value) {
|
||||
let dict = this.getDict(key)
|
||||
if (dict) {
|
||||
let item = dict.find(v => v.dictValue == value)
|
||||
return item ? item.dictName : value
|
||||
} else return value
|
||||
},
|
||||
getColor(key, value) {
|
||||
let dict = this.getDict(key)
|
||||
if (dict) {
|
||||
let item = dict.find(v => v.dictValue == value)
|
||||
return item ? item.dictColor : value
|
||||
} else return value
|
||||
},
|
||||
}
|
||||
|
||||
export default $dict
|
||||
23
ui/meta/js/encryption.js
Normal file
23
ui/meta/js/encryption.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import CryptoJs from "../cdn/crypto-js";
|
||||
|
||||
/**
|
||||
* 密码加密工具
|
||||
* @param params
|
||||
* @param c 加载尝试计数器
|
||||
* @returns {string}
|
||||
*/
|
||||
export const $encryption = (params, c = 0) => {
|
||||
if (CryptoJs) {
|
||||
const key = "thanks,villcloud"
|
||||
let iv = CryptoJs.enc.Latin1.parse(key)
|
||||
let encrypted = CryptoJs.AES.encrypt(params.password, iv, {
|
||||
iv,
|
||||
mode: CryptoJs.mode.CBC,
|
||||
padding: CryptoJs.pad.ZeroPadding
|
||||
})
|
||||
return encrypted.toString()
|
||||
} else if (c < 10) {
|
||||
setTimeout(() => $encryption(params, ++c), 200)
|
||||
} else console.error("无法加载CryptoJs")
|
||||
}
|
||||
export default $encryption
|
||||
238
ui/meta/js/identity.js
Normal file
238
ui/meta/js/identity.js
Normal file
@@ -0,0 +1,238 @@
|
||||
import dayjs from "./moment";
|
||||
|
||||
|
||||
const PARAMS = {
|
||||
/* 省,直辖市代码表 */
|
||||
provinceAndCitys: {
|
||||
11: '北京',
|
||||
12: '天津',
|
||||
13: '河北',
|
||||
14: '山西',
|
||||
15: '内蒙古',
|
||||
21: '辽宁',
|
||||
22: '吉林',
|
||||
23: '黑龙江',
|
||||
31: '上海',
|
||||
32: '江苏',
|
||||
33: '浙江',
|
||||
34: '安徽',
|
||||
35: '福建',
|
||||
36: '江西',
|
||||
37: '山东',
|
||||
41: '河南',
|
||||
42: '湖北',
|
||||
43: '湖南',
|
||||
44: '广东',
|
||||
45: '广西',
|
||||
46: '海南',
|
||||
50: '重庆',
|
||||
51: '四川',
|
||||
52: '贵州',
|
||||
53: '云南',
|
||||
54: '西藏',
|
||||
61: '陕西',
|
||||
62: '甘肃',
|
||||
63: '青海',
|
||||
64: '宁夏',
|
||||
65: '新疆',
|
||||
71: '台湾',
|
||||
81: '香港',
|
||||
82: '澳门',
|
||||
91: '国外'
|
||||
},
|
||||
|
||||
/* 每位加权因子 */
|
||||
powers: [
|
||||
'7',
|
||||
'9',
|
||||
'10',
|
||||
'5',
|
||||
'8',
|
||||
'4',
|
||||
'2',
|
||||
'1',
|
||||
'6',
|
||||
'3',
|
||||
'7',
|
||||
'9',
|
||||
'10',
|
||||
'5',
|
||||
'8',
|
||||
'4',
|
||||
'2'
|
||||
],
|
||||
|
||||
/* 第18位校检码 */
|
||||
parityBit: ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'],
|
||||
|
||||
/* 性别 */
|
||||
genders: {1: '男', 0: '女'},
|
||||
}
|
||||
|
||||
const Check = {
|
||||
/* 校验地址码 */
|
||||
checkAddressCode(addressCode) {
|
||||
const check = /^[1-9]\d{5}$/.test(addressCode)
|
||||
if (!check) return false
|
||||
return !!PARAMS.provinceAndCitys[parseInt(addressCode.substring(0, 2))]
|
||||
},
|
||||
/* 校验日期码 */
|
||||
checkBirthDayCode(birDayCode) {
|
||||
const check = /^[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))$/.test(
|
||||
birDayCode
|
||||
)
|
||||
if (!check) return false
|
||||
const yyyy = parseInt(birDayCode.substring(0, 4), 10)
|
||||
const mm = parseInt(birDayCode.substring(4, 6), 10)
|
||||
const dd = parseInt(birDayCode.substring(6), 10)
|
||||
const xdata = new Date(yyyy, mm - 1, dd)
|
||||
if (xdata > new Date()) {
|
||||
return false // 生日不能大于当前日期
|
||||
} else {
|
||||
return (
|
||||
xdata.getFullYear() == yyyy &&
|
||||
xdata.getMonth() == mm - 1 &&
|
||||
xdata.getDate() == dd
|
||||
)
|
||||
}
|
||||
},
|
||||
/* 验证校检码 */
|
||||
checkParityBit(idCardNo) {
|
||||
const parityBit = idCardNo.charAt(17).toUpperCase()
|
||||
return this.getParityBit(idCardNo) == parityBit
|
||||
},
|
||||
// 校验15位的身份证号码
|
||||
check15IdCardNo(idCardNo) {
|
||||
// 15位身份证号码的基本校验
|
||||
let check = /^[1-9]\d{7}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}$/.test(
|
||||
idCardNo
|
||||
)
|
||||
if (!check) return false
|
||||
// 校验地址码
|
||||
const addressCode = idCardNo.substring(0, 6)
|
||||
check = this.checkAddressCode(addressCode)
|
||||
if (!check) return false
|
||||
const birDayCode = '19' + idCardNo.substring(6, 12)
|
||||
// 校验日期码
|
||||
return this.checkBirthDayCode(birDayCode)
|
||||
},
|
||||
|
||||
// 校验18位的身份证号码
|
||||
check18IdCardNo(idCardNo) {
|
||||
// 18位身份证号码的基本格式校验
|
||||
let check = /^[1-9]\d{5}[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}(\d|x|X)$/.test(
|
||||
idCardNo
|
||||
)
|
||||
if (!check) return false
|
||||
// 校验地址码
|
||||
const addressCode = idCardNo.substring(0, 6)
|
||||
check = this.checkAddressCode(addressCode)
|
||||
if (!check) return false
|
||||
// 校验日期码
|
||||
const birDayCode = idCardNo.substring(6, 14)
|
||||
check = this.checkBirthDayCode(birDayCode)
|
||||
if (!check) return false
|
||||
// 验证校检码
|
||||
return this.checkParityBit(idCardNo)
|
||||
},
|
||||
/* 计算校检码 */
|
||||
getParityBit(idCardNo) {
|
||||
const id17 = idCardNo.substring(0, 17)
|
||||
/* 加权 */
|
||||
let power = 0
|
||||
for (let i = 0; i < 17; i++) {
|
||||
power += parseInt(id17.charAt(i), 10) * parseInt(PARAMS.powers[i])
|
||||
}
|
||||
/* 取模 */
|
||||
const mod = power % 11
|
||||
return PARAMS.parityBit[mod]
|
||||
}
|
||||
}
|
||||
|
||||
export default class Identity {
|
||||
constructor(code) {
|
||||
this.code = this.getId18(code)
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
const {code} = this
|
||||
if (!!code) {
|
||||
this.hideCode = Identity.hideId(code)
|
||||
this.getIdCardInfo(code)
|
||||
}
|
||||
}
|
||||
|
||||
static hideId(code) {
|
||||
return code?.replace(/^(\d{10})\d{4}(.{4}$)/g, `$1${Array(5).join('*')}$2`)
|
||||
}
|
||||
|
||||
getId18(code) {
|
||||
if (code.length == 15) {
|
||||
const id17 = code.substring(0, 6) + '19' + code.substring(6)
|
||||
const parityBit = Check.getParityBit(id17)
|
||||
return id17 + parityBit
|
||||
} else if (code.length == 18) {
|
||||
return code
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
getIdCardInfo(idCardNo) {
|
||||
this.birthday = Identity.getBirthday(idCardNo)
|
||||
this.sex = Identity.getSex(idCardNo)
|
||||
this.gender = PARAMS.genders[this.sex]
|
||||
this.age = Identity.getAge(idCardNo)
|
||||
this.areaId = Identity.getArea(idCardNo)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性别
|
||||
* @param code
|
||||
* @returns {string}
|
||||
*/
|
||||
static getSex(code) {
|
||||
return (Number(code.substring(16, 17)) % 2).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年龄
|
||||
* @param code
|
||||
* @returns {number}
|
||||
*/
|
||||
static getAge(code) {
|
||||
const birthday = dayjs(code.substring(6, 14), 'YYYYMMDD')
|
||||
return Math.ceil(dayjs.duration(dayjs().unix() - dayjs(birthday).unix(), 's').asYears())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地区编码
|
||||
* @param code
|
||||
*/
|
||||
static getArea(code) {
|
||||
return code.substring(0, 6) + new Array(7).join(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生日
|
||||
*/
|
||||
static getBirthday(code) {
|
||||
return dayjs(code.substring(6, 14), 'YYYYMMDD').format("YYYY-MM-DD").replace('Invalid Date', '')
|
||||
}
|
||||
|
||||
/* 校验15位或18位的身份证号码 */
|
||||
static check(idCardNo) {
|
||||
// 15位和18位身份证号码的基本校验
|
||||
const check = /^\d{15}|(\d{17}(\d|x|X))$/.test(idCardNo)
|
||||
if (!check) return false
|
||||
// 判断长度为15位或18位
|
||||
if (idCardNo.length == 15) {
|
||||
return Check.check15IdCardNo(idCardNo)
|
||||
} else if (idCardNo.length == 18) {
|
||||
return Check.check18IdCardNo(idCardNo)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
305
ui/meta/js/modules.js
Normal file
305
ui/meta/js/modules.js
Normal file
@@ -0,0 +1,305 @@
|
||||
import http from "./request";
|
||||
import utils from "./utils";
|
||||
import Vue from "vue"
|
||||
|
||||
export const sys = {
|
||||
state: () => ({
|
||||
info: {},
|
||||
theme: {}
|
||||
}),
|
||||
mutations: {
|
||||
setSysInfo(state, info) {
|
||||
state.info = info
|
||||
},
|
||||
setTheme(state, theme) {
|
||||
state.theme = theme
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
getSystem({commit}, info) {
|
||||
return http.post("/app/appdvcpconfig/getSystemInfo", null, {withoutToken: true}).then(res => {
|
||||
if (res?.data) {
|
||||
let {systemInfo, colorScheme, enableGreyFilter} = res.data
|
||||
systemInfo = JSON.parse(systemInfo || null) || {}
|
||||
colorScheme = JSON.parse(colorScheme || null) || {}
|
||||
commit("setSysInfo", {...info, ...systemInfo})
|
||||
commit("setTheme", {colorScheme, enableGreyFilter})
|
||||
return res.data
|
||||
} else return Promise.reject()
|
||||
}).catch(() => commit("setSysInfo", info))
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 用户信息
|
||||
*/
|
||||
export const user = {
|
||||
state: () => ({
|
||||
token: "",
|
||||
info: {},
|
||||
routes: []
|
||||
}),
|
||||
mutations: {
|
||||
setToken(state, token) {
|
||||
state.token = token;
|
||||
},
|
||||
setUserInfo(state, info) {
|
||||
state.info = info
|
||||
},
|
||||
cleanUserInfo(state) {
|
||||
state.info = {}
|
||||
state.token = ""
|
||||
},
|
||||
setUserExtra(state, extra = {}) {
|
||||
Object.keys(extra).map(e => Vue.set(state, e, extra[e]))
|
||||
},
|
||||
setRoutes(state,routes){
|
||||
state.routes = routes
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
getToken({commit}, params) {
|
||||
let action = "/auth/oauth/token"
|
||||
if (params?.action) {
|
||||
action = params?.action
|
||||
delete params?.action
|
||||
}
|
||||
const password = utils.$encryption(params)
|
||||
return http.post(action, null, {
|
||||
auth: {
|
||||
username: 'villcloud',
|
||||
password: "villcloud"
|
||||
},
|
||||
params: {grant_type: 'password', scope: 'server', ...params, password}
|
||||
}).then(res => {
|
||||
if (res?.access_token) {
|
||||
const {token_type, access_token} = res, token = [token_type, access_token].join(" ")
|
||||
return commit('setToken', token)
|
||||
}
|
||||
})
|
||||
},
|
||||
getUserInfo({commit, dispatch}) {
|
||||
return http.post("/admin/user/detail-phone").then(res => {
|
||||
if (res?.data) {
|
||||
commit("setUserInfo", res.data)
|
||||
return Promise.all([dispatch('getWorkflowConfigs')]).then(() => res.data)
|
||||
}
|
||||
})
|
||||
},
|
||||
getRouteName({state}, appName) {
|
||||
return state.routes.find(e => e.component == appName)?.route || appName
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 企微jssdk功能
|
||||
*/
|
||||
let timer = {injectJWeixin: null, initOpenData: null}
|
||||
export const wxwork = {
|
||||
state: () => ({
|
||||
agentSignURL: "",
|
||||
apiList: [],
|
||||
config: {}
|
||||
}),
|
||||
mutations: {
|
||||
setConfig(state, config) {
|
||||
state.config = config
|
||||
},
|
||||
setAgentSignURL(state, url) {
|
||||
state.agentSignURL = url
|
||||
},
|
||||
setApiList(state, list) {
|
||||
state.apiList = list
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
agentSign({state, commit, rootState}, params) {
|
||||
//授权jssdk在url上使用,并获取corpId
|
||||
let url = window.location.href
|
||||
if (state.agentSignURL == url && state.config.corpId) {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
commit("setAgentSignURL", url)
|
||||
commit("setApiList", [])
|
||||
let action = "/app/wxcptp/portal/agentSign"
|
||||
if (!!params?.action) {
|
||||
action = params.action
|
||||
delete params.action
|
||||
}
|
||||
const {corpId} = rootState.user.info
|
||||
return http.post(action, null, {
|
||||
withoutToken: true,
|
||||
params: {corpId, ...params, url}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
let config = {
|
||||
...params,
|
||||
debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
|
||||
beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
|
||||
corpid: res.data.corpid, // 必填,企业微信的corpid,必须与当前登录的企业一致
|
||||
agentid: res.data.agentId, // 必填,企业微信的应用id (e.g. 1000247)
|
||||
timestamp: Number(res.data.timestamp), // 必填,生成签名的时间戳
|
||||
nonceStr: res.data.nonceStr, // 必填,生成签名的随机串
|
||||
signature: res.data.signature,// 必填,签名,见 附录-JS-SDK使用权限签名算法
|
||||
...res.data
|
||||
}
|
||||
commit("setConfig", config)
|
||||
return config
|
||||
}
|
||||
}).catch(err => {
|
||||
commit("setAgentSignURL", "")
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
},
|
||||
injectJWeixin({state, commit}, apis) {
|
||||
const inject = (jsApiList) => new Promise((resolve, reject) => {
|
||||
jsApiList = jsApiList || []
|
||||
if (timer.injectJWeixin) {//节流设置,50ms内的多次请求合并到一处
|
||||
clearTimeout(timer.injectJWeixin)
|
||||
jsApiList = [...new Set([...state.apiList, ...jsApiList])]
|
||||
commit("setApiList", jsApiList)
|
||||
}
|
||||
timer.injectJWeixin = setTimeout(() => {
|
||||
const sdk = wx?.agentConfig ? wx : jWeixin
|
||||
sdk?.agentConfig({
|
||||
...state.config, jsApiList,
|
||||
success: res => resolve(res),
|
||||
fail: err => {
|
||||
console.error(err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}, 50)
|
||||
})
|
||||
return inject(apis)
|
||||
},
|
||||
initOpenData({dispatch, commit}, params = {}) {
|
||||
const initWOD = (count = 0) => {
|
||||
if (!!window?.WWOpenData) {
|
||||
const canvas = params?.canvas
|
||||
if (canvas) delete params.canvas
|
||||
if (timer.initOpenData) {
|
||||
clearTimeout(timer.initOpenData)
|
||||
}
|
||||
const init = () => canvas ? dispatch('initCanvas') : dispatch('bindElements')
|
||||
timer.initOpenData = setTimeout(() => {
|
||||
window?.WWOpenData?.checkSession({
|
||||
success: () => init(),
|
||||
fail: () => {
|
||||
dispatch('agentSign', params).then(() => dispatch("injectJWeixin")).then(() => init())
|
||||
}
|
||||
})
|
||||
}, 50)
|
||||
} else if (count > 10) {
|
||||
console.log("无法获取WWOpenData")
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
initWOD(++count)
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
dispatch('agentSign', params).then(() => dispatch("injectJWeixin")).then(() => initWOD())
|
||||
},
|
||||
bindElements() {
|
||||
const nodes = document.querySelectorAll('.AiOpenData')
|
||||
window.WWOpenData?.bindAll(nodes)
|
||||
},
|
||||
initCanvas() {
|
||||
window.WWOpenData?.initCanvas()
|
||||
},
|
||||
transCanvas(store, items) {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.WWOpenData?.prefetch({items}, (err, data) => {
|
||||
err ? reject(err) : resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 各种前端方案记录选择
|
||||
*/
|
||||
export const logs = {
|
||||
state: () => ({
|
||||
closeIntro: []
|
||||
}),
|
||||
mutations: {
|
||||
addCloseIntro(state, app) {
|
||||
state.closeIntro.push(app)
|
||||
}
|
||||
},
|
||||
}
|
||||
/**
|
||||
* 流程信息
|
||||
*/
|
||||
const startProcess = (form) => {
|
||||
const {bid, app, flows = {}} = form
|
||||
const process = flows[app]
|
||||
if (!!process) {
|
||||
const {id: pid, config} = process
|
||||
let workflowConfig = JSON.parse(config || null), nowNodeId = [], startId
|
||||
workflowConfig?.nodes?.map(e => {
|
||||
if (e.type == "start") {
|
||||
e.properties.isStart = true
|
||||
e.properties.updateTime = utils.$moment().format("YYYY-MM-DD HH:mm:ss")
|
||||
startId = e.id
|
||||
}
|
||||
})
|
||||
workflowConfig?.edges?.map(e => {
|
||||
if (e.sourceNodeId == startId) {
|
||||
nowNodeId.push(e.targetNodeId)
|
||||
}
|
||||
})
|
||||
nowNodeId = nowNodeId?.toString()
|
||||
return !!nowNodeId && http.post("/app/appworkflowlog/addOrUpdate", {bid, pid, nowNodeId, workflowConfig: JSON.stringify(workflowConfig)})
|
||||
}
|
||||
}
|
||||
export const workflow = {
|
||||
state: () => ({}),
|
||||
mutations: {
|
||||
setWfConfigs(state, configs) {
|
||||
configs.map(e => {
|
||||
Vue.set(state, e.app, e)
|
||||
})
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
getWorkflowConfigs({commit}) {
|
||||
return http.post("/app/appworkflowmanage/list", null, {
|
||||
params: {size: 999}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
return commit("setWfConfigs", res.data.records)
|
||||
}
|
||||
}).catch(() => 0)
|
||||
},
|
||||
startFlow(context, form) {
|
||||
startProcess(form)
|
||||
},
|
||||
endFlow(context, form) {
|
||||
let {workflowConfig = {}, nowNodeId} = form
|
||||
workflowConfig?.nodes?.map(e => {
|
||||
if (e.type == "end") {
|
||||
e.properties.isFinished = true
|
||||
e.properties.updateTime = utils.$moment().format("YYYY-MM-DD HH:mm:ss")
|
||||
nowNodeId = "nowNodeId"
|
||||
}
|
||||
})
|
||||
return http.post("/app/appworkflowlog/addOrUpdate", {...form, nowNodeId, workflowConfig: JSON.stringify(workflowConfig)})
|
||||
}
|
||||
},
|
||||
processAdapter(config) {//流程业务拦截器
|
||||
if (/addOrUpdate/.test(config.url)) {
|
||||
let app = config.url.replace(/.+(app[^\\\/]+).+/g, '$1')
|
||||
if (/appapplicationinfo/.test(app)) {//动态台账表单
|
||||
app = config.params?.appId
|
||||
} else if (/appcontentinfo/.test(app)) {//内容发布
|
||||
app = config.data?.moduleId
|
||||
}
|
||||
config.workflow = app
|
||||
}
|
||||
return config
|
||||
},
|
||||
startProcess
|
||||
}
|
||||
35
ui/meta/js/moment.js
Normal file
35
ui/meta/js/moment.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import moment from 'dayjs'
|
||||
import duration from 'dayjs/plugin/duration'
|
||||
import updateLocale from 'dayjs/plugin/updateLocale'
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
|
||||
moment.locale('zh-cn')
|
||||
moment.extend(updateLocale)
|
||||
moment.extend(customParseFormat)
|
||||
moment.extend(duration)
|
||||
moment.updateLocale('zh-cn', {
|
||||
weekdays: "星期日|星期一|星期二|星期三|星期四|星期五|星期六".split("|"),
|
||||
meridiem(hour) {
|
||||
let word = ""
|
||||
if (hour < 6) {
|
||||
word = "凌晨"
|
||||
} else if (hour < 9) {
|
||||
word = "早上"
|
||||
} else if (hour < 12) {
|
||||
word = "上午"
|
||||
} else if (hour < 14) {
|
||||
word = "中午"
|
||||
} else if (hour < 17) {
|
||||
word = "下午"
|
||||
} else if (hour < 19) {
|
||||
word = "傍晚"
|
||||
} else if (hour < 22) {
|
||||
word = "晚上"
|
||||
} else {
|
||||
word = "夜里"
|
||||
}
|
||||
return word;
|
||||
}
|
||||
})
|
||||
export default moment
|
||||
83
ui/meta/js/request.js
Normal file
83
ui/meta/js/request.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/* eslint-disable */
|
||||
import axios from 'axios'
|
||||
import {Message} from "element-ui";
|
||||
import {workflow} from "./modules";
|
||||
|
||||
const instance = axios.create({
|
||||
baseURL: process.env.NODE_ENV === "production" ? "/" : "/lan",
|
||||
timeout: 600000,
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
const getStore = () => JSON.parse(localStorage.getItem("vuex") || null) || {}
|
||||
|
||||
const getToken = () => getStore().user?.token
|
||||
/**
|
||||
* 节流工具
|
||||
*/
|
||||
let throttleMap = {}
|
||||
const source = axios.CancelToken.source();
|
||||
instance.interceptors.request.use(config => {
|
||||
if (config.throttle) {// 节流处理
|
||||
let timer = throttleMap[config.url]
|
||||
if (!!timer) {
|
||||
config.cancelToken = source.token
|
||||
source.cancel("节流控制,取消请求:" + config.url)
|
||||
}
|
||||
throttleMap[config.url] = setTimeout(() => {
|
||||
throttleMap[config.url] = null
|
||||
}, config.throttle)
|
||||
}
|
||||
if (!config.withoutToken && getToken()) {
|
||||
config.headers["Authorization"] = getToken()
|
||||
}
|
||||
//BUG 9456 去除传参空格
|
||||
if (config.params) {
|
||||
Object.keys(config.params).map(e => {
|
||||
if (typeof config.params[e] == "string") config.params[e] = config.params[e].trim()
|
||||
})
|
||||
}
|
||||
config = workflow.processAdapter(config)
|
||||
return config
|
||||
}, err => {
|
||||
console.error(err)
|
||||
})
|
||||
instance.interceptors.response.use(res => {
|
||||
if (res && !!res.config.workflow) {
|
||||
const {config: {workflow: app}, data: {data: bid}} = res
|
||||
bid && workflow.startProcess({app, bid, flows: getStore().workflow})
|
||||
}
|
||||
if (res && res.data) {
|
||||
if (!!res.data.code?.toString()) {
|
||||
if (res.data.code == 0) {
|
||||
return res.data
|
||||
} else if (!!res.config.pureBack) {
|
||||
return res.data
|
||||
} else if (res.data.code == 401) {
|
||||
console.error("安全令牌验证无法通过!")
|
||||
return Promise.reject(res.data)
|
||||
} else {
|
||||
Message.error(res.data.msg || "请求失败!")
|
||||
return !!res.config.returnError ? res.data : Promise.reject(res.data.msg)
|
||||
}
|
||||
} else return res.data
|
||||
} else {
|
||||
Message.error("服务器异常,请联系管理员!")
|
||||
}
|
||||
}, err => {
|
||||
if (err) {
|
||||
if (err.response) {
|
||||
if (err.response.status == 401) {
|
||||
console.error("安全令牌验证无法通过!")
|
||||
} else {
|
||||
console.error(err.response.statusText)
|
||||
}
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
} else {
|
||||
console.error("通信异常,请联系管理员!")
|
||||
}
|
||||
})
|
||||
|
||||
export default instance
|
||||
211
ui/meta/js/utils.js
Normal file
211
ui/meta/js/utils.js
Normal file
@@ -0,0 +1,211 @@
|
||||
import {MessageBox} from 'element-ui'
|
||||
import $moment from './moment'
|
||||
import $dict from './dict'
|
||||
import $encryption from './encryption'
|
||||
import $coin from './coin'
|
||||
import Area from "./area"
|
||||
import ID from "./identity"
|
||||
|
||||
/**
|
||||
* 生成子节点的递归方法
|
||||
* @param parent 父元素
|
||||
* @param pending 待递归的数组
|
||||
* @param config 配置
|
||||
*/
|
||||
const addChild = (parent, pending, config) => {
|
||||
let conf = {
|
||||
key: 'id',
|
||||
parent: 'parentId',
|
||||
children: 'children',
|
||||
...config
|
||||
},
|
||||
doBeforeCount = pending.length
|
||||
for (let i = pending.length - 1; i >= 0; i--) {
|
||||
let e = pending[i]
|
||||
if (e[conf.parent] == parent[conf.key]) {
|
||||
parent[conf.children] = [...(parent[conf.children] || []), e]
|
||||
pending.splice(i, 1)
|
||||
}
|
||||
}
|
||||
parent[conf.children] &&
|
||||
(parent[conf.children] = parent[conf.children].reverse())
|
||||
if (pending.length > 0 && doBeforeCount > pending.length) {
|
||||
parent[conf.children].map(c => addChild(c, pending, conf))
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 数组转tree
|
||||
* @param list 待转化的数组
|
||||
* @param config 配置
|
||||
*/
|
||||
const $arr2tree = (list, config = {}) => {
|
||||
const {key = 'id', parent = 'parentId', children = 'children'} = config, result = [], itemMap = {}, ids = list?.map(e => `${e[key]}`)?.toString()
|
||||
for (const e of list) {
|
||||
const id = e[key], pid = e[parent]
|
||||
itemMap[id] = {...e, [children]: [itemMap[id]?.[children]].flat().filter(Boolean)}
|
||||
const treeItem = itemMap[id]
|
||||
if (!!pid && ids.indexOf(pid) > -1) {
|
||||
if (!itemMap[pid]) {
|
||||
itemMap[pid] = {
|
||||
children: [],
|
||||
}
|
||||
}
|
||||
itemMap[pid].children.push(treeItem)
|
||||
} else result.push(treeItem)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装提示框
|
||||
*/
|
||||
const $confirm = (content, options) => {
|
||||
return MessageBox.confirm(content, {
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认',
|
||||
center: true,
|
||||
title: '提示',
|
||||
dangerouslyUseHTMLString: true,
|
||||
customClass: "AiConfirm",
|
||||
...options
|
||||
}).catch(() => 0)
|
||||
}
|
||||
|
||||
|
||||
const $decimalCalc = (...arr) => {
|
||||
// 确认提升精度
|
||||
let decimalLengthes = arr.map(e => {
|
||||
let index = ('' + e).indexOf('.')
|
||||
return ('' + e).length - index
|
||||
})
|
||||
let maxDecimal = Math.max(...decimalLengthes),
|
||||
precision = Math.pow(10, maxDecimal)
|
||||
// 计算
|
||||
let intArr = arr.map(e => (Number(e) || 0) * precision)
|
||||
// 返回计算值
|
||||
return intArr.reduce((t, a) => t + a) / precision
|
||||
}
|
||||
|
||||
/**
|
||||
* 封装权限判断方法
|
||||
*/
|
||||
const $permissions = flag => {
|
||||
let buttons = []
|
||||
if (localStorage.getItem('vuex')) {
|
||||
const vuex = JSON.parse(localStorage.getItem('vuex'))
|
||||
buttons = vuex.user.info.buttons
|
||||
}
|
||||
if (buttons && buttons.length > 0) {
|
||||
return buttons.some(b => b.id == flag || b.permission == flag)
|
||||
} else return false
|
||||
}
|
||||
|
||||
const $colorUtils = {
|
||||
Hex2RGBA(color, alpha = 1) {
|
||||
let hex = 0
|
||||
if (color.charAt(0) == '#') {
|
||||
if (color.length == 4) {
|
||||
// 检测诸如#FFF简写格式
|
||||
color =
|
||||
'#' +
|
||||
color.charAt(1).repeat(2) +
|
||||
color.charAt(2).repeat(2) +
|
||||
color.charAt(3).repeat(2)
|
||||
}
|
||||
hex = parseInt(color.slice(1), 16)
|
||||
}
|
||||
let r = (hex >> 16) & 0xff
|
||||
let g = (hex >> 8) & 0xff
|
||||
let b = hex & 0xff
|
||||
return `rgba(${r},${g},${b},${alpha})`
|
||||
},
|
||||
RGBtoHex(r, g, b) {
|
||||
let hex = (r << 16) | (g << 8) | b
|
||||
return '#' + hex.toString(16)
|
||||
}
|
||||
}
|
||||
export const $copy = any => {
|
||||
if (any) {
|
||||
return JSON.parse(JSON.stringify(any))
|
||||
} else return any
|
||||
}
|
||||
let debounceWait = null
|
||||
export const $debounce = (fn, wait) => {
|
||||
if (debounceWait !== null) clearTimeout(debounceWait);
|
||||
debounceWait = setTimeout(function () {
|
||||
typeof fn === 'function' && fn();
|
||||
}, wait);
|
||||
}
|
||||
export const $checkJson = str => {
|
||||
if (typeof str == 'string') {
|
||||
try {
|
||||
let obj = JSON.parse(str);
|
||||
return !!(typeof obj == 'object' && obj);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const $load = (sdk, interval = 200, name = "", c = 0) => {
|
||||
if (!!sdk) {
|
||||
return Promise.resolve()
|
||||
} else if (c < 10) {
|
||||
return new Promise(resolve => setTimeout(() => resolve($load(sdk, interval, name, ++c)), interval))
|
||||
} else return Promise.reject("无法加载" + name)
|
||||
}
|
||||
|
||||
export {Area, ID}
|
||||
|
||||
export default {
|
||||
addChild,
|
||||
$confirm,
|
||||
$decimalCalc,
|
||||
$dict,
|
||||
$permissions,
|
||||
$colorUtils,
|
||||
$moment,
|
||||
$encryption,
|
||||
$coin,
|
||||
$injectLib: (url, cb = () => 0) => {
|
||||
const scriptList = document.body.querySelectorAll('script')
|
||||
if (Object.values(scriptList || {}).findIndex(e => e.src == url) == -1) {
|
||||
const script = document.createElement('script')
|
||||
script.type = 'text/javascript'
|
||||
script.src = url
|
||||
script.addEventListener('load', () => cb())
|
||||
document.body.appendChild(script)
|
||||
} else cb()
|
||||
},
|
||||
$injectCss: (url, cb = () => 0) => {
|
||||
const linkList = document.body.querySelectorAll('link')
|
||||
if (Object.values(linkList || {}).findIndex(e => e.href == url) == -1) {
|
||||
const link = document.createElement('link')
|
||||
link.rel = "stylesheet"
|
||||
link.type = "text/css"
|
||||
link.href = url
|
||||
link.addEventListener('load', () => cb())
|
||||
document.head.appendChild(link)
|
||||
} else cb()
|
||||
},
|
||||
$dateFormat: (time, format) => {
|
||||
return $moment(time)
|
||||
.format(format || 'YYYY-MM-DD')
|
||||
.replace('Invalid Date', '')
|
||||
},
|
||||
$copy,
|
||||
$download: url => {
|
||||
fetch(url).then(res => res.blob()).then(blob => {
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.href = URL.createObjectURL(blob)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
})
|
||||
},
|
||||
$debounce,
|
||||
$checkJson,
|
||||
$arr2tree,
|
||||
$load
|
||||
}
|
||||
522
ui/meta/styles/ckeditor.scss
Normal file
522
ui/meta/styles/ckeditor.scss
Normal file
@@ -0,0 +1,522 @@
|
||||
/*
|
||||
* CKEditor 5 (v34.2.0) content styles.
|
||||
* Generated on Fri, 29 Jul 2022 13:03:52 GMT.
|
||||
* For more information, check out https://ckeditor.com/docs/ckeditor5/latest/installation/advanced/content-styles.html
|
||||
*/
|
||||
|
||||
:root {
|
||||
--ck-color-base-active: hsl(208, 88%, 52%);
|
||||
--ck-color-image-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-image-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-color-mention-background: hsla(341, 100%, 30%, 0.1);
|
||||
--ck-color-mention-text: hsl(341, 100%, 30%);
|
||||
--ck-color-table-caption-background: hsl(0, 0%, 97%);
|
||||
--ck-color-table-caption-text: hsl(0, 0%, 20%);
|
||||
--ck-color-table-column-resizer-hover: var(--ck-color-base-active);
|
||||
--ck-highlight-marker-blue: hsl(201, 97%, 72%);
|
||||
--ck-highlight-marker-green: hsl(120, 93%, 68%);
|
||||
--ck-highlight-marker-pink: hsl(345, 96%, 73%);
|
||||
--ck-highlight-marker-yellow: hsl(60, 97%, 73%);
|
||||
--ck-highlight-pen-green: hsl(112, 100%, 27%);
|
||||
--ck-highlight-pen-red: hsl(0, 85%, 49%);
|
||||
--ck-image-style-spacing: 1.5em;
|
||||
--ck-inline-image-style-spacing: calc(var(--ck-image-style-spacing) / 2);
|
||||
--ck-table-column-resizer-position-offset: calc(var(--ck-table-column-resizer-width) * -0.5 - 0.5px);
|
||||
--ck-table-column-resizer-width: 7px;
|
||||
--ck-todo-list-checkmark-size: 16px;
|
||||
--ck-z-default: 1;
|
||||
}
|
||||
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content blockquote {
|
||||
overflow: hidden;
|
||||
padding-right: 1.5em;
|
||||
padding-left: 1.5em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-style: italic;
|
||||
border-left: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
|
||||
/* ckeditor5-block-quote/theme/blockquote.css */
|
||||
.ck-content[dir="rtl"] blockquote {
|
||||
border-left: 0;
|
||||
border-right: solid 5px hsl(0, 0%, 80%);
|
||||
}
|
||||
|
||||
/* ckeditor5-basic-styles/theme/code.css */
|
||||
.ck-content code {
|
||||
background-color: hsla(0, 0%, 78%, 0.3);
|
||||
padding: .15em;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-tiny {
|
||||
font-size: .7em;
|
||||
}
|
||||
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-small {
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-big {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
/* ckeditor5-font/theme/fontsize.css */
|
||||
.ck-content .text-huge {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-yellow {
|
||||
background-color: var(--ck-highlight-marker-yellow);
|
||||
}
|
||||
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-green {
|
||||
background-color: var(--ck-highlight-marker-green);
|
||||
}
|
||||
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-pink {
|
||||
background-color: var(--ck-highlight-marker-pink);
|
||||
}
|
||||
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .marker-blue {
|
||||
background-color: var(--ck-highlight-marker-blue);
|
||||
}
|
||||
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-red {
|
||||
color: var(--ck-highlight-pen-red);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ckeditor5-highlight/theme/highlight.css */
|
||||
.ck-content .pen-green {
|
||||
color: var(--ck-highlight-pen-green);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image {
|
||||
display: table;
|
||||
clear: both;
|
||||
text-align: center;
|
||||
margin: 0.9em auto;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image img {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline {
|
||||
/*
|
||||
* Normally, the .image-inline would have "display: inline-block" and "img { width: 100% }" (to follow the wrapper while resizing).;
|
||||
* Unfortunately, together with "srcset", it gets automatically stretched up to the width of the editing root.
|
||||
* This strange behavior does not happen with inline-flex.
|
||||
*/
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline picture {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/image.css */
|
||||
.ck-content .image-inline picture,
|
||||
.ck-content .image-inline img {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left,
|
||||
.ck-content .image-style-block-align-right {
|
||||
max-width: calc(100% - var(--ck-image-style-spacing));
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left,
|
||||
.ck-content .image-style-align-right {
|
||||
clear: none;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-side {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-left {
|
||||
float: left;
|
||||
margin-right: var(--ck-image-style-spacing);
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-align-right {
|
||||
float: right;
|
||||
margin-left: var(--ck-image-style-spacing);
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-right {
|
||||
margin-right: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-style-block-align-left {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content p + .image-style-align-left,
|
||||
.ck-content p + .image-style-align-right,
|
||||
.ck-content p + .image-style-side {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-left,
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-top: var(--ck-inline-image-style-spacing);
|
||||
margin-bottom: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-left {
|
||||
margin-right: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagestyle.css */
|
||||
.ck-content .image-inline.image-style-align-right {
|
||||
margin-left: var(--ck-inline-image-style-spacing);
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imagecaption.css */
|
||||
.ck-content .image > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: bottom;
|
||||
word-break: break-word;
|
||||
color: var(--ck-color-image-caption-text);
|
||||
background-color: var(--ck-color-image-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ckeditor5-image/theme/imageresize.css */
|
||||
.ck-content .image.image_resized > figcaption {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ckeditor5-language/theme/language.css */
|
||||
.ck-content span[lang] {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list li .todo-list {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input {
|
||||
-webkit-appearance: none;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: var(--ck-todo-list-checkmark-size);
|
||||
height: var(--ck-todo-list-checkmark-size);
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
left: -25px;
|
||||
margin-right: -15px;
|
||||
right: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input::before {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid hsl(0, 0%, 20%);
|
||||
border-radius: 2px;
|
||||
transition: 250ms ease-in-out box-shadow, 250ms ease-in-out background, 250ms ease-in-out border;
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input::after {
|
||||
display: block;
|
||||
position: absolute;
|
||||
box-sizing: content-box;
|
||||
pointer-events: none;
|
||||
content: '';
|
||||
left: calc(var(--ck-todo-list-checkmark-size) / 3);
|
||||
top: calc(var(--ck-todo-list-checkmark-size) / 5.3);
|
||||
width: calc(var(--ck-todo-list-checkmark-size) / 5.3);
|
||||
height: calc(var(--ck-todo-list-checkmark-size) / 2.6);
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-width: 0 calc(var(--ck-todo-list-checkmark-size) / 8) calc(var(--ck-todo-list-checkmark-size) / 8) 0;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input[checked]::before {
|
||||
background: hsl(126, 64%, 41%);
|
||||
border-color: hsl(126, 64%, 41%);
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label > input[checked]::after {
|
||||
border-color: hsl(0, 0%, 100%);
|
||||
}
|
||||
|
||||
/* ckeditor5-list/theme/todolist.css */
|
||||
.ck-content .todo-list .todo-list__label .todo-list__label__description {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* ckeditor5-media-embed/theme/mediaembed.css */
|
||||
.ck-content .media {
|
||||
clear: both;
|
||||
margin: 0.9em 0;
|
||||
display: block;
|
||||
min-width: 15em;
|
||||
}
|
||||
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
position: relative;
|
||||
clear: both;
|
||||
padding: 5px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-bottom: 2px dashed hsl(0, 0%, 77%);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break__label {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: .3em .6em;
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
font-family: Helvetica, Arial, Tahoma, Verdana, Sans-Serif;
|
||||
font-size: 0.75em;
|
||||
font-weight: bold;
|
||||
color: hsl(0, 0%, 20%);
|
||||
background: hsl(0, 0%, 100%);
|
||||
box-shadow: 2px 2px 1px hsla(0, 0%, 0%, 0.15);
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecaption.css */
|
||||
.ck-content .table > figcaption {
|
||||
display: table-caption;
|
||||
caption-side: top;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
color: var(--ck-color-table-caption-text);
|
||||
background-color: var(--ck-color-table-caption-background);
|
||||
padding: .6em;
|
||||
font-size: .75em;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table {
|
||||
margin: 0.9em auto;
|
||||
display: table;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px double hsl(0, 0%, 70%);
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table td,
|
||||
.ck-content .table table th {
|
||||
min-width: 2em;
|
||||
padding: .4em;
|
||||
border: 1px solid hsl(0, 0%, 75%);
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content .table table th {
|
||||
font-weight: bold;
|
||||
background: hsla(0, 0%, 0%, 5%);
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="rtl"] .table th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/table.css */
|
||||
.ck-content[dir="ltr"] .table th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table table {
|
||||
overflow: hidden;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table td,
|
||||
.ck-content .table th {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table .table-column-resizer {
|
||||
position: absolute;
|
||||
top: -999999px;
|
||||
bottom: -999999px;
|
||||
right: var(--ck-table-column-resizer-position-offset);
|
||||
width: var(--ck-table-column-resizer-width);
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
z-index: var(--ck-z-default);
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table[draggable] .table-column-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content .table .table-column-resizer:hover,
|
||||
.ck-content .table .table-column-resizer__active {
|
||||
background-color: var(--ck-color-table-column-resizer-hover);
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content[dir=rtl] .table .table-column-resizer {
|
||||
left: var(--ck-table-column-resizer-position-offset);
|
||||
right: unset;
|
||||
}
|
||||
|
||||
/* ckeditor5-table/theme/tablecolumnresize.css */
|
||||
.ck-content.ck-read-only .table .table-column-resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre {
|
||||
padding: 1em;
|
||||
color: hsl(0, 0%, 20.8%);
|
||||
background: hsla(0, 0%, 78%, 0.3);
|
||||
border: 1px solid hsl(0, 0%, 77%);
|
||||
border-radius: 2px;
|
||||
text-align: left;
|
||||
direction: ltr;
|
||||
tab-size: 4;
|
||||
white-space: pre-wrap;
|
||||
font-style: normal;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* ckeditor5-code-block/theme/codeblock.css */
|
||||
.ck-content pre code {
|
||||
background: unset;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* ckeditor5-horizontal-line/theme/horizontalline.css */
|
||||
.ck-content hr {
|
||||
margin: 15px 0;
|
||||
height: 4px;
|
||||
background: hsl(0, 0%, 87%);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* ckeditor5-mention/theme/mention.css */
|
||||
.ck-content .mention {
|
||||
background: var(--ck-color-mention-background);
|
||||
color: var(--ck-color-mention-text);
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break {
|
||||
padding: 0;
|
||||
}
|
||||
/* ckeditor5-page-break/theme/pagebreak.css */
|
||||
.ck-content .page-break::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
673
ui/meta/styles/common.scss
Normal file
673
ui/meta/styles/common.scss
Normal file
@@ -0,0 +1,673 @@
|
||||
@import "iconfont/iconfont";
|
||||
@import 'iconfont/logofont';
|
||||
@import "ckeditor";
|
||||
@import "vars";
|
||||
|
||||
$cdn: "https://cdn.cunwuyun.cn/";
|
||||
$--color-primary: $primaryColor;
|
||||
$--color-text-placeholder: $placeholderColor;
|
||||
$--border-color-base: $borderColor;
|
||||
$--color-success: $successColor;
|
||||
$--color-warning: $warnColor;
|
||||
$--color-danger: $errorColor;
|
||||
$--color-info: $infoColor;
|
||||
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||
@import "~element-ui/packages/theme-chalk/src/index";
|
||||
|
||||
/**
|
||||
常用内外边距样式
|
||||
*/
|
||||
@each $padMar, $pm in (mar:margin, pad:padding) {
|
||||
@each $pos, $p in (l:left, r:right, t:top, b:bottom) {
|
||||
@each $v in (8, 10, 16, 20, 32, 48, 60) {
|
||||
.#{$padMar}-#{$pos+$v} {
|
||||
#{$pm}-#{$p}: #{$v}px
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
不换行文本
|
||||
*/
|
||||
.nowarp-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/**
|
||||
表头式样
|
||||
*/
|
||||
.table-header {
|
||||
background-color: rgba(243, 246, 249, 1) !important;
|
||||
color: #333;
|
||||
line-height: 44px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 0 !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
表行样式
|
||||
*/
|
||||
.table-row {
|
||||
height: 44px;
|
||||
|
||||
&:hover, &:hover td.table-cell {
|
||||
background-color: #EFF6FF !important;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
表格样式
|
||||
*/
|
||||
.table-cell {
|
||||
font-size: 14px;
|
||||
padding: 0 !important;
|
||||
|
||||
.cell {
|
||||
line-height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
图标统一样式
|
||||
*/
|
||||
.iconfont {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.iconShow:hover, .iconEdit:hover, .iconParent:hover, .iconChange:hover {
|
||||
color: $primaryColor;
|
||||
}
|
||||
|
||||
.iconDelete:hover {
|
||||
color: $errorColor;
|
||||
}
|
||||
|
||||
.iconBack_Large {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: $primaryColor;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
缺省页相关样式
|
||||
*/
|
||||
.no-data {
|
||||
background: url("https://cdn.cunwuyun.cn/ui/svg/NoData.svg") no-repeat center;
|
||||
background-size: 120px 120px;
|
||||
height: 120px;
|
||||
margin: 48px auto 10px;
|
||||
}
|
||||
|
||||
/**
|
||||
缺省页相关样式
|
||||
*/
|
||||
.ai-empty__bg {
|
||||
background: url("https://cdn.cunwuyun.cn/ui/svg/empty.svg") no-repeat center;
|
||||
background-size: 120px 120px;
|
||||
height: 120px;
|
||||
margin: 48px auto 0;
|
||||
}
|
||||
|
||||
.no-permission {
|
||||
background: url("https://cdn.cunwuyun.cn/ui/svg/NoAuthority.svg") no-repeat center;
|
||||
background-size: 120px 120px;
|
||||
height: 120px;
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.no-message {
|
||||
background: url("https://cdn.cunwuyun.cn/ui/svg/NoMessage.svg") no-repeat center;
|
||||
background-size: 120px 120px;
|
||||
height: 120px;
|
||||
margin-top: 48px;
|
||||
}
|
||||
|
||||
.done-success {
|
||||
background: url("https://cdn.cunwuyun.cn/ui/svg/Success.svg") no-repeat center;
|
||||
background-size: 120px 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.no-div-text {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #666
|
||||
}
|
||||
|
||||
.developing {
|
||||
background: url("https://cdn.cunwuyun.cn/ui/svg/developing.svg") no-repeat center;
|
||||
background-size: 400px 320px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:after {
|
||||
margin-top: 270px;
|
||||
font-size: 14px;
|
||||
color: $primaryColor;
|
||||
display: block;
|
||||
content: "功能正在开发中..."
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
placeholder 样式
|
||||
*/
|
||||
input::-webkit-input-placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input::-moz-placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
input::-ms-input-placeholder {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
//切换地区Tab 样式
|
||||
.area-popover {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
//弹窗树菜单的样式
|
||||
.dialog-tree {
|
||||
&.el-tree--highlight-current .el-tree-node.is-current > .el-tree-node__content {
|
||||
background: rgba(239, 246, 255, 1);
|
||||
border-radius: 2px;
|
||||
font-weight: normal;
|
||||
color: rgba(80, 136, 255, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//发起群聊的按钮样式
|
||||
.openIM {
|
||||
width: 26px;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(208, 212, 220, 1);
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&.iconGroup_IM {
|
||||
color: #89B;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $primaryColor;
|
||||
border-color: $primaryColor;
|
||||
|
||||
&.iconGroup_IM {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fuzzy {
|
||||
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
color: transparent !important;
|
||||
}
|
||||
|
||||
/**
|
||||
马赛克样式
|
||||
*/
|
||||
.mosaic {
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-position: 0 0, 10px 10px;
|
||||
background-size: 20px 20px;
|
||||
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee 100%), linear-gradient(45deg, #eee 25%, white 25%, white 75%, #eee 75%, #eee 100%);
|
||||
z-index: 202011051009;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
自定义弹性盒子快速用
|
||||
*/
|
||||
div[flex] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&.wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&.gap {
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.fill {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
|
||||
&::-webkit-outer-spin-button, &::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2.0公共样式
|
||||
|
||||
.ai-form {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
.el-form-item {
|
||||
width: 50%;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.el-form-item__content {
|
||||
margin-left: 134px !important;
|
||||
}
|
||||
|
||||
::v-deep.el-form-item__content {
|
||||
& .el-input--small, & > .el-range-editor--small, & > .el-cascader, & > .el-select {
|
||||
width: 325px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
width: 520px;
|
||||
padding-bottom: 0;
|
||||
|
||||
.el-message-box__header {
|
||||
padding-left: 40px;
|
||||
}
|
||||
|
||||
.el-message-box__content {
|
||||
padding-left: 77px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.el-message-box__title {
|
||||
justify-content: start;
|
||||
font-size: 14px;
|
||||
|
||||
.el-icon-error:before {
|
||||
content: "\e7a3";
|
||||
}
|
||||
}
|
||||
|
||||
.el-message-box__status {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.el-message-box__message {
|
||||
text-align: start;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.el-message-box__btns {
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
background-color: #F3F6F9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 64px;
|
||||
|
||||
.el-button {
|
||||
padding: 0 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-options {
|
||||
.el-button--text {
|
||||
height: auto;
|
||||
margin-right: 8px !important;
|
||||
margin-left: 0 !important;
|
||||
padding: 0;
|
||||
border-radius: unset;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin-right: 8px;
|
||||
color: $primaryColor;
|
||||
font-size: 14px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
.is-disabled span {
|
||||
color: #999 !important;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
border: none;
|
||||
background: $primaryBtnColor;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
background: $primaryBtnColor;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
font-size: 14px;
|
||||
border-radius: 2px;
|
||||
height: 32px;
|
||||
padding: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
[class*=iconfont] {
|
||||
font-size: 14px;
|
||||
|
||||
& + span {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-button--text [class*=iconfont] {
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.wechat-message__container {
|
||||
padding: 8px !important;
|
||||
background: #F6F9FF !important;
|
||||
border-radius: 2px !important;
|
||||
color: #222222 !important;
|
||||
font-size: 14px !important;
|
||||
border: 1px solid $primaryColor !important;
|
||||
|
||||
h2 {
|
||||
color: #222222;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
h1, h2, h3, p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.el-tooltip__popper {
|
||||
max-width: 600px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
|
||||
.ai-personselect .el-input__suffix .el-input__validateIcon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.AiWechatSelecter-container .el-input__suffix .el-input__validateIcon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.AiWechatSelecter-container .el-input__suffix .el-input__validateIcon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-color89B {
|
||||
color: #89B;
|
||||
}
|
||||
|
||||
// flex 布局
|
||||
.flex-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-start {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ailist-wrapper {
|
||||
div, p, h2, h1, h3, h5, span {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
.mt10 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ai-scan {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -14px;
|
||||
cursor: pointer;
|
||||
|
||||
.poptip-arrow {
|
||||
position: relative;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
margin-right: 8px;
|
||||
margin-top: 4px;
|
||||
padding: 0 10px;
|
||||
color: $primaryColor;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
background: $primaryLightColor;
|
||||
border: 1px solid $primaryColor;
|
||||
|
||||
em {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: hsla(0, 0%, 100%, 0);
|
||||
border-style: solid;
|
||||
overflow: hidden;
|
||||
top: 50%;
|
||||
right: -6px;
|
||||
transform: translateY(-50%);
|
||||
border-left-color: $primaryLightColor;
|
||||
border-width: 6px 0 6px 6px;
|
||||
}
|
||||
|
||||
a {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: hsla(0, 0%, 100%, 0);
|
||||
border-style: solid;
|
||||
overflow: hidden;
|
||||
top: 50%;
|
||||
right: -7px;
|
||||
transform: translateY(-50%);
|
||||
border-left-color: $primaryColor;
|
||||
border-width: 6px 0 6px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
& > i {
|
||||
padding: 0;
|
||||
color: $primaryColor;
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
背景图设置
|
||||
*/
|
||||
|
||||
.signLeftBg {
|
||||
background-image: url('#{$cdn}/ui/background/#{$theme}/loginLeft.png');
|
||||
}
|
||||
|
||||
.navBg {
|
||||
background-image: url('#{$cdn}/ui/background/#{$theme}/nav_bg.png');
|
||||
}
|
||||
|
||||
/**
|
||||
特殊样式字体
|
||||
*/
|
||||
.projectName {
|
||||
font-family: "Microsoft YaHei UI", "Microsoft YaHei", serif;
|
||||
height: 56px;
|
||||
line-height: 56px;
|
||||
font-size: 42px;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
text-shadow: 0 6px 3px rgba(#222, .1);
|
||||
color: transparent;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
content: attr(title);
|
||||
background: $projectName;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
content: attr(title);
|
||||
text-shadow: 3px 0 0 #fff, -3px 0 0 #fff, 0 -3px 0 #fff, 0 3px 0 #fff;
|
||||
z-index: 66;
|
||||
}
|
||||
}
|
||||
|
||||
.textShadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
text-shadow: 0 2px 0 $textShadow;
|
||||
color: transparent;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
#ai-waiting {
|
||||
color: $primaryColor
|
||||
}
|
||||
|
||||
.color-primary {
|
||||
color: $primaryColor
|
||||
}
|
||||
|
||||
.hoverActive {
|
||||
&:hover, &.current, &.isActive {
|
||||
color: $primaryColor !important;
|
||||
}
|
||||
}
|
||||
|
||||
/** 登录页左侧标题样式 **/
|
||||
.signLeftContent {
|
||||
.titlePane {
|
||||
margin-top: 124px;
|
||||
font-size: 20px;
|
||||
margin-bottom: 64px;
|
||||
|
||||
& > b {
|
||||
display: block;
|
||||
font-size: 40px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&:before {
|
||||
content: " ";
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #fff;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.AiConfirm {
|
||||
position: fixed;
|
||||
z-index: 202210261023;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/**
|
||||
节流器
|
||||
*/
|
||||
.throttle {
|
||||
animation: throttle 0.5s step-end forwards;
|
||||
}
|
||||
|
||||
.throttle:active {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
@keyframes throttle {
|
||||
from {
|
||||
pointer-events: none;
|
||||
}
|
||||
to {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
958
ui/meta/styles/iconfont/iconfont.css
Normal file
958
ui/meta/styles/iconfont/iconfont.css
Normal file
@@ -0,0 +1,958 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 1309749 */
|
||||
src: url('//at.alicdn.com/t/c/font_1309749_t7bf1dhlp7e.woff2?t=1666749875458') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_1309749_t7bf1dhlp7e.woff?t=1666749875458') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_1309749_t7bf1dhlp7e.ttf?t=1666749875458') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconpingfenkaohe:before {
|
||||
content: "\e736";
|
||||
}
|
||||
|
||||
.iconcloss:before {
|
||||
content: "\e735";
|
||||
}
|
||||
|
||||
.iconzhengcefuwu:before {
|
||||
content: "\e731";
|
||||
}
|
||||
|
||||
.iconpeixunguanli:before {
|
||||
content: "\e730";
|
||||
}
|
||||
|
||||
.iconxiaoxizhongxin:before {
|
||||
content: "\e72f";
|
||||
}
|
||||
|
||||
.iconzhaopinhuiguanli:before {
|
||||
content: "\e72e";
|
||||
}
|
||||
|
||||
.iconshuzixiangcun:before {
|
||||
content: "\e72d";
|
||||
}
|
||||
|
||||
.iconteshurenqun:before {
|
||||
content: "\e72c";
|
||||
}
|
||||
|
||||
.iconzhengminhudong:before {
|
||||
content: "\e72b";
|
||||
}
|
||||
|
||||
.iconjicengzhili:before {
|
||||
content: "\e72a";
|
||||
}
|
||||
|
||||
.iconxiaochengxuguanli:before {
|
||||
content: "\e729";
|
||||
}
|
||||
|
||||
.iconyingjizhihui:before {
|
||||
content: "\e728";
|
||||
}
|
||||
|
||||
.iconjicengdangjian:before {
|
||||
content: "\e727";
|
||||
}
|
||||
|
||||
.iconxiangcunchanye:before {
|
||||
content: "\e726";
|
||||
}
|
||||
|
||||
.iconfangfanpin:before {
|
||||
content: "\e725";
|
||||
}
|
||||
|
||||
.iconnumber:before {
|
||||
content: "\e724";
|
||||
}
|
||||
|
||||
.iconattachments:before {
|
||||
content: "\e719";
|
||||
}
|
||||
|
||||
.icondate:before {
|
||||
content: "\e71a";
|
||||
}
|
||||
|
||||
.iconcard:before {
|
||||
content: "\e71b";
|
||||
}
|
||||
|
||||
.iconid:before {
|
||||
content: "\e71c";
|
||||
}
|
||||
|
||||
.iconcascade:before {
|
||||
content: "\e71d";
|
||||
}
|
||||
|
||||
.iconlocation:before {
|
||||
content: "\e71e";
|
||||
}
|
||||
|
||||
.iconname:before {
|
||||
content: "\e71f";
|
||||
}
|
||||
|
||||
.iconrich_text:before {
|
||||
content: "\e720";
|
||||
}
|
||||
|
||||
.iconphone_number:before {
|
||||
content: "\e721";
|
||||
}
|
||||
|
||||
.iconswitch:before {
|
||||
content: "\e722";
|
||||
}
|
||||
|
||||
.icontime:before {
|
||||
content: "\e723";
|
||||
}
|
||||
|
||||
.icondianliang:before {
|
||||
content: "\e711";
|
||||
}
|
||||
|
||||
.iconxueya:before {
|
||||
content: "\e714";
|
||||
}
|
||||
|
||||
.iconzhuangtai:before {
|
||||
content: "\e715";
|
||||
}
|
||||
|
||||
.iconxinlv:before {
|
||||
content: "\e716";
|
||||
}
|
||||
|
||||
.icontiwen:before {
|
||||
content: "\e717";
|
||||
}
|
||||
|
||||
.iconxueyang:before {
|
||||
content: "\e718";
|
||||
}
|
||||
|
||||
.iconzongzhizuzhi:before {
|
||||
content: "\e710";
|
||||
}
|
||||
|
||||
.iconvideolink:before {
|
||||
content: "\e70f";
|
||||
}
|
||||
|
||||
.iconfacial_recognition:before {
|
||||
content: "\e70e";
|
||||
}
|
||||
|
||||
.iconunpublish:before {
|
||||
content: "\e70c";
|
||||
}
|
||||
|
||||
.iconpreview:before {
|
||||
content: "\e708";
|
||||
}
|
||||
|
||||
.iconpublish:before {
|
||||
content: "\e709";
|
||||
}
|
||||
|
||||
.iconmore:before {
|
||||
content: "\e70a";
|
||||
}
|
||||
|
||||
.iconshare:before {
|
||||
content: "\e70b";
|
||||
}
|
||||
|
||||
.iconcheck_box:before {
|
||||
content: "\e700";
|
||||
}
|
||||
|
||||
.iconline:before {
|
||||
content: "\e701";
|
||||
}
|
||||
|
||||
.iconradio:before {
|
||||
content: "\e702";
|
||||
}
|
||||
|
||||
.icontext_box:before {
|
||||
content: "\e703";
|
||||
}
|
||||
|
||||
.icontext_area:before {
|
||||
content: "\e704";
|
||||
}
|
||||
|
||||
.iconpage:before {
|
||||
content: "\e705";
|
||||
}
|
||||
|
||||
.iconpic:before {
|
||||
content: "\e706";
|
||||
}
|
||||
|
||||
.iconSelect:before {
|
||||
content: "\e707";
|
||||
}
|
||||
|
||||
.iconhuluhuxian:before {
|
||||
content: "\e6f6";
|
||||
}
|
||||
|
||||
.iconkaoheguanli:before {
|
||||
content: "\e6f9";
|
||||
}
|
||||
|
||||
.iconhujiaozhongxin:before {
|
||||
content: "\e6fa";
|
||||
}
|
||||
|
||||
.iconliangxinzuzhi:before {
|
||||
content: "\e6fb";
|
||||
}
|
||||
|
||||
.iconxiaoyuananfang:before {
|
||||
content: "\e6fc";
|
||||
}
|
||||
|
||||
.iconzhongdianqingshaonian:before {
|
||||
content: "\e6fd";
|
||||
}
|
||||
|
||||
.iconanquanshengchan:before {
|
||||
content: "\e6fe";
|
||||
}
|
||||
|
||||
.iconshehuizhian:before {
|
||||
content: "\e6ff";
|
||||
}
|
||||
|
||||
.iconguangbofabu:before {
|
||||
content: "\e6f4";
|
||||
}
|
||||
|
||||
.iconwangshangbanshi:before {
|
||||
content: "\e6f3";
|
||||
}
|
||||
|
||||
.iconyunyingzhongxin:before {
|
||||
content: "\e6f1";
|
||||
}
|
||||
|
||||
.iconzhengwuweixin:before {
|
||||
content: "\e6f2";
|
||||
}
|
||||
|
||||
.iconkaoqinguanli:before {
|
||||
content: "\e6eb";
|
||||
}
|
||||
|
||||
.iconshijianguanli:before {
|
||||
content: "\e6ec";
|
||||
}
|
||||
|
||||
.iconyifangzhaoren:before {
|
||||
content: "\e6ed";
|
||||
}
|
||||
|
||||
.iconwanggeguanli:before {
|
||||
content: "\e6ee";
|
||||
}
|
||||
|
||||
.iconxinfangguanli:before {
|
||||
content: "\e6ef";
|
||||
}
|
||||
|
||||
.iconshipinjiankong:before {
|
||||
content: "\e6f0";
|
||||
}
|
||||
|
||||
.iconloudongmoxing:before {
|
||||
content: "\e6ea";
|
||||
}
|
||||
|
||||
.iconwanggeyuan:before {
|
||||
content: "\e6e8";
|
||||
}
|
||||
|
||||
.iconjinqishijian:before {
|
||||
content: "\e6e6";
|
||||
}
|
||||
|
||||
.iconxiaoquzonglan:before {
|
||||
content: "\e6e7";
|
||||
}
|
||||
|
||||
.iconloudongxinxi:before {
|
||||
content: "\e6e9";
|
||||
}
|
||||
|
||||
.iconchuangyebutie:before {
|
||||
content: "\e6db";
|
||||
}
|
||||
|
||||
.icondanweiguanli:before {
|
||||
content: "\e6dc";
|
||||
}
|
||||
|
||||
.iconjiuyefuwu:before {
|
||||
content: "\e6e2";
|
||||
}
|
||||
|
||||
.iconchuangyejiuyeguanli:before {
|
||||
content: "\e6e3";
|
||||
}
|
||||
|
||||
.iconchuangyedanbaodaikuan:before {
|
||||
content: "\e6e4";
|
||||
}
|
||||
|
||||
.icondaxueshengshixishixun:before {
|
||||
content: "\e6e5";
|
||||
}
|
||||
|
||||
.iconsearch:before {
|
||||
content: "\e732";
|
||||
}
|
||||
|
||||
.iconxqhd:before {
|
||||
content: "\e733";
|
||||
}
|
||||
|
||||
.iconwdhd:before {
|
||||
content: "\e734";
|
||||
}
|
||||
|
||||
.iconrobot:before {
|
||||
content: "\e6da";
|
||||
}
|
||||
|
||||
.iconfangda:before {
|
||||
content: "\e6d7";
|
||||
}
|
||||
|
||||
.iconsuoxiao:before {
|
||||
content: "\e6d8";
|
||||
}
|
||||
|
||||
.iconEarth:before {
|
||||
content: "\e6d9";
|
||||
}
|
||||
|
||||
.iconxianfengyeweihui:before {
|
||||
content: "\e6d6";
|
||||
}
|
||||
|
||||
.iconhuiyuanguanli:before {
|
||||
content: "\e6d1";
|
||||
}
|
||||
|
||||
.iconcunganbuguanli:before {
|
||||
content: "\e6d2";
|
||||
}
|
||||
|
||||
.iconzhaopinguanli:before {
|
||||
content: "\e6d3";
|
||||
}
|
||||
|
||||
.iconqiyeguanli:before {
|
||||
content: "\e6d5";
|
||||
}
|
||||
|
||||
.iconxingfujifen:before {
|
||||
content: "\e6ce";
|
||||
}
|
||||
|
||||
.iconxinxizhongxin:before {
|
||||
content: "\e6cf";
|
||||
}
|
||||
|
||||
.iconpinyipin:before {
|
||||
content: "\e6d0";
|
||||
}
|
||||
|
||||
.iconUnpublish:before {
|
||||
content: "\e6cc";
|
||||
}
|
||||
|
||||
.iconPublish:before {
|
||||
content: "\e6cd";
|
||||
}
|
||||
|
||||
.iconDelay:before {
|
||||
content: "\e6cb";
|
||||
}
|
||||
|
||||
.iconwarning:before {
|
||||
content: "\e6f5";
|
||||
}
|
||||
|
||||
.iconzpg:before {
|
||||
content: "\e6f7";
|
||||
}
|
||||
|
||||
.iconsqy:before {
|
||||
content: "\e6f8";
|
||||
}
|
||||
|
||||
.iconzxjyzdls:before {
|
||||
content: "\e6dd";
|
||||
}
|
||||
|
||||
.iconzxjygwgl:before {
|
||||
content: "\e6de";
|
||||
}
|
||||
|
||||
.iconzxjywdzy:before {
|
||||
content: "\e6df";
|
||||
}
|
||||
|
||||
.iconzxjycydb:before {
|
||||
content: "\e6e0";
|
||||
}
|
||||
|
||||
.iconzxjyckmb:before {
|
||||
content: "\e6e1";
|
||||
}
|
||||
|
||||
.iconarea:before {
|
||||
content: "\e6d4";
|
||||
}
|
||||
|
||||
.iconyiqingfangkong:before {
|
||||
content: "\e6ca";
|
||||
}
|
||||
|
||||
.iconPostil:before {
|
||||
content: "\e6c9";
|
||||
}
|
||||
|
||||
.iconjiaonadangfei:before {
|
||||
content: "\e6c8";
|
||||
}
|
||||
|
||||
.iconSuccess:before {
|
||||
content: "\e6c7";
|
||||
}
|
||||
|
||||
.iconAccount_Login:before {
|
||||
content: "\e6c5";
|
||||
}
|
||||
|
||||
.iconQR_code:before {
|
||||
content: "\e6c6";
|
||||
}
|
||||
|
||||
.iconMediaPlayer_Play:before {
|
||||
content: "\e6c3";
|
||||
}
|
||||
|
||||
.iconMediaPlayer_Stop:before {
|
||||
content: "\e6c4";
|
||||
}
|
||||
|
||||
.icondangyuan:before {
|
||||
content: "\e6b0";
|
||||
}
|
||||
|
||||
.iconEnvironment:before {
|
||||
content: "\e6bc";
|
||||
}
|
||||
|
||||
.iconLaw:before {
|
||||
content: "\e6bd";
|
||||
}
|
||||
|
||||
.iconjicengbangong:before {
|
||||
content: "\e6be";
|
||||
}
|
||||
|
||||
.iconjicengzhuzhi:before {
|
||||
content: "\e6bf";
|
||||
}
|
||||
|
||||
.iconwenmingxiangfeng:before {
|
||||
content: "\e6c0";
|
||||
}
|
||||
|
||||
.iconyangguangcunwu:before {
|
||||
content: "\e6c1";
|
||||
}
|
||||
|
||||
.iconminzhuyishi:before {
|
||||
content: "\e6c2";
|
||||
}
|
||||
|
||||
.iconshehuijiuzhu:before {
|
||||
content: "\e6b8";
|
||||
}
|
||||
|
||||
.iconshujugongxiang:before {
|
||||
content: "\e6b9";
|
||||
}
|
||||
|
||||
.iconjuminxinxi:before {
|
||||
content: "\e6ba";
|
||||
}
|
||||
|
||||
.iconxinxiguanli:before {
|
||||
content: "\e6bb";
|
||||
}
|
||||
|
||||
.iconRecommend:before {
|
||||
content: "\e6af";
|
||||
}
|
||||
|
||||
.iconqiandao:before {
|
||||
content: "\e6ad";
|
||||
}
|
||||
|
||||
.iconqingjia:before {
|
||||
content: "\e6ae";
|
||||
}
|
||||
|
||||
.iconhistogram:before {
|
||||
content: "\e6ac";
|
||||
}
|
||||
|
||||
.iconAudit:before {
|
||||
content: "\e6a9";
|
||||
}
|
||||
|
||||
.iconVerify:before {
|
||||
content: "\e6aa";
|
||||
}
|
||||
|
||||
.iconTransaction:before {
|
||||
content: "\e6ab";
|
||||
}
|
||||
|
||||
.iconEwm:before {
|
||||
content: "\e6a6";
|
||||
}
|
||||
|
||||
.iconIOS:before {
|
||||
content: "\e6a7";
|
||||
}
|
||||
|
||||
.iconAndroid:before {
|
||||
content: "\e6a8";
|
||||
}
|
||||
|
||||
.iconjdq_led_clean:before {
|
||||
content: "\e67a";
|
||||
}
|
||||
|
||||
.iconjdq_led_edit:before {
|
||||
content: "\e67b";
|
||||
}
|
||||
|
||||
.iconjdq_led_Led:before {
|
||||
content: "\e680";
|
||||
}
|
||||
|
||||
.iconjdq_led_Led1:before {
|
||||
content: "\e681";
|
||||
}
|
||||
|
||||
.iconjdq_led_Right:before {
|
||||
content: "\e688";
|
||||
}
|
||||
|
||||
.iconjdq_led_Ledjiesu:before {
|
||||
content: "\e689";
|
||||
}
|
||||
|
||||
.iconjdq_led_show:before {
|
||||
content: "\e68a";
|
||||
}
|
||||
|
||||
.iconjdq_led_Ledwx:before {
|
||||
content: "\e68b";
|
||||
}
|
||||
|
||||
.iconjdq_led_Lednrhg:before {
|
||||
content: "\e68c";
|
||||
}
|
||||
|
||||
.iconClean1:before {
|
||||
content: "\e6a4";
|
||||
}
|
||||
|
||||
.iconLoading:before {
|
||||
content: "\e6a5";
|
||||
}
|
||||
|
||||
.iconSubordinates:before {
|
||||
content: "\e6a1";
|
||||
}
|
||||
|
||||
.iconDownload:before {
|
||||
content: "\e6a2";
|
||||
}
|
||||
|
||||
.iconNext_Mission:before {
|
||||
content: "\e6a3";
|
||||
}
|
||||
|
||||
.iconAdmitted:before {
|
||||
content: "\e69c";
|
||||
}
|
||||
|
||||
.iconEmployment_Confirmation:before {
|
||||
content: "\e69d";
|
||||
}
|
||||
|
||||
.iconRepulsebeifen:before {
|
||||
content: "\e69e";
|
||||
}
|
||||
|
||||
.iconUpdate_Files:before {
|
||||
content: "\e69f";
|
||||
}
|
||||
|
||||
.iconRepulse:before {
|
||||
content: "\e6a0";
|
||||
}
|
||||
|
||||
.iconOverrule:before {
|
||||
content: "\e69a";
|
||||
}
|
||||
|
||||
.iconWithdrawn:before {
|
||||
content: "\e69b";
|
||||
}
|
||||
|
||||
.iconDetails:before {
|
||||
content: "\e699";
|
||||
}
|
||||
|
||||
.iconSpecial_Populations:before {
|
||||
content: "\e698";
|
||||
}
|
||||
|
||||
.iconPerson_Transfer:before {
|
||||
content: "\e693";
|
||||
}
|
||||
|
||||
.iconCreate_Files:before {
|
||||
content: "\e694";
|
||||
}
|
||||
|
||||
.iconPrint:before {
|
||||
content: "\e695";
|
||||
}
|
||||
|
||||
.iconPerson_Transfered:before {
|
||||
content: "\e696";
|
||||
}
|
||||
|
||||
.iconReject:before {
|
||||
content: "\e697";
|
||||
}
|
||||
|
||||
.iconGroup_IM:before {
|
||||
content: "\e692";
|
||||
}
|
||||
|
||||
.iconData_Screen:before {
|
||||
content: "\e68f";
|
||||
}
|
||||
|
||||
.iconCustomer_Service:before {
|
||||
content: "\e690";
|
||||
}
|
||||
|
||||
.iconDocumentation:before {
|
||||
content: "\e691";
|
||||
}
|
||||
|
||||
.iconTriangle_Up:before {
|
||||
content: "\e68d";
|
||||
}
|
||||
|
||||
.iconTriangle_Left:before {
|
||||
content: "\e68e";
|
||||
}
|
||||
|
||||
.iconDouble_Up:before {
|
||||
content: "\e686";
|
||||
}
|
||||
|
||||
.iconDouble_Down:before {
|
||||
content: "\e687";
|
||||
}
|
||||
|
||||
.iconServer:before {
|
||||
content: "\e685";
|
||||
}
|
||||
|
||||
.iconIM:before {
|
||||
content: "\e682";
|
||||
}
|
||||
|
||||
.iconStatistics:before {
|
||||
content: "\e683";
|
||||
}
|
||||
|
||||
.iconData_Reporting:before {
|
||||
content: "\e684";
|
||||
}
|
||||
|
||||
.iconMoveUp:before {
|
||||
content: "\e67c";
|
||||
}
|
||||
|
||||
.iconRevoke:before {
|
||||
content: "\e67d";
|
||||
}
|
||||
|
||||
.iconMoveDown:before {
|
||||
content: "\e67e";
|
||||
}
|
||||
|
||||
.iconAll_Profile:before {
|
||||
content: "\e67f";
|
||||
}
|
||||
|
||||
.iconzu:before {
|
||||
content: "\e712";
|
||||
}
|
||||
|
||||
.iconzu1:before {
|
||||
content: "\e713";
|
||||
}
|
||||
|
||||
.iconShow_Content:before {
|
||||
content: "\e66e";
|
||||
}
|
||||
|
||||
.iconShow:before {
|
||||
content: "\e66f";
|
||||
}
|
||||
|
||||
.iconCCP:before {
|
||||
content: "\e70d";
|
||||
}
|
||||
|
||||
.iconAlready_Read:before {
|
||||
content: "\e669";
|
||||
}
|
||||
|
||||
.iconRegister:before {
|
||||
content: "\e66a";
|
||||
}
|
||||
|
||||
.iconNotice:before {
|
||||
content: "\e66b";
|
||||
}
|
||||
|
||||
.iconUnfinished:before {
|
||||
content: "\e66c";
|
||||
}
|
||||
|
||||
.iconDate1:before {
|
||||
content: "\e664";
|
||||
}
|
||||
|
||||
.iconLogout:before {
|
||||
content: "\e665";
|
||||
}
|
||||
|
||||
.iconPhoto:before {
|
||||
content: "\e666";
|
||||
}
|
||||
|
||||
.iconResetting:before {
|
||||
content: "\e667";
|
||||
}
|
||||
|
||||
.iconProfile_Picture:before {
|
||||
content: "\e668";
|
||||
}
|
||||
|
||||
.iconModal_Success:before {
|
||||
content: "\e662";
|
||||
}
|
||||
|
||||
.iconModal_Warning:before {
|
||||
content: "\e663";
|
||||
}
|
||||
|
||||
.iconSteps_Finished:before {
|
||||
content: "\e660";
|
||||
}
|
||||
|
||||
.iconSteps_In_Progress:before {
|
||||
content: "\e661";
|
||||
}
|
||||
|
||||
.iconAdd_Subordinates:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.iconAdd_Peers:before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.iconHide_Content:before {
|
||||
content: "\e65f";
|
||||
}
|
||||
|
||||
.iconLogo:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.iconAdd:before {
|
||||
content: "\e657";
|
||||
}
|
||||
|
||||
.iconFunction_Management:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.iconDelete:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.iconExported:before {
|
||||
content: "\e65a";
|
||||
}
|
||||
|
||||
.iconImport:before {
|
||||
content: "\e65b";
|
||||
}
|
||||
|
||||
.iconTriangle_Down:before {
|
||||
content: "\e655";
|
||||
}
|
||||
|
||||
.iconTriangle_Right:before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.iconNav_DataCenter:before {
|
||||
content: "\e650";
|
||||
}
|
||||
|
||||
.iconNav_Dashborad:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.iconNav_Pack_Up:before {
|
||||
content: "\e652";
|
||||
}
|
||||
|
||||
.iconNav_Setting:before {
|
||||
content: "\e653";
|
||||
}
|
||||
|
||||
.iconNav_Application:before {
|
||||
content: "\e654";
|
||||
}
|
||||
|
||||
.iconSkip:before {
|
||||
content: "\e64a";
|
||||
}
|
||||
|
||||
.iconSearch:before {
|
||||
content: "\e64b";
|
||||
}
|
||||
|
||||
.iconWeChat:before {
|
||||
content: "\e64c";
|
||||
}
|
||||
|
||||
.iconParent:before {
|
||||
content: "\e64e";
|
||||
}
|
||||
|
||||
.iconSetting:before {
|
||||
content: "\e64f";
|
||||
}
|
||||
|
||||
.iconChange:before {
|
||||
content: "\e63c";
|
||||
}
|
||||
|
||||
.iconBack_Large:before {
|
||||
content: "\e63d";
|
||||
}
|
||||
|
||||
.iconCorrect:before {
|
||||
content: "\e63e";
|
||||
}
|
||||
|
||||
.iconMessage:before {
|
||||
content: "\e63f";
|
||||
}
|
||||
|
||||
.iconDate:before {
|
||||
content: "\e640";
|
||||
}
|
||||
|
||||
.iconMore:before {
|
||||
content: "\e641";
|
||||
}
|
||||
|
||||
.iconEdit:before {
|
||||
content: "\e642";
|
||||
}
|
||||
|
||||
.iconPhone:before {
|
||||
content: "\e643";
|
||||
}
|
||||
|
||||
.iconClean:before {
|
||||
content: "\e644";
|
||||
}
|
||||
|
||||
.iconProlife:before {
|
||||
content: "\e645";
|
||||
}
|
||||
|
||||
.iconClock:before {
|
||||
content: "\e646";
|
||||
}
|
||||
|
||||
.iconLocation:before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.iconPassword:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.iconCopy:before {
|
||||
content: "\e649";
|
||||
}
|
||||
|
||||
.iconArrow_Up:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.iconArrow_Left:before {
|
||||
content: "\e639";
|
||||
}
|
||||
|
||||
.iconArrow_Down:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.iconArrow_Right:before {
|
||||
content: "\e63b";
|
||||
}
|
||||
1
ui/meta/styles/iconfont/iconfont.js
Normal file
1
ui/meta/styles/iconfont/iconfont.js
Normal file
File diff suppressed because one or more lines are too long
78
ui/meta/styles/iconfont/logofont.css
Normal file
78
ui/meta/styles/iconfont/logofont.css
Normal file
@@ -0,0 +1,78 @@
|
||||
@font-face {
|
||||
font-family: "logofont"; /* Project id 1557923 */
|
||||
src: url('//at.alicdn.com/t/c/font_1557923_ymu4rpvijlo.woff2?t=1661421638099') format('woff2'),
|
||||
url('//at.alicdn.com/t/c/font_1557923_ymu4rpvijlo.woff?t=1661421638099') format('woff'),
|
||||
url('//at.alicdn.com/t/c/font_1557923_ymu4rpvijlo.ttf?t=1661421638099') format('truetype');
|
||||
}
|
||||
|
||||
.logofont {
|
||||
font-family: "logofont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.iconhuizhili:before {
|
||||
content: "\e720";
|
||||
}
|
||||
|
||||
.iconxiuxingtong1:before {
|
||||
content: "\e71e";
|
||||
}
|
||||
|
||||
.iconhuizhengwu2:before {
|
||||
content: "\e71a";
|
||||
}
|
||||
|
||||
.iconhuizhengwu:before {
|
||||
content: "\e719";
|
||||
}
|
||||
|
||||
.iconxiuxingtong:before {
|
||||
content: "\e717";
|
||||
}
|
||||
|
||||
.iconzgydzhsq:before {
|
||||
content: "\e6fb";
|
||||
}
|
||||
|
||||
.iconzhongguoyidong2:before {
|
||||
content: "\e6fa";
|
||||
}
|
||||
|
||||
.iconzhongguoyidong:before {
|
||||
content: "\e6f8";
|
||||
}
|
||||
|
||||
.iconcunwei:before {
|
||||
content: "\e6f3";
|
||||
}
|
||||
|
||||
.iconcunwei1:before {
|
||||
content: "\e6f0";
|
||||
}
|
||||
|
||||
.iconccb:before {
|
||||
content: "\e6d7";
|
||||
}
|
||||
|
||||
.iconzhongzhi:before {
|
||||
content: "\e6cb";
|
||||
}
|
||||
|
||||
.iconzxjy:before {
|
||||
content: "\e6c2";
|
||||
}
|
||||
|
||||
.iconLogo:before {
|
||||
content: "\e6a9";
|
||||
}
|
||||
|
||||
.iconminzhengju:before {
|
||||
content: "\e667";
|
||||
}
|
||||
|
||||
.iconzhongguoliantong:before {
|
||||
content: "\e618";
|
||||
}
|
||||
72
ui/meta/styles/theme.hzl.scss
Normal file
72
ui/meta/styles/theme.hzl.scss
Normal file
@@ -0,0 +1,72 @@
|
||||
$theme: "hzl";
|
||||
@import "common";
|
||||
.signLeftContent {
|
||||
color: #333;
|
||||
font-family: -apple-system, BlinkMacSystemFont, PingFang SC, Source Han Sans CN, Microsoft Yahei, sans-serif;
|
||||
|
||||
.titlePane {
|
||||
margin-top: 84px;
|
||||
margin-bottom: 40px;
|
||||
font-family: MicrosoftYaHei, sans-serif;
|
||||
}
|
||||
|
||||
.subTitle:before {
|
||||
border-color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.projectName {
|
||||
font-family: MicrosoftYaHeiS0pxibold;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.ai-sign {
|
||||
width: 420px !important;
|
||||
|
||||
& > .el-row--flex {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.is-always-shadow {
|
||||
box-shadow: 0 24px 48px 0 rgba(15, 56, 139, 0.05);
|
||||
width: 420px !important;
|
||||
min-height: 430px;
|
||||
|
||||
& > .el-card__body {
|
||||
padding: 20px 40px;
|
||||
}
|
||||
|
||||
.ai-scan {
|
||||
right: -30px;
|
||||
|
||||
.iconfont {
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.reset-password-row {
|
||||
text-align: center !important;
|
||||
}
|
||||
}
|
||||
|
||||
.headerNav {
|
||||
.AiIcon {
|
||||
font-size: 28px !important;
|
||||
-webkit-text-fill-color: white !important;
|
||||
font-weight: normal !important;
|
||||
}
|
||||
|
||||
.headerTitle {
|
||||
font-family: FZZZHONGJW--GB1-0 !important;
|
||||
line-height: normal !important;
|
||||
font-weight: normal !important;
|
||||
-webkit-text-fill-color: white !important;
|
||||
}
|
||||
|
||||
.textShadow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
7
ui/meta/styles/theme.yellow.scss
Normal file
7
ui/meta/styles/theme.yellow.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
$theme: "yellow";
|
||||
$primaryColor: #f62;
|
||||
$primaryBtnColor: linear-gradient(90deg, #FFA322 0%, #FF6622 100%);
|
||||
$projectName: linear-gradient(180deg, #FFA322 0%, #FF6622 100%);
|
||||
$primaryLightColor: #FFF7F4;
|
||||
$textShadow: #CA693E;
|
||||
@import "common";
|
||||
12
ui/meta/styles/vars.scss
Normal file
12
ui/meta/styles/vars.scss
Normal file
@@ -0,0 +1,12 @@
|
||||
$primaryColor: #26f !default;
|
||||
$borderColor: #d0d4dc !default;
|
||||
$primaryBtnColor: linear-gradient(90deg, #299FFF 0%, #0C61FF 100%) !default;
|
||||
$successColor: #2EA222 !default;
|
||||
$warnColor: #F82 !default;
|
||||
$errorColor: #F46 !default;
|
||||
$infoColor: #8D96A9 !default;
|
||||
$theme: "blue" !default;
|
||||
$projectName: linear-gradient(180deg, #5AC4FF 11%, #1347B6 100%) !default;
|
||||
$primaryLightColor: rgba(239, 246, 255, 1) !default;
|
||||
$textShadow: #384DC3 !default;
|
||||
$placeholderColor: #888 !default;
|
||||
59
ui/package.json
Normal file
59
ui/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "dui",
|
||||
"version": "2.0.0",
|
||||
"private": false,
|
||||
"author": "kubbo",
|
||||
"scripts": {
|
||||
"build": "node ../bin/ui.js&&vue-cli-service build --no-clean --target lib --dest lib packages/index.js",
|
||||
"lib": "npm run build&&npm unpublish --force&&npm publish"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"main": "lib/dui.common.js",
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@ckeditor/ckeditor5-vue2": "^3.0.1",
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
"axios": "^0.19.2",
|
||||
"dayjs": "^1.8.35",
|
||||
"echarts": "^5.1.2",
|
||||
"v-viewer": "^1.5.1",
|
||||
"vue-cropper": "^0.5.5",
|
||||
"vue-qr": "^2.2.1",
|
||||
"vuedraggable": "^2.24.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"element-ui": "^2.13.2",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"rules": {},
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
],
|
||||
"web-types": "docs/web-types.json",
|
||||
"vetur": {
|
||||
"tags": "docs/tags.json",
|
||||
"attributes": "docs/attributes.json"
|
||||
}
|
||||
}
|
||||
331
ui/packages/basic/AiArticle.vue
Normal file
331
ui/packages/basic/AiArticle.vue
Normal file
@@ -0,0 +1,331 @@
|
||||
<template>
|
||||
<div class="ai-article">
|
||||
<div v-html="value"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiArticle',
|
||||
|
||||
props: {
|
||||
value: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-article {
|
||||
width: 100%;
|
||||
line-height: 1.75;
|
||||
font-weight: 400;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
text-align: justify;
|
||||
overflow-x: auto;
|
||||
word-break: break-word;
|
||||
|
||||
::v-deep h1 {
|
||||
margin: 1.3rem 0;
|
||||
line-height: 1.2
|
||||
}
|
||||
|
||||
::v-deep p {
|
||||
line-height: 2.27rem
|
||||
}
|
||||
|
||||
::v-deep hr {
|
||||
border: none;
|
||||
border-top: 1px solid #ddd;
|
||||
margin-top: 2.7rem;
|
||||
margin-bottom: 2.7rem
|
||||
}
|
||||
|
||||
::v-deep img:not(.equation), ::v-deep iframe, ::v-deep embed, ::v-deep video {
|
||||
display: block;
|
||||
margin: 18px auto;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
::v-deep img.equation {
|
||||
margin: 0 .1em;
|
||||
max-width: 100% !important;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
::v-deep figure {
|
||||
margin: 2.7rem auto;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
::v-deep img:not(.equation) {
|
||||
cursor: zoom-in
|
||||
}
|
||||
|
||||
::v-deep figure figcaption {
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
line-height: 2.7rem;
|
||||
color: #909090
|
||||
}
|
||||
|
||||
::v-deep pre {
|
||||
line-height: 1.93rem;
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
::v-deep code,
|
||||
::v-deep pre {
|
||||
font-family: Menlo, Monaco, Consolas, Courier New, monospace
|
||||
}
|
||||
|
||||
::v-deep code {
|
||||
font-size: 1rem;
|
||||
padding: .26rem .53em;
|
||||
word-break: break-word;
|
||||
color: #4e5980;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 2px;
|
||||
overflow-x: auto
|
||||
}
|
||||
|
||||
::v-deep pre>code {
|
||||
font-size: 1rem;
|
||||
padding: .67rem 1.3rem;
|
||||
margin: 0;
|
||||
word-break: normal;
|
||||
display: block
|
||||
}
|
||||
|
||||
::v-deep a {
|
||||
color: #259
|
||||
}
|
||||
|
||||
::v-deep a:active,
|
||||
::v-deep a:hover {
|
||||
color: #275b8c
|
||||
}
|
||||
|
||||
::v-deep table {
|
||||
width: 100%;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 18px;
|
||||
overflow: auto;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
border-top: 1px solid #ccc;
|
||||
border-left: 1px solid #ccc;
|
||||
}
|
||||
|
||||
::v-deep thead {
|
||||
background: #f6f6f6;
|
||||
color: #000;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
::v-deep td,
|
||||
::v-deep th {
|
||||
padding: 3px 5px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
::v-deep td {
|
||||
min-width: 10rem
|
||||
}
|
||||
|
||||
::v-deep blockquote {
|
||||
margin: 1em 0;
|
||||
border-left: 4px solid #ddd;
|
||||
padding: 0 1.3rem
|
||||
}
|
||||
|
||||
::v-deep blockquote>p {
|
||||
margin: .6rem 0
|
||||
}
|
||||
|
||||
::v-deep ol,
|
||||
::v-deep ul {
|
||||
padding-left: 2.7rem
|
||||
}
|
||||
|
||||
::v-deep ol li,
|
||||
::v-deep ul li {
|
||||
margin-bottom: .6rem
|
||||
}
|
||||
|
||||
::v-deep ol ol,
|
||||
::v-deep ol ul,
|
||||
::v-deep ul ol,
|
||||
::v-deep ul ul {
|
||||
margin-top: .27rem
|
||||
}
|
||||
|
||||
::v-deep pre>code {
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
color: #333;
|
||||
background: #f8f8f8
|
||||
}
|
||||
|
||||
::v-deep p {
|
||||
line-height: inherit;
|
||||
margin-top: 18px;
|
||||
margin-bottom: 18px
|
||||
}
|
||||
|
||||
::v-deep img {
|
||||
max-height: none
|
||||
}
|
||||
|
||||
::v-deep a {
|
||||
color: #0269c8;
|
||||
border-bottom: 1px solid #d1e9ff
|
||||
}
|
||||
|
||||
::v-deep code {
|
||||
background-color: #fff5f5;
|
||||
color: #ff502c;
|
||||
font-size: .87em;
|
||||
padding: .065em .4em
|
||||
}
|
||||
|
||||
::v-deep blockquote {
|
||||
color: #666;
|
||||
padding: 1px 23px;
|
||||
margin: 18px 0;
|
||||
border-left: 4px solid #cbcbcb;
|
||||
background-color: #f8f8f8
|
||||
}
|
||||
|
||||
::v-deep blockquote:after {
|
||||
display: block;
|
||||
content: ""
|
||||
}
|
||||
|
||||
::v-deep blockquote>p {
|
||||
margin: 10px 0
|
||||
}
|
||||
|
||||
::v-deep blockquote.warning {
|
||||
position: relative;
|
||||
border-left-color: #f75151;
|
||||
margin-left: 8px
|
||||
}
|
||||
|
||||
::v-deep blockquote.warning:before {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: -12px;
|
||||
background: #f75151;
|
||||
border-radius: 50%;
|
||||
content: "!";
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
::v-deep ol,
|
||||
::v-deep ul {
|
||||
padding-left: 28px
|
||||
}
|
||||
|
||||
::v-deep ol li,
|
||||
::v-deep ul li {
|
||||
margin-bottom: 0;
|
||||
list-style: inherit
|
||||
}
|
||||
|
||||
::v-deep ol li.task-list-item,
|
||||
::v-deep ul li.task-list-item {
|
||||
list-style: none
|
||||
}
|
||||
|
||||
::v-deep ol li.task-list-item ol,
|
||||
::v-deep ol li.task-list-item ul,
|
||||
::v-deep ul li.task-list-item ol,
|
||||
::v-deep ul li.task-list-item ul {
|
||||
margin-top: 0
|
||||
}
|
||||
|
||||
::v-deep ol li {
|
||||
padding-left: 6px
|
||||
}
|
||||
|
||||
::v-deep pre {
|
||||
position: relative;
|
||||
line-height: 1.75
|
||||
}
|
||||
|
||||
::v-deep pre>code {
|
||||
padding: 15px 12px
|
||||
}
|
||||
|
||||
::v-deep pre>code.hljs[lang] {
|
||||
padding: 18px 15px 12px
|
||||
}
|
||||
|
||||
::v-deep h1,
|
||||
::v-deep h2,
|
||||
::v-deep h3,
|
||||
::v-deep h4,
|
||||
::v-deep h5,
|
||||
::v-deep h6 {
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 10px;
|
||||
padding-bottom: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
::v-deep h1 {
|
||||
font-size: 30px;
|
||||
margin-bottom: 5px
|
||||
}
|
||||
|
||||
::v-deep h2 {
|
||||
padding-bottom: 12px;
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ececec
|
||||
}
|
||||
|
||||
::v-deep h3 {
|
||||
font-size: 18px;
|
||||
padding-bottom: 0
|
||||
}
|
||||
|
||||
::v-deep h4 {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
::v-deep h5 {
|
||||
font-size: 15px
|
||||
}
|
||||
|
||||
::v-deep h6 {
|
||||
margin-top: 5px
|
||||
}
|
||||
|
||||
::v-deep h1.heading+h2.heading {
|
||||
margin-top: 20px
|
||||
}
|
||||
|
||||
::v-deep h1.heading+h3.heading {
|
||||
margin-top: 15px
|
||||
}
|
||||
|
||||
::v-deep .heading+.heading {
|
||||
margin-top: 0
|
||||
}
|
||||
|
||||
::v-deep h1+:not(.heading) {
|
||||
margin-top: 25px
|
||||
}
|
||||
}
|
||||
</style>
|
||||
238
ui/packages/basic/AiAudio.vue
Normal file
238
ui/packages/basic/AiAudio.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<section class="ai-audio">
|
||||
<div class="controller" :class="skinClassName">
|
||||
<el-row :gutter="10" type="flex">
|
||||
<el-col>
|
||||
<i class="play-icon" :class="playIcon" @click="play"></i>
|
||||
</el-col>
|
||||
<el-col class="play-progress">
|
||||
<el-slider
|
||||
:format-tooltip="getDateFormat"
|
||||
v-model="currentTime"
|
||||
:max="totalDuration"
|
||||
@change="audio.currentTime = currentTime">
|
||||
</el-slider>
|
||||
<span class="total" v-if="skin === 'flat'">{{ getDateFormat(currentTime) }}</span>
|
||||
<span class="total" v-else>{{ [getDateFormat(currentTime), getDateFormat(totalDuration)].join('/') }}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<audio
|
||||
ref="audio"
|
||||
style="display: none;"
|
||||
:src="src"
|
||||
preload="metadata"
|
||||
@pause="playState = false"
|
||||
@playing="playing"
|
||||
@loadedmetadata="loadedmetadata"
|
||||
@timeupdate="timeupdate"
|
||||
></audio>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'dayjs'
|
||||
|
||||
export default {
|
||||
name: 'AiAudio',
|
||||
props: {
|
||||
/**
|
||||
* 播放资源url
|
||||
*/
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* 同时只允许一个播放
|
||||
*/
|
||||
singlePlay: {type: Boolean, default: false},
|
||||
/**
|
||||
* 播放器风格
|
||||
* default 旧版样式,flat 新版样式
|
||||
* @values default,flat
|
||||
*/
|
||||
skin: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
audio: null,
|
||||
playState: false,
|
||||
currentTime: 0,
|
||||
totalTime: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
playIcon() {
|
||||
if (this.skin === 'flat') {
|
||||
return this.playState ? 'iconfont iconMediaPlayer_Stop' : 'iconfont iconMediaPlayer_Play'
|
||||
}
|
||||
|
||||
return this.playState ? 'el-icon-video-pause' : 'el-icon-video-play'
|
||||
},
|
||||
|
||||
totalDuration() {
|
||||
return Math.round(this.totalTime)
|
||||
},
|
||||
|
||||
skinClassName() {
|
||||
return this.skin === 'default' ? '' : this.skin
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.audio = this.$refs.audio
|
||||
},
|
||||
|
||||
methods: {
|
||||
play() {
|
||||
if (this.audio) {
|
||||
if (this.audio.paused) {
|
||||
if (this.singlePlay) this.stopAllAudio()
|
||||
this.audio.play()
|
||||
} else {
|
||||
this.audio.pause()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stopAllAudio() {
|
||||
let audios = document.getElementsByTagName('audio')
|
||||
for (let i = 0; i < audios.length; i++) {
|
||||
if (audios[i]) audios[i].pause()
|
||||
}
|
||||
},
|
||||
|
||||
playing() {
|
||||
this.playState = true
|
||||
},
|
||||
|
||||
loadedmetadata() {
|
||||
this.totalTime = this.$refs.audio.duration || 0
|
||||
},
|
||||
|
||||
getDateFormat(val) {
|
||||
const time = moment.duration(val, 'seconds')
|
||||
return [
|
||||
this.prefixNum(time.minutes(), 2),
|
||||
this.prefixNum(time.seconds(), 2),
|
||||
].join(':')
|
||||
},
|
||||
|
||||
prefixNum(val, num) {
|
||||
return (Array(num).join('0') + val).slice(-num)
|
||||
},
|
||||
|
||||
timeupdate() {
|
||||
this.currentTime = parseInt(this.audio.currentTime)
|
||||
}
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
this.audio.pause()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ai-audio {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
.controller {
|
||||
width: 205px;
|
||||
height: 40px;
|
||||
padding: 0 5px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 99px;
|
||||
|
||||
.el-col {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
color: #4c84ff;
|
||||
cursor: pointer;
|
||||
line-height: 40px;
|
||||
font-size: 32px;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #b3d8ff;
|
||||
}
|
||||
}
|
||||
|
||||
.play-progress {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
.el-slider {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.total {
|
||||
margin-left: 15px;
|
||||
font-size: 12px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flat {
|
||||
height: 32px;
|
||||
background: rgba(239, 246, 255, 1);
|
||||
border: 1px solid rgba(132, 181, 255, 1);
|
||||
border-radius: 5px;
|
||||
box-shadow: none;
|
||||
|
||||
.play-icon {
|
||||
position: relative;
|
||||
padding-left: 3px;
|
||||
color: #4c84ff;
|
||||
cursor: pointer;
|
||||
line-height: 32px;
|
||||
font-size: 20px;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover {
|
||||
color: #b3d8ff;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.el-slider__button {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #1365DD;
|
||||
}
|
||||
|
||||
::v-deep.el-slider__bar {
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
::v-deep.el-slider__button-wrapper {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
::v-deep.el-slider__runway {
|
||||
height: 2px;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.play-progress {
|
||||
display: flex;
|
||||
|
||||
.el-slider {
|
||||
width: 84px;
|
||||
}
|
||||
|
||||
.total {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
30
ui/packages/basic/AiBadge.vue
Normal file
30
ui/packages/basic/AiBadge.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<section class="AiBadge">
|
||||
<slot></slot>
|
||||
<sup v-if="isShow" class="badge">
|
||||
<span v-if="badge">{{badge}}</span>
|
||||
<slot v-else name="badge"></slot>
|
||||
</sup>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiBadge",
|
||||
props: {
|
||||
badge: String,
|
||||
isShow: {type: Boolean, default: true}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiBadge {
|
||||
.badge {
|
||||
position: absolute;
|
||||
margin-left: -10px;
|
||||
margin-top: -10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
117
ui/packages/basic/AiBar.vue
Normal file
117
ui/packages/basic/AiBar.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="aibar" :style="{ marginBottom: marginBottom }" :class="[titlePosition === 'center' ? 'aibar-center' : '']">
|
||||
<div v-if="titlePosition === 'center'"></div>
|
||||
<div class="aibar-left" :class="[titlePosition === 'center' ? 'aibar-left__center' : '']">
|
||||
<template v-if="!isHasTitleSlot">{{ title }}</template>
|
||||
<slot name="title" v-else></slot>
|
||||
</div>
|
||||
<div class="aibar-right">
|
||||
<slot name="right"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiBar',
|
||||
|
||||
props: {
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
|
||||
customCliker: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
marginBottom: {
|
||||
type: String,
|
||||
default: '16px'
|
||||
},
|
||||
|
||||
titlePosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isHasTitleSlot () {
|
||||
return this.$slots.title
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.aibar {
|
||||
display: flex;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 56px;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
|
||||
.aibar-left {
|
||||
color: #222;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.aibar-left__center {
|
||||
position: relative;
|
||||
width: 556px;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.aibar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #5088FF;
|
||||
font-size: 12px;
|
||||
|
||||
i {
|
||||
line-height: 1;
|
||||
color: #5088FF;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
& > div, & > a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aibar-center {
|
||||
height: auto;
|
||||
padding: 10px 0;
|
||||
|
||||
h2 {
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
151
ui/packages/basic/AiBigTable.vue
Normal file
151
ui/packages/basic/AiBigTable.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<section class="AiBigTable"/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import AiEmpty from "./AiEmpty";
|
||||
|
||||
export default {
|
||||
name: "AiBigTable",
|
||||
components: {AiEmpty},
|
||||
model: {
|
||||
prop: "data",
|
||||
event: "input"
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* 表数据
|
||||
*/
|
||||
data: {default: () => []},
|
||||
/**
|
||||
* 表格配置
|
||||
*/
|
||||
colConfigs: {default: () => []},
|
||||
/**
|
||||
* 是否显示边框
|
||||
*/
|
||||
border: Boolean,
|
||||
/**
|
||||
* 是否启用dom对象
|
||||
*/
|
||||
isDom: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
html2canvas: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
renderTable() {
|
||||
const table = document.createElement("div")
|
||||
table.style.display = 'flex'
|
||||
table.style.flexDirection = 'column'
|
||||
table.appendChild(this.renderHead())
|
||||
if (this.data.length > 0) {
|
||||
this.data.map(e => table.appendChild(this.renderRow(e)))
|
||||
} else table.appendChild(this.renderEmpty())
|
||||
if (this.isDom) {
|
||||
this.$el.appendChild(table)
|
||||
} else {
|
||||
this.$el.appendChild(table)
|
||||
this.$load(this.html2canvas).then(() => this.html2canvas(table, {
|
||||
allowTaint: true,
|
||||
useCORS: true,
|
||||
height: this.$el.offsetHeight,
|
||||
width: this.$el.offsetWidth
|
||||
})).then(ctx => {
|
||||
this.$el.removeChild(table)
|
||||
this.$el.appendChild(ctx)
|
||||
})
|
||||
}
|
||||
},
|
||||
renderHead() {
|
||||
const head = document.createElement("div")
|
||||
head.style.display = 'flex'
|
||||
head.style.alignItems = 'center'
|
||||
head.style.fontWeight = 'bold'
|
||||
head.style.background = 'rgba(243, 246, 249, 1)'
|
||||
if (this.border) {
|
||||
head.style.borderLeft = '1px solid #eee'
|
||||
head.style.borderTop = '1px solid #eee'
|
||||
} else {
|
||||
head.style.borderBottom = '1px solid #eee'
|
||||
}
|
||||
this.colConfigs.map(e => {
|
||||
const cell = this.renderCell(e)
|
||||
head.appendChild(cell)
|
||||
})
|
||||
return head
|
||||
},
|
||||
renderRow(item) {
|
||||
const row = document.createElement("div")
|
||||
row.style.display = 'flex'
|
||||
row.style.alignItems = 'center'
|
||||
if (this.border) {
|
||||
row.style.borderLeft = '1px solid #eee'
|
||||
} else {
|
||||
row.style.borderBottom = '1px solid #eee'
|
||||
}
|
||||
this.colConfigs.map(e => {
|
||||
const cell = this.renderCell(e, item)
|
||||
row.appendChild(cell)
|
||||
})
|
||||
return row
|
||||
},
|
||||
renderCell(config, row) {
|
||||
const cell = document.createElement("div")
|
||||
cell.style.display = 'flex'
|
||||
cell.style.alignItems = 'center'
|
||||
cell.style.minheight = '32px'
|
||||
cell.style.padding = '0 8px'
|
||||
if (this.border) {
|
||||
cell.style.borderBottom = '1px solid #eee'
|
||||
cell.style.borderRight = '1px solid #eee'
|
||||
}
|
||||
if (config.align) {
|
||||
cell.style.justifyContent = config.align
|
||||
}
|
||||
if (config.width) {
|
||||
cell.style.width = config.width.toString().replace(/(\d+)/g, '$1px')
|
||||
cell.style.flexShrink = 0
|
||||
} else {
|
||||
cell.style.flex = 1
|
||||
cell.style.minWidth = 0
|
||||
}
|
||||
cell.innerHTML = row?.[config.prop] || config.label
|
||||
return cell
|
||||
},
|
||||
renderEmpty() {
|
||||
const empty = document.createElement("div")
|
||||
empty.style.background = 'url("https://cdn.cunwuyun.cn/dvcp/empty.svg") no-repeat'
|
||||
empty.style.width = '100%'
|
||||
empty.style.height = '140px'
|
||||
empty.style.backgroundPosition = 'center 0'
|
||||
empty.style.backgroundSize = '120px'
|
||||
empty.style.borderBottom = '1px solid #eee'
|
||||
empty.style.borderLeft = '1px solid #eee'
|
||||
empty.style.borderRight = '1px solid #eee'
|
||||
empty.style.textAlign = 'center'
|
||||
empty.style.color = '#999'
|
||||
empty.style.paddingTop = '110px'
|
||||
empty.innerHTML = "暂无数据"
|
||||
return empty
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$injectLib("https://cdn.cunwuyun.cn/html2canvas.min.js", () => {
|
||||
this.html2canvas = window?.html2canvas
|
||||
this.$nextTick(this.renderTable)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiBigTable {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
</style>
|
||||
522
ui/packages/basic/AiCron.vue
Normal file
522
ui/packages/basic/AiCron.vue
Normal file
@@ -0,0 +1,522 @@
|
||||
<template>
|
||||
<section class="AiCron">
|
||||
<div id="changeContab">
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i>秒</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="second.cronEvery" label="1">每一秒中</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="second.cronEvery" label="2">每隔
|
||||
<el-input-number size="small" v-model="second.incrementIncrement" :min="1" :max="60"></el-input-number>
|
||||
秒执行 从
|
||||
<el-input-number size="small" v-model="second.incrementStart" :min="0" :max="59"></el-input-number>
|
||||
秒开始
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="second.cronEvery" label="3">具体秒数(可多选)
|
||||
<el-select size="small" multiple v-model="second.specificSpecific">
|
||||
<el-option v-for="(val,i) in 60" :key="i" :value="val-1">{{val-1}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="second.cronEvery" label="4">周期从
|
||||
<el-input-number size="small" v-model="second.rangeStart" :min="1" :max="60"></el-input-number>
|
||||
到
|
||||
<el-input-number size="small" v-model="second.rangeEnd" :min="0" :max="59"></el-input-number>
|
||||
秒
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i>分</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="minute.cronEvery" label="1">每一分钟</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="minute.cronEvery" label="2">每隔
|
||||
<el-input-number size="small" v-model="minute.incrementIncrement" :min="1" :max="60"></el-input-number>
|
||||
分执行 从
|
||||
<el-input-number size="small" v-model="minute.incrementStart" :min="0" :max="59"></el-input-number>
|
||||
分开始
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="minute.cronEvery" label="3">具体分钟数(可多选)
|
||||
<el-select size="small" multiple v-model="minute.specificSpecific">
|
||||
<el-option v-for="(val,i) in 60" :key="i" :value="val-1">{{val-1}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="minute.cronEvery" label="4">周期从
|
||||
<el-input-number size="small" v-model="minute.rangeStart" :min="1" :max="60"></el-input-number>
|
||||
到
|
||||
<el-input-number size="small" v-model="minute.rangeEnd" :min="0" :max="59"></el-input-number>
|
||||
分
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i>时</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="hour.cronEvery" label="1">每一小时</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="hour.cronEvery" label="2">每隔
|
||||
<el-input-number size="small" v-model="hour.incrementIncrement" :min="0" :max="23"></el-input-number>
|
||||
小时执行 从
|
||||
<el-input-number size="small" v-model="hour.incrementStart" :min="0" :max="23"></el-input-number>
|
||||
小时开始
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="hour.cronEvery" label="3">具体小时数(可多选)
|
||||
<el-select size="small" multiple v-model="hour.specificSpecific">
|
||||
<el-option v-for="(val,i) in 24" :key="i" :value="val-1">{{val-1}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="hour.cronEvery" label="4">周期从
|
||||
<el-input-number size="small" v-model="hour.rangeStart" :min="0" :max="23"></el-input-number>
|
||||
到
|
||||
<el-input-number size="small" v-model="hour.rangeEnd" :min="0" :max="23"></el-input-number>
|
||||
小时
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i>天</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="1">每一天</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="2">每隔
|
||||
<el-input-number size="small" v-model="week.incrementIncrement" :min="1" :max="7"></el-input-number>
|
||||
周执行 从
|
||||
<el-select size="small" v-model="week.incrementStart">
|
||||
<el-option v-for="(val,i) in 7" :key="i" :label="weeks[val-1]" :value="val"></el-option>
|
||||
</el-select>
|
||||
开始
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="3">每隔
|
||||
<el-input-number size="small" v-model="day.incrementIncrement" :min="1" :max="31"></el-input-number>
|
||||
天执行 从
|
||||
<el-input-number size="small" v-model="day.incrementStart" :min="1" :max="31"></el-input-number>
|
||||
天开始
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="day.cronEvery" label="4">具体星期几(可多选)
|
||||
<el-select size="small" multiple v-model="week.specificSpecific">
|
||||
<el-option v-for="(val,i) in 7"
|
||||
:key="i"
|
||||
:label="weeks[val-1]"
|
||||
:value="['SUN','MON','TUE','WED','THU','FRI','SAT'][val-1]"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="day.cronEvery" label="5">具体天数(可多选)
|
||||
<el-select size="small" multiple v-model="day.specificSpecific">
|
||||
<el-option v-for="(val,i) in 31" :key="i" :value="val">{{val}}</el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="6">在这个月的最后一天</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="7">在这个月的最后一个工作日</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="8">在这个月的最后一个
|
||||
<el-select size="small" v-model="day.cronLastSpecificDomDay">
|
||||
<el-option v-for="(val,i) in 7" :key="i" :label="weeks[val-1]" :value="val"></el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="9">
|
||||
<el-input-number size="small" v-model="day.cronDaysBeforeEomMinus" :min="1" :max="31"></el-input-number>
|
||||
在本月底前
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="10">最近的工作日(周一至周五) 至本月
|
||||
<el-input-number size="small" v-model="day.cronDaysNearestWeekday" :min="1" :max="31"></el-input-number>
|
||||
日
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="day.cronEvery" label="11">在这个月的第
|
||||
<el-input-number size="small" v-model="week.cronNthDayNth" :min="1" :max="5"></el-input-number>
|
||||
个
|
||||
<el-select size="small" v-model="week.cronNthDayDay">
|
||||
<el-option v-for="(val,i) in 7" :key="i" :label="weeks[val-1]" :value="val"></el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane>
|
||||
<span slot="label"><i class="el-icon-date"></i> 月</span>
|
||||
<div class="tabBody">
|
||||
<el-row>
|
||||
<el-radio v-model="month.cronEvery" label="1">每个月</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="month.cronEvery" label="2">每隔
|
||||
<el-input-number size="small" v-model="month.incrementIncrement" :min="0" :max="12"></el-input-number>
|
||||
月执行 从
|
||||
<el-input-number size="small" v-model="month.incrementStart" :min="0" :max="12"></el-input-number>
|
||||
月开始
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio class="long" v-model="month.cronEvery" label="3">具体月数(可多选)
|
||||
<el-select size="small" multiple v-model="month.specificSpecific">
|
||||
<el-option v-for="(val,i) in 12" :key="i" :label="val" :value="val"></el-option>
|
||||
</el-select>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-radio v-model="month.cronEvery" label="4">从
|
||||
<el-input-number size="small" v-model="month.rangeStart" :min="1" :max="12"></el-input-number>
|
||||
到
|
||||
<el-input-number size="small" v-model="month.rangeEnd" :min="1" :max="12"></el-input-number>
|
||||
</el-radio>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div class="bottom">
|
||||
<span class="value">{{cron}}</span>
|
||||
<el-button type="primary" @click="change">保存</el-button>
|
||||
<el-button type="primary" @click="close">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiCron",
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
weeks: ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"],
|
||||
second: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [],
|
||||
},
|
||||
minute: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [],
|
||||
},
|
||||
hour: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [],
|
||||
},
|
||||
day: {
|
||||
cronEvery: '',
|
||||
incrementStart: '1',
|
||||
incrementIncrement: '1',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [],
|
||||
cronLastSpecificDomDay: 1,
|
||||
cronDaysBeforeEomMinus: '',
|
||||
cronDaysNearestWeekday: '',
|
||||
},
|
||||
week: {
|
||||
cronEvery: '',
|
||||
incrementStart: '1',
|
||||
incrementIncrement: '1',
|
||||
specificSpecific: [],
|
||||
cronNthDayDay: 1,
|
||||
cronNthDayNth: '1',
|
||||
},
|
||||
month: {
|
||||
cronEvery: '',
|
||||
incrementStart: '3',
|
||||
incrementIncrement: '5',
|
||||
rangeStart: '',
|
||||
rangeEnd: '',
|
||||
specificSpecific: [],
|
||||
},
|
||||
output: {
|
||||
second: '',
|
||||
minute: '',
|
||||
hour: '',
|
||||
day: '',
|
||||
month: '',
|
||||
Week: '',
|
||||
},
|
||||
visible: false,
|
||||
cronArr:['*','*','*','*','*','?']
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
cron(val) {
|
||||
this.value = val
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
secondsText() {
|
||||
let seconds = this.cronArr[0];
|
||||
let cronEvery = this.second.cronEvery;
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
seconds = '*';
|
||||
break;
|
||||
case '2':
|
||||
seconds = this.second.incrementStart + '/' + this.second.incrementIncrement;
|
||||
break;
|
||||
case '3':
|
||||
this.second.specificSpecific.map(val => {
|
||||
seconds += val + ','
|
||||
});
|
||||
seconds = seconds.slice(0, -1);
|
||||
break;
|
||||
case '4':
|
||||
seconds = this.second.rangeStart + '-' + this.second.rangeEnd;
|
||||
break;
|
||||
}
|
||||
return seconds;
|
||||
},
|
||||
minutesText() {
|
||||
let minutes = this.cronArr[2];;
|
||||
let cronEvery = this.minute.cronEvery;
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
minutes = '*';
|
||||
break;
|
||||
case '2':
|
||||
minutes = this.minute.incrementStart + '/' + this.minute.incrementIncrement;
|
||||
break;
|
||||
case '3':
|
||||
this.minute.specificSpecific.map(val => {
|
||||
minutes += val + ','
|
||||
});
|
||||
minutes = minutes.slice(0, -1);
|
||||
break;
|
||||
case '4':
|
||||
minutes = this.minute.rangeStart + '-' + this.minute.rangeEnd;
|
||||
break;
|
||||
}
|
||||
return minutes;
|
||||
},
|
||||
hoursText() {
|
||||
let hours = this.cronArr[2];;
|
||||
let cronEvery = this.hour.cronEvery;
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
hours = '*';
|
||||
break;
|
||||
case '2':
|
||||
hours = this.hour.incrementStart + '/' + this.hour.incrementIncrement;
|
||||
break;
|
||||
case '3':
|
||||
this.hour.specificSpecific.map(val => {
|
||||
hours += val + ','
|
||||
});
|
||||
hours = hours.slice(0, -1);
|
||||
break;
|
||||
case '4':
|
||||
hours = this.hour.rangeStart + '-' + this.hour.rangeEnd;
|
||||
break;
|
||||
}
|
||||
return hours;
|
||||
},
|
||||
daysText() {
|
||||
let days = this.cronArr[3];;
|
||||
let cronEvery = this.day.cronEvery;
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
break;
|
||||
case '2':
|
||||
case '4':
|
||||
case '11':
|
||||
days = '?';
|
||||
break;
|
||||
case '3':
|
||||
days = this.day.incrementStart + '/' + this.day.incrementIncrement;
|
||||
break;
|
||||
case '5':
|
||||
this.day.specificSpecific.map(val => {
|
||||
days += val + ','
|
||||
});
|
||||
days = days.slice(0, -1);
|
||||
break;
|
||||
case '6':
|
||||
days = "L";
|
||||
break;
|
||||
case '7':
|
||||
days = "LW";
|
||||
break;
|
||||
case '8':
|
||||
days = this.day.cronLastSpecificDomDay + 'L';
|
||||
break;
|
||||
case '9':
|
||||
days = 'L-' + this.day.cronDaysBeforeEomMinus;
|
||||
break;
|
||||
case '10':
|
||||
days = this.day.cronDaysNearestWeekday + "W";
|
||||
break
|
||||
}
|
||||
return days;
|
||||
},
|
||||
weeksText() {
|
||||
let weeks = this.cronArr[5];;
|
||||
let cronEvery = this.day.cronEvery;
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
case '3':
|
||||
case '5':
|
||||
weeks = '?';
|
||||
break;
|
||||
case '2':
|
||||
weeks = this.week.incrementStart + '/' + this.week.incrementIncrement;
|
||||
break;
|
||||
case '4':
|
||||
this.week.specificSpecific.map(val => {
|
||||
weeks += val + ','
|
||||
});
|
||||
weeks = weeks.slice(0, -1);
|
||||
break;
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
case '10':
|
||||
weeks = "?";
|
||||
break;
|
||||
case '11':
|
||||
weeks = this.week.cronNthDayDay + "#" + this.week.cronNthDayNth;
|
||||
break;
|
||||
}
|
||||
return weeks;
|
||||
},
|
||||
monthsText() {
|
||||
let months = this.cronArr[4];
|
||||
let cronEvery = this.month.cronEvery;
|
||||
switch (cronEvery.toString()) {
|
||||
case '1':
|
||||
months = '*';
|
||||
break;
|
||||
case '2':
|
||||
months = this.month.incrementStart + '/' + this.month.incrementIncrement;
|
||||
break;
|
||||
case '3':
|
||||
this.month.specificSpecific.map(val => {
|
||||
months += val + ','
|
||||
});
|
||||
months = months.slice(0, -1);
|
||||
break;
|
||||
case '4':
|
||||
months = this.month.rangeStart + '-' + this.month.rangeEnd;
|
||||
break;
|
||||
}
|
||||
return months;
|
||||
},
|
||||
cron() {
|
||||
return `${this.secondsText || '*'} ${this.minutesText || '*'} ${this.hoursText || '*'} ${this.daysText || '*'} ${this.monthsText || '*'} ${this.weeksText || '?'} `
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getValue() {
|
||||
return this.cron;
|
||||
},
|
||||
change() {
|
||||
this.$emit('change', this.cron);
|
||||
this.close();
|
||||
},
|
||||
close() {
|
||||
this.$emit('close')
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if(this.value) this.cronArr = this.value.trim().split(" ")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiCron {
|
||||
|
||||
#changeContab {
|
||||
.language {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.el-tabs {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tabBody {
|
||||
.el-row {
|
||||
margin: 10px 0;
|
||||
|
||||
.long {
|
||||
.el-select {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-input-number {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
|
||||
.value {
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
163
ui/packages/basic/AiDialog.vue
Normal file
163
ui/packages/basic/AiDialog.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<section class="ai-dialog__wrapper">
|
||||
<el-dialog
|
||||
custom-class="ai-dialog"
|
||||
v-on="$listeners"
|
||||
v-if="isEmpty"
|
||||
:close-on-click-modal="closeOnClickModal"
|
||||
v-bind="$attrs"
|
||||
:destroy-on-close="destroyOnClose"
|
||||
:visible.sync="dialogVisible">
|
||||
<div class="ai-dialog__header" slot="title">
|
||||
<h2>{{ title }}</h2>
|
||||
</div>
|
||||
<div class="ai-dialog__content" :style="{'max-height': isScrool ? '500px' : 'auto'}">
|
||||
<div class="ai-dialog__content--wrapper" :style="{'padding-right': isScrool ? '8px' : '0'}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="customFooter" slot="footer">
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
<div v-else class="dialog-footer" slot="footer">
|
||||
<el-button @click="onCancel">取消</el-button>
|
||||
<el-button @click="onConfirm" type="primary" style="width: 92px;">确认</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiDialog',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
customFooter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
'close-on-click-modal': {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
destroyOnClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
dialogVisible: false,
|
||||
isScrool: true,
|
||||
isEmpty: true
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
visible: {
|
||||
handler (val) {
|
||||
this.dialogVisible = val
|
||||
|
||||
// if (val) {
|
||||
// this.$nextTick(() => {
|
||||
// setTimeout(() => {
|
||||
// this.isScrool = document.querySelector('.ai-dialog__content') && document.querySelector('.ai-dialog__content').clientHeight >= 500
|
||||
// }, 100)
|
||||
// })
|
||||
// }
|
||||
|
||||
if (this.destroyOnClose && !val) {
|
||||
setTimeout(() => {
|
||||
this.isEmpty = false
|
||||
|
||||
setTimeout(() => {
|
||||
this.isEmpty = true
|
||||
}, 50)
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
},
|
||||
|
||||
methods: {
|
||||
onCancel () {
|
||||
this.$emit('update:visible', false)
|
||||
this.$emit('onCancel')
|
||||
},
|
||||
|
||||
onConfirm () {
|
||||
this.$emit('onConfirm')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.ai-dialog {
|
||||
margin: 0!important;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
.el-dialog__body {
|
||||
padding: 20px 40px 20px;
|
||||
}
|
||||
|
||||
.ai-dialog__content {
|
||||
overflow-y: auto;
|
||||
padding-bottom: 4px;
|
||||
|
||||
.ai-dialog__content--wrapper {
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-dialog__header {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__footer {
|
||||
padding: 16px 20px;
|
||||
box-sizing: border-box;
|
||||
background: #F3F6F9;
|
||||
text-align: center;
|
||||
|
||||
& + .el-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
width: 92px!important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-dialog__header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 24px;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
139
ui/packages/basic/AiDrawer.vue
Normal file
139
ui/packages/basic/AiDrawer.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<section class="AiDrawer" :style="{width:`${width}px`,height:`${height}px`}">
|
||||
<slot name="tools"/>
|
||||
<div class="resetSignature" @click.stop="handleClean">
|
||||
<ai-icon icon="iconResetting"/>
|
||||
<span>重写</span>
|
||||
</div>
|
||||
<div v-if="drawPlaceholder" class="placeholder">{{ placeholder }}</div>
|
||||
<canvas id="drawer" :width="width" :height="height" @mousedown.stop="handleDrawStart($event)"
|
||||
@mouseup.stop="handleDrawEnd" @mouseleave="handleDrawEnd"
|
||||
@mousemove.stop="handleDrawing($event)"
|
||||
@touchstart.stop="handleDrawStart" @touchend.stop="handleDrawEnd" @touchmove="handleDrawing"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiDrawer",
|
||||
props: {
|
||||
seal: String,
|
||||
placeholder: {type: String, default: "请在此处清晰书写你的签名"},
|
||||
width: {type: Number, default: 640},
|
||||
height: {type: Number, default: 480}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
drawing: false,//判断是否处于绘画中
|
||||
drawer: {},
|
||||
drawPlaceholder: true
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
drawing() {
|
||||
this.drawPlaceholder = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initDrawer()
|
||||
},
|
||||
methods: {
|
||||
initDrawer() {
|
||||
this.$nextTick(() => {
|
||||
let canvas = document.querySelector("#drawer")
|
||||
if (canvas) {
|
||||
this.drawer = canvas?.getContext("2d")
|
||||
this.drawer.lineWidth = 3
|
||||
this.drawer.shadowColor = '#333'
|
||||
this.drawer.strokeStyle = '#333'
|
||||
this.drawer.shadowBlur = 2
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDrawStart(e) {
|
||||
this.drawing = true
|
||||
const canvasX = e.offsetX
|
||||
const canvasY = e.offsetY
|
||||
this.drawer?.beginPath()
|
||||
this.drawer?.moveTo(canvasX, canvasY)
|
||||
},
|
||||
handleDrawEnd() {
|
||||
this.drawing = false
|
||||
this.$emit("update:seal", this.drawer.canvas.toDataURL("image/png", 1))
|
||||
},
|
||||
handleDrawing(e) {
|
||||
if (this.drawing) {
|
||||
const t = e.target?.getBoundingClientRect()
|
||||
let canvasX
|
||||
let canvasY
|
||||
// 根据触发事件类型来进行定位
|
||||
if (e.type == "touchmove") {
|
||||
canvasX = e.changedTouches[0].clientX - t.x
|
||||
canvasY = e.changedTouches[0].clientY - t.y
|
||||
} else {
|
||||
canvasX = e.offsetX
|
||||
canvasY = e.offsetY
|
||||
}
|
||||
// 连接到移动的位置并上色
|
||||
this.drawer?.lineTo(canvasX, canvasY)
|
||||
this.drawer?.stroke()
|
||||
}
|
||||
},
|
||||
handleClean() {
|
||||
this.drawer.clearRect(0, 0, this.drawer.canvas.width, this.drawer.canvas.height)
|
||||
this.drawPlaceholder = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiDrawer {
|
||||
margin: 0 40px;
|
||||
background: #F7F7F7;
|
||||
border: 1px solid #CCCCCC;
|
||||
position: relative;
|
||||
|
||||
#drawer {
|
||||
display: block;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.resetSignature {
|
||||
position: absolute;
|
||||
width: 76px;
|
||||
height: 32px;
|
||||
background: rgba(#000, .5);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: rgba(#fff, .6);
|
||||
cursor: pointer;
|
||||
right: 16px;
|
||||
top: 16px;
|
||||
|
||||
.AiIcon {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
max-width: 407px;
|
||||
text-align: center;
|
||||
font-size: 64px;
|
||||
color: #DDDDDD;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
129
ui/packages/basic/AiEditor/AiEditor.vue
Normal file
129
ui/packages/basic/AiEditor/AiEditor.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<section class="AiEditor" :class="{invalid:valid&&!validateState}">
|
||||
<ck-editor v-if="sourceEditor" :editor="sourceEditor" v-model="content" :config="editorConfig" @ready="handleReady" @input="$emit('change',repairContent)"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 原组件ckeditor封装
|
||||
* 修改者:Kubbo
|
||||
*/
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue2'
|
||||
import {UploadAdapter} from "./models";
|
||||
|
||||
export default {
|
||||
name: "AiEditor",
|
||||
components: {ckEditor: CKEditor.component},
|
||||
inject: {
|
||||
elFormItem: {default: ""},
|
||||
elForm: {default: ''},
|
||||
},
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: {type: String, required: true, default: ""},
|
||||
placeholder: {type: String, default: '请输入正文'},
|
||||
conf: Object,
|
||||
instance: {type: Function, required: true},
|
||||
valid: {type: Boolean, default: true},
|
||||
text: {default: ""},
|
||||
action: {default: "/admin/file/add"},
|
||||
params: {default: () => ({})},
|
||||
mode: {default: "default"}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sourceEditor: null,
|
||||
editor: null,
|
||||
isPasteStyle: true,//粘贴是否携带格式
|
||||
content: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
validateState() {
|
||||
return ['', 'success'].includes(this.elFormItem?.validateState)
|
||||
},
|
||||
editorConfig() {
|
||||
return {
|
||||
mediaEmbed: {
|
||||
previewsInData: true
|
||||
},
|
||||
htmlSupport: {
|
||||
allow: [{name: /[\s\S]+/, styles: true, classes: true, attributes: true}]
|
||||
},
|
||||
...this.conf
|
||||
}
|
||||
},
|
||||
repairContent: v => `<section class="ck-content">${v.content?.replace(/\s0w"/g, '"')?.replace(/srcset/g, 'src')}</section>`
|
||||
},
|
||||
watch: {
|
||||
content(v) {
|
||||
v && this.dispatch('ElFormItem', 'el.form.change', [v])
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleReady(editor) {
|
||||
this.editor = editor
|
||||
const {instance, action, params} = this
|
||||
editor.plugins.get('FileRepository').createUploadAdapter = loader => new UploadAdapter(loader, instance, action, params)
|
||||
},
|
||||
/**
|
||||
* 表单验证
|
||||
* @param componentName
|
||||
* @param eventName
|
||||
* @param params
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root;
|
||||
let name = parent.$options.componentName;
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent;
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.componentName;
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
initValue() {
|
||||
let unwatch = this.$watch('value', (v) => {
|
||||
const init = v.replace(/<section class="ck-content">(.*)<\/section>/, '$1')
|
||||
if (!!this.content) unwatch && unwatch()
|
||||
else if (!!init) {
|
||||
this.content = init
|
||||
unwatch && unwatch()
|
||||
}
|
||||
}, {immediate: true})
|
||||
},
|
||||
loadEditor(count = 0) {
|
||||
if (!!window?.ClassicEditor) {
|
||||
this.sourceEditor = ClassicEditor
|
||||
this.initValue()
|
||||
} else if (count < 10) {
|
||||
setTimeout(() => this.loadEditor(++count), 50)
|
||||
} else {
|
||||
console.error("无法加载编辑器资源,请联系管理员")
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$injectLib("https://cdn.cunwuyun.cn/ckeditor.js", () => {
|
||||
this.loadEditor()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiEditor {
|
||||
::v-deep.ck-content {
|
||||
min-height: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
ui/packages/basic/AiEditor/models.js
Normal file
24
ui/packages/basic/AiEditor/models.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import {Loading} from "element-ui"
|
||||
|
||||
export class UploadAdapter {
|
||||
constructor(loader, instance, action, params) {
|
||||
this.instance = instance
|
||||
this.action = action
|
||||
this.loader = loader
|
||||
this.params = params
|
||||
}
|
||||
|
||||
async upload() {
|
||||
const formData = new FormData()
|
||||
formData.append('file', await this.loader.file)
|
||||
const loading = Loading.service({})
|
||||
return this.instance.post(this.action, formData, {...this.params, returnError: true}).then(res => {
|
||||
if (res?.data) {
|
||||
return res.data.map(m => m.split(";")?.[0])
|
||||
} else return this.loader.status = "aborted" && Promise.reject()
|
||||
}).finally(() => loading.close())
|
||||
}
|
||||
|
||||
abort() {
|
||||
}
|
||||
}
|
||||
34
ui/packages/basic/AiEmpty.vue
Normal file
34
ui/packages/basic/AiEmpty.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="ai-empty">
|
||||
<div class="ai-empty__bg"></div>
|
||||
<template v-if="!isHasTitleSlot">暂无数据</template>
|
||||
<slot v-else></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiEmpty',
|
||||
|
||||
computed: {
|
||||
isHasTitleSlot () {
|
||||
return this.$slots.default
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ai-empty {
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
color: #acaaad;
|
||||
|
||||
.ai-empty__bg {
|
||||
background: url("https://cdn.cunwuyun.cn/dvcp/empty.svg") no-repeat center;
|
||||
background-size: 120px 120px;
|
||||
height: 120px;
|
||||
margin: 48px auto 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
118
ui/packages/basic/AiFileList.vue
Normal file
118
ui/packages/basic/AiFileList.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<div class="ai-filelist">
|
||||
<div class="ai-flie__item" v-for="(item, index) in fileList" :key="index" @click="downFile(item)"
|
||||
:title="item.name">
|
||||
<div class="ai-flie__item--left flex1">
|
||||
<svg aria-hidden="true">
|
||||
<use xlink:href="#iconAppendix_UNdownload"></use>
|
||||
</svg>
|
||||
<span>{{ item[fileProps.name] }}</span>
|
||||
</div>
|
||||
<div class="ai-file__item--right">
|
||||
<span>{{ item[fileProps.size] }}</span>
|
||||
<i class="iconfont iconDownload"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!fileList.length" style="width: 120px" class="no-data"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiFileList',
|
||||
|
||||
props: {
|
||||
fileList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
|
||||
fileOps: {
|
||||
type: Object
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
fileProps() {
|
||||
const props = {
|
||||
name: 'name',
|
||||
size: 'size'
|
||||
}
|
||||
|
||||
return this.fileOps || props
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
downFile(item) {
|
||||
window.open(`${item.url}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-filelist {
|
||||
.ai-flie__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin-bottom: 16px;
|
||||
padding: 0 8px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #d0d4dc;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f3f6f9;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-flie__item--left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 20px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-file__item--right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 1;
|
||||
|
||||
span {
|
||||
padding-right: 5px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #5088FF;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
38
ui/packages/basic/AiIcon.vue
Normal file
38
ui/packages/basic/AiIcon.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div class="AiIcon">
|
||||
<div v-if="type=='icon'" class="iconfont" :class="icon"/>
|
||||
<div v-if="type=='logo'" class="logofont" :class="icon"/>
|
||||
<svg v-if="type=='svg'" class="icon" aria-hidden="true">
|
||||
<use :xlink:href="`#${icon}`"></use>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import "../../meta/styles/iconfont/iconfont";
|
||||
import "../../meta/styles/iconfont/iconfont.css";
|
||||
import "../../meta/styles/iconfont/logofont.css";
|
||||
|
||||
export default {
|
||||
name: "AiIcon",
|
||||
props: {
|
||||
type: {type: String, default: "icon"},
|
||||
icon: {type: String, required: true},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiIcon {
|
||||
box-sizing: border-box;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 16px;
|
||||
|
||||
.iconfont, .logofont, .icon {
|
||||
font-size: inherit;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
321
ui/packages/basic/AiImport.vue
Normal file
321
ui/packages/basic/AiImport.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<section class="ai-import">
|
||||
<a v-if="$slots.default" class="custom-clicker" @click="dialog = true">
|
||||
<slot/>
|
||||
</a>
|
||||
<el-button v-else size="small" @click="dialog=true" icon="iconfont iconImport">导入</el-button>
|
||||
<ai-dialog
|
||||
:title="dialogTitle"
|
||||
:visible.sync="dialog"
|
||||
:destroy-on-close="true"
|
||||
width="800px"
|
||||
customFooter
|
||||
:close-on-click-modal="false"
|
||||
@closed="onClose">
|
||||
<el-form size="small" ref="importForm" label-width="0" class="import-form" :model="fileForm"
|
||||
:rules="fileRules">
|
||||
<el-form-item class="ai-import__tips">
|
||||
<h2>导入说明:</h2>
|
||||
<p class="ai-import__content">
|
||||
1、您正在进行【{{ name }}】批量导入操作,请先
|
||||
<span @click="downloadFile" title="下载导入模板" class="ai-link" v-text="'【点击下载导入模板】'"/>
|
||||
并规范填写;</p>
|
||||
<p class="ai-import__content">2、填写完成后,上传您编辑完成的模板文件:</p>
|
||||
<div class="ai-import__text">
|
||||
<template v-if="$slots.tips">
|
||||
<div>请注意:</div>
|
||||
<slot name="tips"/>
|
||||
</template>
|
||||
<el-row type="flex" v-else-if="!!dict">
|
||||
<div v-text="'请注意:'"/>
|
||||
<span v-text="dict.getLabel('importTips',type)"/>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item prop="file" style="width: 100%">
|
||||
<ai-uploader isImport :instance="instance" v-model="fileForm.file" fileType="file" :limit="1"
|
||||
acceptType=".xls,.xlsx" @change="onChange" :clearable="false">
|
||||
<template #trigger>
|
||||
<el-button icon="iconfont iconfangda">选择文件</el-button>
|
||||
</template>
|
||||
<template #tips>最多上传1个文件,单个文件最大10MB,仅支持Excel格式</template>
|
||||
</ai-uploader>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" style="text-align: center;">
|
||||
<el-button @click="dialog=false">取消</el-button>
|
||||
<el-button type="primary" @click="onClick">立即导入</el-button>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
<div class="ai-import__loading" v-if="isHasLoadingSlot && isLoading">
|
||||
<slot name="loading"/>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiImport',
|
||||
props: {
|
||||
title: String,
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
instance: {
|
||||
type: Function
|
||||
},
|
||||
importUrl: {
|
||||
type: String,
|
||||
},
|
||||
importParams: {
|
||||
type: Object
|
||||
},
|
||||
suffixName: {
|
||||
type: String,
|
||||
default: 'xls'
|
||||
},
|
||||
tplParams: Object,
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
timeout: {
|
||||
type: Number,
|
||||
default: 10 * 60 * 1000
|
||||
},
|
||||
customError: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: String,
|
||||
dict: Object
|
||||
},
|
||||
computed: {
|
||||
isHasLoadingSlot() {
|
||||
return this.$slots.loading
|
||||
},
|
||||
actions() {
|
||||
return {
|
||||
url: this.importUrl || `/app/${this.type}/import`,//导入接口
|
||||
tpl: this.url || `/app/${this.type}/downloadTemplate`,//下载模板接口
|
||||
}
|
||||
},
|
||||
dialogTitle() {
|
||||
return this.title || `${this.name}数据导入`
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
fileForm: {
|
||||
file: []
|
||||
},
|
||||
loading: null,
|
||||
isLoading: false,
|
||||
fileRules: {
|
||||
file: [{required: true, message: '请上传相关文件', trigger: 'change'}]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onChange(e) {
|
||||
if (e.length) {
|
||||
this.$refs.importForm.clearValidate()
|
||||
} else {
|
||||
this.$refs.importForm.validate()
|
||||
}
|
||||
},
|
||||
onClick() {
|
||||
this.$refs.importForm.validate(v => {
|
||||
if (v) {
|
||||
const data = new FormData()
|
||||
data.append('file', this.fileForm.file[0].raw)
|
||||
if (!this.isHasLoadingSlot) {
|
||||
this.loading = this.$loading({
|
||||
lock: true,
|
||||
text: '导入中',
|
||||
background: 'rgba(0, 0, 0, 0.5)'
|
||||
})
|
||||
} else {
|
||||
this.isLoading = true
|
||||
}
|
||||
this.instance.post(this.actions.url, data, {
|
||||
params: this.importParams,
|
||||
timeout: this.timeout
|
||||
}).then(res => {
|
||||
if (!this.isHasLoadingSlot) {
|
||||
this.loading?.close()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
if (res?.data?.importStatus == 1) {
|
||||
this.dialog = false
|
||||
const h = this.$createElement
|
||||
this.$emit('onSuccess', res)
|
||||
this.$emit('success', res)
|
||||
if (this.customError) {
|
||||
this.$emit('error')
|
||||
} else this.$confirm('导入失败数据具体原因请', {
|
||||
type: 'success',
|
||||
title: '数据导入完成',
|
||||
closeOnClickModal: false,
|
||||
customClass: 'message-wrapper',
|
||||
showConfirmButton: false,
|
||||
cancelButtonText: '关闭',
|
||||
message: h('div', {
|
||||
class: 'importResult'
|
||||
}, [
|
||||
h('span', null, '成功新增'),
|
||||
h('a', {
|
||||
style: 'color: #2EA222;'
|
||||
}, `${res.data.addCount}`),
|
||||
h('span', null, '条数据,更新'),
|
||||
h('a', {
|
||||
style: 'color: #26f;'
|
||||
}, `${res.data.updateCount}`),
|
||||
h('span', null, '条数据,导入失败'),
|
||||
h('a', {
|
||||
style: 'color: #f46;'
|
||||
}, `${res.data.failCount}`),
|
||||
h('span', null, '条数据。'),
|
||||
h('div', {class: 'gap'}),
|
||||
h('div', {style: `display:${res.data.errorFileURL ? 'block' : 'none'}`}, [
|
||||
h('span', null, '点此'),
|
||||
h('a', {
|
||||
class: 'tips-link',
|
||||
attrs: {
|
||||
href: res.data.errorFileURL
|
||||
}
|
||||
}, '下载异常数据')
|
||||
])
|
||||
])
|
||||
})
|
||||
} else if (res?.data?.importStatus == 0) {
|
||||
this.$message.error(res?.data?.errorMsg)
|
||||
}
|
||||
}).catch(() => {
|
||||
if (!this.isHasLoadingSlot) {
|
||||
this.loading?.close()
|
||||
} else {
|
||||
this.isLoading = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onClose() {
|
||||
this.loading?.close()
|
||||
this.fileForm.file = []
|
||||
},
|
||||
downloadFile() {
|
||||
this.instance.post(this.actions.tpl, null, {
|
||||
responseType: 'blob',
|
||||
params: this.tplParams
|
||||
}).then((res) => {
|
||||
const link = document.createElement('a')
|
||||
let blob = new Blob([res], {type: 'application/vnd.ms-excel'})
|
||||
link.style.display = 'none'
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.setAttribute('download', this.name + '模板.' + this.suffixName)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
})
|
||||
},
|
||||
hide() {
|
||||
this.dialog = false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.dict?.load("importTips")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ai-import {
|
||||
.empty-input {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
.custom-clicker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ai-import__loading {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
::v-deep .el-message-box {
|
||||
width: 720px !important;
|
||||
}
|
||||
|
||||
.ai-import__content {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.ai-import__tips {
|
||||
line-height: 1;
|
||||
|
||||
::v-deep.el-form-item__content {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 4px;
|
||||
color: #333333;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 8px;
|
||||
color: #424242;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ai-link {
|
||||
cursor: pointer;
|
||||
color: $primaryColor;
|
||||
}
|
||||
|
||||
.ai-import__text {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
line-height: 16px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.message-wrapper {
|
||||
width: 560px !important;
|
||||
|
||||
.importResult {
|
||||
color: #222222;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: bold;
|
||||
|
||||
.gap {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.tips-link {
|
||||
color: $primaryColor;
|
||||
text-decoration: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
91
ui/packages/basic/AiInfoItem.vue
Normal file
91
ui/packages/basic/AiInfoItem.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="ai-info-item" :style="contentStyle">
|
||||
<label class="ai-info-item__left" :style="contentLabelStyle">
|
||||
<template v-if="$slots.label">
|
||||
<slot name="label"/>
|
||||
</template>
|
||||
<template v-else>{{ label }}</template>
|
||||
</label>
|
||||
<div class="ai-info-item__right">
|
||||
<slot v-if="$scopedSlots.default"/>
|
||||
<template v-else-if="!!openType">
|
||||
<ai-open-data :type="openType" :openid="value"/>
|
||||
</template>
|
||||
<template v-else>{{ value || '-' }}</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiInfoItem',
|
||||
|
||||
inject: ['AiWrapper'],
|
||||
|
||||
props: {
|
||||
label: {
|
||||
type: String
|
||||
},
|
||||
|
||||
value: {
|
||||
type: [String, Number]
|
||||
},
|
||||
|
||||
'label-width': {
|
||||
type: String
|
||||
},
|
||||
|
||||
isLine: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
openType: {default: ""}
|
||||
},
|
||||
|
||||
computed: {
|
||||
contentStyle() {
|
||||
let width = this.AiWrapper.autoWidth
|
||||
|
||||
if (this.isLine) {
|
||||
width = '100%'
|
||||
}
|
||||
|
||||
return {
|
||||
width
|
||||
}
|
||||
},
|
||||
|
||||
contentLabelStyle() {
|
||||
return {
|
||||
width: this.labelWidth || this.AiWrapper.autoLableWidth
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-info-item {
|
||||
display: flex;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 16px;
|
||||
|
||||
label {
|
||||
flex-shrink: 0;
|
||||
width: 96px;
|
||||
margin-right: 40px;
|
||||
text-align: right;
|
||||
color: #888;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.ai-info-item__right {
|
||||
flex: 1;
|
||||
color: #222;
|
||||
font-size: 14px;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
58
ui/packages/basic/AiNumber.vue
Normal file
58
ui/packages/basic/AiNumber.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<section class="AiNumber">
|
||||
<el-input v-model="num" :size="size" @input.native="validate" @focus="$emit('focus')" @blur="format" clearable
|
||||
@change="$emit('change')" :validate-event="isRule"></el-input>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiNumber",
|
||||
model: {
|
||||
prop: "value",
|
||||
event: "change"
|
||||
},
|
||||
props: {
|
||||
value: String,
|
||||
size: String,
|
||||
decimal: {type: [String, Number], default: 2},
|
||||
isRule: {type: Boolean, default: true}
|
||||
},
|
||||
computed: {
|
||||
validRegex() {
|
||||
let dec = Number(this.decimal || 0),
|
||||
regex = `^(\\d*\\.?\\d{0,${dec}}).*`
|
||||
return new RegExp(regex)
|
||||
}
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
num: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validate() {
|
||||
let num = JSON.parse(JSON.stringify(this.num))
|
||||
this.num = num.replace(this.validRegex, '$1')
|
||||
},
|
||||
format() {
|
||||
this.num = Number(this.num||0).toString()
|
||||
this.$emit("blur")
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
num(v) {
|
||||
this.$emit("change", v)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.num = this.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiNumber {
|
||||
}
|
||||
</style>
|
||||
98
ui/packages/basic/AiOperate.vue
Normal file
98
ui/packages/basic/AiOperate.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="ai-operate">
|
||||
<template v-if="filterBtns.length <= 3">
|
||||
<el-button
|
||||
v-for="(item, index) in filterBtns"
|
||||
:key="index"
|
||||
type="text"
|
||||
:title="item.text"
|
||||
@click="onClick(index)">
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
type="text"
|
||||
:title="item.text"
|
||||
@click="onClick(index)">
|
||||
{{ item.text }}
|
||||
</el-button>
|
||||
<el-dropdown
|
||||
trigger="click"
|
||||
@command="onDropClick">
|
||||
<el-button
|
||||
style="margin-left: 10px;"
|
||||
title="更多"
|
||||
type="text">
|
||||
更多
|
||||
</el-button>
|
||||
<el-dropdown-menu
|
||||
slot="dropdown">
|
||||
<el-dropdown-item
|
||||
v-for="(item, index) in moreList"
|
||||
:key="index"
|
||||
:command="index">
|
||||
{{ item.text }}
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiOperate',
|
||||
|
||||
props: {
|
||||
permissions: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
|
||||
btns: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
filterBtns() {
|
||||
return this.btns.filter(e => this.permissions(e.permissions))
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
moreList: []
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.filterBtns.forEach((item, index) => {
|
||||
if (index <= 1) {
|
||||
this.list.push(item)
|
||||
} else {
|
||||
this.moreList.push(item)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick(index) {
|
||||
this.$emit('on-click', index)
|
||||
},
|
||||
|
||||
onDropClick(e) {
|
||||
this.onClick(e + 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
76
ui/packages/basic/AiPullDown.vue
Normal file
76
ui/packages/basic/AiPullDown.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<section class="AiPullDown">
|
||||
<div class="line"/>
|
||||
<div class="down-content" @click="handleExpand">
|
||||
<i :class="expandIcon"/>
|
||||
<span>{{ btnText }}</span>
|
||||
</div>
|
||||
<div class="line"/>
|
||||
</section>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiPullDown",
|
||||
props: {
|
||||
target: String,
|
||||
height: {default: 4},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
expand: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleExpand() {
|
||||
this.expand = !this.expand
|
||||
if (this.target) {
|
||||
|
||||
} else this.$emit('change', this.expandStyle)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
btnText() {
|
||||
return this.expand ? '收起高级搜索' : '展开高级搜索'
|
||||
},
|
||||
expandStyle() {
|
||||
let initStyle = {overflow: 'hidden', height: `${this.height}px`}
|
||||
this.expand && (initStyle.height = "auto")
|
||||
return initStyle
|
||||
},
|
||||
expandIcon() {
|
||||
return this.expand ? 'iconfont iconDouble_Up' : 'iconfont iconDouble_Down'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$emit("change", this.expandStyle)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.AiPullDown {
|
||||
display: flex;
|
||||
|
||||
.line {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.down-content {
|
||||
cursor: pointer;
|
||||
padding: 0 8px;
|
||||
height: 24px;
|
||||
border-radius: 0 0 8px 8px;
|
||||
border: 1px solid #eee;
|
||||
border-top: 0;
|
||||
box-sizing: border-box;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
ui/packages/basic/AiRange.vue
Normal file
101
ui/packages/basic/AiRange.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<section class="AiRange" :style="{width}">
|
||||
<el-input type="number" v-model.number="valueStart" size="small" :placeholder="startPlaceholder" @change="saveNum"/>
|
||||
<span class="separator">-</span>
|
||||
<el-input type="number" v-model.number="valueEnd" size="small" :placeholder="endPlaceholder" @change="saveNum"/>
|
||||
<span class="el-icon-circle-close" v-if="isUse" @click="shutoff"/>
|
||||
<div v-else/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiRange",
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
width: {type: String, default: '200px'},
|
||||
startPlaceholder: String,
|
||||
endPlaceholder: String,
|
||||
value: {default: () => []},
|
||||
},
|
||||
computed: {
|
||||
isUse() {
|
||||
return !!this.valueStart || !!this.valueEnd
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueStart: null,
|
||||
valueEnd: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
shutoff() {
|
||||
this.valueStart = null
|
||||
this.valueEnd = null
|
||||
this.saveNum()
|
||||
},
|
||||
saveNum() {
|
||||
this.$emit('change', [this.valueStart, this.valueEnd])
|
||||
},
|
||||
initValue() {
|
||||
let unwatch = this.$watch('value', (v) => {
|
||||
if (this.isUse) unwatch && unwatch()
|
||||
else if (!!v) {
|
||||
this.valueStart = v?.[0] || ""
|
||||
this.valueEnd = v?.[1] || ""
|
||||
unwatch && unwatch()
|
||||
}
|
||||
}, {immediate: true})
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initValue()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.AiRange {
|
||||
display: flex;
|
||||
border: 1px solid #D0D4DC;
|
||||
border-radius: 2px;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
box-sizing: border-box;
|
||||
|
||||
::v-deep.el-input {
|
||||
min-width: 80px;
|
||||
flex: 1;
|
||||
|
||||
.el-input__inner {
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon-circle-close {
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
margin-right: 4px;
|
||||
|
||||
&:hover {
|
||||
color: #5088FF;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #5088FF;
|
||||
|
||||
.el-icon-circle-close {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
106
ui/packages/basic/AiSelect.vue
Normal file
106
ui/packages/basic/AiSelect.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<div class="ai-select">
|
||||
<el-select
|
||||
style="width: 100%;"
|
||||
clearable
|
||||
:value="value"
|
||||
:size="$attrs.size || 'small'"
|
||||
:filterable="isAction"
|
||||
v-bind="$attrs"
|
||||
v-on="$listeners">
|
||||
<template v-if="isAction">
|
||||
<el-option v-for="op in actionOps" :key="op.id"
|
||||
:label="op[actionProp.label]" :value="op[actionProp.value]"/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-option
|
||||
v-for="(item, index) in selectList"
|
||||
:key="index"
|
||||
:label="item.dictName"
|
||||
:value="item.dictValue"/>
|
||||
</template>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiSelect',
|
||||
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
watch: {
|
||||
instance: {
|
||||
deep: true,
|
||||
handler(v) {
|
||||
v && this.isAction && !this.options.toString() && this.getOptions()
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
value: {default: null},
|
||||
selectList: {
|
||||
type: Array
|
||||
},
|
||||
|
||||
width: {
|
||||
type: String,
|
||||
default: '216'
|
||||
},
|
||||
instance: Function,
|
||||
action: {default: ""},
|
||||
prop: {
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
options: [],
|
||||
filter: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectWidth() {
|
||||
if (this.width.indexOf('px') > -1) {
|
||||
return this.width
|
||||
}
|
||||
return `${this.width}px`
|
||||
},
|
||||
isAction() {
|
||||
return !!this.action
|
||||
},
|
||||
actionOps() {
|
||||
return this.options.filter(e => !this.filter || e[this.actionProp.label].indexOf(this.filter) > -1)
|
||||
},
|
||||
actionProp() {
|
||||
return {
|
||||
label: 'label',
|
||||
value: 'id',
|
||||
...this.prop
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getOptions() {
|
||||
this.instance?.post(this.action, null, {
|
||||
params: {size: 999}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.options = res.data.records || res.data
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getOptions()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep .ai-select .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
446
ui/packages/basic/AiTable.vue
Normal file
446
ui/packages/basic/AiTable.vue
Normal file
@@ -0,0 +1,446 @@
|
||||
<template>
|
||||
<div class="ai-table" :class="[isShowBorder ? 'ai-table__border' : 'ai-table__noborder']">
|
||||
<el-table
|
||||
:data="tableData"
|
||||
header-cell-class-name="ai-table__header"
|
||||
cell-class-name="ai-table__cell"
|
||||
row-class-name="ai-table__row"
|
||||
:class="{'ai-header__border': isShowBorder}"
|
||||
:ref="refName"
|
||||
:size="tableSize"
|
||||
:stripe="stripe"
|
||||
:tooltip-effect="tooltipEffect"
|
||||
@selection-change="handleSelectionChange"
|
||||
v-on="$listeners"
|
||||
v-bind="$attrs"
|
||||
v-loading="loading">
|
||||
<template v-for="colConfig in colConfigs.filter(e=>!e.hide)">
|
||||
<slot v-if="colConfig.slot && colConfig.slot !== 'options'" :name="colConfig.slot"/>
|
||||
<component
|
||||
:key="colConfig.id"
|
||||
v-else-if="colConfig.component"
|
||||
:is="colConfig.component"
|
||||
:col-config="colConfig">
|
||||
</component>
|
||||
<el-table-column
|
||||
v-else-if="colConfig.dict"
|
||||
:key="colConfig.id"
|
||||
v-bind="colConfig">
|
||||
<span slot-scope="{row}" :style="{color:colConfig.color||dict.getColor(colConfig.dict, row[colConfig.prop])}">
|
||||
{{ dict.getLabel(colConfig.dict, row[colConfig.prop]) }}
|
||||
</span>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-else-if="colConfig.openType"
|
||||
:key="colConfig.id"
|
||||
v-bind="colConfig">
|
||||
<template v-slot="{row}">
|
||||
<ai-open-data :type="colConfig.openType" :openid="row[colConfig.prop]"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-else-if="colConfig.type"
|
||||
:key="colConfig.id"
|
||||
v-bind="colConfig"
|
||||
:width="colConfig.width || 100"/>
|
||||
<el-table-column v-else v-bind="colConfig" :key="colConfig.id"
|
||||
:show-overflow-tooltip="colConfig['show-overflow-tooltip'] != false">
|
||||
<template slot-scope="scope">
|
||||
<render-slot v-if="colConfig.render" :render="colConfig.render" :row="scope.row" :index="scope.$index"
|
||||
:column="colConfig"/>
|
||||
<span v-else>{{ getValue(colConfig, scope.row) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<slot class="table-options" name="options"></slot>
|
||||
<template #empty>
|
||||
<slot v-if="$scopedSlots.empty" name="empty"/>
|
||||
<div v-else class="no-data" style="height:160px;"/>
|
||||
</template>
|
||||
</el-table>
|
||||
<div class="pagination newPagination" v-if="isShowPagination">
|
||||
<el-pagination
|
||||
background
|
||||
:current-page.sync="page.current"
|
||||
:total="page.total"
|
||||
:page-size="page.size"
|
||||
v-bind="$attrs"
|
||||
:page-sizes="pageSizes"
|
||||
:layout="layout"
|
||||
:pager-count="page.pagerCount"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleChange">
|
||||
<div class="paginationPre">
|
||||
<el-checkbox v-if="isHasPaginationBtnsSlot" :disabled="!tableData.length" :indeterminate="isIndeterminate"
|
||||
:value="checkAll"
|
||||
@click.native="toggleAllSelection">全选
|
||||
</el-checkbox>
|
||||
<slot name="pagination"/>
|
||||
<div class="pagination-btns">
|
||||
<slot name="paginationBtns"></slot>
|
||||
</div>
|
||||
<div class="paginationPre-total" :style="{marginLeft: isHasPaginationBtnsSlot ? '24px' : 0}">共<label class="color-primary">{{ page.total }}</label>条记录
|
||||
</div>
|
||||
</div>
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import moment from 'dayjs'
|
||||
import dict from "../../meta/js/dict"
|
||||
|
||||
let renderSlot = {
|
||||
functional: true,
|
||||
props: {
|
||||
row: Object,
|
||||
render: Function,
|
||||
index: Number,
|
||||
column: {type: Object, default: null},
|
||||
},
|
||||
render: (h, data) => {
|
||||
const params = {
|
||||
row: data.props.row,
|
||||
index: data.props.index
|
||||
}
|
||||
|
||||
if (data.props.column) {
|
||||
params.column = data.props.column
|
||||
}
|
||||
|
||||
return data.props.render(h, params)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AiTable',
|
||||
props: {
|
||||
colConfigs: Array,
|
||||
tableData: Array,
|
||||
current: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
isShowPagination: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
total: {
|
||||
type: Number
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: 'slot,->, prev, pager, next, sizes, jumper'
|
||||
},
|
||||
stripe: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
tooltipEffect: {
|
||||
type: String
|
||||
},
|
||||
tableSize: {
|
||||
type: String
|
||||
},
|
||||
tableRef: String,
|
||||
dict: {default: () => dict},
|
||||
pagerCount: {default: 5},
|
||||
pageSizes: {default: () => [10, 20, 50, 100]}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
chooseList: []
|
||||
}
|
||||
},
|
||||
components: {renderSlot},
|
||||
computed: {
|
||||
refName() {
|
||||
return this.tableRef || `aiTable${new Date().getTime()}`
|
||||
},
|
||||
isShowBorder() {
|
||||
return !!this.$attrs.border || this.$attrs.border === ''
|
||||
},
|
||||
|
||||
isHasPaginationBtnsSlot() {
|
||||
return this.$slots.paginationBtns
|
||||
},
|
||||
|
||||
page() {
|
||||
return {
|
||||
current: this.current,
|
||||
size: this.size,
|
||||
total: this.total,
|
||||
pagerCount: this.pagerCount
|
||||
}
|
||||
},
|
||||
isIndeterminate() {
|
||||
return this.chooseList.length > 0 && this.chooseList.length < this.tableData.length
|
||||
},
|
||||
checkAll() {
|
||||
return this.chooseList.length == this.tableData.length && this.tableData !== 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleChange(e) {
|
||||
this.$emit('update:current', e)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$emit('getList')
|
||||
})
|
||||
},
|
||||
handleSizeChange(e) {
|
||||
this.$emit('update:size', e)
|
||||
this.$nextTick(() => {
|
||||
this.$emit('getList')
|
||||
})
|
||||
},
|
||||
handleSelectionChange(e) {
|
||||
this.chooseList = e
|
||||
this.$emit('handleSelectionChange', e)
|
||||
},
|
||||
getValue(colConfig, row) {
|
||||
if (this.isFunction(colConfig.formart)) {
|
||||
return colConfig.formart.call(this, row[colConfig.prop])
|
||||
}
|
||||
|
||||
if (colConfig.dateFormat) {
|
||||
return moment(row[colConfig.prop]).format(colConfig.dateFormat)
|
||||
}
|
||||
|
||||
return this.isInvalidValue(row[colConfig.prop])
|
||||
},
|
||||
isInvalidValue(value) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
isFunction(fun) {
|
||||
return typeof fun === 'function'
|
||||
},
|
||||
/**
|
||||
* 表格方法代理
|
||||
*/
|
||||
clearSelection() {
|
||||
this.$refs[this.refName].clearSelection()
|
||||
},
|
||||
toggleRowSelection() {
|
||||
this.$refs[this.refName].toggleRowSelection(...arguments)
|
||||
},
|
||||
toggleAllSelection() {
|
||||
this.$refs[this.refName].toggleAllSelection()
|
||||
},
|
||||
toggleRowExpansion() {
|
||||
this.$refs[this.refName].toggleRowExpansion(...arguments)
|
||||
},
|
||||
setCurrentRow() {
|
||||
this.$refs[this.refName].setCurrentRow(...arguments)
|
||||
},
|
||||
clearSort() {
|
||||
this.$refs[this.refName].clearSort()
|
||||
},
|
||||
clearFilter() {
|
||||
this.$refs[this.refName].clearFilter(...arguments)
|
||||
},
|
||||
doLayout() {
|
||||
this.$refs[this.refName].doLayout()
|
||||
},
|
||||
sort() {
|
||||
this.$refs[this.refName].sort(...arguments)
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-table {
|
||||
.color-primary {
|
||||
color: $primaryColor;
|
||||
}
|
||||
|
||||
::v-deep .ai-header__border .ai-table__header {
|
||||
border-bottom: 1px solid $borderColor !important;
|
||||
border-right: 1px solid $borderColor !important;
|
||||
}
|
||||
|
||||
::v-deep .el-table--border {
|
||||
border: 1px solid $borderColor;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
::v-deep .el-table {
|
||||
color: #222;
|
||||
|
||||
.caret-wrapper {
|
||||
height: 24px;
|
||||
|
||||
.ascending {
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.descending {
|
||||
bottom: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
color: #555
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .cell {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
::v-deep .el-table__header {
|
||||
th {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
tr {
|
||||
.cell {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
th:first-child {
|
||||
.cell {
|
||||
padding-left: 40px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-table__body {
|
||||
tr td:first-child .cell {
|
||||
padding-left: 40px !important;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .el-table__fixed-right {
|
||||
.el-table__body {
|
||||
tr td:first-child .cell {
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep .ai-table__header {
|
||||
border-bottom: none;
|
||||
background: #F3F4F5;
|
||||
}
|
||||
|
||||
::v-deep.el-pager {
|
||||
li.active + li {
|
||||
border-left: 1px solid $borderColor;
|
||||
}
|
||||
}
|
||||
|
||||
.newPagination {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
padding: 0 40px !important;
|
||||
|
||||
.el-pagination {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.paginationPre {
|
||||
display: flex;
|
||||
height: 28px;
|
||||
line-height: 1;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
align-items: center;
|
||||
|
||||
.pagination-btns {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: $primaryColor !important;
|
||||
|
||||
::v-deep span, ::v-deep div {
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: $primaryColor !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.paginationPre-total {
|
||||
font-size: 12px;
|
||||
color: #555;
|
||||
|
||||
label {
|
||||
padding: 0 2px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
& > * + * {
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
::v-deep .el-pagination button, .el-pagination span:not([class*=suffix]) {
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
::v-deep.el-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.el-checkbox__input, .el-checkbox__inner {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
min-width: 0 !important;
|
||||
line-height: 1 !important;
|
||||
}
|
||||
|
||||
.el-checkbox__label {
|
||||
font-size: 12px;
|
||||
color: #222222;
|
||||
height: auto !important;
|
||||
line-height: 1 !important;
|
||||
padding-left: 3px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ai-table__noborder {
|
||||
::v-deep .el-table td, ::v-deep .el-table th.is-center {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.el-table::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td {
|
||||
background: #F5F6F9;
|
||||
}
|
||||
|
||||
::v-deep .el-table__fixed-right::before, ::v-deep .el-table__fixed::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
242
ui/packages/basic/AiTableSelect.vue
Normal file
242
ui/packages/basic/AiTableSelect.vue
Normal file
@@ -0,0 +1,242 @@
|
||||
<template>
|
||||
<section class="AiTableSelect">
|
||||
<el-row type="flex">
|
||||
<ai-table v-if="isShowPagination" ref="PendingTable" :tableData="tableData" :total="page.total" :current.sync="page.current"
|
||||
:size.sync="page.size" class="fill" border height="330px" @getList="getTableData" tableSize="mini"
|
||||
:col-configs="[{slot: 'resident'}]" layout="slot,->, prev, pager, next, jumper" :pagerCount="5">
|
||||
<el-table-column slot="resident">
|
||||
<template #header>
|
||||
<b v-text="tableTitle"/>
|
||||
<el-input class="fill searchbar" v-model="search[searchKey]" size="small" placeholder="搜索" clearable
|
||||
@change="page.current=1,getTableData()"/>
|
||||
</template>
|
||||
<template slot-scope="{row}">
|
||||
<slot name="pending" v-if="$scopedSlots.pending" :row="row"/>
|
||||
<el-row v-else type="flex" justify="space-between" @click.native="handleSelect(row)" class="toggle"
|
||||
:class="{selected:findSelected(row)>-1}">
|
||||
<span v-text="row[nodeName]"/>
|
||||
<slot name="extra" v-if="$scopedSlots.extra" :row="row"/>
|
||||
<span v-else v-text="getExtra(row)"/>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
<ai-table v-else ref="PendingTable" :tableData="tableData" class="fill" border height="330px" @getList="getTableData" tableSize="mini"
|
||||
:col-configs="[{slot: 'resident'}]" :isShowPagination="false">
|
||||
<el-table-column slot="resident">
|
||||
<template #header>
|
||||
<b v-text="tableTitle"/>
|
||||
<el-input class="fill searchbar" v-model="search[searchKey]" size="small" placeholder="搜索" clearable
|
||||
@change="page.current=1,getTableData()"/>
|
||||
</template>
|
||||
<template slot-scope="{row}">
|
||||
<slot name="pending" v-if="$scopedSlots.pending" :row="row"/>
|
||||
<el-row v-else type="flex" justify="space-between" @click.native="handleSelect(row)" class="toggle"
|
||||
:class="{selected:findSelected(row)>-1}">
|
||||
<span v-text="row[nodeName]"/>
|
||||
<slot name="extra" v-if="$scopedSlots.extra" :row="row"/>
|
||||
<span v-else v-text="getExtra(row)"/>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
<ai-table :tableData="selected" :col-configs="[{slot:'resident'}]" :isShowPagination="false" border
|
||||
height="330px" tableSize="mini" class="el-table--scrollable-y">
|
||||
<el-table-column slot="resident">
|
||||
<template #header>
|
||||
<b v-text="`已选择`"/>
|
||||
<el-button type="text" @click="selected=[]">清空</el-button>
|
||||
</template>
|
||||
<template slot-scope="{row,$index}">
|
||||
<slot name="selected" v-if="$scopedSlots.selected" :row="row" :index="$index"/>
|
||||
<el-row v-else type="flex" align="middle" justify="space-between">
|
||||
<div v-text="[row[nodeName], getExtra(row)].join(' ')"/>
|
||||
<el-button type="text" @click="selected.splice($index,1)">删除</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ai-table>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 智能列表选择器
|
||||
* @displayName AiTableSelect
|
||||
*/
|
||||
export default {
|
||||
name: "AiTableSelect",
|
||||
model: {
|
||||
event: "change",
|
||||
prop: "value"
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* 接口方法类:必填
|
||||
*/
|
||||
instance: {type: Function, required: true},
|
||||
/**
|
||||
* 接口方法地址:必填
|
||||
*/
|
||||
action: {default: "", required: true},
|
||||
/**
|
||||
* 选择表格标题
|
||||
*/
|
||||
tableTitle: {default: "选择列表"},
|
||||
/**
|
||||
* 选择项
|
||||
* @model
|
||||
*/
|
||||
value: {default: ""},
|
||||
/**
|
||||
* 选择项绑定key
|
||||
*/
|
||||
nodeKey: {default: "id"},
|
||||
/**
|
||||
* 选择项绑定展示值:必填
|
||||
*/
|
||||
nodeName: {default: "name"},
|
||||
/**
|
||||
* 是否多选
|
||||
*/
|
||||
multiple: Boolean,
|
||||
/**
|
||||
* 返回值为选择的对象而不是id
|
||||
*/
|
||||
valueObj: Boolean,
|
||||
/**
|
||||
* 扩展字段,用于右侧展示
|
||||
*/
|
||||
extra: {default: ""},
|
||||
/**
|
||||
* 搜索字段,用于搜索框
|
||||
*/
|
||||
searchKey: {default: "con"},
|
||||
/**
|
||||
* 是否分页
|
||||
*/
|
||||
isShowPagination: {default: true}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
page: {total: 0, current: 1, size: 10},
|
||||
search: {},
|
||||
tableData: [],
|
||||
selected: [],
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
action(v) {
|
||||
v && (this.page.current = 1, this.getTableData())
|
||||
},
|
||||
selected: {
|
||||
deep: true, handler(v) {
|
||||
let ids = v.map(e => e[this.nodeKey] || e).filter(e => !!e)
|
||||
this.$emit("change", this.valueObj ? v : this.multiple ? ids : ids?.toString())
|
||||
this.$emit("select", v)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getExtra(row) {
|
||||
let {extra} = this
|
||||
return extra ? row[extra] : this.idCardNoUtil.hideId(row.idNumber)
|
||||
},
|
||||
initValue() {
|
||||
let unwatch = this.$watch('value', (v) => {
|
||||
if (this.selected.length > 0) unwatch && unwatch()
|
||||
else if (!!v) {
|
||||
this.selected = this.multiple ? [v].flat().filter(e => !!e) || [] : (v?.split(",") || [])
|
||||
unwatch && unwatch()
|
||||
}
|
||||
}, {immediate: true})
|
||||
},
|
||||
getTableData() {
|
||||
let {page, search, action} = this
|
||||
this.instance?.post(action, null, {
|
||||
params: {...page, ...search}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.tableData = res.data.records || res.data || []
|
||||
this.isShowPagination && (this.page.total = res.data.total)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleSelect(row) {
|
||||
let index = this.findSelected(row)
|
||||
if (index > -1) {
|
||||
this.selected.splice(index, 1)
|
||||
} else {
|
||||
if (this.multiple) {
|
||||
this.selected.push(row)
|
||||
} else {
|
||||
this.selected = [row]
|
||||
}
|
||||
}
|
||||
this.$forceUpdate()
|
||||
},
|
||||
findSelected(item) {
|
||||
let {nodeKey} = this
|
||||
return this.selected?.findIndex(e => e[nodeKey] == item[nodeKey])
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$set(this.search, this.searchKey, "")
|
||||
this.initValue()
|
||||
this.getTableData()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.AiTableSelect {
|
||||
.el-row {
|
||||
width: 100%;
|
||||
|
||||
.ai-table + .ai-table {
|
||||
margin-left: 16px;
|
||||
width: 400px;
|
||||
|
||||
.ai-table__header > .cell {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
color: $primaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-table__header {
|
||||
padding: 0 !important;
|
||||
|
||||
& > .cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.searchbar {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.newPagination {
|
||||
height: 32px;
|
||||
padding: 0 !important;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.ai-table__cell {
|
||||
.el-button--text {
|
||||
padding: 0 8px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
163
ui/packages/basic/AiTitle.vue
Normal file
163
ui/packages/basic/AiTitle.vue
Normal file
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<section class="AiTitle" :class="{ 'bottomBorder': isShowBottomBorder, AiTitleSub: isHasSub}">
|
||||
<i class="iconfont iconBack_Large" v-if="isShowBack" @click="onBackBtnClick"/>
|
||||
<div class="fill">
|
||||
<div class="ailist-title">
|
||||
<div class="ailist-title__left">
|
||||
<h2>{{ title }}</h2>
|
||||
<div v-if="isShowIM" class="openIM iconfont iconGroup_IM" @click="openIM"></div>
|
||||
</div>
|
||||
<div class="ailist-title__right">
|
||||
<ai-area
|
||||
v-if="isShowArea"
|
||||
:instance="instance"
|
||||
v-bind="$attrs"
|
||||
:value="value"
|
||||
v-on="$listeners"
|
||||
:areaLevel="areaLevel"
|
||||
:hideLevel="hideLevel"
|
||||
:valueLevel="valueLevel"
|
||||
:disabled="disabled"/>
|
||||
<div class="aititle-right__btns">
|
||||
<slot name="rightBtn"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subtitle" v-if="$scopedSlots.sub">
|
||||
<slot name="sub"/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AiTitle',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
instance: {
|
||||
type: Function
|
||||
},
|
||||
areaLevel: [String, Number],
|
||||
hideLevel: {default: 2},
|
||||
valueLevel: [String, Number],
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: String
|
||||
},
|
||||
isShowIM: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShowArea: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
openIM: {
|
||||
type: Function
|
||||
},
|
||||
isShowBack: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShowBottomBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
classic: Boolean
|
||||
},
|
||||
|
||||
computed: {
|
||||
isHasSub() {
|
||||
return this.$slots.sub
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onBackBtnClick() {
|
||||
this.$emit('onBackClick')
|
||||
this.$emit('back')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.AiTitle {
|
||||
display: flex;
|
||||
|
||||
.ailist-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 48px;
|
||||
|
||||
.ailist-title__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& > i {
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #222;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.openIM {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.ailist-title__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
::v-deep.el-button {
|
||||
margin-left: 8px !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.AiTitleSub {
|
||||
height: auto;
|
||||
padding: 16px 0;
|
||||
|
||||
.ailist-title {
|
||||
height: auto;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
&.bottomBorder {
|
||||
border-bottom: 1px solid #D8DCE3;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
width: 100%;
|
||||
color: #888888;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.iconBack_Large {
|
||||
line-height: 48px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
44
ui/packages/basic/AiTransSpeech.vue
Normal file
44
ui/packages/basic/AiTransSpeech.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-row type="flex" align="middle">
|
||||
<el-button type="text" size="small" :loading="loading" icon="iconfont iconClock" title="语音播报"
|
||||
@click="getSpeechByContent"/>
|
||||
<ai-audio v-if="speech" :src="speech" skin="flat"/>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiTransSpeech",
|
||||
props: {
|
||||
instance: {type: Function, required: true},
|
||||
content: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
speech: "",
|
||||
loading: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSpeechByContent() {
|
||||
this.loading = true
|
||||
this.instance.post("/app/msc/transToSpeech", null, {
|
||||
params: {
|
||||
fileName: "demo",
|
||||
words: this.content
|
||||
}
|
||||
}).then(res => {
|
||||
this.loading = false
|
||||
if (res && res.data) {
|
||||
let url = res.data.join("")
|
||||
this.speech = url.substring(0, url.indexOf(";"))
|
||||
}
|
||||
}).catch(() => this.loading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
514
ui/packages/basic/AiUploader.vue
Normal file
514
ui/packages/basic/AiUploader.vue
Normal file
@@ -0,0 +1,514 @@
|
||||
<template>
|
||||
<section class="uploader">
|
||||
<el-upload
|
||||
action
|
||||
multiple
|
||||
ref="upload"
|
||||
:class="{validError:!validateState}"
|
||||
:http-request="submitUpload"
|
||||
:on-remove="handleRemove"
|
||||
:on-change="handleChange"
|
||||
:before-upload="onBeforeUpload"
|
||||
:file-list="fileList"
|
||||
:limit="limit"
|
||||
:disabled="disabled"
|
||||
:list-type="isImg ? 'picture-card' : 'text'"
|
||||
:accept="accept"
|
||||
:show-file-list="!isSingle"
|
||||
:on-preview="handlePictureCardPreview"
|
||||
:auto-upload="isAutoUpload"
|
||||
:on-exceed="handleExceed">
|
||||
<template v-if="!disabled">
|
||||
<template v-if="hasUploaded&&isSingle">
|
||||
<div class="fileItem">
|
||||
<div class="uploadFile" @click.stop>
|
||||
<ai-icon type="svg" :icon="uploadFile.icon"/>
|
||||
<div class="info">
|
||||
<span v-text="uploadFile.name"/>
|
||||
<span class="size" v-text="uploadFile.size"/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button>重新选择</el-button>
|
||||
<el-button v-if="clearable" plain type="danger" @click.stop="handleClear">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="limit > fileList.length">
|
||||
<slot v-if="hasTriggerSlot" name="trigger"/>
|
||||
<div v-else class="uploaderBox">
|
||||
<span class="iconfont" :class="isImg ? 'iconPhoto' : 'iconAdd'"/>
|
||||
<p>上传{{ isImg ? '图片' : '附件' }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<div slot="tip" class="el-upload__tip" v-if="showTips">
|
||||
<p v-if="fileType === 'img' && !acceptType && !hasTipsSlot">最多上传{{
|
||||
limit
|
||||
}}张图片,单个文件最大10MB,支持jpg、jpeg、png格式</p>
|
||||
<p v-if="fileType === 'file' && !acceptType && !hasTipsSlot">最多上传{{ limit }}个附件,单个文件最大10MB</p>
|
||||
<p v-if="fileType === 'file' && !acceptType && !hasTipsSlot">
|
||||
支持.zip、.rar、.doc、.docx、.xls、.xlsx、.ppt、.pptx、.pdf、.txt、.jpg、.png格式</p>
|
||||
<p>
|
||||
<slot name="tips" v-if="hasTipsSlot"></slot>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<el-dialog :visible.sync="dialog" title="图片预览编辑器" :modal="false" :show-close="false" append-to-body>
|
||||
<vue-cropper
|
||||
ref="cropper"
|
||||
style="height: 400px;"
|
||||
:img="fileList.length ? fileList[0].url : ''" v-bind="crop"/>
|
||||
<div style="text-align: center;margin-top: 10px;">
|
||||
<el-radio-group v-if="crop.fixed" size="small" v-model="currFixedIndex" @change="onFixedChange"
|
||||
style="margin-right: 8px;">
|
||||
<el-radio-button
|
||||
:label="0"
|
||||
size="small">
|
||||
1.6:1
|
||||
</el-radio-button>
|
||||
<el-radio-button
|
||||
:label="1"
|
||||
size="small">
|
||||
4:3
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<el-button size="small" circle icon="el-icon-refresh-right" @click="$refs.cropper.rotateRight()"></el-button>
|
||||
<el-button size="small" circle icon="el-icon-refresh-left" @click="$refs.cropper.rotateLeft()"></el-button>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<el-popconfirm title="是否关闭图片预览器,并上传图片?" @confirm="previewCrop">
|
||||
<el-button slot="reference" type="primary">保存</el-button>
|
||||
</el-popconfirm>
|
||||
<el-button @click="onClose">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<div class="images" v-viewer="{movable: true}" v-show="false">
|
||||
<img v-for="(item, index) in imgList" :src="item" :key="index" alt="">
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {VueCropper} from 'vue-cropper'
|
||||
import 'viewerjs/dist/viewer.css'
|
||||
import Viewer from 'v-viewer'
|
||||
import Vue from "vue";
|
||||
|
||||
Viewer.setDefaults({
|
||||
zIndex: 20170
|
||||
})
|
||||
Vue.use(Viewer)
|
||||
export default {
|
||||
name: 'AiUploader',
|
||||
components: {VueCropper},
|
||||
inject: {
|
||||
elFormItem: {default: ""},
|
||||
elForm: {default: ''},
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
value: {default: () => []},
|
||||
url: {
|
||||
type: String,
|
||||
default: '/admin/file/add'
|
||||
},
|
||||
isShowTip: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isWechat: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxSize: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
instance: Function,
|
||||
acceptType: {type: String},
|
||||
fileType: {type: String, default: 'img'},
|
||||
limit: {type: Number, default: 9},
|
||||
disabled: {type: Boolean, default: false},
|
||||
isCrop: {type: Boolean, default: false},
|
||||
cropOps: Object,
|
||||
isImport: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearable: {default: true},
|
||||
valueIsUrl: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fileList: [],
|
||||
dialog: false,
|
||||
currFixedIndex: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(v) {
|
||||
this.dispatch('ElFormItem', 'el.form.change', [v]);
|
||||
if (v?.length > 0) {
|
||||
this.fileList = this.valueIsUrl ? v?.split(",")?.map(url => ({url})) : [...v]
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isImg() {
|
||||
return this.fileType === 'img'
|
||||
},
|
||||
validateState() {
|
||||
return ['', 'success'].includes(this.elFormItem?.validateState)
|
||||
},
|
||||
isAutoUpload() {
|
||||
return !(this.isCrop || this.isImport);
|
||||
},
|
||||
hasTipsSlot() {
|
||||
return this.$slots.tips
|
||||
},
|
||||
hasTriggerSlot() {
|
||||
return this.$slots.trigger
|
||||
},
|
||||
accept() {
|
||||
if (this.acceptType) {
|
||||
return this.acceptType
|
||||
}
|
||||
|
||||
return this.isImg ? '.jpg,.png,.jpeg' : '.zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.jpg,.png,.mp4'
|
||||
},
|
||||
crop() {
|
||||
return {
|
||||
autoCrop: true,
|
||||
outputType: 'png',
|
||||
fixedBox: false,
|
||||
fixed: true,
|
||||
fixedNumber: [1.6, 1],
|
||||
width: 0,
|
||||
height: 0,
|
||||
...this.cropOps
|
||||
}
|
||||
},
|
||||
imgList() {
|
||||
return this.fileList.map(v => v.url)
|
||||
},
|
||||
isSingle() {
|
||||
return this.limit == 1 && this.isImport
|
||||
},
|
||||
showTips() {
|
||||
return this.isShowTip || this.$slots.tips
|
||||
},
|
||||
hasUploaded() {
|
||||
return this.fileList?.length > 0
|
||||
},
|
||||
uploadFile() {
|
||||
let file = this.fileList?.[0],
|
||||
size = Number(file.size),
|
||||
icon = "iconTxt"
|
||||
//显示大小
|
||||
if (size > Math.pow(1024, 2)) {
|
||||
size = (size / Math.pow(1024, 2)).toFixed(1) + 'MB'
|
||||
} else {
|
||||
size = (size / 1024).toFixed(1) + 'KB'
|
||||
}
|
||||
//显示图标
|
||||
if (/\.(xls|xlsx)$/.test(file.name)) {
|
||||
icon = "iconExcel"
|
||||
} else if (/\.(zip)$/.test(file.name)) {
|
||||
icon = "iconZip"
|
||||
} else if (/\.(rar)$/.test(file.name)) {
|
||||
icon = "iconRar"
|
||||
} else if (/\.(png)$/.test(file.name)) {
|
||||
icon = "iconPng"
|
||||
} else if (/\.(pptx|ppt)$/.test(file.name)) {
|
||||
icon = "iconPPT"
|
||||
} else if (/\.(doc|docx)$/.test(file.name)) {
|
||||
icon = "iconWord"
|
||||
}
|
||||
|
||||
return {...file, size, icon}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onFixedChange(e) {
|
||||
this.fixedNumber = e === 0 ? [1.6, 1] : [4, 3]
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.cropper.goAutoCrop()
|
||||
})
|
||||
},
|
||||
handleChange(file, fileList) {
|
||||
if (this.isImport) {
|
||||
if (!this.onOverSize(file)) {
|
||||
this.fileList = []
|
||||
return false
|
||||
}
|
||||
this.fileList = fileList
|
||||
this.emitChange(fileList)
|
||||
return false
|
||||
}
|
||||
if (this.isCrop) {
|
||||
if (file.raw.type === 'image/gif') {
|
||||
this.$message.error(`不支持gif格式的图片`)
|
||||
this.fileList = []
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
if (!this.onOverSize(file)) {
|
||||
this.fileList = []
|
||||
|
||||
return false
|
||||
}
|
||||
this.dialog = true
|
||||
this.fileList = fileList
|
||||
} else {
|
||||
this.fileList = fileList
|
||||
}
|
||||
},
|
||||
handleExceed(files) {
|
||||
if (this.isSingle && files[0]) {
|
||||
this.$refs.upload?.clearFiles()
|
||||
this.$refs.upload?.handleStart(files[0])
|
||||
} else this.$message.warning(`最多上传${this.limit}个${this.isImg ? '图片' : '文件'}`)
|
||||
},
|
||||
handlePictureCardPreview(file) {
|
||||
if (this.fileType !== 'img') return
|
||||
|
||||
const index = this.imgList.indexOf(file.url)
|
||||
const viewer = this.$el.querySelector('.images').$viewer
|
||||
viewer.view(index)
|
||||
},
|
||||
|
||||
handleRemove(file, fileList) {
|
||||
this.fileList = fileList
|
||||
this.emitChange(fileList)
|
||||
},
|
||||
emitChange(files) {
|
||||
this.$emit('change', this.valueIsUrl ? files?.map(e => e.url)?.toString() : files)
|
||||
},
|
||||
handleClear() {
|
||||
this.fileList = []
|
||||
},
|
||||
getExtension(name) {
|
||||
return name.substring(name.lastIndexOf('.'))
|
||||
},
|
||||
|
||||
onOverSize(e) {
|
||||
const isLt10M = e.size / 1024 / 1024 < this.maxSize
|
||||
const suffixName = this.getExtension(e.name)
|
||||
const suffixNameList = this.accept.split(',')
|
||||
|
||||
if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) {
|
||||
this.$message.error(`不支持该格式`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (!isLt10M) {
|
||||
this.$message.error(`${this.isImg ? '图片' : '文件'}大小不超过${this.maxSize}MB!`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
onBeforeUpload(event) {
|
||||
return this.onOverSize(event)
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.fileList = []
|
||||
this.dialog = false
|
||||
},
|
||||
|
||||
submitUpload(file) {
|
||||
let formData = new FormData()
|
||||
formData.append('file', file.file)
|
||||
this.instance.post(this.url, formData, {
|
||||
withCredentials: false
|
||||
}).then(res => {
|
||||
if (res?.code == 0) {
|
||||
if (this.isWechat) {
|
||||
this.emitChange([{
|
||||
...res.data.file,
|
||||
media: res.data.media
|
||||
}])
|
||||
this.fileList.forEach(item => {
|
||||
if (item.uid === file.file.uid) {
|
||||
item.id = res.data.file.id
|
||||
item.path = res.data.file.url
|
||||
item.url = res.data.file.url,
|
||||
item.media = res.data.media
|
||||
}
|
||||
})
|
||||
this.emitChange(this.fileList)
|
||||
this.$message.success('上传成功')
|
||||
return false
|
||||
}
|
||||
let data = res.data[0].split(';')
|
||||
this.fileList.forEach(item => {
|
||||
if (item.uid === file.file.uid) {
|
||||
item.id = data[1]
|
||||
item.path = data[0]
|
||||
item.url = data[0]
|
||||
}
|
||||
})
|
||||
this.emitChange(this.fileList)
|
||||
this.$message.success('上传成功')
|
||||
}
|
||||
})
|
||||
},
|
||||
previewCrop() {
|
||||
this.$refs.cropper.getCropBlob(data => {
|
||||
data.name = this.fileList[0].name;
|
||||
this.fileList[0].file = new window.File([data], data.name, {type: data.type})
|
||||
this.fileList[0].file.uid = this.fileList[0].uid
|
||||
this.submitUpload(this.fileList[0])
|
||||
this.dialog = false
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 表单验证
|
||||
* @param componentName
|
||||
* @param eventName
|
||||
* @param params
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root;
|
||||
let name = parent.$options.componentName;
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent;
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.componentName;
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uploader {
|
||||
line-height: 1;
|
||||
|
||||
::v-deep.el-upload {
|
||||
width: 100%;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
::v-deep.validError {
|
||||
.el-button {
|
||||
border-color: #f46;
|
||||
color: #f46;
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep.el-upload--picture-card {
|
||||
border: none;
|
||||
}
|
||||
|
||||
::v-deep .el-list-leave-active, ::v-deep .el-upload-list__item {
|
||||
transition: all 0s !important;
|
||||
}
|
||||
|
||||
::v-deep.el-upload-list--picture-card .el-upload-list__item {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::v-deep.el-upload--picture-card {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.el-upload__tip p {
|
||||
color: #999;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
::v-deep.fileItem {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #D0D4DC;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
cursor: default;
|
||||
|
||||
.uploadFile {
|
||||
text-align: start;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
color: #222;
|
||||
|
||||
.AiIcon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 8px;
|
||||
margin-right: 40px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.size {
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uploaderBox {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
line-height: 1;
|
||||
background: #F3F4F7;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #D0D4DC;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 32px;
|
||||
color: #8899bb;
|
||||
|
||||
&:hover {
|
||||
color: #8899bb;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding-top: 4px;
|
||||
color: #555;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
86
ui/packages/basic/AiWrapper.vue
Normal file
86
ui/packages/basic/AiWrapper.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<div class="ai-wrapper">
|
||||
<ai-title class="w100" v-if="title" :title="title"/>
|
||||
<div class="ai-wrapper-content" :class="{border}">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AiTitle from "./AiTitle";
|
||||
|
||||
export default {
|
||||
name: 'AiWrapper',
|
||||
components: {AiTitle},
|
||||
componentName: 'AiWrapper',
|
||||
|
||||
provide() {
|
||||
return {
|
||||
AiWrapper: this
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
'label-width': {
|
||||
type: String
|
||||
},
|
||||
|
||||
columnsNumber: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
border: Boolean,
|
||||
title: String
|
||||
},
|
||||
|
||||
computed: {
|
||||
autoWidth() {
|
||||
return ((1 / this.columnsNumber) * 100).toFixed(2) + '%'
|
||||
},
|
||||
|
||||
autoLableWidth() {
|
||||
return this.labelWidth
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ai-wrapper {
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ai-wrapper-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
&.border {
|
||||
border-left: 1px solid $borderColor;
|
||||
border-top: 1px solid $borderColor;
|
||||
|
||||
.ai-info-item {
|
||||
border-bottom: 1px solid $borderColor;
|
||||
border-right: 1px solid $borderColor;
|
||||
margin-bottom: 0;
|
||||
line-height: 32px;
|
||||
|
||||
.ai-info-item__left {
|
||||
background: rgba(0, 0, 0, .03);
|
||||
padding-right: 16px;
|
||||
border-right: 1px solid $borderColor;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.el-textarea__inner {
|
||||
border: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
368
ui/packages/common/AiArea.vue
Normal file
368
ui/packages/common/AiArea.vue
Normal file
@@ -0,0 +1,368 @@
|
||||
<template>
|
||||
<section class="ai-area">
|
||||
<div v-if="inputClicker" @click="chooseArea" class="input-clicker">
|
||||
<el-row type="flex" justify="space-between">
|
||||
<div class="prepend">
|
||||
<i style="font-size: 16px" class="iconfont iconLocation"/>
|
||||
切换地区
|
||||
</div>
|
||||
<div class="content nowarp-text fill" v-text="fullName"/>
|
||||
<i class="iconfont iconChange pad-r10"/>
|
||||
</el-row>
|
||||
</div>
|
||||
<el-button v-else-if="!customClicker" class="area-btn" type="primary" size="mini" @click="chooseArea">
|
||||
{{ btnShowArea ? selectedName : "切换地区" }}
|
||||
</el-button>
|
||||
<a class="custom-clicker" v-else @click="chooseArea">
|
||||
<slot :areaname="selectedName" :fullname="fullName" :id="selected" :areatype="selectedAreaType"/>
|
||||
</a>
|
||||
<ai-dialog :visible.sync="dialog" title="选择地区" width="60%" @onConfirm="confirmArea" @open="selected=(value||'')">
|
||||
<ai-highlight content="您当前选择 @v" :value="selectedName" color="#333" bold/>
|
||||
<div class="area_edge">
|
||||
<div class="area-box" v-for="ops in showOps">
|
||||
<h2 v-text="ops.header"/>
|
||||
<div class="area-item" :class="{selected: selectedMap.includes(area.id)}" v-for="area in ops.list"
|
||||
@click="getChildrenAreas(area)">
|
||||
<ai-badge>
|
||||
<span>{{ area.name }}</span>
|
||||
<div slot="badge" v-if="showBadge&&area.tipName" :class="getLabelClassByLabelType(area.labelType)">
|
||||
{{ area.tipName }}
|
||||
</div>
|
||||
</ai-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ai-dialog>
|
||||
</section>
|
||||
</template>
|
||||
<script>
|
||||
import AiHighlight from "../layout/AiHighlight";
|
||||
import instance from "../../meta/js/request";
|
||||
import Area from "../../meta/js/area";
|
||||
|
||||
export default {
|
||||
name: 'AiArea',
|
||||
components: {AiHighlight},
|
||||
inject: {
|
||||
elFormItem: {default: ""},
|
||||
elForm: {default: ''},
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change'
|
||||
},
|
||||
props: {
|
||||
instance: {default: () => instance},
|
||||
action: String,
|
||||
areaLevel: {type: [Number, String], default: 5},
|
||||
btnShowArea: {type: Boolean, default: false},
|
||||
customClicker: {type: Boolean, default: false},
|
||||
disabled: {type: Boolean, default: false},
|
||||
hideLevel: {type: [Number, String], default: 0},
|
||||
inputClicker: {type: Boolean, default: true},
|
||||
provinceAction: String,
|
||||
separator: {type: String, default: ""},
|
||||
showBadge: {type: Boolean, default: true},
|
||||
value: String,
|
||||
valueLevel: {type: [Number, String], default: -1}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
areaOps: [],
|
||||
fullName: '',
|
||||
dialog: false,
|
||||
ProvinceCityCounty: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentArea: v => v.selected || v.value,
|
||||
selectedArea: v => new Area(v.currentArea, v.hashMap),
|
||||
startLevel: v => Number(v.hideLevel) || 0,//地区最高可选行政地区等级
|
||||
endLevel: v => Number(v.areaLevel) || 0,//地区最低可选行政地区等级
|
||||
selectedName: v => v.selectedArea.name || "无",
|
||||
validateState: v => ['', 'success'].includes(v.elFormItem?.validateState),
|
||||
selectedMap: v => v.selectedArea.areaMap,
|
||||
hashMap() {
|
||||
//地区数据缓存器,用于快速获取数据
|
||||
const hash = {}
|
||||
this.areaOps.flat().map(e => hash[e.id] = e)
|
||||
return hash
|
||||
},
|
||||
showOps() {
|
||||
const levelLabels = {
|
||||
0: "省/直辖市",
|
||||
1: "市",
|
||||
2: "县/区",
|
||||
3: "乡/镇/街道",
|
||||
4: "村/社区"
|
||||
}
|
||||
let ops = this.areaOps.map((list, i) => ({
|
||||
header: levelLabels[i], list
|
||||
})).slice(Math.max(0, this.startLevel), this.endLevel)
|
||||
if (this.startLevel > 0 && ops.length > 0) {
|
||||
ops[0].list = ops[0].list.filter(e => e.id == this.selectedMap[this.startLevel])
|
||||
}
|
||||
return ops
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(v) {
|
||||
this.dispatch('ElFormItem', 'el.form.change', [v]);
|
||||
this.initAreaName()
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
dispatch(componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root;
|
||||
let name = parent.$options.componentName;
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent;
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.componentName;
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
chooseArea() {
|
||||
if (this.disabled) return
|
||||
this.selected = this.$copy(this.value)
|
||||
this.initOptions().then(() => this.dialog = true)
|
||||
},
|
||||
confirmArea() {
|
||||
if (this.valueLevel > -1) {
|
||||
this.$emit("change", this.selectedMap[this.valueLevel])
|
||||
} else {
|
||||
this.$emit("change", this.selected)
|
||||
}
|
||||
this.$emit("area", this.selected, this.selectedArea);
|
||||
this.dialog = false
|
||||
},
|
||||
getChildrenAreas(area) {
|
||||
this.selected = area.id;
|
||||
const level = Area.getLevelByAreaId(area.id);
|
||||
if (level < 4) {
|
||||
this.getAreasByParentId(area.id).then(list => {
|
||||
this.areaOps.splice(level + 1, 5, list)
|
||||
})
|
||||
}
|
||||
},
|
||||
getAreasByParentId(id) {
|
||||
const level = Area.getLevelByAreaId(id)
|
||||
return new Promise(resolve => {
|
||||
if (level < 2) {
|
||||
this.getProvinceCityCounty().then(() => {
|
||||
resolve(this.ProvinceCityCounty.filter(e => e.parentId == id))
|
||||
})
|
||||
} else {
|
||||
this.instance.post(this.action || "/admin/area/queryAreaByParentId", null, {
|
||||
withoutToken: true,
|
||||
params: {id}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
resolve(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
getLabelClassByLabelType(type) {
|
||||
let cls = "badge-label"
|
||||
switch (type) {
|
||||
case '1':
|
||||
cls += ' label-town'
|
||||
break;
|
||||
case '3':
|
||||
cls += ' label-village'
|
||||
break;
|
||||
default:
|
||||
cls += ' label-poor'
|
||||
break
|
||||
}
|
||||
return cls
|
||||
},
|
||||
getProvinceCityCounty() {
|
||||
return new Promise(resolve => {
|
||||
if (localStorage.getItem("ProvinceCityCounty")) {
|
||||
resolve(JSON.parse(localStorage.getItem("ProvinceCityCounty")))
|
||||
} else {
|
||||
this.instance.post(this.provinceAction || "/admin/area/queryProvinceListContainCity", null, {
|
||||
withoutToken: true
|
||||
}).then(res => {
|
||||
if (res && res.data) {
|
||||
localStorage.setItem("ProvinceCityCounty", JSON.stringify(res.data))
|
||||
resolve(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}).then(list => this.ProvinceCityCounty = list)
|
||||
},
|
||||
initOptions() {
|
||||
this.areaOps = []
|
||||
let map = {};
|
||||
return Promise.all([null, ...this.selectedMap].map((id, i) => this.getAreasByParentId(id).then(list => map[i] = list))).then(() => {
|
||||
this.areaOps = Object.values(map)
|
||||
})
|
||||
},
|
||||
initAreaName() {
|
||||
if (this.value) {
|
||||
Area.createByAction(this.currentArea, this.instance).then(names => {
|
||||
this.selectedArea.getName(names)
|
||||
this.fullName = this.selectedArea.nameMap.join(this.separator)
|
||||
this.$emit("update:name", this.selectedName)
|
||||
this.$emit("fullname", this.fullName)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ai-area {
|
||||
.area-btn {
|
||||
box-shadow: 0 2px 8px 0 rgba(76, 132, 255, 0.6);
|
||||
}
|
||||
|
||||
.input-clicker {
|
||||
width: 320px;
|
||||
cursor: pointer;
|
||||
border: 1px solid #D0D4DC;
|
||||
line-height: 32px;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
|
||||
.prepend {
|
||||
background: rgba(245, 245, 245, 1);
|
||||
width: auto;
|
||||
border-right: 1px solid #D0D4DC;
|
||||
padding: 0 8px;
|
||||
white-space: nowrap;
|
||||
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: left;
|
||||
padding-left: 14px;
|
||||
padding-right: 8px;
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.suffix {
|
||||
width: auto;
|
||||
padding: 0 12px
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: $primaryColor;
|
||||
}
|
||||
}
|
||||
|
||||
.custom-clicker {
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.area_edge {
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.area-box {
|
||||
box-shadow: 0px -1px 0px 0px rgba(238, 238, 238, 1);
|
||||
padding: 16px 0 8px 0;
|
||||
|
||||
& > section {
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
& > h2 {
|
||||
color: rgba(51, 51, 51, 1);
|
||||
line-height: 22px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.area-item {
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #D0D4DC;
|
||||
margin: 8px 8px 8px 0;
|
||||
padding: 3px 10px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: normal;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
color: rgba($primaryColor, .8);
|
||||
border-color: rgba($primaryColor, .8);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ddd;
|
||||
line-height: normal;
|
||||
margin: 5px;
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: rgba($primaryColor, .8);
|
||||
border-color: rgba($primaryColor, .8);
|
||||
}
|
||||
}
|
||||
|
||||
.selected {
|
||||
color: rgba($primaryColor, .8);
|
||||
border-color: rgba($primaryColor, .8);
|
||||
}
|
||||
}
|
||||
|
||||
.badge-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
border-radius: 15px;
|
||||
color: #fff;
|
||||
width: 12px;
|
||||
letter-spacing: 10px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
padding: 3px 5px;
|
||||
white-space: nowrap;
|
||||
transition: width 1s, letter-spacing 0.05s;
|
||||
|
||||
&.label-town {
|
||||
background: rgba($primaryColor, .8);
|
||||
}
|
||||
|
||||
&.label-village {
|
||||
background: rgba($primaryColor, .8);
|
||||
}
|
||||
|
||||
&.label-poor {
|
||||
background: #ffb14c;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
width: initial;
|
||||
letter-spacing: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
295
ui/packages/common/AiAreaGet.vue
Normal file
295
ui/packages/common/AiAreaGet.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<template>
|
||||
<section class="AiAreaGet">
|
||||
<el-cascader v-if="refresh" ref="areaCascader" :value="value" size="small" :props="props" :show-all-levels="showAll"
|
||||
:options="options" @visible-change="editing=true" clearable
|
||||
filterable :before-filter="handleFindArea" @change="handleAfterFilter"
|
||||
v-bind="$attrs" v-on="$listeners" popper-class="popperSelectors"/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* 智能地区选择器
|
||||
* @displayName AiAreaGet
|
||||
*/
|
||||
export default {
|
||||
name: "AiAreaGet",
|
||||
inject: {
|
||||
elFormItem: {default: ""},
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* 接口方法类:必填
|
||||
*/
|
||||
instance: {default: () => null},
|
||||
/**
|
||||
* 绑定地区编码
|
||||
* @model
|
||||
*/
|
||||
value: {default: ""},
|
||||
/**
|
||||
* 是否多选
|
||||
*/
|
||||
multiple: Boolean,
|
||||
/**
|
||||
* 获取地区信息接口地址,默认为:/admin/area/queryAreaByParentId
|
||||
*/
|
||||
action: {default: "/admin/area/queryAreaByParentId"},
|
||||
/**
|
||||
* 限制获取地区编码的范围,1:省 2:地级市 3:县/区 4:镇/街道 5:村/社区
|
||||
* @values 1,2,3,4,5
|
||||
*/
|
||||
valueLevel: {default: 5},
|
||||
/**
|
||||
* 指定根级地区范围
|
||||
*/
|
||||
root: {default: ""},
|
||||
/**
|
||||
* 获取地区名称,支持.sync 双向获取绑定
|
||||
*/
|
||||
name: {default: ""},
|
||||
/**
|
||||
* 显示完整地区名称
|
||||
*/
|
||||
showAll: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
rules: [10, 8, 6, 3, 0],
|
||||
cacheOptions: [],
|
||||
editing: false,
|
||||
filterData: [],
|
||||
caches: [],
|
||||
roots: [],
|
||||
refresh: true,
|
||||
rootLoad: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(v) {
|
||||
!this.editing && !this.caches.includes(this.value) && this.getCacheOptions()
|
||||
this.dispatch('ElFormItem', 'el.form.change', [v]);
|
||||
setTimeout(() => this.$emit("update:name", this.$refs.areaCascader?.inputValue))
|
||||
},
|
||||
root() {
|
||||
if (this.value) {
|
||||
this.getCacheOptions()
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.refresh = false
|
||||
this.$nextTick(() => this.refresh = true)
|
||||
}, 200)
|
||||
}
|
||||
},
|
||||
options: {
|
||||
handler() {
|
||||
this.$nextTick(() => this.$forceUpdate())
|
||||
}, deep: true, immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
fullArea() {
|
||||
const length = 12,
|
||||
getFull = v => this.rules.map(e => {
|
||||
let reg = new RegExp(`(\\d{${length-e}})\\d{${e}}`, 'g')
|
||||
return v?.replace(reg, '$1' + Array(e).fill(0).join(''))
|
||||
}).filter((e, i) => i <= this.getLevel(v))
|
||||
return this.multiple ? [this.value].flat()?.map(e => getFull(e)) : getFull(this.value)
|
||||
},
|
||||
props() {
|
||||
return {
|
||||
label: 'name',
|
||||
value: 'id',
|
||||
lazy: true,
|
||||
multiple: this.multiple,
|
||||
checkStrictly: true,
|
||||
emitPath: false,
|
||||
lazyLoad: (node, resolve) => {
|
||||
if (!(this.caches.includes(node.value) && this.fullArea.includes(node.value)) || node.loading) {
|
||||
if (node?.level == 0) {
|
||||
this.getRoots(resolve, "lazyLoad")
|
||||
} else if (node?.level > 0 && node.children?.length == 0) {
|
||||
let {id, leaf} = node.data
|
||||
leaf ? resolve([]) : this.getAreasByParent(id, resolve)
|
||||
} else resolve([])
|
||||
} else resolve([])
|
||||
}
|
||||
}
|
||||
},
|
||||
options() {
|
||||
return [...this.cacheOptions, ...this.filterData]
|
||||
},
|
||||
filtering() {
|
||||
let v = this.$refs?.areaCascader?.filtering
|
||||
if (!v) this.filterData = []
|
||||
return v
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getLevel(code) {
|
||||
let lv = -1
|
||||
this.rules.some((e, index) => {
|
||||
let reg = new RegExp(`0{${e}}$`, "g")
|
||||
if (reg.test(code)) {
|
||||
lv = index
|
||||
return true
|
||||
}
|
||||
})
|
||||
return lv
|
||||
},
|
||||
getRoots(resolve, from) {
|
||||
let url = '/admin/area/queryProvinceList'
|
||||
if (this.root) {
|
||||
url = "/admin/area/queryAreaByAreaid"
|
||||
if (this.rootLoad == this.root) {
|
||||
let waitRoots = (count = 0) => setTimeout(() => {
|
||||
if (this.roots.length > 0 || count == 5) {
|
||||
resolve(this.roots)
|
||||
} else waitRoots(++count)
|
||||
}, 500)
|
||||
return from == "lazyLoad" ? '' : waitRoots()
|
||||
}
|
||||
}
|
||||
this.rootLoad = JSON.parse(JSON.stringify(this.root))
|
||||
if (this.roots.some(e => e.id == this.root)) {
|
||||
resolve(this.roots)
|
||||
} else this.instance.post(url, null, {
|
||||
params: {id: this.root, from}, withoutToken: true
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
this.roots = [res.data].flat().map(e => ({...e, leaf: e.type == this.valueLevel}))
|
||||
resolve(this.roots)
|
||||
}
|
||||
})
|
||||
},
|
||||
getAreasByParent(id, resolve) {
|
||||
id && this.instance.post(this.action, null, {
|
||||
params: {id}, withoutToken: true,
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
resolve(res.data.map(e => ({...e, leaf: e.type == this.valueLevel})))
|
||||
}
|
||||
})
|
||||
},
|
||||
async getCacheOptions() {
|
||||
let finished = 0
|
||||
const hasChild = ids => ids?.some(e => this.fullArea?.flat()?.includes(e)),
|
||||
appendChildren = (area, resolve) => {
|
||||
let values = [this.value].flat()
|
||||
if (values.includes(area.id)) {
|
||||
finished++
|
||||
if (finished == values.length) {
|
||||
this.$emit("update:name", area.name)
|
||||
resolve()
|
||||
}
|
||||
} else this.getAreasByParent(area.id, data => {
|
||||
this.$set(area, "children", data)
|
||||
data.map(d => {
|
||||
this.caches.push(d.id)
|
||||
hasChild([d.id]) && appendChildren(d, resolve)
|
||||
})
|
||||
})
|
||||
}
|
||||
if (!!this.value?.toString()) {
|
||||
this.cacheOptions = []
|
||||
this.caches = []
|
||||
await this.getRoots(data => {
|
||||
this.caches = data?.map(e => e.id) || []
|
||||
new Promise(resolve => {
|
||||
if (hasChild(data.map(e => e.id))) {
|
||||
data.map(e => hasChild([e.id]) && appendChildren(e, resolve))
|
||||
} else resolve()
|
||||
}).then(() => {
|
||||
this.cacheOptions = data
|
||||
})
|
||||
}, "initWithValue")
|
||||
} else if (!!this.root) {
|
||||
this.caches = []
|
||||
await this.getRoots(data => {
|
||||
this.caches = data?.map(e => e.id) || []
|
||||
new Promise(resolve => {
|
||||
if (hasChild(data.map(e => e.id))) {
|
||||
data.map(e => hasChild([e.id]) && appendChildren(e, resolve))
|
||||
} else resolve()
|
||||
}).then(() => {
|
||||
this.cacheOptions = data
|
||||
})
|
||||
}, "initWithRoot")
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 表单验证
|
||||
* @param componentName
|
||||
* @param eventName
|
||||
* @param params
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root;
|
||||
let name = parent.$options.componentName;
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent;
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.componentName;
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
handleFindArea(areaName) {
|
||||
return new Promise(resolve => {
|
||||
this.instance.post("/admin/area/queryAreaByAreaName", null, {
|
||||
params: {areaName}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
let range = new RegExp(`^${this.root.replace(/0+$/g, '')||'\d'}`)
|
||||
this.filterData = res.data.filter(e => !this.caches.includes(e.id) && range.test(e.id)).map(e => ({
|
||||
...e,
|
||||
leaf: e.type == this.valueLevel
|
||||
}))
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
handleAfterFilter(v) {
|
||||
this.$emit('select', this.$refs.areaCascader?.getCheckedNodes(true))
|
||||
this.$refs.areaCascader?.toggleDropDownVisible(false)
|
||||
if (!this.multiple) {
|
||||
if (this.filterData?.length > 0) {
|
||||
this.filterData = []
|
||||
}
|
||||
this.editing = this.caches.includes(v);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
setTimeout(() => {
|
||||
this.cacheOptions.length == 0 && this.getCacheOptions()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiAreaGet {
|
||||
width: 100%;
|
||||
|
||||
.el-cascader {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.popperSelectors {
|
||||
.el-cascader-menu__wrap {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
273
ui/packages/common/AiAreaSelect.vue
Normal file
273
ui/packages/common/AiAreaSelect.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<section class="AiAreaSelect">
|
||||
<el-row type="flex" :gutter="5">
|
||||
<el-col v-for="(item,i) in areaInfo" :key="i" v-show="isShowSelector(i)" style="width: auto;">
|
||||
<el-select :size="selectClass" v-if="areaLists[i].length>0||alwaysShow" @focus="$emit('focus')"
|
||||
@blur="$emit('blur')" :placeholder="placeVal[i]" clearable
|
||||
:disabled="isDisabledSelector(i)" :value="areaInfo[i]" @change="v=>handleSelectorChange(v,i)">
|
||||
<template v-if="lockFirstOption&&i==(hideNum||0)">
|
||||
<el-option v-if="!areaInfo[i]||areaInfo[i]===area.id" v-for="(area,j) in areaLists[i]" :key="j"
|
||||
:value="area.id" :label="area.name"/>
|
||||
</template>
|
||||
<el-option v-else v-for="(area,j) in areaLists[i]" :key="j" :value="area.id" :label="area.name"/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "AiAreaSelect",
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
inject: {
|
||||
elFormItem: {default: ""},
|
||||
},
|
||||
props: {
|
||||
value: String,
|
||||
areaLevel: {type: [Number, String], default: 5},
|
||||
disabledLevel: {type: [Number, String], default: 0},
|
||||
disabled: {type: Boolean, default: false},
|
||||
hideLevel: {type: [Number, String], default: 0},
|
||||
valueLevel: {type: [Number, String], default: -1},
|
||||
alwaysShow: {type: Boolean, default: false},
|
||||
selectClass: {type: String, default: 'small'},
|
||||
lockFirstOption: {type: Boolean, default: false},
|
||||
instance: Function,
|
||||
action: String,
|
||||
provinceAction: String
|
||||
},
|
||||
computed: {
|
||||
hideNum() {
|
||||
return Number(this.hideLevel)
|
||||
},
|
||||
disabledNum() {
|
||||
return Number(this.disabledLevel)
|
||||
},
|
||||
showNum() {
|
||||
return Number(this.areaLevel)
|
||||
},
|
||||
valueIndex() {
|
||||
return Number(this.valueLevel)
|
||||
},
|
||||
current() {
|
||||
return this.selected || this.value
|
||||
},
|
||||
areaInfo() {
|
||||
let info = {}
|
||||
const currentLevel = this.getLevelByAreaId(this.current)
|
||||
for (let i = 0; i < this.showNum; i++) {
|
||||
//防止地区代码出现,在获取到选项后再赋值
|
||||
if (i <= currentLevel) info[i] = this.areaLists[i]?.length ? this.getAreaByAreaType(i) : ""
|
||||
else info[i] = null
|
||||
}
|
||||
return info
|
||||
},
|
||||
areaObj() {
|
||||
let info = {}
|
||||
for (let key in this.areaInfo) {
|
||||
if (this.areaInfo[key]) {
|
||||
info[key] = this.areaLists[key].find(e => e.id === this.areaInfo[key])
|
||||
}
|
||||
}
|
||||
return info
|
||||
},
|
||||
selectedName() {
|
||||
const index = this.getLevelByAreaId(this.current)
|
||||
if (index > -1) {
|
||||
const area = this.areaLists[index].find(e => e.id === this.current)
|
||||
return area ? area.name : ""
|
||||
} else return ""
|
||||
},
|
||||
selectedFullName() {
|
||||
let name = ""
|
||||
for (let i in this.areaInfo) {
|
||||
if (this.areaInfo[i]) {
|
||||
const area = this.areaLists[i].find(e => e.id === this.areaInfo[i])
|
||||
name += area ? area.name : ""
|
||||
}
|
||||
}
|
||||
return name
|
||||
},
|
||||
areaLelve() {
|
||||
return this.getLevelByAreaId(this.current);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selected() {
|
||||
if (this.valueIndex > -1) {
|
||||
this.$emit("change", this.areaInfo[this.valueIndex] || null)
|
||||
} else {
|
||||
this.$emit("change", this.current)
|
||||
}
|
||||
this.$emit("area", this.areaInfo);
|
||||
this.$emit('areaLelve', this.areaLelve)
|
||||
},
|
||||
areaObj() {
|
||||
this.$emit("areaObj", this.areaObj)
|
||||
},
|
||||
selectedName() {
|
||||
this.$emit("name", this.selectedName)
|
||||
},
|
||||
selectedFullName() {
|
||||
this.$emit("fullname", this.selectedFullName)
|
||||
},
|
||||
valueLevel() {
|
||||
this.refreshAreaList();
|
||||
},
|
||||
value(v) {
|
||||
//特殊处置结果 后台传值赋值改变需要重新加载数据
|
||||
this.dispatch('ElFormItem', 'el.form.change', [v]);
|
||||
if (!this.selected) {
|
||||
this.refreshAreaList();
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selected: null,
|
||||
areaLists: {
|
||||
0: [],
|
||||
1: [],
|
||||
2: [],
|
||||
3: [],
|
||||
4: [],
|
||||
},
|
||||
placeVal: {
|
||||
0: '请选择省',
|
||||
1: '请选择市',
|
||||
2: '请选择区',
|
||||
3: '请选择镇',
|
||||
4: '请选择村'
|
||||
},
|
||||
ProvinceCityCounty: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isShowSelector(i) {
|
||||
let index = Number(i)
|
||||
return index >= this.hideNum
|
||||
},
|
||||
isDisabledSelector(i) {
|
||||
let index = Number(i)
|
||||
return this.disabled || (index < this.disabledNum)
|
||||
},
|
||||
handleSelectorChange(area, index) {
|
||||
if (area) {
|
||||
this.selected = area
|
||||
if (index < 4 && this.areaInfo[index]) {
|
||||
this.getAreasByParentId(index).then(() => {
|
||||
for (let i = index; i < this.showNum; i++) {
|
||||
if (this.areaLists[i + 2]) this.areaLists[i + 2] = []
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {//清空操作
|
||||
let i = Number(index), pre = i - 1, origin = JSON.parse(JSON.stringify(this.areaInfo))
|
||||
if (pre > 0) {
|
||||
this.selected = origin[pre]
|
||||
for (let j = i + 1; j < this.showNum; j++) {
|
||||
this.areaLists[j] = []
|
||||
}
|
||||
} else {
|
||||
this.selected = origin[0]
|
||||
for (let j = i + 2; j < this.showNum; j++) {
|
||||
this.areaLists[j] = []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getAreasByParentId(index) {
|
||||
index = Number(index)
|
||||
return new Promise((resolve) => {
|
||||
this.areaLists[index + 1] = []
|
||||
if (index < 2) {
|
||||
index = Number(index)
|
||||
let data = this.ProvinceCityCounty.filter(e => e.parentId == this.areaInfo[index])
|
||||
if (data && (index + 1 < this.showNum)) this.areaLists[index + 1] = data
|
||||
this.$forceUpdate();
|
||||
resolve(index)
|
||||
} else {
|
||||
this.instance.post(this.action || "/admin/area/queryAreaByParentId", null, {
|
||||
withoutToken: true,
|
||||
params: {id: this.areaInfo[index]}
|
||||
}).then(res => {
|
||||
if (res?.data) {
|
||||
index = Number(index)
|
||||
if (index + 1 < this.showNum) this.areaLists[index + 1] = res.data
|
||||
this.$forceUpdate()
|
||||
}
|
||||
resolve(index)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
getProvinceCityCounty() {
|
||||
return this.instance.post(this.provinceAction || "/admin/area/queryProvinceListContainCity", null, {withoutToken: true})
|
||||
},
|
||||
getAreaByAreaType(areaType) {
|
||||
let lvCount = [2, 4, 6, 9, 12]
|
||||
return this.current?.split("").map((e, i) => i < lvCount[areaType] ? e : 0).join("")
|
||||
},
|
||||
getLevelByAreaId(code) {
|
||||
if (code) {
|
||||
if (code.length == 2 || code.endsWith('0000000000')) return 0
|
||||
else if (code.endsWith('00000000')) return 1
|
||||
else if (code.endsWith('000000')) return 2
|
||||
else if (code.endsWith('000')) return 3
|
||||
else return 4
|
||||
} else return -1
|
||||
},
|
||||
refreshAreaList() {
|
||||
for (let i = 0; i < this.showNum; i++) {
|
||||
this.areaLists[i] = []
|
||||
}
|
||||
this.getProvinceCityCounty().then(res => {
|
||||
this.ProvinceCityCounty = res.data
|
||||
this.areaLists[0] = res.data.filter(d => !d.parentId)
|
||||
const getAreaList = i => {
|
||||
if (this.areaInfo?.[i])
|
||||
this.getAreasByParentId(i).then(next => getAreaList(next + 1))
|
||||
}
|
||||
getAreaList(0)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 表单验证
|
||||
* @param componentName
|
||||
* @param eventName
|
||||
* @param params
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root;
|
||||
let name = parent.$options.componentName;
|
||||
|
||||
while (parent && (!name || name !== componentName)) {
|
||||
parent = parent.$parent;
|
||||
|
||||
if (parent) {
|
||||
name = parent.$options.componentName;
|
||||
}
|
||||
}
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent, [eventName].concat(params));
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.refreshAreaList()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.AiAreaSelect {
|
||||
.el-col {
|
||||
min-width: 90px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user