Merge remote-tracking branch 'yudao/dev' into dev

pull/170/head
jason 2025-07-11 21:52:45 +08:00
commit f697dfb266
13 changed files with 76 additions and 93 deletions

View File

@ -22,7 +22,7 @@ const md = new MarkdownIt({
if (lang && hljs.getLanguage(lang)) { if (lang && hljs.getLanguage(lang)) {
try { try {
const copyHtml = `<div id="copy" data-copy='${str}' style="position: absolute; right: 10px; top: 5px; color: #fff;cursor: pointer;">复制</div>`; const copyHtml = `<div id="copy" data-copy='${str}' style="position: absolute; right: 10px; top: 5px; color: #fff;cursor: pointer;">复制</div>`;
return `<pre style="position: relative;">${copyHtml}<code class="hljs">${hljs.highlight(lang, str, true).value}</code></pre>`; return `<pre style="position: relative;">${copyHtml}<code class="hljs">${hljs.highlight(str, { language: lang, ignoreIllegals: true }).value}</code></pre>`;
} catch {} } catch {}
} }
return ``; return ``;

View File

@ -60,39 +60,14 @@ function isIfShow(action: ActionItem): boolean {
/** 处理按钮 actions */ /** 处理按钮 actions */
const getActions = computed(() => { const getActions = computed(() => {
return (props.actions || []) return (props.actions || []).filter((action: ActionItem) => isIfShow(action));
.filter((action: ActionItem) => isIfShow(action))
.map((action: ActionItem) => {
const { popConfirm } = action;
return {
type: action.type || 'link',
...action,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
enable: !!popConfirm,
};
});
}); });
/** 处理下拉菜单 actions */ /** 处理下拉菜单 actions */
const getDropdownList = computed(() => { const getDropdownList = computed(() => {
return (props.dropDownActions || []) return (props.dropDownActions || []).filter((action: ActionItem) =>
.filter((action: ActionItem) => isIfShow(action)) isIfShow(action),
.map((action: ActionItem, index: number) => { );
const { label, popConfirm } = action;
const processedAction = { ...action };
delete processedAction.icon;
return {
...processedAction,
...popConfirm,
onConfirm: popConfirm?.confirm,
onCancel: popConfirm?.cancel,
text: label,
divider:
index < props.dropDownActions.length - 1 ? props.divider : false,
};
});
}); });
/** Space 组件的 size */ /** Space 组件的 size */
@ -103,18 +78,27 @@ const spaceSize = computed(() => {
}); });
/** 获取 PopConfirm 属性 */ /** 获取 PopConfirm 属性 */
function getPopConfirmProps(attrs: PopConfirm) { function getPopConfirmProps(popConfirm: PopConfirm) {
const originAttrs: any = { ...attrs }; if (!popConfirm) return {};
delete originAttrs.icon;
if (attrs.confirm && isFunction(attrs.confirm)) { const attrs: Record<string, any> = {};
originAttrs.onConfirm = attrs.confirm;
delete originAttrs.confirm; //
Object.keys(popConfirm).forEach((key) => {
if (key !== 'confirm' && key !== 'cancel' && key !== 'icon') {
attrs[key] = popConfirm[key as keyof PopConfirm];
} }
if (attrs.cancel && isFunction(attrs.cancel)) { });
originAttrs.onCancel = attrs.cancel;
delete originAttrs.cancel; //
if (popConfirm.confirm && isFunction(popConfirm.confirm)) {
attrs.onConfirm = popConfirm.confirm;
} }
return originAttrs; if (popConfirm.cancel && isFunction(popConfirm.cancel)) {
attrs.onCancel = popConfirm.cancel;
}
return attrs;
} }
/** 获取 Button 属性 */ /** 获取 Button 属性 */
@ -146,6 +130,13 @@ function handleMenuClick(e: any) {
function getActionKey(action: ActionItem, index: number) { function getActionKey(action: ActionItem, index: number) {
return `${action.label || ''}-${action.type || ''}-${index}`; return `${action.label || ''}-${action.type || ''}-${index}`;
} }
/** 处理按钮点击 */
function handleButtonClick(action: ActionItem) {
if (action.onClick && isFunction(action.onClick)) {
action.onClick();
}
}
</script> </script>
<template> <template>
@ -172,7 +163,10 @@ function getActionKey(action: ActionItem, index: number) {
</Tooltip> </Tooltip>
</Popconfirm> </Popconfirm>
<Tooltip v-else v-bind="getTooltipProps(action.tooltip)"> <Tooltip v-else v-bind="getTooltipProps(action.tooltip)">
<Button v-bind="getButtonProps(action)" @click="action.onClick"> <Button
v-bind="getButtonProps(action)"
@click="handleButtonClick(action)"
>
<template v-if="action.icon" #icon> <template v-if="action.icon" #icon>
<IconifyIcon :icon="action.icon" /> <IconifyIcon :icon="action.icon" />
</template> </template>
@ -184,7 +178,7 @@ function getActionKey(action: ActionItem, index: number) {
<Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']"> <Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']">
<slot name="more"> <slot name="more">
<Button :type="getDropdownList[0]?.type"> <Button type="link">
<template #icon> <template #icon>
{{ $t('page.action.more') }} {{ $t('page.action.more') }}
<IconifyIcon icon="lucide:ellipsis-vertical" /> <IconifyIcon icon="lucide:ellipsis-vertical" />
@ -213,7 +207,7 @@ function getActionKey(action: ActionItem, index: number) {
> >
<IconifyIcon v-if="action.icon" :icon="action.icon" /> <IconifyIcon v-if="action.icon" :icon="action.icon" />
<span :class="action.icon ? 'ml-1' : ''"> <span :class="action.icon ? 'ml-1' : ''">
{{ action.text }} {{ action.label }}
</span> </span>
</div> </div>
</Popconfirm> </Popconfirm>

View File

@ -10,6 +10,7 @@ import { AuthenticationLogin, Verification, z } from '@vben/common-ui';
import { isCaptchaEnable, isTenantEnable } from '@vben/hooks'; import { isCaptchaEnable, isTenantEnable } from '@vben/hooks';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
import { getUrlValue } from '@vben/utils';
import { import {
checkCaptcha, checkCaptcha,
@ -124,12 +125,6 @@ async function handleVerifySuccess({ captchaVerification }: any) {
} }
} }
/** tricky: 配合 login.vue 中redirectUri 需要对参数进行 encode需要在回调后进行decode */
function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}
/** 组件挂载时获取租户信息 */ /** 组件挂载时获取租户信息 */
onMounted(async () => { onMounted(async () => {
await fetchTenantList(); await fetchTenantList();

View File

@ -115,7 +115,7 @@ async function handelUpload({
所属岗位 所属岗位
</div> </div>
</template> </template>
{{ profile.posts.map((post) => post.name).join(',') }} {{ profile.posts && profile.posts.length > 0 ? profile.posts.map(post => post.name).join(',') : '-' }}
</DescriptionsItem> </DescriptionsItem>
<DescriptionsItem> <DescriptionsItem>
<template #label> <template #label>

View File

@ -6,6 +6,7 @@ import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { confirm } from '@vben/common-ui'; import { confirm } from '@vben/common-ui';
import { getUrlValue } from '@vben/utils';
import { Button, Card, Image, message } from 'ant-design-vue'; import { Button, Card, Image, message } from 'ant-design-vue';
@ -149,13 +150,6 @@ async function bindSocial() {
window.history.replaceState({}, '', location.pathname); window.history.replaceState({}, '', location.pathname);
} }
// TODO @ util
// encode decode
function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
bindSocial(); bindSocial();

View File

@ -2,7 +2,12 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; import {
CommonStatusEnum,
DICT_TYPE,
getDictOptions,
getRangePickerDefaultProps,
} from '#/utils';
/** 新增/修改的表单 */ /** 新增/修改的表单 */
export function useFormSchema(): VbenFormSchema[] { export function useFormSchema(): VbenFormSchema[] {
@ -97,7 +102,15 @@ export function useGridFormSchema(): VbenFormSchema[] {
allowClear: true, allowClear: true,
}, },
}, },
// TODO 创建时间 等通用方法完善后加 {
fieldName: 'createTime',
label: '创建时间',
component: 'RangePicker',
componentProps: {
...getRangePickerDefaultProps(),
allowClear: true,
},
},
]; ];
} }

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Recordable } from '@vben/types'; import type { Recordable } from '@vben/types';
import type { SystemDeptApi } from '#/api/system/dept'; import type { SystemMenuApi } from '#/api/system/menu';
import type { SystemRoleApi } from '#/api/system/role'; import type { SystemRoleApi } from '#/api/system/role';
import { ref } from 'vue'; import { ref } from 'vue';
@ -21,7 +21,7 @@ import { useAssignMenuFormSchema } from '../data';
const emit = defineEmits(['success']); const emit = defineEmits(['success']);
const menuTree = ref<SystemDeptApi.Dept[]>([]); // const menuTree = ref<SystemMenuApi.Menu[]>([]); //
const menuLoading = ref(false); // const menuLoading = ref(false); //
const isAllSelected = ref(false); // const isAllSelected = ref(false); //
const isExpanded = ref(false); // const isExpanded = ref(false); //
@ -90,7 +90,7 @@ async function loadMenuTree() {
menuLoading.value = true; menuLoading.value = true;
try { try {
const data = await getMenuList(); const data = await getMenuList();
menuTree.value = handleTree(data) as SystemDeptApi.Dept[]; menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally { } finally {
menuLoading.value = false; menuLoading.value = false;
} }

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { SystemDeptApi } from '#/api/system/dept'; import type { SystemMenuApi } from '#/api/system/menu';
import type { SystemTenantPackageApi } from '#/api/system/tenant-package'; import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
@ -27,7 +27,7 @@ const getTitle = computed(() => {
? $t('ui.actionTitle.edit', ['套餐']) ? $t('ui.actionTitle.edit', ['套餐'])
: $t('ui.actionTitle.create', ['套餐']); : $t('ui.actionTitle.create', ['套餐']);
}); });
const menuTree = ref<SystemDeptApi.Dept[]>([]); // const menuTree = ref<SystemMenuApi.Menu[]>([]); //
const menuLoading = ref(false); // const menuLoading = ref(false); //
const isAllSelected = ref(false); // const isAllSelected = ref(false); //
const isExpanded = ref(false); // const isExpanded = ref(false); //
@ -95,7 +95,7 @@ async function loadMenuTree() {
menuLoading.value = true; menuLoading.value = true;
try { try {
const data = await getMenuList(); const data = await getMenuList();
menuTree.value = handleTree(data) as SystemDeptApi.Dept[]; menuTree.value = handleTree(data) as SystemMenuApi.Menu[];
} finally { } finally {
menuLoading.value = false; menuLoading.value = false;
} }
@ -134,7 +134,6 @@ function getAllNodeIds(nodes: any[], ids: number[] = []): number[] {
<Modal :title="getTitle" class="w-2/5"> <Modal :title="getTitle" class="w-2/5">
<Form class="mx-6"> <Form class="mx-6">
<template #menuIds="slotProps"> <template #menuIds="slotProps">
<!-- TODO @芋艿可优化使用 antd tree原因是更原生 -->
<VbenTree <VbenTree
class="max-h-96 overflow-y-auto" class="max-h-96 overflow-y-auto"
:loading="menuLoading" :loading="menuLoading"

View File

@ -10,6 +10,7 @@ import { AuthenticationLogin, Verification, z } from '@vben/common-ui';
import { isCaptchaEnable, isTenantEnable } from '@vben/hooks'; import { isCaptchaEnable, isTenantEnable } from '@vben/hooks';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
import { getUrlValue } from '@vben/utils';
import { import {
checkCaptcha, checkCaptcha,
@ -124,12 +125,6 @@ async function handleVerifySuccess({ captchaVerification }: any) {
} }
} }
/** tricky: 配合 login.vue 中redirectUri 需要对参数进行 encode需要在回调后进行decode */
function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}
/** 组件挂载时获取租户信息 */ /** 组件挂载时获取租户信息 */
onMounted(async () => { onMounted(async () => {
await fetchTenantList(); await fetchTenantList();

View File

@ -6,6 +6,7 @@ import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { confirm } from '@vben/common-ui'; import { confirm } from '@vben/common-ui';
import { getUrlValue } from '@vben/utils';
import { ElButton, ElCard, ElImage, ElMessage } from 'element-plus'; import { ElButton, ElCard, ElImage, ElMessage } from 'element-plus';
@ -149,13 +150,6 @@ async function bindSocial() {
window.history.replaceState({}, '', location.pathname); window.history.replaceState({}, '', location.pathname);
} }
// TODO @ util
// encode decode
function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
bindSocial(); bindSocial();

View File

@ -10,6 +10,7 @@ import { AuthenticationLogin, Verification, z } from '@vben/common-ui';
import { isCaptchaEnable, isTenantEnable } from '@vben/hooks'; import { isCaptchaEnable, isTenantEnable } from '@vben/hooks';
import { $t } from '@vben/locales'; import { $t } from '@vben/locales';
import { useAccessStore } from '@vben/stores'; import { useAccessStore } from '@vben/stores';
import { getUrlValue } from '@vben/utils';
import { import {
checkCaptcha, checkCaptcha,
@ -124,12 +125,6 @@ async function handleVerifySuccess({ captchaVerification }: any) {
} }
} }
/** tricky: 配合 login.vue 中redirectUri 需要对参数进行 encode需要在回调后进行decode */
function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}
/** 组件挂载时获取租户信息 */ /** 组件挂载时获取租户信息 */
onMounted(async () => { onMounted(async () => {
await fetchTenantList(); await fetchTenantList();

View File

@ -6,6 +6,7 @@ import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { confirm } from '@vben/common-ui'; import { confirm } from '@vben/common-ui';
import { getUrlValue } from '@vben/utils';
import { NButton, NCard, NImage } from 'naive-ui'; import { NButton, NCard, NImage } from 'naive-ui';
@ -150,13 +151,6 @@ async function bindSocial() {
window.history.replaceState({}, '', location.pathname); window.history.replaceState({}, '', location.pathname);
} }
// TODO @ util
// encode decode
function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
bindSocial(); bindSocial();
@ -201,7 +195,7 @@ onMounted(() => {
<NButton <NButton
:disabled="!!item.socialUser" :disabled="!!item.socialUser"
size="small" size="small"
type="link" text
@click="onBind(item)" @click="onBind(item)"
> >
{{ item.socialUser ? '已绑定' : '绑定' }} {{ item.socialUser ? '已绑定' : '绑定' }}

View File

@ -42,3 +42,13 @@ export function getNestedValue<T>(obj: T, path: string): any {
return current; return current;
} }
/**
* URL
* @param key -
* @returns
*/
export function getUrlValue(key: string): string {
const url = new URL(decodeURIComponent(location.href));
return url.searchParams.get(key) ?? '';
}