Jelajahi Sumber

merge it-tools pr #913

PR Author By@sharevb
QingFeng 4 bulan lalu
induk
melakukan
61f2f20088

+ 32 - 1
src/composable/queryParams.ts

@@ -1,7 +1,8 @@
 import { useRouteQuery } from '@vueuse/router';
 import { computed } from 'vue';
+import { useStorage } from '@vueuse/core';
 
-export { useQueryParam };
+export { useQueryParam, useQueryParamOrStorage };
 
 const transformers = {
   number: {
@@ -16,6 +17,12 @@ const transformers = {
     fromQuery: (value: string) => value.toLowerCase() === 'true',
     toQuery: (value: boolean) => (value ? 'true' : 'false'),
   },
+  object: {
+    fromQuery: (value: string) => {
+      return JSON.parse(value);
+    },
+    toQuery: (value: object) => JSON.stringify(value),
+  },
 };
 
 function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue: T }) {
@@ -33,3 +40,27 @@ function useQueryParam<T>({ name, defaultValue }: { name: string; defaultValue:
     },
   });
 }
+
+function useQueryParamOrStorage<T>({ name, storageName, defaultValue }: { name: string; storageName: string; defaultValue: T }) {
+  const type = typeof defaultValue;
+  const transformer = transformers[type as keyof typeof transformers] ?? transformers.string;
+
+  const storageRef = useStorage(storageName, defaultValue);
+  const proxyDefaultValue = transformer.toQuery(defaultValue as never);
+  const proxy = useRouteQuery(name, proxyDefaultValue);
+
+  const r = ref(defaultValue);
+
+  watch(r,
+    (value) => {
+      proxy.value = transformer.toQuery(value as never);
+      storageRef.value = value as never;
+    },
+    { deep: true });
+
+  r.value = (proxy.value && proxy.value !== proxyDefaultValue
+    ? transformer.fromQuery(proxy.value) as unknown as T
+    : storageRef.value as T) as never;
+
+  return r;
+}

+ 15 - 1
src/tools/token-generator/token-generator.service.test.ts

@@ -94,5 +94,19 @@ describe('token-generator', () => {
       expect(token).toHaveLength(256);
       expect(token).toMatch(/^[a-zA-Z]+$/);
     });
+
+    it('should generate a random string with just numbers except 1 and 2 if only withNumbers is set and deniedChars contains 1 and 2', () => {
+      const token = createToken({
+        withLowercase: false,
+        withUppercase: false,
+        withNumbers: true,
+        withSymbols: false,
+        length: 256,
+        deniedChars: '12',
+      });
+
+      expect(token).toHaveLength(256);
+      expect(token).toMatch(/^[0-9]+$/);
+    });
   });
-});
+});

+ 4 - 2
src/tools/token-generator/token-generator.service.ts

@@ -5,6 +5,7 @@ export function createToken({
   withLowercase = true,
   withNumbers = true,
   withSymbols = false,
+  deniedChars = '',
   length = 64,
   alphabet,
 }: {
@@ -12,6 +13,7 @@ export function createToken({
   withLowercase?: boolean
   withNumbers?: boolean
   withSymbols?: boolean
+  deniedChars?: string
   length?: number
   alphabet?: string
 }) {
@@ -20,7 +22,7 @@ export function createToken({
     withLowercase ? 'abcdefghijklmopqrstuvwxyz' : '',
     withNumbers ? '0123456789' : '',
     withSymbols ? '.,;:!?./-"\'#{([-|\\@)]=}*+' : '',
-  ].join(''); ;
+  ].filter(c => !(deniedChars?.includes(c))).join(''); ;
 
   return shuffleString(allAlphabet.repeat(length)).substring(0, length);
-}
+}

+ 52 - 40
src/tools/token-generator/token-generator.tool.vue

@@ -1,76 +1,88 @@
 <script setup lang="ts">
 import { createToken } from './token-generator.service';
 import { useCopy } from '@/composable/copy';
-import { useQueryParam } from '@/composable/queryParams';
+import { useQueryParamOrStorage } from '@/composable/queryParams';
 import { computedRefreshable } from '@/composable/computedRefreshable';
 
-const length = useQueryParam({ name: 'length', defaultValue: 64 });
-const withUppercase = useQueryParam({ name: 'uppercase', defaultValue: true });
-const withLowercase = useQueryParam({ name: 'lowercase', defaultValue: true });
-const withNumbers = useQueryParam({ name: 'numbers', defaultValue: true });
-const withSymbols = useQueryParam({ name: 'symbols', defaultValue: false });
+const count = useQueryParamOrStorage({ name: 'count', storageName: 'token-generator:count', defaultValue: 1 });
+const length = useQueryParamOrStorage({ name: 'length', storageName: 'token-generator:length', defaultValue: 64 });
+const withUppercase = useQueryParamOrStorage({ name: 'uppercase', storageName: 'token-generator:uppercase', defaultValue: true });
+const withLowercase = useQueryParamOrStorage({ name: 'lowercase', storageName: 'token-generator:lowercase', defaultValue: true });
+const withNumbers = useQueryParamOrStorage({ name: 'numbers', storageName: 'token-generator:numbers', defaultValue: true });
+const withSymbols = useQueryParamOrStorage({ name: 'symbols', storageName: 'token-generator:symbols', defaultValue: false });
+const deniedChars = useQueryParamOrStorage({ name: 'deny', storageName: 'token-generator:deny', defaultValue: '' });
 const { t } = useI18n();
 
-const [token, refreshToken] = computedRefreshable(() =>
-  createToken({
-    length: length.value,
-    withUppercase: withUppercase.value,
-    withLowercase: withLowercase.value,
-    withNumbers: withNumbers.value,
-    withSymbols: withSymbols.value,
-  }),
+const [tokens, refreshTokens] = computedRefreshable(() =>
+  Array.from({ length: count.value },
+    () => createToken({
+      length: length.value,
+      withUppercase: withUppercase.value,
+      withLowercase: withLowercase.value,
+      withNumbers: withNumbers.value,
+      withSymbols: withSymbols.value,
+      deniedChars: deniedChars.value,
+    })).join('\n'),
 );
 
-const { copy } = useCopy({ source: token, text: t('tools.token-generator.copied') });
+const { copy } = useCopy({ source: tokens, text: t('tools.token-generator.copyclipboard') });
 </script>
 
 <template>
   <div>
     <c-card>
       <n-form label-placement="left" label-width="140">
-        <div flex justify-center>
-          <div>
-            <n-form-item :label="t('tools.token-generator.uppercase')">
-              <n-switch v-model:value="withUppercase" />
-            </n-form-item>
+        <n-space justify="center">
+          <n-form-item :label="t('tools.token-generator.uppercase')">
+            <n-switch v-model:value="withUppercase" />
+          </n-form-item>
 
-            <n-form-item :label="t('tools.token-generator.lowercase')">
-              <n-switch v-model:value="withLowercase" />
-            </n-form-item>
-          </div>
+          <n-form-item :label="t('tools.token-generator.lowercase')">
+            <n-switch v-model:value="withLowercase" />
+          </n-form-item>
+          <n-form-item :label="t('tools.token-generator.numbers')">
+            <n-switch v-model:value="withNumbers" />
+          </n-form-item>
 
-          <div>
-            <n-form-item :label="t('tools.token-generator.numbers')">
-              <n-switch v-model:value="withNumbers" />
-            </n-form-item>
-
-            <n-form-item :label="t('tools.token-generator.symbols')">
-              <n-switch v-model:value="withSymbols" />
-            </n-form-item>
-          </div>
-        </div>
+          <n-form-item :label="t('tools.token-generator.symbols')">
+            <n-switch v-model:value="withSymbols" />
+          </n-form-item>
+        </n-space>
       </n-form>
 
+      <n-form-item label="排除字符" label-placement="left">
+        <c-input-text
+          v-model:value="deniedChars"
+          placeholder="输入需要在token中排除的字符"
+        />
+      </n-form-item>
+
       <n-form-item :label="`${t('tools.token-generator.length')} (${length})`" label-placement="left">
-        <n-slider v-model:value="length" :step="1" :min="1" :max="512" />
+        <n-slider v-model:value="length" :step="1" :min="1" :max="512" mr-2 />
+        <n-input-number v-model:value="length" size="small" />
+      </n-form-item>
+
+      <n-form-item label="Number of token to generate" label-placement="left">
+        <n-input-number v-model:value="count" size="small" />
       </n-form-item>
 
       <c-input-text
-        v-model:value="token"
+        v-model:value="tokens"
         multiline
         :placeholder="t('tools.token-generator.tokenPlaceholder')"
         readonly
         rows="3"
         autosize
         class="token-display"
+        word-wrap
       />
 
       <div mt-5 flex justify-center gap-3>
         <c-button @click="copy()">
-          {{ t('common.operate.copy') }}
+        {{ t('common.operate.copy') }}
         </c-button>
-        <c-button @click="refreshToken">
-          {{ t('common.operate.refresh') }}
+        <c-button @click="refreshTokens">
+        {{ t('common.operate.refresh') }}
         </c-button>
       </div>
     </c-card>
@@ -83,4 +95,4 @@ const { copy } = useCopy({ source: token, text: t('tools.token-generator.copied'
     text-align: center;
   }
 }
-</style>
+</style>