2 Commits
b255ff4dd2
...
652ed8a375
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
652ed8a375 |
11.5
|
3 weeks ago |
|
|
2e0082fec0 |
11.5
|
3 weeks ago |
75 changed files with 1221 additions and 1479 deletions
-
0吴光慧学习笔记/11.3/test1.vue
-
0吴光慧学习笔记/11.3/test2.vue
-
0吴光慧学习笔记/11.3/test3.vue
-
0吴光慧学习笔记/11.3/test4.vue
-
0吴光慧学习笔记/11.4/props.vue
-
0吴光慧学习笔记/11.4/test1.vue
-
0吴光慧学习笔记/11.4/test2.vue
-
BIN吴光慧学习笔记/11.5/11.5学习总结-吴光慧.docx
-
70吴光慧学习笔记/11.5/Count.txt
-
38吴光慧学习笔记/11.5/LoveTalk.txt
-
30吴光慧学习笔记/11.5/query.txt
-
2吴光慧学习笔记/11.5/备份/About.vue
-
12吴光慧学习笔记/11.5/备份/App.txt
-
30吴光慧学习笔记/11.5/备份/Detail.vue
-
10吴光慧学习笔记/11.5/备份/Home.vue
-
61吴光慧学习笔记/11.5/备份/News.txt
-
0吴光慧学习笔记/11.5/备份/hooks.vue
-
21吴光慧学习笔记/11.5/备份/index.ts
-
42吴光慧学习笔记/11.5/备份/router/index.ts
-
BIN吴光慧学习笔记/11.5/道氏理论定义及基本观点.docx
-
4吴光慧学习笔记/hello_vue3/node_modules/.bin/nanoid
-
2吴光慧学习笔记/hello_vue3/node_modules/.bin/nanoid.cmd
-
8吴光慧学习笔记/hello_vue3/node_modules/.bin/nanoid.ps1
-
121吴光慧学习笔记/hello_vue3/node_modules/.package-lock.json
-
31吴光慧学习笔记/hello_vue3/node_modules/.vite/deps/_metadata.json
-
1吴光慧学习笔记/hello_vue3/node_modules/.vite/deps/vue.js
-
16吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/.bin/nanoid
-
17吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/.bin/nanoid.cmd
-
28吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/.bin/nanoid.ps1
-
20吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/LICENSE
-
38吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/README.md
-
45吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/bin/nanoid.js
-
29吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/index.browser.js
-
106吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/index.d.ts
-
47吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/index.js
-
1吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/nanoid.js
-
48吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/non-secure/index.d.ts
-
21吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/non-secure/index.js
-
43吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/package.json
-
2吴光慧学习笔记/hello_vue3/node_modules/@vue/devtools-core/node_modules/nanoid/url-alphabet/index.js
-
17吴光慧学习笔记/hello_vue3/node_modules/nanoid/README.md
-
69吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.browser.cjs
-
34吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.browser.js
-
71吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.cjs
-
56吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.d.ts
-
35吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.js
-
26吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/index.native.js
-
12吴光慧学习笔记/hello_vue3/node_modules/nanoid/async/package.json
-
55吴光慧学习笔记/hello_vue3/node_modules/nanoid/bin/nanoid.cjs
-
72吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.browser.cjs
-
39吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.browser.js
-
85吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.cjs
-
91吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.d.cts
-
25吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.d.ts
-
28吴光慧学习笔记/hello_vue3/node_modules/nanoid/index.js
-
2吴光慧学习笔记/hello_vue3/node_modules/nanoid/nanoid.js
-
34吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/index.cjs
-
23吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/index.d.ts
-
6吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/index.js
-
6吴光慧学习笔记/hello_vue3/node_modules/nanoid/non-secure/package.json
-
82吴光慧学习笔记/hello_vue3/node_modules/nanoid/package.json
-
7吴光慧学习笔记/hello_vue3/node_modules/nanoid/url-alphabet/index.cjs
-
3吴光慧学习笔记/hello_vue3/node_modules/nanoid/url-alphabet/index.js
-
6吴光慧学习笔记/hello_vue3/node_modules/nanoid/url-alphabet/package.json
-
125吴光慧学习笔记/hello_vue3/package-lock.json
-
4吴光慧学习笔记/hello_vue3/package.json
-
71吴光慧学习笔记/hello_vue3/src/App.vue
-
47吴光慧学习笔记/hello_vue3/src/components/Count.vue
-
45吴光慧学习笔记/hello_vue3/src/components/LoveTalk.vue
-
34吴光慧学习笔记/hello_vue3/src/components/News.vue
-
48吴光慧学习笔记/hello_vue3/src/components/Person.vue
-
13吴光慧学习笔记/hello_vue3/src/main.ts
-
29吴光慧学习笔记/hello_vue3/src/store/count.ts
-
22吴光慧学习笔记/hello_vue3/src/store/loveTalk.ts
-
434股票知识评测系统.vue
@ -0,0 +1,70 @@ |
|||||
|
<template> |
||||
|
<div class="count"> |
||||
|
<h2>当前求和为:{{ countStore.sum }}</h2> |
||||
|
<select v-model.number="n"> |
||||
|
<option value="1">1</option> |
||||
|
<option value="2">2</option> |
||||
|
<option value="3">3</option> |
||||
|
</select> |
||||
|
<button @click="add">加</button> |
||||
|
<button @click="minus">减</button> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="Count"> |
||||
|
import { ref,reactive } from "vue"; |
||||
|
import {useCountStore} from '@/store/count' |
||||
|
|
||||
|
const countStore = useCountStore() |
||||
|
|
||||
|
// 以下两种方式都可以拿到state中的数据 |
||||
|
// console.log('@@@',countStore.sum) |
||||
|
// console.log('@@@',countStore.$state.sum) |
||||
|
|
||||
|
/* let obj = reactive({ |
||||
|
a:1, |
||||
|
b:2, |
||||
|
c:ref(3) |
||||
|
}) |
||||
|
let x = ref(9) |
||||
|
console.log(obj.a) |
||||
|
console.log(obj.b) |
||||
|
console.log(obj.c) */ |
||||
|
|
||||
|
|
||||
|
// 数据 |
||||
|
let n = ref(1) // 用户选择的数字 |
||||
|
// 方法 |
||||
|
function add(){ |
||||
|
//第一种修改方式 |
||||
|
// countStore.sum += 1 |
||||
|
|
||||
|
//第二种修改方式 |
||||
|
// countStore.$patch({ |
||||
|
// sum:999, |
||||
|
// school:'xxx', |
||||
|
// address:'yyy' |
||||
|
// }) |
||||
|
|
||||
|
//第三种修改方式 |
||||
|
countStore.increment(n.value) |
||||
|
} |
||||
|
|
||||
|
function minus(){ |
||||
|
countStore.decrement(n.value) |
||||
|
|
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.count { |
||||
|
background-color: skyblue; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
select,button { |
||||
|
margin: 0 5px; |
||||
|
height: 25px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,38 @@ |
|||||
|
<template> |
||||
|
<div class="talk"> |
||||
|
<button @click="getLoveTalk">获取一句土味情话</button> |
||||
|
<ul> |
||||
|
<li v-for="talk in talkList" :key="talk.id">{{talk.title}}</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="LoveTalk"> |
||||
|
import {reactive} from 'vue' |
||||
|
import axios from "axios"; |
||||
|
import {nanoid} from 'nanoid' |
||||
|
// 数据 |
||||
|
let talkList = reactive([ |
||||
|
{id:'ftrfasdf01',title:'今天你有点怪,哪里怪?怪好看的!'}, |
||||
|
{id:'ftrfasdf02',title:'草莓、蓝莓、蔓越莓,今天想我了没?'}, |
||||
|
{id:'ftrfasdf03',title:'心里给你留了一块地,我的死心塌地'} |
||||
|
]) |
||||
|
// 方法 |
||||
|
async function getLoveTalk(){ |
||||
|
// 发请求,下面这行的写法是:连续解构赋值+重命名 |
||||
|
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') |
||||
|
// 把请求回来的字符串,包装成一个对象 |
||||
|
let obj = {id:nanoid(),title} |
||||
|
// 放到数组中 |
||||
|
talkList.unshift(obj) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.talk { |
||||
|
background-color: orange; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,30 @@ |
|||||
|
Detail |
||||
|
<template> |
||||
|
<ul class="news-list"> |
||||
|
<li>编号:{{route.query.id}}</li> |
||||
|
<li>标题:{{route.query.title}}</li> |
||||
|
<li>内容:{{route.query.content}}</li> |
||||
|
</ul> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup name="Detail"> |
||||
|
import {useRoute} from 'vue-router' |
||||
|
const route = useRoute() |
||||
|
const {query} = route |
||||
|
// 这样上面就可以写 <li>内容:{{query.content}}</li> |
||||
|
// 打印query参数 |
||||
|
console.log(route.query) |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.news-list{ |
||||
|
list-style: none; |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
.news-list>li{ |
||||
|
line-height: 30px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,12 @@ |
|||||
|
<template> |
||||
|
<Count/> |
||||
|
<br> |
||||
|
<LoveTalk/> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="App"> |
||||
|
import Count from './components/Count.vue' |
||||
|
import LoveTalk from './components/LoveTalk.vue' |
||||
|
</script> |
||||
|
<style scoped> |
||||
|
</style> |
||||
@ -0,0 +1,30 @@ |
|||||
|
<template> |
||||
|
<ul class="news-list"> |
||||
|
<li>编号:{{id}}</li> |
||||
|
<li>标题:{{title}}</li> |
||||
|
<li>内容:{{content}}</li> |
||||
|
</ul> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup name="Detail"> |
||||
|
// import {useRoute} from 'vue-router' |
||||
|
// const route = useRoute() |
||||
|
// const {query} = route |
||||
|
// // 这样上面就可以写 <li>内容:{{query.content}}</li> |
||||
|
// // 打印query参数 |
||||
|
// console.log(route.query) |
||||
|
|
||||
|
defineProps(['id','title','content']) |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.news-list{ |
||||
|
list-style: none; |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
.news-list>li{ |
||||
|
line-height: 30px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,61 @@ |
|||||
|
<template> |
||||
|
<div class="news"> |
||||
|
<!--导航区--> |
||||
|
<ul> |
||||
|
<li v-for="news in newsList" :key="news.id"> |
||||
|
<!--第一种写法--> |
||||
|
<!-- <RouterLink :to="`/news/detail?id=${news.id} |
||||
|
&title=${news.title} |
||||
|
&content=${news.content}`">{{news.title}}</RouterLink> --> |
||||
|
|
||||
|
<!-- 第二种写法 --> |
||||
|
<RouterLink |
||||
|
:to="{ |
||||
|
path: '/news/detail', |
||||
|
query: { |
||||
|
id: news.id, |
||||
|
title: news.title, |
||||
|
content: news.content}}"> |
||||
|
{{news.title}} |
||||
|
</RouterLink> |
||||
|
</li> |
||||
|
</ul> |
||||
|
<!--内容区--> |
||||
|
<div class="news-content"> |
||||
|
<RouterView></RouterView> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts" setup name="News"> |
||||
|
import { reactive } from 'vue'; |
||||
|
import { RouterLink, RouterView } from 'vue-router'; |
||||
|
|
||||
|
const newsList = reactive([ |
||||
|
{id: 1, title: '新闻1',content: '新闻1的内容'}, |
||||
|
{id: 2, title: '新闻2',content: '新闻2的内容'}, |
||||
|
{id: 3, title: '新闻3',content: '新闻3的内容'}, |
||||
|
{id: 4, title: '新闻4',content: '新闻4的内容'}, |
||||
|
]) |
||||
|
|
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.news { |
||||
|
padding: 0 20px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
height: 100px; |
||||
|
} |
||||
|
.news ul { |
||||
|
margin-top: 30px; |
||||
|
/* list-style: none; */ |
||||
|
padding-left: 10px; |
||||
|
} |
||||
|
.news li>a{ |
||||
|
text-decoration: none; |
||||
|
color: #000; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,42 @@ |
|||||
|
import {createRouter,createWebHistory} from 'vue-router' |
||||
|
import Home from '@/components/Home.vue' |
||||
|
import News from '@/components/News.vue' |
||||
|
import About from '@/components/About.vue' |
||||
|
import Detail from '@/components/Detail.vue' |
||||
|
|
||||
|
const router = createRouter({ |
||||
|
history:createWebHistory(), |
||||
|
routes:[ |
||||
|
{ |
||||
|
name:'zhuye', |
||||
|
path:'/home', |
||||
|
component:Home |
||||
|
}, |
||||
|
{ |
||||
|
name:'xinwen', |
||||
|
path:'/news', |
||||
|
component:News, |
||||
|
children:[ |
||||
|
{ |
||||
|
name:'xiangqing', |
||||
|
path:'detail/:id/:title/:content', |
||||
|
component:Detail, |
||||
|
props(route){ |
||||
|
return route.query |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
}, |
||||
|
{ |
||||
|
name:'guanyu', |
||||
|
path:'/about', |
||||
|
component:About |
||||
|
}, |
||||
|
{ |
||||
|
path:'/', |
||||
|
redirect:'/home' |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export default router |
||||
@ -1,25 +1,46 @@ |
|||||
{ |
{ |
||||
"hash": "547bafd9", |
|
||||
|
"hash": "39b4f1a2", |
||||
"configHash": "e5b55508", |
"configHash": "e5b55508", |
||||
"lockfileHash": "0d539b07", |
|
||||
"browserHash": "2bc9fe4f", |
|
||||
|
"lockfileHash": "22fdfde4", |
||||
|
"browserHash": "b44b993f", |
||||
"optimized": { |
"optimized": { |
||||
|
"pinia": { |
||||
|
"src": "../../pinia/dist/pinia.mjs", |
||||
|
"file": "pinia.js", |
||||
|
"fileHash": "a97780a2", |
||||
|
"needsInterop": false |
||||
|
}, |
||||
"vue": { |
"vue": { |
||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js", |
"src": "../../vue/dist/vue.runtime.esm-bundler.js", |
||||
"file": "vue.js", |
"file": "vue.js", |
||||
"fileHash": "5b2b4475", |
|
||||
|
"fileHash": "1e96b106", |
||||
"needsInterop": false |
"needsInterop": false |
||||
}, |
}, |
||||
"vue-router": { |
"vue-router": { |
||||
"src": "../../vue-router/dist/vue-router.mjs", |
"src": "../../vue-router/dist/vue-router.mjs", |
||||
"file": "vue-router.js", |
"file": "vue-router.js", |
||||
"fileHash": "832988d5", |
|
||||
|
"fileHash": "0eced000", |
||||
|
"needsInterop": false |
||||
|
}, |
||||
|
"axios": { |
||||
|
"src": "../../axios/index.js", |
||||
|
"file": "axios.js", |
||||
|
"fileHash": "0965a06b", |
||||
|
"needsInterop": false |
||||
|
}, |
||||
|
"nanoid": { |
||||
|
"src": "../../nanoid/index.browser.js", |
||||
|
"file": "nanoid.js", |
||||
|
"fileHash": "39507410", |
||||
"needsInterop": false |
"needsInterop": false |
||||
} |
} |
||||
}, |
}, |
||||
"chunks": { |
"chunks": { |
||||
"chunk-SFGEOVP2": { |
"chunk-SFGEOVP2": { |
||||
"file": "chunk-SFGEOVP2.js" |
"file": "chunk-SFGEOVP2.js" |
||||
|
}, |
||||
|
"chunk-PZ5AY32C": { |
||||
|
"file": "chunk-PZ5AY32C.js" |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,16 +0,0 @@ |
|||||
#!/bin/sh |
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") |
|
||||
|
|
||||
case `uname` in |
|
||||
*CYGWIN*|*MINGW*|*MSYS*) |
|
||||
if command -v cygpath > /dev/null 2>&1; then |
|
||||
basedir=`cygpath -w "$basedir"` |
|
||||
fi |
|
||||
;; |
|
||||
esac |
|
||||
|
|
||||
if [ -x "$basedir/node" ]; then |
|
||||
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.js" "$@" |
|
||||
else |
|
||||
exec node "$basedir/../nanoid/bin/nanoid.js" "$@" |
|
||||
fi |
|
||||
@ -1,17 +0,0 @@ |
|||||
@ECHO off |
|
||||
GOTO start |
|
||||
:find_dp0 |
|
||||
SET dp0=%~dp0 |
|
||||
EXIT /b |
|
||||
:start |
|
||||
SETLOCAL |
|
||||
CALL :find_dp0 |
|
||||
|
|
||||
IF EXIST "%dp0%\node.exe" ( |
|
||||
SET "_prog=%dp0%\node.exe" |
|
||||
) ELSE ( |
|
||||
SET "_prog=node" |
|
||||
SET PATHEXT=%PATHEXT:;.JS;=;% |
|
||||
) |
|
||||
|
|
||||
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.js" %* |
|
||||
@ -1,28 +0,0 @@ |
|||||
#!/usr/bin/env pwsh |
|
||||
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent |
|
||||
|
|
||||
$exe="" |
|
||||
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { |
|
||||
# Fix case when both the Windows and Linux builds of Node |
|
||||
# are installed in the same directory |
|
||||
$exe=".exe" |
|
||||
} |
|
||||
$ret=0 |
|
||||
if (Test-Path "$basedir/node$exe") { |
|
||||
# Support pipeline input |
|
||||
if ($MyInvocation.ExpectingInput) { |
|
||||
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} else { |
|
||||
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} |
|
||||
$ret=$LASTEXITCODE |
|
||||
} else { |
|
||||
# Support pipeline input |
|
||||
if ($MyInvocation.ExpectingInput) { |
|
||||
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} else { |
|
||||
& "node$exe" "$basedir/../nanoid/bin/nanoid.js" $args |
|
||||
} |
|
||||
$ret=$LASTEXITCODE |
|
||||
} |
|
||||
exit $ret |
|
||||
@ -1,20 +0,0 @@ |
|||||
The MIT License (MIT) |
|
||||
|
|
||||
Copyright 2017 Andrey Sitnik <andrey@sitnik.ru> |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
||||
this software and associated documentation files (the "Software"), to deal in |
|
||||
the Software without restriction, including without limitation the rights to |
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
|
||||
the Software, and to permit persons to whom the Software is furnished to do so, |
|
||||
subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in all |
|
||||
copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
||||
@ -1,38 +0,0 @@ |
|||||
# Nano ID |
|
||||
|
|
||||
<img src="https://ai.github.io/nanoid/logo.svg" align="right" |
|
||||
alt="Nano ID logo by Anton Lovchikov" width="180" height="94"> |
|
||||
|
|
||||
**English** | [日本語](./README.ja.md) | [Русский](./README.ru.md) | [简体中文](./README.zh-CN.md) | [Bahasa Indonesia](./README.id-ID.md) | [한국어](./README.ko.md) |
|
||||
|
|
||||
A tiny, secure, URL-friendly, unique string ID generator for JavaScript. |
|
||||
|
|
||||
> “An amazing level of senseless perfectionism, |
|
||||
> which is simply impossible not to respect.” |
|
||||
|
|
||||
* **Small.** 118 bytes (minified and brotlied). No dependencies. |
|
||||
[Size Limit] controls the size. |
|
||||
* **Safe.** It uses hardware random generator. Can be used in clusters. |
|
||||
* **Short IDs.** It uses a larger alphabet than UUID (`A-Za-z0-9_-`). |
|
||||
So ID size was reduced from 36 to 21 symbols. |
|
||||
* **Portable.** Nano ID was ported |
|
||||
to over [20 programming languages](./README.md#other-programming-languages). |
|
||||
|
|
||||
```js |
|
||||
import { nanoid } from 'nanoid' |
|
||||
model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT" |
|
||||
``` |
|
||||
|
|
||||
--- |
|
||||
|
|
||||
<img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="" width="22" height="16" /> Made at <b><a href="https://evilmartians.com/devtools?utm_source=nanoid&utm_campaign=devtools-button&utm_medium=github">Evil Martians</a></b>, product consulting for <b>developer tools</b>. |
|
||||
|
|
||||
--- |
|
||||
|
|
||||
[online tool]: https://gitpod.io/#https://github.com/ai/nanoid/ |
|
||||
[with Babel]: https://developer.epages.com/blog/coding/how-to-transpile-node-modules-with-babel-and-webpack-in-a-monorepo/ |
|
||||
[Size Limit]: https://github.com/ai/size-limit |
|
||||
|
|
||||
|
|
||||
## Docs |
|
||||
Read full docs **[here](https://github.com/ai/nanoid#readme)**. |
|
||||
@ -1,45 +0,0 @@ |
|||||
#!/usr/bin/env node
|
|
||||
import { customAlphabet, nanoid } from '../index.js' |
|
||||
function print(msg) { |
|
||||
process.stdout.write(msg + '\n') |
|
||||
} |
|
||||
function error(msg) { |
|
||||
process.stderr.write(msg + '\n') |
|
||||
process.exit(1) |
|
||||
} |
|
||||
if (process.argv.includes('--help') || process.argv.includes('-h')) { |
|
||||
print(`Usage
|
|
||||
$ nanoid [options] |
|
||||
Options |
|
||||
-s, --size Generated ID size |
|
||||
-a, --alphabet Alphabet to use |
|
||||
-h, --help Show this help |
|
||||
Examples |
|
||||
$ nanoid -s 15 |
|
||||
S9sBF77U6sDB8Yg |
|
||||
$ nanoid --size 10 --alphabet abc |
|
||||
bcabababca`)
|
|
||||
process.exit() |
|
||||
} |
|
||||
let alphabet, size |
|
||||
for (let i = 2; i < process.argv.length; i++) { |
|
||||
let arg = process.argv[i] |
|
||||
if (arg === '--size' || arg === '-s') { |
|
||||
size = Number(process.argv[i + 1]) |
|
||||
i += 1 |
|
||||
if (Number.isNaN(size) || size <= 0) { |
|
||||
error('Size must be positive integer') |
|
||||
} |
|
||||
} else if (arg === '--alphabet' || arg === '-a') { |
|
||||
alphabet = process.argv[i + 1] |
|
||||
i += 1 |
|
||||
} else { |
|
||||
error('Unknown argument ' + arg) |
|
||||
} |
|
||||
} |
|
||||
if (alphabet) { |
|
||||
let customNanoid = customAlphabet(alphabet, size) |
|
||||
print(customNanoid()) |
|
||||
} else { |
|
||||
print(nanoid(size)) |
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
/* @ts-self-types="./index.d.ts" */ |
|
||||
import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js' |
|
||||
export { urlAlphabet } from './url-alphabet/index.js' |
|
||||
export let random = bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
export let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
let mask = (2 << Math.log2(alphabet.length - 1)) - 1 |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
let j = step | 0 |
|
||||
while (j--) { |
|
||||
id += alphabet[bytes[j] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
export let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size | 0, random) |
|
||||
export let nanoid = (size = 21) => { |
|
||||
let id = '' |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array((size |= 0))) |
|
||||
while (size--) { |
|
||||
id += scopedUrlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
@ -1,106 +0,0 @@ |
|||||
/** |
|
||||
* A tiny, secure, URL-friendly, unique string ID generator for JavaScript |
|
||||
* with hardware random generator. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid' |
|
||||
* model.id = nanoid() //=> "V1StGXR8_Z5jdHi6B-myT"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @module |
|
||||
*/ |
|
||||
|
|
||||
/** |
|
||||
* Generate secure URL-friendly unique ID. |
|
||||
* |
|
||||
* By default, the ID will have 21 symbols to have a collision probability |
|
||||
* similar to UUID v4. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid' |
|
||||
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string. |
|
||||
*/ |
|
||||
export function nanoid<Type extends string>(size?: number): Type |
|
||||
|
|
||||
/** |
|
||||
* Generate secure unique ID with custom alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string generator. |
|
||||
* |
|
||||
* ```js
|
|
||||
* const { customAlphabet } = require('nanoid') |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* nanoid() //=> "8ё56а"
|
|
||||
* ```
|
|
||||
*/ |
|
||||
export function customAlphabet<Type extends string>( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => Type |
|
||||
|
|
||||
/** |
|
||||
* Generate unique ID with custom random generator and alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customRandom } from 'nanoid/format' |
|
||||
* |
|
||||
* const nanoid = customRandom('abcdef', 5, size => { |
|
||||
* const random = [] |
|
||||
* for (let i = 0; i < size; i++) { |
|
||||
* random.push(randomByte()) |
|
||||
* } |
|
||||
* return random |
|
||||
* }) |
|
||||
* |
|
||||
* nanoid() //=> "fbaef"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate a random string. |
|
||||
* @param size Size of the random string. |
|
||||
* @param random A random bytes generator. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string generator. |
|
||||
*/ |
|
||||
export function customRandom<Type extends string>( |
|
||||
alphabet: string, |
|
||||
size: number, |
|
||||
random: (bytes: number) => Uint8Array |
|
||||
): () => Type |
|
||||
|
|
||||
/** |
|
||||
* URL safe symbols. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { urlAlphabet } from 'nanoid' |
|
||||
* const nanoid = customAlphabet(urlAlphabet, 10) |
|
||||
* nanoid() //=> "Uakgb_J5m9"
|
|
||||
* ```
|
|
||||
*/ |
|
||||
export const urlAlphabet: string |
|
||||
|
|
||||
/** |
|
||||
* Generate an array of random bytes collected from hardware noise. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customRandom, random } from 'nanoid' |
|
||||
* const nanoid = customRandom("abcdef", 5, random) |
|
||||
* ```
|
|
||||
* |
|
||||
* @param bytes Size of the array. |
|
||||
* @returns An array of random bytes. |
|
||||
*/ |
|
||||
export function random(bytes: number): Uint8Array |
|
||||
@ -1,47 +0,0 @@ |
|||||
import { webcrypto as crypto } from 'node:crypto' |
|
||||
import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js' |
|
||||
export { urlAlphabet } from './url-alphabet/index.js' |
|
||||
const POOL_SIZE_MULTIPLIER = 128 |
|
||||
let pool, poolOffset |
|
||||
function fillPool(bytes) { |
|
||||
if (!pool || pool.length < bytes) { |
|
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
|
||||
crypto.getRandomValues(pool) |
|
||||
poolOffset = 0 |
|
||||
} else if (poolOffset + bytes > pool.length) { |
|
||||
crypto.getRandomValues(pool) |
|
||||
poolOffset = 0 |
|
||||
} |
|
||||
poolOffset += bytes |
|
||||
} |
|
||||
export function random(bytes) { |
|
||||
fillPool((bytes |= 0)) |
|
||||
return pool.subarray(poolOffset - bytes, poolOffset) |
|
||||
} |
|
||||
export function customRandom(alphabet, defaultSize, getRandom) { |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
return (size = defaultSize) => { |
|
||||
if (!size) return '' |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
export function customAlphabet(alphabet, size = 21) { |
|
||||
return customRandom(alphabet, size, random) |
|
||||
} |
|
||||
export function nanoid(size = 21) { |
|
||||
fillPool((size |= 0)) |
|
||||
let id = '' |
|
||||
for (let i = poolOffset - size; i < poolOffset; i++) { |
|
||||
id += scopedUrlAlphabet[pool[i] & 63] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
@ -1 +0,0 @@ |
|||||
let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";export let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e));for(let n=0;n<e;n++)t+=a[63&r[n]];return t}; |
|
||||
@ -1,48 +0,0 @@ |
|||||
/** |
|
||||
* By default, Nano ID uses hardware random bytes generation for security |
|
||||
* and low collision probability. If you are not so concerned with security, |
|
||||
* you can use it for environments without hardware random generators. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid/non-secure' |
|
||||
* const id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqLJ"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @module |
|
||||
*/ |
|
||||
|
|
||||
/** |
|
||||
* Generate URL-friendly unique ID. This method uses the non-secure |
|
||||
* predictable random generator with bigger collision probability. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid/non-secure' |
|
||||
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL"
|
|
||||
* ```
|
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string. |
|
||||
*/ |
|
||||
export function nanoid<Type extends string>(size?: number): Type |
|
||||
|
|
||||
/** |
|
||||
* Generate a unique ID based on a custom alphabet. |
|
||||
* This method uses the non-secure predictable random generator |
|
||||
* with bigger collision probability. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @typeparam Type The ID type to replace `string` with some opaque type. |
|
||||
* @returns A random string generator. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customAlphabet } from 'nanoid/non-secure' |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* model.id = nanoid() //=> "8ё56а"
|
|
||||
* ```
|
|
||||
*/ |
|
||||
export function customAlphabet<Type extends string>( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => Type |
|
||||
@ -1,21 +0,0 @@ |
|||||
/* @ts-self-types="./index.d.ts" */ |
|
||||
let urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
export let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
id += alphabet[(Math.random() * alphabet.length) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
} |
|
||||
export let nanoid = (size = 21) => { |
|
||||
let id = '' |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
id += urlAlphabet[(Math.random() * 64) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
@ -1,43 +0,0 @@ |
|||||
{ |
|
||||
"name": "nanoid", |
|
||||
"version": "5.1.6", |
|
||||
"description": "A tiny (118 bytes), secure URL-friendly unique string ID generator", |
|
||||
"keywords": [ |
|
||||
"uuid", |
|
||||
"random", |
|
||||
"id", |
|
||||
"url" |
|
||||
], |
|
||||
"type": "module", |
|
||||
"engines": { |
|
||||
"node": "^18 || >=20" |
|
||||
}, |
|
||||
"funding": [ |
|
||||
{ |
|
||||
"type": "github", |
|
||||
"url": "https://github.com/sponsors/ai" |
|
||||
} |
|
||||
], |
|
||||
"author": "Andrey Sitnik <andrey@sitnik.ru>", |
|
||||
"license": "MIT", |
|
||||
"repository": "ai/nanoid", |
|
||||
"exports": { |
|
||||
".": { |
|
||||
"types": "./index.d.ts", |
|
||||
"browser": "./index.browser.js", |
|
||||
"react-native": "./index.browser.js", |
|
||||
"default": "./index.js" |
|
||||
}, |
|
||||
"./non-secure": "./non-secure/index.js", |
|
||||
"./package.json": "./package.json" |
|
||||
}, |
|
||||
"browser": { |
|
||||
"./index.js": "./index.browser.js" |
|
||||
}, |
|
||||
"react-native": { |
|
||||
"./index.js": "./index.browser.js" |
|
||||
}, |
|
||||
"bin": "./bin/nanoid.js", |
|
||||
"sideEffects": false, |
|
||||
"types": "./index.d.ts" |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
export const urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
@ -1,69 +0,0 @@ |
|||||
let random = async bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
|
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
// `Math.clz32` is not used, because it is not available in browsers. |
|
||||
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
|
|
||||
// `-~f => Math.ceil(f)` if f is a float |
|
||||
// `-~i => i + 1` if i is an integer |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
return async (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array(step)) |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = step | 0 |
|
||||
while (i--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let nanoid = async (size = 21) => { |
|
||||
let id = '' |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array((size |= 0))) |
|
||||
|
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
while (size--) { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
let byte = bytes[size] & 63 |
|
||||
if (byte < 36) { |
|
||||
// `0-9a-z` |
|
||||
id += byte.toString(36) |
|
||||
} else if (byte < 62) { |
|
||||
// `A-Z` |
|
||||
id += (byte - 26).toString(36).toUpperCase() |
|
||||
} else if (byte < 63) { |
|
||||
id += '_' |
|
||||
} else { |
|
||||
id += '-' |
|
||||
} |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, random } |
|
||||
@ -1,34 +0,0 @@ |
|||||
let random = async bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1 |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
return async (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array(step)) |
|
||||
let i = step | 0 |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
let nanoid = async (size = 21) => { |
|
||||
let id = '' |
|
||||
let bytes = crypto.getRandomValues(new Uint8Array((size |= 0))) |
|
||||
while (size--) { |
|
||||
let byte = bytes[size] & 63 |
|
||||
if (byte < 36) { |
|
||||
id += byte.toString(36) |
|
||||
} else if (byte < 62) { |
|
||||
id += (byte - 26).toString(36).toUpperCase() |
|
||||
} else if (byte < 63) { |
|
||||
id += '_' |
|
||||
} else { |
|
||||
id += '-' |
|
||||
} |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
export { nanoid, customAlphabet, random } |
|
||||
@ -1,71 +0,0 @@ |
|||||
let crypto = require('crypto') |
|
||||
|
|
||||
let { urlAlphabet } = require('../url-alphabet/index.cjs') |
|
||||
|
|
||||
// `crypto.randomFill()` is a little faster than `crypto.randomBytes()`, |
|
||||
// because it is possible to use in combination with `Buffer.allocUnsafe()`. |
|
||||
let random = bytes => |
|
||||
new Promise((resolve, reject) => { |
|
||||
// `Buffer.allocUnsafe()` is faster because it doesn’t flush the memory. |
|
||||
// Memory flushing is unnecessary since the buffer allocation itself resets |
|
||||
// the memory with the new bytes. |
|
||||
crypto.randomFill(Buffer.allocUnsafe(bytes), (err, buf) => { |
|
||||
if (err) { |
|
||||
reject(err) |
|
||||
} else { |
|
||||
resolve(buf) |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
let tick = (id, size = defaultSize) => |
|
||||
random(step).then(bytes => { |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
return tick(id, size) |
|
||||
}) |
|
||||
|
|
||||
return size => tick('', size) |
|
||||
} |
|
||||
|
|
||||
let nanoid = (size = 21) => |
|
||||
random((size |= 0)).then(bytes => { |
|
||||
let id = '' |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
while (size--) { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
id += urlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
}) |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, random } |
|
||||
@ -1,56 +0,0 @@ |
|||||
/** |
|
||||
* Generate secure URL-friendly unique ID. The non-blocking version. |
|
||||
* |
|
||||
* By default, the ID will have 21 symbols to have a collision probability |
|
||||
* similar to UUID v4. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { nanoid } from 'nanoid/async' |
|
||||
* nanoid().then(id => { |
|
||||
* model.id = id |
|
||||
* }) |
|
||||
* ```
|
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @returns A promise with a random string. |
|
||||
*/ |
|
||||
export function nanoid(size?: number): Promise<string> |
|
||||
|
|
||||
/** |
|
||||
* A low-level function. |
|
||||
* Generate secure unique ID with custom alphabet. The non-blocking version. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @returns A function that returns a promise with a random string. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { customAlphabet } from 'nanoid/async' |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* nanoid().then(id => { |
|
||||
* model.id = id //=> "8ё56а"
|
|
||||
* }) |
|
||||
* ```
|
|
||||
*/ |
|
||||
export function customAlphabet( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => Promise<string> |
|
||||
|
|
||||
/** |
|
||||
* Generate an array of random bytes collected from hardware noise. |
|
||||
* |
|
||||
* ```js
|
|
||||
* import { random } from 'nanoid/async' |
|
||||
* random(5).then(bytes => { |
|
||||
* bytes //=> [10, 67, 212, 67, 89]
|
|
||||
* }) |
|
||||
* ```
|
|
||||
* |
|
||||
* @param bytes Size of the array. |
|
||||
* @returns A promise with a random bytes array. |
|
||||
*/ |
|
||||
export function random(bytes: number): Promise<Uint8Array> |
|
||||
@ -1,35 +0,0 @@ |
|||||
import crypto from 'crypto' |
|
||||
import { urlAlphabet } from '../url-alphabet/index.js' |
|
||||
let random = bytes => |
|
||||
new Promise((resolve, reject) => { |
|
||||
crypto.randomFill(Buffer.allocUnsafe(bytes), (err, buf) => { |
|
||||
if (err) { |
|
||||
reject(err) |
|
||||
} else { |
|
||||
resolve(buf) |
|
||||
} |
|
||||
}) |
|
||||
}) |
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
let tick = (id, size = defaultSize) => |
|
||||
random(step).then(bytes => { |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
return tick(id, size) |
|
||||
}) |
|
||||
return size => tick('', size) |
|
||||
} |
|
||||
let nanoid = (size = 21) => |
|
||||
random((size |= 0)).then(bytes => { |
|
||||
let id = '' |
|
||||
while (size--) { |
|
||||
id += urlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
}) |
|
||||
export { nanoid, customAlphabet, random } |
|
||||
@ -1,26 +0,0 @@ |
|||||
import { getRandomBytesAsync } from 'expo-random' |
|
||||
import { urlAlphabet } from '../url-alphabet/index.js' |
|
||||
let random = getRandomBytesAsync |
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
let tick = (id, size = defaultSize) => |
|
||||
random(step).then(bytes => { |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length >= size) return id |
|
||||
} |
|
||||
return tick(id, size) |
|
||||
}) |
|
||||
return size => tick('', size) |
|
||||
} |
|
||||
let nanoid = (size = 21) => |
|
||||
random((size |= 0)).then(bytes => { |
|
||||
let id = '' |
|
||||
while (size--) { |
|
||||
id += urlAlphabet[bytes[size] & 63] |
|
||||
} |
|
||||
return id |
|
||||
}) |
|
||||
export { nanoid, customAlphabet, random } |
|
||||
@ -1,12 +0,0 @@ |
|||||
{ |
|
||||
"type": "module", |
|
||||
"main": "index.cjs", |
|
||||
"module": "index.js", |
|
||||
"react-native": { |
|
||||
"./index.js": "./index.native.js" |
|
||||
}, |
|
||||
"browser": { |
|
||||
"./index.js": "./index.browser.js", |
|
||||
"./index.cjs": "./index.browser.cjs" |
|
||||
} |
|
||||
} |
|
||||
@ -1,55 +0,0 @@ |
|||||
#!/usr/bin/env node |
|
||||
|
|
||||
let { nanoid, customAlphabet } = require('..') |
|
||||
|
|
||||
function print(msg) { |
|
||||
process.stdout.write(msg + '\n') |
|
||||
} |
|
||||
|
|
||||
function error(msg) { |
|
||||
process.stderr.write(msg + '\n') |
|
||||
process.exit(1) |
|
||||
} |
|
||||
|
|
||||
if (process.argv.includes('--help') || process.argv.includes('-h')) { |
|
||||
print(` |
|
||||
Usage |
|
||||
$ nanoid [options] |
|
||||
|
|
||||
Options |
|
||||
-s, --size Generated ID size |
|
||||
-a, --alphabet Alphabet to use |
|
||||
-h, --help Show this help |
|
||||
|
|
||||
Examples |
|
||||
$ nanoid --s 15 |
|
||||
S9sBF77U6sDB8Yg |
|
||||
|
|
||||
$ nanoid --size 10 --alphabet abc |
|
||||
bcabababca`) |
|
||||
process.exit() |
|
||||
} |
|
||||
|
|
||||
let alphabet, size |
|
||||
for (let i = 2; i < process.argv.length; i++) { |
|
||||
let arg = process.argv[i] |
|
||||
if (arg === '--size' || arg === '-s') { |
|
||||
size = Number(process.argv[i + 1]) |
|
||||
i += 1 |
|
||||
if (Number.isNaN(size) || size <= 0) { |
|
||||
error('Size must be positive integer') |
|
||||
} |
|
||||
} else if (arg === '--alphabet' || arg === '-a') { |
|
||||
alphabet = process.argv[i + 1] |
|
||||
i += 1 |
|
||||
} else { |
|
||||
error('Unknown argument ' + arg) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (alphabet) { |
|
||||
let customNanoid = customAlphabet(alphabet, size) |
|
||||
print(customNanoid()) |
|
||||
} else { |
|
||||
print(nanoid(size)) |
|
||||
} |
|
||||
@ -1,72 +0,0 @@ |
|||||
// This file replaces `index.js` in bundlers like webpack or Rollup, |
|
||||
// according to `browser` config in `package.json`. |
|
||||
|
|
||||
let { urlAlphabet } = require('./url-alphabet/index.cjs') |
|
||||
|
|
||||
let random = bytes => crypto.getRandomValues(new Uint8Array(bytes)) |
|
||||
|
|
||||
let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
// `Math.clz32` is not used, because it is not available in browsers. |
|
||||
let mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
|
|
||||
// `-~f => Math.ceil(f)` if f is a float |
|
||||
// `-~i => i + 1` if i is an integer |
|
||||
let step = -~((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let j = step | 0 |
|
||||
while (j--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[j] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size, random) |
|
||||
|
|
||||
let nanoid = (size = 21) => |
|
||||
crypto.getRandomValues(new Uint8Array(size)).reduce((id, byte) => { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
byte &= 63 |
|
||||
if (byte < 36) { |
|
||||
// `0-9a-z` |
|
||||
id += byte.toString(36) |
|
||||
} else if (byte < 62) { |
|
||||
// `A-Z` |
|
||||
id += (byte - 26).toString(36).toUpperCase() |
|
||||
} else if (byte > 62) { |
|
||||
id += '-' |
|
||||
} else { |
|
||||
id += '_' |
|
||||
} |
|
||||
return id |
|
||||
}, '') |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, customRandom, urlAlphabet, random } |
|
||||
@ -1,85 +0,0 @@ |
|||||
let crypto = require('crypto') |
|
||||
|
|
||||
let { urlAlphabet } = require('./url-alphabet/index.cjs') |
|
||||
|
|
||||
// It is best to make fewer, larger requests to the crypto module to |
|
||||
// avoid system call overhead. So, random numbers are generated in a |
|
||||
// pool. The pool is a Buffer that is larger than the initial random |
|
||||
// request size by this multiplier. The pool is enlarged if subsequent |
|
||||
// requests exceed the maximum buffer size. |
|
||||
const POOL_SIZE_MULTIPLIER = 128 |
|
||||
let pool, poolOffset |
|
||||
|
|
||||
let fillPool = bytes => { |
|
||||
if (!pool || pool.length < bytes) { |
|
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
|
||||
crypto.randomFillSync(pool) |
|
||||
poolOffset = 0 |
|
||||
} else if (poolOffset + bytes > pool.length) { |
|
||||
crypto.randomFillSync(pool) |
|
||||
poolOffset = 0 |
|
||||
} |
|
||||
poolOffset += bytes |
|
||||
} |
|
||||
|
|
||||
let random = bytes => { |
|
||||
// `|=` convert `bytes` to number to prevent `valueOf` abusing and pool pollution |
|
||||
fillPool((bytes |= 0)) |
|
||||
return pool.subarray(poolOffset - bytes, poolOffset) |
|
||||
} |
|
||||
|
|
||||
let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
// First, a bitmask is necessary to generate the ID. The bitmask makes bytes |
|
||||
// values closer to the alphabet size. The bitmask calculates the closest |
|
||||
// `2^31 - 1` number, which exceeds the alphabet size. |
|
||||
// For example, the bitmask for the alphabet size 30 is 31 (00011111). |
|
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
|
||||
// Though, the bitmask solution is not perfect since the bytes exceeding |
|
||||
// the alphabet size are refused. Therefore, to reliably generate the ID, |
|
||||
// the random bytes redundancy has to be satisfied. |
|
||||
|
|
||||
// Note: every hardware random generator call is performance expensive, |
|
||||
// because the system call for entropy collection takes a lot of time. |
|
||||
// So, to avoid additional system calls, extra bytes are requested in advance. |
|
||||
|
|
||||
// Next, a step determines how many random bytes to generate. |
|
||||
// The number of random bytes gets decided upon the ID size, mask, |
|
||||
// alphabet size, and magic number 1.6 (using 1.6 peaks at performance |
|
||||
// according to benchmarks). |
|
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
|
||||
|
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
while (true) { |
|
||||
let bytes = getRandom(step) |
|
||||
// A compact alternative for `for (let i = 0; i < step; i++)`. |
|
||||
let i = step |
|
||||
while (i--) { |
|
||||
// Adding `|| ''` refuses a random byte that exceeds the alphabet size. |
|
||||
id += alphabet[bytes[i] & mask] || '' |
|
||||
if (id.length === size) return id |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size, random) |
|
||||
|
|
||||
let nanoid = (size = 21) => { |
|
||||
// `|=` convert `size` to number to prevent `valueOf` abusing and pool pollution |
|
||||
fillPool((size |= 0)) |
|
||||
let id = '' |
|
||||
// We are reading directly from the random pool to avoid creating new array |
|
||||
for (let i = poolOffset - size; i < poolOffset; i++) { |
|
||||
// It is incorrect to use bytes exceeding the alphabet size. |
|
||||
// The following mask reduces the random byte in the 0-255 value |
|
||||
// range to the 0-63 value range. Therefore, adding hacks, such |
|
||||
// as empty string fallback or magic numbers, is unneccessary because |
|
||||
// the bitmask trims bytes down to the alphabet size. |
|
||||
id += urlAlphabet[pool[i] & 63] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet, customRandom, urlAlphabet, random } |
|
||||
@ -1,91 +0,0 @@ |
|||||
/** |
|
||||
* Generate secure URL-friendly unique ID. |
|
||||
* |
|
||||
* By default, the ID will have 21 symbols to have a collision probability |
|
||||
* similar to UUID v4. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { nanoid } from 'nanoid' |
|
||||
* model.id = nanoid() //=> "Uakgb_J5m9g-0JDMbcJqL" |
|
||||
* ``` |
|
||||
* |
|
||||
* @param size Size of the ID. The default size is 21. |
|
||||
* @returns A random string. |
|
||||
*/ |
|
||||
export function nanoid(size?: number): string |
|
||||
|
|
||||
/** |
|
||||
* Generate secure unique ID with custom alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate the ID. |
|
||||
* @param defaultSize Size of the ID. The default size is 21. |
|
||||
* @returns A random string generator. |
|
||||
* |
|
||||
* ```js |
|
||||
* const { customAlphabet } = require('nanoid') |
|
||||
* const nanoid = customAlphabet('0123456789абвгдеё', 5) |
|
||||
* nanoid() //=> "8ё56а" |
|
||||
* ``` |
|
||||
*/ |
|
||||
export function customAlphabet( |
|
||||
alphabet: string, |
|
||||
defaultSize?: number |
|
||||
): (size?: number) => string |
|
||||
|
|
||||
/** |
|
||||
* Generate unique ID with custom random generator and alphabet. |
|
||||
* |
|
||||
* Alphabet must contain 256 symbols or less. Otherwise, the generator |
|
||||
* will not be secure. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { customRandom } from 'nanoid/format' |
|
||||
* |
|
||||
* const nanoid = customRandom('abcdef', 5, size => { |
|
||||
* const random = [] |
|
||||
* for (let i = 0; i < size; i++) { |
|
||||
* random.push(randomByte()) |
|
||||
* } |
|
||||
* return random |
|
||||
* }) |
|
||||
* |
|
||||
* nanoid() //=> "fbaef" |
|
||||
* ``` |
|
||||
* |
|
||||
* @param alphabet Alphabet used to generate a random string. |
|
||||
* @param size Size of the random string. |
|
||||
* @param random A random bytes generator. |
|
||||
* @returns A random string generator. |
|
||||
*/ |
|
||||
export function customRandom( |
|
||||
alphabet: string, |
|
||||
size: number, |
|
||||
random: (bytes: number) => Uint8Array |
|
||||
): () => string |
|
||||
|
|
||||
/** |
|
||||
* URL safe symbols. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { urlAlphabet } from 'nanoid' |
|
||||
* const nanoid = customAlphabet(urlAlphabet, 10) |
|
||||
* nanoid() //=> "Uakgb_J5m9" |
|
||||
* ``` |
|
||||
*/ |
|
||||
export const urlAlphabet: string |
|
||||
|
|
||||
/** |
|
||||
* Generate an array of random bytes collected from hardware noise. |
|
||||
* |
|
||||
* ```js |
|
||||
* import { customRandom, random } from 'nanoid' |
|
||||
* const nanoid = customRandom("abcdef", 5, random) |
|
||||
* ``` |
|
||||
* |
|
||||
* @param bytes Size of the array. |
|
||||
* @returns An array of random bytes. |
|
||||
*/ |
|
||||
export function random(bytes: number): Uint8Array |
|
||||
@ -1,45 +1,47 @@ |
|||||
import crypto from 'crypto' |
|
||||
import { urlAlphabet } from './url-alphabet/index.js' |
|
||||
|
import { webcrypto as crypto } from 'node:crypto' |
||||
|
import { urlAlphabet as scopedUrlAlphabet } from './url-alphabet/index.js' |
||||
|
export { urlAlphabet } from './url-alphabet/index.js' |
||||
const POOL_SIZE_MULTIPLIER = 128 |
const POOL_SIZE_MULTIPLIER = 128 |
||||
let pool, poolOffset |
let pool, poolOffset |
||||
let fillPool = bytes => { |
|
||||
|
function fillPool(bytes) { |
||||
if (!pool || pool.length < bytes) { |
if (!pool || pool.length < bytes) { |
||||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
||||
crypto.randomFillSync(pool) |
|
||||
|
crypto.getRandomValues(pool) |
||||
poolOffset = 0 |
poolOffset = 0 |
||||
} else if (poolOffset + bytes > pool.length) { |
} else if (poolOffset + bytes > pool.length) { |
||||
crypto.randomFillSync(pool) |
|
||||
|
crypto.getRandomValues(pool) |
||||
poolOffset = 0 |
poolOffset = 0 |
||||
} |
} |
||||
poolOffset += bytes |
poolOffset += bytes |
||||
} |
} |
||||
let random = bytes => { |
|
||||
|
export function random(bytes) { |
||||
fillPool((bytes |= 0)) |
fillPool((bytes |= 0)) |
||||
return pool.subarray(poolOffset - bytes, poolOffset) |
return pool.subarray(poolOffset - bytes, poolOffset) |
||||
} |
} |
||||
let customRandom = (alphabet, defaultSize, getRandom) => { |
|
||||
|
export function customRandom(alphabet, defaultSize, getRandom) { |
||||
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
let mask = (2 << (31 - Math.clz32((alphabet.length - 1) | 1))) - 1 |
||||
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
let step = Math.ceil((1.6 * mask * defaultSize) / alphabet.length) |
||||
return (size = defaultSize) => { |
return (size = defaultSize) => { |
||||
|
if (!size) return '' |
||||
let id = '' |
let id = '' |
||||
while (true) { |
while (true) { |
||||
let bytes = getRandom(step) |
let bytes = getRandom(step) |
||||
let i = step |
let i = step |
||||
while (i--) { |
while (i--) { |
||||
id += alphabet[bytes[i] & mask] || '' |
id += alphabet[bytes[i] & mask] || '' |
||||
if (id.length === size) return id |
|
||||
|
if (id.length >= size) return id |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
let customAlphabet = (alphabet, size = 21) => |
|
||||
customRandom(alphabet, size, random) |
|
||||
let nanoid = (size = 21) => { |
|
||||
|
export function customAlphabet(alphabet, size = 21) { |
||||
|
return customRandom(alphabet, size, random) |
||||
|
} |
||||
|
export function nanoid(size = 21) { |
||||
fillPool((size |= 0)) |
fillPool((size |= 0)) |
||||
let id = '' |
let id = '' |
||||
for (let i = poolOffset - size; i < poolOffset; i++) { |
for (let i = poolOffset - size; i < poolOffset; i++) { |
||||
id += urlAlphabet[pool[i] & 63] |
|
||||
|
id += scopedUrlAlphabet[pool[i] & 63] |
||||
} |
} |
||||
return id |
return id |
||||
} |
} |
||||
export { nanoid, customAlphabet, customRandom, urlAlphabet, random } |
|
||||
@ -1 +1 @@ |
|||||
export let nanoid=(t=21)=>crypto.getRandomValues(new Uint8Array(t)).reduce(((t,e)=>t+=(e&=63)<36?e.toString(36):e<62?(e-26).toString(36).toUpperCase():e<63?"_":"-"),""); |
|
||||
|
let a="useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";export let nanoid=(e=21)=>{let t="",r=crypto.getRandomValues(new Uint8Array(e));for(let n=0;n<e;n++)t+=a[63&r[n]];return t}; |
||||
@ -1,34 +0,0 @@ |
|||||
// This alphabet uses `A-Za-z0-9_-` symbols. |
|
||||
// The order of characters is optimized for better gzip and brotli compression. |
|
||||
// References to the same file (works both for gzip and brotli): |
|
||||
// `'use`, `andom`, and `rict'` |
|
||||
// References to the brotli default dictionary: |
|
||||
// `-26T`, `1983`, `40px`, `75px`, `bush`, `jack`, `mind`, `very`, and `wolf` |
|
||||
let urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
|
|
||||
let customAlphabet = (alphabet, defaultSize = 21) => { |
|
||||
return (size = defaultSize) => { |
|
||||
let id = '' |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
// `| 0` is more compact and faster than `Math.floor()`. |
|
||||
id += alphabet[(Math.random() * alphabet.length) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
let nanoid = (size = 21) => { |
|
||||
let id = '' |
|
||||
// A compact alternative for `for (var i = 0; i < step; i++)`. |
|
||||
let i = size | 0 |
|
||||
while (i--) { |
|
||||
// `| 0` is more compact and faster than `Math.floor()`. |
|
||||
id += urlAlphabet[(Math.random() * 64) | 0] |
|
||||
} |
|
||||
return id |
|
||||
} |
|
||||
|
|
||||
module.exports = { nanoid, customAlphabet } |
|
||||
@ -1,6 +0,0 @@ |
|||||
{ |
|
||||
"type": "module", |
|
||||
"main": "index.cjs", |
|
||||
"module": "index.js", |
|
||||
"react-native": "index.js" |
|
||||
} |
|
||||
@ -1,7 +0,0 @@ |
|||||
// This alphabet uses `A-Za-z0-9_-` symbols. |
|
||||
// The order of characters is optimized for better gzip and brotli compression. |
|
||||
// Same as in non-secure/index.js |
|
||||
let urlAlphabet = |
|
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
|
||||
|
|
||||
module.exports = { urlAlphabet } |
|
||||
@ -1,3 +1,2 @@ |
|||||
let urlAlphabet = |
|
||||
|
export const urlAlphabet = |
||||
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict' |
||||
export { urlAlphabet } |
|
||||
@ -1,6 +0,0 @@ |
|||||
{ |
|
||||
"type": "module", |
|
||||
"main": "index.cjs", |
|
||||
"module": "index.js", |
|
||||
"react-native": "index.js" |
|
||||
} |
|
||||
@ -1,69 +1,10 @@ |
|||||
<template> |
<template> |
||||
<div class="app"> |
|
||||
<h2 class="title">Vue路由测试</h2> |
|
||||
<!-- 导航区 --> |
|
||||
<div class="navigate"> |
|
||||
<RouterLink to="/home" active-class="active">首页</RouterLink> |
|
||||
<RouterLink to="/news" active-class="active">新闻</RouterLink> |
|
||||
<RouterLink to="/about" active-class="active">关于</RouterLink> |
|
||||
</div> |
|
||||
<!-- 展示区 --> |
|
||||
<div class="main-content"> |
|
||||
<RouterView></RouterView> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
<Count/> |
||||
|
<br> |
||||
|
<LoveTalk/> |
||||
</template> |
</template> |
||||
|
|
||||
<script lang="ts" setup name="App"> |
|
||||
import { RouterLink, RouterView } from 'vue-router' |
|
||||
|
<script setup lang="ts" name="App"> |
||||
|
import Count from './components/Count.vue' |
||||
|
import LoveTalk from './components/LoveTalk.vue' |
||||
</script> |
</script> |
||||
|
|
||||
<style> |
|
||||
/* 全局样式重置(可选) */ |
|
||||
* { |
|
||||
margin: 0; |
|
||||
padding: 0; |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
|
|
||||
/* 页面容器 */ |
|
||||
.app { |
|
||||
width: 800px; |
|
||||
margin: 0 auto; |
|
||||
} |
|
||||
|
|
||||
/* 标题样式 */ |
|
||||
.title { |
|
||||
background-color: #888; |
|
||||
color: #fff; |
|
||||
padding: 10px 0; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
/* 导航区样式 */ |
|
||||
.navigate { |
|
||||
display: flex; |
|
||||
margin: 20px 0; |
|
||||
} |
|
||||
|
|
||||
.navigate a { |
|
||||
padding: 6px 12px; |
|
||||
border-radius: 4px; |
|
||||
text-decoration: none; |
|
||||
margin-right: 10px; |
|
||||
color: #333; |
|
||||
} |
|
||||
|
|
||||
/* 激活态样式 */ |
|
||||
.navigate a.active { |
|
||||
background-color: #6a994e; |
|
||||
color: #fff; |
|
||||
} |
|
||||
|
|
||||
/* 内容展示区样式 */ |
|
||||
.main-content { |
|
||||
border: 1px solid #ccc; |
|
||||
min-height: 300px; |
|
||||
padding: 10px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,47 @@ |
|||||
|
<template> |
||||
|
<div class="count"> |
||||
|
<h2>当前求和为:{{ sum }},放大10倍后:{{ bigSum }}</h2> |
||||
|
<select v-model.number="n"> |
||||
|
<option value="1">1</option> |
||||
|
<option value="2">2</option> |
||||
|
<option value="3">3</option> |
||||
|
</select> |
||||
|
<button @click="add">加</button> |
||||
|
<button @click="minus">减</button> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="Count"> |
||||
|
import { ref,reactive,toRefs } from "vue"; |
||||
|
import {storeToRefs} from 'pinia' |
||||
|
// 引入useCountStore |
||||
|
import {useCountStore} from '@/store/count' |
||||
|
// 使用useCountStore,得到一个专门保存count相关的store |
||||
|
const countStore = useCountStore() |
||||
|
// storeToRefs只会关注sotre中数据,不会对方法进行ref包裹 |
||||
|
const {sum,bigSum} = storeToRefs(countStore) |
||||
|
// console.log('!!!!!',storeToRefs(countStore)) |
||||
|
|
||||
|
// 数据 |
||||
|
let n = ref(1) // 用户选择的数字 |
||||
|
// 方法 |
||||
|
function add(){ |
||||
|
countStore.increment(n.value) |
||||
|
} |
||||
|
function minus(){ |
||||
|
countStore.sum -= n.value |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.count { |
||||
|
background-color: skyblue; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
select,button { |
||||
|
margin: 0 5px; |
||||
|
height: 25px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,45 @@ |
|||||
|
<template> |
||||
|
<div class="talk"> |
||||
|
<button @click="getLoveTalk">获取一句土味情话</button> |
||||
|
<ul> |
||||
|
<li v-for="talk in talkStore.talkList" :key="talk.id">{{talk.title}}</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts" name="LoveTalk"> |
||||
|
import {reactive} from 'vue' |
||||
|
import axios from "axios"; |
||||
|
import {nanoid} from 'nanoid' |
||||
|
import {useTalkStore} from '@/store/loveTalk' |
||||
|
import { storeToRefs } from "pinia"; |
||||
|
|
||||
|
const talkStore = useTalkStore() |
||||
|
const {talkList} = storeToRefs(talkStore) |
||||
|
|
||||
|
talkStore.$subscribe((mutate,state) => { |
||||
|
console.log('talkStore里面保存的数据发生了变化',mutate,state) |
||||
|
localStorage.setItem('talkList',JSON.stringify(state.talkList)) |
||||
|
}) |
||||
|
|
||||
|
// 方法 |
||||
|
async function getLoveTalk(){ |
||||
|
|
||||
|
talkStore.getATalk() |
||||
|
// 发请求,下面这行的写法是:连续解构赋值+重命名 |
||||
|
// let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') |
||||
|
// 把请求回来的字符串,包装成一个对象 |
||||
|
// let obj = {id:nanoid(),title} |
||||
|
// 放到数组中 |
||||
|
// talkList.unshift(obj) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.talk { |
||||
|
background-color: orange; |
||||
|
padding: 10px; |
||||
|
border-radius: 10px; |
||||
|
box-shadow: 0 0 10px; |
||||
|
} |
||||
|
</style> |
||||
@ -1,34 +0,0 @@ |
|||||
<template> |
|
||||
<div class="news"> |
|
||||
<ul> |
|
||||
<li><a href="#">新闻1</a></li> |
|
||||
<li><a href="#">新闻2</a></li> |
|
||||
<li><a href="#">新闻3</a></li> |
|
||||
<li><a href="#">新闻4</a></li> |
|
||||
</ul> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup name="News"> |
|
||||
|
|
||||
|
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
.news { |
|
||||
padding: 0 20px; |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
height: 100px; |
|
||||
} |
|
||||
.news ul { |
|
||||
margin-top: 30px; |
|
||||
list-style: none; |
|
||||
padding-left: 10px; |
|
||||
} |
|
||||
.news li>a{ |
|
||||
text-decoration: none; |
|
||||
color: #000; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,48 +0,0 @@ |
|||||
<template> |
|
||||
<div class="person"> |
|
||||
<h2>当前求和为:{{sum}}</h2> |
|
||||
<button @click="add">点我sum+1</button> |
|
||||
|
|
||||
<br> |
|
||||
<img v-for="(dog,index) in dogList" :src="dog" :key="index" > |
|
||||
<button @click="getDog">再来一只狗</button> |
|
||||
</div> |
|
||||
</template> |
|
||||
|
|
||||
<script lang="ts" setup name="Person"> |
|
||||
import { ref,reactive } from 'vue'; |
|
||||
import axios from 'axios'; |
|
||||
|
|
||||
//数据 |
|
||||
let sum = ref(0) |
|
||||
let dogList = reactive([ |
|
||||
'https://images.dog.ceo/breeds/pembroke/n02113023_4269.jpg' |
|
||||
]) |
|
||||
|
|
||||
|
|
||||
//方法 |
|
||||
function add(){ |
|
||||
sum.value+=1 |
|
||||
} |
|
||||
|
|
||||
async function getDog(){ |
|
||||
try{ |
|
||||
let result = await axios.get('https://dog.ceo/api/breeds/image/random') |
|
||||
dogList.push(result.data.message) |
|
||||
}catch(error){ |
|
||||
alert(error) |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style scoped> |
|
||||
.person{ |
|
||||
width: 200px; |
|
||||
height: 200px; |
|
||||
background-color: palegoldenrod; |
|
||||
} |
|
||||
img{ |
|
||||
width: 100%; |
|
||||
height: auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,29 @@ |
|||||
|
import {defineStore} from 'pinia' |
||||
|
|
||||
|
export const useCountStore = defineStore('count',{ |
||||
|
actions:{ |
||||
|
// 定义方法,可以修改state中的数据,响应组件中的“动作”
|
||||
|
increment(value: number){ |
||||
|
console.log('increment被调用了',value) |
||||
|
//修改数据,响应组件
|
||||
|
//this是当前store实例
|
||||
|
this.sum += value |
||||
|
}, |
||||
|
//减
|
||||
|
decrement(value: number){ |
||||
|
this.sum -= value |
||||
|
} |
||||
|
}, |
||||
|
// 真正存储数据的地方
|
||||
|
state(){ |
||||
|
return { |
||||
|
sum:6 |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
getters:{ |
||||
|
bigSum(state){ |
||||
|
return state.sum * 10 |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,22 @@ |
|||||
|
import {defineStore} from 'pinia' |
||||
|
import axios from 'axios' |
||||
|
import {nanoid} from 'nanoid' |
||||
|
|
||||
|
export const useTalkStore = defineStore('talk',{ |
||||
|
actions:{ |
||||
|
async getATalk(){ |
||||
|
// 发请求,下面这行的写法是:连续解构赋值+重命名
|
||||
|
let {data:{content:title}} = await axios.get('https://api.uomg.com/api/rand.qinghua?format=json') |
||||
|
// 把请求回来的字符串,包装成一个对象
|
||||
|
let obj = {id:nanoid(),title} |
||||
|
// 放到数组中
|
||||
|
this.talkList.unshift(obj) |
||||
|
} |
||||
|
}, |
||||
|
// 真正存储数据的地方
|
||||
|
state(){ |
||||
|
return { |
||||
|
talkList:JSON.parse(localStorage.getItem('talkList') as string) || [] |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
@ -0,0 +1,434 @@ |
|||||
|
<template> |
||||
|
<div class="exam-system"> |
||||
|
<!-- 头部标题 --> |
||||
|
<header class="header"> |
||||
|
<div class="logo"> |
||||
|
<span class="icon">📋</span> |
||||
|
<span class="title">股票知识评测系统</span> |
||||
|
</div> |
||||
|
<div class="subtitle">全方面评估您股票知识水平,还能个性化学习建议</div> |
||||
|
</header> |
||||
|
|
||||
|
<div class="main-content"> |
||||
|
<!-- 左侧题目区域 --> |
||||
|
<div class="left-panel"> |
||||
|
<!-- 题目显示区 --> |
||||
|
<div class="question-area"> |
||||
|
<div class="question-header"> |
||||
|
<input type="text" v-model="searchText" placeholder="搜索题目..." class="search-input" /> |
||||
|
</div> |
||||
|
|
||||
|
<div class="question-content"> |
||||
|
<h3 class="question-title">{{ currentQuestion.title }}</h3> |
||||
|
|
||||
|
<div class="options"> |
||||
|
<div |
||||
|
v-for="option in currentQuestion.options" |
||||
|
:key="option.key" |
||||
|
class="option-item" |
||||
|
:class="{ 'selected': selectedAnswer === option.key }" |
||||
|
@click="selectAnswer(option.key)" |
||||
|
> |
||||
|
<span class="radio-btn" :class="{ 'checked': selectedAnswer === option.key }"></span> |
||||
|
<span class="option-label">{{ option.key }}. {{ option.text }}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 答题卡预览区 --> |
||||
|
<div class="answer-preview"> |
||||
|
<div class="preview-content"> |
||||
|
<!-- 可以显示答题进度或其他信息 --> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 底部按钮 --> |
||||
|
<div class="bottom-actions"> |
||||
|
<button class="btn btn-secondary" @click="submitExam">提交</button> |
||||
|
<button class="btn btn-primary" @click="previousQuestion">← 上一题</button> |
||||
|
<button class="btn btn-primary" @click="nextQuestion">下一题 →</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 右侧答题卡 --> |
||||
|
<div class="right-panel"> |
||||
|
<div class="answer-card"> |
||||
|
<div class="card-header"> |
||||
|
<span class="folder-icon">📁</span> |
||||
|
<span class="card-title">试题导航</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="card-grid"> |
||||
|
<div |
||||
|
v-for="(item, index) in totalQuestions" |
||||
|
:key="index" |
||||
|
class="grid-item" |
||||
|
:class="getQuestionStatus(index)" |
||||
|
@click="jumpToQuestion(index)" |
||||
|
> |
||||
|
{{ index + 1 }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<script setup> |
||||
|
import { ref, computed } from 'vue' |
||||
|
|
||||
|
// 搜索文本 |
||||
|
const searchText = ref('') |
||||
|
|
||||
|
// 当前题目索引 |
||||
|
const currentQuestionIndex = ref(0) |
||||
|
|
||||
|
// 用户答案记录 |
||||
|
const userAnswers = ref({}) |
||||
|
|
||||
|
// 选中的答案 |
||||
|
const selectedAnswer = ref('') |
||||
|
|
||||
|
// 总题目数 |
||||
|
const totalQuestions = ref(25) |
||||
|
|
||||
|
// 题目数据(示例) |
||||
|
const questions = ref([ |
||||
|
{ |
||||
|
id: 1, |
||||
|
title: '以下哪个不属于股票的基本特征?', |
||||
|
options: [ |
||||
|
{ key: 'A', text: '收益性' }, |
||||
|
{ key: 'B', text: '风险性' }, |
||||
|
{ key: 'C', text: '流动性' }, |
||||
|
{ key: 'D', text: '确定性' } |
||||
|
] |
||||
|
}, |
||||
|
{ |
||||
|
id: 2, |
||||
|
title: '股票市场的主要功能是什么?', |
||||
|
options: [ |
||||
|
{ key: 'A', text: '资金融通' }, |
||||
|
{ key: 'B', text: '价格发现' }, |
||||
|
{ key: 'C', text: '资源配置' }, |
||||
|
{ key: 'D', text: '以上都是' } |
||||
|
] |
||||
|
} |
||||
|
// 可以添加更多题目... |
||||
|
]) |
||||
|
|
||||
|
// 当前题目 |
||||
|
const currentQuestion = computed(() => { |
||||
|
return questions.value[currentQuestionIndex.value] || questions.value[0] |
||||
|
}) |
||||
|
|
||||
|
// 选择答案 |
||||
|
const selectAnswer = (answer) => { |
||||
|
selectedAnswer.value = answer |
||||
|
userAnswers.value[currentQuestionIndex.value] = answer |
||||
|
} |
||||
|
|
||||
|
// 上一题 |
||||
|
const previousQuestion = () => { |
||||
|
if (currentQuestionIndex.value > 0) { |
||||
|
currentQuestionIndex.value-- |
||||
|
selectedAnswer.value = userAnswers.value[currentQuestionIndex.value] || '' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 下一题 |
||||
|
const nextQuestion = () => { |
||||
|
if (currentQuestionIndex.value < questions.value.length - 1) { |
||||
|
currentQuestionIndex.value++ |
||||
|
selectedAnswer.value = userAnswers.value[currentQuestionIndex.value] || '' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 跳转到指定题目 |
||||
|
const jumpToQuestion = (index) => { |
||||
|
currentQuestionIndex.value = index |
||||
|
selectedAnswer.value = userAnswers.value[index] || '' |
||||
|
} |
||||
|
|
||||
|
// 获取题目状态 |
||||
|
const getQuestionStatus = (index) => { |
||||
|
if (index === currentQuestionIndex.value) { |
||||
|
return 'current' |
||||
|
} |
||||
|
if (userAnswers.value[index]) { |
||||
|
return 'answered' |
||||
|
} |
||||
|
return '' |
||||
|
} |
||||
|
|
||||
|
// 提交考试 |
||||
|
const submitExam = () => { |
||||
|
const answeredCount = Object.keys(userAnswers.value).length |
||||
|
if (answeredCount < questions.value.length) { |
||||
|
if (confirm(`您还有 ${questions.value.length - answeredCount} 道题未作答,确定要提交吗?`)) { |
||||
|
// 提交逻辑 |
||||
|
console.log('提交答案:', userAnswers.value) |
||||
|
} |
||||
|
} else { |
||||
|
// 提交逻辑 |
||||
|
console.log('提交答案:', userAnswers.value) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.exam-system { |
||||
|
min-height: 100vh; |
||||
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); |
||||
|
color: #fff; |
||||
|
font-family: 'Microsoft YaHei', Arial, sans-serif; |
||||
|
} |
||||
|
|
||||
|
.header { |
||||
|
padding: 20px 40px; |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 10px; |
||||
|
font-size: 24px; |
||||
|
font-weight: bold; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
font-size: 28px; |
||||
|
} |
||||
|
|
||||
|
.subtitle { |
||||
|
color: rgba(255, 255, 255, 0.8); |
||||
|
font-size: 14px; |
||||
|
margin-left: 38px; |
||||
|
} |
||||
|
|
||||
|
.main-content { |
||||
|
display: flex; |
||||
|
gap: 20px; |
||||
|
padding: 20px 40px; |
||||
|
max-width: 1400px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
|
||||
|
.left-panel { |
||||
|
flex: 1; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
|
||||
|
.question-area { |
||||
|
background: rgba(20, 40, 80, 0.6); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.question-header { |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.search-input { |
||||
|
width: 100%; |
||||
|
padding: 10px 15px; |
||||
|
background: rgba(0, 0, 0, 0.3); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 4px; |
||||
|
color: #fff; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.search-input::placeholder { |
||||
|
color: rgba(255, 255, 255, 0.4); |
||||
|
} |
||||
|
|
||||
|
.question-content { |
||||
|
padding: 20px 0; |
||||
|
} |
||||
|
|
||||
|
.question-title { |
||||
|
color: rgba(255, 255, 255, 0.6); |
||||
|
font-size: 14px; |
||||
|
margin-bottom: 20px; |
||||
|
font-weight: normal; |
||||
|
} |
||||
|
|
||||
|
.options { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 15px; |
||||
|
} |
||||
|
|
||||
|
.option-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 12px; |
||||
|
padding: 12px 15px; |
||||
|
background: rgba(0, 0, 0, 0.2); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.option-item:hover { |
||||
|
background: rgba(100, 150, 200, 0.2); |
||||
|
border-color: rgba(100, 150, 200, 0.5); |
||||
|
} |
||||
|
|
||||
|
.option-item.selected { |
||||
|
background: rgba(220, 80, 80, 0.3); |
||||
|
border-color: rgba(220, 80, 80, 0.6); |
||||
|
} |
||||
|
|
||||
|
.radio-btn { |
||||
|
width: 18px; |
||||
|
height: 18px; |
||||
|
border: 2px solid rgba(255, 255, 255, 0.5); |
||||
|
border-radius: 50%; |
||||
|
display: inline-block; |
||||
|
position: relative; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.radio-btn.checked { |
||||
|
border-color: #e74c3c; |
||||
|
background: #e74c3c; |
||||
|
} |
||||
|
|
||||
|
.radio-btn.checked::after { |
||||
|
content: ''; |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
width: 8px; |
||||
|
height: 8px; |
||||
|
background: #fff; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.option-label { |
||||
|
font-size: 15px; |
||||
|
} |
||||
|
|
||||
|
.answer-preview { |
||||
|
background: rgba(20, 40, 80, 0.6); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
min-height: 150px; |
||||
|
} |
||||
|
|
||||
|
.bottom-actions { |
||||
|
display: flex; |
||||
|
gap: 15px; |
||||
|
justify-content: flex-start; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
padding: 12px 30px; |
||||
|
border: none; |
||||
|
border-radius: 4px; |
||||
|
font-size: 14px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.btn-primary { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.btn-primary:hover { |
||||
|
background: rgba(60, 120, 180, 1); |
||||
|
} |
||||
|
|
||||
|
.btn-secondary { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
color: #fff; |
||||
|
} |
||||
|
|
||||
|
.btn-secondary:hover { |
||||
|
background: rgba(70, 130, 190, 1); |
||||
|
} |
||||
|
|
||||
|
.right-panel { |
||||
|
width: 280px; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.answer-card { |
||||
|
background: rgba(20, 40, 80, 0.6); |
||||
|
border: 1px solid rgba(100, 150, 200, 0.3); |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.card-header { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
margin-bottom: 20px; |
||||
|
padding-bottom: 15px; |
||||
|
border-bottom: 1px solid rgba(100, 150, 200, 0.2); |
||||
|
} |
||||
|
|
||||
|
.folder-icon { |
||||
|
font-size: 20px; |
||||
|
} |
||||
|
|
||||
|
.card-title { |
||||
|
color: #6db3f2; |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
.card-grid { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(5, 1fr); |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
.grid-item { |
||||
|
aspect-ratio: 1; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background: rgba(60, 120, 180, 0.6); |
||||
|
border-radius: 4px; |
||||
|
cursor: pointer; |
||||
|
transition: all 0.3s; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.grid-item:hover { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
transform: scale(1.05); |
||||
|
} |
||||
|
|
||||
|
.grid-item.current { |
||||
|
background: #3498db; |
||||
|
box-shadow: 0 0 10px rgba(52, 152, 219, 0.5); |
||||
|
} |
||||
|
|
||||
|
.grid-item.answered { |
||||
|
background: rgba(60, 120, 180, 0.8); |
||||
|
} |
||||
|
|
||||
|
.grid-item:nth-child(1) { |
||||
|
background: #e74c3c; |
||||
|
} |
||||
|
|
||||
|
.grid-item:nth-child(2) { |
||||
|
background: #27ae60; |
||||
|
} |
||||
|
</style> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue