mirror of
https://git.unlock-music.dev/um/um-react.git
synced 2025-05-23 16:27:41 +08:00
Compare commits
6 Commits
3ab73d8369
...
b33ffa6ca7
Author | SHA1 | Date | |
---|---|---|---|
|
b33ffa6ca7 | ||
|
09c1bc474e | ||
|
e0b3bd60c2 | ||
|
6371c58cd5 | ||
|
98f1be9ac7 | ||
|
3541af7a96 |
2
public/_redirects
Normal file
2
public/_redirects
Normal file
@ -0,0 +1,2 @@
|
||||
# Support SPA routing in Netlify
|
||||
/* /index.html 200
|
52
src/App.css
52
src/App.css
@ -1,8 +1,43 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@plugin "daisyui" {
|
||||
themes:
|
||||
emerald --default,
|
||||
dracula --prefersdark;
|
||||
themes: dracula --prefersdark;
|
||||
}
|
||||
|
||||
/* theme: https://daisyui.com/theme-generator/#theme=eJx1kuFupCAUhV-FmGyym1TCBQHt2zB67Zg6MAFNu2323ZdhGkdH_Annu9dzjnwX1lyweC1OZjS2xa54KcrSm26YQxlwxHZyPsqMSo-XtdgPOHZZ5eQ-t_dh-MLtMr7VHrsewsn5Dm80XD_TRYfX6Xw7p5N1Q8CfU-tG58vQnjFFGYe385Sgu3AyAUtgLErufWzPv5v6F2GE_Xlm-JpRkaGMAeG1okLLHS3WNL_TinBVUSlgR7fOTminZYKznQeL8-TNuCBCHyG7bZlEg-3dolfJHzRN9CepEvWaDHPbYggLLO-wrAjIGEo1a_jDeDvYtwVWqSfQDZE1FbBZjN6n__2z9haH8koSrqng8tnsPlQaYFARLiuqWJUxfTTEgYBSFEBkzOfri0OcNJJyrXchDr0JAjHMJvXVDxfj_z4aYqkhqAnUFdVPMTBu7jI4j0-JA9V8E8DE0CsXElKl0Tco2giZsXGUNbYKNYv7s24Op-IDYozWOuNqPyPSjOC3jjSkVjvszTxGZvIzvhRXjz36ED_5Xrz2Zgz47z_whE0z */
|
||||
@plugin "daisyui/theme" {
|
||||
name: 'balanced';
|
||||
default: true;
|
||||
prefersdark: false;
|
||||
color-scheme: 'light';
|
||||
--color-base-100: oklch(98% 0 0);
|
||||
--color-base-200: oklch(96% 0.001 286.375);
|
||||
--color-base-300: oklch(92% 0.006 264.531);
|
||||
--color-base-content: oklch(20% 0 0);
|
||||
--color-primary: oklch(60% 0.118 184.704);
|
||||
--color-primary-content: oklch(98% 0.014 180.72);
|
||||
--color-secondary: oklch(60% 0.126 221.723);
|
||||
--color-secondary-content: oklch(98% 0.019 200.873);
|
||||
--color-accent: oklch(51% 0.222 16.935);
|
||||
--color-accent-content: oklch(93% 0.032 17.717);
|
||||
--color-neutral: oklch(37% 0 0);
|
||||
--color-neutral-content: oklch(98% 0 0);
|
||||
--color-info: oklch(42% 0.199 265.638);
|
||||
--color-info-content: oklch(97% 0.014 254.604);
|
||||
--color-success: oklch(52% 0.154 150.069);
|
||||
--color-success-content: oklch(97% 0.021 166.113);
|
||||
--color-warning: oklch(66% 0.179 58.318);
|
||||
--color-warning-content: oklch(98% 0.022 95.277);
|
||||
--color-error: oklch(57% 0.245 27.325);
|
||||
--color-error-content: oklch(97% 0.013 17.38);
|
||||
--radius-selector: 0.5rem;
|
||||
--radius-field: 0.5rem;
|
||||
--radius-box: 0.5rem;
|
||||
--size-selector: 0.25rem;
|
||||
--size-field: 0.25rem;
|
||||
--border: 1px;
|
||||
--depth: 1;
|
||||
--noise: 1;
|
||||
}
|
||||
@theme {
|
||||
--font-display:
|
||||
@ -19,3 +54,14 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
&:hover > a[data-anchor] {
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,8 @@ import { FaqTab } from '~/tabs/FaqTab';
|
||||
import { SETTINGS_TABS } from '~/features/settings/settingsTabs';
|
||||
import { Bounce, ToastContainer } from 'react-toastify';
|
||||
import { SettingsHome } from '~/features/settings/SettingsHome';
|
||||
import { FAQ_PAGES } from '~/faq/FAQPages';
|
||||
import { FaqHome } from '~/faq/FaqHome';
|
||||
|
||||
// Private to this file only.
|
||||
const store = setupStore();
|
||||
@ -49,7 +51,12 @@ export function AppRoot() {
|
||||
<Route key={key} path={key} Component={Tab} />
|
||||
))}
|
||||
</Route>
|
||||
<Route path="/questions" Component={FaqTab} />
|
||||
<Route path="/questions" Component={FaqTab}>
|
||||
<Route index Component={FaqHome} />
|
||||
{FAQ_PAGES.map(({ id, Component }) => (
|
||||
<Route key={id} path={id} Component={Component} />
|
||||
))}
|
||||
</Route>
|
||||
</Routes>
|
||||
</main>
|
||||
|
||||
|
9
src/components/HelpText/HeaderAnchor.tsx
Normal file
9
src/components/HelpText/HeaderAnchor.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { RiLink } from 'react-icons/ri';
|
||||
|
||||
export function HeaderAnchor({ id }: { id: string }) {
|
||||
return (
|
||||
<a href={`#${id}`} data-anchor={id} className="absolute -left-6 opacity-10 transition-opacity duration-200">
|
||||
<RiLink className="max-h-[.75em]" />
|
||||
</a>
|
||||
);
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { HeaderAnchor } from './HeaderAnchor';
|
||||
|
||||
export interface HeaderProps {
|
||||
children: React.ReactNode;
|
||||
@ -6,9 +7,20 @@ export interface HeaderProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const commonHeaderClasses = 'relative flex items-center pt-3 pb-1 font-bold';
|
||||
|
||||
export function Header2({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h2 id={id} className={`${commonHeaderClasses} text-3xl border-b border-base-300 ${className}`}>
|
||||
{id && <HeaderAnchor id={id} />}
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
export function Header3({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h3 id={id} className={`text-2xl pt-3 pb-1 font-bold border-b border-base-300 ${className}`}>
|
||||
<h3 id={id} className={`${commonHeaderClasses} text-2xl border-b border-base-300 ${className}`}>
|
||||
{id && <HeaderAnchor id={id} />}
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
@ -16,7 +28,8 @@ export function Header3({ children, className, id }: HeaderProps) {
|
||||
|
||||
export function Header4({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h4 id={id} className={`text-xl pt-3 pb-1 font-semibold ${className}`}>
|
||||
<h4 id={id} className={`${commonHeaderClasses} text-xl ${className}`}>
|
||||
{id && <HeaderAnchor id={id} />}
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
@ -24,7 +37,8 @@ export function Header4({ children, className, id }: HeaderProps) {
|
||||
|
||||
export function Header5({ children, className, id }: HeaderProps) {
|
||||
return (
|
||||
<h5 id={id} className={`text-lg pt-3 pb-1 font-semibold ${className}`}>
|
||||
<h5 id={id} className={`${commonHeaderClasses} text-lg ${className}`}>
|
||||
{id && <HeaderAnchor id={id} />}
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
|
43
src/components/ImageFigure.tsx
Normal file
43
src/components/ImageFigure.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import classNames from 'classnames';
|
||||
import { useRef } from 'react';
|
||||
|
||||
export interface ImageFigureProps {
|
||||
srcSet: string;
|
||||
alt: string;
|
||||
className?: string;
|
||||
loading?: 'lazy' | 'eager';
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export function ImageFigure({ alt, srcSet, children, className, loading }: ImageFigureProps) {
|
||||
const refDialog = useRef<HTMLDialogElement>(null);
|
||||
|
||||
return (
|
||||
<figure className={classNames(className, 'inline-flex flex-col items-center')}>
|
||||
<img
|
||||
className={`rounded-md cursor-pointer border border-base-300 max-h-48`}
|
||||
loading={loading}
|
||||
srcSet={srcSet}
|
||||
alt={alt}
|
||||
onClick={() => refDialog?.current?.showModal()}
|
||||
/>
|
||||
{children && <figcaption className="text-sm text-base-content/70">{children}</figcaption>}
|
||||
|
||||
<dialog ref={refDialog} className="modal text-left">
|
||||
<div className="modal-box max-w-[50vw]">
|
||||
<form method="dialog">
|
||||
<button className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
</form>
|
||||
<h3 className="font-bold text-lg">查看图片</h3>
|
||||
|
||||
<figure className="flex flex-col justify-center text-center">
|
||||
<img srcSet={srcSet} alt={alt} />
|
||||
{children && <figcaption className="text-sm text-base-content/70">{children}</figcaption>}
|
||||
</figure>
|
||||
</div>
|
||||
<form method="dialog" className="modal-backdrop">
|
||||
<button>关闭</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</figure>
|
||||
);
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { Fragment, useId } from 'react';
|
||||
|
||||
export type InstructionTab = {
|
||||
@ -8,19 +9,24 @@ export type InstructionTab = {
|
||||
|
||||
export interface InstructionsTabsProps {
|
||||
tabs: InstructionTab[];
|
||||
limitHeight?: boolean;
|
||||
}
|
||||
|
||||
export function InstructionsTabs({ tabs }: InstructionsTabsProps) {
|
||||
export function InstructionsTabs({ limitHeight = false, tabs }: InstructionsTabsProps) {
|
||||
const id = useId();
|
||||
return (
|
||||
<div className="tabs tabs-lift max-h-[32rem] pb-4">
|
||||
<div className={classNames('tabs tabs-lift pb-4', { 'max-h-[32rem]': limitHeight })}>
|
||||
{tabs.map(({ id: _tabId, label, content }, index) => (
|
||||
<Fragment key={_tabId}>
|
||||
<label className="tab">
|
||||
<input type="radio" name={id} defaultChecked={index === 0} />
|
||||
{label}
|
||||
</label>
|
||||
<div className="tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto max-h-[30rem]">
|
||||
<div
|
||||
className={classNames('tab-content border-base-300 bg-base-100 px-4 py-2 overflow-y-auto', {
|
||||
'max-h-[30rem]': limitHeight,
|
||||
})}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
</Fragment>
|
||||
|
75
src/faq/AndroidEmulatorFAQ.tsx
Normal file
75
src/faq/AndroidEmulatorFAQ.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { ExtLink } from '~/components/ExtLink';
|
||||
import { Header2, Header3, Header4 } from '~/components/HelpText/Headers';
|
||||
import { VQuote } from '~/components/HelpText/VQuote';
|
||||
import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
|
||||
import LdPlayerSettingsMisc2x from './assets/ld_settings_misc@2x.webp';
|
||||
import MumuSettingsMisc2x from './assets/mumu_settings_misc@2x.webp';
|
||||
import { ImageFigure } from '~/components/ImageFigure';
|
||||
|
||||
export function AndroidEmulatorFAQ() {
|
||||
return (
|
||||
<>
|
||||
<Header2>安卓模拟器</Header2>
|
||||
<p className="mb-2">目前市面上主流的可以很方便 root 的安卓模拟器有两个:</p>
|
||||
|
||||
<ul className="list-disc pl-6 mb-2">
|
||||
<li>
|
||||
<ExtLink href="https://mumu.163.com/">网易 MuMu 模拟器(安卓 12)</ExtLink> - Hyper-V 兼容较好
|
||||
</li>
|
||||
<li>
|
||||
<ExtLink href="https://www.ldmnq.com/">雷电模拟器(安卓 9)</ExtLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p className="mb-2">上述两款模拟器均包含广告,使用时请注意。</p>
|
||||
|
||||
<div className="my-2 alert alert-warning">
|
||||
<RiErrorWarningLine className="text-lg" />
|
||||
<p>
|
||||
根据应用的风控策略,使用模拟器登录的账号<strong>有可能会导致账号被封锁</strong>。
|
||||
</p>
|
||||
</div>
|
||||
<p className="mb-2">读者在使用前请自行评估风险。</p>
|
||||
|
||||
<Header3 id="enable-root">启用 root</Header3>
|
||||
<p className="mb-2">上述的两款模拟器都有提供比较直接的启用 root 的方法。</p>
|
||||
|
||||
<Header4 id="root-mumu">网易 MuMu 模拟器</Header4>
|
||||
<ul className="list-disc pl-6">
|
||||
<li>
|
||||
打开<VQuote>设置中心</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
选择<VQuote>其他</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
勾选<VQuote>开启手机Root权限</VQuote>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<ImageFigure className="ml-2" alt="网易木木模拟器设置界面" loading="lazy" srcSet={`${MumuSettingsMisc2x} 2x`}>
|
||||
网易木木模拟器设置界面
|
||||
</ImageFigure>
|
||||
</div>
|
||||
|
||||
<Header4 id="root-ld">雷电模拟器</Header4>
|
||||
<ul className="list-disc pl-6">
|
||||
<li>
|
||||
打开<VQuote>模拟器设置</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
选择<VQuote>其他</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
设置<VQuote>ROOT 权限</VQuote>为<VQuote>开启</VQuote>状态
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<ImageFigure className="ml-2" alt="雷电模拟器设置界面" loading="lazy" srcSet={`${LdPlayerSettingsMisc2x} 2x`}>
|
||||
雷电模拟器设置界面
|
||||
</ImageFigure>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
20
src/faq/FAQPages.tsx
Normal file
20
src/faq/FAQPages.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ComponentType } from 'react';
|
||||
import { QQMusicFAQ } from './QQMusicFAQ';
|
||||
import { KuwoFAQ } from './KuwoFAQ';
|
||||
import { KugouFAQ } from './KugouFAQ';
|
||||
import { OtherFAQ } from './OtherFAQ';
|
||||
import { AndroidEmulatorFAQ } from './AndroidEmulatorFAQ';
|
||||
|
||||
export type FAQEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
Component: ComponentType;
|
||||
};
|
||||
|
||||
export const FAQ_PAGES: FAQEntry[] = [
|
||||
{ id: 'qqmusic', name: 'QQ 音乐', Component: QQMusicFAQ },
|
||||
{ id: 'kuwo', name: '酷我音乐', Component: KuwoFAQ },
|
||||
{ id: 'kugou', name: '酷狗音乐', Component: KugouFAQ },
|
||||
{ id: 'android-emu', name: '安卓模拟器', Component: AndroidEmulatorFAQ },
|
||||
{ id: 'other', name: '其它问题', Component: OtherFAQ },
|
||||
];
|
14
src/faq/FaqHome.tsx
Normal file
14
src/faq/FaqHome.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { ExtLink } from '~/components/ExtLink';
|
||||
|
||||
export function FaqHome() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<h1 className="text-2xl font-bold">答疑</h1>
|
||||
<p>从目录选择一项来查看相关说明。</p>
|
||||
<p>
|
||||
也欢迎造访
|
||||
<ExtLink href={'https://t.me/unlock_music_chat'}>“音乐解锁-交流” 交流群</ExtLink> 进行交流。
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Header4 } from '~/components/HelpText/Headers';
|
||||
import { Header2, Header3 } from '~/components/HelpText/Headers';
|
||||
import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions';
|
||||
import { KugouAllInstructions } from '~/features/settings/panels/Kugou/KugouAllInstructions.tsx';
|
||||
import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
@ -6,19 +6,19 @@ import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
export function KugouFAQ() {
|
||||
return (
|
||||
<>
|
||||
<Header4>解锁失败</Header4>
|
||||
<p>
|
||||
<Header2>酷狗音乐</Header2>
|
||||
<Header3 id="failed">解锁失败</Header3>
|
||||
<p className="mb-2">
|
||||
酷狗现在对部分用户推送了 <code>kgg</code> 加密格式(安卓、Windows 客户端)。
|
||||
</p>
|
||||
<p className="my-4">根据平台不同,你需要提取密钥数据库。</p>
|
||||
<p className="mb-2">根据平台不同,你需要提取密钥数据库。</p>
|
||||
|
||||
<div className="p-2 @container">
|
||||
<div className="alert alert-warning">
|
||||
<RiErrorWarningLine className="size-6" />
|
||||
<p>安卓用户提取密钥需要 root 权限,或注入文件提供器。</p>
|
||||
</div>
|
||||
<div className="alert alert-warning mb-2">
|
||||
<RiErrorWarningLine className="size-6" />
|
||||
<p>安卓用户提取密钥需要 root 权限,或注入文件提供器。</p>
|
||||
</div>
|
||||
|
||||
<Header3 id="keys">导入密钥</Header3>
|
||||
<SegmentKeyImportInstructions tab="酷狗密钥" clientInstructions={<KugouAllInstructions />} />
|
||||
</>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Header4 } from '~/components/HelpText/Headers';
|
||||
import { Header2, Header3 } from '~/components/HelpText/Headers';
|
||||
import { VQuote } from '~/components/HelpText/VQuote';
|
||||
import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer';
|
||||
import { HiWord } from '~/components/HelpText/HiWord';
|
||||
@ -9,9 +9,10 @@ import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
export function KuwoFAQ() {
|
||||
return (
|
||||
<>
|
||||
<Header4>解锁失败</Header4>
|
||||
<Header2>酷我音乐</Header2>
|
||||
<Header3 id="failed">解锁失败</Header3>
|
||||
<SegmentTryOfficialPlayer />
|
||||
<p className="my-4">
|
||||
<p className="mb-2">
|
||||
日前,仅<HiWord>手机客户端</HiWord>下载的
|
||||
<VQuote>
|
||||
<strong>至臻全景声</strong>
|
||||
@ -22,10 +23,11 @@ export function KuwoFAQ() {
|
||||
</VQuote>
|
||||
音质的音乐文件采用新版加密。
|
||||
</p>
|
||||
<p className="my-4">其他音质目前不需要提取密钥。</p>
|
||||
<p className="my-4">PC平台暂未推出使用新版加密的音质。</p>
|
||||
<p className="mb-2">其他音质目前不需要提取密钥。</p>
|
||||
<p className="mb-2">PC平台暂未推出使用新版加密的音质。</p>
|
||||
|
||||
<div className="alert alert-warning mb-4">
|
||||
<Header3 id="keys">导入密钥</Header3>
|
||||
<div className="alert alert-warning my-2">
|
||||
<RiErrorWarningLine className="text-2xl" />
|
||||
<div>
|
||||
<p>安卓用户提取密钥需要 root 权限,或注入文件提供器。</p>
|
||||
|
@ -1,20 +1,18 @@
|
||||
import { ExtLink } from '~/components/ExtLink';
|
||||
import { Header4, Header5 } from '~/components/HelpText/Headers';
|
||||
import { VQuote } from '~/components/HelpText/VQuote';
|
||||
import { Header2, Header3, Header4 } from '~/components/HelpText/Headers';
|
||||
import { ProjectIssue } from '~/components/ProjectIssue';
|
||||
import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
|
||||
import LdPlayerSettingsMisc2x from './assets/ld_settings_misc@2x.webp';
|
||||
import MumuSettingsMisc2x from './assets/mumu_settings_misc@2x.webp';
|
||||
import { NavLink } from 'react-router';
|
||||
|
||||
export function OtherFAQ() {
|
||||
return (
|
||||
<>
|
||||
<Header4>解密后没有封面等信息</Header4>
|
||||
<Header2>其它问题</Header2>
|
||||
<Header3 id="metadata">解密后没有封面等信息</Header3>
|
||||
<p>该项目进行解密处理。如果加密前的资源没有内嵌元信息或封面,解密的文件也没有。</p>
|
||||
<p>请使用第三方工具进行编辑或管理元信息。</p>
|
||||
|
||||
<Header4>批量下载</Header4>
|
||||
<Header3 id="batch-dl">批量下载</Header3>
|
||||
<p>
|
||||
{'暂时没有实现,不过你可以在 '}
|
||||
<ProjectIssue id={34} title="[UI] 全部下载功能" />
|
||||
@ -23,11 +21,11 @@ export function OtherFAQ() {
|
||||
{' 追踪该问题。'}
|
||||
</p>
|
||||
|
||||
<Header4>安卓: 浏览器支持说明</Header4>
|
||||
<Header3 id="android-browsers">安卓: 浏览器支持说明</Header3>
|
||||
<p>⚠️ 手机端浏览器支持有限,请使用最新版本的 Chrome 或 Firefox 官方浏览器。</p>
|
||||
<div className="flex flex-col md:flex-row gap-2 md:gap-8">
|
||||
<div>
|
||||
<Header5>已知有问题的浏览器</Header5>
|
||||
<Header4>已知有问题的浏览器</Header4>
|
||||
<ul className="list-disc list-inside pl-2">
|
||||
<li>Via 浏览器</li>
|
||||
<li>夸克浏览器</li>
|
||||
@ -36,7 +34,7 @@ export function OtherFAQ() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Header5>可能会遇到的问题包括</Header5>
|
||||
<Header4>可能会遇到的问题包括</Header4>
|
||||
<ul className="list-disc list-inside pl-2">
|
||||
<li>网页白屏</li>
|
||||
<li>无法下载解密后内容</li>
|
||||
@ -45,70 +43,20 @@ export function OtherFAQ() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Header4>安卓: root 相关说明</Header4>
|
||||
<Header3 id="android-root">安卓 root</Header3>
|
||||
<p>
|
||||
对安卓设备获取 root 特权通常会破坏系统的完整性并导致部分功能无法使用。
|
||||
例如部分厂商的安卓设备会在解锁后丧失保修资格,或导致无法使用 NFC 移动支付等限制。
|
||||
</p>
|
||||
<p className="my-2">如果希望不破坏系统完整性,你可以考虑在电脑上使用安卓模拟器。</p>
|
||||
<p className="my-2">
|
||||
很多安卓模拟器都提供了 root 特权支持,可以很方便的启用,例如
|
||||
<VQuote>
|
||||
<ExtLink href="https://mumu.163.com/">网易 MuMu 模拟器(安卓 12,推荐)</ExtLink>
|
||||
</VQuote>
|
||||
或
|
||||
<VQuote>
|
||||
<ExtLink href="https://www.ldmnq.com/">雷电模拟器(安卓 9)</ExtLink>
|
||||
</VQuote>
|
||||
如果希望不破坏系统完整性,你可以考虑在电脑上使用
|
||||
<NavLink className="link link-info" to="/questions/android-emu">
|
||||
安卓模拟器
|
||||
</NavLink>
|
||||
。
|
||||
</p>
|
||||
|
||||
<div className="my-4 alert alert-warning">
|
||||
<RiErrorWarningLine className="text-lg" />
|
||||
<div>
|
||||
<p>
|
||||
根据应用的风控策略,使用模拟器登录的账号<strong>有可能会导致账号被封锁</strong>。
|
||||
</p>
|
||||
<p>使用前请自行评估风险。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p>以下是为上述模拟器启用 root 的方式:</p>
|
||||
<div className="grid grid-cols-1 gap-2 md:gap-4 lg:grid-cols-2">
|
||||
<div>
|
||||
<Header5>网易 MuMu模拟器</Header5>
|
||||
<ul className="list-disc pl-6">
|
||||
<li>
|
||||
打开<VQuote>设置中心</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
选择<VQuote>其他</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
勾选<VQuote>开启手机Root权限</VQuote>
|
||||
</li>
|
||||
</ul>
|
||||
<img className="rounded-md border border-base-300" loading="lazy" srcSet={`${MumuSettingsMisc2x} 2x`} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Header5>雷电模拟器</Header5>
|
||||
<ul className="list-disc pl-6">
|
||||
<li>
|
||||
打开<VQuote>模拟器设置</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
选择<VQuote>其他</VQuote>
|
||||
</li>
|
||||
<li>
|
||||
设置<VQuote>ROOT 权限</VQuote>为<VQuote>开启</VQuote>状态
|
||||
</li>
|
||||
</ul>
|
||||
<img className="rounded-md border border-base-300" loading="lazy" srcSet={`${LdPlayerSettingsMisc2x} 2x`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Header4>相关项目</Header4>
|
||||
<Header3 id="projects">相关项目</Header3>
|
||||
<ul className="list-disc pl-6">
|
||||
<li>
|
||||
<p>
|
||||
@ -150,7 +98,7 @@ export function OtherFAQ() {
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Header4>有更多问题?</Header4>
|
||||
<Header3 id="more-questions">有更多问题?</Header3>
|
||||
<p className="flex flex-row gap-1">
|
||||
欢迎加入
|
||||
<ExtLink href={'https://t.me/unlock_music_chat'}>“音乐解锁-交流” 交流群</ExtLink>
|
||||
|
@ -1,17 +1,24 @@
|
||||
import { Header4 } from '~/components/HelpText/Headers';
|
||||
import { Header2, Header3 } from '~/components/HelpText/Headers';
|
||||
import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer';
|
||||
import { QMCv2QQMusicAllInstructions } from '~/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions';
|
||||
|
||||
export function QQMusicFAQ() {
|
||||
return (
|
||||
<>
|
||||
<Header4>解锁失败</Header4>
|
||||
<Header2>QQ 音乐</Header2>
|
||||
<Header3 id="failed">解锁失败</Header3>
|
||||
<SegmentTryOfficialPlayer />
|
||||
<p className="my-4">重复下载同一首的歌曲不重复扣下载配额,但是同一首歌的两个版本会重复扣下载配额,请仔细分辨。</p>
|
||||
<p className="mb-4">
|
||||
<p className="mb-2">新版本的 QQ 音乐客户端下载的文件通常都需要导入密钥数据库。</p>
|
||||
<p className="mb-2">每一个资源(即一首歌的某个音质)都有独立的密钥,下载音乐时会被写出到密钥数据库中。</p>
|
||||
<p className="mb-2">因此若是解密失败,很有可能是因为你需要导入密钥,或降级客户端。</p>
|
||||
|
||||
<Header3 id="about-download">关于下载</Header3>
|
||||
<p>重复下载同一首的歌曲不重复扣下载配额,但是同一首歌的两个版本会重复扣下载配额,请仔细分辨。</p>
|
||||
<p className="my-2">
|
||||
部分平台获取的加密文件未包含密钥。选择你<strong>下载文件时</strong>使用的客户端来查看说明。
|
||||
</p>
|
||||
|
||||
<Header3 id="keys-or-downgrade">导入密钥或降级客户端</Header3>
|
||||
<QMCv2QQMusicAllInstructions />
|
||||
</>
|
||||
);
|
||||
|
@ -2,7 +2,7 @@ import { RiErrorWarningLine } from 'react-icons/ri';
|
||||
|
||||
export function SegmentTryOfficialPlayer({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<div className={`alert alert-warning ${className}`}>
|
||||
<div className={`alert alert-warning my-2 ${className}`}>
|
||||
<RiErrorWarningLine className="text-2xl" />
|
||||
<p>尝试用下载音乐的设备播放一次看看,如果官方客户端都无法播放,那解锁肯定会失败哦。</p>
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@ export function ResponsiveNav({
|
||||
className={`@container/nav grow grid grid-cols-1 grid-rows-[auto_1fr] md:grid-rows-1 md:grid-cols-[10rem_1fr] ${className}`}
|
||||
>
|
||||
{/* Sidebar */}
|
||||
<aside className={`bg-base-300 md:p-4 md:block ${navigationClassName}`}>{navigation}</aside>
|
||||
<aside className={`bg-base-100 md:p-4 md:block ${navigationClassName}`}>{navigation}</aside>
|
||||
|
||||
{/* Main content */}
|
||||
<div className={`p-4 grow ${contentClassName}`}>{children}</div>
|
||||
|
21
src/features/nav/TabNavLink.tsx
Normal file
21
src/features/nav/TabNavLink.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import classNames from 'classnames';
|
||||
import type { RefAttributes } from 'react';
|
||||
import { NavLink, type NavLinkProps } from 'react-router';
|
||||
|
||||
const tabClassNames = ({ isActive }: { isActive: boolean }) =>
|
||||
classNames(
|
||||
'link inline-flex text-nowrap mb-[-2px] no-underline w-full',
|
||||
'border-b-2 md:border-b-0 md:border-r-2',
|
||||
'tab md:grow',
|
||||
{
|
||||
'tab-active bg-accent/10 border-accent': isActive,
|
||||
},
|
||||
);
|
||||
|
||||
export function TabNavLink({ children, ...props }: NavLinkProps & RefAttributes<HTMLAnchorElement>) {
|
||||
return (
|
||||
<NavLink className={tabClassNames} role="tab" {...props}>
|
||||
{children}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { useAppDispatch, useAppSelector } from '~/hooks';
|
||||
import { commitStagingChange, discardStagingChanges } from './settingsSlice';
|
||||
import { selectIsSettingsNotSaved } from './settingsSelector';
|
||||
import { NavLink, Outlet } from 'react-router';
|
||||
import { Outlet } from 'react-router';
|
||||
import { SETTINGS_TABS } from '~/features/settings/settingsTabs.tsx';
|
||||
import { MdOutlineSettingsBackupRestore } from 'react-icons/md';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ResponsiveNav } from '../nav/ResponsiveNav';
|
||||
import classNames from 'classnames';
|
||||
import { TabNavLink } from '../nav/TabNavLink';
|
||||
|
||||
export function Settings() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -27,28 +27,18 @@ export function Settings() {
|
||||
};
|
||||
const isSettingsNotSaved = useAppSelector(selectIsSettingsNotSaved);
|
||||
|
||||
const tabClassNames = ({ isActive }: { isActive: boolean }) =>
|
||||
classNames(
|
||||
'link inline-flex text-nowrap mb-[-2px] no-underline w-full',
|
||||
'border-b-2 md:border-b-0 md:border-r-2',
|
||||
'tab md:grow',
|
||||
{
|
||||
'tab-active bg-accent/10 border-accent': isActive,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col flex-1 container w-full">
|
||||
<ResponsiveNav
|
||||
className="grow h-full overflow-auto"
|
||||
contentClassName="flex flex-col overflow-auto"
|
||||
navigationClassName="overflow-x-auto pb-[2px] md:pb-0 h-full md:items-center [&]:md:flex"
|
||||
navigationClassName="overflow-x-auto pb-[2px] md:pb-0 h-full items-start [&]:md:flex"
|
||||
navigation={
|
||||
<div role="tablist" className="tabs gap-1 flex-nowrap md:flex-col grow items-center">
|
||||
{Object.entries(SETTINGS_TABS).map(([id, { name }]) => (
|
||||
<NavLink className={tabClassNames} key={id} to={`/settings/${id}`} role="tab">
|
||||
<TabNavLink key={id} to={`/settings/${id}`}>
|
||||
{name}
|
||||
</NavLink>
|
||||
</TabNavLink>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export function PanelKGGKey() {
|
||||
<div className="container flex flex-col grow min-h-0 w-full">
|
||||
<h2 className="text-2xl font-bold">酷狗解密密钥 (KGG / KGM v5)</h2>
|
||||
|
||||
<p>酷狗已经升级了加密方式,现在使用 KGG / KGM v5 加密。</p>
|
||||
<p>酷狗已经升级了加密方式,现在最新版本的客户端使用 KGG / KGM v5 加密。</p>
|
||||
|
||||
<h3 className="mt-2 text-xl font-bold">密钥管理</h3>
|
||||
<AddKey
|
||||
|
@ -43,6 +43,8 @@ export function PanelKWMv2Key() {
|
||||
酷我安卓版本的「臻品音质」已经换用 V2 版,表现为加密文件的后缀名为 <code>mflac</code> 或 <code>mgg</code>。
|
||||
</p>
|
||||
<p>该格式需要提取密钥后才能正常解密。</p>
|
||||
|
||||
<h3 className="mt-2 text-xl font-bold">密钥管理</h3>
|
||||
<AddKey
|
||||
addKey={addKey}
|
||||
refContainer={refKeyContainer}
|
||||
|
@ -4,7 +4,7 @@ import { InstructionsMac } from './InstructionsMac';
|
||||
import { InstructionsPC } from './InstructionsPC';
|
||||
import { InstructionsTabs, InstructionTab } from '~/components/InstructionsTabs.tsx';
|
||||
|
||||
export function QMCv2QQMusicAllInstructions() {
|
||||
export function QMCv2QQMusicAllInstructions({ limitHeight }: { limitHeight?: boolean }) {
|
||||
const tabs: InstructionTab[] = [
|
||||
{
|
||||
id: 'android',
|
||||
@ -16,5 +16,5 @@ export function QMCv2QQMusicAllInstructions() {
|
||||
{ id: 'windows', label: 'Windows', content: <InstructionsPC /> },
|
||||
];
|
||||
|
||||
return <InstructionsTabs tabs={tabs} />;
|
||||
return <InstructionsTabs tabs={tabs} limitHeight={limitHeight} />;
|
||||
}
|
||||
|
@ -1,43 +1,27 @@
|
||||
import { ComponentType, Fragment } from 'react';
|
||||
import { Header3 } from '~/components/HelpText/Headers';
|
||||
import { KuwoFAQ } from '~/faq/KuwoFAQ';
|
||||
import { OtherFAQ } from '~/faq/OtherFAQ';
|
||||
import { QQMusicFAQ } from '~/faq/QQMusicFAQ';
|
||||
import { KugouFAQ } from '~/faq/KugouFAQ.tsx';
|
||||
|
||||
type FAQEntry = {
|
||||
id: string;
|
||||
title: string;
|
||||
Help: ComponentType;
|
||||
};
|
||||
|
||||
const faqEntries: FAQEntry[] = [
|
||||
{ id: 'qqmusic', title: 'QQ 音乐', Help: QQMusicFAQ },
|
||||
{ id: 'kuwo', title: '酷我音乐', Help: KuwoFAQ },
|
||||
{ id: 'kugou', title: '酷狗音乐', Help: KugouFAQ },
|
||||
{ id: 'other', title: '其它问题', Help: OtherFAQ },
|
||||
];
|
||||
import { Outlet } from 'react-router';
|
||||
import { FAQ_PAGES } from '~/faq/FAQPages';
|
||||
import { ResponsiveNav } from '~/features/nav/ResponsiveNav';
|
||||
import { TabNavLink } from '~/features/nav/TabNavLink';
|
||||
|
||||
export function FaqTab() {
|
||||
return (
|
||||
<section className="container pb-10 px-4">
|
||||
<h2 className="text-3xl font-bold text-center">常见问题解答</h2>
|
||||
<Header3>答疑目录</Header3>
|
||||
<ul className="list-disc pl-6">
|
||||
{faqEntries.map(({ id, title }) => (
|
||||
<li key={id}>
|
||||
<a className="link link-info no-underline" href={`#faq-${id}`}>
|
||||
{title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{faqEntries.map(({ id, title, Help }) => (
|
||||
<Fragment key={id}>
|
||||
<Header3 id={`faq-${id}`}>{title}</Header3>
|
||||
<Help />
|
||||
</Fragment>
|
||||
))}
|
||||
</section>
|
||||
<div className="flex flex-col flex-1 container w-full">
|
||||
<ResponsiveNav
|
||||
className="grow h-full overflow-auto"
|
||||
contentClassName="flex flex-col overflow-auto px-8"
|
||||
navigationClassName="overflow-x-auto pb-[2px] md:pb-0 h-full md:items-start [&]:md:flex"
|
||||
navigation={
|
||||
<div role="tablist" className="tabs gap-1 flex-nowrap md:flex-col grow items-center">
|
||||
{FAQ_PAGES.map(({ id, name }) => (
|
||||
<TabNavLink key={id} to={`/questions/${id}`}>
|
||||
{name}
|
||||
</TabNavLink>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Outlet />
|
||||
</ResponsiveNav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
},
|
||||
base: './',
|
||||
optimizeDeps: {
|
||||
exclude: ['@unlock-music/crypto', 'sql.js'],
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user