mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
feat(user): user manage
This commit is contained in:
@@ -37,6 +37,7 @@
|
||||
"vue-dompurify-html": "^5.3.0",
|
||||
"vue-i18n": "^9.14.4",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue-types": "^6.0.0",
|
||||
"web-storage-cache": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const userImportApi = {
|
||||
downExcelTemplateApi: () => request.post('/user/excelTemplate', {}, { responseType: 'blob' }),
|
||||
importUserApi: (data: any) =>
|
||||
request.post('/user/batchImport', data, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}),
|
||||
downErrorRecordApi: (key: string) =>
|
||||
request.get(`/user/errorRecord/${key}`, { responseType: 'blob' }),
|
||||
clearErrorApi: (key: string) => {
|
||||
request.get(`/user/clearErrorRecord/${key}`)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { request } from '@/utils/request'
|
||||
|
||||
export const workspaceUserList = (params: any, pageNum: number, pageSize: number) =>
|
||||
request.get(`/system/workspace/uws/pager/${pageNum}/${pageSize}`, { params })
|
||||
|
||||
export const workspaceCreate = (data: any) => request.post('/system/workspace/uws', data)
|
||||
export const workspaceDelete = (data: any) => request.delete('/system/workspace/uws', data)
|
||||
export const workspaceList = () => request.get('/system/workspace')
|
||||
export const workspaceDetail = (id: any) => request.get(`/system/workspace/${id}`)
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.3334 4.33333C10.3334 2.49233 8.84004 1 7.00004 1C5.16004 1 3.66671 2.49233 3.66671 4.33333C3.66671 6.17433 5.16004 7.66667 7.00004 7.66667C8.84004 7.66667 10.3334 6.17433 10.3334 4.33333ZM7.00004 6.33333C5.89671 6.33333 5.00004 5.438 5.00004 4.33333C5.00004 3.22867 5.89671 2.33333 7.00004 2.33333C8.10337 2.33333 9.00004 3.22867 9.00004 4.33333C9.00004 5.438 8.10337 6.33333 7.00004 6.33333Z" fill="#646A73"/>
|
||||
<path d="M2.66671 12.6667C2.66671 12.4363 2.69671 12.213 2.75004 12C3.04671 10.85 4.09004 10 5.33337 10H8.01668C8.20077 10 8.35001 9.85076 8.35001 9.66667V9C8.35001 8.81591 8.20077 8.66667 8.01668 8.66667H5.33337C3.12337 8.66667 1.33337 10.4577 1.33337 12.6667V14C1.33337 14.3683 1.63337 14.6667 2.00004 14.6667H8.01668C8.20077 14.6667 8.35001 14.5174 8.35001 14.3333V13.6667C8.35001 13.4826 8.20077 13.3333 8.01668 13.3333H2.66671V12.6667Z" fill="#646A73"/>
|
||||
<path d="M12.1312 10.3738L13.11 11.3526H9.66671C9.48261 11.3526 9.33337 11.5019 9.33337 11.686V12.3526C9.33337 12.5367 9.48261 12.686 9.66671 12.686H13.1188L12.1312 13.6736C12.001 13.8038 12.001 14.0148 12.1312 14.145L12.6026 14.6164C12.7328 14.7466 12.9438 14.7466 13.074 14.6164L15.431 12.2594C15.5612 12.1292 15.5612 11.9182 15.431 11.788L13.074 9.43096C12.9438 9.30079 12.7328 9.30079 12.6026 9.43096L12.1312 9.90237C12.001 10.0325 12.001 10.2436 12.1312 10.3738Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.99993 5.29289L9.85868 1.43413C9.95632 1.3365 10.1146 1.3365 10.2122 1.43413L10.5658 1.78769C10.6634 1.88532 10.6634 2.04361 10.5658 2.14124L6.70703 6L10.5658 9.85875C10.6634 9.95639 10.6634 10.1147 10.5658 10.2123L10.2122 10.5659C10.1146 10.6635 9.95632 10.6635 9.85868 10.5659L5.99993 6.7071L2.14117 10.5659C2.04354 10.6635 1.88525 10.6635 1.78762 10.5659L1.43406 10.2123C1.33643 10.1147 1.33643 9.95639 1.43406 9.85875L5.29282 6L1.43406 2.14124C1.33643 2.04361 1.33643 1.88532 1.43406 1.78769L1.78762 1.43413C1.88525 1.3365 2.04354 1.3365 2.14117 1.43413L5.99993 5.29289Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 8.66667H6C4.15905 8.66667 2 9.95432 2 12.2667V14.0667C2 14.398 2.29848 14.6667 2.66667 14.6667H13.3333C13.7015 14.6667 14 14.398 14 14.0667V12.2667C14 9.95237 11.841 8.66667 10 8.66667Z" fill="#8F959E"/>
|
||||
<path d="M4.66667 4.66667C4.66667 6.50762 6.15905 8 8 8C9.84095 8 11.3333 6.50762 11.3333 4.66667C11.3333 2.82572 9.84095 1.33333 8 1.33333C6.15905 1.33333 4.66667 2.82572 4.66667 4.66667Z" fill="#8F959E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 527 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.35399 11.2488H7.62467L14.3787 4.4946C14.6108 4.26253 14.6113 3.88643 14.3798 3.65375L12.3342 1.59735C12.1017 1.36485 11.7247 1.36485 11.4922 1.59735L11.0723 2.01722L11.074 2.01887L4.75864 8.3828V10.6535C4.75864 10.9823 5.02519 11.2488 5.35399 11.2488ZM11.8943 2.84351L13.1 4.05552L12.0797 5.07678L10.8803 3.87736L11.8943 2.84351ZM11.2382 5.91913L7.1225 10.0388H7.12068L5.9687 8.88498L10.0465 4.72746L11.2382 5.91913Z" fill="#646A73"/>
|
||||
<path d="M8.76313 2.316H2.44339C2.07805 2.316 1.78189 2.61216 1.78189 2.9775V13.5615C1.78189 13.9268 2.07805 14.223 2.44339 14.223H13.0274C13.3927 14.223 13.6889 13.9268 13.6889 13.5615V7.1614L12.3576 8.50072V12.8982H3.11313V3.6408H7.46239L8.76313 2.316Z" fill="#646A73"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 823 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
import DrawerFilter from './src/DrawerFilter.vue'
|
||||
|
||||
export { DrawerFilter }
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { reactive } from 'vue'
|
||||
import { useEmitt } from '@/utils/useEmitt'
|
||||
|
||||
const props = defineProps({
|
||||
optionList: propTypes.arrayOf(
|
||||
propTypes.shape({
|
||||
id: propTypes.string,
|
||||
name: propTypes.string,
|
||||
})
|
||||
),
|
||||
index: propTypes.number,
|
||||
title: propTypes.string,
|
||||
})
|
||||
|
||||
const state = reactive<{ activeStatus: any[]; optionList: any[] }>({
|
||||
activeStatus: [],
|
||||
optionList: [],
|
||||
})
|
||||
|
||||
const nodeChange = (id: string | number) => {
|
||||
const len = state.activeStatus.indexOf(id)
|
||||
if (len >= 0) {
|
||||
state.activeStatus.splice(len, 1)
|
||||
} else {
|
||||
state.activeStatus.push(id)
|
||||
}
|
||||
emits('filter-change', state.activeStatus)
|
||||
}
|
||||
|
||||
const emits = defineEmits(['filter-change'])
|
||||
const clear = (index: number) => {
|
||||
if (index !== props.index) return
|
||||
state.activeStatus = []
|
||||
}
|
||||
|
||||
useEmitt({
|
||||
name: 'clear-drawer_main',
|
||||
callback: clear,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="draw-filter_enum">
|
||||
<span>{{ title }}</span>
|
||||
<div class="filter-item">
|
||||
<span
|
||||
v-for="ele in props.optionList"
|
||||
:key="ele.id"
|
||||
class="item"
|
||||
:class="[state.activeStatus.includes(ele.id) ? 'active' : '']"
|
||||
@click="nodeChange(ele.id)"
|
||||
>{{ ele.name }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scope>
|
||||
.draw-filter_enum {
|
||||
margin-bottom: 4px;
|
||||
|
||||
> :nth-child(1) {
|
||||
color: var(--deTextSecondary, #1f2329);
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
margin-top: 8px;
|
||||
.item,
|
||||
.more {
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
margin-right: 12px;
|
||||
text-align: center;
|
||||
padding: 1px 6px;
|
||||
background: var(--deTextPrimary5, #f5f6f7);
|
||||
color: var(--deTextPrimary, #1f2329);
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.active,
|
||||
.more:hover {
|
||||
background: var(--primary10, var(--ed-color-primary-1a, #1cba9033));
|
||||
color: var(--primaryselect, #0c296e);
|
||||
}
|
||||
|
||||
.more {
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
.filter-popper {
|
||||
padding: 0 !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ElSelect, ElOption } from 'element-plus-secondary'
|
||||
import { computed, reactive } from 'vue'
|
||||
import { useEmitt } from '@/utils/useEmitt'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
optionList: propTypes.arrayOf(
|
||||
propTypes.shape({
|
||||
id: propTypes.string,
|
||||
name: propTypes.string,
|
||||
})
|
||||
),
|
||||
index: propTypes.number,
|
||||
title: propTypes.string,
|
||||
property: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const state = reactive({
|
||||
activeStatus: [],
|
||||
})
|
||||
const emits = defineEmits(['filter-change'])
|
||||
|
||||
const selectStatus = (ids: any[]) => {
|
||||
emits(
|
||||
'filter-change',
|
||||
ids.map((item) => item.id || item.value)
|
||||
)
|
||||
}
|
||||
|
||||
const optionListNotSelect = computed(() => {
|
||||
return [...(props.optionList as any[])]
|
||||
})
|
||||
const clear = (index: number) => {
|
||||
if (index !== props.index) return
|
||||
state.activeStatus = []
|
||||
}
|
||||
|
||||
useEmitt({
|
||||
name: 'clear-drawer_main',
|
||||
callback: clear,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="draw-filter_base">
|
||||
<span>{{ title }}</span>
|
||||
<div class="filter-item">
|
||||
<el-select
|
||||
v-model="state.activeStatus"
|
||||
:teleported="false"
|
||||
style="width: 100%"
|
||||
value-key="id"
|
||||
filterable
|
||||
:placeholder="t('datasource.Please_select') + props.property.placeholder"
|
||||
multiple
|
||||
@change="selectStatus"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in optionListNotSelect"
|
||||
:key="item.name"
|
||||
:label="item.name"
|
||||
:value="item"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scope>
|
||||
.draw-filter_base {
|
||||
margin-bottom: 16px;
|
||||
|
||||
> :nth-child(1) {
|
||||
color: var(--deTextSecondary, #1f2329);
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import DrawerMain from './src/DrawerMain.vue'
|
||||
|
||||
export { DrawerMain }
|
||||
@@ -0,0 +1,163 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { ElDrawer, ElButton } from 'element-plus-secondary'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import DrawerFilter from '@/components/drawer-filter/src/DrawerFilter.vue'
|
||||
import DrawerEnumFilter from '@/components/drawer-filter/src/DrawerEnumFilter.vue'
|
||||
import { useEmitt } from '@/utils/useEmitt'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
filterOptions: propTypes.arrayOf(
|
||||
propTypes.shape({
|
||||
type: propTypes.string,
|
||||
field: propTypes.string,
|
||||
option: propTypes.array,
|
||||
title: propTypes.string,
|
||||
property: propTypes.shape({}),
|
||||
})
|
||||
),
|
||||
title: propTypes.string,
|
||||
})
|
||||
|
||||
const componentList = computed(() => {
|
||||
return props.filterOptions as any[]
|
||||
})
|
||||
|
||||
const state = reactive<{ conditions: any[] }>({
|
||||
conditions: [],
|
||||
})
|
||||
const userDrawer = ref(false)
|
||||
|
||||
const init = () => {
|
||||
userDrawer.value = true
|
||||
}
|
||||
|
||||
const clear = (index: any) => {
|
||||
useEmitt().emitter.emit('clear-drawer_main', index)
|
||||
}
|
||||
const cleanrInnerValue = (index: number) => {
|
||||
const field = componentList.value[index]?.field
|
||||
if (!field) {
|
||||
return
|
||||
}
|
||||
clear(index)
|
||||
for (let i = 0; i < state.conditions.length; i++) {
|
||||
if (state.conditions[i].field === field) {
|
||||
state.conditions[i].value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
const clearInnerTag = (index?: number) => {
|
||||
if (isNaN(index as number)) {
|
||||
for (let i = 0; i < componentList.value.length; i++) {
|
||||
clear(i)
|
||||
}
|
||||
return
|
||||
}
|
||||
const condition = state.conditions[index as number]
|
||||
const field = condition?.field
|
||||
for (let i = 0; i < componentList.value.length; i++) {
|
||||
if (componentList.value[i].field === field) {
|
||||
clear(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
const clearFilter = (id?: number) => {
|
||||
clearInnerTag(id)
|
||||
if (isNaN(id as number)) {
|
||||
const len = state.conditions.length
|
||||
state.conditions.splice(0, len)
|
||||
} else {
|
||||
state.conditions.splice(id as number, 1)
|
||||
}
|
||||
trigger()
|
||||
}
|
||||
const filterChange = (value: any, field: any, operator: any) => {
|
||||
let exits = false
|
||||
let len = state.conditions.length
|
||||
while (len--) {
|
||||
const condition = state.conditions[len]
|
||||
if (condition.field === field) {
|
||||
exits = true
|
||||
condition['value'] = value
|
||||
}
|
||||
if (!condition?.value?.length) {
|
||||
state.conditions.splice(len, 1)
|
||||
}
|
||||
}
|
||||
if (!exits && value?.length) {
|
||||
state.conditions.push({ field, value, operator })
|
||||
}
|
||||
treeFilterChange(value, field, operator)
|
||||
}
|
||||
const reset = () => {
|
||||
clearFilter()
|
||||
userDrawer.value = false
|
||||
}
|
||||
const close = () => {
|
||||
userDrawer.value = false
|
||||
}
|
||||
const emits = defineEmits(['trigger-filter', 'tree-filter-change'])
|
||||
const trigger = () => {
|
||||
emits('trigger-filter', state.conditions)
|
||||
}
|
||||
const treeFilterChange = (value: any, field: any, operator: any) => {
|
||||
emits('tree-filter-change', {
|
||||
value,
|
||||
field,
|
||||
operator,
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
init,
|
||||
clearFilter,
|
||||
close,
|
||||
cleanrInnerValue,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer
|
||||
v-model="userDrawer"
|
||||
:title="t('user.filter_conditions')"
|
||||
size="600px"
|
||||
modal-class="drawer-main-container"
|
||||
direction="rtl"
|
||||
>
|
||||
<div v-for="(component, index) in componentList" :key="index">
|
||||
<drawer-filter
|
||||
v-if="component.type === 'select'"
|
||||
:option-list="component.option"
|
||||
:title="component.title"
|
||||
:index="index"
|
||||
:property="component.property"
|
||||
@filter-change="(v) => filterChange(v, component.field, 'in')"
|
||||
/>
|
||||
<drawer-enum-filter
|
||||
v-if="component.type === 'enum'"
|
||||
:option-list="component.option"
|
||||
:title="component.title"
|
||||
:index="index"
|
||||
@filter-change="(v) => filterChange(v, component.field, 'in')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<el-button secondary @click="reset">{{ t('common.reset') }}</el-button>
|
||||
<el-button type="primary" @click="trigger">{{ t('common.search') }}</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.drawer-main-container {
|
||||
.ed-drawer__body {
|
||||
padding: 16px 24px 80px !important;
|
||||
}
|
||||
.ed-drawer__footer {
|
||||
padding: 16px 24px;
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
import FilterText from './src/FilterText.vue'
|
||||
|
||||
export { FilterText }
|
||||
|
||||
const fieldText = (field: string, options: any[]) => {
|
||||
for (let index = 0; index < options.length; index++) {
|
||||
const element = options[index]
|
||||
if (field === element.field) {
|
||||
return element.title
|
||||
}
|
||||
}
|
||||
}
|
||||
const valueTextFormTree = (val: string, options: any[]) => {
|
||||
let result = null
|
||||
const stack = [...options]
|
||||
while (stack.length) {
|
||||
const item = stack.pop()
|
||||
if (item.value === val) {
|
||||
result = item.label
|
||||
break
|
||||
}
|
||||
if (item.children?.length) {
|
||||
item.children.forEach((kid: any) => stack.push(kid))
|
||||
}
|
||||
}
|
||||
return result || val
|
||||
}
|
||||
const timestampFormatDate = (value: string) => {
|
||||
if (!value) {
|
||||
return '-'
|
||||
}
|
||||
return new Date(value).toLocaleString()
|
||||
}
|
||||
const valueText = (field: string, val: string, options: any[]) => {
|
||||
for (let index = 0; index < options.length; index++) {
|
||||
const element = options[index]
|
||||
if (field === element.field) {
|
||||
let isTree = false
|
||||
const selectOption = element.option
|
||||
for (let i = 0; i < selectOption.length; i++) {
|
||||
const item = selectOption[i] as any
|
||||
if (item.children !== undefined) {
|
||||
isTree = true
|
||||
break
|
||||
}
|
||||
if (item.id === val || item.value === val) {
|
||||
return item.name || item.label
|
||||
}
|
||||
}
|
||||
if (element.type === 'time') {
|
||||
return timestampFormatDate(val)
|
||||
}
|
||||
if (isTree) {
|
||||
return valueTextFormTree(val, selectOption)
|
||||
}
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
export const convertFilterText = (conditions: any[], options: any[]) => {
|
||||
console.log('conditions', conditions, options)
|
||||
|
||||
const result: any[] = []
|
||||
conditions.forEach((condition) => {
|
||||
const field = condition.field
|
||||
const ope = condition.operator
|
||||
const value = condition.value
|
||||
const fieldName = fieldText(field, options)
|
||||
const textOpe = ':'
|
||||
let separator = ','
|
||||
if (ope === 'in') {
|
||||
separator = '、'
|
||||
}
|
||||
if (ope === 'between') {
|
||||
separator = '~'
|
||||
}
|
||||
let valText = value
|
||||
|
||||
if (value) {
|
||||
if (Array.isArray(value)) {
|
||||
const valTextArray = value.map((val) => valueText(field, val, options))
|
||||
valText = valTextArray.join(separator)
|
||||
}
|
||||
if (valText?.length) {
|
||||
const obj = fieldName + textOpe + valText
|
||||
result.push(obj)
|
||||
}
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<script lang="ts" setup>
|
||||
import icon_left_outlined from '@/assets/svg/common-back.svg'
|
||||
import icon_close_outlined from '@/assets/svg/icon_close_outlined.svg'
|
||||
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete.svg'
|
||||
import icon_right_outlined from '@/assets/svg/icon_right_outlined.svg'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { Icon } from '@/components/icon-custom'
|
||||
import { ElButton, ElDivider, ElIcon } from 'element-plus-secondary'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
filterTexts: {
|
||||
type: Array<string>,
|
||||
default: () => [],
|
||||
},
|
||||
total: propTypes.number.def(0),
|
||||
})
|
||||
|
||||
const emits = defineEmits(['clearFilter'])
|
||||
const container = ref<any>(null)
|
||||
|
||||
const showScroll = ref(false)
|
||||
const scrollPre = () => {
|
||||
container.value!.scrollLeft -= 10
|
||||
if (container.value.scrollLeft <= 0) {
|
||||
container.value.scrollLeft = 0
|
||||
}
|
||||
}
|
||||
const scrollNext = () => {
|
||||
container.value.scrollLeft += 10
|
||||
const width = container.value.scrollWidth - container.value.offsetWidth
|
||||
if (container.value.scrollLeft > width) {
|
||||
container.value.scrollLeft = width
|
||||
}
|
||||
}
|
||||
const clearFilter = (index?: number) => {
|
||||
emits('clearFilter', index)
|
||||
}
|
||||
|
||||
const clearFilterAll = () => {
|
||||
emits('clearFilter', 'empty')
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.filterTexts,
|
||||
() => {
|
||||
nextTick(() => {
|
||||
showScroll.value = container.value?.scrollWidth > container.value?.offsetWidth
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="filterTexts.length" class="filter-texts">
|
||||
<span class="sum">{{ total }}</span>
|
||||
<span class="title">{{ t('common.result_count') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<el-icon v-if="showScroll" class="arrow-left arrow-filter" @click="scrollPre">
|
||||
<Icon name="icon_left_outlined"><icon_left_outlined class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
<div ref="container" class="filter-texts-container">
|
||||
<p v-for="(ele, index) in filterTexts" :key="ele" class="text">
|
||||
<el-tooltip effect="dark" :content="ele" placement="top-start">
|
||||
{{ ele }}
|
||||
</el-tooltip>
|
||||
<el-icon @click="clearFilter(index)">
|
||||
<Icon name="icon_close_outlined"><icon_close_outlined class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</p>
|
||||
<el-button
|
||||
v-if="!showScroll"
|
||||
type="text"
|
||||
class="clear-btn clear-btn-inner"
|
||||
@click="clearFilterAll"
|
||||
>
|
||||
<template #icon>
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</template>
|
||||
{{ t('common.clear_filter') }}</el-button
|
||||
>
|
||||
</div>
|
||||
<el-icon v-if="showScroll" class="arrow-right arrow-filter" @click="scrollNext">
|
||||
<Icon name="icon_right_outlined"><icon_right_outlined class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
<el-button
|
||||
v-if="showScroll"
|
||||
type="text"
|
||||
class="clear-btn"
|
||||
style="height: 24px; line-height: 24px"
|
||||
@click="clearFilterAll"
|
||||
>
|
||||
<template #icon>
|
||||
<Icon name="icon_delete-trash_outlined"
|
||||
><icon_deleteTrash_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</template>
|
||||
{{ t('common.clear_filter') }}</el-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.filter-texts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 16px 0;
|
||||
font-weight: 400;
|
||||
|
||||
.sum {
|
||||
color: #1f2329;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #999999;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.text {
|
||||
max-width: 280px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 1px 22px 1px 6px;
|
||||
display: inline-block;
|
||||
align-items: center;
|
||||
color: #0c296e;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
background: var(--ed-color-primary-1a, rgba(51, 112, 255, 0.1));
|
||||
border-radius: 2px;
|
||||
margin: 0;
|
||||
margin-right: 8px;
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 50%;
|
||||
font-size: 12px;
|
||||
transform: translateY(-50%);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.clear-btn {
|
||||
color: #646a73;
|
||||
padding: 0 4px;
|
||||
border-radius: 6px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.clear-btn:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
.clear-btn:active {
|
||||
background: #1f232933;
|
||||
}
|
||||
|
||||
.filter-texts-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.arrow-filter {
|
||||
font-size: 16px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
color: #646a73;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.arrow-filter:hover {
|
||||
background: rgba(31, 35, 41, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ed-icon-arrow-right.arrow-filter {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.ed-icon-arrow-left.arrow-filter {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.filter-texts-container {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
height: 24px;
|
||||
|
||||
.clear-btn-inner {
|
||||
margin-top: -16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { h } from 'vue'
|
||||
import { ElMenuItem, ElSubMenu, ElIcon } from 'element-plus-secondary'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import model from '@/assets/svg/icon_dataset_filled.svg'
|
||||
import ds from '@/assets/svg/ds.svg'
|
||||
import user from '@/assets/svg/icon_member_filled.svg'
|
||||
import workspace from '@/assets/svg/icon_moments-categories_outlined.svg'
|
||||
|
||||
const iconMap = {
|
||||
icon_ai: model,
|
||||
ds,
|
||||
user,
|
||||
workspace,
|
||||
} as { [key: string]: any }
|
||||
|
||||
const titleWithIcon = (props: any) => {
|
||||
@@ -21,6 +24,8 @@ const titleWithIcon = (props: any) => {
|
||||
}
|
||||
|
||||
const MenuItem = (props: any) => {
|
||||
const router = useRouter()
|
||||
|
||||
const { children, hidden, path } = props.menu
|
||||
if (hidden) {
|
||||
return null
|
||||
@@ -28,7 +33,9 @@ const MenuItem = (props: any) => {
|
||||
if (children?.length) {
|
||||
return h(
|
||||
ElSubMenu,
|
||||
{ index: path },
|
||||
{
|
||||
index: path,
|
||||
},
|
||||
{
|
||||
title: () => titleWithIcon(props),
|
||||
default: () => children.map((ele: any) => h(MenuItem, { menu: ele })),
|
||||
@@ -39,7 +46,12 @@ const MenuItem = (props: any) => {
|
||||
const iconCom: any = iconMap[icon] ? ElIcon : null
|
||||
return h(
|
||||
ElMenuItem,
|
||||
{ index: path },
|
||||
{
|
||||
index: path,
|
||||
onClick: () => {
|
||||
router.push(path)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: h('span', null, { default: () => title }),
|
||||
default: h(iconCom, null, {
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
"save_success": "Save Successful",
|
||||
"next": "Next",
|
||||
"save": "Save",
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"result_count": "result",
|
||||
"clear_filter": "Clear conditions",
|
||||
"reset": "Reset"
|
||||
},
|
||||
"dashboard": {
|
||||
"length_1_64_characters": "Name field length must be 1-64 characters",
|
||||
@@ -155,6 +158,7 @@
|
||||
"field_name": "Field name",
|
||||
"relevant_content_found": "No relevant content found",
|
||||
"please_enter": "Please enter",
|
||||
"Please_select": "Please select",
|
||||
"table_notes": "Table notes",
|
||||
"field_notes": "Field notes",
|
||||
"select_all": "Select all",
|
||||
@@ -221,7 +225,50 @@
|
||||
"1240_results": "{msg} results",
|
||||
"clear_conditions": "Clear conditions",
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled"
|
||||
"enabled": "Enabled",
|
||||
"edit_user": "Edit User",
|
||||
"del_user": "Do you want to delete user: {msg}?",
|
||||
"please_first": "Please first",
|
||||
"download_the_template": "download the template",
|
||||
"required_and_upload": ", fill in as required and upload",
|
||||
"file": "file",
|
||||
"upload_file": "Upload file",
|
||||
"xls_format_files": "Only supports xlsx and xls format files",
|
||||
"import": "Import",
|
||||
"change_file": "Change file",
|
||||
"data_import_completed": "Data import completed",
|
||||
"imported_100_data": "Successfully imported {msg} data",
|
||||
"return_to_view": "Return to view",
|
||||
"continue_importing": "Continue importing",
|
||||
"import_20_can": "Successfully imported {msg} data, failed to import {loss}, can",
|
||||
"download_error_report": "Download error report",
|
||||
"modify_and_re_import": ", modify and re-import",
|
||||
"data_import_failed": "Data import failed",
|
||||
"failed_100_can": "Import failed {msg}, can",
|
||||
"change_password": "Change password",
|
||||
"new_password": "New password",
|
||||
"confirm_password": "Confirm password",
|
||||
"old_password": "Old password"
|
||||
},
|
||||
"workspace": {
|
||||
"add_workspace": "Add workspace",
|
||||
"administrator": "Administrator",
|
||||
"ordinary_member": "Ordinary member",
|
||||
"people": "People",
|
||||
"name_username_email": "Search name, username, email",
|
||||
"add_member": "Add member",
|
||||
"member_type": "Member type",
|
||||
"workspace_name": "Workspace name",
|
||||
"no_user": "No user",
|
||||
"remove": "Remove",
|
||||
"selected_2_members": "Do you want to remove the selected {msg} members?",
|
||||
"removed_successfully": "Removed successfully",
|
||||
"workspace:_de_workspace": "Do you want to delete the workspace: {msg}?",
|
||||
"member:_feng_yibudao": "Do you want to remove the member: {msg}?",
|
||||
"select_member": "Select member",
|
||||
"selected:_2_people": "Selected: {msg} people",
|
||||
"clear": "Clear",
|
||||
"historical_dialogue": "No historical dialogue"
|
||||
},
|
||||
"chat": {
|
||||
"chart_type": {
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
"save_success": "保存成功",
|
||||
"next": "下一步",
|
||||
"save": "保存",
|
||||
"logout": "退出登录"
|
||||
"logout": "退出登录",
|
||||
"result_count": "个结果",
|
||||
"clear_filter": "清空条件",
|
||||
"reset": "重置"
|
||||
},
|
||||
"dashboard": {
|
||||
"length_1_64_characters": "名称字段长度1-64个字符",
|
||||
@@ -155,6 +158,7 @@
|
||||
"field_name": "字段名称",
|
||||
"relevant_content_found": "没有找到相关内容",
|
||||
"please_enter": "请输入",
|
||||
"Please_select": "请选择",
|
||||
"table_notes": "表备注",
|
||||
"field_notes": "字段备注",
|
||||
"select_all": "全选",
|
||||
@@ -224,7 +228,50 @@
|
||||
"1240_results": "{msg} 个结果 ",
|
||||
"clear_conditions": "清空条件",
|
||||
"disabled": "已禁用",
|
||||
"enabled": "已启用"
|
||||
"enabled": "已启用",
|
||||
"edit_user": "编辑用户",
|
||||
"del_user": "是否删除用户: {msg}?",
|
||||
"please_first": "请先",
|
||||
"download_the_template": "下载模板",
|
||||
"required_and_upload": ",按要求填写后上传",
|
||||
"file": "文件",
|
||||
"upload_file": "上传文件",
|
||||
"xls_format_files": "仅支持xlsx、xls格式的文件",
|
||||
"import": "导入",
|
||||
"change_file": "更换文件",
|
||||
"data_import_completed": "数据导入完成",
|
||||
"imported_100_data": "成功导入数据 {msg} 条",
|
||||
"return_to_view": "返回查看",
|
||||
"continue_importing": "继续导入",
|
||||
"import_20_can": "成功导入数据 {msg} 条,导入失败 {loss} 条,可 ",
|
||||
"download_error_report": "下载错误报告",
|
||||
"modify_and_re_import": ",修改后重新导入",
|
||||
"data_import_failed": "数据导入失败",
|
||||
"failed_100_can": "导入失败 {msg} 条,可",
|
||||
"change_password": "修改密码",
|
||||
"new_password": "新密码",
|
||||
"confirm_password": "确认密码",
|
||||
"old_password": "旧密码"
|
||||
},
|
||||
"workspace": {
|
||||
"add_workspace": "添加工作空间",
|
||||
"administrator": "管理员",
|
||||
"ordinary_member": "普通成员",
|
||||
"people": "人",
|
||||
"name_username_email": "搜索姓名、用户名、邮箱",
|
||||
"add_member": "添加成员",
|
||||
"member_type": "成员类型",
|
||||
"workspace_name": "工作空间名称",
|
||||
"no_user": "暂无用户",
|
||||
"remove": "移除",
|
||||
"selected_2_members": "是否移除选中的 {msg} 个成员?",
|
||||
"removed_successfully": "移除成功",
|
||||
"workspace:_de_workspace": "是否删除工作空间:{msg}?",
|
||||
"member:_feng_yibudao": "是否移除成员:{msg}?",
|
||||
"select_member": "选择成员",
|
||||
"selected:_2_people": "已选:{msg} 人",
|
||||
"clear": "清空",
|
||||
"historical_dialogue": "暂无历史对话"
|
||||
},
|
||||
"chat": {
|
||||
"chart_type": {
|
||||
|
||||
@@ -11,6 +11,7 @@ import DashboardPreview from '@//views/dashboard/preview/SQPreviewSingle.vue'
|
||||
import Dashboard from '@/views/dashboard/index.vue'
|
||||
import Model from '@/views/system/model/Model.vue'
|
||||
import User from '@/views/system/user/User.vue'
|
||||
import Workspace from '@/views/system/workspace/index.vue'
|
||||
import { watchRouter } from './watch'
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
@@ -120,18 +121,24 @@ const router = createRouter({
|
||||
component: LayoutDsl,
|
||||
redirect: '/system/model',
|
||||
children: [
|
||||
{
|
||||
path: 'model',
|
||||
name: 'model',
|
||||
component: Model,
|
||||
meta: { title: 'AI Model Configuration', icon: 'icon_ai' },
|
||||
},
|
||||
{
|
||||
path: 'user',
|
||||
name: 'user',
|
||||
component: User,
|
||||
meta: { title: '用户管理', icon: 'user' },
|
||||
},
|
||||
{
|
||||
path: 'workspace',
|
||||
name: 'workspace',
|
||||
component: Workspace,
|
||||
meta: { title: '工作空间', icon: 'workspace' },
|
||||
},
|
||||
{
|
||||
path: 'model',
|
||||
name: 'model',
|
||||
component: Model,
|
||||
meta: { title: 'AI Model Configuration', icon: 'icon_ai' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -154,3 +154,25 @@ body {
|
||||
.ed-input__prefix-inner .ed-icon {
|
||||
color: #646a73;
|
||||
}
|
||||
|
||||
.confirm-no_icon {
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
.tip {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.form-content_error {
|
||||
.ed-form-item--default {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.is-error {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ed-dialog {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { toType, createTypes, type VueTypesInterface, type VueTypeValidableDef } from 'vue-types'
|
||||
import type { CSSProperties } from 'vue'
|
||||
|
||||
// 自定义扩展vue-types
|
||||
type PropTypes = VueTypesInterface & {
|
||||
readonly style: VueTypeValidableDef<CSSProperties>
|
||||
}
|
||||
|
||||
const ProjectTypes = createTypes({
|
||||
func: undefined,
|
||||
bool: undefined,
|
||||
string: undefined,
|
||||
number: undefined,
|
||||
object: undefined,
|
||||
integer: undefined,
|
||||
}) as PropTypes
|
||||
|
||||
class propTypes extends ProjectTypes {
|
||||
style() {
|
||||
return toType('style', {
|
||||
type: [String, Object],
|
||||
default: undefined,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export { propTypes }
|
||||
@@ -12,8 +12,7 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
name: string
|
||||
type: string
|
||||
type_name: string
|
||||
rate?: string
|
||||
typeName: string
|
||||
description?: string
|
||||
id?: string
|
||||
}>(),
|
||||
@@ -22,7 +21,7 @@ const props = withDefaults(
|
||||
type: '-',
|
||||
description: '-',
|
||||
id: '-',
|
||||
type_name: '-',
|
||||
typeName: '-',
|
||||
}
|
||||
)
|
||||
|
||||
@@ -58,7 +57,7 @@ const onClickOutside = () => {
|
||||
<img :src="icon" width="32px" height="32px" />
|
||||
<div class="info">
|
||||
<div class="name">{{ name }}</div>
|
||||
<div class="type">{{ type_name }}</div>
|
||||
<div class="type">{{ typeName }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="type-value">
|
||||
|
||||
@@ -247,7 +247,7 @@ const dataTableDetail = (ele: any) => {
|
||||
:key="ele.id"
|
||||
:name="ele.name"
|
||||
:type="ele.type"
|
||||
:type_name="ele.type_name"
|
||||
:type-name="ele.type_name"
|
||||
:description="ele.description"
|
||||
@question="handleQuestion"
|
||||
@edit="handleEditDatasource(ele)"
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button secondary>
|
||||
<el-button secondary @click="drawerMainOpen">
|
||||
<template #icon>
|
||||
<iconFilter></iconFilter>
|
||||
</template>
|
||||
{{ $t('user.filter') }}
|
||||
</el-button>
|
||||
<el-button secondary>
|
||||
<el-button secondary @click="handleUserImport">
|
||||
<template #icon>
|
||||
<ccmUpload></ccmUpload>
|
||||
</template>
|
||||
@@ -36,12 +36,17 @@
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sqlbot-table">
|
||||
<div class="sqlbot-table" :class="state.filterTexts.length && 'is-filter'">
|
||||
<filter-text
|
||||
:total="state.pageInfo.total"
|
||||
:filter-texts="state.filterTexts"
|
||||
@clear-filter="clearFilter"
|
||||
/>
|
||||
<el-table
|
||||
ref="multipleTableRef"
|
||||
@selection-change="handleSelectionChange"
|
||||
:data="state.tableData"
|
||||
style="width: 100%"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="name" :label="$t('user.name')" width="280" />
|
||||
@@ -89,7 +94,7 @@
|
||||
:content="$t('datasource.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="editHandler(scope.row.id)">
|
||||
<el-icon class="action-btn" size="16" @click="handleEditPassword(scope.row.id)">
|
||||
<IconLock></IconLock>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
@@ -100,7 +105,7 @@
|
||||
:content="$t('dashboard.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="deleteHandler(scope.row.id)">
|
||||
<el-icon class="action-btn" size="16" @click="deleteHandler(scope.row)">
|
||||
<IconOpeDelete></IconOpeDelete>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
@@ -123,7 +128,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-select">
|
||||
<div v-if="multipleSelectionAll.length" class="bottom-select">
|
||||
<el-checkbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@@ -132,24 +137,24 @@
|
||||
{{ $t('datasource.select_all') }}
|
||||
</el-checkbox>
|
||||
|
||||
<button class="danger-button">{{ $t('dashboard.delete') }}</button>
|
||||
<button class="danger-button" @click="deleteBatchUser">{{ $t('dashboard.delete') }}</button>
|
||||
|
||||
<span class="selected">{{
|
||||
$t('user.selected_2_items', { msg: multipleSelectionAll.length })
|
||||
}}</span>
|
||||
|
||||
<el-button text>
|
||||
<el-button text @click="cancelDelete">
|
||||
{{ $t('common.cancel') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog
|
||||
<el-drawer
|
||||
v-model="dialogFormVisible"
|
||||
title="Add Terminology"
|
||||
:title="dialogTitle"
|
||||
destroy-on-close
|
||||
width="500"
|
||||
class="sqlbot-dialog"
|
||||
size="600px"
|
||||
modle-class="sqlbot-drawer"
|
||||
@close="onFormClose"
|
||||
>
|
||||
<el-form
|
||||
@@ -157,40 +162,119 @@
|
||||
:model="state.form"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
class="sqlbot-form"
|
||||
size="large"
|
||||
:rules="rules"
|
||||
class="form-content_error"
|
||||
>
|
||||
<el-form-item label="Account">
|
||||
<el-input v-model="state.form.term" autocomplete="off" />
|
||||
<el-form-item prop="name" :label="t('user.name')">
|
||||
<el-input
|
||||
v-model="state.form.name"
|
||||
:placeholder="$t('datasource.please_enter') + $t('common.empty') + $t('user.name')"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Definition">
|
||||
<el-input v-model="state.form.definition" type="textarea" />
|
||||
<el-form-item prop="account" :label="t('user.account')">
|
||||
<el-input
|
||||
v-model="state.form.account"
|
||||
:placeholder="$t('datasource.please_enter') + $t('common.empty') + $t('user.account')"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="Domain">
|
||||
<el-select v-model="state.form.domain" placeholder="Please select a domain">
|
||||
<el-form-item prop="email" :label="$t('user.email')">
|
||||
<el-input
|
||||
v-model="state.form.email"
|
||||
:placeholder="$t('datasource.please_enter') + $t('common.empty') + $t('user.email')"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('user.phone_number')">
|
||||
<el-input
|
||||
v-model="state.form.phoneNumber"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('user.phone_number')
|
||||
"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('user.workspace')">
|
||||
<el-select
|
||||
v-model="state.form.workspace"
|
||||
:placeholder="$t('datasource.Please_select') + $t('common.empty') + $t('user.workspace')"
|
||||
>
|
||||
<el-option label="domain1" value="domain1" />
|
||||
<el-option label="domain2" value="domain2" />
|
||||
<el-option label="domain3" value="domain3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('user.user_status')">
|
||||
<el-switch v-model="state.form.status" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="closeForm">Cancel</el-button>
|
||||
<el-button type="primary" @click="saveHandler"> save </el-button>
|
||||
<el-button @click="closeForm">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveHandler"> {{ $t('model.add') }} </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
<el-dialog
|
||||
v-model="dialogVisiblePassword"
|
||||
:title="$t('user.change_password')"
|
||||
width="500"
|
||||
:before-close="handleClosePassword"
|
||||
>
|
||||
<el-form
|
||||
ref="passwordRef"
|
||||
:model="password"
|
||||
label-width="180px"
|
||||
label-position="top"
|
||||
:rules="passwordRules"
|
||||
class="form-content_error"
|
||||
>
|
||||
<el-form-item prop="new" :label="t('user.new_password')">
|
||||
<el-input
|
||||
v-model="password.new"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('user.new_password')
|
||||
"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="old" :label="t('user.confirm_password')">
|
||||
<el-input
|
||||
v-model="password.old"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('user.confirm_password')
|
||||
"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="handleClosePassword">{{ $t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="handleConfirmPassword">
|
||||
{{ $t('common.save') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<UserImport ref="userImportRef"></UserImport>
|
||||
<drawer-main
|
||||
ref="drawerMainRef"
|
||||
:filter-options="filterOption"
|
||||
@trigger-filter="searchCondition"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, nextTick } from 'vue'
|
||||
/* import IconOpeUpload from '@/assets/svg/operate/ope-upload.svg';
|
||||
import IconOpeDownload from '@/assets/svg/operate/ope-download.svg';
|
||||
import IconOpeAdd from '@/assets/svg/operate/ope-add.svg'; */
|
||||
import UserImport from './UserImport.vue'
|
||||
import SuccessFilled from '@/assets/svg/gou_icon.svg'
|
||||
import CircleCloseFilled from '@/assets/svg/icon_ban_filled.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { convertFilterText, FilterText } from '@/components/filter-text'
|
||||
|
||||
import IconLock from '@/assets/svg/icon-key_outlined.svg'
|
||||
import IconOpeEdit from '@/assets/svg/icon_edit_outlined.svg'
|
||||
@@ -201,18 +285,28 @@ import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import { userApi } from '@/api/auth'
|
||||
import { formatTimestamp } from '@/utils/date'
|
||||
|
||||
const { t } = useI18n()
|
||||
const keyword = ref('')
|
||||
const dialogFormVisible = ref(false)
|
||||
const termFormRef = ref()
|
||||
const checkAll = ref(false)
|
||||
const dialogVisiblePassword = ref(false)
|
||||
const isIndeterminate = ref(true)
|
||||
const state = reactive({
|
||||
const drawerMainRef = ref()
|
||||
const userImportRef = ref()
|
||||
const filterOption = ref<any[]>([])
|
||||
const state = reactive<any>({
|
||||
tableData: [],
|
||||
filterTexts: [],
|
||||
conditions: [],
|
||||
form: {
|
||||
id: '',
|
||||
term: '',
|
||||
definition: '',
|
||||
domain: '',
|
||||
name: '',
|
||||
account: '',
|
||||
workspace: '',
|
||||
email: '',
|
||||
status: '',
|
||||
phoneNumber: '',
|
||||
},
|
||||
pageInfo: {
|
||||
currentPage: 1,
|
||||
@@ -220,10 +314,78 @@ const state = reactive({
|
||||
total: 0,
|
||||
},
|
||||
})
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.name'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
account: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.account'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
email: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.email'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const passwordRules = {
|
||||
new: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.new_password'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
old: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.confirm_password'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const multipleTableRef = ref()
|
||||
const multipleSelectionAll = ref<any[]>([])
|
||||
const dialogTitle = ref('')
|
||||
const passwordRef = ref()
|
||||
const password = ref({
|
||||
new: '',
|
||||
old: '',
|
||||
id: '',
|
||||
})
|
||||
|
||||
const handleClosePassword = () => {
|
||||
dialogVisiblePassword.value = false
|
||||
}
|
||||
|
||||
const handleEditPassword = (id: any) => {
|
||||
password.value.id = id
|
||||
dialogVisiblePassword.value = true
|
||||
}
|
||||
|
||||
const handleUserImport = () => {
|
||||
userImportRef.value.showDialog()
|
||||
}
|
||||
|
||||
const handleConfirmPassword = () => {
|
||||
passwordRef.value.validate((val: any) => {
|
||||
if (val) {
|
||||
console.log(val)
|
||||
}
|
||||
})
|
||||
dialogVisiblePassword.value = false
|
||||
}
|
||||
const handleSelectionChange = (val: any[]) => {
|
||||
const ids = state.tableData.map((ele: any) => ele.id)
|
||||
multipleSelectionAll.value = [
|
||||
@@ -236,8 +398,11 @@ const handleSelectionChange = (val: any[]) => {
|
||||
const handleCheckAllChange = (val: any) => {
|
||||
isIndeterminate.value = false
|
||||
handleSelectionChange(val ? state.tableData : [])
|
||||
val && handleToggleRowSelection()
|
||||
!val && multipleTableRef.value.clearSelection()
|
||||
if (val) {
|
||||
handleToggleRowSelection()
|
||||
} else {
|
||||
multipleTableRef.value.clearSelection()
|
||||
}
|
||||
}
|
||||
|
||||
const handleToggleRowSelection = (check: boolean = true) => {
|
||||
@@ -255,20 +420,110 @@ const handleToggleRowSelection = (check: boolean = true) => {
|
||||
const handleSearch = (e: any) => {
|
||||
console.log('search', e)
|
||||
}
|
||||
const fillFilterText = () => {
|
||||
const textArray = state.conditions?.length
|
||||
? convertFilterText(state.conditions, filterOption.value)
|
||||
: []
|
||||
state.filterTexts = [...textArray]
|
||||
Object.assign(state.filterTexts, textArray)
|
||||
}
|
||||
const clearFilter = (params?: number) => {
|
||||
let index = params ? params : 0
|
||||
if (isNaN(index)) {
|
||||
state.filterTexts = []
|
||||
} else {
|
||||
state.filterTexts.splice(index, 1)
|
||||
}
|
||||
drawerMainRef.value.clearFilter(index)
|
||||
}
|
||||
const searchCondition = (conditions: any) => {
|
||||
state.conditions = conditions
|
||||
fillFilterText()
|
||||
console.log(conditions)
|
||||
drawerMainClose()
|
||||
}
|
||||
const drawerMainOpen = async () => {
|
||||
filterOption.value = [
|
||||
{
|
||||
type: 'enum',
|
||||
option: [
|
||||
{ id: true, name: t('user.enable') },
|
||||
{ id: false, name: t('user.disable') },
|
||||
],
|
||||
field: 'statusList',
|
||||
title: t('user.user_status'),
|
||||
operate: 'in',
|
||||
},
|
||||
{
|
||||
type: 'enum',
|
||||
option: [
|
||||
{ id: '0', name: t('user.local_creation') },
|
||||
{ id: 1, name: 'LDAP' },
|
||||
{ id: 2, name: 'OIDC' },
|
||||
{ id: 3, name: 'CAS' },
|
||||
{ id: 9, name: 'OAuth2' },
|
||||
{ id: 4, name: t('user.feishu') },
|
||||
{ id: 5, name: t('user.dingtalk') },
|
||||
{ id: 6, name: t('user.wechat_for_business') },
|
||||
],
|
||||
field: 'originList',
|
||||
title: t('user.user_source'),
|
||||
operate: 'in',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
option: [{ id: '0', name: 'LOCAL' }],
|
||||
field: 'workspaceList',
|
||||
title: t('user.workspace'),
|
||||
operate: 'in',
|
||||
property: { placeholder: t('common.empty') + t('user.workspace') },
|
||||
},
|
||||
]
|
||||
|
||||
drawerMainRef.value.init()
|
||||
}
|
||||
const drawerMainClose = () => {
|
||||
drawerMainRef.value.close()
|
||||
}
|
||||
const editHandler = (id: any) => {
|
||||
console.log('editHandler', id)
|
||||
/* userApi.query(id).then((res: any) => {
|
||||
console.log('term detail', res)
|
||||
state.form = res
|
||||
dialogFormVisible.value = true
|
||||
}) */
|
||||
dialogFormVisible.value = true
|
||||
dialogTitle.value = id ? t('user.edit_user') : t('user.add_users')
|
||||
|
||||
// userApi.query(id).then((res: any) => {
|
||||
// console.log('term detail', res)
|
||||
// state.form = res
|
||||
// dialogFormVisible.value = true
|
||||
// })
|
||||
}
|
||||
const deleteHandler = (id: any) => {
|
||||
console.log('deleteHandler', id)
|
||||
ElMessageBox.confirm('Are you sure to delete?', 'Warning', {
|
||||
confirmButtonText: 'OK',
|
||||
cancelButtonText: 'Cancel',
|
||||
type: 'warning',
|
||||
|
||||
const cancelDelete = () => {
|
||||
multipleSelectionAll.value = []
|
||||
checkAll.value = false
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
const deleteBatchUser = () => {
|
||||
ElMessageBox.confirm(t('user.selected_2_users', { msg: multipleSelectionAll.value.length }), {
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
callback: (val: string) => {
|
||||
console.log(val)
|
||||
},
|
||||
})
|
||||
}
|
||||
const deleteHandler = (row: any) => {
|
||||
ElMessageBox.confirm(t('user.del_user', { msg: row.name }), {
|
||||
confirmButtonType: 'danger',
|
||||
confirmButtonText: t('dashboard.delete'),
|
||||
cancelButtonText: t('common.cancel'),
|
||||
customClass: 'confirm-no_icon',
|
||||
autofocus: false,
|
||||
callback: (val: string) => {
|
||||
console.log(val)
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
/* userApi.delete(id).then(() => {
|
||||
@@ -409,6 +664,10 @@ onMounted(() => {
|
||||
border-radius: 6px;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 200px);
|
||||
|
||||
&.is-filter {
|
||||
max-height: calc(100vh - 256px);
|
||||
}
|
||||
:deep(.ed-table) {
|
||||
--el-table-header-bg-color: #f5f7fa;
|
||||
--el-table-border-color: #ebeef5;
|
||||
@@ -472,29 +731,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-form {
|
||||
width: 100%;
|
||||
.ed-form-item {
|
||||
margin-bottom: 16px;
|
||||
text-align: left;
|
||||
label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sqlbot-dialog {
|
||||
width: 500px;
|
||||
|
||||
:deep(.ed-dialog__header) {
|
||||
text-align: left !important;
|
||||
font-size: 16px;
|
||||
background-color: #f5f7fa;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
color: #606266;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-status-container {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
<script lang="ts" setup>
|
||||
import icon_warning_filled from '@/assets/svg/icon_info_colorful.svg'
|
||||
import icon_upload_outlined from '@/assets/svg/icon_upload_outlined.svg'
|
||||
import icon_fileExcel_colorful from '@/assets/datasource/icon_excel.png'
|
||||
import IconOpeDelete from '@/assets/svg/icon_delete.svg'
|
||||
import { ref, reactive, h } from 'vue'
|
||||
import {
|
||||
ElMessage,
|
||||
ElMessageBox,
|
||||
ElLoading,
|
||||
type UploadRequestOptions,
|
||||
type UploadProps,
|
||||
ElButton,
|
||||
} from 'element-plus-secondary'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { userImportApi } from '@/api/user'
|
||||
const { t } = useI18n()
|
||||
const defaultTip = t('user.xls_format_files')
|
||||
const loadingInstance = ref<any>(null)
|
||||
const dialogShow = ref(false)
|
||||
const form = ref({})
|
||||
const file = ref<any>(null)
|
||||
const fileName = ref('')
|
||||
const errorFileKey = ref(null)
|
||||
const emits = defineEmits(['refresh-grid'])
|
||||
const state = reactive({
|
||||
errList: [],
|
||||
filesTmp: [],
|
||||
})
|
||||
|
||||
const showLoading = () => {
|
||||
loadingInstance.value = ElLoading.service({ target: '.user-import-class' })
|
||||
}
|
||||
const closeLoading = () => {
|
||||
loadingInstance.value?.close()
|
||||
}
|
||||
const showDialog = () => {
|
||||
file.value = null
|
||||
fileName.value = ''
|
||||
errorFileKey.value = null
|
||||
dialogShow.value = true
|
||||
}
|
||||
const closeDialog = () => {
|
||||
dialogShow.value = false
|
||||
}
|
||||
const handleExceed: UploadProps['onExceed'] = () => {
|
||||
ElMessage.warning(t('userimport.exceedMsg'))
|
||||
}
|
||||
const handleError = () => {
|
||||
ElMessage.warning(t('user.contact_the_administrator'))
|
||||
}
|
||||
const uploadValidate = (file: any) => {
|
||||
const suffix = file.name.substring(file.name.lastIndexOf('.') + 1)
|
||||
if (suffix !== 'xlsx' && suffix !== 'xls') {
|
||||
ElMessage.warning(t('userimport.suffixMsg'))
|
||||
return false
|
||||
}
|
||||
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
ElMessage.warning(t('userimport.limitMsg'))
|
||||
return false
|
||||
}
|
||||
state.errList = []
|
||||
return true
|
||||
}
|
||||
const fileSize = ref('-')
|
||||
const setFile = (options: UploadRequestOptions) => {
|
||||
file.value = options.file
|
||||
fileName.value = options.file.name
|
||||
fileSize.value = setSize(options.file.size)
|
||||
}
|
||||
|
||||
const buildFormData = (file: any, files: any, param: any) => {
|
||||
const formData = new FormData()
|
||||
if (file) {
|
||||
formData.append('file', file)
|
||||
}
|
||||
if (files) {
|
||||
files.forEach((f: any) => {
|
||||
formData.append('files', f)
|
||||
})
|
||||
}
|
||||
if (param) {
|
||||
formData.append('request', new Blob([JSON.stringify(param)], { type: 'application/json' }))
|
||||
}
|
||||
|
||||
return formData
|
||||
}
|
||||
const downExcel = () => {
|
||||
showLoading()
|
||||
userImportApi
|
||||
.downExcelTemplateApi()
|
||||
.then((res) => {
|
||||
const blobData = res.data
|
||||
const blob = new Blob([blobData], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = 'user.xlsx' // 下载的文件名
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
closeLoading()
|
||||
})
|
||||
.catch(() => {
|
||||
closeLoading()
|
||||
})
|
||||
}
|
||||
|
||||
const setSize = (size: any) => {
|
||||
let data = ''
|
||||
const _size = Number.parseFloat(size)
|
||||
if (_size < 1 * 1024) {
|
||||
//如果小于0.1KB转化成B
|
||||
data = _size.toFixed(2) + 'B'
|
||||
} else if (_size < 1 * 1024 * 1024) {
|
||||
//如果小于0.1MB转化成KB
|
||||
data = (_size / 1024).toFixed(2) + 'KB'
|
||||
} else if (_size < 1 * 1024 * 1024 * 1024) {
|
||||
//如果小于0.1GB转化成MB
|
||||
data = (_size / (1024 * 1024)).toFixed(2) + 'MB'
|
||||
} else {
|
||||
//其他转化成GB
|
||||
data = (_size / (1024 * 1024 * 1024)).toFixed(2) + 'GB'
|
||||
}
|
||||
const size_str = data + ''
|
||||
const len = size_str.indexOf('.')
|
||||
const dec = size_str.substr(len + 1, 2)
|
||||
if (dec == '00') {
|
||||
//当小数点后为00时 去掉小数部分
|
||||
return size_str.substring(0, len) + size_str.substr(len + 3, 2)
|
||||
}
|
||||
return size_str
|
||||
}
|
||||
|
||||
const toGrid = () => {
|
||||
file.value = null
|
||||
fileName.value = ''
|
||||
dialogShow.value = false
|
||||
emits('refresh-grid')
|
||||
}
|
||||
|
||||
const clearFile = () => {
|
||||
file.value = null
|
||||
fileName.value = ''
|
||||
}
|
||||
const sure = () => {
|
||||
const param = buildFormData(file.value, null, null)
|
||||
showLoading()
|
||||
userImportApi
|
||||
.importUserApi(param)
|
||||
.then((res) => {
|
||||
closeLoading()
|
||||
const data = res.data
|
||||
errorFileKey.value = data.dataKey
|
||||
closeDialog()
|
||||
showTips(data.successCount, data.errorCount)
|
||||
})
|
||||
.catch(() => {
|
||||
closeLoading()
|
||||
})
|
||||
}
|
||||
const downErrorExcel = () => {
|
||||
if (errorFileKey.value) {
|
||||
showLoading()
|
||||
userImportApi
|
||||
.downErrorRecordApi(errorFileKey.value)
|
||||
.then((res) => {
|
||||
const blobData = res.data
|
||||
const blob = new Blob([blobData], { type: 'application/vnd.ms-excel' })
|
||||
const link = document.createElement('a')
|
||||
link.style.display = 'none'
|
||||
link.href = URL.createObjectURL(blob)
|
||||
link.download = 'error.xlsx' // 下载的文件名
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
closeLoading()
|
||||
// closeDialog()
|
||||
})
|
||||
.catch(() => {
|
||||
closeLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
const showTips = (successCount: any, errorCount: any) => {
|
||||
let title = !errorCount
|
||||
? t('user.data_import_successful')
|
||||
: successCount
|
||||
? t('user.data_import_failed')
|
||||
: t('user.data_import_failed_de')
|
||||
const childrenDomList = [
|
||||
h('strong', null, title),
|
||||
h('br', {}, {}),
|
||||
h('span', null, t('user.imported_1_data', { msg: successCount })),
|
||||
]
|
||||
if (errorCount) {
|
||||
const errorCountDom = h('span', null, t('user.import_1_data', { msg: errorCount }))
|
||||
const errorDom = h('div', { class: 'error-record-tip flex-align-center' }, [
|
||||
h('span', null, t('user.can')),
|
||||
h(
|
||||
ElButton,
|
||||
{
|
||||
onClick: downErrorExcel,
|
||||
type: 'primary',
|
||||
text: true,
|
||||
class: 'down-button',
|
||||
},
|
||||
t('user.download_error_report')
|
||||
),
|
||||
h('span', null, t('user.modify_and_re_import')),
|
||||
])
|
||||
|
||||
childrenDomList.push(errorCountDom)
|
||||
childrenDomList.push(errorDom)
|
||||
}
|
||||
ElMessageBox.confirm('', {
|
||||
confirmButtonType: 'primary',
|
||||
type: !errorCount ? 'success' : successCount ? 'warning' : 'error',
|
||||
autofocus: false,
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: h('div', { class: 'import-tip-box' }, childrenDomList),
|
||||
showClose: false,
|
||||
cancelButtonText: t('user.return_to_view'),
|
||||
confirmButtonText: t('user.continue_importing'),
|
||||
})
|
||||
.then(() => {
|
||||
clearErrorRecord()
|
||||
showDialog()
|
||||
emits('refresh-grid')
|
||||
})
|
||||
.catch(() => {
|
||||
clearErrorRecord()
|
||||
toGrid()
|
||||
})
|
||||
}
|
||||
const clearErrorRecord = () => {
|
||||
if (errorFileKey.value) {
|
||||
userImportApi.clearErrorApi(errorFileKey.value)
|
||||
}
|
||||
}
|
||||
|
||||
const rules = {
|
||||
file: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('user.file'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
showDialog,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogShow"
|
||||
:title="t('user.batch_import')"
|
||||
width="600px"
|
||||
model-class="user-import-class"
|
||||
@before-close="closeDialog"
|
||||
>
|
||||
<div class="down-template">
|
||||
<span class="icon-span">
|
||||
<el-icon>
|
||||
<Icon name="icon_warning_filled"><icon_warning_filled class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<div class="down-template-content">
|
||||
<span>{{ t('user.please_first') }}</span>
|
||||
<el-button type="primary" text class="down-button" @click="downExcel">{{
|
||||
t('user.download_the_template')
|
||||
}}</el-button>
|
||||
<span>{{ t('user.required_and_upload') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<el-form
|
||||
ref="form"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
class="form-content_error import-form"
|
||||
:model="form"
|
||||
label-width="0px"
|
||||
>
|
||||
<el-form-item prop="file" :label="t('user.file')" style="margin-bottom: 0px">
|
||||
<div v-if="fileName" class="pdf-card">
|
||||
<img :src="icon_fileExcel_colorful" width="40px" height="40px" />
|
||||
<div class="file-name">
|
||||
<div class="name">{{ fileName }}</div>
|
||||
<div class="size">{{ fileName.split('.')[1] }} - {{ fileSize }}</div>
|
||||
</div>
|
||||
<el-icon class="action-btn" size="16" @click="clearFile">
|
||||
<IconOpeDelete></IconOpeDelete>
|
||||
</el-icon>
|
||||
</div>
|
||||
<el-upload
|
||||
v-if="fileName"
|
||||
class="upload-user"
|
||||
action=""
|
||||
accept=".xlsx,.xls"
|
||||
:on-exceed="handleExceed"
|
||||
:before-upload="uploadValidate"
|
||||
:on-error="handleError"
|
||||
:show-file-list="false"
|
||||
:file-list="state.filesTmp"
|
||||
:http-request="setFile"
|
||||
>
|
||||
<el-button text style="line-height: 22px; height: 22px">
|
||||
{{ $t('user.change_file') }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-upload
|
||||
v-else
|
||||
class="upload-user"
|
||||
action=""
|
||||
accept=".xlsx,.xls"
|
||||
:on-exceed="handleExceed"
|
||||
:before-upload="uploadValidate"
|
||||
:on-error="handleError"
|
||||
:show-file-list="false"
|
||||
:file-list="state.filesTmp"
|
||||
:http-request="setFile"
|
||||
>
|
||||
<el-button secondary>
|
||||
<el-icon size="16" style="margin-right: 4px">
|
||||
<icon_upload_outlined></icon_upload_outlined>
|
||||
</el-icon>
|
||||
{{ t('user.upload_file') }}</el-button
|
||||
>
|
||||
</el-upload>
|
||||
|
||||
<el-link
|
||||
v-if="!fileName"
|
||||
style="width: 100%; line-height: 22px; height: 22px; justify-content: start"
|
||||
class="font12"
|
||||
type="info"
|
||||
disabled
|
||||
>
|
||||
{{ defaultTip }}
|
||||
</el-link>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="closeDialog">{{ t('common.cancel') }}</el-button>
|
||||
<el-button
|
||||
:type="file && fileName ? 'primary' : 'info'"
|
||||
:disabled="!file || !fileName"
|
||||
@click="sure"
|
||||
>{{ t('user.import') }}</el-button
|
||||
>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
.upload-user {
|
||||
height: 32px;
|
||||
.ed-upload {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
.color-danger {
|
||||
:deep(.el-link--inner) {
|
||||
color: var(--deDanger, #f54a45) !important;
|
||||
}
|
||||
}
|
||||
.font12 {
|
||||
color: #8f959e !important;
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22px;
|
||||
}
|
||||
.down-template {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
background: #d2f1e9;
|
||||
border-radius: 4px;
|
||||
padding-left: 10px;
|
||||
.icon-span {
|
||||
color: var(--ed-color-primary);
|
||||
font-size: 18px;
|
||||
i {
|
||||
top: 3px;
|
||||
}
|
||||
}
|
||||
.down-template-content {
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: 10px;
|
||||
.down-button {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.import-form {
|
||||
margin-top: 16px;
|
||||
|
||||
.pdf-card {
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px 0 12px;
|
||||
border: 1px solid #dee0e3;
|
||||
border-radius: 6px;
|
||||
|
||||
.file-name {
|
||||
margin-left: 8px;
|
||||
.name {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.size {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #8f959e;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: #646a73;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.import-tip-box {
|
||||
strong {
|
||||
font-size: 16px;
|
||||
}
|
||||
span {
|
||||
font-size: 13px;
|
||||
}
|
||||
.error-record-tip {
|
||||
font-size: 13px;
|
||||
flex-flow: wrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,108 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const paramsRef = ref()
|
||||
const paramsForm = reactive({
|
||||
name: '',
|
||||
key: '',
|
||||
val: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('model.display_name'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
key: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('model.parameters'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
val: [
|
||||
{
|
||||
required: true,
|
||||
message: t('datasource.please_enter') + t('common.empty') + t('model.parameter_value'),
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const initForm = (item: any) => {
|
||||
if (item) {
|
||||
Object.assign(paramsForm, { ...item })
|
||||
}
|
||||
paramsRef.value.clearValidate()
|
||||
}
|
||||
|
||||
const emits = defineEmits(['submit'])
|
||||
|
||||
const submit = () => {
|
||||
paramsRef.value.validate((res: any) => {
|
||||
if (res) {
|
||||
emits('submit', paramsForm)
|
||||
}
|
||||
})
|
||||
}
|
||||
defineExpose({
|
||||
initForm,
|
||||
submit,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="params-form">
|
||||
<el-form
|
||||
ref="paramsRef"
|
||||
:rules="rules"
|
||||
label-position="top"
|
||||
:model="paramsForm"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-form-item prop="key" :label="$t('model.parameters')">
|
||||
<el-input
|
||||
v-model="paramsForm.key"
|
||||
:placeholder="$t('datasource.please_enter') + $t('common.empty') + $t('model.parameters')"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" :label="$t('model.display_name')">
|
||||
<el-input
|
||||
v-model="paramsForm.name"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('model.display_name')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item prop="val" :label="$t('model.parameter_value')">
|
||||
<el-input
|
||||
v-model="paramsForm.val"
|
||||
:placeholder="
|
||||
$t('datasource.please_enter') + $t('common.empty') + $t('model.parameter_value')
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.params-form {
|
||||
.ed-form-item--default {
|
||||
margin-bottom: 16px;
|
||||
|
||||
&.is-error {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.ed-input-number {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,444 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { workspaceList, workspaceUserList } from '@/api/workspace'
|
||||
import icon_form_outlined from '@/assets/svg/icon_form_outlined.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
// import EmptyBackground from '@/views/dashboard/common/EmptyBackground.vue'
|
||||
import assigned from '@/assets/svg/icon_assigned_outlined.svg'
|
||||
// import rename from '@/assets/svg/icon_rename_outlined.svg'
|
||||
// import icon_member from '@/assets/svg/icon_member.svg'
|
||||
import SuccessFilled from '@/assets/svg/gou_icon.svg'
|
||||
import CircleCloseFilled from '@/assets/svg/icon_ban_filled.svg'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ParamsForm from './ParamsForm.vue'
|
||||
|
||||
interface Table {
|
||||
name: string
|
||||
host: string
|
||||
port: string
|
||||
username: string
|
||||
password: string
|
||||
database: string
|
||||
extraJdbc: string
|
||||
dbSchema: string
|
||||
filename: string
|
||||
sheets: string
|
||||
mode: string
|
||||
timeout: string
|
||||
configuration: string
|
||||
id: number
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
info: Table
|
||||
}>(),
|
||||
{
|
||||
info: () => ({
|
||||
name: '-',
|
||||
host: '-',
|
||||
port: '-',
|
||||
username: '-',
|
||||
password: '-',
|
||||
database: '-',
|
||||
extraJdbc: '-',
|
||||
dbSchema: '-',
|
||||
filename: '-',
|
||||
sheets: '-',
|
||||
mode: '-',
|
||||
timeout: '-',
|
||||
configuration: '-',
|
||||
id: 0,
|
||||
}),
|
||||
}
|
||||
)
|
||||
const { t } = useI18n()
|
||||
const paramsFormRef = ref()
|
||||
const tableList = ref([] as any[])
|
||||
const keywords = ref('')
|
||||
const tableListWithSearch = computed(() => {
|
||||
if (!keywords.value) return tableList.value
|
||||
return tableList.value.filter((ele) =>
|
||||
ele.name.toLowerCase().includes(keywords.value.toLowerCase())
|
||||
)
|
||||
})
|
||||
const currentTable = ref<any>({})
|
||||
|
||||
const init = () => {
|
||||
workspaceList().then((res) => {
|
||||
tableList.value = res
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
const tableComment = ref('')
|
||||
const fieldDialog = ref<boolean>(false)
|
||||
const tableDialog = ref<boolean>(false)
|
||||
const fieldComment = ref('')
|
||||
const currentField = ref<any>({})
|
||||
const fieldList = ref<any>([])
|
||||
|
||||
const handleSelectTableList = () => {
|
||||
paramsFormRef.value.open(props.info)
|
||||
}
|
||||
|
||||
const clickTable = (table: any) => {
|
||||
currentTable.value = table
|
||||
workspaceUserList({ oid: table.id }, 1, 10).then((res) => {
|
||||
fieldList.value = res
|
||||
// datasourceApi.previewData(props.info.id, buildData()).then((res) => {
|
||||
// previewData.value = res
|
||||
// })
|
||||
})
|
||||
}
|
||||
|
||||
const closeTable = () => {
|
||||
tableDialog.value = false
|
||||
}
|
||||
const editTable = () => {
|
||||
tableComment.value = currentTable.value.custom_comment
|
||||
tableDialog.value = true
|
||||
}
|
||||
const saveTable = () => {
|
||||
currentTable.value.custom_comment = tableComment.value
|
||||
// datasourceApi.saveTable(currentTable.value).then(() => {
|
||||
// closeTable()
|
||||
// ElMessage({
|
||||
// message: t('common.save_success'),
|
||||
// type: 'success',
|
||||
// showClose: true,
|
||||
// })
|
||||
// })
|
||||
}
|
||||
const closeField = () => {
|
||||
fieldDialog.value = false
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
// datasourceApi.tableList(props.info.id).then((res) => {
|
||||
// tableList.value = res
|
||||
// if (!currentTable.value.name) return
|
||||
// const nameArr = tableList.value.map((ele: any) => ele.name)
|
||||
// if (!nameArr.includes(currentTable.value.name)) {
|
||||
// currentTable.value = {}
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
const saveField = () => {
|
||||
currentField.value.custom_comment = fieldComment.value
|
||||
// datasourceApi.saveField(currentField.value).then(() => {
|
||||
// closeField()
|
||||
// ElMessage({
|
||||
// message: t('common.save_success'),
|
||||
// type: 'success',
|
||||
// showClose: true,
|
||||
// })
|
||||
// })
|
||||
}
|
||||
|
||||
const editField = (row: any) => {
|
||||
currentField.value = row
|
||||
fieldComment.value = currentField.value.custom_comment
|
||||
fieldDialog.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="workspace no-padding">
|
||||
<div class="side-list">
|
||||
<div class="select-table_top">
|
||||
{{ $t('user.workspace') }}
|
||||
|
||||
<el-tooltip effect="dark" :content="$t('workspace.add_workspace')" placement="top">
|
||||
<el-icon size="18" @click="handleSelectTableList">
|
||||
<icon_add_outlined></icon_add_outlined>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-input
|
||||
v-model="keywords"
|
||||
clearable
|
||||
style="width: 232px"
|
||||
:placeholder="$t('datasource.search')"
|
||||
>
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<icon_searchOutline_outlined class="svg-icon" />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<div class="list-content">
|
||||
<div
|
||||
v-for="ele in tableListWithSearch"
|
||||
:key="ele.name"
|
||||
class="model"
|
||||
:class="currentTable.name === ele.name && 'isActive'"
|
||||
@click="clickTable(ele)"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<icon_form_outlined></icon_form_outlined>
|
||||
</el-icon>
|
||||
<span class="name">{{ ele.name }}</span>
|
||||
</div>
|
||||
<div v-if="!!keywords && !tableListWithSearch.length" class="no-result">
|
||||
{{ $t('workspace.historical_dialogue') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentTable.name" class="info-table">
|
||||
<div class="table-name">
|
||||
<div class="name">{{ currentTable.name }}</div>
|
||||
<div class="notes">
|
||||
{{ $t('about.remark') }}:
|
||||
{{ currentTable.custom_comment || '-' }}
|
||||
<el-icon style="margin-left: 8px; cursor: pointer" size="16" @click="editTable">
|
||||
<edit></edit>
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-content">
|
||||
<div class="preview-or-schema">
|
||||
<el-table :data="fieldList" style="width: 100%">
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column prop="name" :label="$t('user.name')" width="280" />
|
||||
<el-table-column prop="account" :label="$t('user.account')" width="280" />
|
||||
<el-table-column prop="status" :label="$t('user.user_status')" width="180">
|
||||
<template #default="scope">
|
||||
<div
|
||||
class="user-status-container"
|
||||
:class="[scope.row.status ? 'active' : 'disabled']"
|
||||
>
|
||||
<el-icon size="16">
|
||||
<SuccessFilled v-if="scope.row.status" />
|
||||
<CircleCloseFilled v-else />
|
||||
</el-icon>
|
||||
<span>{{ $t(`user.${scope.row.status ? 'enabled' : 'disabled'}`) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" :label="$t('user.email')" width="280" />
|
||||
<el-table-column prop="phone" :label="$t('user.phone_number')" width="280" />
|
||||
<el-table-column prop="user_source" :label="$t('user.user_source')" width="280" />
|
||||
<el-table-column :label="t('datasource.custom_notes')">
|
||||
<template #default="scope">
|
||||
<div class="field-comment">
|
||||
<span>{{ scope.row.custom_comment }}</span>
|
||||
<el-tooltip
|
||||
:offset="14"
|
||||
effect="dark"
|
||||
:content="$t('datasource.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="action-btn" size="16" @click="editField(scope.row)">
|
||||
<assigned></assigned>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="tableDialog"
|
||||
:title="t('datasource.table_notes')"
|
||||
width="600"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
@closed="closeTable"
|
||||
>
|
||||
<el-input
|
||||
v-model="tableComment"
|
||||
:placeholder="$t('datasource.please_enter')"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeTable">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveTable">{{ t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="fieldDialog"
|
||||
:title="t('datasource.field_notes')"
|
||||
width="600"
|
||||
:destroy-on-close="true"
|
||||
:close-on-click-modal="false"
|
||||
@closed="closeField"
|
||||
>
|
||||
<el-input
|
||||
v-model="fieldComment"
|
||||
:placeholder="$t('datasource.please_enter')"
|
||||
:rows="3"
|
||||
type="textarea"
|
||||
/>
|
||||
<div style="display: flex; justify-content: flex-end; margin-top: 20px">
|
||||
<el-button @click="closeField">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="saveField">{{ t('common.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
<ParamsForm ref="paramsFormRef" @refresh="refresh"></ParamsForm>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workspace {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
.side-list {
|
||||
width: 280px;
|
||||
padding: 8px 16px;
|
||||
height: 100%;
|
||||
border-right: 1px solid #1f232926;
|
||||
.select-table_top {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
|
||||
.ed-icon {
|
||||
cursor: pointer;
|
||||
color: var(--ed-color-primary);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
background: #1cba901a;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ed-input {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
height: calc(100% - 100px);
|
||||
|
||||
.no-result {
|
||||
margin-top: 72px;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
color: #646a73;
|
||||
}
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 8px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
.name {
|
||||
margin-left: 8px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
&:hover {
|
||||
background: #1f23291a;
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
background: #1cba901a;
|
||||
color: var(--ed-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.info-table {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: calc(100% - 280px);
|
||||
.table-name {
|
||||
height: 80px;
|
||||
padding: 16px 0 0 24px;
|
||||
border-bottom: 1px solid #1f232926;
|
||||
|
||||
.name {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.notes {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.table-content {
|
||||
padding: 16px 24px;
|
||||
|
||||
.preview-or-schema {
|
||||
margin-top: 16px;
|
||||
|
||||
.field-comment {
|
||||
height: 24px;
|
||||
}
|
||||
.ed-icon {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: #1f23291a;
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::after {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-num {
|
||||
margin: 12px 0;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #646a73;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user