feat: add about page

This commit is contained in:
vben 2024-06-23 20:05:22 +08:00
parent d4f61c283f
commit 199d5506ac
28 changed files with 394 additions and 18 deletions

View File

@ -35,9 +35,9 @@
"@vben/icons": "workspace:*",
"@vben/layouts": "workspace:*",
"@vben/locales": "workspace:*",
"@vben/universal-ui": "workspace:*",
"@vben/styles": "workspace:*",
"@vben/types": "workspace:*",
"@vben/universal-ui": "workspace:*",
"@vben/utils": "workspace:*",
"@vben/widgets": "workspace:*",
"@vueuse/core": "^10.11.0",

View File

@ -1,9 +1,9 @@
<script lang="ts" setup>
import { Fallback } from '@vben/universal-ui';
import { About } from '@vben/universal-ui';
defineOptions({ name: 'Menu1' });
defineOptions({ name: 'About' });
</script>
<template>
<Fallback status="hello" />
<About />
</template>

View File

@ -22,7 +22,6 @@
"qrcode",
"shadcn",
"sonner",
"ui-kit",
"unplugin",
"vben",
"vueuse",
@ -35,7 +34,9 @@
"nocheck",
"prefixs",
"vitepress",
"ependencies"
"ependencies",
"vite",
"echarts"
],
"ignorePaths": ["**/node_modules/**", "**/dist/**", "**/iconify/**"]
}

View File

@ -5,6 +5,8 @@ import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
dayjs.extend(timezone);
const dateUtil = dayjs().tz('Asia/Shanghai');
dayjs.tz.setDefault('Asia/Shanghai');
const dateUtil = dayjs;
export { dateUtil };

View File

@ -9,6 +9,6 @@ export { prettierFormat } from './prettier';
export type { Package } from '@manypkg/get-packages';
export { consola } from 'consola';
export { nanoid } from 'nanoid';
export { readPackageJSON } from 'pkg-types';
export { type PackageJson, readPackageJSON } from 'pkg-types';
export { rimraf } from 'rimraf';
export { $, chalk as colors, fs, spinner } from 'zx';

View File

@ -8,7 +8,7 @@
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"useDefineForClassFields": true,
"moduleResolution": "bundler",
"types": ["vite/client"],
"types": ["vite/client", "@vben/types/window"],
"declaration": false
}
}

View File

@ -24,6 +24,7 @@ function defineApplicationConfig(options: DefineApplicationOptions = {}) {
html: true,
i18n: true,
injectAppLoading: true,
injectMetadata: true,
isBuild,
license: true,
mock: true,

View File

@ -19,6 +19,7 @@ function defineLibraryConfig(options: DefineLibraryOptions = {}) {
const plugins = await getLibraryConditionPlugins({
dts: false,
injectLibCss: true,
injectMetadata: true,
isBuild,
mode,
...(typeof library === 'function' ? library(config) : library),

View File

@ -27,6 +27,7 @@ import viteVueDevTools from 'vite-plugin-vue-devtools';
import { viteExtraAppConfigPlugin } from './extra-app-config';
import { viteImportMapPlugin } from './importmap';
import { viteInjectAppLoadingPlugin } from './inject-app-loading';
import { viteMetadataPlugin } from './inject-metadata';
import { viteLicensePlugin } from './license';
/**
@ -52,7 +53,7 @@ async function getConditionEstablishedPlugins(
async function getCommonConditionPlugins(
options: CommonPluginOptions,
): Promise<ConditionPlugin[]> {
const { devtools, isBuild, visualizer } = options;
const { devtools, injectMetadata, isBuild, visualizer } = options;
return [
{
condition: true,
@ -66,10 +67,15 @@ async function getCommonConditionPlugins(
viteVueJsx(),
],
},
{
condition: !isBuild && devtools,
plugins: () => [viteVueDevTools()],
},
{
condition: injectMetadata,
plugins: async () => [await viteMetadataPlugin()],
},
{
condition: isBuild && !!visualizer,
plugins: () => [<PluginOption>viteVisualizerPlugin({

View File

@ -0,0 +1,87 @@
import type { PluginOption } from 'vite';
import { dateUtil, getPackages, readPackageJSON } from '@vben/node-utils';
function resolvePackageVersion(
pkgsMeta: Record<string, string>,
name: string,
value: string,
) {
if (value.includes('workspace')) {
return pkgsMeta[name];
}
return value;
}
async function resolveMonorepoDependencies() {
const { packages } = await getPackages();
const resultDevDependencies: Record<string, string> = {};
const resultDependencies: Record<string, string> = {};
const pkgsMeta: Record<string, string> = {};
for (const { packageJson } of packages) {
pkgsMeta[packageJson.name] = packageJson.version;
}
for (const { packageJson } of packages) {
const { dependencies = {}, devDependencies = {} } = packageJson;
for (const [key, value] of Object.entries(dependencies)) {
resultDependencies[key] = resolvePackageVersion(pkgsMeta, key, value);
}
for (const [key, value] of Object.entries(devDependencies)) {
resultDevDependencies[key] = resolvePackageVersion(pkgsMeta, key, value);
}
}
return {
dependencies: resultDependencies,
devDependencies: resultDevDependencies,
};
}
/**
*
*/
async function viteMetadataPlugin(
root = process.cwd(),
): Promise<PluginOption | undefined> {
const { author, description, homepage, license, repository, version } =
await readPackageJSON(root);
const buildTime = dateUtil().format('YYYY-MM-DD HH:mm:ss');
return {
async config() {
const { dependencies, devDependencies } =
await resolveMonorepoDependencies();
const repositoryUrl =
typeof repository === 'object' ? repository.url : repository;
const isAuthorObject = typeof author === 'object';
const authorName = isAuthorObject ? author.name : author;
const authorEmail = isAuthorObject ? author.email : null;
const authorUrl = isAuthorObject ? author.url : null;
return {
define: {
__VBEN_ADMIN_METADATA__: JSON.stringify({
authorEmail,
authorName,
authorUrl,
buildTime,
dependencies,
description,
devDependencies,
homepage,
license,
repositoryUrl,
version,
}),
},
};
},
enforce: 'post',
name: 'vite:inject-metadata',
};
}
export { viteMetadataPlugin };

View File

@ -10,7 +10,7 @@ import { EOL } from 'node:os';
import { dateUtil, readPackageJSON } from '@vben/node-utils';
/**
*
*
* @returns
*/
@ -28,8 +28,7 @@ async function viteLicensePlugin(
enforce: 'post',
generateBundle: {
handler: (_options: NormalizedOutputOptions, bundle: OutputBundle) => {
const date = dateUtil.format('YYYY-MM-DD ');
const date = dateUtil().format('YYYY-MM-DD ');
const copyrightText = `/*!
* Vben Admin Pro
* Version: ${version}

View File

@ -42,6 +42,8 @@ interface CommonPluginOptions {
devtools?: boolean;
/** 环境变量 */
env?: Record<string, any>;
/** 是否开启注入metadata */
injectMetadata: boolean;
/** 是否构建模式 */
isBuild?: boolean;
/** 构建模式 */

View File

@ -6,7 +6,9 @@
--foreground: 220 13% 91%;
/* Background color for <Card /> */
--card: 222.2 84% 4.9%;
--card: 222.86deg 8.43% 16.27%;
/* --card: 222.2 84% 4.9%; */
--card-foreground: 210 40% 98%;
/* Background color for popovers such as <DropdownMenu />, <HoverCard />, <Popover /> */

View File

@ -46,7 +46,7 @@ a:active,
a:hover,
a:link,
a:visited {
color: inherit;
// color: inherit;
text-decoration: none;
}

View File

@ -13,10 +13,12 @@ export * from './hover-card';
export * from './icon';
export * from './input';
export * from './input-password';
export * from './link';
export * from './logo';
export * from './menu-badge';
export * from './pin-input';
export * from './popover';
export * from './render-content';
export * from './scrollbar';
export * from './segmented';
export * from './sheet';
@ -27,6 +29,7 @@ export * from './ui/avatar';
export * from './ui/badge';
export * from './ui/breadcrumb';
export * from './ui/button';
export * from './ui/card';
export * from './ui/checkbox';
export * from './ui/dialog';
export * from './ui/dropdown-menu';

View File

@ -0,0 +1 @@
export { default as VbenLink } from './link.vue';

View File

@ -0,0 +1,30 @@
<script setup lang="ts">
import type { HTMLAttributes } from 'vue';
import { cn } from '@vben-core/toolkit';
import { Primitive, type PrimitiveProps } from 'radix-vue';
interface Props extends PrimitiveProps {
class?: HTMLAttributes['class'];
href: string;
}
const props = withDefaults(defineProps<Props>(), {
as: 'a',
class: '',
href: '',
});
</script>
<template>
<Primitive
:as="as"
:as-child="asChild"
:class="cn('text-primary hover:text-primary-hover', props.class)"
:href="href"
target="_blank"
>
<slot></slot>
</Primitive>
</template>

View File

@ -0,0 +1 @@
export { default as VbenRenderContent } from './render-content.vue';

View File

@ -0,0 +1,23 @@
<script setup lang="ts">
import type { Component } from 'vue';
defineOptions({
name: 'RenderContent',
});
const props = withDefaults(
defineProps<{ content: Component | string; props?: Record<string, any> }>(),
{
props: () => ({}),
},
);
const isComponent = typeof props.content === 'object' && props.content !== null;
</script>
<template>
<component :is="content" v-bind="props" v-if="isComponent" />
<template v-else-if="!isComponent">
{{ content }}
</template>
</template>

View File

@ -61,7 +61,7 @@ function onTransitionEnd() {
:class="{
'invisible opacity-0': !showSpinner,
}"
class="flex-center bg-overlay absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500"
class="flex-center bg-overlay z-100 absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500"
@transitionend="onTransitionEnd"
>
<div

View File

@ -11,7 +11,10 @@ const props = defineProps<{
<template>
<div
:class="
cn('bg-card text-card-foreground rounded-xl border shadow', props.class)
cn(
'bg-card text-card-foreground border-border rounded-xl border shadow',
props.class,
)
"
>
<slot></slot>

View File

@ -9,7 +9,7 @@ const props = defineProps<{
</script>
<template>
<div :class="cn('flex flex-col gap-y-1.5 p-6', props.class)">
<div :class="cn('flex flex-col gap-y-1.5 p-5', props.class)">
<slot></slot>
</div>
</template>

View File

@ -0,0 +1,14 @@
import type { Component } from 'vue';
interface AboutProps {
description?: string;
name?: string;
title?: string;
}
interface DescriptionItem {
content: Component | string;
title: string;
}
export type { AboutProps, DescriptionItem };

View File

@ -0,0 +1,176 @@
<script setup lang="ts">
import type { AboutProps, DescriptionItem } from './about';
import { h } from 'vue';
import { VbenLink, VbenRenderContent } from '@vben-core/shadcn-ui';
interface Props extends AboutProps {}
defineOptions({
name: 'AboutUI',
});
withDefaults(defineProps<Props>(), {
description:
'是一个基于Vue3.0、Vite 、TypeScript 等前沿技术的后台解决方案,目标是为服务中大型项目开发,提供现成的开箱解决方案及丰富的示例。',
name: 'Vben Admin Pro',
title: '关于我们',
});
const {
authorEmail,
authorName,
authorUrl,
buildTime,
dependencies = {},
devDependencies = {},
homepage,
license,
repositoryUrl,
version,
} = window.__VBEN_ADMIN_METADATA__ || {};
const vbenDescriptionItems: DescriptionItem[] = [
{
content: version,
title: '版本号',
},
{
content: license,
title: '开源许可协议',
},
{
content: buildTime,
title: '最后构建时间',
},
{
// TODO:
content: h(VbenLink, { href: homepage }, { default: () => '点击查看' }),
title: '主页',
},
{
// TODO:
content: h(
VbenLink,
{ href: repositoryUrl },
{ default: () => '点击查看' },
),
title: '文档地址',
},
{
// TODO:
content: h(
VbenLink,
{ href: repositoryUrl },
{ default: () => '点击查看' },
),
title: '预览地址',
},
{
content: h(
VbenLink,
{ href: repositoryUrl },
{ default: () => '点击查看' },
),
title: 'Github',
},
{
content: h('div', [
h(
VbenLink,
{ class: 'mr-2', href: authorUrl },
{ default: () => authorName },
),
h(
VbenLink,
{ href: `mailto:${authorEmail}` },
{ default: () => authorEmail },
),
]),
title: '作者',
},
];
const dependenciesItems = Object.keys(dependencies).map((key) => ({
content: dependencies[key],
title: key,
}));
const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
content: devDependencies[key],
title: key,
}));
</script>
<template>
<div class="m-5">
<div class="bg-card rounded-md p-5">
<div>
<h3 class="text-foreground text-2xl font-semibold leading-7">
{{ title }}
</h3>
<p class="text-foreground/80 mt-3 text-sm leading-6">
<VbenLink :href="repositoryUrl">
{{ name }}
</VbenLink>
{{ description }}
</p>
</div>
<div class="mt-4">
<dl class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<template v-for="item in vbenDescriptionItems" :key="item.title">
<div class="border-border border-t px-4 py-6 sm:col-span-1 sm:px-0">
<dt class="text-foreground text-sm font-medium leading-6">
{{ item.title }}
</dt>
<dd class="text-foreground/80 mt-1 text-sm leading-6 sm:mt-2">
<VbenRenderContent :content="item.content" />
</dd>
</div>
</template>
</dl>
</div>
</div>
<div class="bg-card mt-6 rounded-md p-5">
<div>
<h5 class="text-foreground text-lg">生产环境依赖</h5>
</div>
<div class="mt-4">
<dl class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<template v-for="item in dependenciesItems" :key="item.title">
<div class="border-border border-t px-4 py-3 sm:col-span-1 sm:px-0">
<dt class="text-foreground text-sm">
{{ item.title }}
</dt>
<dd class="text-foreground/60 mt-1 text-sm sm:mt-2">
<VbenRenderContent :content="item.content" />
</dd>
</div>
</template>
</dl>
</div>
</div>
<div class="bg-card mt-6 rounded-md p-5">
<div>
<h5 class="text-foreground text-lg">开发环境依赖</h5>
</div>
<div class="mt-4">
<dl class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
<template v-for="item in devDependenciesItems" :key="item.title">
<div class="border-border border-t px-4 py-3 sm:col-span-1 sm:px-0">
<dt class="text-foreground text-sm">
{{ item.title }}
</dt>
<dd class="text-foreground/60 mt-1 text-sm sm:mt-2">
<VbenRenderContent :content="item.content" />
</dd>
</div>
</template>
</dl>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1 @@
export { default as About } from './about.vue';

View File

@ -1,3 +1,4 @@
export * from './about';
export * from './authentication';
export * from './dashboard';
export * from './fallback';

View File

@ -28,6 +28,9 @@
},
"./global": {
"types": "./global.d.ts"
},
"./window": {
"types": "./window.d.ts"
}
},
"publishConfig": {

19
packages/types/window.d.ts vendored Normal file
View File

@ -0,0 +1,19 @@
export {};
declare global {
interface Window {
__VBEN_ADMIN_METADATA__: {
authorEmail: string;
authorName: string;
authorUrl: string;
buildTime: string;
dependencies: Record<string, string>;
description: string;
devDependencies: Record<string, string>;
homepage: string;
license: string;
repositoryUrl: string;
version: string;
};
}
}