feat: Custom sorting function style
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled
Typos Check / Spell Check with Typos (push) Has been cancelled

This commit is contained in:
wangdan-fit2cloud 2025-12-23 19:08:10 +08:00
parent d8d15c8902
commit f7b3cc9ae0
14 changed files with 183 additions and 158 deletions

View File

@ -1,34 +1,31 @@
import { t } from '@/locales'
export const SORT_TYPES = {
CREATE_TIME_ASC: 'createTime-asc',
CREATE_TIME_DESC: 'createTime-desc',
NAME_ASC: 'name-asc',
NAME_DESC: 'name-desc',
CUSTOM: 'custom'
CUSTOM: 'custom',
} as const
export type SortType = typeof SORT_TYPES[keyof typeof SORT_TYPES]
export type SortType = (typeof SORT_TYPES)[keyof typeof SORT_TYPES]
export const SORT_MENU_CONFIG = [
{
title: 'time',
title: 'time',
items: [
{ label: t('components.folder.ascTime', '按创建时间升序'), value: SORT_TYPES.CREATE_TIME_ASC},
{ label: t('components.folder.descTime', '按创建时间降序'), value: SORT_TYPES.CREATE_TIME_DESC },
]
{ label: t('components.folder.ascTime'), value: SORT_TYPES.CREATE_TIME_ASC },
{ label: t('components.folder.descTime'), value: SORT_TYPES.CREATE_TIME_DESC },
],
},
{
title: 'name',
items: [
{ label: t('components.folder.ascName', '按名称升序'), value: SORT_TYPES.NAME_ASC },
{ label: t('components.folder.descName', '按名称降序'), value: SORT_TYPES.NAME_DESC },
]
{ label: t('components.folder.ascName'), value: SORT_TYPES.NAME_ASC },
{ label: t('components.folder.descName'), value: SORT_TYPES.NAME_DESC },
],
},
{
items: [
{ label: t('components.folder.custom', '按用户拖拽排序'), value: SORT_TYPES.CUSTOM },
]
}
]
items: [{ label: t('components.folder.sortDrop'), value: SORT_TYPES.CUSTOM }],
},
]

View File

@ -1,27 +1,29 @@
<template>
<div class="folder-tree">
<div class="flex ml-4 p-8 pb-0 items-start">
<div class="flex ml-4 p-8 pb-0">
<el-input
v-model="filterText"
:placeholder="$t('common.search')"
prefix-icon="Search"
clearable
class="flex-[5]"
/>
<el-dropdown trigger="click" :teleported="false" @command="switchSortMethod">
<el-button class="flex-1 ml-4">
<el-button class="ml-4">
<el-icon><Operation /></el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-menu class="w-180">
<template v-for="(group, index) in SORT_MENU_CONFIG" :key="index">
<el-dropdown-item
v-for="obj in group.items"
:key="obj.value"
:command="obj.value"
class="mr-2"
:class="`${currentSort === obj.value ? 'active' : ''} flex-between`"
>
{{ obj.label }}
<span>
{{ obj.label }}
</span>
<el-icon v-if="currentSort === obj.value" class="ml-4">
<Check />
</el-icon>
@ -272,7 +274,7 @@ function addOrderToTree(nodes: any, parentId: string): Node[] {
return nodes
}
let positions = getPositions(parentId)
const positions = getPositions(parentId)
let needSave = false
nodes.forEach((node: any) => {

View File

@ -61,9 +61,6 @@ const noMore = computed(
props.size > 0 && props.size === props.total && props.total > props.page_size && !props.loading,
)
const disabledScroll = computed(() => props.size > 0 && (props.loading || noMore.value))
console.log(props.size)
console.log(props.total)
console.log(props.page_size)
function loadData() {
if (props.total > props.page_size) {
current.value += 1

View File

@ -109,7 +109,7 @@
</div>
<template #dropdown>
<el-dropdown-menu style="width: 180px">
<el-dropdown-menu class="w-180">
<el-dropdown-item
v-for="(lang, index) in langList"
:key="index"

View File

@ -8,7 +8,7 @@
<el-col :xs="24" :sm="24" :md="14" :lg="14" :xl="14" class="right-container flex-center">
<el-dropdown trigger="click" type="primary" class="lang" v-if="lang">
<template #dropdown>
<el-dropdown-menu style="width: 180px">
<el-dropdown-menu class="w-180">
<el-dropdown-item
v-for="(lang, index) in langList"
:key="index"

View File

@ -17,5 +17,10 @@ export default {
folderNamePlaceholder: 'Please enter a name',
requiredMessage: 'Please select a folder',
deleteConfirmMessage: 'Folders with resources will be deleted, please be cautious.',
ascTime: 'Sort by creation time ascending',
descTime: 'Sort by creation time descending',
ascName: 'Sort by name ascending',
descName: 'Sort by name descending',
custom: 'Sort by user drag-and-drop',
},
}

View File

@ -17,5 +17,10 @@ export default {
folderNamePlaceholder: '请输入名称',
requiredMessage: '请选择文件夹',
deleteConfirmMessage: '文件夹下的资源会被删除,请谨慎操作。',
ascTime: '按创建时间升序',
descTime: '按创建时间降序',
ascName: '按名称升序',
descName: '按名称降序',
sortDrop: '按用户拖拽排序',
},
}

View File

@ -17,5 +17,10 @@ export default {
folderNamePlaceholder: '請輸入名稱',
requiredMessage: '請選擇文件夾',
deleteConfirmMessage: '文件夹下的資源會被刪除,請謹慎操作。',
ascTime: '按創建時間升序',
descTime: '按創建時間降序',
ascName: '按名稱升序',
descName: '按名稱降序',
sortDrop: '按用戶拖拽排序',
},
}

View File

@ -125,6 +125,9 @@ h5 {
.w-120 {
width: 120px;
}
.w-180 {
width: 180px;
}
.w-240 {
width: 240px;
}

View File

@ -150,9 +150,9 @@
<div class="mb-16">
<el-select
v-model="history_day"
class="mr-12"
class="mr-12 w-180"
@change="changeDayHandle"
style="width: 180px"
>
<el-option
v-for="item in dayOptions"

View File

@ -174,7 +174,7 @@
style="height: 120px"
@submitDialog="submitNoReferencesPromptDialog"
:placeholder="
$t('views.application.form.roleSettings.placeholder', {
$t('views.application.form.prompt.placeholder', {
data: '{data}',
question: '{question}',
})
@ -288,7 +288,7 @@
style="height: 150px"
@submitDialog="submitPromptDialog"
:placeholder="
$t('views.application.form.roleSettings.placeholder', {
$t('views.application.form.prompt.placeholder', {
data: '{data}',
question: '{question}',
})

View File

@ -25,9 +25,8 @@
</div>
<el-select
v-model="history_day"
class="ml-12"
class="ml-12 w-180"
@change="changeDayHandle"
style="width: 180px"
>
<el-option
v-for="item in dayOptions"

View File

@ -2,8 +2,13 @@
<div class="authentication-setting__main main-calc-height">
<el-scrollbar>
<div class="form-container p-24" v-loading="loading">
<el-form ref="authFormRef" :model="form" label-position="top"
require-asterisk-position="right" @submit.prevent>
<el-form
ref="authFormRef"
:model="form"
label-position="top"
require-asterisk-position="right"
@submit.prevent
>
<!-- 登录方式选择框 -->
<el-form-item
:label="$t('views.system.default_login')"
@ -17,8 +22,11 @@
prop="default_value"
style="padding-top: 16px"
>
<el-radio-group v-model="form.default_value" class="radio-group"
style="margin-left: 10px;">
<el-radio-group
v-model="form.default_value"
class="radio-group"
style="margin-left: 10px"
>
<el-radio
v-for="method in loginMethods"
:key="method.value"
@ -41,13 +49,13 @@
]"
prop="max_attempts"
>
<el-row :gutter="16" style="margin-left: 10px;">
<el-row :gutter="16" style="margin-left: 10px">
<el-col :span="24">
<span style="font-size: 13px;">
{{ $t('views.system.loginFailed') }}
</span>
<span style="font-size: 13px">
{{ $t('views.system.loginFailed') }}
</span>
<el-input-number
style="margin-left: 8px;"
style="margin-left: 8px"
v-model="form.max_attempts"
:min="-1"
:max="10"
@ -55,20 +63,20 @@
controls-position="right"
@change="onMaxAttemptsChange"
/>
<span style="margin-left: 8px; font-size: 13px;">
{{ $t('views.system.loginFailedMessage') }}
</span>
<span style="margin-left: 8px; color: #909399; font-size: 12px;">
({{ $t('views.system.display_codeTip') }})
</span>
<span style="margin-left: 8px; font-size: 13px">
{{ $t('views.system.loginFailedMessage') }}
</span>
<span style="margin-left: 8px; color: #909399; font-size: 12px">
({{ $t('views.system.display_codeTip') }})
</span>
</el-col>
<el-col :span="24" style="margin-top: 8px;">
<span style="font-size: 13px;">
{{ $t('views.system.loginFailed') }}
</span>
<el-col :span="24" style="margin-top: 8px">
<span style="font-size: 13px">
{{ $t('views.system.loginFailed') }}
</span>
<el-input-number
style="margin-left: 8px;"
style="margin-left: 8px"
v-model="form.failed_attempts"
:min="-1"
:max="10"
@ -76,42 +84,42 @@
controls-position="right"
@change="onFailedAttemptsChange"
/>
<span style="margin-left: 8px; font-size: 13px;">
{{ $t('views.system.failedTip') }}
</span>
<span style="margin-left: 8px; font-size: 13px">
{{ $t('views.system.failedTip') }}
</span>
<el-input-number
style="margin-left: 8px;"
style="margin-left: 8px"
v-model="form.lock_time"
:min="1"
:step="1"
controls-position="right"
/>
<span style="margin-left: 8px; font-size: 13px;">
{{ $t('views.system.minute') }}
</span>
<span style="margin-left: 8px; font-size: 13px">
{{ $t('views.system.minute') }}
</span>
</el-col>
</el-row>
</el-form-item>
<el-form-item
:label="$t('views.system.third_party_user_default_role')"
:rules="[
{
required: true,
message: $t('views.system.thirdPartyUserDefaultRoleRequired'),
trigger: 'change',
},
]">
<el-row :gutter="16" style="margin-left: 10px;">
{
required: true,
message: $t('views.system.thirdPartyUserDefaultRoleRequired'),
trigger: 'change',
},
]"
>
<el-row :gutter="16" style="margin-left: 10px">
<el-col :span="12">
<div style="display: flex; align-items: center; gap: 8px; min-width: 0;">
<span style="font-size: 13px; white-space: nowrap;">
{{ $t('views.role.member.role') }}
</span>
<div style="display: flex; align-items: center; gap: 8px; min-width: 0">
<span style="font-size: 13px; white-space: nowrap">
{{ $t('views.role.member.role') }}
</span>
<el-select
v-model="form.role_id"
:placeholder="`${$t('common.selectPlaceholder')}${$t('views.role.member.role')}`"
style="flex: 1; min-width: 180px;"
style="flex: 1; min-width: 180px"
@change="handleRoleChange"
>
<el-option
@ -119,20 +127,19 @@
:key="role.id"
:label="role.name"
:value="role.id"
/>
</el-select>
</div>
</el-col>
<el-col :span="12" v-if="user.isEE() && showWorkspaceSelector">
<div style="display: flex; align-items: center; gap: 8px; min-width: 0;">
<span style="font-size: 13px; white-space: nowrap;">
{{ $t('views.role.member.workspace') }}
</span>
<div style="display: flex; align-items: center; gap: 8px; min-width: 0">
<span style="font-size: 13px; white-space: nowrap">
{{ $t('views.role.member.workspace') }}
</span>
<el-select
v-model="form.workspace_id"
:placeholder="`${$t('common.selectPlaceholder')}${$t('views.role.member.workspace')}`"
style="flex: 1; min-width: 180px;"
style="flex: 1; min-width: 180px"
>
<el-option
v-for="workspace in workspaceOptions"
@ -144,21 +151,20 @@
</div>
</el-col>
</el-row>
</el-form-item>
</el-form>
<div style="margin-top:16px;">
<span
v-hasPermission="
new ComplexPermission([RoleConst.ADMIN], [PermissionConst.LOGIN_AUTH_EDIT], [], 'OR')
"
class="mr-12"
>
<!-- 直接调用 submit不传参 -->
<el-button @click="submit" type="primary" :disabled="loading">
{{ $t('common.save') }}
</el-button>
</span>
<div style="margin-top: 16px">
<span
v-hasPermission="
new ComplexPermission([RoleConst.ADMIN], [PermissionConst.LOGIN_AUTH_EDIT], [], 'OR')
"
class="mr-12"
>
<!-- 直接调用 submit不传参 -->
<el-button @click="submit" type="primary" :disabled="loading">
{{ $t('common.save') }}
</el-button>
</span>
</div>
</div>
</el-scrollbar>
@ -166,20 +172,20 @@
</template>
<script setup lang="ts">
import {ref, onMounted, computed} from "vue";
import {ComplexPermission} from "@/utils/permission/type";
import {PermissionConst, RoleConst} from "@/utils/permission/data";
import type {FormInstance} from 'element-plus';
import {t} from "@/locales";
import authApi from "@/api/system-settings/auth-setting.ts";
import {MsgSuccess} from "@/utils/message.ts";
import WorkspaceApi from "@/api/workspace/workspace.ts";
import useStore from "@/stores";
import { ref, onMounted, computed } from 'vue'
import { ComplexPermission } from '@/utils/permission/type'
import { PermissionConst, RoleConst } from '@/utils/permission/data'
import type { FormInstance } from 'element-plus'
import { t } from '@/locales'
import authApi from '@/api/system-settings/auth-setting.ts'
import { MsgSuccess } from '@/utils/message.ts'
import WorkspaceApi from '@/api/workspace/workspace.ts'
import useStore from '@/stores'
const loginMethods = ref<Array<{ label: string; value: string }>>([]);
const loading = ref(false);
const loginMethods = ref<Array<{ label: string; value: string }>>([])
const loading = ref(false)
// null访
const authFormRef = ref<FormInstance | null>(null);
const authFormRef = ref<FormInstance | null>(null)
const form = ref<any>({
default_value: 'LOCAL',
@ -192,35 +198,35 @@ const form = ref<any>({
const normalizeInputValue = (val: number | null): number => {
// 1
let normalizedVal = typeof val === 'number' ? Math.trunc(val) : NaN;
let normalizedVal = typeof val === 'number' ? Math.trunc(val) : NaN
if (!Number.isFinite(normalizedVal)) {
normalizedVal = 1;
normalizedVal = 1
}
if (normalizedVal === 0) {
normalizedVal = 1;
normalizedVal = 1
} else if (normalizedVal < -1) {
normalizedVal = -1;
normalizedVal = -1
}
return normalizedVal;
};
return normalizedVal
}
const onFailedAttemptsChange = (val: number | null) => {
form.value.failed_attempts = normalizeInputValue(val);
};
form.value.failed_attempts = normalizeInputValue(val)
}
const onMaxAttemptsChange = (val: number | null) => {
form.value.max_attempts = normalizeInputValue(val);
};
form.value.max_attempts = normalizeInputValue(val)
}
// 使 authFormRef.value.validate() Promise loading finally
const submit = async () => {
const formRef = authFormRef.value;
if (!formRef) return;
const formRef = authFormRef.value
if (!formRef) return
try {
await formRef.validate();
loading.value = true;
await formRef.validate()
loading.value = true
const params = {
default_value: form.value.default_value,
max_attempts: form.value.max_attempts,
@ -228,85 +234,92 @@ const submit = async () => {
lock_time: form.value.lock_time,
role_id: form.value.role_id,
workspace_id: form.value.workspace_id,
};
await authApi.putLoginSetting(params);
MsgSuccess(t('common.saveSuccess'));
}
await authApi.putLoginSetting(params)
MsgSuccess(t('common.saveSuccess'))
} catch (err) {
//
// console.error(err);
} finally {
loading.value = false;
loading.value = false
}
};
}
const roleOptions = ref<Array<{ id: string; name: string, type?: string }>>([]);
const workspaceOptions = ref<Array<{ id: string; name: string }>>([]);
const {user} = useStore();
const selectedRoleType = ref<string>(''); // workspace
const showWorkspaceSelector = computed(() => selectedRoleType.value !== 'ADMIN');
const roleOptions = ref<Array<{ id: string; name: string; type?: string }>>([])
const workspaceOptions = ref<Array<{ id: string; name: string }>>([])
const { user } = useStore()
const selectedRoleType = ref<string>('') // workspace
const showWorkspaceSelector = computed(() => selectedRoleType.value !== 'ADMIN')
// selectedRoleType
const handleRoleChange = (roleId: string) => {
const selectedRole = roleOptions.value.find(role => role.id === roleId);
selectedRoleType.value = selectedRole?.type || '';
};
const selectedRole = roleOptions.value.find((role) => role.id === roleId)
selectedRoleType.value = selectedRole?.type || ''
}
onMounted(async () => {
loading.value = true;
loading.value = true
try {
const isEE = typeof user?.isEE === 'function' ? user.isEE() : false;
const isEE = typeof user?.isEE === 'function' ? user.isEE() : false
// + EE workspace
const roleP = WorkspaceApi.getWorkspaceRoleList().then(r => r).catch(() => ({data: []}));
const settingP = authApi.getLoginSetting().then(r => r).catch(() => ({data: {}}));
const tasks: Promise<any>[] = [roleP, settingP];
const roleP = WorkspaceApi.getWorkspaceRoleList()
.then((r) => r)
.catch(() => ({ data: [] }))
const settingP = authApi
.getLoginSetting()
.then((r) => r)
.catch(() => ({ data: {} }))
const tasks: Promise<any>[] = [roleP, settingP]
if (isEE) {
tasks.push(WorkspaceApi.getWorkspaceList().then(r => r).catch(() => ({data: []})));
tasks.push(
WorkspaceApi.getWorkspaceList()
.then((r) => r)
.catch(() => ({ data: [] })),
)
}
const results = await Promise.all(tasks);
const roleRes = results[0] ?? {data: []};
const settingRes = results[1] ?? {data: {}};
const workspaceRes = isEE ? results[2] ?? {data: []} : null;
const results = await Promise.all(tasks)
const roleRes = results[0] ?? { data: [] }
const settingRes = results[1] ?? { data: {} }
const workspaceRes = isEE ? (results[2] ?? { data: [] }) : null
//
const rolesData = Array.isArray(roleRes?.data) ? roleRes.data : [];
const rolesData = Array.isArray(roleRes?.data) ? roleRes.data : []
roleOptions.value = rolesData.map((item: any) => ({
id: item.id,
name: item.name,
type: item.type
}));
type: item.type,
}))
// setting访
const data = settingRes?.data ?? {};
const data = settingRes?.data ?? {}
form.value = {
...form.value,
...data,
failed_attempts: data.failed_attempts ?? form.value.failed_attempts ?? 5,
lock_time: data.lock_time ?? form.value.lock_time ?? 10,
role_id: data.role_id ?? form.value.role_id ?? 'USER',
workspace_id: data.workspace_id ?? form.value.workspace_id ?? 'default'
};
loginMethods.value = Array.isArray(data.auth_types) ? data.auth_types : [];
workspace_id: data.workspace_id ?? form.value.workspace_id ?? 'default',
}
loginMethods.value = Array.isArray(data.auth_types) ? data.auth_types : []
// workspace
if (isEE && workspaceRes) {
const wks = Array.isArray(workspaceRes.data) ? workspaceRes.data : [];
workspaceOptions.value = wks.map((item: any) => ({id: item.id, name: item.name}));
const wks = Array.isArray(workspaceRes.data) ? workspaceRes.data : []
workspaceOptions.value = wks.map((item: any) => ({ id: item.id, name: item.name }))
}
// selectedRoleType role_id roleOptions
const initRole = roleOptions.value.find(r => r.id === form.value.role_id);
selectedRoleType.value = initRole?.type || '';
const initRole = roleOptions.value.find((r) => r.id === form.value.role_id)
selectedRoleType.value = initRole?.type || ''
} catch (e) {
// overall error,
// console.error(e);
} finally {
loading.value = false;
loading.value = false
}
});
})
</script>
<style scoped>

View File

@ -7,9 +7,8 @@
<div>
<el-select
v-model="history_day"
class="mr-12"
class="mr-12 w-180"
@change="changeDayHandle"
style="width: 180px"
>
<el-option
v-for="item in dayOptions"