committed by
GitHub
102 changed files with 2683 additions and 2170 deletions
@ -1,6 +0,0 @@ |
|||
echo Start running commit-msg hook... |
|||
|
|||
# Check whether the git commit information is standardized |
|||
pnpm exec commitlint --edit "$1" |
|||
|
|||
echo Run commit-msg hook done. |
|||
@ -1,3 +0,0 @@ |
|||
# 每次 git pull 之后, 安装依赖 |
|||
|
|||
pnpm install |
|||
@ -1,7 +0,0 @@ |
|||
# update `.vscode/vben-admin.code-workspace` file |
|||
pnpm vsh code-workspace --auto-commit |
|||
|
|||
# Format and submit code according to lintstagedrc.js configuration |
|||
pnpm exec lint-staged |
|||
|
|||
echo Run pre-commit hook done. |
|||
@ -1,20 +0,0 @@ |
|||
export default { |
|||
'*.md': ['prettier --cache --ignore-unknown --write'], |
|||
'*.vue': [ |
|||
'prettier --write', |
|||
'eslint --cache --fix', |
|||
'stylelint --fix --allow-empty-input', |
|||
], |
|||
'*.{js,jsx,ts,tsx}': [ |
|||
'prettier --cache --ignore-unknown --write', |
|||
'eslint --cache --fix', |
|||
], |
|||
'*.{scss,less,styl,html,vue,css}': [ |
|||
'prettier --cache --ignore-unknown --write', |
|||
'stylelint --fix --allow-empty-input', |
|||
], |
|||
'package.json': ['prettier --cache --write'], |
|||
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ |
|||
'prettier --cache --write--parser json', |
|||
], |
|||
}; |
|||
@ -1,266 +1,32 @@ |
|||
<script lang="ts" setup> |
|||
import type { |
|||
WorkbenchProjectItem, |
|||
WorkbenchQuickNavItem, |
|||
WorkbenchTodoItem, |
|||
WorkbenchTrendItem, |
|||
} from '@vben/common-ui'; |
|||
<script setup lang="ts"> |
|||
import type { FavoriteMenu } from '@abp/platform'; |
|||
|
|||
import { ref } from 'vue'; |
|||
import { useRouter } from 'vue-router'; |
|||
|
|||
import { |
|||
AnalysisChartCard, |
|||
WorkbenchHeader, |
|||
WorkbenchProject, |
|||
WorkbenchQuickNav, |
|||
WorkbenchTodo, |
|||
WorkbenchTrends, |
|||
} from '@vben/common-ui'; |
|||
import { preferences } from '@vben/preferences'; |
|||
import { useUserStore } from '@vben/stores'; |
|||
import { openWindow } from '@vben/utils'; |
|||
|
|||
import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue'; |
|||
|
|||
const userStore = useUserStore(); |
|||
|
|||
// 这是一个示例数据,实际项目中需要根据实际情况进行调整 |
|||
// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转 |
|||
// 例如:url: /dashboard/workspace |
|||
const projectItems: WorkbenchProjectItem[] = [ |
|||
{ |
|||
color: '', |
|||
content: '不要等待机会,而要创造机会。', |
|||
date: '2021-04-01', |
|||
group: '开源组', |
|||
icon: 'carbon:logo-github', |
|||
title: 'Github', |
|||
url: 'https://github.com', |
|||
}, |
|||
{ |
|||
color: '#3fb27f', |
|||
content: '现在的你决定将来的你。', |
|||
date: '2021-04-01', |
|||
group: '算法组', |
|||
icon: 'ion:logo-vue', |
|||
title: 'Vue', |
|||
url: 'https://vuejs.org', |
|||
}, |
|||
{ |
|||
color: '#e18525', |
|||
content: '没有什么才能比努力更重要。', |
|||
date: '2021-04-01', |
|||
group: '上班摸鱼', |
|||
icon: 'ion:logo-html5', |
|||
title: 'Html5', |
|||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML', |
|||
}, |
|||
{ |
|||
color: '#bf0c2c', |
|||
content: '热情和欲望可以突破一切难关。', |
|||
date: '2021-04-01', |
|||
group: 'UI', |
|||
icon: 'ion:logo-angular', |
|||
title: 'Angular', |
|||
url: 'https://angular.io', |
|||
}, |
|||
{ |
|||
color: '#00d8ff', |
|||
content: '健康的身体是实现目标的基石。', |
|||
date: '2021-04-01', |
|||
group: '技术牛', |
|||
icon: 'bx:bxl-react', |
|||
title: 'React', |
|||
url: 'https://reactjs.org', |
|||
}, |
|||
{ |
|||
color: '#EBD94E', |
|||
content: '路是走出来的,而不是空想出来的。', |
|||
date: '2021-04-01', |
|||
group: '架构组', |
|||
icon: 'ion:logo-javascript', |
|||
title: 'Js', |
|||
url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript', |
|||
}, |
|||
]; |
|||
|
|||
// 同样,这里的 url 也可以使用以 http 开头的外部链接 |
|||
const quickNavItems: WorkbenchQuickNavItem[] = [ |
|||
{ |
|||
color: '#1fdaca', |
|||
icon: 'ion:home-outline', |
|||
title: '首页', |
|||
url: '/', |
|||
}, |
|||
{ |
|||
color: '#bf0c2c', |
|||
icon: 'ion:grid-outline', |
|||
title: '仪表盘', |
|||
url: '/dashboard', |
|||
}, |
|||
{ |
|||
color: '#e18525', |
|||
icon: 'ion:layers-outline', |
|||
title: '组件', |
|||
url: '/demos/features/icons', |
|||
}, |
|||
{ |
|||
color: '#3fb27f', |
|||
icon: 'ion:settings-outline', |
|||
title: '系统管理', |
|||
url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整 |
|||
}, |
|||
{ |
|||
color: '#4daf1bc9', |
|||
icon: 'ion:key-outline', |
|||
title: '权限管理', |
|||
url: '/demos/access/page-control', |
|||
}, |
|||
{ |
|||
color: '#00d8ff', |
|||
icon: 'ion:bar-chart-outline', |
|||
title: '图表', |
|||
url: '/analytics', |
|||
}, |
|||
]; |
|||
|
|||
const todoItems = ref<WorkbenchTodoItem[]>([ |
|||
{ |
|||
completed: false, |
|||
content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '审查前端代码提交', |
|||
}, |
|||
{ |
|||
completed: true, |
|||
content: `检查并优化系统性能,降低CPU使用率。`, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '系统性能优化', |
|||
}, |
|||
{ |
|||
completed: false, |
|||
content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '安全检查', |
|||
}, |
|||
{ |
|||
completed: false, |
|||
content: `更新项目中的所有npm依赖包,确保使用最新版本。`, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '更新项目依赖', |
|||
}, |
|||
{ |
|||
completed: false, |
|||
content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `, |
|||
date: '2024-07-30 11:00:00', |
|||
title: '修复UI显示问题', |
|||
}, |
|||
]); |
|||
const trendItems: WorkbenchTrendItem[] = [ |
|||
{ |
|||
avatar: 'svg:avatar-1', |
|||
content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`, |
|||
date: '刚刚', |
|||
title: '威廉', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-2', |
|||
content: `关注了 <a>威廉</a> `, |
|||
date: '1个小时前', |
|||
title: '艾文', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-3', |
|||
content: `发布了 <a>个人动态</a> `, |
|||
date: '1天前', |
|||
title: '克里斯', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-4', |
|||
content: `发表文章 <a>如何编写一个Vite插件</a> `, |
|||
date: '2天前', |
|||
title: 'Vben', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-1', |
|||
content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`, |
|||
date: '3天前', |
|||
title: '皮特', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-2', |
|||
content: `关闭了问题 <a>如何运行项目</a> `, |
|||
date: '1周前', |
|||
title: '杰克', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-3', |
|||
content: `发布了 <a>个人动态</a> `, |
|||
date: '1周前', |
|||
title: '威廉', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-4', |
|||
content: `推送了代码到 <a>Github</a>`, |
|||
date: '2021-04-01 20:00', |
|||
title: '威廉', |
|||
}, |
|||
{ |
|||
avatar: 'svg:avatar-4', |
|||
content: `发表文章 <a>如何编写使用 Admin Vben</a> `, |
|||
date: '2021-03-01 20:00', |
|||
title: 'Vben', |
|||
}, |
|||
]; |
|||
import { Workbench } from '@abp/platform'; |
|||
|
|||
const router = useRouter(); |
|||
|
|||
// 这是一个示例方法,实际项目中需要根据实际情况进行调整 |
|||
// This is a sample method, adjust according to the actual project requirements |
|||
function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) { |
|||
if (nav.url?.startsWith('http')) { |
|||
openWindow(nav.url); |
|||
function navTo(menu: FavoriteMenu) { |
|||
if (menu.path?.startsWith('http')) { |
|||
openWindow(menu.path); |
|||
return; |
|||
} |
|||
if (nav.url?.startsWith('/')) { |
|||
router.push(nav.url).catch((error) => { |
|||
if (menu.path?.startsWith('/')) { |
|||
router.push(menu.path).catch((error) => { |
|||
console.error('Navigation failed:', error); |
|||
}); |
|||
} else { |
|||
console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`); |
|||
console.warn( |
|||
`Unknown URL for navigation item: ${menu.displayName} -> ${menu.path}`, |
|||
); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="p-5"> |
|||
<WorkbenchHeader |
|||
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar" |
|||
> |
|||
<template #title> |
|||
早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧! |
|||
</template> |
|||
<template #description> 今日晴,20℃ - 32℃! </template> |
|||
</WorkbenchHeader> |
|||
|
|||
<div class="mt-5 flex flex-col lg:flex-row"> |
|||
<div class="mr-4 w-full lg:w-3/5"> |
|||
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" /> |
|||
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" /> |
|||
</div> |
|||
<div class="w-full lg:w-2/5"> |
|||
<WorkbenchQuickNav |
|||
:items="quickNavItems" |
|||
class="mt-5 lg:mt-0" |
|||
title="快捷导航" |
|||
@click="navTo" |
|||
/> |
|||
<WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> |
|||
<AnalysisChartCard class="mt-5" title="访问来源"> |
|||
<AnalyticsVisitsSource /> |
|||
</AnalysisChartCard> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<Workbench @nav-to="navTo" /> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
|
|||
@ -0,0 +1,62 @@ |
|||
import type { ListResultDto } from '@abp/core'; |
|||
|
|||
import type { |
|||
UserFavoriteMenuCreateDto, |
|||
UserFavoriteMenuDto, |
|||
} from '../types/favorites'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useMyFavoriteMenusApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 新增常用菜单 |
|||
* @param input 参数 |
|||
* @returns 常用菜单 |
|||
*/ |
|||
function createApi( |
|||
input: UserFavoriteMenuCreateDto, |
|||
): Promise<UserFavoriteMenuDto> { |
|||
return request<UserFavoriteMenuDto>( |
|||
`/api/platform/menus/favorites/my-favorite-menus`, |
|||
{ |
|||
data: input, |
|||
method: 'POST', |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 删除常用菜单 |
|||
* @param id 菜单Id |
|||
*/ |
|||
function deleteApi(id: string): Promise<void> { |
|||
return request(`/api/platform/menus/favorites/my-favorite-menus/${id}`, { |
|||
method: 'DELETE', |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取常用菜单列表 |
|||
* @param framework ui框架 |
|||
* @returns 菜单列表 |
|||
*/ |
|||
function getListApi( |
|||
framework?: string, |
|||
): Promise<ListResultDto<UserFavoriteMenuDto>> { |
|||
return request<ListResultDto<UserFavoriteMenuDto>>( |
|||
`/api/platform/menus/favorites/my-favorite-menus?framework=${framework}`, |
|||
{ |
|||
method: 'GET', |
|||
}, |
|||
); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
createApi, |
|||
deleteApi, |
|||
getListApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
<script lang="ts" setup> |
|||
import { VbenAvatar } from '@vben-core/shadcn-ui'; |
|||
|
|||
interface Props { |
|||
avatar?: string; |
|||
notifierCount?: number; |
|||
text?: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchHeader', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
avatar: '', |
|||
text: '', |
|||
notifierCount: 0, |
|||
}); |
|||
</script> |
|||
<template> |
|||
<div class="card-box p-4 py-6 lg:flex"> |
|||
<VbenAvatar :alt="text" :src="avatar" class="size-20" /> |
|||
<div |
|||
v-if="$slots.title || $slots.description" |
|||
class="flex flex-col justify-center md:ml-6 md:mt-0" |
|||
> |
|||
<h1 v-if="$slots.title" class="text-md font-semibold md:text-xl"> |
|||
<slot name="title"></slot> |
|||
</h1> |
|||
<span v-if="$slots.description" class="text-foreground/80 mt-1"> |
|||
<slot name="description"></slot> |
|||
</span> |
|||
</div> |
|||
<div class="mt-4 flex flex-1 justify-end md:mt-0"> |
|||
<div class="flex flex-col justify-center text-right"> |
|||
<span class="text-foreground/80"> |
|||
{{ $t('workbench.header.notifier.title') }} |
|||
</span> |
|||
<a class="text-2xl">{{ |
|||
$t('workbench.header.notifier.count', [notifierCount]) |
|||
}}</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
@ -0,0 +1,124 @@ |
|||
<script setup lang="ts"> |
|||
import type { FavoriteMenu } from '../types'; |
|||
|
|||
import { computed, h } from 'vue'; |
|||
|
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { |
|||
Card, |
|||
CardContent, |
|||
CardHeader, |
|||
CardTitle, |
|||
VbenIcon, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
import { DeleteOutlined } from '@ant-design/icons-vue'; |
|||
import { Dropdown, Menu, Modal } from 'ant-design-vue'; |
|||
|
|||
interface Props { |
|||
items?: FavoriteMenu[]; |
|||
title: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchQuickNav', |
|||
}); |
|||
|
|||
const props = withDefaults(defineProps<Props>(), { |
|||
items: () => [], |
|||
}); |
|||
|
|||
const emits = defineEmits<{ |
|||
(event: 'click', menu: FavoriteMenu): void; |
|||
(event: 'delete', menu: FavoriteMenu): void; |
|||
(event: 'add'): void; |
|||
}>(); |
|||
|
|||
const MenuItem = Menu.Item; |
|||
|
|||
const getFavoriteMenus = computed(() => { |
|||
const addMenu: FavoriteMenu = { |
|||
id: 'addMenu', |
|||
displayName: $t('workbench.content.favoriteMenu.create'), |
|||
icon: 'ion:add-outline', |
|||
color: '#00bfff', |
|||
isDefault: true, |
|||
}; |
|||
return [...props.items, addMenu]; |
|||
}); |
|||
|
|||
function onClick(menu: FavoriteMenu) { |
|||
if (menu.id === 'addMenu') { |
|||
emits('add'); |
|||
return; |
|||
} |
|||
emits('click', menu); |
|||
} |
|||
|
|||
function onMenuClick(key: string, menu: FavoriteMenu) { |
|||
switch (key) { |
|||
case 'delete': { |
|||
Modal.confirm({ |
|||
centered: true, |
|||
iconType: 'warning', |
|||
title: $t('AbpUi.AreYouSure'), |
|||
content: $t('AbpUi.ItemWillBeDeletedMessage'), |
|||
okCancel: true, |
|||
onOk: () => { |
|||
emits('delete', menu); |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<CardHeader class="py-4"> |
|||
<CardTitle class="text-lg">{{ title }}</CardTitle> |
|||
</CardHeader> |
|||
<CardContent class="flex flex-wrap p-0"> |
|||
<template |
|||
v-for="(item, index) in getFavoriteMenus" |
|||
:key="item.displayName" |
|||
> |
|||
<Dropdown :trigger="['contextmenu']"> |
|||
<div |
|||
:class="{ |
|||
'border-r-0': index % 3 === 2, |
|||
'border-b-0': index < 3, |
|||
'pb-4': index > 2, |
|||
'rounded-bl-xl': index === items.length - 3, |
|||
'rounded-br-xl': index === items.length - 1, |
|||
}" |
|||
class="flex-col-center border-border group w-1/3 cursor-pointer border-r border-t py-8 hover:shadow-xl" |
|||
@click="onClick(item)" |
|||
> |
|||
<VbenIcon |
|||
:color="item.color" |
|||
:icon="item.icon" |
|||
class="size-7 transition-all duration-300 group-hover:scale-125" |
|||
/> |
|||
<span class="text-md mt-2 truncate">{{ item.displayName }}</span> |
|||
</div> |
|||
<template #overlay> |
|||
<Menu |
|||
v-if="!item.isDefault" |
|||
@click=" |
|||
({ key: menuKey }) => onMenuClick(menuKey.toString(), item) |
|||
" |
|||
> |
|||
<MenuItem key="delete" :icon="h(DeleteOutlined)"> |
|||
{{ $t('workbench.content.favoriteMenu.delete') }} |
|||
</MenuItem> |
|||
</Menu> |
|||
</template> |
|||
</Dropdown> |
|||
</template> |
|||
</CardContent> |
|||
</Card> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,137 @@ |
|||
<script setup lang="ts"> |
|||
import type { MenuDto, UserFavoriteMenuDto } from '../../../types'; |
|||
|
|||
import { defineAsyncComponent, ref } from 'vue'; |
|||
|
|||
import { useVbenForm, useVbenModal } from '@vben/common-ui'; |
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { IconifyIcon } from '@vben/icons'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { listToTree } from '@abp/core'; |
|||
import { message, TreeSelect } from 'ant-design-vue'; |
|||
|
|||
import { useMyFavoriteMenusApi } from '../../../api/useMyFavoriteMenusApi'; |
|||
import { useMyMenusApi } from '../../../api/useMyMenusApi'; |
|||
|
|||
const emits = defineEmits<{ |
|||
(event: 'change', data: UserFavoriteMenuDto): void; |
|||
}>(); |
|||
|
|||
const ColorPicker = defineAsyncComponent(() => |
|||
import('vue3-colorpicker').then((res) => { |
|||
import('vue3-colorpicker/style.css'); |
|||
return res.ColorPicker; |
|||
}), |
|||
); |
|||
|
|||
const availableMenus = ref<MenuDto[]>([]); |
|||
|
|||
const { getAllApi } = useMyMenusApi(); |
|||
const { createApi } = useMyFavoriteMenusApi(); |
|||
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
|
|||
const [Form, formApi] = useVbenForm({ |
|||
schema: [ |
|||
{ |
|||
label: $t('workbench.content.favoriteMenu.select'), |
|||
fieldName: 'menuId', |
|||
component: 'TreeSelect', |
|||
rules: 'selectRequired', |
|||
}, |
|||
{ |
|||
label: $t('workbench.content.favoriteMenu.color'), |
|||
fieldName: 'color', |
|||
component: 'ColorPicker', |
|||
defaultValue: '#000000', |
|||
modelPropName: 'pureColor', |
|||
}, |
|||
{ |
|||
label: $t('workbench.content.favoriteMenu.alias'), |
|||
fieldName: 'aliasName', |
|||
component: 'Input', |
|||
}, |
|||
{ |
|||
label: $t('workbench.content.favoriteMenu.icon'), |
|||
fieldName: 'icon', |
|||
component: 'IconPicker', |
|||
}, |
|||
], |
|||
showDefaultActions: false, |
|||
handleSubmit: onSubmit, |
|||
commonConfig: { |
|||
colon: true, |
|||
componentProps: { |
|||
class: 'w-full', |
|||
}, |
|||
}, |
|||
}); |
|||
const [Modal, modalApi] = useVbenModal({ |
|||
async onConfirm() { |
|||
await formApi.validateAndSubmitForm(); |
|||
}, |
|||
async onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
await onInitMenus(); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
async function onInitMenus() { |
|||
const { items } = await getAllApi({ |
|||
framework: uiFramework, |
|||
}); |
|||
const menus = listToTree<MenuDto>(items, { id: 'id', pid: 'parentId' }); |
|||
availableMenus.value = menus; |
|||
} |
|||
|
|||
async function onSubmit(values: Record<string, any>) { |
|||
try { |
|||
modalApi.setState({ submitting: true }); |
|||
const menuDto = await createApi({ |
|||
framework: uiFramework, |
|||
menuId: values.menuId, |
|||
color: values.color, |
|||
icon: values.icon, |
|||
aliasName: values.aliasName, |
|||
}); |
|||
message.success($t('AbpUi.SavedSuccessfully')); |
|||
emits('change', menuDto); |
|||
modalApi.close(); |
|||
} finally { |
|||
modalApi.setState({ submitting: false }); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal :title="$t('workbench.content.favoriteMenu.manage')"> |
|||
<Form> |
|||
<template #color="slotProps"> |
|||
<div class="flex flex-row items-center"> |
|||
<ColorPicker v-bind="slotProps" format="hex" /> |
|||
<span>({{ slotProps.value }})</span> |
|||
</div> |
|||
</template> |
|||
<template #menuId="slotProps"> |
|||
<TreeSelect |
|||
allow-clear |
|||
class="w-full" |
|||
tree-icon |
|||
v-bind="slotProps" |
|||
:field-names="{ label: 'displayName', value: 'id' }" |
|||
:tree-data="availableMenus" |
|||
> |
|||
<template #title="item"> |
|||
<div class="flex flex-row items-center gap-1"> |
|||
<IconifyIcon v-if="item.meta?.icon" :icon="item.meta.icon" /> |
|||
<span>{{ item.displayName }}</span> |
|||
</div> |
|||
</template> |
|||
</TreeSelect> |
|||
</template> |
|||
</Form> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,64 @@ |
|||
<script setup lang="ts"> |
|||
import type { WorkbenchTodoItem } from '@vben/common-ui'; |
|||
|
|||
import { |
|||
Card, |
|||
CardContent, |
|||
CardHeader, |
|||
CardTitle, |
|||
VbenCheckbox, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
interface Props { |
|||
items?: WorkbenchTodoItem[]; |
|||
title: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchTodo', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
items: () => [], |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<CardHeader class="py-4"> |
|||
<CardTitle class="text-lg">{{ title }}</CardTitle> |
|||
</CardHeader> |
|||
<slot v-if="items.length === 0" name="empty"></slot> |
|||
<CardContent v-else class="flex flex-wrap p-5 pt-0"> |
|||
<ul class="divide-border w-full divide-y" role="list"> |
|||
<li |
|||
v-for="item in items" |
|||
:key="item.title" |
|||
:class="{ |
|||
'select-none line-through opacity-60': item.completed, |
|||
}" |
|||
class="flex cursor-pointer justify-between gap-x-6 py-5" |
|||
> |
|||
<div class="flex min-w-0 items-center gap-x-4"> |
|||
<VbenCheckbox v-model:checked="item.completed" name="completed" /> |
|||
<div class="min-w-0 flex-auto"> |
|||
<p class="text-foreground text-sm font-semibold leading-6"> |
|||
{{ item.title }} |
|||
</p> |
|||
<!-- eslint-disable vue/no-v-html --> |
|||
<p |
|||
class="text-foreground/80 *:text-primary mt-1 truncate text-xs leading-5" |
|||
v-html="item.content" |
|||
></p> |
|||
</div> |
|||
</div> |
|||
<div class="hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end"> |
|||
<span class="text-foreground/80 mt-6 text-xs leading-6"> |
|||
{{ item.date }} |
|||
</span> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</CardContent> |
|||
</Card> |
|||
</template> |
|||
@ -0,0 +1,65 @@ |
|||
<script setup lang="ts"> |
|||
import type { WorkbenchTrendItem } from '@vben/common-ui'; |
|||
|
|||
import { |
|||
Card, |
|||
CardContent, |
|||
CardHeader, |
|||
CardTitle, |
|||
VbenIcon, |
|||
} from '@vben-core/shadcn-ui'; |
|||
|
|||
interface Props { |
|||
items?: WorkbenchTrendItem[]; |
|||
title: string; |
|||
} |
|||
|
|||
defineOptions({ |
|||
name: 'WorkbenchTrends', |
|||
}); |
|||
|
|||
withDefaults(defineProps<Props>(), { |
|||
items: () => [], |
|||
}); |
|||
</script> |
|||
|
|||
<template> |
|||
<Card> |
|||
<CardHeader class="py-4"> |
|||
<CardTitle class="text-lg">{{ title }}</CardTitle> |
|||
</CardHeader> |
|||
<slot v-if="items.length === 0" name="empty"></slot> |
|||
<CardContent v-else class="flex flex-wrap p-5 pt-0"> |
|||
<ul class="divide-border w-full divide-y" role="list"> |
|||
<li |
|||
v-for="item in items" |
|||
:key="item.title" |
|||
class="flex justify-between gap-x-6 py-5" |
|||
> |
|||
<div class="flex min-w-0 items-center gap-x-4"> |
|||
<VbenIcon |
|||
:icon="item.avatar" |
|||
alt="" |
|||
class="size-10 flex-none rounded-full" |
|||
/> |
|||
<div class="min-w-0 flex-auto"> |
|||
<p class="text-foreground text-sm font-semibold leading-6"> |
|||
{{ item.title }} |
|||
</p> |
|||
<!-- eslint-disable vue/no-v-html --> |
|||
<p |
|||
class="text-foreground/80 *:text-primary mt-1 truncate text-xs leading-5" |
|||
v-html="item.content" |
|||
></p> |
|||
</div> |
|||
</div> |
|||
<div class="hidden h-full shrink-0 sm:flex sm:flex-col sm:items-end"> |
|||
<span class="text-foreground/80 mt-6 text-xs leading-6"> |
|||
{{ item.date }} |
|||
</span> |
|||
</div> |
|||
</li> |
|||
</ul> |
|||
</CardContent> |
|||
</Card> |
|||
</template> |
|||
@ -0,0 +1,218 @@ |
|||
<script setup lang="ts"> |
|||
import type { WorkbenchTodoItem, WorkbenchTrendItem } from '@vben/common-ui'; |
|||
|
|||
import type { FavoriteMenu } from './types'; |
|||
|
|||
import { computed, defineAsyncComponent, onMounted, ref } from 'vue'; |
|||
|
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
import { useAppConfig } from '@vben/hooks'; |
|||
import { $t } from '@vben/locales'; |
|||
import { preferences } from '@vben/preferences'; |
|||
import { useUserStore } from '@vben/stores'; |
|||
|
|||
import { formatToDateTime } from '@abp/core'; |
|||
import { |
|||
NotificationReadState, |
|||
useMyNotifilersApi, |
|||
useNotificationSerializer, |
|||
} from '@abp/notifications'; |
|||
import { Empty, message } from 'ant-design-vue'; |
|||
|
|||
import { useMyFavoriteMenusApi } from '../../api/useMyFavoriteMenusApi'; |
|||
import WorkbenchHeader from './components/WorkbenchHeader.vue'; |
|||
import WorkbenchQuickNav from './components/WorkbenchQuickNav.vue'; |
|||
import WorkbenchTodo from './components/WorkbenchTodo.vue'; |
|||
import WorkbenchTrends from './components/WorkbenchTrends.vue'; |
|||
|
|||
defineEmits<{ |
|||
(event: 'navTo', menu: FavoriteMenu): void; |
|||
}>(); |
|||
|
|||
const userStore = useUserStore(); |
|||
const { getMyNotifilersApi } = useMyNotifilersApi(); |
|||
const { getListApi: getFavoriteMenusApi, deleteApi: deleteFavoriteMenuApi } = |
|||
useMyFavoriteMenusApi(); |
|||
const { deserialize } = useNotificationSerializer(); |
|||
const { uiFramework } = useAppConfig(import.meta.env, import.meta.env.PROD); |
|||
|
|||
const defaultMenus: FavoriteMenu[] = [ |
|||
{ |
|||
id: '1', |
|||
color: '#1fdaca', |
|||
icon: 'ion:home-outline', |
|||
displayName: $t('workbench.content.favoriteMenu.home'), |
|||
path: '/', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '2', |
|||
color: '#bf0c2c', |
|||
icon: 'ion:grid-outline', |
|||
displayName: $t('workbench.content.favoriteMenu.dashboard'), |
|||
path: '/', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '3', |
|||
color: '#00d8ff', |
|||
icon: 'ant-design:notification-outlined', |
|||
displayName: $t('workbench.content.favoriteMenu.notifiers'), |
|||
path: '/manage/notifications/my-notifilers', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '4', |
|||
color: '#4daf1bc9', |
|||
icon: 'tdesign:user-setting', |
|||
displayName: $t('workbench.content.favoriteMenu.settings'), |
|||
path: '/account/my-settings', |
|||
isDefault: true, |
|||
}, |
|||
{ |
|||
id: '5', |
|||
color: '#3fb27f', |
|||
icon: 'hugeicons:profile-02', |
|||
displayName: $t('workbench.content.favoriteMenu.profile'), |
|||
path: '/account/profile', |
|||
isDefault: true, |
|||
}, |
|||
]; |
|||
const unReadNotifilerCount = ref(0); |
|||
const unReadNotifilers = ref<WorkbenchTrendItem[]>([]); |
|||
const favoriteMenus = ref<FavoriteMenu[]>([]); |
|||
const todoList = ref<WorkbenchTodoItem[]>([]); |
|||
|
|||
const getFavoriteMenus = computed(() => { |
|||
return [...defaultMenus, ...favoriteMenus.value]; |
|||
}); |
|||
const getWelcomeTitle = computed(() => { |
|||
const now = new Date(); |
|||
const hour = now.getHours(); |
|||
if (hour < 12) { |
|||
return $t('workbench.header.welcome.morning', [ |
|||
userStore.userInfo?.realName, |
|||
]); |
|||
} |
|||
if (hour < 14) { |
|||
return $t('workbench.header.welcome.atoon', [userStore.userInfo?.realName]); |
|||
} |
|||
if (hour < 17) { |
|||
return $t('workbench.header.welcome.afternoon', [ |
|||
userStore.userInfo?.realName, |
|||
]); |
|||
} |
|||
if (hour < 24) { |
|||
return $t('workbench.header.welcome.evening', [ |
|||
userStore.userInfo?.realName, |
|||
]); |
|||
} |
|||
return ''; |
|||
}); |
|||
|
|||
const [WorkbenchQuickNavModal, quickNavModalApi] = useVbenModal({ |
|||
connectedComponent: defineAsyncComponent( |
|||
() => import('./components/WorkbenchQuickNavModal.vue'), |
|||
), |
|||
}); |
|||
|
|||
async function onInit() { |
|||
await Promise.all([ |
|||
onInitFavoriteMenus(), |
|||
onInitNotifiers(), |
|||
onInitTodoList(), |
|||
]); |
|||
} |
|||
async function onInitFavoriteMenus() { |
|||
const { items } = await getFavoriteMenusApi(uiFramework); |
|||
favoriteMenus.value = items.map((item) => { |
|||
return { |
|||
...item, |
|||
id: item.menuId, |
|||
isDefault: false, |
|||
}; |
|||
}); |
|||
} |
|||
async function onInitNotifiers() { |
|||
const { items, totalCount } = await getMyNotifilersApi({ |
|||
maxResultCount: 10, |
|||
readState: NotificationReadState.UnRead, |
|||
}); |
|||
unReadNotifilers.value = items.map((item) => { |
|||
const notifier = deserialize(item); |
|||
return { |
|||
avatar: '', |
|||
date: formatToDateTime(item.creationTime), |
|||
title: notifier.title, |
|||
content: notifier.message, |
|||
}; |
|||
}); |
|||
unReadNotifilerCount.value = totalCount; |
|||
} |
|||
async function onInitTodoList() { |
|||
// TODO: 实现待办事项列表 |
|||
todoList.value = []; |
|||
} |
|||
|
|||
function onCreatingFavoriteMenu() { |
|||
quickNavModalApi.open(); |
|||
} |
|||
|
|||
async function onDeleteFavoriteMenu(menu: FavoriteMenu) { |
|||
await deleteFavoriteMenuApi(menu.id); |
|||
await onInitFavoriteMenus(); |
|||
message.success($t('AbpUi.SuccessfullyDeleted')); |
|||
} |
|||
|
|||
onMounted(onInit); |
|||
</script> |
|||
|
|||
<template> |
|||
<div class="p-5"> |
|||
<WorkbenchHeader |
|||
:avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar" |
|||
:text="userStore.userInfo?.realName" |
|||
:notifier-count="unReadNotifilerCount" |
|||
> |
|||
<template #title> |
|||
{{ getWelcomeTitle }} |
|||
</template> |
|||
<template #description> 今日晴,20℃ - 32℃! </template> |
|||
</WorkbenchHeader> |
|||
|
|||
<div class="mt-5 flex flex-col lg:flex-row"> |
|||
<div class="mr-4 w-full lg:w-3/5"> |
|||
<WorkbenchQuickNav |
|||
:items="getFavoriteMenus" |
|||
class="mt-5 lg:mt-0" |
|||
:title="$t('workbench.content.favoriteMenu.title')" |
|||
@add="onCreatingFavoriteMenu" |
|||
@delete="onDeleteFavoriteMenu" |
|||
@click="(menu: FavoriteMenu) => $emit('navTo', menu)" |
|||
/> |
|||
<WorkbenchTodo |
|||
:items="todoList" |
|||
class="mt-5" |
|||
:title="$t('workbench.content.todo.title')" |
|||
> |
|||
<template #empty> |
|||
<Empty /> |
|||
</template> |
|||
</WorkbenchTodo> |
|||
</div> |
|||
<div class="w-full lg:w-2/5"> |
|||
<WorkbenchTrends |
|||
:items="unReadNotifilers" |
|||
:title="$t('workbench.content.trends.title')" |
|||
> |
|||
<template #empty> |
|||
<Empty /> |
|||
</template> |
|||
</WorkbenchTrends> |
|||
</div> |
|||
</div> |
|||
<WorkbenchQuickNavModal @change="onInitFavoriteMenus" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,10 @@ |
|||
interface FavoriteMenu { |
|||
color?: string; |
|||
displayName: string; |
|||
icon?: string; |
|||
id: string; |
|||
isDefault: boolean; |
|||
path?: string; |
|||
} |
|||
|
|||
export type { FavoriteMenu }; |
|||
@ -1,4 +1,5 @@ |
|||
export * from './api'; |
|||
export * from './components'; |
|||
export * from './hooks'; |
|||
export * from './locales'; |
|||
export * from './types'; |
|||
|
|||
@ -0,0 +1,20 @@ |
|||
import type { SupportedLanguagesType } from '@vben/locales'; |
|||
|
|||
import { loadLocalesMapFromDir } from '@vben/locales'; |
|||
|
|||
const modules = import.meta.glob('./langs/**/*.json'); |
|||
|
|||
const localesMap = loadLocalesMapFromDir( |
|||
/\.\/langs\/([^/]+)\/(.*)\.json$/, |
|||
modules, |
|||
); |
|||
|
|||
/** |
|||
* 加载平台服务本地化资源 |
|||
* @param lang 当前语言 |
|||
* @returns 资源集合 |
|||
*/ |
|||
export async function loadPaltformMessages(lang: SupportedLanguagesType) { |
|||
const locales = localesMap[lang]?.(); |
|||
return locales; |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
{ |
|||
"header": { |
|||
"welcome": { |
|||
"atoon": "Good afternoon, {0}, pay attention to rest oh~", |
|||
"afternoon": "Good afternoon, {0}, relax in time, can improve work efficiency~", |
|||
"evening": "Good evening, {0}. Still at work? The off work~", |
|||
"morning": "Good morning, {0}. Begin your day~" |
|||
}, |
|||
"notifier": { |
|||
"title": "Notifier", |
|||
"count": "({0})" |
|||
} |
|||
}, |
|||
"content": { |
|||
"favoriteMenu": { |
|||
"title": "Favorite Menus", |
|||
"home": "Home", |
|||
"dashboard": "Dashboard", |
|||
"profile": "Personal Profile", |
|||
"settings": "Personal Settings", |
|||
"notifiers": "Notifiers", |
|||
"manage": "Manage menu", |
|||
"create": "New menu", |
|||
"delete": "Delete Menu", |
|||
"select": "Select Menu", |
|||
"color": "Select Color", |
|||
"alias": "Alias Name", |
|||
"icon": "Icon" |
|||
}, |
|||
"trends": { |
|||
"title": "Latest News" |
|||
}, |
|||
"todo": { |
|||
"title": "Todo List" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
{ |
|||
"header": { |
|||
"welcome": { |
|||
"atoon": "中午好, {0}, 注意休息哦~", |
|||
"afternoon": "下午好, {0}, 适时放松,可以提高工作效率~", |
|||
"evening": "晚上好, {0}, 还在工作么?该下班了~", |
|||
"morning": "早安, {0}, 开始您一天的工作吧~" |
|||
}, |
|||
"notifier": { |
|||
"title": "通知", |
|||
"count": "({0})" |
|||
} |
|||
}, |
|||
"content": { |
|||
"favoriteMenu": { |
|||
"title": "常用", |
|||
"home": "首页", |
|||
"dashboard": "仪表盘", |
|||
"profile": "个人中心", |
|||
"settings": "个人设置", |
|||
"notifiers": "通知消息", |
|||
"manage": "管理菜单", |
|||
"create": "添加菜单", |
|||
"delete": "删除菜单", |
|||
"select": "选择菜单", |
|||
"color": "选择颜色", |
|||
"alias": "自定义别名", |
|||
"icon": "自定义图标" |
|||
}, |
|||
"trends": { |
|||
"title": "最新消息" |
|||
}, |
|||
"todo": { |
|||
"title": "待办事项" |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,34 @@ |
|||
import type { AuditedEntityDto, IHasConcurrencyStamp } from '@abp/core'; |
|||
|
|||
interface UserFavoriteMenuDto extends AuditedEntityDto<string> { |
|||
aliasName?: string; |
|||
color?: string; |
|||
displayName: string; |
|||
framework: string; |
|||
icon?: string; |
|||
menuId: string; |
|||
name: string; |
|||
path?: string; |
|||
userId: string; |
|||
} |
|||
|
|||
interface UserFavoriteMenuCreateOrUpdateDto { |
|||
aliasName?: string; |
|||
color?: string; |
|||
icon?: string; |
|||
menuId: string; |
|||
} |
|||
|
|||
interface UserFavoriteMenuCreateDto extends UserFavoriteMenuCreateOrUpdateDto { |
|||
framework: string; |
|||
} |
|||
|
|||
interface UserFavoriteMenuUpdateDto |
|||
extends IHasConcurrencyStamp, |
|||
UserFavoriteMenuCreateOrUpdateDto {} |
|||
|
|||
export type { |
|||
UserFavoriteMenuCreateDto, |
|||
UserFavoriteMenuDto, |
|||
UserFavoriteMenuUpdateDto, |
|||
}; |
|||
@ -1,4 +1,5 @@ |
|||
export * from './dataDictionaries'; |
|||
export * from './favorites'; |
|||
export * from './layouts'; |
|||
export * from './menus'; |
|||
export * from './messages'; |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\..\configureawait.props" /> |
|||
<Import Project="..\..\..\..\common.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net9.0</TargetFramework> |
|||
<AssemblyName>LINGYUN.Abp.AspNetCore.Auditing</AssemblyName> |
|||
<PackageId>LINGYUN.Abp.AspNetCore.Auditing</PackageId> |
|||
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
|||
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
|||
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,19 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace LINGYUN.Abp.AspNetCore.Auditing; |
|||
public class AbpAspNetCoreAuditingHeaderOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 是否在审计日志中记录Http请求头,默认: true
|
|||
/// </summary>
|
|||
public bool IsEnabled { get; set; } |
|||
/// <summary>
|
|||
/// 要记录的Http请求头
|
|||
/// </summary>
|
|||
public IList<string> HttpHeaders { get; } |
|||
public AbpAspNetCoreAuditingHeaderOptions() |
|||
{ |
|||
IsEnabled = true; |
|||
HttpHeaders = new List<string>(); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using Volo.Abp.AspNetCore; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.AspNetCore.Auditing; |
|||
|
|||
[DependsOn(typeof(AbpAspNetCoreModule))] |
|||
public class AbpAspNetCoreAuditingModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpAuditingOptions>(options => |
|||
{ |
|||
options.Contributors.Add(new AspNetCoreRecordHeaderAuditLogContributor()); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using Volo.Abp.Auditing; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace LINGYUN.Abp.AspNetCore.Auditing; |
|||
public class AspNetCoreRecordHeaderAuditLogContributor : AuditLogContributor, ITransientDependency |
|||
{ |
|||
private const string HttpHeaderRecordKey = "HttpHeaders"; |
|||
|
|||
public AspNetCoreRecordHeaderAuditLogContributor() |
|||
{ |
|||
} |
|||
|
|||
public override void PreContribute(AuditLogContributionContext context) |
|||
{ |
|||
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpAspNetCoreAuditingHeaderOptions>>(); |
|||
if (!options.Value.IsEnabled) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var httpContext = context.ServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext; |
|||
if (httpContext == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (context.AuditInfo.HasProperty(HttpHeaderRecordKey)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var headerRcords = new Dictionary<string, string>(); |
|||
var httpHeaders = httpContext.Request.Headers.ToImmutableDictionary(); |
|||
|
|||
foreach (var headerKey in options.Value.HttpHeaders) |
|||
{ |
|||
if (httpHeaders.TryGetValue(headerKey, out var headers)) |
|||
{ |
|||
headerRcords[headerKey] = headers.JoinAsString(";"); |
|||
} |
|||
} |
|||
|
|||
context.AuditInfo.SetProperty(HttpHeaderRecordKey, headerRcords); |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
# LINGYUN.Abp.AspNetCore.Auditing |
|||
|
|||
审计日期扩展模块, 用于在审计日志中加入特定的Http请求头记录 |
|||
|
|||
## 模块引用 |
|||
|
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpAspNetCoreAuditingModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
// other |
|||
} |
|||
``` |
|||
|
|||
## 配置项 |
|||
|
|||
* AbpAspNetCoreAuditingHeaderOptions.IsEnabled 是否在审计日志中记录Http请求头,默认: true |
|||
* AbpAspNetCoreAuditingHeaderOptions.HttpHeaders 需要在审计日志中记录的Http请求头列表 |
|||
@ -1,19 +0,0 @@ |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Content; |
|||
|
|||
namespace LINGYUN.Abp.OssManagement; |
|||
|
|||
public interface IFileAppService : IApplicationService |
|||
{ |
|||
Task<OssObjectDto> UploadAsync(UploadFileInput input); |
|||
|
|||
Task<IRemoteStreamContent> GetAsync(GetPublicFileInput input); |
|||
|
|||
Task<ListResultDto<OssObjectDto>> GetListAsync(GetFilesInput input); |
|||
|
|||
Task UploadChunkAsync(UploadFileChunkInput input); |
|||
|
|||
Task DeleteAsync(GetPublicFileInput input); |
|||
} |
|||
@ -1,5 +1,19 @@ |
|||
namespace LINGYUN.Abp.OssManagement; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Content; |
|||
|
|||
public interface IPublicFileAppService : IFileAppService |
|||
namespace LINGYUN.Abp.OssManagement; |
|||
|
|||
public interface IPublicFileAppService : IApplicationService |
|||
{ |
|||
Task<OssObjectDto> UploadAsync(UploadFileInput input); |
|||
|
|||
Task<IRemoteStreamContent> GetAsync(GetPublicFileInput input); |
|||
|
|||
Task<ListResultDto<OssObjectDto>> GetListAsync(GetFilesInput input); |
|||
|
|||
Task UploadChunkAsync(UploadFileChunkInput input); |
|||
|
|||
Task DeleteAsync(GetPublicFileInput input); |
|||
} |
|||
|
|||
@ -1,3 +1,4 @@ |
|||
Modules |
|||
file-blob-storing |
|||
blobs |
|||
blobs |
|||
yarn.lock |
|||
@ -1,9 +1,9 @@ |
|||
{ |
|||
"version": "9.2.0", |
|||
"version": "9.2.3", |
|||
"name": "my-app-single", |
|||
"private": true, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.mvc.ui.theme.leptonxlite": "4.2.0", |
|||
"@abp/qrcode": "9.2.0" |
|||
"@abp/aspnetcore.mvc.ui.theme.leptonxlite": "4.2.3", |
|||
"@abp/qrcode": "9.2.3" |
|||
} |
|||
} |
|||
@ -1,9 +1,9 @@ |
|||
{ |
|||
"version": "9.2.0", |
|||
"version": "9.2.3", |
|||
"name": "my-app-authserver", |
|||
"private": true, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.mvc.ui.theme.leptonxlite": "4.2.0", |
|||
"@abp/qrcode": "9.2.0" |
|||
"@abp/aspnetcore.mvc.ui.theme.leptonxlite": "4.2.3", |
|||
"@abp/qrcode": "9.2.3" |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1,12 +1,79 @@ |
|||
:"2025-07-05 11:31:41 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Connections] [1] [135] - Connection id \"0HNDQQATBSFQF\" completed keep alive response.\n","stream":"stderr","time":"2025-07-05T03:31:41.530579165Z"} |
|||
{"log":"2025-07-05 11:31:41 [INF] [Microsoft.AspNetCore.Hosting.Diagnostics] [1] [135] - Request finished HTTP/1.1 GET http://localhost/healthz - 500 0 null 3.1038ms\n","stream":"stderr","time":"2025-07-05T03:31:41.530631619Z"} |
|||
{"log":"2025-07-05 11:31:41 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets] [1] [95] - Connection id \"0HNDQQATBSFQF\" received FIN.\n","stream":"stderr","time":"2025-07-05T03:31:41.530770288Z"} |
|||
{"log":"2025-07-05 11:31:41 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets] [1] [95] - Connection id \"0HNDQQATBSFQF\" sending FIN because: \"The Socket transport's send loop completed gracefully.\"\n","stream":"stderr","time":"2025-07-05T03:31:41.5309084Z"} |
|||
{"log":"2025-07-05 11:31:41 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Connections] [1] [95] - Connection id \"0HNDQQATBSFQF\" disconnecting.\n","stream":"stderr","time":"2025-07-05T03:31:41.530921423Z"} |
|||
{"log":"2025-07-05 11:31:41 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Connections] [1] [135] - Connection id \"0HNDQQATBSFQF\" stopped.\n","stream":"stderr","time":"2025-07-05T03:31:41.531023048Z"} |
|||
{"log":"2025-07-05 11:31:51 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Connections] [1] [100] - Connection id \"0HNDQQATBSFQG\" accepted.\n","stream":"stderr","time":"2025-07-05T03:31:51.593587844Z"} |
|||
{"log":"2025-07-05 11:31:51 [DBG] [Microsoft.AspNetCore.Server.Kestrel.Connections] [1] [135] - Connection id \"0HNDQQATBSFQG\" started.\n","stream":"stderr","time":"2025-07-05T03:31:51.593608909Z"} |
|||
{"log":"2025-07-05 11:31:51 [INF] [Microsoft.AspNetCore.Hosting.Diagnostics] [1] [135] - Request starting HTTP/1.1 HEAD http://localhost/healthz - null null\n","stream":"stderr","time":"2025-07-05T03:31:51.593735211Z"} |
|||
{"log":"2025-07-05 11:31:51 [DBG] [Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware] [1] [135] - AbpCultureMapRequestCultureProvider returned the following unsupported cultures '[]'.\n","stream":"stderr","time":"2025-07-05T03:31:51.594228126Z"} |
|||
{"log":"2025-07-05 11:31:51 [DBG] [Microsoft.AspNetCore.Localization.RequestLocalizationMiddleware] [1] [135] - AbpCultureMapRequestCultureProvider returned the following unsupported UI Cultures '[]'.\n","stream":"stderr","time":"2025-07-05T03:31:51.594231565Z"} |
|||
{"log":"2025-07-05 11:31:51 [DBG] [Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware] [1] [135] - The request path /healthz does not match a supported file type\n","stream":"st |
|||
//! moment.js locale configuration
|
|||
//! locale : English (Australia) [en-au]
|
|||
//! author : Jared Morse : https://github.com/jarcoal
|
|||
|
|||
;(function (global, factory) { |
|||
typeof exports === 'object' && typeof module !== 'undefined' |
|||
&& typeof require === 'function' ? factory(require('../moment')) : |
|||
typeof define === 'function' && define.amd ? define(['../moment'], factory) : |
|||
factory(global.moment) |
|||
}(this, (function (moment) { 'use strict'; |
|||
|
|||
//! moment.js locale configuration
|
|||
|
|||
var enAu = moment.defineLocale('en-au', { |
|||
months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split( |
|||
'_' |
|||
), |
|||
monthsShort: 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), |
|||
weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split( |
|||
'_' |
|||
), |
|||
weekdaysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), |
|||
weekdaysMin: 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), |
|||
longDateFormat: { |
|||
LT: 'h:mm A', |
|||
LTS: 'h:mm:ss A', |
|||
L: 'DD/MM/YYYY', |
|||
LL: 'D MMMM YYYY', |
|||
LLL: 'D MMMM YYYY h:mm A', |
|||
LLLL: 'dddd, D MMMM YYYY h:mm A', |
|||
}, |
|||
calendar: { |
|||
sameDay: '[Today at] LT', |
|||
nextDay: '[Tomorrow at] LT', |
|||
nextWeek: 'dddd [at] LT', |
|||
lastDay: '[Yesterday at] LT', |
|||
lastWeek: '[Last] dddd [at] LT', |
|||
sameElse: 'L', |
|||
}, |
|||
relativeTime: { |
|||
future: 'in %s', |
|||
past: '%s ago', |
|||
s: 'a few seconds', |
|||
ss: '%d seconds', |
|||
m: 'a minute', |
|||
mm: '%d minutes', |
|||
h: 'an hour', |
|||
hh: '%d hours', |
|||
d: 'a day', |
|||
dd: '%d days', |
|||
M: 'a month', |
|||
MM: '%d months', |
|||
y: 'a year', |
|||
yy: '%d years', |
|||
}, |
|||
dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, |
|||
ordinal: function (number) { |
|||
var b = number % 10, |
|||
output = |
|||
~~((number % 100) / 10) === 1 |
|||
? 'th' |
|||
: b === 1 |
|||
? 'st' |
|||
: b === 2 |
|||
? 'nd' |
|||
: b === 3 |
|||
? 'rd' |
|||
: 'th'; |
|||
return number + output; |
|||
}, |
|||
week: { |
|||
dow: 0, // Sunday is the first day of the week.
|
|||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
|||
}, |
|||
}); |
|||
|
|||
return enAu; |
|||
|
|||
}))); |
|||
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,9 +1,9 @@ |
|||
{ |
|||
"version": "9.2.0", |
|||
"version": "9.2.3", |
|||
"name": "my-app-identityserver", |
|||
"private": true, |
|||
"dependencies": { |
|||
"@abp/aspnetcore.mvc.ui.theme.leptonxlite": "4.2.0", |
|||
"@abp/qrcode": "~9.2.0" |
|||
"@abp/aspnetcore.mvc.ui.theme.leptonxlite": "4.2.3", |
|||
"@abp/qrcode": "~9.2.3" |
|||
} |
|||
} |
|||
File diff suppressed because it is too large
@ -1 +1,3 @@ |
|||
","ebx_topicSuggestionExplanationTitle":"Kay rimanata sutichay huk runakunata imayna kasqanman, runakunaman, churayninkunaman yaykunankupaq.","ebx_repeatedWordsExplanationTitle":"Kay simiqa kutipasqam","ebx_showBeta":"Beta kaypi {0}","ebx_googleDocs":"Google Docs","ebx_indicatorTooltip":"Qillqachaqta kichay utaq clicta ruway alliq ñitinapi aswan akllaykuna qawanapaq","ebx_indicatorTooltipActive":"Editorpi llanpan yacharichiyta qaway utaq clicta ruway alliq ñitinapi aswan akllaykuna qawanapaq","contextAccessibilityLabelForSpelling":"Mana allin qillqasqa, {0}, {1}","contextAccessibilityLabelForRepeatedWord":"Kutipasqa simi, {0}, {1}","contextAccessibilityLabelForGrammar":"Allin rimana pantay, {0}, {1}","contextAccessibilityLabelForRefinement":"Refinamiento rikchay, {0 |
|||
/*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */ |
|||
|
|||
!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/nb",[],function(){return{errorLoading:function(){return"Kunne ikke hente resultater."},inputTooLong:function(e){return"Vennligst fjern "+(e.input.length-e.maximum)+" tegn"},inputTooShort:function(e){return"Vennligst skriv inn "+(e.minimum-e.input.length)+" tegn til"},loadingMore:function(){return"Laster flere resultater…"},maximumSelected:function(e){return"Du kan velge maks "+e.maximum+" elementer"},noResults:function(){return"Ingen treff"},searching:function(){return"Søker…"},removeAllItems:function(){return"Fjern alle elementer"}}}),e.define,e.require}(); |
|||
Binary file not shown.
Binary file not shown.
@ -1 +1,30 @@ |
|||
olour to ${color} on table ${tableName}",TableBodyRangeFormatSetFontColorLabel:"Set the font colour to ${color} on table ${tableName} body",TableHeaderRowRangeFormatSetFontColorLabel:"Set the font colour to ${color} on table ${tableName} header row",TableTotalRowRangeFormatSetFontColorLabel:"Set the font colour to ${color} on table ${tableName} total row",TableGeneralRangeFormatSetFontColorLabel:"Set the font colour to ${color} on sub-range of table ${tableName}",TableRowsRangeFormatSetFontColorLabel:"Set the font colour to ${color} on ${count} row(s) in table ${tableName}",TableRowByIndexRangeFormatSetFontColorLabel:"Set the font colour to ${color} on row at index ${rowIndex} in table ${tableName}",TableColumnsRangeFormatSetFontColorLabel:"Set the font colour to ${color} on ${count} column(s) in table ${tableName}",TableColumnByNameRangeFormatSetFontColorLabel:"Set the font colour to ${color} on column '${columnName}' in table ${tableName}",TableColumnByIndexRangeFormatSetFontColorLabel:"Set the font colour to ${color} |
|||
(function (factory) { |
|||
if (typeof define === 'function' && define.amd) { |
|||
define(['jquery'], factory); |
|||
} else if (typeof module === 'object' && typeof module.exports === 'object') { |
|||
factory(require('jquery')); |
|||
} else { |
|||
factory(jQuery); |
|||
} |
|||
}(function (jQuery) { |
|||
// Thai
|
|||
jQuery.timeago.settings.strings = { |
|||
prefixAgo: null, |
|||
prefixFromNow: null, |
|||
suffixAgo: "ที่แล้ว", |
|||
suffixFromNow: "จากตอนนี้", |
|||
seconds: "น้อยกว่าหนึ่งนาที", |
|||
minute: "ประมาณหนึ่งนาที", |
|||
minutes: "%d นาที", |
|||
hour: "ประมาณหนึ่งชั่วโมง", |
|||
hours: "ประมาณ %d ชั่วโมง", |
|||
day: "หนึ่งวัน", |
|||
days: "%d วัน", |
|||
month: "ประมาณหนึ่งเดือน", |
|||
months: "%d เดือน", |
|||
year: "ประมาณหนึ่งปี", |
|||
years: "%d ปี", |
|||
wordSeparator: "", |
|||
numbers: [] |
|||
}; |
|||
})); |
|||
|
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue