├── .eslintignore
├── .eslintrc.yml
├── .gitattributes
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── docs
└── .vuepress
│ └── config.js
├── lib
├── comp
│ ├── Character.vue
│ ├── Result.vue
│ ├── ResultChar.vue
│ ├── Select.vue
│ ├── Settings.vue
│ └── index.vue
├── data
│ ├── characters.yaml
│ ├── games.yaml
│ ├── index.js
│ ├── rank_cn7.yaml
│ └── tags.yaml
├── index.js
├── pages
│ └── about.md
├── styles
│ └── index.styl
└── utils
│ ├── BackupTree.ts
│ ├── SortNode.ts
│ ├── index.ts
│ ├── settings.ts
│ └── sort.mixin.ts
├── package.json
└── tsconfig.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | !.vuepress
2 | dist
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | root: true
2 |
3 | extends:
4 | - plugin:vue/essential
5 | - standard
6 |
7 | rules:
8 | comma-dangle:
9 | - error
10 | - always-multiline
11 | indent:
12 | - error
13 | - 2
14 | - SwitchCase: 1
15 | MemberExpression: off
16 | no-undef: error
17 | no-unused-vars: off
18 | no-return-assign: off
19 | operator-linebreak:
20 | - error
21 | - before
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 | yarn-error.log
4 | node_modules
5 | package-lock.json
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | git:
4 | depth: false
5 |
6 | branches:
7 | only:
8 | - master
9 |
10 | node_js:
11 | - node
12 |
13 | before_deploy:
14 | - yarn build
15 |
16 | deploy:
17 | provider: pages
18 | github-token: $GITHUB_TOKEN
19 | local-dir: docs/.vuepress/dist
20 | skip-cleanup: true
21 | on:
22 | branch: master
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Shigma
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 东方本命角色测试
2 |
3 | [](https://travis-ci.org/uzkk/favorite)
4 |
5 | - 主站链接:[https://vp.uzkk.net/favorite/](https://vp.uzkk.net/favorite/)
6 | - 分站链接:[https://uzkk.github.io/favorite/](https://uzkk.github.io/favorite/)
7 |
8 | > 注:分站的更新会比主站快一两个版本。
9 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = (context) => ({
2 | base: '/favorite/',
3 |
4 | title: '二色幽紫蝶',
5 |
6 | description: '东方 Project - 从入坑到入坟',
7 |
8 | theme: 'uzkk',
9 |
10 | plugins: [
11 | [require('@uzkk/not-found')],
12 | [require('@uzkk/shared-assets')],
13 | [require('../..'), {
14 | base: '/',
15 | }],
16 | ],
17 |
18 | themeConfig: {
19 | search: false,
20 | nav: [
21 | { text: '主页', link: '/', exact: true },
22 | { text: '关于', link: '/about.html', exact: false },
23 | { text: 'GitHub', link: 'https://github.com/uzkk/favorite', exact: false },
24 | ],
25 | },
26 |
27 | evergreen: () => !context.isProd,
28 | })
29 |
--------------------------------------------------------------------------------
/lib/comp/Character.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
![]()
13 |
14 |
15 |
16 |
17 |
{{ node.name }}
18 |
{{ node.nick }}
19 |
20 |
21 |
22 |
23 |
52 |
53 |
113 |
--------------------------------------------------------------------------------
/lib/comp/Result.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
您的前 {{ ranking.length }} 位本命角色排行
4 |
5 |
21 |
22 |
23 | 测试结果
24 |
25 | 本次测试中,您通过 {{ questionCount }}
26 | 轮回答,从共计 {{ characters.length }}
27 | 名角色中选出了前 {{ ranking.length }} 名。
28 |
29 |
30 |
31 | 排名 |
32 | 姓名 |
33 | 称号 |
34 |
35 |
36 | {{ index + 1 }} |
37 | {{ name }} |
38 | {{ charMap[name].nick }} |
39 |
40 |
41 |
42 |
43 |
44 | 偏好分数
45 |
46 | 排名数量过少,不予统计。
47 |
48 |
49 | 未找到您可能的偏好标签。
50 |
51 |
52 |
53 |
幻想乡众贤者开会后一致认为,您极有可能属于下列人群:
54 |
55 |
56 | {{ name }}控
57 |
58 |
59 |
考虑到此类人群对幻想乡生物的危险性,贤者们已决定在您幻想入后将您调教成罪袋。请做好相应的觉悟。
60 |
61 |
62 |
63 | 属性名 |
64 | 参考值 |
65 |
66 |
67 | {{ name }} |
68 | {{ value.toFixed(3) }} |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
82 |
88 |
89 |
90 |
91 |
92 |
136 |
137 |
156 |
--------------------------------------------------------------------------------
/lib/comp/ResultChar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
第 {{ rank }} 位
4 |
![]()
5 |
6 |
{{ node.name }}
7 |
{{ node.nick }}
8 |
9 |
10 |
11 |
12 |
19 |
20 |
61 |
--------------------------------------------------------------------------------
/lib/comp/Select.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 第 {{ questionCount }} 轮:请点击图片选择更喜欢的角色
5 |
6 |
7 | 排名第 {{ currentRank }} 的角色已经确定
8 |
9 |
10 |
11 | |
12 | |
13 |
14 |
15 |
16 |
22 | |
23 |
24 |
30 | |
31 |
32 |
33 |
34 |
40 | |
41 |
42 |
49 | |
50 |
51 |
52 |
53 |
59 | |
60 |
61 |
67 | |
68 |
69 |
70 |
71 |
78 | |
79 |
80 |
81 |
82 |
83 |
84 |
127 |
128 |
161 |
--------------------------------------------------------------------------------
/lib/comp/Settings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
21 |
22 | -
23 |
24 |
25 |
26 |
35 |
36 | -
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 选择排名数:
56 |
57 |
58 | -
63 |
64 |
65 | -
66 |
67 | 全选 ({{ characters.length }})
68 |
69 |
70 |
71 |
72 |
73 |
74 | 选择立绘表情:
75 |
76 | -
77 | {{ key }}
78 |
79 |
80 |
81 |
84 |
85 |
86 |
87 |
90 |
93 |
96 |
97 |
98 |
99 |
100 |
221 |
--------------------------------------------------------------------------------
/lib/comp/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/lib/data/characters.yaml:
--------------------------------------------------------------------------------
1 | - - 1
2 | - 博丽灵梦
3 | - 乐园的巫女
4 | - abcdefghijADFGHIJ
5 | - rank_cn7: 1
6 | - []
7 | - - 2
8 | - 雾雨魔理沙
9 | - 普通的魔法使
10 | - abcdefghijABDGHIJ
11 | - rank_cn7: 2
12 | - []
13 | - - 3
14 | - 露米娅
15 | - 宵暗的妖怪
16 | - a
17 | - rank_cn7: 43
18 | - - loli
19 | - baka
20 | - - 4
21 | - 大妖精
22 | - 新月般的妖精
23 | - aB
24 | - rank_cn7: 53
25 | - - loli
26 | - yousei
27 | - baka
28 | - - 5
29 | - 琪露诺
30 | - 湖上的冰精
31 | - adikAB
32 | - rank_cn7: 17
33 | - - loli
34 | - yousei
35 | - baka
36 | - - 6
37 | - 红美铃
38 | - 华人小娘
39 | - aA
40 | - rank_cn7: 32
41 | - []
42 | - - 7
43 | - 小恶魔
44 | - 图书馆的恶魔
45 | - a
46 | - rank_cn7: 64
47 | - - uniform
48 | - - 8
49 | - 帕秋莉·诺蕾姬
50 | - 不动的大图书馆
51 | - aA
52 | - rank_cn7: 20
53 | - []
54 | - - 9
55 | - 十六夜咲夜
56 | - 完美潇洒的从者
57 | - abcdiA
58 | - rank_cn7: 7
59 | - - uniform
60 | - - 10
61 | - 蕾米莉亚·斯卡蕾特
62 | - 永远鲜红的幼月
63 | - acA
64 | - rank_cn7: 4
65 | - - loli
66 | - - 11
67 | - 芙兰朵露·斯卡蕾特
68 | - 恶魔之妹
69 | - a
70 | - rank_cn7: 12
71 | - - loli
72 | - - 12
73 | - 蕾蒂·霍瓦特洛克
74 | - 冬天的遗忘之物
75 | - b
76 | - rank_cn7: 98
77 | - []
78 | - - 13
79 | - 橙
80 | - 凶兆的黑猫
81 | - b
82 | - rank_cn7: 73
83 | - - loli
84 | - beast
85 | - baka
86 | - - 14
87 | - 爱丽丝·玛格特洛依德
88 | - 七色的人偶师
89 | - bAJ
90 | - rank_cn7: 11
91 | - []
92 | - - 15
93 | - 莉莉白
94 | - 告知春天的妖精
95 | - bdkB
96 | - rank_cn7: 61
97 | - - loli
98 | - yousei
99 | - - 16
100 | - 露娜萨·普莉兹姆利巴
101 | - 骚灵小提琴手
102 | - bd
103 | - rank_cn7: 101
104 | - - loli
105 | - - 17
106 | - 梅露兰·普莉兹姆利巴
107 | - 骚灵小号手
108 | - bd
109 | - rank_cn7: 118
110 | - - loli
111 | - - 18
112 | - 莉莉卡·普莉兹姆利巴
113 | - 骚灵键盘手
114 | - bd
115 | - rank_cn7: 124
116 | - - loli
117 | - - 19
118 | - 魂魄妖梦
119 | - 半分虚幻的庭师
120 | - bcdhA
121 | - rank_cn7: 14
122 | - []
123 | - - 20
124 | - 西行寺幽幽子
125 | - 华胥的亡灵
126 | - bcA
127 | - rank_cn7: 8
128 | - - bba
129 | - - 21
130 | - 八云蓝
131 | - 策士之九尾
132 | - b
133 | - rank_cn7: 54
134 | - - bba
135 | - beast
136 | - - 22
137 | - 八云紫
138 | - 幻想的境界
139 | - bcA
140 | - rank_cn7: 6
141 | - - bba
142 | - - 23
143 | - 伊吹萃香
144 | - 小小的百鬼夜行
145 | - A
146 | - rank_cn7: 27
147 | - []
148 | - - 24
149 | - 莉格露·奈特巴格
150 | - 暗中蠢动的光虫
151 | - c
152 | - rank_cn7: 102
153 | - - baka
154 | - - 25
155 | - 米斯蒂娅·萝蕾拉
156 | - 夜雀妖怪
157 | - cd
158 | - rank_cn7: 66
159 | - - loli
160 | - baka
161 | - - 26
162 | - 上白泽慧音
163 | - 知识与历史的半兽
164 | - c
165 | - rank_cn7: 42
166 | - []
167 | - - 27
168 | - 因幡帝
169 | - 幸运的白兔
170 | - cd
171 | - rank_cn7: 67
172 | - - loli
173 | - beast
174 | - - 28
175 | - 铃仙·优昙华院·因幡
176 | - 狂气的月兔
177 | - cdjA
178 | - rank_cn7: 15
179 | - - beast
180 | - uniform
181 | - - 29
182 | - 八意永琳
183 | - 蓬莱的药贩
184 | - c
185 | - rank_cn7: 45
186 | - - bba
187 | - - 30
188 | - 蓬莱山辉夜
189 | - 永远的公主殿下
190 | - c
191 | - rank_cn7: 19
192 | - []
193 | - - 31
194 | - 藤原妹红
195 | - 红色自警队
196 | - cA
197 | - rank_cn7: 9
198 | - []
199 | - - 32
200 | - 铃仙 II 号
201 | - 月之玉兔
202 | - D
203 | - alias_cn: Reisen
204 | - - beast
205 | - uniform
206 | - - 33
207 | - 绵月依姬
208 | - 神灵凭附的月之公主
209 | - D
210 | - rank_cn7: 96
211 | - []
212 | - - 34
213 | - 绵月丰姬
214 | - 连系海与山的月之公主
215 | - D
216 | - rank_cn7: 107
217 | - []
218 | - - 35
219 | - 射命丸文
220 | - 传统的幻想书屋
221 | - dkAB
222 | - rank_cn7: 10
223 | - []
224 | - - 36
225 | - 梅蒂欣·梅兰可莉
226 | - 小小的甜蜜毒药
227 | - d
228 | - rank_cn7: 87
229 | - - loli
230 | - - 37
231 | - 风见幽香
232 | - 四季的鲜花之主
233 | - dIJ
234 | - rank_cn7: 26
235 | - - bba
236 | - - 38
237 | - 小野塚小町
238 | - 三途河畔的摆渡人
239 | - dA
240 | - rank_cn7: 70
241 | - []
242 | - - 39
243 | - 四季映姬·亚玛萨那度
244 | - 乐园的最高审判长
245 | - d
246 | - rank_cn7: 21
247 | - - loli
248 | - - 40
249 | - 桑妮·米尔克
250 | - 闪耀的日之光
251 | - BD
252 | - alias_cn: 桑尼米尔克
253 | - - loli
254 | - yousei
255 | - baka
256 | - - 41
257 | - 斯塔·萨菲雅
258 | - 倾泻而下的星之光
259 | - BD
260 | - alias_cn: 斯塔萨菲雅
261 | - - loli
262 | - yousei
263 | - baka
264 | - - 42
265 | - 露娜·切尔德
266 | - 静谧的月之光
267 | - BD
268 | - alias_cn: 露娜切尔德
269 | - - loli
270 | - yousei
271 | - baka
272 | - - 43
273 | - 稗田阿求
274 | - 九代御阿礼少女
275 | - DE
276 | - rank_cn7: 44
277 | - - loli
278 | - - 44
279 | - 森近霖之助
280 | - 不动的古道具店
281 | - D
282 | - rank_cn7: 58
283 | - []
284 | - - 45
285 | - 秋静叶
286 | - 寂寞与终焉的象征
287 | - e
288 | - rank_cn7: 85
289 | - - loli
290 | - - 46
291 | - 秋穰子
292 | - 丰裕与收成的象征
293 | - e
294 | - rank_cn7: 92
295 | - - loli
296 | - - 47
297 | - 键山雏
298 | - 秘神流雏
299 | - e
300 | - rank_cn7: 34
301 | - []
302 | - - 48
303 | - 河城荷取
304 | - 超妖怪弹头
305 | - eA
306 | - rank_cn7: 56
307 | - - loli
308 | - - 49
309 | - 犬走椛
310 | - 下位哨戒天狗
311 | - e
312 | - rank_cn7: 47
313 | - - beast
314 | - - 50
315 | - 东风谷早苗
316 | - 祭祀风的人类
317 | - efghjA
318 | - rank_cn7: 13
319 | - []
320 | - - 51
321 | - 八坂神奈子
322 | - 山坂与湖水的化身
323 | - e
324 | - rank_cn7: 65
325 | - - bba
326 | - - 52
327 | - 洩矢诹访子
328 | - 土著神的顶点
329 | - eA
330 | - rank_cn7: 30
331 | - - loli
332 | - - 53
333 | - 琪斯美
334 | - 可怕的水井妖怪
335 | - f
336 | - rank_cn7: 127
337 | - - loli
338 | - - 54
339 | - 黑谷山女
340 | - 昏暗洞窟中明亮的网
341 | - f
342 | - rank_cn7: 109
343 | - []
344 | - - 55
345 | - 水桥帕露西
346 | - 地壳下的嫉妒心
347 | - f
348 | - rank_cn7: 46
349 | - []
350 | - - 56
351 | - 星熊勇仪
352 | - 人所谈论的怪力乱神
353 | - f
354 | - rank_cn7: 81
355 | - []
356 | - - 57
357 | - 古明地觉
358 | - 连怨灵都恐惧的少女
359 | - f
360 | - rank_cn7: 5
361 | - - loli
362 | - - 58
363 | - 火焰猫燐
364 | - 地狱的车祸
365 | - f
366 | - rank_cn7: 48
367 | - - beast
368 | - - 59
369 | - 灵乌路空
370 | - 难以驾驭的神之火
371 | - fA
372 | - rank_cn7: 41
373 | - - baka
374 | - - 60
375 | - 古明地恋
376 | - 闭合的恋之瞳
377 | - fA
378 | - rank_cn7: 3
379 | - - loli
380 | - - 61
381 | - 永江衣玖
382 | - 美丽的绯之衣
383 | - A
384 | - rank_cn7: 71
385 | - []
386 | - - 62
387 | - 比那名居天子
388 | - 非想非非想天之女
389 | - A
390 | - rank_cn7: 16
391 | - []
392 | - - 63
393 | - 娜兹玲
394 | - 探宝的小小大将
395 | - g
396 | - rank_cn7: 93
397 | - - loli
398 | - beast
399 | - - 64
400 | - 多多良小伞
401 | - 愉快的遗忘之伞
402 | - g
403 | - rank_cn7: 38
404 | - - loli
405 | - - 65
406 | - 云居一轮
407 | - 守护与被守护的大轮
408 | - gA
409 | - rank_cn7: 108
410 | - []
411 | - - 66
412 | - 村纱水蜜
413 | - 水难事故的念缚灵
414 | - g
415 | - rank_cn7: 74
416 | - - uniform
417 | - - 67
418 | - 寅丸星
419 | - 毗沙门天的弟子
420 | - g
421 | - rank_cn7: 112
422 | - []
423 | - - 68
424 | - 圣白莲
425 | - 被封印的大魔法使
426 | - gA
427 | - rank_cn7: 35
428 | - - bba
429 | - - 69
430 | - 封兽鵺
431 | - 未确认幻想飞行少女
432 | - g
433 | - rank_cn7: 50
434 | - []
435 | - - 70
436 | - 幽谷响子
437 | - 诵经的山彦
438 | - h
439 | - rank_cn7: 89
440 | - - loli
441 | - beast
442 | - baka
443 | - - 71
444 | - 宫古芳香
445 | - 忠实的尸体
446 | - h
447 | - rank_cn7: 95
448 | - - baka
449 | - - 72
450 | - 霍青娥
451 | - 穿墙的邪仙
452 | - h
453 | - rank_cn7: 57
454 | - []
455 | - - 73
456 | - 苏我屠自古
457 | - 神明后裔的亡灵
458 | - h
459 | - rank_cn7: 88
460 | - []
461 | - - 74
462 | - 物部布都
463 | - 古代日本的尸解仙
464 | - hA
465 | - rank_cn7: 55
466 | - []
467 | - - 75
468 | - 丰聪耳神子
469 | - 圣德道士
470 | - hA
471 | - rank_cn7: 33
472 | - []
473 | - - 76
474 | - 二岩猯藏
475 | - 佐渡的二岩
476 | - hA
477 | - rank_cn7: 69
478 | - - bba
479 | - beast
480 | - - 77
481 | - 姬海棠果
482 | - 当代的念写记者
483 | - B
484 | - rank_cn7: 82
485 | - - uniform
486 | - - 78
487 | - 茨木华扇
488 | - 独臂有角的仙人
489 | - AD
490 | - rank_cn7: 49
491 | - []
492 | - - 79
493 | - 玛艾露贝莉·赫恩
494 | - 化猫之幻
495 | - E
496 | - alias_cn: 玛艾露贝莉·赫恩(梅莉)
497 | - []
498 | - - 80
499 | - 宇佐见莲子
500 | - 月之妖鸟
501 | - E
502 | - rank_cn7: 22
503 | - - uniform
504 | - - 83
505 | - 魅魔
506 | - 将命运托付给久远之梦的精神
507 | - FGHJ
508 | - rank_cn7: 68
509 | - - bba
510 | - - 86
511 | - 神玉
512 | - 门番
513 | - F
514 | - rank_cn7: 165
515 | - []
516 | - - 87
517 | - 幽玄魔眼
518 | - 邪恶之眼
519 | - F
520 | - rank_cn7: 156
521 | - []
522 | - - 88
523 | - 依莉斯
524 | - 无罪的恶魔
525 | - F
526 | - rank_cn7: 141
527 | - - loli
528 | - - 89
529 | - 萨丽爱尔
530 | - 死天使
531 | - F
532 | - rank_cn7: 106
533 | - []
534 | - - 90
535 | - 菊理
536 | - 地狱之月
537 | - F
538 | - rank_cn7: 175
539 | - []
540 | - - 91
541 | - 矜羯罗
542 | - 星幽剑士
543 | - F
544 | - rank_cn7: 139
545 | - - bba
546 | - - 92
547 | - 里香
548 | - 战车少女
549 | - G
550 | - rank_cn7: 126
551 | - []
552 | - - 93
553 | - 明罗
554 | - 武士
555 | - G
556 | - rank_cn7: 128
557 | - []
558 | - - 94
559 | - 爱莲
560 | - 在工作的人身上梦见恋爱的魔女
561 | - H
562 | - rank_cn7: 116
563 | - - loli
564 | - - 95
565 | - 小兔姬
566 | - 在弹幕中梦见美的公主殿下
567 | - H
568 | - rank_cn7: 125
569 | - []
570 | - - 96
571 | - 卡娜·安娜贝拉尔
572 | - 失去梦的少女骚灵
573 | - H
574 | - rank_cn7: 103
575 | - []
576 | - - 97
577 | - 朝仓理香子
578 | - 探寻梦想的科学家
579 | - H
580 | - rank_cn7: 135
581 | - - uniform
582 | - - 98
583 | - 北白河千百合
584 | - 超越时空的梦幻居民
585 | - H
586 | - rank_cn7: 130
587 | - - uniform
588 | - - 99
589 | - 冈崎梦美
590 | - 梦幻传说
591 | - H
592 | - rank_cn7: 59
593 | - []
594 | - - 100
595 | - 奥莲姬
596 | - 妖怪
597 | - I
598 | - rank_cn7: 149
599 | - []
600 | - - 101
601 | - 胡桃
602 | - 吸血少女
603 | - I
604 | - rank_cn7: 119
605 | - - loli
606 | - - 102
607 | - 艾丽
608 | - 洋馆的门番
609 | - I
610 | - rank_cn7: 132
611 | - []
612 | - - 103
613 | - 梦月
614 | - 女仆幻想
615 | - I
616 | - rank_cn7: 122
617 | - - loli
618 | - uniform
619 | - - 104
620 | - 幻月
621 | - 可爱的恶魔
622 | - I
623 | - rank_cn7: 97
624 | - - loli
625 | - - 105
626 | - 萨拉
627 | - 门扉的看守者
628 | - J
629 | - rank_cn7: 166
630 | - []
631 | - - 106
632 | - 露易兹
633 | - 魔界人
634 | - J
635 | - rank_cn7: 150
636 | - []
637 | - - 107
638 | - 雪
639 | - 黑色的魔法使
640 | - J
641 | - rank_cn7: 137
642 | - []
643 | - - 108
644 | - 舞
645 | - 白色的魔法使
646 | - J
647 | - rank_cn7: 134
648 | - []
649 | - - 109
650 | - 梦子
651 | - 魔界女仆
652 | - J
653 | - rank_cn7: 121
654 | - - uniform
655 | - - 110
656 | - 神绮
657 | - 魔界之神
658 | - J
659 | - rank_cn7: 52
660 | - - bba
661 | - - 111
662 | - 玄爷
663 | - 飞行龟
664 | - G
665 | - rank_cn7: 144
666 | - []
667 | - - 112
668 | - 云山
669 | - 见越入道
670 | - gA
671 | - rank_cn7: 133
672 | - []
673 | - - 113
674 | - 蕾拉·普莉兹姆利巴
675 | - 虹之川的幺女
676 | - b
677 | - rank_cn7: 123
678 | - - loli
679 | - - 116
680 | - 魂魄妖忌
681 | - 妖梦的祖父
682 | - b
683 | - rank_cn7: 114
684 | - []
685 | - - 119
686 | - 朱鹭子
687 | - 无名的读书妖怪
688 | - D
689 | - alias_cn: 无名的读书妖怪(朱鹭子)
690 | - - loli
691 | - - 120
692 | - 爱丽丝的人偶
693 | - 蓬莱人偶
694 | - b
695 | - alias_cn: 人偶(含上海人偶、哥利亚人偶)
696 | - - loli
697 | - - 121
698 | - 本居小铃
699 | - 识文解意的爱书人
700 | - D
701 | - rank_cn7: 51
702 | - - loli
703 | - - 122
704 | - 秦心
705 | - 表情丰富的扑克脸
706 | - A
707 | - rank_cn7: 18
708 | - []
709 | - - 123
710 | - 宇佐见堇子
711 | - 秘封俱乐部初代会长
712 | - AB
713 | - rank_cn7: 40
714 | - []
715 | - - 124
716 | - 若鹭姬
717 | - 栖息于淡水的人鱼
718 | - i
719 | - rank_cn7: 91
720 | - - baka
721 | - - 125
722 | - 赤蛮奇
723 | - 辘轳首的怪奇
724 | - i
725 | - rank_cn7: 78
726 | - - baka
727 | - - 126
728 | - 今泉影狼
729 | - 竹林的狼人
730 | - i
731 | - rank_cn7: 80
732 | - - beast
733 | - baka
734 | - - 127
735 | - 九十九弁弁
736 | - 古琵琶的付丧神
737 | - i
738 | - rank_cn7: 86
739 | - []
740 | - - 128
741 | - 九十九八桥
742 | - 古琴的付丧神
743 | - i
744 | - rank_cn7: 111
745 | - []
746 | - - 129
747 | - 鬼人正邪
748 | - 逆袭的天邪鬼
749 | - iB
750 | - rank_cn7: 24
751 | - []
752 | - - 130
753 | - 少名针妙丸
754 | - 辉光之针的利立浦特
755 | - iA
756 | - rank_cn7: 37
757 | - - loli
758 | - - 131
759 | - 堀川雷鼓
760 | - 梦幻的打击乐手
761 | - i
762 | - rank_cn7: 60
763 | - - uniform
764 | - - 132
765 | - 清兰
766 | - 浅葱色的 Eagle Rabbit
767 | - j
768 | - rank_cn7: 110
769 | - - beast
770 | - - 133
771 | - 铃瑚
772 | - 橘色的 Eagle Rabbit
773 | - j
774 | - rank_cn7: 117
775 | - - beast
776 | - - 134
777 | - 哆来咪·苏伊特
778 | - 梦之支配者
779 | - jA
780 | - rank_cn7: 36
781 | - - beast
782 | - - 135
783 | - 稀神探女
784 | - 带来口舌之祸的女神
785 | - j
786 | - rank_cn7: 28
787 | - []
788 | - - 136
789 | - 克劳恩皮丝
790 | - 地狱的妖精
791 | - j
792 | - rank_cn7: 39
793 | - - yousei
794 | - baka
795 | - - 137
796 | - 纯狐
797 | - 无名的存在
798 | - j
799 | - rank_cn7: 25
800 | - - bba
801 | - - 138
802 | - 赫卡提亚·拉碧斯拉祖利
803 | - 地狱的女神
804 | - j
805 | - rank_cn7: 63
806 | - - bba
807 | - - 139
808 | - 易者
809 | - 占卜师
810 | - D
811 | - alias_cn: 占卜师(易者)
812 | - []
813 | - - 143
814 | - 爱塔妮缇·拉尔瓦
815 | - 接近神的蝶之妖精
816 | - k
817 | - alias_cn: 爱塔妮缇拉尔瓦
818 | - - loli
819 | - yousei
820 | - - 144
821 | - 坂田合欢乃
822 | - 跨越浮世门关的山姥
823 | - k
824 | - rank_cn7: 115
825 | - []
826 | - - 145
827 | - 高丽野阿吽
828 | - 醉心于神佛的守护神兽
829 | - k
830 | - rank_cn7: 75
831 | - - beast
832 | - - 146
833 | - 矢田寺成美
834 | - 垂迹森林的魔法地藏
835 | - k
836 | - rank_cn7: 76
837 | - []
838 | - - 147
839 | - 丁礼田舞
840 | - 过于危险的背景舞者
841 | - k
842 | - rank_cn7: 94
843 | - []
844 | - - 148
845 | - 尔子田里乃
846 | - 过于危险的背景舞者
847 | - k
848 | - rank_cn7: 99
849 | - []
850 | - - 149
851 | - 摩多罗隐岐奈
852 | - 究极的绝对秘神
853 | - k
854 | - rank_cn7: 31
855 | - - bba
856 | - - 150
857 | - 依神紫苑
858 | - 最凶最恶的双子之姐
859 | - A
860 | - rank_cn7: 23
861 | - []
862 | - - 151
863 | - 依神女苑
864 | - 最凶最恶的双子之妹
865 | - A
866 | - rank_cn7: 62
867 | - []
868 | - - 152
869 | - 留琴
870 | - 博丽神社的机械女仆
871 | - H
872 | - rank_cn7: 147
873 | - - uniform
874 | - - 153
875 | - 冴月麟
876 | - 神秘代码
877 | - a
878 | - rank_cn7: 72
879 | - - loli
880 | - - 154
881 | - 命莲
882 | - 飞仓的圣僧
883 | - g
884 | - rank_cn7: 140
885 | - []
886 | - - 155
887 | - 邪眼西格玛
888 | - 可爱的 ICBM
889 | - G
890 | - rank_cn7: 157
891 | - []
892 | - - 156
893 | - 咪咪号
894 | - 少女的梦之战车
895 | - H
896 | - rank_cn7: 177
897 | - []
--------------------------------------------------------------------------------
/lib/data/games.yaml:
--------------------------------------------------------------------------------
1 | integer:
2 | - tag: a
3 | name: 东方红魔乡
4 | - tag: b
5 | name: 东方妖妖梦
6 | - tag: c
7 | name: 东方永夜抄
8 | - tag: d
9 | name: 东方花映冢
10 | - tag: e
11 | name: 东方风神录
12 | - tag: f
13 | name: 东方地灵殿
14 | - tag: g
15 | name: 东方星莲船
16 | - tag: h
17 | name: 东方神灵庙
18 | - tag: i
19 | name: 东方辉针城
20 | - tag: j
21 | name: 东方绀珠传
22 | - tag: k
23 | name: 东方天空璋
24 |
25 | others:
26 | - tag: A
27 | name: 格斗作
28 | - tag: B
29 | name: 外传 STG
30 | - tag: D
31 | name: 官方书籍
32 | - tag: E
33 | name: 音乐 CD
34 |
35 | old:
36 | - tag: F
37 | name: 东方灵异传
38 | - tag: G
39 | name: 东方封魔录
40 | - tag: H
41 | name: 东方梦时空
42 | - tag: I
43 | name: 东方幻想乡
44 | - tag: J
45 | name: 东方怪绮谈
46 |
--------------------------------------------------------------------------------
/lib/data/index.js:
--------------------------------------------------------------------------------
1 | import RANK_CN7 from './rank_cn7'
2 |
3 | import SortNode from '../utils/SortNode'
4 | import characters from './characters'
5 | import games from './games'
6 | import tags from './tags'
7 |
8 | export const faces = {
9 | default: '正常',
10 | smiling: '笑脸',
11 | trauma: '疮痍',
12 | }
13 |
14 | const rankMap = {}
15 | const charMap = {}
16 | characters.forEach((rawChar) => {
17 | const char = new SortNode(...rawChar)
18 | charMap[char.name] = char
19 | rankMap[char.meta.alias_cn || char.name] = char.name
20 | })
21 |
22 | const charList = Object.keys(charMap)
23 |
24 | function filterRanks (ranks) {
25 | return ranks.map((name) => {
26 | if (!(name in rankMap)) return
27 | return rankMap[name]
28 | }).filter(n => n)
29 | }
30 |
31 | export const ranks = {
32 | cn7: filterRanks(RANK_CN7),
33 | }
34 |
35 | export { characters, games, tags, charMap, charList }
36 |
--------------------------------------------------------------------------------
/lib/data/rank_cn7.yaml:
--------------------------------------------------------------------------------
1 | # http://touhou.vote/v7/?m=chara&type=simple
2 |
3 | - 博丽灵梦
4 | - 雾雨魔理沙
5 | - 古明地恋
6 | - 蕾米莉亚·斯卡蕾特
7 | - 古明地觉
8 | - 八云紫
9 | - 十六夜咲夜
10 | - 西行寺幽幽子
11 | - 藤原妹红
12 | - 射命丸文
13 | - 爱丽丝·玛格特洛依德
14 | - 芙兰朵露·斯卡蕾特
15 | - 东风谷早苗
16 | - 魂魄妖梦
17 | - 铃仙·优昙华院·因幡
18 | - 比那名居天子
19 | - 琪露诺
20 | - 秦心
21 | - 蓬莱山辉夜
22 | - 帕秋莉·诺蕾姬
23 | - 四季映姬·亚玛萨那度
24 | - 宇佐见莲子
25 | - 依神紫苑
26 | - 鬼人正邪
27 | - 纯狐
28 | - 风见幽香
29 | - 伊吹萃香
30 | - 稀神探女
31 | - 玛艾露贝莉·赫恩(梅莉)
32 | - 洩矢诹访子
33 | - 摩多罗隐岐奈
34 | - 红美铃
35 | - 丰聪耳神子
36 | - 键山雏
37 | - 圣白莲
38 | - 哆来咪·苏伊特
39 | - 少名针妙丸
40 | - 多多良小伞
41 | - 克劳恩皮丝
42 | - 宇佐见堇子
43 | - 灵乌路空
44 | - 上白泽慧音
45 | - 露米娅
46 | - 稗田阿求
47 | - 八意永琳
48 | - 水桥帕露西
49 | - 犬走椛
50 | - 火焰猫燐
51 | - 茨木华扇
52 | - 封兽鵺
53 | - 本居小铃
54 | - 神绮
55 | - 大妖精
56 | - 八云蓝
57 | - 物部布都
58 | - 河城荷取
59 | - 霍青娥
60 | - 森近霖之助
61 | - 冈崎梦美
62 | - 堀川雷鼓
63 | - 莉莉白
64 | - 依神女苑
65 | - 赫卡提亚·拉碧斯拉祖利
66 | - 小恶魔
67 | - 八坂神奈子
68 | - 米斯蒂娅·萝蕾拉
69 | - 因幡帝
70 | - 魅魔
71 | - 二岩猯藏
72 | - 小野塚小町
73 | - 永江衣玖
74 | - 冴月麟
75 | - 橙
76 | - 村纱水蜜
77 | - 高丽野阿吽
78 | - 矢田寺成美
79 | - 露娜切尔德
80 | - 赤蛮奇
81 | - 占卜师(易者)
82 | - 今泉影狼
83 | - 星熊勇仪
84 | - 姬海棠果
85 | - 爱塔妮缇拉尔瓦
86 | - 斯塔萨菲雅
87 | - 秋静叶
88 | - 九十九弁弁
89 | - 梅蒂欣·梅兰可莉
90 | - 苏我屠自古
91 | - 幽谷响子
92 | - 桑尼米尔克
93 | - 若鹭姬
94 | - 秋穰子
95 | - 娜兹玲
96 | - 丁礼田舞
97 | - 宫古芳香
98 | - 绵月依姬
99 | - 幻月
100 | - 蕾蒂·霍瓦特洛克
101 | - 尔子田里乃
102 | - 人偶(含上海人偶、哥利亚人偶)
103 | - 露娜萨·普莉兹姆利巴
104 | - 莉格露·奈特巴格
105 | - 卡娜·安娜贝拉尔
106 | - 长相酷似周杰伦的参拜客
107 | - 毛玉
108 | - 萨丽爱尔
109 | - 绵月丰姬
110 | - 云居一轮
111 | - 黑谷山女
112 | - 清兰
113 | - 九十九八桥
114 | - 寅丸星
115 | - 无名的读书妖怪(朱鹭子)
116 | - 魂魄妖忌
117 | - 坂田合欢乃
118 | - 爱莲
119 | - 铃瑚
120 | - 梅露兰·普莉兹姆利巴
121 | - 胡桃
122 | - 大鲶鱼
123 | - 梦子
124 | - 梦月
125 | - 蕾拉·普莉兹姆利巴
126 | - 莉莉卡·普莉兹姆利巴
127 | - 小兔姬
128 | - 里香
129 | - 琪斯美
130 | - 明罗
131 | - 妖精(含持花妖精,僵尸妖精)
132 | - 北白河千百合
133 | - 假扮魔理沙的妖狐
134 | - 艾丽
135 | - 云山
136 | - 舞
137 | - 朝仓理香子
138 | - STG作品中没有名称的角色
139 | - 雪
140 | - 非想天则
141 | - 矜羯罗
142 | - 命莲
143 | - 依莉斯
144 | - 龙神
145 | - 秦河胜
146 | - 玄爷
147 | - Reisen
148 | - 嫦娥
149 | - 留琴
150 | - UFO
151 | - 奥莲姬
152 | - 露易兹
153 | - 求闻史纪中的受访人群
154 | - 阴阳玉
155 | - 苏格拉底
156 | - 月夜见
157 | - 酒吧老板
158 | - 幽玄魔眼
159 | - 邪眼西格玛
160 | - 岩笠
161 | - 久米 / 竿打
162 | - 万岁乐
163 | - 妖怪兔
164 | - 绿巨人
165 | - 阿菊
166 | - 黄帝
167 | - 神玉
168 | - 萨拉
169 | - 伊豆能卖
170 | - 邪龙
171 | - 怨灵
172 | - 河童(含山童)
173 | - 兜风四人组
174 | - 导航
175 | - 彭祖
176 | - 尼西号
177 | - 菊理
178 | - Flower~战车
179 | - 咪咪号
180 | - 玛○奇
181 | - 木花咲耶姬
182 | - 天照大御神
183 | - 野槌
184 | - 酒虫
185 | - 怨灵少女
186 | - 读书狐狸
187 | - 白仙和尚
188 | - 幽灵
189 | - 狸猫
190 | - 杀死大老爷的侍童
191 | - 八尺大人
192 | - 石长姬
193 | - 石凝老命
194 | - 天宇受卖命
195 | - 火雷神
196 | - 瑞江浦岛子
197 | - 前鬼 / 后鬼
198 | - 雷兽(务光)
199 | - 运松翁
200 | - 管狐
201 | - 水鬼鬼神长
202 | - 人面犬
203 | - 烟烟罗
204 | - 卓柏卡布拉
205 | - 沓颊
206 | - 稻荷大人
207 | - 佑天上人
208 | - 野铁炮
209 | - 地精
210 | - 座敷童子
211 | - 盐家老板
212 | - 马凭
213 | - 竹扫帚付丧神
214 | - 大老爷
215 | - 酒吧客人
216 | - 百鬼夜行绘卷妖怪(黑鬼)
217 | - 卖脚婆
218 | - 袈裟罗婆娑罗
219 | - 饿神
220 | - 睡鼠
221 |
--------------------------------------------------------------------------------
/lib/data/tags.yaml:
--------------------------------------------------------------------------------
1 | loli: 萝莉
2 | bba: BBA
3 | yousei: 妖精
4 | beast: 兽娘
5 | baka: 笨蛋
6 | uniform: 制服
7 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 |
3 | // workaround vuepress #1525
4 | const App = require('@vuepress/core/lib/node/App')
5 | App.prototype.addPage = async function (options) {
6 | const Page = require('@vuepress/core/lib/node/Page')
7 | options.permalinkPattern = this.siteConfig.permalink
8 | const page = new Page(options, this)
9 | await page.process({
10 | markdown: this.markdown,
11 | computed: new this.ClientComputedMixinConstructor(),
12 | enhancers: this.pluginAPI.getOption('extendPageData').items,
13 | })
14 | const index = this.pages.findIndex(({ path }) => path === page.path)
15 | if (index >= 0) {
16 | this.pages.splice(index, 1, page)
17 | } else {
18 | this.pages.push(page)
19 | }
20 | }
21 |
22 | module.exports = ({
23 | base = '/favorite/',
24 | }, context) => ({
25 | name: '@uzkk/favorite',
26 |
27 | plugins: [
28 | [require('@uzkk/favorite-assets')],
29 | ['@vuepress/register-components', {
30 | components: [
31 | { name: 'Favorite', path: resolve(__dirname, 'comp') },
32 | ],
33 | }],
34 | ],
35 |
36 | additionalPages: [{
37 | title: '本命角色测试',
38 | path: base,
39 | permalink: base,
40 | frontmatter: {
41 | description: '测试你的本命东方 Project 角色',
42 | layout: 'Favorite',
43 | },
44 | }, {
45 | title: '关于本命角色测试',
46 | path: base + 'about.html',
47 | permalink: base + 'about.html',
48 | frontmatter: {
49 | description: '关于本命角色测试',
50 | layout: 'Post',
51 | aside: false,
52 | },
53 | filePath: resolve(__dirname, 'pages/about.md'),
54 | }],
55 |
56 | enhanceAppFiles: {
57 | name: 'uzkk-favorite-base.js',
58 | content: `export default ({ Vue }) => {
59 | Vue.prototype.UZKK_FAVORITE_BASE = ${JSON.stringify(base)}
60 | }`,
61 | },
62 |
63 | chainWebpack (config) {
64 | config.module
65 | .rule('ts')
66 | .test(/\.ts$/)
67 | .exclude
68 | .add(path => !path.startsWith(__dirname))
69 | .end()
70 | .use('ts-loader')
71 | .loader('ts-loader')
72 | .options({
73 | configFile: resolve(__dirname, '../tsconfig.json'),
74 | })
75 | .end()
76 | },
77 | })
78 |
--------------------------------------------------------------------------------
/lib/pages/about.md:
--------------------------------------------------------------------------------
1 | 这个项目的灵感来源于日文网站 [東方キャラソート](http://readalittle.net/sort/)。在编写时也有许多细节参考了这个网站。我们在此基础上,进行了一些改变:
2 |
3 | 1. 将全部内容翻译成了中文。
4 | 2. 对网站进行了移动端适配。
5 | 3. 增删了一些角色(比如增加了冴月麟,删除了旧作与新作重合的角色)。
6 | 4. 基于原网站缺乏角色在后期作品中出场的记录,我们重新将全部角色进行分类。
7 | 5. 结果页增加了与人气投票的对比,并添加了标签取向判断的功能。
8 |
9 | 下面是大家或许好奇的一些问题,我们将一一解答。
10 |
11 | ## 关于算法
12 |
13 | 这种排序的原理是 [Pairwise Comparison](https://en.wikipedia.org/wiki/Pairwise_comparison)。根据选择的顺序,结果可能会发生不同。排序的过程使用了堆排序。
14 |
15 | 计算标签取向的算法如下:假设你已经选出了你最喜欢的 $m$ 名角色,则将这些角色及人气排名前 $m$ 名取并集(设这个集合的大小为 $n$)。设 $r_i,p_i$ 分别表示第 $i$ 名角色你的排名结果和人气排名的结果(如果未出现在某个排名中,则取 $m+1$),并计算下面的值:
16 |
17 | $$u_i=\tanh{\frac{p_i-r_i}{m}},\ v_i=\frac{1}{1+r_i}$$
18 |
19 | 从这些角色中取出所有含有第 $k$ 个标签的角色,设对应的下标集为 $\mathcal{I}$,则该标签的参考值为:
20 |
21 | $$w_k=\frac{\sum_{i\in\mathcal{I}}u_i v_i}{\sum_{i=1}^{n}v_i}$$
22 |
23 | 这种计算方法仅供参考。如果有更好的建议,欢迎向我们提出。
24 |
25 | ## 更新日志
26 |
27 | ### 2019-4-27
28 |
29 | - 添加了“命莲”等三个角色
30 | - 修复了记忆功能对全选角色失效的问题
31 |
32 | ### 2019-04-15
33 |
34 | - 可以选择全部角色进行排序了
35 | - 为十六夜咲夜和部分旧作角色添加了标签
36 | - 优化了结果页面对手机屏幕的适配
37 |
38 | ### 2019-04-12
39 |
40 | - 新增了“疮痍”系列图片
41 | - 优化了偏好参考值的算法
42 | - 结果页面添加了统计信息
43 |
44 | ### 2019-04-11
45 |
46 | - 将外传角色加入到默认配置中
47 | - 添加了根据偏好参考值生成的提示语
48 | - 设置界面增加了记忆功能,进入时将自动恢复到上一次的设置
49 |
50 | ### 2019-04-09
51 |
52 | - 新增了随机选择功能
53 | - 选择界面增加了 50 和 100 角色的选项
54 | - 优化了按钮和单选框在手机界面上的显示
55 |
56 | ### 2019-04-07
57 |
58 | - 重写了计算偏好参考值的逻辑
59 | - 使用 TypeScript 重构了排序部分
60 | - 取消了魂魄妖梦的萝莉标签
61 |
62 | ### 2019-04-05
63 |
64 | - 新增了“笨蛋”和“制服”两个标签
65 | - 将分类“东方文花帖”和“妖精大战争”合并为“外传 STG”
66 | - 修复了一些人名错误(如:爱丽丝·玛格特洛依德)
67 | - 修复了辉针城以后部分角色的出场信息缺失(如:琪露诺)
68 |
69 | ### 2019-04-03
70 |
71 | - 优化了结果界面
72 | - 对网站进行了移动端适配
73 | - 将分类“旧作”进行进一步细分
74 | - 新增了“妖精”和“兽娘”两个标签
75 |
76 | ### 2019-3-25
77 |
78 | - 优化了设置界面
79 | - 支持在选择时回退一步
80 | - 新增了标签系统,包含“萝莉”和“BBA”两个标签
81 |
82 | ### 2019-3-23
83 |
84 | - 初步实现原网站的功能
85 | - 将全部内容翻译成了中文
86 | - 增删了一些角色(比如增加了冴月麟,删除了旧作与新作重合的角色)
87 |
88 | ## 制作人员
89 |
90 |
91 |
92 |
93 | 部分原作没有的称号由 @荭茶 和 @龙翼雨 提供。
94 |
95 | ## 版权与协议
96 |
97 | 命莲、邪眼西格玛及咪咪号的图片采用了画师 [kaoru](https://www.pixiv.net/member_illust.php?id=743845) 的作品,其他角色图片采用了画师 [dairi](https://www.pixiv.net/member_illust.php?id=4920496) 的作品。全部人物来源于东方 Project,版权属于上海爱丽丝幻乐团。
98 |
99 | 源代码已经开源到 [GitHub](https://github.com/uzkk/favorite),遵循 [MIT](https://mit-license.org/) 协议。脚本的使用,修改,复制等是免费的。
100 |
101 |
102 | 点此返回测试页面
103 |
104 |
--------------------------------------------------------------------------------
/lib/styles/index.styl:
--------------------------------------------------------------------------------
1 | .favorite .emphasize
2 | color #d00
3 |
4 | .favorite .section
5 | a:hover
6 | text-decoration underline
7 |
8 | h3
9 | font-size 1.2em
10 |
11 | hr
12 | display block
13 | border 0
14 | border-top 2px solid $borderColor
15 | margin 0.5em 0
16 | transform translateY(-1px)
17 | padding 0
18 |
19 | a:hover
20 | text-decoration underline
21 |
22 | hr
23 | display block
24 | border 0
25 | border-top 2px solid $borderColor
26 | margin 0.6em 0
27 | transform translateY(-1px)
28 | padding 0
29 |
30 | > :first-child
31 | margin-top 0 !important
32 |
33 | > :last-child
34 | margin-bottom 0 !important
35 |
36 | p, ul
37 | margin 0.5em 0
38 | line-height 1.6
39 |
40 | p.comment
41 | margin 0.2em 0
42 |
43 | p.title
44 | font-weight bold
45 |
46 | p.list
47 | margin 0
48 | > :first-child
49 | font-weight bold
50 | display inline-block
51 | margin 0.5em 0
52 | &:not(:last-child) > :not(:first-child)
53 | margin-top 0
54 | &:first-child > *
55 | margin-top 0
56 | &:last-child > *
57 | margin-bottom 0
58 | & + p.list
59 | margin-top -0.5em
60 |
61 | ul
62 | padding-inline-start 1.6em
63 | &.inline
64 | display inline-block
65 |
66 | li
67 | list-style-type none
68 | &.inline
69 | display inline-block
70 | &.medium
71 | min-width 9em
72 | &.short
73 | min-width 5em
74 |
75 | &.collapse-view
76 | .collapse-header h3
77 | margin 0
78 | .collapse-content > :first-child
79 | margin-top 1em
80 |
81 | table
82 | max-width 100%
83 | border-collapse collapse
84 | margin 1.5rem auto 0
85 | text-align center
86 |
87 | tr
88 | border-top 1px solid #dfe2e5
89 | &:nth-child(2n)
90 | background-color #f6f8fa
91 |
92 | th, td
93 | border 1px solid #dfe2e5
94 | padding .5em 1em
95 |
96 | .favorite .button-container
97 | margin 1.8em auto
98 | width 30%
99 | max-width 20em
100 | min-width 14em
101 | text-align center
102 |
103 | button
104 | width 100%
105 | display block
106 | margin 0.6em 0
107 |
--------------------------------------------------------------------------------
/lib/utils/BackupTree.ts:
--------------------------------------------------------------------------------
1 | type Character = [number, string, string, string, Record, string[]]
2 |
3 | // @ts-ignore
4 | import { characters } from '../data'
5 | import SortNode from './SortNode'
6 |
7 | export default class BackupTree {
8 | id: number = -1
9 | isEven: boolean = false
10 | nodes: BackupTree[] = []
11 | cTree: SortNode = null
12 |
13 | constructor () {}
14 |
15 | /**
16 | * set root node
17 | */
18 | public setup (root: SortNode) {
19 | this.cTree = new SortNode(0)
20 | this.nodes = []
21 | this.init(root)
22 | return this
23 | }
24 |
25 | /**
26 | * generate ID tree from current character ranks
27 | */
28 | private init (root: SortNode, tree: BackupTree = this) {
29 | for (const child of root.children) {
30 | const node = new BackupTree()
31 | node.id = child.id
32 | node.isEven = child.isEven
33 | tree.nodes.push(node)
34 | this.init(child, node)
35 | }
36 | }
37 |
38 | /**
39 | * restore character ranks from ID tree
40 | */
41 | public restore (idTree: BackupTree = this, cNode = this.cTree) {
42 | var len = idTree.nodes.length
43 | for (var i = 0; i < len; i++) {
44 | var nodeId = idTree.nodes[i].id
45 | var isEven = idTree.nodes[i].isEven
46 | var cItem = null
47 | for (var j = 0; j < characters.length; j++) {
48 | if (nodeId === characters[j][0]) {
49 | cItem = new SortNode(...characters[j])
50 | cItem.isEven = isEven
51 | break
52 | }
53 | }
54 | // why the hell is this NOT FOUND ???
55 | if (cItem === null) {
56 | continue
57 | }
58 | cNode.add(cItem, false)
59 | this.restore(idTree.nodes[i], cItem)
60 | }
61 | return this.cTree
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/lib/utils/SortNode.ts:
--------------------------------------------------------------------------------
1 | export default class SortNode {
2 | public isEven: boolean = false
3 | public parent: SortNode = null
4 | public children: SortNode[] = []
5 |
6 | constructor (
7 | public id: number = 0,
8 | public name: string = '',
9 | public nick: string = '',
10 | public appearence: string = '',
11 | public meta: Record = {},
12 | public tags: string[] = [],
13 | ) {}
14 |
15 | /**
16 | * clone a node
17 | */
18 | clone () {
19 | return new SortNode(
20 | this.id,
21 | this.name,
22 | this.nick,
23 | this.appearence,
24 | this.meta,
25 | this.tags,
26 | )
27 | }
28 |
29 | /**
30 | * get current rank
31 | */
32 | rank () {
33 | return this.parent
34 | ? this.isEven
35 | ? this.parent.rank()
36 | : this.level()
37 | : 0
38 | }
39 |
40 | /**
41 | * get node depth
42 | */
43 | level () {
44 | if (!this.parent) return 0
45 | return this.parent.level() + 1
46 | }
47 |
48 | /**
49 | * add child node
50 | */
51 | add (child: SortNode, doEvenAction = false) {
52 | // remove node from its parent
53 | if (child.parent) {
54 | child.parent.children.splice(child.parent.children.indexOf(child), 1)
55 | }
56 |
57 | // handle even
58 | if (doEvenAction) {
59 | var copies = this.children.splice(0, this.children.length)
60 | for (var i = copies.length - 1; i >= 0; i--) {
61 | copies[i].parent = child
62 | }
63 | child.children.push(...copies)
64 | }
65 |
66 | this.children.push(child)
67 | child.parent = this
68 | }
69 |
70 | /**
71 | * remove current node
72 | */
73 | remove () {
74 | while (this.children.length > 0) {
75 | this.parent.add(this.children[0], false)
76 | }
77 | this.parent.children.splice(this.parent.children.indexOf(this), 1)
78 | }
79 |
80 | /**
81 | * 全体结点中搜索 resourceId 并返回 SortObject。没找到则返回 null。
82 | */
83 | findSortObjectById (resourceId) {
84 | if (this.id === resourceId) {
85 | return this
86 | }
87 | var len = this.children.length
88 | for (var i = 0; i < len; i++) {
89 | var result = this.children[i].findSortObjectById(resourceId)
90 | if (result !== null) {
91 | return result
92 | }
93 | }
94 | return null
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/lib/utils/index.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import { ranks, charMap, tags, charList } from '../data'
3 | import SortNode from './SortNode'
4 |
5 | function getRank (name: string, ranks: string[]) {
6 | const index = ranks.indexOf(name)
7 | return index === -1 ? ranks.length : index
8 | }
9 |
10 | export function getCharactersInRange (range: string, chars: string[] = charList) {
11 | return chars.filter((name) => {
12 | for (const char of charMap[name].appearence) {
13 | if (range.includes(char)) return true
14 | }
15 | return false
16 | })
17 | }
18 |
19 | export function getPreference (userRanking: string[], range: string) {
20 | const { length } = userRanking
21 | let denominator = 0
22 | const popRanking = getCharactersInRange(range, ranks.cn7).slice(0, length)
23 | const rankingChars = Array
24 | .from(new Set([...popRanking, ...userRanking]))
25 | .map((name) => {
26 | const userRank = getRank(name, userRanking)
27 | const popRank = getRank(name, popRanking)
28 | const weight = 1 / (2 + userRank)
29 | denominator += weight
30 | return {
31 | node: charMap[name] as SortNode,
32 | value: Math.tanh((popRank - userRank) / length) * weight,
33 | }
34 | })
35 |
36 | const preference = []
37 | for (const tag in tags) {
38 | const name = tags[tag]
39 |
40 | const chars = rankingChars.filter(({ node }) => node.tags.includes(tag))
41 | if (!chars.length) continue
42 |
43 | const value = chars.reduce((sum, { value }) => sum + value, 0) / denominator
44 | preference.push({ tag, name, value })
45 | }
46 |
47 | return preference
48 | .filter(p => p.value > 0)
49 | .sort((a, b) => a.value > b.value ? -1 : 1)
50 | }
51 |
52 | type Group = [string, number, number]
53 |
54 | function group (length: number, groupLength: number, startIndex: number = 0) {
55 | length -= startIndex
56 | const groups = new Array(Math.ceil(length / groupLength)).fill(undefined)
57 | groups[groups.length - 1] = length % groupLength
58 | return groups.map((_, index): Group => {
59 | if (index < groups.length - 1) {
60 | const start = groupLength * index + startIndex
61 | return ['sm', start, groupLength + start]
62 | }
63 | const end = length + startIndex
64 | return ['sm', end - (length % groupLength || groupLength), end]
65 | })
66 | }
67 |
68 | const groupMap: Record Group[]> = {
69 | 2 (length) {
70 | switch (length % 2) {
71 | case 0: return group(length, 2)
72 | case 1: return [['lg', 0, 1], ...group(length, 2, 1)]
73 | }
74 | },
75 | 3 (length) {
76 | switch (length % 3) {
77 | case 0: return [['lg', 0, 1], ['md', 1, 3], ...group(length, 3, 3)]
78 | case 1: return [['lg', 0, 1], ...group(length, 3, 1)]
79 | case 2: return [['lg', 0, 2], ...group(length, 3, 2)]
80 | }
81 | },
82 | 4 (length) {
83 | if (length === 1) return [['lg', 0, 1]]
84 | switch (length % 4) {
85 | case 0: return [['lg', 0, 1], ['sm', 1, 4], ...group(length, 4, 4)]
86 | case 1: return [['lg', 0, 2], ['sm', 2, 5], ...group(length, 4, 5)]
87 | case 2: return [['lg', 0, 2], ...group(length, 4, 2)]
88 | case 3: return [['lg', 0, 1], ['md', 2, 3], ...group(length, 4, 3)]
89 | }
90 | },
91 | 5 (length) {
92 | if (length === 1) return [['lg', 0, 1]]
93 | if (length === 2) return [['lg', 0, 2]]
94 | if (length === 3) return [['lg', 0, 1], ['md', 1, 3]]
95 | switch (length % 5) {
96 | case 0: return [['lg', 0, 2], ['md', 2, 5], ...group(length, 5, 5)]
97 | case 1: return [['lg', 0, 1], ['md', 1, 3], ['md', 3, 6], ...group(length, 5, 6)]
98 | case 2: return [['lg', 0, 1], ['md', 1, 4], ['md', 4, 7], ...group(length, 5, 7)]
99 | case 3: return [['lg', 0, 2], ['md', 2, 5], ['md', 5, 8], ...group(length, 5, 8)]
100 | case 4: return [['lg', 0, 1], ['md', 1, 4], ...group(length, 5, 4)]
101 | }
102 | },
103 | 6 (length) {
104 | if (length === 1) return [['lg', 0, 1]]
105 | if (length === 2) return [['lg', 0, 2]]
106 | if (length === 3) return [['lg', 0, 1], ['md', 1, 3]]
107 | if (length === 4) return [['lg', 0, 1], ['md', 1, 4]]
108 | if (length === 5) return [['lg', 0, 2], ['md', 2, 5]]
109 | switch (length % 6) {
110 | case 0: return [['lg', 0, 1], ['md', 1, 3], ['md', 3, 6], ...group(length, 6, 6)]
111 | case 1: return [['lg', 0, 1], ['md', 1, 4], ['md', 4, 7], ...group(length, 6, 7)]
112 | case 2: return [['lg', 0, 2], ['md', 2, 5], ['md', 5, 8], ...group(length, 6, 8)]
113 | case 3: return [['lg', 0, 2], ['md', 2, 5], ['md', 5, 9], ...group(length, 6, 9)]
114 | case 4: return [['lg', 0, 2], ['md', 2, 5], ['sm', 5, 10], ...group(length, 6, 10)]
115 | case 5: return [['lg', 0, 2], ['md', 2, 6], ['sm', 6, 11], ...group(length, 6, 11)]
116 | }
117 | },
118 | }
119 |
120 | // .main-container padding 2em (+ scrollbar 1em) = 48px
121 | // .char-view = 16px * (10 + 1.5 * 2) = 208px
122 | // .char-view.lg = 208px * 1.125 = 234px
123 | // .char-view.sm = 208px * 0.75 = 156px
124 | export function groupByWidth (length: number, width: number) {
125 | const groupLength = Math.max(Math.min(Math.floor((width - 49) / 156), 6), 2)
126 | return groupMap[groupLength](length)
127 | }
128 |
--------------------------------------------------------------------------------
/lib/utils/settings.ts:
--------------------------------------------------------------------------------
1 | const VERSION = 1
2 |
3 | const getFallback = () => ({
4 | ranknum: 1,
5 | face: 'default',
6 | range: 'abcdefghijkABDE',
7 | })
8 |
9 | export function getSettings () {
10 | const fallbackSettings = getFallback()
11 |
12 | if (typeof localStorage === 'undefined') return fallbackSettings
13 |
14 | const oldSettings = localStorage.getItem('uzkk.favorite.settings')
15 | if (!oldSettings) return fallbackSettings
16 |
17 | try {
18 | const { version, settings } = JSON.parse(oldSettings)
19 | if (version === VERSION) {
20 | return { ...fallbackSettings, ...settings }
21 | } else {
22 | return fallbackSettings
23 | }
24 | } catch (error) {
25 | console.warn('An error was encounted when parsing settings:\n' + oldSettings)
26 | return fallbackSettings
27 | }
28 | }
29 |
30 | export function setSettings (settings) {
31 | const userSettings = {}
32 | for (const key in getFallback()) {
33 | userSettings[key] = settings[key]
34 | }
35 |
36 | if (typeof localStorage !== 'undefined') {
37 | try {
38 | const newSettings = { version: VERSION, settings: userSettings }
39 | localStorage.setItem('uzkk.favorite.settings', JSON.stringify(newSettings))
40 | } catch (error) {
41 | console.warn('An error was encounted when stringifying settings.')
42 | console.warn(error)
43 | }
44 | }
45 |
46 | return userSettings
47 | }
48 |
49 | export function useFallback (settings) {
50 | const fallbackSettings = getFallback()
51 | return Object.assign(settings, fallbackSettings)
52 | }
53 |
--------------------------------------------------------------------------------
/lib/utils/sort.mixin.ts:
--------------------------------------------------------------------------------
1 | import BackupTree from './BackupTree'
2 |
3 | export default {
4 | data: () => ({
5 | currentPair: [],
6 | questionCount: 1,
7 | currentRank: 0,
8 | isPrevious: false,
9 | }),
10 |
11 | methods: {
12 | init () {
13 | this.currentPair = this.ask(this.root)
14 | this.backupTree = new BackupTree().setup(this.root)
15 | },
16 | backup () {
17 | this.backupPair = this.currentPair.slice()
18 | this.backupTree.setup(this.root)
19 | },
20 | restore () {
21 | this.currentPair = this.backupPair.slice()
22 | this.backupPair = null
23 | this.currentRank = 0
24 | this.root = this.backupTree.restore()
25 | },
26 | ask (node, pair) {
27 | if (pair) {
28 | const left = node.findSortObjectById(pair[0].id)
29 | const right = node.findSortObjectById(pair[1].id)
30 | if (left !== null && right !== null) {
31 | return [left, right]
32 | }
33 | }
34 | if (node.children.length === 0) {
35 | return false
36 | }
37 | if (node.children.length === 1) {
38 | this.currentRank = node.level() + 1
39 | return this.ask(node.children[0])
40 | }
41 | const both = [0, 0]
42 | while (true) {
43 | if (both[0] !== both[1]) {
44 | break
45 | }
46 | for (const i of [0, 1]) {
47 | both[i] = Math.floor(Math.random() * node.children.length)
48 | }
49 | }
50 | return [node.children[both[0]], node.children[both[1]]]
51 | },
52 | getNextPair (back: boolean) {
53 | if (back) {
54 | this.currentPair = this.ask(this.root, this.currentPair)
55 | } else {
56 | this.currentPair = this.ask(this.root)
57 | }
58 | if (this.currentPair && this.currentPair[1].level() <= this.ranknum) {
59 | this.questionCount += 1
60 | return true
61 | }
62 | return false
63 | },
64 | moveOn (back: boolean) {
65 | return this.getNextPair(back)
66 | },
67 | selectChar (index: number) {
68 | this.backup()
69 | this.currentPair[index].add(this.currentPair[1 - index], false)
70 | this.isPrevious = false
71 | this.moveOn(false)
72 | },
73 | exclude (...indices) {
74 | this.backup()
75 | for (let i of indices) {
76 | this.currentPair[i].remove()
77 | }
78 | this.moveOn(false)
79 | },
80 | previous () {
81 | this.restore()
82 | this.questionCount -= 2
83 | this.isPrevious = true
84 | this.moveOn(true)
85 | },
86 | randomPick () {
87 | const index = Math.floor(Math.random() * 2)
88 | this.selectChar(index)
89 | },
90 | randomPickForAll () {
91 | let index = Math.floor(Math.random() * 2)
92 | this.currentPair[index].add(this.currentPair[1 - index], false)
93 | let pair = this.ask(this.root)
94 | while (pair && pair[1].level() <= this.ranknum) {
95 | this.questionCount += 1
96 | index = Math.floor(Math.random() * 2)
97 | pair[index].add(pair[1 - index], false)
98 | pair = this.ask(this.root)
99 | }
100 | this.moveOn(false)
101 | },
102 | },
103 | }
104 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@uzkk/favorite",
3 | "version": "1.3.0",
4 | "main": "lib/index.js",
5 | "author": "Shigma <1700011071@pku.edu.cn>",
6 | "license": "MIT",
7 | "files": [
8 | "lib",
9 | "tsconfig.json"
10 | ],
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/uzkk/favorite.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/uzkk/favorite/issues"
17 | },
18 | "homepage": "https://vp.uzkk.net/favorite/",
19 | "scripts": {
20 | "build": "vuepress build docs",
21 | "dev": "vuepress dev docs",
22 | "serve": "vuepress serve docs",
23 | "lint": "eslint . --fix"
24 | },
25 | "devDependencies": {
26 | "@uzkk/shared-assets": "^0.0.4",
27 | "@uzkk/not-found": "^1.0.1",
28 | "eslint": "^5.15.1",
29 | "eslint-config-standard": "^12.0.0",
30 | "eslint-plugin-import": "^2.14.0",
31 | "eslint-plugin-node": "^8.0.0",
32 | "eslint-plugin-promise": "^4.0.1",
33 | "eslint-plugin-standard": "^4.0.0",
34 | "eslint-plugin-vue": "^5.2.2",
35 | "vuepress": "^1.0.0-alpha.47",
36 | "vuepress-theme-uzkk": "^1.0.0-alpha.31"
37 | },
38 | "dependencies": {
39 | "@uzkk/favorite-assets": "^0.0.8",
40 | "js-yaml-loader": "^1.0.1",
41 | "ts-loader": "^5.3.3",
42 | "typescript": "^3.4.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "allowJs": true,
6 | },
7 | "include": [
8 | "lib/utils"
9 | ]
10 | }
--------------------------------------------------------------------------------