81 changed files with 1729 additions and 1479 deletions
-
60zhangbo/第九天技术学习笔记.txt
-
61zhangbo/第九天股票知识学习笔记.txt
-
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
-
35吴光慧学习笔记/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
-
76吴光慧学习笔记/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
-
BIN尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05AI探牛深入学习.docx
-
BIN尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05学习总结.docx
-
387尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05学习笔记.md
-
BIN尹顺宇学习笔记/尹顺宇11.05作业/尹顺宇11.05学习笔记.pdf
-
434股票知识评测系统.vue
@ -0,0 +1,60 @@ |
|||
Spring框架复习笔记整理以“三层架构”为核心 |
|||
|
|||
Spring框架下的三层架构 |
|||
一、三层架构核心概念 |
|||
三层架构是项目后端开发的经典分层结构,它将业务逻辑按职责划分为三个独立层次,目的是降低代码耦合度、提高可维护性和可扩展性。这三个层次自上而下分别是**表现层(Controller)**、**业务逻辑层(Service)** 和**数据访问层(Dao)**,每层仅与上下相邻层交互,不跨层调用。 |
|||
|
|||
在Spring框架中,三层架构的实现完全依赖于IoC容器和注解,每层的Bean都由Spring统一管理,层与层之间通过依赖注入(@Autowired/@Resource)建立关联。 |
|||
|
|||
二、各层职责与Spring实现 |
|||
1. 表现层(Controller层) |
|||
核心职责:接收前端请求、参数校验、调用Service层方法、封装响应数据(如JSON)返回给前端。它是用户与系统交互的入口,不处理复杂业务逻辑。 |
|||
Spring实现方式: |
|||
使用`@Controller`或`@RestController`注解声明类为表现层Bean,纳入Spring IoC容器。 |
|||
通过`@RequestMapping`、`@GetMapping`、`@PostMapping`等注解映射前端请求路径和请求方式。 |
|||
方法参数通过`@RequestParam`(获取URL参数)、`@RequestBody`(获取JSON请求体)等注解接收前端数据。 |
|||
|
|||
2. 业务逻辑层(Service层) |
|||
核心职责:处理核心业务逻辑(如用户注册时的合法性校验、订单生成时的库存扣减)、协调多个Dao层方法完成复杂业务、事务管理(如转账时的原子性保证)。它是系统的“大脑”,承上启下。 |
|||
Spring实现方式: |
|||
使用`@Service`注解声明类为业务层Bean,纳入IoC容器。 |
|||
业务层通常包含“接口+实现类”的结构(如`UserService`接口和`UserServiceImpl`实现类),接口定义方法,实现类写具体逻辑,降低耦合。 |
|||
通过`@Autowired`注入Dao层Bean,调用Dao层方法操作数据。 |
|||
使用`@Transactional`注解声明事务,确保业务操作的原子性(如失败回滚)。 |
|||
|
|||
3. 数据访问层(Dao层/Repository层) |
|||
核心职责:直接与数据库交互,执行数据的CRUD操作(Create/Read/Update/Delete),不包含业务逻辑。它是系统与数据库之间的“桥梁”。 |
|||
Spring实现方式: |
|||
使用`@Repository`注解声明类为数据访问层Bean,纳入IoC容器(若使用MyBatis,`@Mapper`注解可替代,且需配置扫描路径)。 |
|||
传统方式:Dao层通过Spring JDBC(如`JdbcTemplate`)执行SQL;实际开发中多结合MyBatis,通过XML或注解(`@Select`/`@Insert`)编写SQL。 |
|||
不依赖Service层,仅对外提供数据操作方法,由Service层调用。 |
|||
|
|||
三、三层架构的请求流转流程 |
|||
以“用户查询”功能为例,完整的请求流转如下: |
|||
1. 前端发送GET请求:`http://localhost:8080/user/1`,请求查询ID为1的用户。 |
|||
2. Spring MVC拦截请求,根据`@RequestMapping("/user")`和`@GetMapping("/{id}")`,将请求分发到`UserController`的`getUserById`方法。 |
|||
3. `UserController`通过`@PathVariable`接收参数`id=1`,调用注入的`UserService`的`getUserById`方法。 |
|||
4. `UserService`(`UserServiceImpl`)调用注入的`UserDao`的`selectById`方法,请求数据。 |
|||
5. `UserDao`执行SQL查询数据库,返回`User`对象给`UserService`。 |
|||
6. `UserService`将`User`对象返回给`UserController`。 |
|||
7. `UserController`将`User`对象封装成统一响应格式(如`Result<User>`),返回JSON给前端。 |
|||
|
|||
四、三层架构的优势与注意事项 |
|||
1. 核心优势 |
|||
低耦合:每层职责单一,修改某一层(如更换数据库,仅改Dao层)不影响其他层。 |
|||
高可维护:代码按功能分层,定位问题时可快速锁定到具体层(如前端报错查Controller,数据错误查Dao)。 |
|||
高可扩展:支持分层扩展,如增加“用户积分”功能,仅需新增`PointService`和`PointDao`,不修改现有代码。 |
|||
|
|||
2. 注意事项 |
|||
禁止跨层调用:不能直接在Controller层调用Dao层,必须通过Service层中转,否则会破坏分层逻辑,增加耦合。 |
|||
事务声明在Service层:事务必须加在Service层方法上,因为Service层是业务逻辑的处理单元,Controller层仅负责请求转发,不适合管理事务。 |
|||
Bean命名规范:各层Bean命名建议加前缀,如`UserController`、`UserService`、`UserDao`,提高代码可读性。 |
|||
|
|||
五、三层架构与MVC的区别 |
|||
三层架构和MVC,两者本质是不同维度的设计思想,核心区别如下: |
|||
|
|||
| 对比维度 | 三层架构 | MVC | |
|||
|---------- |---------------------------------------------|------------------------------------------------------| |
|||
| 设计范围 | 后端代码分层(Controller/Service/Dao) | 前后端交互架构(Model/View/Controller) | |
|||
| 核心目的 | 降低后端代码耦合,优化后端开发流程 | 分离前端视图与后端数据、逻辑,优化前后端交互 | |
|||
| 对应关系 | 三层架构的“表现层(Controller)”对应MVC的“Controller”;三层架构的“Service+Dao”对应MVC的“Model”;MVC的“View”是前端页面(三层架构不包含前端) | |
|||
@ -0,0 +1,61 @@ |
|||
股票知识复习 |
|||
|
|||
股票基础 |
|||
A 股,全称人民币普通股票,如同寻宝世界里最常见的宝藏线索,是在中国境内注册、上市,以人民币标明面值,供境内投资者用人民币认购和交易的普通股股票。我们日常炒股,大多围绕 A 股展开。在这个市场中,有两类主要 “玩家”。 |
|||
大股东,如同夺宝团队中的核心成员,手握公司 5% 以上的股票,拥有巨大影响力。他们的买卖行为,就像核心成员的关键决策,直接左右股价走势。比如,当大股东减持股票,就如同核心成员决定放弃部分宝藏,市场会认为公司前景可能不佳,股价往往随之下跌。 |
|||
而散户,恰似寻宝世界里的小探险家,资金量小,单个操作对股价影响微弱。但我们不能忽视大股东的一举一动,要像关注核心成员动向一样,通过 “股东变动公告”,捕捉股价变化的信号,以此调整自己的投资策略。 |
|||
|
|||
交易规则要点速览 |
|||
在股票交易中,开盘价是每天早上 9 点 30 分开市后的第一笔成交价格,它像清晨的第一缕阳光,透露着早盘投资者的情绪。若开盘价上涨,表明早盘买入意愿强烈,大家对当天行情充满期待;反之,则可能意味着市场情绪谨慎。 |
|||
收盘价是下午 3 点收盘时的最后一笔成交价格(深市通过集合竞价确定),它是当天交易的终点,也是判断当日行情的重要基准,如同一天冒险结束后的总结,反映了当天市场的最终博弈结果。 |
|||
涨跌停制度则是市场的 “安全阀”,普通股票每日涨跌幅限制在 10%(ST 股为 5%)。涨停时,股票价格达到当日上限,虽可卖出但难以买入,如同宝藏被暂时封存,难以获取;跌停时,价格降至下限,能买却难卖,仿佛陷入困境。这种限制防止了股价的过度波动,维护了市场的相对稳定。 |
|||
成交量和成交额是判断行情真假的关键指标。成交量指一天内成交的股票数量,成交额是成交量与股价的乘积。就像寻宝时,不仅要关注找到的宝藏数量,还要考虑其价值。股价上涨时,若成交量未同步放大,如同发现的宝藏看似珍贵却缺乏足够的支撑,可能是 “假涨”;若股价上涨且成交量翻倍,则表明有大量资金涌入,如同众多寻宝者竞相追逐,是行情真实上涨的有力证据。 |
|||
|
|||
洞察市场大环境 |
|||
股票市场如同神秘的寻宝大陆,有着明显的牛熊周期。牛市时,市场长期上涨,投资者信心爆棚,仿佛置身遍地宝藏的乐园,赚钱似乎轻而易举,如 2020 年下半年的市场行情,许多股票价格持续攀升,投资者收获颇丰。 |
|||
熊市则恰恰相反,市场长期下跌,恐惧笼罩着每一个投资者,大家纷纷担忧财富缩水,就像进入了危机四伏的黑暗森林。以 2018 年为例,股市持续低迷,新手若在熊市高位入场,就如同在危险区域盲目寻宝,极易被深度套牢。 |
|||
要了解市场整体情况,就需关注关键指数。上证综指反映上海股市的整体表现,深证成指展现深圳股市的全貌。它们如同寻宝地图上的重要地标,通过其涨跌,我们能知晓市场的大致走向。当上证综指上涨 1%,意味着上海市场多数股票上涨;反之,则多数股票下跌,帮助我们把握市场的整体脉搏。 |
|||
|
|||
评估股票价值 |
|||
市盈率(PE)是衡量股票价值的常用指标,计算公式为 “股价 ÷ 每股收益”,简单来说,就是你为获取 1 元利润愿意付出的成本。低市盈率的股票通常被认为价格相对便宜,但不同行业的市盈率存在差异。科技股由于增长潜力大,投资者愿意为其未来收益支付更高价格,市盈率往往高于传统的银行股。因此,在比较市盈率时,需考虑行业特性,不能一概而论。 |
|||
每股收益(EPS)等于公司总利润除以总股数,代表每一股所获得的利润。这一指标持续增长,表明公司盈利能力不断增强,如同宝藏的价值在不断提升,股价也更有可能上涨,吸引更多投资者关注。 |
|||
股息率则是(每股分红 ÷ 股价)×100%,反映了股票的分红收益水平。对于追求稳定收益的投资者,如退休长辈,股息率较高的股票颇具吸引力,就像稳定的宝藏产出。银行股、水电燃气股等行业,通常具有较高的股息率,能为投资者提供相对稳定的现金流回报 。 |
|||
|
|||
AI 工具:股票投资的夺宝神器 |
|||
在变幻莫测的股票市场中,我们就如同深入神秘遗迹的夺宝奇兵,每一次决策都关乎着财富的得失。而如今,AI 工具就像是我们手中的神奇罗盘,为我们指引着投资的方向。它能够快速处理海量的数据,精准捕捉市场的细微变化,帮助我们在复杂的股市中做出更明智的决策。接下来,让我们一同深入探索夺宝奇兵中那些强大的 AI 工具,看看它们是如何助力我们在股市中披荆斩棘,斩获财富的。 |
|||
|
|||
夺宝利剑:捕捉股票上涨信号 |
|||
夺宝利剑是一款极具特色的 AI 工具,它依靠 4 根线来精准判断股票的趋势,每根线都肩负着独特的使命,如同寻宝团队中分工明确的成员,协同为我们传递着股票涨跌的重要信息。 |
|||
天线,作为长期趋势的瞭望者,时刻关注着股票的长期走势。当这根线持续向上攀升时,就如同寻宝地图上一条清晰指向宝藏的路线,表明股票在长期内处于上涨趋势,即便短期内出现些许波动,也无需过度担忧。 |
|||
飞线主要负责反映股票的中期情况,它如同寻宝过程中对周边环境的探测,关注着公司未来产能的规划以及行业发展的预期。如果飞线向上,说明公司的发展前景乐观,行业预期良好。 |
|||
中线则聚焦于短期资金的动向,是我们观察近期资金流向的重要窗口。若中线向上,意味着近期有资金持续买入,为股票价格的上涨提供了动力。 |
|||
流线堪称夺宝利剑的核心,它如同宝藏发出的神秘信号,专门提示股票何时可能出现暴涨行情,是我们捕捉股票启动信号的关键指标。 |
|||
当 4 根线紧密靠拢,股价波动幅度不超过 3%,且流线率先向上冲刺时,这便是强启动信号,如同宝藏的大门已经缓缓开启,我们应迅速予以关注。此时,多空双方意见高度一致,资金集中涌入,股票极有可能迎来主升浪,开启大幅上涨之旅。 |
|||
而当只有 3 根线靠在一起,流线尚未向上冲时,这属于弱启动信号,表明虽然长期或中期趋势向好,但还需耐心等待流线也向上运行,才能确认买入时机,以免过早入场导致踏空。 |
|||
在实战过程中,我们要时刻牢记,只要天线没有向下拐头,就说明股票的长期趋势依然向好,短期的下跌只是暂时的调整,无需惊慌抛售。但如果流线突然向上冲,而飞线和天线却向下,这大概率是一个假信号,就像在寻宝途中遇到的虚假线索,此时应谨慎对待,避免盲目追涨。 |
|||
以 N 奕材 - U 这只半导体股为例,由于行业景气度高,公司实力强劲,其天线陡峭向上,彰显出长期上涨的趋势。飞线也因公司产能扩张的预期而同步向上。中线则因为股票交易活跃,换手率高达 60% 以上而呈现上升态势。在上市开盘后的 15 分钟内,流线迅速与其他 3 根线汇聚,精准地捕捉到了上市首日股价近 200% 的涨幅,从 10 块左右一路飙升至 30 块,为投资者带来了丰厚的收益。 |
|||
|
|||
AI 探牛:辨别股票涨跌真伪 |
|||
AI 探牛如同一位经验丰富的寻宝导师,凭借独特的判断逻辑,帮助我们分清股票的 “真涨” 与 “假反弹”,避免在投资中误入陷阱。 |
|||
它主要依靠牵牛绳和 K 线属性这两个关键要素来判断股票的涨跌趋势。牵牛绳就像是我们手中的指南针,红色代表股票处于上涨趋势,绿色则表示下跌趋势,为我们确定了股票走势的大方向。 |
|||
K 线属性则如同宝藏的不同特征,通过不同的颜色来传递股票的不同状态。红色进攻 K 线表明买入的主力正在发力,如同寻宝者全力挖掘宝藏;蓝色推进 K 线意味着股票稳步上涨,无需过度担忧;黄色防守 K 线则警示我们股票快扛不住下跌的压力,要小心谨慎;青色撤退 K 线更是明确地提醒我们赶紧卖出,股票即将下跌。 |
|||
为了避免被短期的波动所迷惑,AI 探牛采用了跨周期验证的方法。比如,当某一天股票显示为红线且是进攻 K 线,但从一周的时间跨度来看却是绿线,这很可能只是短期的反弹,此时不宜大量买入。相反,如果当天和一周内都呈现红线且是进攻 K 线,说明大周期对小周期形成了有力的支撑,股票上涨的态势更为稳健。 |
|||
在判断股票回调时,AI 探牛也有明确的规则。当出现绿线和撤退 K 线时,如果股票跌幅不到 5%,且未跌破一周的趋势线,这通常只是短期的回调,无需匆忙卖出。但如果跌幅超过 8%,且一周趋势线也开始向下拐头,那就必须果断止损,以免遭受更大的损失。 |
|||
对于成长股而言,由于其波动较大,牵牛绳对股价变化的反应更为灵敏。例如,当股票一天涨幅达到 50% 时,牵牛绳就会迅速从绿色转为红色。而进攻 K 线的确认则更为严格,要求第一天涨幅超过 50%,并且收盘价能站在当天最高涨幅的 80% 以上,以此确保上涨信号的真实性,避免被虚假的上涨所误导。 |
|||
|
|||
AI 雷达:寻找股票投资机会 |
|||
AI 雷达就像是寻宝过程中的探测仪,帮助我们精准寻找股票的 “底部” 和 “加速涨” 的机会,为我们在股市中把握最佳的投资时机。 |
|||
它主要通过两根线和 K 线形态来进行判断。天轨(蓝色)代表着买的主力能够撑住的价格,如同宝藏上方的一道阻力,股票涨到这个位置可能会受到阻碍;地轨(黄色)则表示卖的主力能压到的价格,是股票的支撑位,当股价跌到这个位置时可能会止跌反弹。 |
|||
当出现底部启动信号时,就意味着我们可能找到了宝藏的入口,是买入股票的好时机。底部启动需要满足三个条件:一是跌的阴线数量比上一波更多,表明市场的下跌动能在逐渐释放;二是股价跌到了新的低点,显示出市场的恐慌情绪达到一定程度;三是地轨往上走或者天地轨平着,这说明卖的主力力量已经逐渐耗尽。在满足这些条件后,还需要等待两根阳线的出现。第一根是 “试盘阳线”,这是主力在试探市场的买入意愿;第二根 “启动阳线” 则确认了底部的形成,此时我们就可以放心买入。 |
|||
而当股票处于上涨过程中,如果天轨从平着变为往上走,对应出现的阳线就是 “拉升阳线”,这表明买的主力开始发力,如同寻宝者找到了推动宝藏上升的力量,股票即将进入加速涨阶段,我们应紧紧抓住这个机会,获取更多的收益。 |
|||
然而,AI 雷达目前还存在一些待完善的地方。例如,在股票跌破地轨后,如何确定止损的标准,是跌幅达到一定比例还是持续下跌一定天数后卖出,还需要进一步明确。在震荡行情中,如何分辨真假信号,比如限定股价下跌的时间以及成交量的阈值等,也需要进一步优化。此外,不同类型的股票具有不同的特性,小盘股周期较短,蓝筹股则需要更多的成交量才能有效,因此如何针对不同股票适配不同的参数,也是未来需要解决的问题。 |
|||
|
|||
超级云脑:实现全流程决策 |
|||
超级云脑堪称散户专属的 “智能军师”,它如同一个智慧的宝库,为我们提供全方位的投资决策支持,帮助我们在股市中实现全流程的精准决策。 |
|||
超级云脑分为三层结构,每一层都承担着独特的功能。最核心的超级云脑层,运用强大的数据分析能力,精准算出股票的估值、安全等级等关键指标,为我们的投资决策提供坚实的数据基础。 |
|||
云脑探秘层则以直观的图表形式展示 6 个关键信息,让我们一目了然。在这里,我们可以清晰地了解股票的估值情况,通过对比 PE/PB/EPS 等指标,判断股票是贵了、刚好还是便宜了。例如,如果一只股票的 PE 比行业平均水平低,那就说明它相对便宜,具有一定的投资价值。 |
|||
安全级别通过 6 种颜色分为 5 级风险,红色代表极高风险,绿色表示非常安全,让我们对投资风险一目了然。压力支撑模块则明确地标出股票 “跌到哪能止跌”(支撑位)、“涨到哪会受阻”(压力位),为我们的买卖操作提供重要参考。 |
|||
趋势研判帮助我们分清股票的长期(如半年)和短期(如一周)走势是涨是跌,让我们对股票的未来走向有更清晰的认识。庄家成本模块可以让我们了解主力和散户的成本谁低,如果我们的成本比主力还低,那么在投资中就更具优势,安全性更高。 |
|||
情绪价值则如同市场的温度计,提示我们市场是过于狂热(此时应避免追高)、过于恐慌(不要轻易割肉)还是即将发生方向转变,帮助我们把握市场的情绪变化,做出更明智的决策。通过云脑探秘,我们能够全面了解一只股票,解决 “这股票值不值得买、现在能不能买、该拿着还是卖掉” 的问题。 |
|||
机构动向层主要用于跟踪主力资金的流向,让我们清楚地知道大资金在做什么。在这里,我们可以看到当日资金的买卖情况,了解主力资金是在买入还是卖出,以及主力是否在偷偷建仓。通过庄散对决模块,我们还能知晓主力和散户谁持有的股票更多。例如,当一只股票显示便宜,同时主力在买入且偷偷建仓时,这就形成了三重利好,是我们重点关注的对象。 |
|||
在实际操作中,我们可以按照以下流程进行。首先是选股,重点关注那些估值便宜、安全级别高、主力成本低且机构在买入的股票。然后等待买入时机,当股票跌到支撑位,且市场情绪稳定时,果断买入。在持有过程中,密切关注股票的长期趋势是否发生改变,主力是否还在持仓,筹码是否稳定。最后,当股票涨到压力位时,及时止盈;如果市场出现恐慌情绪,股票走势恶化,则果断止损,确保我们的投资收益。 |
|||
@ -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", |
|||
"lockfileHash": "0d539b07", |
|||
"browserHash": "2bc9fe4f", |
|||
"lockfileHash": "22fdfde4", |
|||
"browserHash": "b44b993f", |
|||
"optimized": { |
|||
"pinia": { |
|||
"src": "../../pinia/dist/pinia.mjs", |
|||
"file": "pinia.js", |
|||
"fileHash": "a97780a2", |
|||
"needsInterop": false |
|||
}, |
|||
"vue": { |
|||
"src": "../../vue/dist/vue.runtime.esm-bundler.js", |
|||
"file": "vue.js", |
|||
"fileHash": "5b2b4475", |
|||
"fileHash": "1e96b106", |
|||
"needsInterop": false |
|||
}, |
|||
"vue-router": { |
|||
"src": "../../vue-router/dist/vue-router.mjs", |
|||
"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 |
|||
} |
|||
}, |
|||
"chunks": { |
|||
"chunk-SFGEOVP2": { |
|||
"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 |
|||
let pool, poolOffset |
|||
let fillPool = bytes => { |
|||
function fillPool(bytes) { |
|||
if (!pool || pool.length < bytes) { |
|||
pool = Buffer.allocUnsafe(bytes * POOL_SIZE_MULTIPLIER) |
|||
crypto.randomFillSync(pool) |
|||
crypto.getRandomValues(pool) |
|||
poolOffset = 0 |
|||
} else if (poolOffset + bytes > pool.length) { |
|||
crypto.randomFillSync(pool) |
|||
crypto.getRandomValues(pool) |
|||
poolOffset = 0 |
|||
} |
|||
poolOffset += bytes |
|||
} |
|||
let random = bytes => { |
|||
export function random(bytes) { |
|||
fillPool((bytes |= 0)) |
|||
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 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 |
|||
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)) |
|||
let id = '' |
|||
for (let i = poolOffset - size; i < poolOffset; i++) { |
|||
id += urlAlphabet[pool[i] & 63] |
|||
id += scopedUrlAlphabet[pool[i] & 63] |
|||
} |
|||
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' |
|||
export { urlAlphabet } |
|||
@ -1,6 +0,0 @@ |
|||
{ |
|||
"type": "module", |
|||
"main": "index.cjs", |
|||
"module": "index.js", |
|||
"react-native": "index.js" |
|||
} |
|||
@ -1,69 +1,10 @@ |
|||
<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> |
|||
|
|||
<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> |
|||
|
|||
<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,387 @@ |
|||
# SpringBoot+Redis |
|||
|
|||
前瞻: |
|||
|
|||
Reids的Java客户端有 |
|||
|
|||
1.Jedis |
|||
|
|||
2.Lettuce |
|||
|
|||
3.Spring Data Redis |
|||
|
|||
我们主要使用Spring Data Redis |
|||
|
|||
## 1.操作步骤: |
|||
|
|||
### 1.1 导入Spring Data Redis 的maven坐标 |
|||
|
|||
```xml |
|||
<dependency> |
|||
<groupId>org.springframework.boot</groupId> |
|||
<artifactId>spring-boot-starter-data-redis</artifactId> |
|||
</dependency> |
|||
``` |
|||
|
|||
### 1.2 配置redis数据源 |
|||
|
|||
``` |
|||
spring: |
|||
redis: |
|||
host: ${sky.redis.host} |
|||
port: ${sky.redis.port} |
|||
password: ${sky.redis.password} |
|||
database: ${sky.redis.database} |
|||
``` |
|||
|
|||
```yaml |
|||
sky: |
|||
redis: |
|||
host: localhost |
|||
port: 6379 |
|||
password: |
|||
database: 10 |
|||
``` |
|||
|
|||
### 1.3 编写配置类,创建RedisTemplete对象 |
|||
|
|||
```java |
|||
@Configuration |
|||
@Slf4j |
|||
public class RedisConfiguration { |
|||
@Bean |
|||
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){ |
|||
log.info("创建RedisTemplate对象..."); |
|||
RedisTemplate redisTemplate = new RedisTemplate(); |
|||
//设置redis连接工厂对象 |
|||
redisTemplate.setConnectionFactory(redisConnectionFactory); |
|||
//设置redis的key的序列化器 |
|||
redisTemplate.setKeySerializer(new StringRedisSerializer()); |
|||
return redisTemplate; |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
 |
|||
|
|||
加redis的key的序列化器之前: |
|||
|
|||
 |
|||
|
|||
加redis的key的序列化器之后: |
|||
|
|||
 |
|||
|
|||
### 1.4 通过Redis Template对象操作Redis |
|||
|
|||
```java |
|||
@SpringBootTest |
|||
public class SpringDataRedisTest { |
|||
@Autowired |
|||
private RedisTemplate redisTemplate; |
|||
@Test |
|||
public void testRedisTemplate(){ |
|||
System.out.println(redisTemplate); |
|||
//操作字符串类型 |
|||
ValueOperations valueOperations = redisTemplate.opsForValue(); |
|||
HashOperations hashOperations = redisTemplate.opsForHash(); |
|||
ListOperations listOperations = redisTemplate.opsForList(); |
|||
SetOperations setOperations = redisTemplate.opsForSet(); |
|||
ZSetOperations zSetOperations = redisTemplate.opsForZSet(); |
|||
} |
|||
//String类型 |
|||
@Test |
|||
public void testString(){ |
|||
//set get setex setnx |
|||
redisTemplate.opsForValue().set("name","小明"); |
|||
String city = (String) redisTemplate.opsForValue().get("name"); |
|||
System.out.println(city); |
|||
redisTemplate.opsForValue().set("code","1234",3, TimeUnit.MINUTES); |
|||
//setIfAbsent就是setnx |
|||
redisTemplate.opsForValue().setIfAbsent("lock","1"); |
|||
redisTemplate.opsForValue().setIfAbsent("lock","2"); |
|||
} |
|||
//Hash类型 |
|||
@Test |
|||
public void testHash(){ |
|||
//hset hget hdel hkeys hvals |
|||
HashOperations hashOperations = redisTemplate.opsForHash(); |
|||
//hset |
|||
hashOperations.put("100","name","yinshunyu"); |
|||
hashOperations.put("100","age","20"); |
|||
//get |
|||
String name = (String) hashOperations.get("100", "name"); |
|||
System.out.println(name); |
|||
//hkeys |
|||
Set keys = hashOperations.keys("100"); |
|||
System.out.println(keys); |
|||
//hvals |
|||
List values = hashOperations.values("100"); |
|||
System.out.println(values); |
|||
//hdel |
|||
hashOperations.delete("100","age"); |
|||
} |
|||
//List类型 |
|||
@Test |
|||
public void testList(){ |
|||
//lpush lrange rpop llen |
|||
ListOperations listOperations = redisTemplate.opsForList(); |
|||
listOperations.leftPushAll("mylist","a","b","c"); |
|||
listOperations.leftPush("mylist","d"); |
|||
List mylist = listOperations.range("mylist", 0, -1); |
|||
System.out.println(mylist); |
|||
listOperations.rightPop("mylist"); |
|||
Long size = listOperations.size("mylist"); |
|||
System.out.println(size); |
|||
} |
|||
//Set类型 |
|||
@Test |
|||
public void testSet(){ |
|||
//sadd smembers scard sinter sunion srem |
|||
SetOperations setOperations = redisTemplate.opsForSet(); |
|||
setOperations.add("set1","a","b","c","d"); |
|||
setOperations.add("set2","a","b","x","y"); |
|||
//smembers 获取元素 |
|||
Set members = setOperations.members("set1"); |
|||
System.out.println(members); |
|||
//scard 元素个数 |
|||
Long size = setOperations.size("set1"); |
|||
System.out.println(size); |
|||
//sinter 交集 |
|||
Set intersect = setOperations.intersect("set1", "set2"); |
|||
System.out.println(intersect); |
|||
//sunion 并集 |
|||
Set union = setOperations.union("set1", "set2"); |
|||
System.out.println( union); |
|||
setOperations.remove("set1","a","b"); |
|||
} |
|||
//ZSet类型 |
|||
@Test |
|||
public void testZset(){ |
|||
//zadd zrange zincrby zrem |
|||
ZSetOperations zSetOperations = redisTemplate.opsForZSet(); |
|||
|
|||
zSetOperations.add("zset1","a",10); |
|||
zSetOperations.add("zset1","b",12); |
|||
zSetOperations.add("zset1","c",9); |
|||
|
|||
Set zset1 = zSetOperations.range("zset1", 0, -1); |
|||
System.out.println(zset1); |
|||
//zincrby 修改分数 |
|||
zSetOperations.incrementScore("zset1","c",10); |
|||
|
|||
zSetOperations.remove("zset1","a","b"); |
|||
} |
|||
|
|||
} |
|||
|
|||
``` |
|||
|
|||
 |
|||
|
|||
## 2.缓存菜品模拟 |
|||
|
|||
**修改用户端接口 DishController 的 list 方法,加入缓存处理逻辑:** |
|||
|
|||
```java |
|||
@Autowired |
|||
private RedisTemplate redisTemplate; |
|||
/** |
|||
* 根据分类id查询菜品 |
|||
* |
|||
* @param categoryId |
|||
* @return |
|||
*/ |
|||
@GetMapping("/list") |
|||
@ApiOperation("根据分类id查询菜品") |
|||
public Result<List<DishVO>> list(Long categoryId) { |
|||
|
|||
//构造redis中的key,规则:dish_分类id |
|||
String key = "dish_" + categoryId; |
|||
|
|||
//查询redis中是否存在菜品数据 |
|||
List<DishVO> list = (List<DishVO>) redisTemplate.opsForValue().get(key); |
|||
if(list != null && list.size() > 0){ |
|||
//如果存在,直接返回,无须查询数据库 |
|||
return Result.success(list); |
|||
} |
|||
//////////////////////////////////////////////////////// |
|||
Dish dish = new Dish(); |
|||
dish.setCategoryId(categoryId); |
|||
dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品 |
|||
|
|||
//如果不存在,查询数据库,将查询到的数据放入redis中 |
|||
list = dishService.listWithFlavor(dish); |
|||
//////////////////////////////////////////////////////// |
|||
redisTemplate.opsForValue().set(key, list); |
|||
|
|||
return Result.success(list); |
|||
} |
|||
``` |
|||
|
|||
**抽取清理缓存的方法:** |
|||
|
|||
在管理端DishController中添加 |
|||
|
|||
```java |
|||
@Autowired |
|||
private RedisTemplate redisTemplate; |
|||
/** |
|||
* 清理缓存数据 |
|||
* @param pattern |
|||
*/ |
|||
private void cleanCache(String pattern){ |
|||
Set keys = redisTemplate.keys(pattern); |
|||
redisTemplate.delete(keys); |
|||
} |
|||
``` |
|||
|
|||
**调用清理缓存的方法,保证数据一致性:** |
|||
|
|||
**1). 新增菜品优化** |
|||
|
|||
```java |
|||
/** |
|||
* 新增菜品 |
|||
* |
|||
* @param dishDTO |
|||
* @return |
|||
*/ |
|||
@PostMapping |
|||
@ApiOperation("新增菜品") |
|||
public Result save(@RequestBody DishDTO dishDTO) { |
|||
log.info("新增菜品:{}", dishDTO); |
|||
dishService.saveWithFlavor(dishDTO); |
|||
|
|||
//清理缓存数据 |
|||
String key = "dish_" + dishDTO.getCategoryId(); |
|||
cleanCache(key); |
|||
return Result.success(); |
|||
} |
|||
``` |
|||
|
|||
# EasyExcel初步理解 |
|||
|
|||
## 依赖导入 |
|||
|
|||
``` |
|||
|
|||
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel --> |
|||
<dependency> |
|||
<groupId>com.alibaba</groupId> |
|||
<artifactId>easyexcel</artifactId> |
|||
<version>2.1.1</version> |
|||
</dependency> |
|||
<dependency> |
|||
<groupId>org.projectlombok</groupId> |
|||
<artifactId>lombok</artifactId> |
|||
<version>1.18.10</version> |
|||
``` |
|||
|
|||
### 创建与表格对应的实体类 |
|||
|
|||
``` |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class Student { |
|||
|
|||
@ExcelProperty(value = "学生id") |
|||
private Integer id; |
|||
@ExcelProperty(value = "学生姓名") |
|||
private String name; |
|||
|
|||
@ExcelProperty(value = "学生年龄") |
|||
private Integer age; |
|||
} |
|||
|
|||
``` |
|||
|
|||
## 写操作 |
|||
|
|||
``` |
|||
|
|||
public class WriteExcel { |
|||
public static void main(String[] args) { |
|||
|
|||
//准备文件路径 |
|||
String fileName="D:/destory/test/easyexcel.xls"; |
|||
//写出文件 |
|||
EasyExcel.write(fileName, Student.class).sheet("easyexcel") |
|||
.doWrite(data()); |
|||
} |
|||
|
|||
|
|||
private static List<Student> data(){ |
|||
ArrayList<Student> list = new ArrayList<>(); |
|||
for (int i = 0; i < 10; i++) { |
|||
Student student = new Student(i, "董德" + 1, 22 + i); |
|||
list.add(student); |
|||
} |
|||
return list; |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## 读操作 |
|||
|
|||
### 改造实体类 |
|||
|
|||
``` |
|||
|
|||
@Data |
|||
@AllArgsConstructor |
|||
@NoArgsConstructor |
|||
public class Student { |
|||
|
|||
@ExcelProperty(value = "学生id",index = 0) |
|||
private Integer id; |
|||
@ExcelProperty(value = "学生姓名",index = 1) |
|||
private String name; |
|||
|
|||
@ExcelProperty(value = "学生年龄",index = 2) |
|||
private Integer age; |
|||
} |
|||
|
|||
``` |
|||
|
|||
### 创建监听器 |
|||
|
|||
``` |
|||
|
|||
public class EasyExcelLinster extends AnalysisEventListener<Student> { |
|||
|
|||
List<Student> list= new ArrayList<Student>(); |
|||
//一行一行的去读取里面的数据 |
|||
@Override |
|||
public void invoke(Student student, AnalysisContext analysisContext) { |
|||
System.out.println(student); |
|||
list.add(student); |
|||
} |
|||
|
|||
@Override |
|||
public void doAfterAllAnalysed(AnalysisContext analysisContext) { |
|||
|
|||
} |
|||
|
|||
|
|||
} |
|||
|
|||
``` |
|||
|
|||
### 读取 |
|||
|
|||
``` |
|||
|
|||
public class ReadExcel { |
|||
public static void main(String[] args) { |
|||
//准备文件路径 |
|||
String fileName="D:/destory/test/easyexcel.xls"; |
|||
EasyExcel.read(fileName, Student.class, new ExcelListener()).sheet().doRead(); |
|||
} |
|||
} |
|||
|
|||
``` |
|||
|
|||
@ -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