perf: asset permission set

This commit is contained in:
ibuler
2025-04-29 16:24:14 +08:00
committed by 老广
parent 5b627350d8
commit 73c593cc4e
5 changed files with 230 additions and 179 deletions

View File

@@ -16,7 +16,7 @@
:is="component"
ref="SearchInput"
v-model.trim="filterValue"
:fetch-suggestions="autocomplete"
:fetch-suggestions="autocomplete ? autocomplete : () => {}"
:placeholder="iPlaceholder"
:type="inputType"
class="search-input"
@@ -78,16 +78,18 @@ export default {
},
data() {
return {
filterTags: this.value,
focus: false,
filterValue: '',
isCheckShowPassword: this.replaceShowPassword,
component: this.autocomplete ? 'el-autocomplete' : 'el-input'
filterTags: this.value,
isCheckShowPassword: this.replaceShowPassword
}
},
computed: {
iPlaceholder() {
return `${this.placeholder} (${this.$t('EnterToContinue')})`
},
component() {
return this.autocomplete !== null ? 'el-autocomplete' : 'el-input'
}
},
watch: {
@@ -98,7 +100,7 @@ export default {
methods: {
handleTagClose(tag) {
this.filterTags.splice(this.filterTags.indexOf(tag), 1)
this.$emit('change', this.filterTags)
this.handleConfirm()
},
handleSelect(item) {
this.filterValue = item.value
@@ -113,14 +115,15 @@ export default {
if (!this.filterTags.includes(this.filterValue)) {
this.filterTags.push(this.filterValue)
this.filterValue = ''
this.$emit('change', this.filterTags)
}
this.$refs.SearchInput.focus()
this.$emit('change', this.filterTags)
this.$emit('input', this.filterTags)
// this.$refs.SearchInput.focus()
},
handleTagClick(v, k) {
this.$delete(this.filterTags, k)
this.filterValue = v
this.$refs.SearchInput.focus()
// this.$refs.SearchInput.focus()
},
matchRule(value) {
const regex = new RegExp(this.replaceRule)
@@ -189,7 +192,7 @@ export default {
}
.filter-field ::v-deep .el-input__inner {
height: 28px !important;
height: 27px !important;
}
.show-password {

View File

@@ -1,53 +1,59 @@
<template>
<el-form class="account-content" @submit.native.prevent>
<el-form-item>
<el-checkbox-group v-model="choicesSelected">
<el-checkbox
v-for="(i) in choices"
<el-radio-group v-model="realRadioSelected" @input="handleRadioChanged">
<el-radio
v-for="(i) in realChoices"
:key="i.label"
:disabled="i.disabled"
:label="i.value"
@change="handleCheckboxCheck(i, $event)"
>
{{ i.label }}
<el-tooltip :content="i.tip" :open-delay="500" placement="top">
<i class="fa fa-question-circle-o" />
</el-tooltip>
</el-checkbox>
</el-radio>
</el-radio-group>
<div v-if="showSpecAccounts" class="spec-accounts spec-zone">
<div class="group-title">{{ $t('SpecAccount') }}</div>
<TagInput
:autocomplete="autocomplete"
:tag-type="getTagType"
:value="specAccountsInput"
@change="handleTagChange"
/>
<span v-if="showAddTemplate">
<el-button size="mini" type="primary" @click="showTemplateDialog=true">
{{ $t('TemplateAdd') }}
</el-button>
<span class="help-block">
{{ addTemplateHelpText }}
</span>
<div v-if="showSpecAccounts" class="spec-accounts spec-zone">
<div class="group-title">{{ $t('SpecAccount') }}</div>
<TagInput
v-model="specAccountsInput"
:autocomplete="autocomplete"
:tag-type="getTagType"
@change="handleTagChange"
/>
<span v-if="showAddTemplate">
<el-button size="mini" type="primary" @click="showTemplateDialog=true">
{{ $t('TemplateAdd') }}
</el-button>
<span class="help-block">
{{ addTemplateHelpText }}
</span>
</div>
</span>
</div>
<div v-if="showVirtualAccountCheckbox" class="spec-zone">
<div class="group-title">{{ $t('VirtualAccounts') }}</div>
<el-checkbox
<div v-if="showNotAccounts" class="not-accounts spec-zone">
<div class="group-title">{{ $t('ExcludeAccount') }}</div>
<TagInput v-model="excludeAccountsInput" @change="handleTagChange" />
</div>
<div v-if="showVirtualAccount" class="spec-zone virtual-choices">
<el-checkbox v-model="virtualChecked"> Virtual Account</el-checkbox>
<el-select v-model="virtualSelected" :multiple="true" @change="handleVirtualChecked">
<el-option
v-for="i in virtualAccounts"
:key="i.label"
:label="i.value"
@change="handleCheckboxCheck(i, $event)"
:label="i.label"
:value="i.value"
>
{{ i.label }}
<el-tooltip :content="i.tip" :open-delay="500" placement="top">
<i class="fa fa-question-circle-o" />
</el-tooltip>
</el-checkbox>
</div>
</el-checkbox-group>
</el-option>
</el-select>
</div>
</el-form-item>
<Dialog
@@ -65,12 +71,15 @@
<script>
import { TagInput } from '@/components/Form/FormFields'
import {
AccountLabelMapper,
accountTemplateTable,
AllAccount,
AnonymousAccount,
ManualAccount,
NotAccount,
realChoices,
SameAccount,
SpecAccount
SpecAccount,
virtualAccounts
} from '@/views/perms/const'
import ListTable from '@/components/Table/ListTable'
import Dialog from '@/components/Dialog'
@@ -116,50 +125,20 @@ export default {
data() {
const vm = this
const virtual = '@VIRTUAL'
const choices = [
{
label: AccountLabelMapper[AllAccount],
value: AllAccount,
tip: this.$t('AllAccountTip')
},
{
label: AccountLabelMapper[SpecAccount],
value: SpecAccount,
tip: this.$t('SpecAccountTip')
},
{
label: this.$t('VirtualAccounts'),
value: virtual,
tip: this.$t('VirtualAccountHelpMsg'),
disabled: !this.showVirtualAccount,
has: this.showVirtualAccount !== false
}
]
return {
ALL: AllAccount,
SPEC: SpecAccount,
VIRTUAL: virtual,
EXCLUDE: NotAccount,
showTemplateDialog: false,
choices: choices,
virtualAccounts: [
{
label: AccountLabelMapper[ManualAccount],
value: ManualAccount,
tip: this.$t('ManualAccountTip')
},
{
label: AccountLabelMapper[SameAccount],
value: SameAccount,
tip: this.$t('SameAccountTip')
},
{
label: AccountLabelMapper[AnonymousAccount],
value: AnonymousAccount,
tip: this.$t('AnonymousAccountTip')
}
],
realRadioSelected: this.ALL,
realChoices: realChoices,
virtualChecked: false,
virtualSelected: [],
output: [],
excludeAccountsInput: [],
virtualAccounts: virtualAccounts,
virtualAccountsNames: [ManualAccount, SameAccount, AnonymousAccount],
choicesSelected: [this.ALL],
specAccountsInput: [],
specAccountsTemplate: [],
showSpecAccounts: false,
@@ -170,43 +149,8 @@ export default {
return 'info'
}
},
accountTemplateTable: {
tableConfig: {
url: '/api/v1/accounts/account-templates/',
columns: [
'name', 'username', 'has_secret', 'comment',
'date_created', 'date_updated'
],
columnsMeta: {
name: {
formatterArgs: {
openInNewPage: true,
getRoute({ row, col, cellValue }) {
return {
name: 'AccountTemplateDetail',
params: {
id: row.id
}
}
}
}
},
has_secret: {
formatterArgs: {
showFalse: false
}
},
actions: {
has: false
}
}
},
headerActions: {
hasLeftActions: false,
hasImport: false,
hasExport: false
}
},
showNotAccounts: false,
accountTemplateTable: accountTemplateTable,
autocomplete: (query, cb) => {
const data = {
username: query,
@@ -228,52 +172,55 @@ export default {
}
}
},
computed: {
showVirtualAccountCheckbox() {
if (!this.showVirtualAccount) {
return false
}
const hasVirtual = this.choicesSelected.filter(i => {
return i && i.startsWith('@') && i !== '@ALL' && i !== '@SPEC'
})
return hasVirtual.length > 0
watch: {
realRadioSelected(val) {
this.showSpecAccounts = val === this.SPEC
this.showNotAccounts = val === this.EXCLUDE
}
},
mounted() {
this.initDefaultChoice()
setTimeout(() => {
if (this.value.length === 0) {
this.$emit('input', ['@ALL'])
} else {
this.$emit('input', this.value)
}
})
},
methods: {
getVirtualChoices(val) {
return this.virtualAccounts.filter(i => {
return val.includes(i.value)
}).map(i => i.value)
},
getExcludeChoices(val) {
return val.filter(i => i.startsWith('!')).map(i => i.substring(1))
},
getSpecValues(val) {
return val.filter(i => !i.startsWith('@') && !i.startsWith('!'))
},
initDefaultChoice() {
const choicesSelected = this.value.filter(i => {
return i.startsWith('@') && i !== this.SPEC && i !== this.VIRTUAL
})
// 是否添加特定账号选择
const specAccountsInput = this.value.filter(i => !i.startsWith('@') && i !== this.SPEC)
if (specAccountsInput.length > 0 && !choicesSelected.includes(this.ALL)) {
choicesSelected.push(this.SPEC)
this.showSpecAccounts = true
}
// 是否添加虚拟账号选择
const hasVirtual = this.value.filter(i => {
return i && i.startsWith('@') && i !== '@ALL' && i !== '@SPEC'
})
if (hasVirtual.length > 0 && !choicesSelected.includes(this.VIRTUAL)) {
choicesSelected.push(this.VIRTUAL)
const value = this.value || []
if (value.length === 0) {
value.push(this.ALL)
}
// 如果没有就设置 ALL
if (choicesSelected.length === 0) {
choicesSelected.push(this.ALL)
const specAccountsInput = this.getSpecValues(value)
// const excludeAccountsInput = this.getExcludeChoices(value)
// 先清理 radio
const isAll = value.includes(this.ALL)
if (isAll) {
this.realRadioSelected = this.ALL
} else if (specAccountsInput.length > 0) {
this.realRadioSelected = this.SPEC
this.specAccountsInput = specAccountsInput
// } else if (excludeAccountsInput.length > 0) {
// this.realRadioSelected = this.EXCLUDE
// this.excludeAccountsInput = excludeAccountsInput
} else {
this.realRadioSelected = this.ALL
}
// 清理虚拟账号
const virtualChoices = this.getVirtualChoices(this.value)
if (virtualChoices.length > 0) {
this.virtualChecked = true
this.virtualSelected = virtualChoices
}
this.choicesSelected = choicesSelected
this.specAccountsInput = specAccountsInput
},
handleAccountTemplateCancel() {
this.showTemplateDialog = false
@@ -288,33 +235,32 @@ export default {
this.outputValue()
}, 100)
},
handleCheckboxCheck(item, checked) {
if (item.value === this.SPEC) {
this.showSpecAccounts = checked
} else if (item.value === this.ALL) {
this.showSpecAccounts = checked ? false : checked
}
if (item.value === this.ALL) {
this.choicesSelected = this.choicesSelected.filter(i => i !== this.SPEC)
} else if (item.value === this.SPEC) {
this.choicesSelected = this.choicesSelected.filter(i => i !== this.ALL)
} else if (item.value === this.VIRTUAL) {
if (!checked) {
this.choicesSelected = this.choicesSelected.filter(i => !this.virtualAccountsNames.includes(i))
}
}
handleVirtualChecked(evt, checked) {
this.outputValue()
},
handleTagChange(val) {
this.specAccountsInput = val
handleRadioChanged(value) {
this.outputValue()
},
handleTagChange() {
this.outputValue()
},
outputValue() {
let choicesSelected = this.choicesSelected
if (this.showSpecAccounts) {
// 这是真是的
let choicesSelected = []
if (this.realRadioSelected === this.ALL) {
choicesSelected = [this.ALL]
} else if (this.realRadioSelected === this.SPEC && this.showSpecAccounts) {
const templateIds = this.specAccountsTemplate.map(i => `%${i.id}`)
choicesSelected = [...this.choicesSelected, ...this.specAccountsInput, ...templateIds]
choicesSelected = [this.realRadioSelected, ...this.specAccountsInput, ...templateIds]
}
// else if (this.realRadioSelected === this.EXCLUDE && this.excludeAccountsInput) {
// choicesSelected = [...this.excludeAccountsInput].map(i => '!' + i)
// }
if (this.virtualChecked) {
choicesSelected = [...choicesSelected, ...this.virtualSelected]
}
this.$emit('input', choicesSelected)
this.$emit('change', choicesSelected)
}
@@ -349,13 +295,19 @@ export default {
.spec-zone {
border-bottom: dashed 1px var(--color-border);
padding-bottom: 5px;
padding-bottom: 10px;
&:last-child {
border-bottom: none;
}
}
.virtual-choices {
.el-select {
width: 100%;
}
}
.account-content {
::v-deep {
.el-form-item__content {

View File

@@ -154,6 +154,7 @@ export const AssetPermissionListPageSearchConfigOptions = [
export const AllAccount = '@ALL'
export const SpecAccount = '@SPEC'
export const NotAccount = '@NOT'
export const SameAccount = '@USER'
export const ManualAccount = '@INPUT'
export const AnonymousAccount = '@ANON'
@@ -161,7 +162,82 @@ export const AnonymousAccount = '@ANON'
export const AccountLabelMapper = {
[AllAccount]: i18n.t('AllAccounts'),
[SpecAccount]: i18n.t('SpecAccount'),
[NotAccount]: i18n.t('ExcludeAccount'),
[SameAccount]: i18n.t('SameAccount'),
[ManualAccount]: i18n.t('ManualAccount'),
[AnonymousAccount]: i18n.t('AnonymousAccount')
}
export const realChoices = [
{
label: AccountLabelMapper[AllAccount],
value: AllAccount,
tip: i18n.t('AllAccountTip')
},
{
label: AccountLabelMapper[SpecAccount],
value: SpecAccount,
tip: i18n.t('SpecAccountTip')
}
// {
// label: AccountLabelMapper[NotAccount],
// value: NotAccount,
// tip: i18n.t('NotAccountTip')
// }
]
export const virtualAccounts = [
{
label: AccountLabelMapper[ManualAccount],
value: ManualAccount,
tip: i18n.t('ManualAccountTip')
},
{
label: AccountLabelMapper[SameAccount],
value: SameAccount,
tip: i18n.t('SameAccountTip')
},
{
label: AccountLabelMapper[AnonymousAccount],
value: AnonymousAccount,
tip: i18n.t('AnonymousAccountTip')
}
]
export const accountTemplateTable = {
tableConfig: {
url: '/api/v1/accounts/account-templates/',
columns: [
'name', 'username', 'has_secret', 'comment',
'date_created', 'date_updated'
],
columnsMeta: {
name: {
formatterArgs: {
openInNewPage: true,
getRoute({ row, col, cellValue }) {
return {
name: 'AccountTemplateDetail',
params: {
id: row.id
}
}
}
}
},
has_secret: {
formatterArgs: {
showFalse: false
}
},
actions: {
has: false
}
}
},
headerActions: {
hasLeftActions: false,
hasImport: false,
hasExport: false
}
}

View File

@@ -129,7 +129,7 @@ export default {
this.$axios.get(url).then(res => {
return this.makeCredReq(res)
}).then((options) => {
if (!location.protocol.startsWith('https')) {
if (!location.protocol.startsWith('https') && location.host !== 'localhost') {
throw new Error(this.$tc('HTTPSRequiredForSupport'))
}
return navigator.credentials.create(options)

View File

@@ -24,17 +24,22 @@
style="margin-top: 15px"
type="warning"
/>
<IBox v-if="!store.getters.publicSettings['PRIVACY_MODE']" :title="$tc('InformationModification')" fa="fa-edit">
<IBox
v-if="!store.getters.publicSettings['PRIVACY_MODE']"
:title="$tc('InformationModification')"
class="update-info"
fa="fa-edit"
>
<table>
<tr>
<td> {{ $t('Phone') }}</td>
<td>
<td class="label"> {{ $t('Phone') }}</td>
<td class="value">
<PhoneInput :value="object.phone" />
</td>
</tr>
<tr>
<td> {{ $t('WeChat') }}</td>
<td>
<td class="label"> {{ $t('WeChat') }}</td>
<td class="value">
<el-input v-model="object.wechat" />
</td>
</tr>
@@ -487,6 +492,21 @@ export default {
}
}
</script>
<style scoped>
<style lang="scss" scoped>
.update-info {
table {
width: 100%;
.label {
width: 20%;
}
.value {
width: 60%;
}
}
}
</style>