merge: with dev

This commit is contained in:
ibuler
2024-02-05 10:06:15 +08:00
67 changed files with 852 additions and 328 deletions

View File

@@ -0,0 +1,191 @@
import { UpdateToken, UploadSecret } from '@/components/Form/FormFields'
import Select2 from '@/components/Form/FormFields/Select2.vue'
import AssetSelect from '@/components/Apps/AssetSelect/index.vue'
import { Required, RequiredChange } from '@/components/Form/DataForm/rules'
import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue'
export const accountFieldsMeta = (vm) => {
const defaultPrivilegedAccounts = ['root', 'administrator']
return {
assets: {
rules: [Required],
component: AssetSelect,
label: vm.$t('assets.Asset'),
el: {
multiple: false
},
hidden: () => {
return vm.platform || vm.asset
}
},
template: {
component: Select2,
rules: [Required],
el: {
multiple: false,
ajax: {
url: '/api/v1/accounts/account-templates/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: () => {
return vm.platform || vm.asset || !vm.addTemplate
}
},
on_invalid: {
rules: [Required],
label: vm.$t('accounts.AccountPolicy'),
helpText: vm.$t('accounts.BulkCreateStrategy'),
hidden: () => {
return vm.platform || vm.asset
}
},
name: {
label: vm.$t('common.Name'),
rules: [RequiredChange],
on: {
input: ([value], updateForm) => {
if (!vm.usernameChanged) {
if (!vm.account?.name) {
updateForm({ username: value })
}
const maybePrivileged = defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
}
},
hidden: () => {
return vm.addTemplate
}
},
username: {
el: {
disabled: !!vm.account?.name
},
on: {
input: ([value], updateForm) => {
vm.usernameChanged = true
},
change: ([value], updateForm) => {
const maybePrivileged = defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
},
hidden: () => {
return vm.addTemplate
}
},
privileged: {
label: vm.$t('assets.Privileged'),
hidden: () => {
return vm.addTemplate
}
},
su_from: {
component: Select2,
hidden: (formValue) => {
return !vm.asset?.id || !vm.iPlatform.su_enabled
},
el: {
multiple: false,
clearable: true,
ajax: {
url: `/api/v1/accounts/accounts/su-from-accounts/?account=${vm.account?.id || ''}&asset=${vm.asset?.id || ''}`,
transformOption: (item) => {
return { label: `${item.name}(${item.username})`, value: item.id }
}
}
}
},
su_from_username: {
label: vm.$t('assets.UserSwitchFrom'),
hidden: (formValue) => {
return vm.platform || vm.asset || vm.addTemplate
}
},
password: {
label: vm.$t('assets.Password'),
component: UpdateToken,
hidden: (formValue) => {
console.log('formValue: ', formValue)
return formValue.secret_type !== 'password' || vm.addTemplate
}
},
ssh_key: {
label: vm.$t('assets.PrivateKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || vm.addTemplate
},
passphrase: {
label: vm.$t('assets.Passphrase'),
component: UpdateToken,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || vm.addTemplate
},
token: {
label: vm.$t('assets.Token'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'token' || vm.addTemplate
},
access_key: {
id: 'access_key',
label: vm.$t('assets.AccessKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'access_key' || vm.addTemplate
},
api_key: {
id: 'api_key',
label: vm.$t('assets.ApiKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'api_key' || vm.addTemplate
},
secret_type: {
type: 'radio-group',
options: [],
hidden: () => {
return vm.addTemplate
}
},
push_now: {
helpText: vm.$t('accounts.AccountPush.WindowsPushHelpText'),
hidden: (formValue) => {
const automation = vm.iPlatform.automation || {}
return !automation.push_account_enabled ||
!automation.ansible_enabled ||
!vm.$hasPerm('accounts.push_account') ||
(formValue.secret_type === 'ssh_key' && vm.iPlatform.type.value === 'windows') ||
vm.addTemplate
}
},
params: {
label: vm.$t('assets.PushParams'),
component: AutomationParamsForm,
el: {
method: vm.asset?.auto_config?.push_account_method
},
hidden: (formValue) => {
const automation = vm.iPlatform.automation || {}
return !formValue.push_now ||
!automation.push_account_enabled ||
!automation.ansible_enabled ||
(formValue.secret_type === 'ssh_key' && vm.iPlatform.type.value === 'windows') ||
!vm.$hasPerm('accounts.push_account') ||
vm.addTemplate
}
},
is_active: {
label: vm.$t('common.IsActive')
},
comment: {
label: vm.$t('common.Comment'),
hidden: () => {
return vm.addTemplate
}
}
}
}

View File

@@ -9,12 +9,8 @@
<script>
import AutoDataForm from '@/components/Form/AutoDataForm/index.vue'
import { UpdateToken, UploadSecret } from '@/components/Form/FormFields'
import Select2 from '@/components/Form/FormFields/Select2.vue'
import AssetSelect from '@/components/Apps/AssetSelect/index.vue'
import { encryptPassword } from '@/utils/crypto'
import { Required, RequiredChange } from '@/components/Form/DataForm/rules'
import AutomationParamsForm from '@/views/assets/Platform/AutomationParamsSetting.vue'
import { accountFieldsMeta } from '@/components/Apps/AccountCreateUpdateForm/const'
export default {
name: 'AccountCreateForm',
@@ -48,7 +44,6 @@ export default {
return {
loading: true,
usernameChanged: false,
defaultPrivilegedAccounts: ['root', 'administrator'],
iPlatform: {
automation: {},
su_enabled: false,
@@ -72,179 +67,7 @@ export default {
]],
[this.$t('Other'), ['push_now', 'params', 'on_invalid', 'is_active', 'comment']]
],
fieldsMeta: {
assets: {
rules: [Required],
component: AssetSelect,
label: this.$t('Asset'),
el: {
multiple: false
},
hidden: () => {
return this.platform || this.asset
}
},
template: {
component: Select2,
rules: [Required],
el: {
multiple: false,
ajax: {
url: '/api/v1/accounts/account-templates/',
transformOption: (item) => {
return { label: item.name, value: item.id }
}
}
},
hidden: () => {
return this.platform || this.asset || !this.addTemplate
}
},
on_invalid: {
rules: [Required],
label: this.$t('AccountPolicy'),
helpText: this.$t('BulkCreateStrategy'),
hidden: () => {
return this.platform || this.asset
}
},
name: {
rules: [RequiredChange],
on: {
input: ([value], updateForm) => {
if (!this.usernameChanged) {
if (!this.account?.name) {
updateForm({ username: value })
}
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
}
},
hidden: () => {
return this.addTemplate
}
},
username: {
el: {
disabled: !!this.account?.name
},
on: {
input: ([value], updateForm) => {
this.usernameChanged = true
},
change: ([value], updateForm) => {
const maybePrivileged = this.defaultPrivilegedAccounts.includes(value)
if (maybePrivileged) {
updateForm({ privileged: true })
}
}
},
hidden: () => {
return this.addTemplate
}
},
privileged: {
hidden: () => {
return this.addTemplate
}
},
su_from: {
component: Select2,
hidden: (formValue) => {
return !this.asset?.id || !this.iPlatform.su_enabled
},
el: {
multiple: false,
clearable: true,
ajax: {
url: `/api/v1/accounts/accounts/su-from-accounts/?account=${this.account?.id || ''}&asset=${this.asset?.id || ''}`,
transformOption: (item) => {
return { label: `${item.name}(${item.username})`, value: item.id }
}
}
}
},
su_from_username: {
label: this.$t('SwitchFrom'),
hidden: (formValue) => {
return this.platform || this.asset || this.addTemplate
}
},
password: {
label: this.$t('Password'),
component: UpdateToken,
hidden: (formValue) => formValue.secret_type !== 'password' || this.addTemplate
},
ssh_key: {
label: this.$t('PrivateKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || this.addTemplate
},
passphrase: {
label: this.$t('Passphrase'),
component: UpdateToken,
hidden: (formValue) => formValue.secret_type !== 'ssh_key' || this.addTemplate
},
token: {
label: this.$t('Token'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'token' || this.addTemplate
},
access_key: {
id: 'access_key',
label: this.$t('AccessKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'access_key' || this.addTemplate
},
api_key: {
id: 'api_key',
label: this.$t('ApiKey'),
component: UploadSecret,
hidden: (formValue) => formValue.secret_type !== 'api_key' || this.addTemplate
},
secret_type: {
type: 'radio-group',
options: [],
hidden: () => {
return this.addTemplate
}
},
push_now: {
helpText: this.$t('WindowsPushHelpText'),
hidden: (formValue) => {
const automation = this.iPlatform.automation || {}
return !automation.push_account_enabled ||
!automation.ansible_enabled ||
!this.$hasPerm('accounts.push_account') ||
(formValue.secret_type === 'ssh_key' && this.iPlatform.type.value === 'windows') ||
this.addTemplate
}
},
params: {
label: this.$t('PushParams'),
component: AutomationParamsForm,
el: {
method: this.asset?.auto_config?.push_account_method
},
hidden: (formValue) => {
const automation = this.iPlatform.automation || {}
return !formValue.push_now ||
!automation.push_account_enabled ||
!automation.ansible_enabled ||
(formValue.secret_type === 'ssh_key' && this.iPlatform.type.value === 'windows') ||
!this.$hasPerm('accounts.push_account') ||
this.addTemplate
}
},
comment: {
hidden: () => {
return this.addTemplate
}
}
},
fieldsMeta: accountFieldsMeta(this),
hasSaveContinue: false
}
},

View File

@@ -0,0 +1,87 @@
<template>
<GenericUpdateFormDialog
v-if="visible"
:form-setting="formSetting"
:selected-rows="selectedRows"
:visible="visible"
v-on="$listeners"
/>
</template>
<script>
import { GenericUpdateFormDialog } from '@/layout/components'
import { accountFieldsMeta } from '@/components/Apps/AccountCreateUpdateForm/const'
import { encryptPassword } from '@/utils/crypto'
export default {
name: 'AccountBulkUpdateDialog',
components: {
GenericUpdateFormDialog
},
props: {
visible: {
type: Boolean,
default: false
},
selectedRows: {
type: Array,
default: () => ([])
}
},
data() {
return {
formSetting: {
url: '/api/v1/accounts/accounts/',
hasSaveContinue: false,
fields: [],
fieldsMeta: accountFieldsMeta(this),
cleanOtherFormValue: (formValue) => {
for (const value of formValue) {
Object.keys(value).forEach((item, index, arr) => {
if (['ssh_key', 'token', 'access_key', 'api_key', 'password'].includes(item)) {
value['secret'] = encryptPassword(value[item])
delete value[item]
}
})
}
return formValue
}
}
}
},
created() {
this.filterFieldsMeta()
},
methods: {
filterFieldsMeta() {
let fields = ['privileged']
const fieldsMeta = {}
const secretFields = ['password', 'ssh_key', 'passphrase', 'token', 'access_key', 'api_key']
const secret_type = this.selectedRows[0].secret_type?.value || 'password'
for (const field of secretFields) {
if (secret_type === 'ssh_key' && field === 'passphrase') {
fields.push('passphrase')
this.formSetting.fieldsMeta['passphrase'].hidden = () => false
continue
}
if (secret_type === field) {
fields.push(field)
this.formSetting.fieldsMeta[field].hidden = () => false
continue
}
delete this.formSetting.fieldsMeta[field]
}
fields = fields.concat(['is_active', 'comment'])
for (const field of fields) {
fieldsMeta[field] = this.formSetting.fieldsMeta[field]
}
this.formSetting.fields = fields
this.formSetting.fieldsMeta = fieldsMeta
}
}
}
</script>
<style scoped>
</style>

View File

@@ -37,6 +37,12 @@
:result="createAccountResults"
:visible.sync="showResultDialog"
/>
<AccountBulkUpdateDialog
v-if="updateSelectedDialogSetting.visible"
:visible.sync="updateSelectedDialogSetting.visible"
v-bind="updateSelectedDialogSetting"
@update="handleAccountBulkUpdate"
/>
</div>
</template>
@@ -49,10 +55,12 @@ import AccountCreateUpdate from './AccountCreateUpdate.vue'
import { connectivityMeta } from './const'
import { openTaskPage } from '@/utils/jms'
import ResultDialog from './BulkCreateResultDialog.vue'
import AccountBulkUpdateDialog from '@/components/Apps/AccountListTable/AccountBulkUpdateDialog.vue'
export default {
name: 'AccountListTable',
components: {
AccountBulkUpdateDialog,
ResultDialog,
ListTable,
UpdateSecretInfo,
@@ -116,6 +124,10 @@ export default {
headerExtraActions: {
type: Array,
default: () => []
},
extraQuery: {
type: Object,
default: () => ({})
}
},
data() {
@@ -137,9 +149,7 @@ export default {
app: 'assets',
resource: 'account'
},
extraQuery: {
order: '-date_updated'
},
extraQuery: this.extraQuery,
columnsExclude: ['spec_info'],
columnsShow: {
min: ['name', 'username', 'actions'],
@@ -336,6 +346,25 @@ export default {
...this.headerExtraActions
],
extraMoreActions: [
{
name: 'BulkVerify',
title: this.$t('accounts.BulkVerify'),
type: 'primary',
fa: 'fa-handshake-o',
can: ({ selectedRows }) => {
return selectedRows.length > 0
},
callback: function({ selectedRows }) {
const ids = selectedRows.map(v => { return v.id })
this.$axios.post(
'/api/v1/accounts/accounts/tasks/',
{ action: 'verify', accounts: ids }).then(res => {
openTaskPage(res['task'])
}).catch(err => {
this.$message.error(this.$tc('common.bulkVerifyErrorMsg' + ' ' + err))
})
}.bind(this)
},
{
name: 'ClearSecrets',
title: this.$t('ClearSecret'),
@@ -354,6 +383,21 @@ export default {
this.$message.error(this.$tc('BulkClearErrorMsg' + ' ' + err))
})
}.bind(this)
},
{
name: 'actionUpdateSelected',
title: this.$t('accounts.AccountBatchUpdate'),
fa: 'batch-update',
can: ({ selectedRows }) => {
return selectedRows.length > 0 &&
!this.$store.getters.currentOrgIsRoot &&
vm.$hasPerm('accounts.change_account') &&
selectedRows.every(i => i.secret_type.value === selectedRows[0].secret_type.value)
},
callback: ({ selectedRows }) => {
vm.updateSelectedDialogSetting.selectedRows = selectedRows
vm.updateSelectedDialogSetting.visible = true
}
}
],
canBulkDelete: vm.$hasPerm('accounts.delete_account'),
@@ -362,6 +406,10 @@ export default {
exclude: ['asset']
},
hasSearch: true
},
updateSelectedDialogSetting: {
visible: false,
selectedRows: []
}
}
},
@@ -429,6 +477,10 @@ export default {
setTimeout(() => {
this.showResultDialog = true
}, 100)
},
handleAccountBulkUpdate() {
this.updateSelectedDialogSetting.visible = false
this.$refs.ListTable.reloadTable()
}
}
}

View File

@@ -1,5 +1,6 @@
<template>
<Dialog
:close-on-click-modal="false"
:title="$tc('Assets')"
custom-class="asset-select-dialog"
top="2vh"
@@ -109,6 +110,7 @@ export default {
headerActions: {
hasLeftActions: false,
hasRightActions: false,
hasLabelSearch: true,
searchConfig: {
getUrlQuery: false
}

View File

@@ -60,6 +60,7 @@ export default {
const showAssets = this.treeSetting?.showAssets || this.showAssets
const treeUrlQuery = this.setTreeUrlQuery()
const assetTreeUrl = `${this.treeUrl}?assets=${showAssets ? '1' : '0'}&${treeUrlQuery}`
const vm = this
return {
treeTabConfig: {
@@ -81,7 +82,13 @@ export default {
nodeUrl: this.nodeUrl,
treeUrl: assetTreeUrl,
callback: {
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode)
onSelected: (event, treeNode) => this.getAssetsUrl(treeNode),
beforeRefresh: () => {
const query = { ...this.$route.query, node_id: '', asset_id: '' }
setTimeout(() => {
vm.$router.replace({ query: query })
}, 100)
}
},
...this.treeSetting
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="container">
<div class="close-sidebar">
<i class="el-icon-close" @click="onClose" />
<i v-if="hasClose" class="el-icon-close" @click="onClose" />
</div>
<el-tabs v-model="active" :tab-position="'right'" @tab-click="handleClick">
<el-tab-pane v-for="(item) in submenu" :key="item.name" :name="item.name">
@@ -22,6 +22,10 @@ export default {
type: String,
default: 'chat'
},
hasClose: {
type: Boolean,
default: true
},
submenu: {
type: Array,
default: () => []
@@ -48,9 +52,10 @@ export default {
height: 100%;
background-color: #f0f1f5;
.close-sidebar {
height: 48px;
padding: 12px 0;
text-align: center;
font-size: 14px;
padding: 12px 0;
cursor: pointer;
i {
font-size: 16px;

View File

@@ -18,7 +18,7 @@
</div>
</div>
<div class="sidebar">
<Sidebar :active.sync="active" :submenu="submenu" />
<Sidebar v-bind="$attrs" :active.sync="active" :submenu="submenu" />
</div>
</div>
</template>
@@ -62,11 +62,14 @@ export default {
watch: {
drawerPanelVisible(value) {
if (value && !ws) {
this.$refs.component?.init()
this.initWebSocket()
}
}
},
methods: {
initWebSocket() {
this.$refs.component?.init()
},
onClose() {
this.$parent.show = false
},

View File

@@ -5,6 +5,7 @@
<script type="text/jsx">
import TreeTable from '../../Table/TreeTable/index.vue'
import { DetailFormatter } from '@/components/Table/TableFormatters'
import { connectivityMeta } from '@/components/Apps/AccountListTable/const'
export default {
name: 'GrantedAssets',
@@ -58,9 +59,9 @@ export default {
url: this.tableUrl,
hasTree: true,
columnsExclude: ['spec_info'],
columnShow: {
columnsShow: {
min: ['name', 'address', 'accounts'],
default: ['name', 'address', 'accounts', 'actions']
default: ['name', 'address', 'platform', 'connectivity']
},
columnsMeta: {
name: {
@@ -71,7 +72,8 @@ export default {
},
actions: {
has: false
}
},
connectivity: connectivityMeta
}
},
headerActions: {

View File

@@ -7,7 +7,6 @@ import BasicTree from '@/components/Form/FormFields/BasicTree.vue'
import JsonEditor from '@/components/Form/FormFields/JsonEditor.vue'
import { assignIfNot, capitalizeFirst } from '@/utils/common'
import TagInput from '@/components/Form/FormFields/TagInput.vue'
import Select2 from '@/components/Form/FormFields/Select2.vue'
import TransferSelect from '@/components/Form/FormFields/TransferSelect.vue'
export class FormFieldGenerator {
@@ -46,7 +45,7 @@ export class FormFieldGenerator {
break
case 'field':
type = ''
field.component = Select2
field.component = ObjectSelect2
if (fieldRemoteMeta.required) {
field.el.clearable = false
}
@@ -77,7 +76,7 @@ export class FormFieldGenerator {
field.component = ObjectSelect2
break
case 'm2m_related_field':
field.component = Select2
field.component = ObjectSelect2
field.el.label = field.label
break
case 'nested object':

View File

@@ -100,7 +100,7 @@
<div style="font-size: 13px;">{{ contabValueString }}</div>
</div>
</div>
<CrontabResult :ex="contabValueString" />
<CrontabResult :ex="contabValueString" @crontabDiffChange="crontabDiffChangeHandle" />
<div class="pop_btn">
<el-button
@@ -167,7 +167,8 @@ export default {
week: '*'
// year: "",
},
newContabValueString: ''
newContabValueString: '',
crontabDiff: 0
}
},
computed: {
@@ -364,6 +365,12 @@ export default {
},
// 填充表达式
submitFill() {
const crontabDiffMin = this.crontabDiff / 1000 / 60
if (crontabDiffMin > 0 && crontabDiffMin < 10) {
const msg = this.$tc('common.crontabDiffError')
this.$message.error(msg)
return
}
this.$emit('fill', this.contabValueString)
this.hidePopup()
},
@@ -381,6 +388,9 @@ export default {
for (const j in this.contabValueObj) {
this.changeRadio(j, this.contabValueObj[j])
}
},
crontabDiffChangeHandle(diff) {
this.crontabDiff = diff
}
}
}
@@ -454,7 +464,7 @@ export default {
}
.crontab-panel {
>>> .el-input-number {
> > > .el-input-number {
margin: 0 5px
}
}

View File

@@ -52,6 +52,10 @@ export default {
const cur = interval.next().toString()
this.resultList.push(toSafeLocalDateStr(cur))
}
const first = new Date(this.resultList[0])
const second = new Date(this.resultList[1])
const diff = Math.abs(second - first)
this.$emit('crontabDiffChange', diff)
} catch (error) {
this.isShow = false
// debug(error, 'error')

View File

@@ -7,13 +7,11 @@
v-bind="data.attrs"
>
<template v-if="data.helpTips" #label>
{{ data.label }}
<el-tooltip placement="top" effect="light" popper-class="help-tips">
<div slot="content" v-html="data.helpTips" />
<el-button style="padding: 0">
<i class="fa fa-question-circle" />
</el-button>
<i class="fa fa-question-circle-o" />
</el-tooltip>
{{ data.label }}
</template>
<template v-if="readonly && hasReadonlyContent">
<div
@@ -70,7 +68,8 @@
:key="opt.label"
v-bind="opt"
:label="'value' in opt ? opt.value : opt.label"
>{{ opt.label }}</el-radio>
>{{ opt.label }}
</el-radio>
</template>
</custom-component>
<div v-if="data.helpText" class="help-block" v-html="data.helpText" />

View File

@@ -2,7 +2,7 @@
<div class="code-editor" style="font-size: 12px">
<div class="toolbar">
<div
v-for="(item,index) in toolbar.left"
v-for="(item,index) in iActions"
:key="index"
style="display: inline-block; margin: 0 2px"
>
@@ -93,6 +93,16 @@
</el-tooltip>
</div>
<div v-if="toolbar.hasOwnProperty('fold')" class="fold">
<el-tooltip :content="$tc('common.MoreActions')" placement="top">
<i
class="fa"
:class="[isFold ? 'fa-angle-double-right': 'fa-angle-double-down']"
@click="onChangeFold"
/>
</el-tooltip>
</div>
<div class="right-side" style="float: right">
<div
v-for="(item,index) in toolbar.right"
@@ -154,9 +164,19 @@ export default {
}
},
data() {
return {}
return {
isFold: true
}
},
computed: {
iActions() {
let actions = this.toolbar.left || {}
const fold = this.toolbar.fold || {}
if (!this.isFold) {
actions = { ...actions, ...fold }
}
return actions
},
iValue: {
get() {
return this.value
@@ -179,6 +199,9 @@ export default {
}
},
methods: {
onChangeFold() {
this.isFold = !this.isFold
},
getLabel(value, items) {
for (const item of items) {
if (item.value === value) {
@@ -205,6 +228,16 @@ export default {
margin-bottom: 5px;
}
.fold {
display: inline-block;
padding-left: 4px;
i {
font-weight: bold;
font-size: 15px;
cursor: pointer;
}
}
> > > .CodeMirror pre.CodeMirror-line,
> > > .CodeMirror-linenumber.CodeMirror-gutter-elt {
line-height: 18px !important;

View File

@@ -24,6 +24,7 @@
</template>
<script>
import { mapGetters } from 'vuex'
export default {
name: 'PhoneInput',
@@ -35,21 +36,7 @@ export default {
},
data() {
return {
rawValue: {},
countries: [
{ name: 'China(中国)', value: '+86' },
{ name: 'HongKong(中国香港)', value: '+852' },
{ name: 'Macao(中国澳门)', value: '+853' },
{ name: 'Taiwan(中国台湾)', value: '+886' },
{ name: 'America(America)', value: '+1' },
{ name: 'Russia(Россия)', value: '+7' },
{ name: 'France(français)', value: '+33' },
{ name: 'Britain(Britain)', value: '+44' },
{ name: 'Germany(Deutschland)', value: '+49' },
{ name: 'Japan(日本)', value: '+81' },
{ name: 'Korea(한국)', value: '+82' },
{ name: 'India(भारत)', value: '+91' }
]
rawValue: {}
}
},
computed: {
@@ -58,7 +45,13 @@ export default {
return ''
}
return `${this.rawValue.code}${this.rawValue.phone}`
}
},
countries: {
get() {
return this.publicSettings.COUNTRY_CALLING_CODES
}
},
...mapGetters(['publicSettings'])
},
mounted() {
this.rawValue = this.value || { code: '+86', phone: '' }

View File

@@ -13,6 +13,7 @@
v-if="showTransfer"
:title="label"
:visible.sync="showTransfer"
:close-on-click-modal="false"
width="730px"
@cancel="handleTransCancel"
@confirm="handleTransConfirm"
@@ -125,13 +126,17 @@ export default {
return _.uniq(value)
},
set(val) {
this.$emit('input', val)
this.emit(val)
}
}
},
methods: {
emit(val) {
const value = _.uniq(val)
this.$emit('input', value)
},
onInputChange(val) {
this.$emit('input', val)
this.emit(val)
},
handleFocus() {
this.$refs.select2.selectRef.blur()
@@ -154,7 +159,7 @@ export default {
return { value: item.id, label: item.label }
})
this.select2.options = options
this.$emit('input', options.map(item => item.value))
this.emit(options.map(item => item.value))
this.showTransfer = false
}
}

View File

@@ -204,7 +204,12 @@ export default {
},
formatWeektime(col) {
const timeStamp = 1542384000000 // '2018-11-17 00:00:00'
const beginStamp = timeStamp + col * 1800000 // col * 30 * 60 * 1000
const timezone = 8
const offsetGMT = new Date().getTimezoneOffset() // 本地时间和格林威治的时间差,单位为分钟
const nowDate = new Date(timeStamp).getTime()
const targetStamp = new Date(nowDate + offsetGMT * 60 * 1000 + timezone * 60 * 60 * 1000).getTime()
const beginStamp = targetStamp + col * 1800000 // col * 30 * 60 * 1000
const endStamp = beginStamp + 1800000
const begin = this.formatDate(new Date(beginStamp), 'hh:mm')

View File

@@ -68,6 +68,9 @@ export default {
},
methods: {
handleTagSearch(tags) {
if (_.isEqual(tags, this.tags)) {
return
}
this.tags = tags
if (tags.length === 0) {
this.manualSearch = false

View File

@@ -209,6 +209,12 @@ export default {
.left-side {
float: left;
display: block;
&>>> .action-item.el-dropdown {
height: 33px;
&> .el-button {
height: 100%;
}
}
}
.right-side {

View File

@@ -3,17 +3,19 @@
<template>
<el-popover
:disabled="!showItems"
:open-delay="500"
:title="title"
placement="top-start"
trigger="hover"
width="400"
@show="getAsyncItems"
>
<div class="detail-content">
<div v-for="item of items" :key="getKey(item)" class="detail-item">
<span class="detail-item-name">{{ item }}</span>
</div>
</div>
<span slot="reference">{{ items && items.length }}</span>
<span slot="reference">{{ amount }}</span>
</el-popover>
</template>
</DetailFormatter>
@@ -37,41 +39,73 @@ export default {
showItems: true,
getItem(item) {
return item.name
}
},
async: false,
ajax: {},
title: ''
}
}
}
},
data() {
const formatterArgs = Object.assign(this.formatterArgsDefault, this.col.formatterArgs || {})
return {
formatterArgs: Object.assign(this.formatterArgsDefault, this.col.formatterArgs || {})
formatterArgs: formatterArgs,
data: formatterArgs.async ? [] : (this.cellValue || []),
amount: '',
asyncGetDone: false
}
},
computed: {
title() {
return this.formatterArgs.title || ''
return this.formatterArgs.title || this.col.label.replace('amount', '').replace('数量', '')
},
items() {
if (this.formatterArgs.async && !this.asyncGetDone) {
return [this.$t('common.tree.Loading') + '...']
}
const getItem = this.formatterArgs.getItem || (item => item.name)
let data = this.cellValue?.map(item => getItem(item)) || []
let data = this.data.map(item => getItem(item)) || []
data = data.filter(Boolean)
return data
},
showItems() {
return this.formatterArgs.showItems !== false && this.cellValue?.length > 0
return this.amount !== 0 && this.amount !== ''
}
},
async mounted() {
this.amount = this.formatterArgs.async ? this.cellValue : (this.cellValue || []).length
},
methods: {
getKey(item) {
const id = Math.random().toString(36).substring(2)
const id = Math.random().toString(36).substring(16)
return id + item
},
getDefaultUrl() {
const url = new URL(this.url, location.origin)
url.pathname += this.row.id + '/'
return url.pathname
},
async getAsyncItems() {
if (!this.formatterArgs.async) {
return
}
if (this.asyncGetDone) {
return
}
const url = this.formatterArgs.ajax.url || this.getDefaultUrl()
const params = this.formatterArgs.ajax.params || {}
const transform = this.formatterArgs.ajax.transform || (resp => resp[this.col.prop.replace('_amount', '')])
const response = await this.$axios.get(url, { params: params })
this.data = transform(response)
this.asyncGetDone = true
}
}
}
</script>
<style lang="scss" scoped>
.detail-content {
padding: 20px 10px;
padding: 5px 10px;
max-height: 60vh;
overflow-y: auto;
}
@@ -86,6 +120,6 @@ export default {
}
.detail-item:first-child {
border-top: 1px solid #EBEEF5;
//border-top: 1px solid #EBEEF5;
}
</style>

View File

@@ -167,6 +167,9 @@ export default {
},
async refresh() {
this.treeSearchValue = ''
if (this.treeSetting?.callback?.beforeRefresh) {
this.treeSetting.callback.beforeRefresh()
}
if (this.treeSetting?.callback?.refresh) {
await this.treeSetting.callback.refresh()
}