snow před 1 rokem
rodič
revize
0aea751702

+ 2 - 1
package.json

@@ -39,7 +39,8 @@
     "pinia": "^2.1.7",
     "vue": "^3.4.21",
     "vue-i18n": "^9.10.2",
-    "vue-router": "^4.3.0"
+    "vue-router": "^4.3.0",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@commitlint/cli": "^19.2.1",

+ 80 - 0
pnpm-lock.yaml

@@ -46,6 +46,7 @@ specifiers:
   vue-i18n: ^9.10.2
   vue-router: ^4.3.0
   vue-tsc: ^2.0.7
+  xlsx: ^0.18.5
 
 dependencies:
   '@kirklin/logger': registry.npmmirror.com/@kirklin/logger/0.0.2
@@ -60,6 +61,7 @@ dependencies:
   vue: registry.npmmirror.com/vue/3.4.21_typescript@5.4.3
   vue-i18n: registry.npmmirror.com/vue-i18n/9.10.2_vue@3.4.21
   vue-router: registry.npmmirror.com/vue-router/4.3.0_vue@3.4.21
+  xlsx: registry.npmmirror.com/xlsx/0.18.5
 
 devDependencies:
   '@commitlint/cli': registry.npmmirror.com/@commitlint/cli/19.2.1_tm4orrswz3oz6srbzj2kmxbyve
@@ -4022,6 +4024,13 @@ packages:
     hasBin: true
     dev: true
 
+  registry.npmmirror.com/adler-32/1.3.1:
+    resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz}
+    name: adler-32
+    version: 1.3.1
+    engines: {node: '>=0.8'}
+    dev: false
+
   registry.npmmirror.com/agent-base/7.1.1:
     resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/agent-base/-/agent-base-7.1.1.tgz}
     name: agent-base
@@ -4433,6 +4442,16 @@ packages:
     name: caniuse-lite
     version: 1.0.30001603
 
+  registry.npmmirror.com/cfb/1.2.2:
+    resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz}
+    name: cfb
+    version: 1.2.2
+    engines: {node: '>=0.8'}
+    dependencies:
+      adler-32: registry.npmmirror.com/adler-32/1.3.1
+      crc-32: registry.npmmirror.com/crc-32/1.2.2
+    dev: false
+
   registry.npmmirror.com/chai/4.4.1:
     resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/chai/-/chai-4.4.1.tgz}
     name: chai
@@ -4573,6 +4592,13 @@ packages:
       wrap-ansi: registry.npmmirror.com/wrap-ansi/7.0.0
     dev: true
 
+  registry.npmmirror.com/codepage/1.15.0:
+    resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz}
+    name: codepage
+    version: 1.15.0
+    engines: {node: '>=0.8'}
+    dev: false
+
   registry.npmmirror.com/color-convert/1.9.3:
     resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz}
     name: color-convert
@@ -4775,6 +4801,14 @@ packages:
       typescript: registry.npmmirror.com/typescript/5.4.3
     dev: true
 
+  registry.npmmirror.com/crc-32/1.2.2:
+    resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz}
+    name: crc-32
+    version: 1.2.2
+    engines: {node: '>=0.8'}
+    hasBin: true
+    dev: false
+
   registry.npmmirror.com/cross-spawn/7.0.3:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz}
     name: cross-spawn
@@ -6142,6 +6176,13 @@ packages:
       mime-types: registry.npmmirror.com/mime-types/2.1.35
     dev: true
 
+  registry.npmmirror.com/frac/1.1.2:
+    resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz}
+    name: frac
+    version: 1.1.2
+    engines: {node: '>=0.8'}
+    dev: false
+
   registry.npmmirror.com/fraction.js/4.3.7:
     resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz}
     name: fraction.js
@@ -9005,6 +9046,15 @@ packages:
     engines: {node: '>= 10.x'}
     dev: true
 
+  registry.npmmirror.com/ssf/0.11.2:
+    resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz}
+    name: ssf
+    version: 0.11.2
+    engines: {node: '>=0.8'}
+    dependencies:
+      frac: registry.npmmirror.com/frac/1.1.2
+    dev: false
+
   registry.npmmirror.com/stackback/0.0.2:
     resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz}
     name: stackback
@@ -10473,6 +10523,20 @@ packages:
       stackback: registry.npmmirror.com/stackback/0.0.2
     dev: true
 
+  registry.npmmirror.com/wmf/1.0.2:
+    resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz}
+    name: wmf
+    version: 1.0.2
+    engines: {node: '>=0.8'}
+    dev: false
+
+  registry.npmmirror.com/word/0.3.0:
+    resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/word/-/word-0.3.0.tgz}
+    name: word
+    version: 0.3.0
+    engines: {node: '>=0.8'}
+    dev: false
+
   registry.npmmirror.com/workbox-background-sync/7.0.0:
     resolution: {integrity: sha512-S+m1+84gjdueM+jIKZ+I0Lx0BDHkk5Nu6a3kTVxP4fdj3gKouRNmhO8H290ybnJTOPfBDtTMXSQA/QLTvr7PeA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/workbox-background-sync/-/workbox-background-sync-7.0.0.tgz}
     name: workbox-background-sync
@@ -10704,6 +10768,22 @@ packages:
         optional: true
     dev: true
 
+  registry.npmmirror.com/xlsx/0.18.5:
+    resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz}
+    name: xlsx
+    version: 0.18.5
+    engines: {node: '>=0.8'}
+    hasBin: true
+    dependencies:
+      adler-32: registry.npmmirror.com/adler-32/1.3.1
+      cfb: registry.npmmirror.com/cfb/1.2.2
+      codepage: registry.npmmirror.com/codepage/1.15.0
+      crc-32: registry.npmmirror.com/crc-32/1.2.2
+      ssf: registry.npmmirror.com/ssf/0.11.2
+      wmf: registry.npmmirror.com/wmf/1.0.2
+      word: registry.npmmirror.com/word/0.3.0
+    dev: false
+
   registry.npmmirror.com/xml-name-validator/4.0.0:
     resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz}
     name: xml-name-validator

+ 8 - 3
src/App.tsx

@@ -1,13 +1,18 @@
 import { defineComponent } from "vue";
 import { RouterView } from "vue-router";
-import { NConfigProvider, darkTheme } from "naive-ui";
+import { NConfigProvider, NMessageProvider, darkTheme } from "naive-ui";
+import { useTheme } from "./hooks/theme";
 
 const App = defineComponent({
   name: "App",
   setup() {
+    const { theme } = useTheme();
+
     return () => (
-      <NConfigProvider theme={darkTheme}>
-        <RouterView />
+      <NConfigProvider theme={theme.value === "dark" ? darkTheme : null}>
+        <NMessageProvider>
+          <RouterView />
+        </NMessageProvider>
       </NConfigProvider>
     );
   },

+ 1 - 1
src/composables/number/props.ts

@@ -8,7 +8,7 @@ export function makeNumberProps() {
      */
     size: {
       type: Number,
-      default: 28,
+      default: 20,
     },
     /**
      * 号码球的颜色

+ 2 - 0
src/constant/key.ts

@@ -0,0 +1,2 @@
+export const COLLECTION_KEY = "collection_key";
+export const THEME_KEY = "theme_key";

+ 21 - 0
src/hooks/theme.ts

@@ -0,0 +1,21 @@
+import { THEME_KEY } from "~/constant/key";
+import type { CustomTheme } from "~/layouts/Navigation/components/ThemeChange/types";
+
+export function useTheme() {
+  const mode = useColorMode<CustomTheme>({ attribute: "data-theme" });
+  const theme = useLocalStorage(THEME_KEY, "dark");
+
+  function toggleTheme() {
+    const _theme = theme.value === "dark" ? "light" : "dark";
+    mode.value = _theme;
+    theme.value = _theme;
+  }
+
+  const isDarkTheme = computed(() => theme.value === "dark");
+
+  return {
+    theme,
+    isDarkTheme,
+    toggleTheme,
+  };
+}

+ 0 - 41
src/layouts/Navigation/components/LocalesChange/index.vue

@@ -1,41 +0,0 @@
-<script setup lang="ts">
-import { useLocalStorage } from "@vueuse/core";
-import { useI18n } from "vue-i18n";
-import { languagesNameList } from "~/composables/langugage";
-
-defineOptions({
-  name: "LocalesChange",
-});
-const { availableLocales, locale } = useI18n();
-function ChangeLocales(lang: string) {
-  locale.value = lang;
-  const localedLang = useLocalStorage("locale", "zh");
-  localedLang.value = lang;
-}
-</script>
-
-<template>
-  <div title="Change Locales" class="dropdown dropdown-end">
-    <div tabindex="0" class="gap-1 normal-case btn btn-ghost">
-      <UnoCSSIconButton icon="i-tabler-language" />
-      <span class="hidden md:inline" />
-      <UnoCSSIconButton icon="i-tabler-chevron-down" />
-    </div>
-    <div class="dropdown-content top-px mt-16 w-56 overflow-y-auto bg-base-200 text-base-content shadow-2xl rounded-t-box rounded-b-box">
-      <ul class="menu-compact gap-1 p-3 menu" tabindex="0">
-        <li v-for="lang in availableLocales" :key="lang">
-          <button class="flex" @click="ChangeLocales(lang)">
-            <span class="flex flex-1 justify-between">
-              {{ languagesNameList.find((item) => item.code === lang)?.nativeName }}
-              <!--              <span class="badge-ghost badge badge-sm" /> -->
-            </span>
-          </button>
-        </li>
-      </ul>
-    </div>
-  </div>
-</template>
-
- <style scoped>
-
- </style>

+ 0 - 120
src/layouts/Navigation/components/ThemeChange/data.ts

@@ -1,120 +0,0 @@
-import type { ThemeList } from "~/layouts/Navbar/components/ThemeChange/types";
-
-export const themeList: ThemeList[] = [
-  {
-    name: "🌝 light",
-    id: "light",
-  },
-  {
-    name: "🌚 dark",
-    id: "dark",
-  },
-  // {
-  //   name: "🧁 cupcake",
-  //   id: "cupcake",
-  // },
-  // {
-  //   name: "🐝 bumblebee",
-  //   id: "bumblebee",
-  // },
-  // {
-  //   name: "✳️ Emerald",
-  //   id: "emerald",
-  // },
-  // {
-  //   name: "🏢 Corporate",
-  //   id: "corporate",
-  // },
-  // {
-  //   name: "🌃 synthwave",
-  //   id: "synthwave",
-  // },
-  // {
-  //   name: "👴 retro",
-  //   id: "retro",
-  // },
-  // {
-  //   name: "🤖 cyberpunk",
-  //   id: "cyberpunk",
-  // },
-  // {
-  //   name: "🌸 valentine",
-  //   id: "valentine",
-  // },
-  // {
-  //   name: "🎃 halloween",
-  //   id: "halloween",
-  // },
-  // {
-  //   name: "🌷 garden",
-  //   id: "garden",
-  // },
-  // {
-  //   name: "🌲 forest",
-  //   id: "forest",
-  // },
-  // {
-  //   name: "🐟 aqua",
-  //   id: "aqua",
-  // },
-  // {
-  //   name: "👓 lofi",
-  //   id: "lofi",
-  // },
-  // {
-  //   name: "🖍 pastel",
-  //   id: "pastel",
-  // },
-  // {
-  //   name: "🧚‍♀️ fantasy",
-  //   id: "fantasy",
-  // },
-  // {
-  //   name: "📝 Wireframe",
-  //   id: "wireframe",
-  // },
-  // {
-  //   name: "🏴 black",
-  //   id: "black",
-  // },
-  // {
-  //   name: "💎 luxury",
-  //   id: "luxury",
-  // },
-  // {
-  //   name: "🧛‍♂️ dracula",
-  //   id: "dracula",
-  // },
-  // {
-  //   name: "🖨 CMYK",
-  //   id: "cmyk",
-  // },
-  // {
-  //   name: "🍁 Autumn",
-  //   id: "autumn",
-  // },
-  // {
-  //   name: "💼 Business",
-  //   id: "business",
-  // },
-  // {
-  //   name: "💊 Acid",
-  //   id: "acid",
-  // },
-  // {
-  //   name: "🍋 Lemonade",
-  //   id: "lemonade",
-  // },
-  // {
-  //   name: "🌙 Night",
-  //   id: "night",
-  // },
-  // {
-  //   name: "☕️ Coffee",
-  //   id: "coffee",
-  // },
-  // {
-  //   name: "❄️ Winter",
-  //   id: "winter",
-  // },
-];

+ 0 - 129
src/layouts/Navigation/components/ThemeChange/index.tsx

@@ -1,129 +0,0 @@
-import { defineComponent } from "vue";
-import { useI18n } from "vue-i18n";
-import { logger } from "@kirklin/logger";
-import { themeList } from "./data";
-import type { CustomTheme } from "./types";
-import { UnoCSSIconButton } from "~/components/icon";
-
-// import store from "~/store";
-
-const ThemeChange = defineComponent({
-  name: "ThemeChange",
-  setup() {
-    const { t } = useI18n();
-
-    const _theme = useLocalStorage("theme", "dark");
-
-    const mode = useColorMode<CustomTheme>({
-      attribute: "data-theme",
-      modes: {
-        cupcake: "cupcake",
-        bumblebee: "bumblebee",
-        emerald: "emerald",
-        corporate: "corporate",
-        synthwave: "synthwave",
-        retro: "retro",
-        cyberpunk: "cyberpunk",
-        valentine: "valentine",
-        halloween: "halloween",
-        garden: "garden",
-        forest: "forest",
-        aqua: "aqua",
-        lofi: "lofi",
-        pastel: "pastel",
-        fantasy: "fantasy",
-        wireframe: "wireframe",
-        black: "black",
-        luxury: "luxury",
-        dracula: "dracula",
-        cmyk: "cmyk",
-        autumn: "autumn",
-        business: "business",
-        acid: "acid",
-        lemonade: "lemonade",
-        night: "night",
-        coffee: "coffee",
-        winter: "winter",
-      },
-    });
-
-    function changeTheme(event: MouseEvent, theme: CustomTheme) {
-      const isSameTheme = mode.value === theme;
-      _theme.value = theme;
-      try {
-        // eslint-disable-next-line ts/ban-ts-comment
-        // @ts-expect-error
-        if (document.startViewTransition === undefined) {
-          throw new Error("document.startViewTransition is undefined, please update your browser to the latest version or use a modern browser.");
-        }
-        const x = event.clientX;
-        const y = event.clientY;
-        const endRadius = Math.hypot(
-          Math.max(x, innerWidth - x),
-          Math.max(y, innerHeight - y),
-        );
-        // eslint-disable-next-line ts/ban-ts-comment
-        // @ts-expect-error
-        const transition = document.startViewTransition();
-        transition.ready.then(() => {
-          const clipPath = [
-            `circle(0px at ${x}px ${y}px)`,
-            `circle(${endRadius}px at ${x}px ${y}px)`,
-          ];
-          document.documentElement.animate(
-            {
-              clipPath: isSameTheme ? [...clipPath].reverse() : clipPath,
-            },
-            {
-              duration: 500,
-              easing: "ease-in",
-              pseudoElement: isSameTheme
-                ? "::view-transition-old(root)"
-                : "::view-transition-new(root)",
-            },
-          );
-        });
-      } catch (error: unknown) {
-        logger.error(`Failed to change theme : ${error instanceof Error ? error.message : ""}`);
-      } finally {
-        mode.value = theme;
-      }
-    }
-
-    return () => (
-      <div title="Change Theme" class="dropdown dropdown-end">
-        <div tabindex="0" class="gap-1 normal-case btn btn-ghost">
-          <UnoCSSIconButton icon="i-tabler-color-swatch" />
-          <span class="hidden md:inline">{ t("Themes") }</span>
-          <UnoCSSIconButton icon="i-tabler-chevron-down" />
-        </div>
-        <div
-          class="dropdown-content scrollbar top-px mt-16 h-[130px] max-h-96 w-52 overflow-y-auto bg-base-200 text-base-content shadow-2xl rounded-t-box rounded-b-box"
-        >
-          <div class="grid grid-cols-1 gap-3 p-3" tabindex="0">
-            {themeList.map(theme => (
-              <div
-                v-for="theme in themeList"
-                key={theme.id}
-                class={`${mode === theme.id ? "outline" : ""} overflow-hidden rounded-lg outline-2 outline-base-content outline-offset-2 hover:outline`}
-                onClick={event => changeTheme(event, theme.id)}
-              >
-                <div data-theme={theme.id} class="w-full cursor-pointer bg-base-100 text-base-content font-sans">
-                  <div class="grid grid-cols-5 grid-rows-3">
-                    <div class="col-span-5 row-span-3 row-start-1 flex gap-1 px-4 py-3">
-                      <div class="grow text-sm font-bold">
-                        { theme.id }
-                      </div>
-                    </div>
-                  </div>
-                </div>
-              </div>
-            ))}
-          </div>
-        </div>
-      </div>
-    );
-  },
-});
-
-export default ThemeChange;

+ 0 - 10
src/layouts/Navigation/components/ThemeChange/types.ts

@@ -1,10 +0,0 @@
-import type { BasicColorSchema } from "@vueuse/core";
-
-export declare type CustomTheme = "cupcake" | "bumblebee" | "emerald" | "corporate" | "synthwave"
-  | "retro" | "cyberpunk" | "valentine" | "halloween" | "garden" | "forest" | "aqua" |
-  "lofi" | "pastel" | "fantasy" | "wireframe" | "black" | "luxury" | "dracula" | "cmyk" |
-  "autumn" | "business" | "acid" | "lemonade" | "night" | "coffee" | "winter" | BasicColorSchema;
-export interface ThemeList {
-  name: string;
-  id: CustomTheme;
-}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 2
src/layouts/Navigation/index.tsx


+ 1 - 1
src/pages/home/index.tsx

@@ -14,7 +14,7 @@ const Home = defineComponent({
           <h2
             class="from-purple-600 to-gray-200 bg-gradient-to-r bg-clip-text text-4xl text-transparent font-extrabold tracking-tight dark:from-purple-600 dark:to-gray-100 lg:text-5xl md:text-5xl xl:text-5xl"
           >
-            没有什么卵用
+            ************
           </h2>
 
           <div class="mt-5">

+ 7 - 4
src/pages/number/columns.tsx

@@ -1,7 +1,8 @@
 import { type DataTableColumn, NButton, NTooltip } from "naive-ui";
 import { ControlNumber } from "~/components/number";
+import { COLLECTION_KEY } from "~/constant/key";
 
-const collection = useLocalStorage("collection", []);
+const collection = useLocalStorage(COLLECTION_KEY, []);
 
 function handleDeleteNumber(number) {
   const index = collection.value.findIndex(_number => _number === number);
@@ -13,7 +14,7 @@ export const columns: DataTableColumn[] = [
     title: "序号",
     key: "index",
     fixed: "left",
-    width: 60,
+    width: 50,
     render(row) {
       return Number(row.index) + 1;
     },
@@ -34,7 +35,9 @@ export const columns: DataTableColumn[] = [
   {
     title: "操作",
     key: "action",
-    width: 80,
+    align: "center",
+    width: 60,
+    fixed: "right",
     render(row) {
       return (
         <NTooltip
@@ -52,7 +55,7 @@ export const columns: DataTableColumn[] = [
             ),
           }}
         >
-          从我的号码中删除
+          从我的号码中移除此号码
         </NTooltip>
       );
     },

+ 73 - 0
src/pages/number/components/my-numbers.tsx

@@ -0,0 +1,73 @@
+import { NCard, NDataTable, useMessage } from "naive-ui";
+import { utils, writeFile } from "xlsx";
+import { columns } from "../columns";
+import { NumbersDrawer } from "./numbers-drawer";
+import { COLLECTION_KEY } from "~/constant/key";
+import { Button } from "~/components/button";
+import { useState } from "~/hooks/state";
+
+export const MyNumbers = defineComponent({
+  name: "MyNumbers",
+  props: {
+    winning: {
+      type: Array as PropType<string[]>,
+      default: () => ([]),
+    },
+  },
+  setup(props) {
+    const { height } = useWindowSize();
+    const collection = useLocalStorage(COLLECTION_KEY, []);
+    const [visible, onUpdateVisible] = useState(false, { shallow: true });
+    const message = useMessage();
+
+    const _collection = computed(() => collection.value.map((number: string, index) => ({
+      index,
+      numbers: number.split(","),
+      winning: props.winning,
+    })));
+
+    function handleExport() {
+      const workBook = utils.book_new();
+      if (collection.value.length === 0) {
+        message.error("没有可以导出的号码~");
+        return;
+      }
+
+      const data = collection.value.map((numbers, index) => ({
+        序号: index + 1,
+        号码: numbers,
+      }));
+
+      const workSheet = utils.json_to_sheet(data);
+      utils.book_append_sheet(workBook, workSheet, "sheet");
+
+      writeFile(workBook, "我的号码.xlsx", {
+        bookType: "xlsx",
+      });
+    }
+
+    return () => (
+      <NCard
+        size="small"
+        title="我的号码"
+        v-slots={{ "header-extra": () => (
+          <div class="flex gap-[10px]">
+            <Button onClick={() => visible.value = true}>添加</Button>
+            <Button onClick={() => handleExport()}>导出</Button>
+          </div>
+        ) }}
+      >
+        <NDataTable
+          singleColumn={false}
+          virtual-scroll={true}
+          data={_collection.value}
+          maxHeight={height.value - 360}
+          size="small"
+          columns={columns}
+        />
+
+        <NumbersDrawer visible={visible.value} onUpdate:visible={onUpdateVisible} />
+      </NCard>
+    );
+  },
+});

+ 90 - 0
src/pages/number/components/numbers-drawer.tsx

@@ -0,0 +1,90 @@
+import { NModal, useMessage } from "naive-ui";
+import { useState } from "~/hooks/state";
+
+import { ChooseNumberPanel, ControlNumber } from "~/components/number";
+import type { INumberClickOptions } from "~/composables/number";
+import { fillZero2Numbers, formatNumber } from "~/composables/number/utils";
+import { Button } from "~/components/button";
+import { COLLECTION_KEY } from "~/constant/key";
+
+export const NumbersDrawer = defineComponent({
+  name: "CreateNumber",
+  props: {
+    visible: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  emits: {
+    "update:visible": (_visible: boolean) => true,
+  },
+  setup(props, { emit }) {
+    const index = shallowRef(-1);
+    const [numbers, onUpdateNumbers] = useState<number[]>(fillZero2Numbers());
+    const [visible, onUpdateVisible] = useState(false, { shallow: true });
+    const collection = useLocalStorage<string[]>(COLLECTION_KEY, []);
+    const message = useMessage();
+
+    function handleNumberClick(options: INumberClickOptions) {
+      visible.value = true;
+      index.value = options.index;
+    }
+
+    function handleNumberChoose(number: number) {
+      numbers.value[index.value] = number;
+      if (index.value !== 6) {
+        index.value++;
+      } else {
+        visible.value = false;
+      }
+    }
+
+    function save() {
+      const _numbers = numbers.value.filter(number => Number(number) !== 0);
+
+      if (_numbers.length !== 7) {
+        message.error("填写的号码不完整");
+        return;
+      }
+
+      const next = _numbers.map(number => formatNumber(number)).join(",");
+      if (collection.value.includes(next)) {
+        message.error("号码重复");
+        return;
+      }
+
+      emit("update:visible", false);
+      collection.value.push(next);
+      message.success("添加成功");
+    }
+
+    return () => (
+      <NModal
+        show={props.visible}
+        onUpdate:show={visible => emit("update:visible", visible)}
+        onClose={() => numbers.value = fillZero2Numbers()}
+        title="添加至我的号码"
+        preset="dialog"
+      >
+        <div class="mt-[20px]">
+          <ControlNumber
+            onNumberClick={handleNumberClick}
+            onUpdate:numbers={onUpdateNumbers}
+            numbers={numbers.value}
+          />
+          <ChooseNumberPanel
+            index={index.value}
+            visible={visible.value}
+            exclude={numbers.value}
+            onUpdate:visible={onUpdateVisible}
+            onChoose={handleNumberChoose}
+          />
+
+          <div class="flex justify-end">
+            <Button onClick={save}>保存</Button>
+          </div>
+        </div>
+      </NModal>
+    );
+  },
+});

+ 0 - 33
src/pages/number/components/numbers.tsx

@@ -1,33 +0,0 @@
-import { NCard, NDataTable } from "naive-ui";
-import { columns } from "../columns";
-
-export const MyNumbers = defineComponent({
-  name: "MyNumbers",
-  props: {
-    winning: {
-      type: Array as PropType<string[]>,
-      default: () => ([]),
-    },
-  },
-  setup(props) {
-    const { height } = useWindowSize();
-    const collection = useLocalStorage("collection", []);
-
-    const _collection = computed(() => collection.value.map((number: string, index) => ({
-      index,
-      numbers: number.split(","),
-      winning: props.winning,
-    })));
-
-    return () => (
-      <NCard size="small" title="我的号码">
-        <NDataTable
-          virtual-scroll={true}
-          data={_collection.value}
-          columns={columns}
-          maxHeight={height.value - 360}
-        />
-      </NCard>
-    );
-  },
-});

+ 1 - 0
src/pages/number/components/winning-numbers.tsx

@@ -18,6 +18,7 @@ export const WinningNumbers = defineComponent({
   },
   setup(props, { emit }) {
     const [numberOfPeriods] = useState<any[]>([]);
+    // const { data, execute } = useFetch(`${PageConstant.API}?type=` + `ssq`);
 
     const [code] = useState("", { shallow: true });
     const [index] = useState(-1, { shallow: true });

+ 1 - 6
src/pages/number/index.tsx

@@ -1,16 +1,11 @@
-import { MyNumbers } from "./components/numbers";
 import { WinningNumbers } from "./components/winning-numbers";
+import { MyNumbers } from "./components/my-numbers";
 import { useState } from "~/hooks/state";
 
 const Number = defineComponent({
   name: "Number",
   setup() {
-    // const { data, execute } = useFetch(`${PageConstant.API}?type=` + `ssq`);
-    // onMounted(() => execute());
-    // const [data] = useState(response);
-
     const [winning, onUpdateWinning] = useState<string[]>([]);
-
     return () => (
       <div class="p-[10px]" style="height:calc(100% - 70px)">
         <WinningNumbers winning={winning.value} onOnUpdate:winning={onUpdateWinning} />

+ 16 - 15
src/pages/random/columns.tsx

@@ -1,7 +1,8 @@
 import { type DataTableColumn, NRate, NTag, NTooltip } from "naive-ui";
 import { ControlNumber } from "~/components/number";
+import { COLLECTION_KEY } from "~/constant/key";
 
-const collection = useLocalStorage<string[]>("collection", []);
+const collection = useLocalStorage<string[]>(COLLECTION_KEY, []);
 
 function handleCollection(isCollection: boolean, number: string) {
   if (isCollection) {
@@ -21,7 +22,7 @@ export const columns: DataTableColumn[] = [
   },
   {
     title: "收藏",
-    width: 60,
+    width: 55,
     key: "number",
     fixed: "left",
     render(row) {
@@ -38,23 +39,11 @@ export const columns: DataTableColumn[] = [
             ),
           }}
         >
-          {isCollection ? "从我的号码中移除" : "加入我的号码" }
+          {isCollection ? "从我的号码中移除此号码" : "添加至我的号码" }
         </NTooltip>
       );
     },
   },
-  {
-    title: "状态",
-    key: "isSelected",
-    width: 80,
-    render(row) {
-      return (
-        <NTag type={row.isSelected ? "success" : "error"}>
-          {row.isSelected ? "选中" : "未选中"}
-        </NTag>
-      );
-    },
-  },
   {
     title: "号码",
     minWidth: 300,
@@ -68,4 +57,16 @@ export const columns: DataTableColumn[] = [
       );
     },
   },
+  {
+    title: "状态",
+    key: "isSelected",
+    width: 80,
+    render(row) {
+      return (
+        <NTag type={row.isSelected ? "success" : "error"}>
+          {row.isSelected ? "选中" : "未选中"}
+        </NTag>
+      );
+    },
+  },
 ];

+ 23 - 25
src/pages/random/components/preference-modal.tsx

@@ -26,33 +26,31 @@ export const PreferenceModal = defineComponent({
     });
 
     return () => (
-      <NModal show={visible.value} onUpdate:show={onUpdateVisible}>
-        <NCard title="偏好设置" class="w-[600px]">
-          <NForm>
-            <NFormItem label="生成号码数量">
-              <NInputNumber
-                class="w-full"
-                value={formData.value.generatedNumbers}
-                min={10}
-                max={10000}
-                onUpdate:value={value => setFormData({ ...formData.value, generatedNumbers: value || 10 })}
-              />
-            </NFormItem>
+      <NModal show={visible.value} onUpdate:show={onUpdateVisible} preset="dialog" title="偏好设置">
+        <NForm class="mt-[20px]">
+          <NFormItem label="生成号码数量">
+            <NInputNumber
+              class="w-full"
+              value={formData.value.generatedNumbers}
+              min={10}
+              max={10000}
+              onUpdate:value={value => setFormData({ ...formData.value, generatedNumbers: value || 10 })}
+            />
+          </NFormItem>
 
-            <NFormItem label="选中的号码数量">
-              <NInputNumber
-                class="w-full"
-                value={formData.value.selectedNumbers}
-                min={1}
-                onUpdate:value={value => setFormData({ ...formData.value, selectedNumbers: value || 1 })}
-              />
-            </NFormItem>
+          <NFormItem label="选中的号码数量">
+            <NInputNumber
+              class="w-full"
+              value={formData.value.selectedNumbers}
+              min={1}
+              onUpdate:value={value => setFormData({ ...formData.value, selectedNumbers: value || 1 })}
+            />
+          </NFormItem>
 
-            <NFormItem class="flex justify-end">
-              <Button onClick={save}>保存,并重新生成</Button>
-            </NFormItem>
-          </NForm>
-        </NCard>
+          <NFormItem class="flex justify-end">
+            <Button onClick={save}>保存,并重新生成</Button>
+          </NFormItem>
+        </NForm>
       </NModal>
     );
   },

+ 2 - 1
src/pages/random/index.tsx

@@ -14,7 +14,7 @@ const Random = defineComponent({
     const [generatedNumbers] = useState<INumbers[]>([]);
     const [selectedNumbers] = useState<INumbers[]>([]);
     const [preferenceModalRef] = useState<IPreferenceModalInstance | null>(null);
-    const [isGenerated, onUpdateIsGenerated] = useState(true, { shallow: true });
+    const [isGenerated, onUpdateIsGenerated] = useState(false, { shallow: true });
     const [preferences, setPreferences] = useState<IPreferences>({ ...initalPreferences });
     const numbers = computed(() => isGenerated.value ? generatedNumbers.value : selectedNumbers.value);
     const { height } = useWindowSize();
@@ -58,6 +58,7 @@ const Random = defineComponent({
           </div>
 
           <NDataTable
+            bordered={true}
             data={numbers.value}
             columns={columns}
             virtualScroll={true}

+ 1 - 2
src/styles/number.css

@@ -6,7 +6,7 @@
   font-size: 16px;
   font-weight: bold;
   color: #FFFFFF;
-  box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2);
+  /* box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); */
   user-select: none;
   padding: 5px;
 }
@@ -21,7 +21,6 @@
 }
 
 .is-green {
-  background-color: gree;
   background:  linear-gradient(180deg, greenyellow 0%,  green 100%)
 }
 

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů