Browse Source

feat: add about page

vben 9 months ago
parent
commit
199d5506ac
28 changed files with 394 additions and 18 deletions
  1. 1 1
      apps/web-antd/package.json
  2. 3 3
      apps/web-antd/src/views/_essential/vben/about/index.vue
  3. 3 2
      cspell.json
  4. 3 1
      internal/node-utils/src/date.ts
  5. 1 1
      internal/node-utils/src/index.ts
  6. 1 1
      internal/tsconfig/web.json
  7. 1 0
      internal/vite-config/src/config/application.ts
  8. 1 0
      internal/vite-config/src/config/library.ts
  9. 7 1
      internal/vite-config/src/plugins/index.ts
  10. 87 0
      internal/vite-config/src/plugins/inject-metadata.ts
  11. 2 3
      internal/vite-config/src/plugins/license.ts
  12. 2 0
      internal/vite-config/src/typing.ts
  13. 3 1
      packages/@core/shared/design-tokens/src/dark/index.css
  14. 1 1
      packages/@core/shared/design/src/scss/common/base.scss
  15. 3 0
      packages/@core/ui-kit/shadcn-ui/src/components/index.ts
  16. 1 0
      packages/@core/ui-kit/shadcn-ui/src/components/link/index.ts
  17. 30 0
      packages/@core/ui-kit/shadcn-ui/src/components/link/link.vue
  18. 1 0
      packages/@core/ui-kit/shadcn-ui/src/components/render-content/index.ts
  19. 23 0
      packages/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue
  20. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue
  21. 4 1
      packages/@core/ui-kit/shadcn-ui/src/components/ui/card/Card.vue
  22. 1 1
      packages/@core/ui-kit/shadcn-ui/src/components/ui/card/CardHeader.vue
  23. 14 0
      packages/business/universal-ui/src/about/about.ts
  24. 176 0
      packages/business/universal-ui/src/about/about.vue
  25. 1 0
      packages/business/universal-ui/src/about/index.ts
  26. 1 0
      packages/business/universal-ui/src/index.ts
  27. 3 0
      packages/types/package.json
  28. 19 0
      packages/types/window.d.ts

+ 1 - 1
apps/web-antd/package.json

@@ -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",

+ 3 - 3
apps/web-antd/src/views/_essential/vben/about/index.vue

@@ -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>

+ 3 - 2
cspell.json

@@ -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/**"]
 }

+ 3 - 1
internal/node-utils/src/date.ts

@@ -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 };

+ 1 - 1
internal/node-utils/src/index.ts

@@ -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';

+ 1 - 1
internal/tsconfig/web.json

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

+ 1 - 0
internal/vite-config/src/config/application.ts

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

+ 1 - 0
internal/vite-config/src/config/library.ts

@@ -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),

+ 7 - 1
internal/vite-config/src/plugins/index.ts

@@ -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({

+ 87 - 0
internal/vite-config/src/plugins/inject-metadata.ts

@@ -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 };

+ 2 - 3
internal/vite-config/src/plugins/license.ts

@@ -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}

+ 2 - 0
internal/vite-config/src/typing.ts

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

+ 3 - 1
packages/@core/shared/design-tokens/src/dark/index.css

@@ -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 /> */

+ 1 - 1
packages/@core/shared/design/src/scss/common/base.scss

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

+ 3 - 0
packages/@core/ui-kit/shadcn-ui/src/components/index.ts

@@ -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';

+ 1 - 0
packages/@core/ui-kit/shadcn-ui/src/components/link/index.ts

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

+ 30 - 0
packages/@core/ui-kit/shadcn-ui/src/components/link/link.vue

@@ -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>

+ 1 - 0
packages/@core/ui-kit/shadcn-ui/src/components/render-content/index.ts

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

+ 23 - 0
packages/@core/ui-kit/shadcn-ui/src/components/render-content/render-content.vue

@@ -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>

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/spinner/spinner.vue

@@ -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

+ 4 - 1
packages/@core/ui-kit/shadcn-ui/src/components/ui/card/Card.vue

@@ -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>

+ 1 - 1
packages/@core/ui-kit/shadcn-ui/src/components/ui/card/CardHeader.vue

@@ -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>

+ 14 - 0
packages/business/universal-ui/src/about/about.ts

@@ -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 };

+ 176 - 0
packages/business/universal-ui/src/about/about.vue

@@ -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>

+ 1 - 0
packages/business/universal-ui/src/about/index.ts

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

+ 1 - 0
packages/business/universal-ui/src/index.ts

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

+ 3 - 0
packages/types/package.json

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

+ 19 - 0
packages/types/window.d.ts

@@ -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;
+    };
+  }
+}