Files
CeruMusic/website/design.html

2872 lines
190 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width initial-scale=1" />
<style type="text/css">
html {
overflow-x: initial !important;
}
:root {
--bg-color: #ffffff;
--text-color: #333333;
--select-text-bg-color: #b5d6fc;
--select-text-font-color: auto;
--monospace: 'Lucida Console', Consolas, 'Courier', monospace;
--title-bar-height: 20px;
}
.mac-os-11 {
--title-bar-height: 28px;
}
html {
font-size: 14px;
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
}
body {
margin: 0px;
padding: 0px;
height: auto;
inset: 0px;
font-size: 1rem;
line-height: 1.42857;
overflow-x: hidden;
background: inherit;
tab-size: 4;
}
iframe {
margin: auto;
}
a.url {
word-break: break-all;
}
a:active,
a:hover {
outline: 0px;
}
.in-text-selection,
::selection {
text-shadow: none;
background: var(--select-text-bg-color);
color: var(--select-text-font-color);
}
#write {
margin: 0px auto;
height: auto;
width: inherit;
word-break: normal;
overflow-wrap: break-word;
position: relative;
white-space: normal;
overflow-x: visible;
padding-top: 36px;
}
#write.first-line-indent p {
text-indent: 2em;
}
#write.first-line-indent li p,
#write.first-line-indent p * {
text-indent: 0px;
}
#write.first-line-indent li {
margin-left: 2em;
}
.for-image #write {
padding-left: 8px;
padding-right: 8px;
}
body.typora-export {
padding-left: 30px;
padding-right: 30px;
}
.typora-export .footnote-line,
.typora-export li,
.typora-export p {
white-space: pre-wrap;
}
.typora-export .task-list-item input {
pointer-events: none;
}
@media screen and (max-width: 500px) {
body.typora-export {
padding-left: 0px;
padding-right: 0px;
}
#write {
padding-left: 20px;
padding-right: 20px;
}
}
#write li > figure:last-child {
margin-bottom: 0.5rem;
}
#write ol,
#write ul {
position: relative;
}
img {
max-width: 100%;
vertical-align: middle;
image-orientation: from-image;
}
button,
input,
select,
textarea {
color: inherit;
font: inherit;
}
input[type='checkbox'],
input[type='radio'] {
line-height: normal;
padding: 0px;
}
*,
::after,
::before {
box-sizing: border-box;
}
#write h1,
#write h2,
#write h3,
#write h4,
#write h5,
#write h6,
#write p,
#write pre {
width: inherit;
}
#write h1,
#write h2,
#write h3,
#write h4,
#write h5,
#write h6,
#write p {
position: relative;
}
p {
line-height: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6 {
break-after: avoid-page;
break-inside: avoid;
orphans: 4;
}
p {
orphans: 4;
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.8rem;
}
h3 {
font-size: 1.6rem;
}
h4 {
font-size: 1.4rem;
}
h5 {
font-size: 1.2rem;
}
h6 {
font-size: 1rem;
}
.md-math-block,
.md-rawblock,
h1,
h2,
h3,
h4,
h5,
h6,
p {
margin-top: 1rem;
margin-bottom: 1rem;
}
.hidden {
display: none;
}
.md-blockmeta {
color: rgb(204, 204, 204);
font-weight: 700;
font-style: italic;
}
a {
cursor: pointer;
}
sup.md-footnote {
padding: 2px 4px;
background-color: rgba(238, 238, 238, 0.7);
color: rgb(85, 85, 85);
border-radius: 4px;
cursor: pointer;
}
sup.md-footnote a,
sup.md-footnote a:hover {
color: inherit;
text-transform: inherit;
text-decoration: inherit;
}
#write input[type='checkbox'] {
cursor: pointer;
width: inherit;
height: inherit;
}
figure {
overflow-x: auto;
margin: 1.2em 0px;
max-width: calc(100% + 16px);
padding: 0px;
}
figure > table {
margin: 0px;
}
thead,
tr {
break-inside: avoid;
break-after: auto;
}
thead {
display: table-header-group;
}
table {
border-collapse: collapse;
border-spacing: 0px;
width: 100%;
overflow: auto;
break-inside: auto;
text-align: left;
}
table.md-table td {
min-width: 32px;
}
.CodeMirror-gutters {
border-right: 0px;
background-color: inherit;
}
.CodeMirror-linenumber {
user-select: none;
}
.CodeMirror {
text-align: left;
}
.CodeMirror-placeholder {
opacity: 0.3;
}
.CodeMirror pre {
padding: 0px 4px;
}
.CodeMirror-lines {
padding: 0px;
}
div.hr:focus {
cursor: none;
}
#write pre {
white-space: pre-wrap;
}
#write.fences-no-line-wrapping pre {
white-space: pre;
}
#write pre.ty-contain-cm {
white-space: normal;
}
.CodeMirror-gutters {
margin-right: 4px;
}
.md-fences {
font-size: 0.9rem;
display: block;
break-inside: avoid;
text-align: left;
overflow: visible;
white-space: pre;
background: inherit;
position: relative !important;
}
.md-fences-adv-panel {
width: 100%;
margin-top: 10px;
text-align: center;
padding-top: 0px;
padding-bottom: 8px;
overflow-x: auto;
}
#write .md-fences.mock-cm {
white-space: pre-wrap;
}
.md-fences.md-fences-with-lineno {
padding-left: 0px;
}
#write.fences-no-line-wrapping .md-fences.mock-cm {
white-space: pre;
overflow-x: auto;
}
.md-fences.mock-cm.md-fences-with-lineno {
padding-left: 8px;
}
.CodeMirror-line,
twitterwidget {
break-inside: avoid;
}
svg {
break-inside: avoid;
}
.footnotes {
opacity: 0.8;
font-size: 0.9rem;
margin-top: 1em;
margin-bottom: 1em;
}
.footnotes + .footnotes {
margin-top: 0px;
}
.md-reset {
margin: 0px;
padding: 0px;
border: 0px;
outline: 0px;
vertical-align: top;
background: 0px 0px;
text-decoration: none;
text-shadow: none;
float: none;
position: static;
width: auto;
height: auto;
white-space: nowrap;
cursor: inherit;
-webkit-tap-highlight-color: transparent;
line-height: normal;
font-weight: 400;
text-align: left;
box-sizing: content-box;
direction: ltr;
}
li div {
padding-top: 0px;
}
blockquote {
margin: 1rem 0px;
}
li .mathjax-block,
li p {
margin: 0.5rem 0px;
}
li blockquote {
margin: 1rem 0px;
}
li {
margin: 0px;
position: relative;
}
blockquote > :last-child {
margin-bottom: 0px;
}
blockquote > :first-child,
li > :first-child {
margin-top: 0px;
}
.footnotes-area {
color: rgb(136, 136, 136);
margin-top: 0.714rem;
padding-bottom: 0.143rem;
white-space: normal;
}
#write .footnote-line {
white-space: pre-wrap;
}
@media print {
body,
html {
border: 1px solid transparent;
height: 99%;
break-after: avoid;
break-before: avoid;
font-variant-ligatures: no-common-ligatures;
}
#write {
margin-top: 0px;
padding-top: 0px;
border-color: transparent !important;
padding-bottom: 0px !important;
}
.typora-export * {
-webkit-print-color-adjust: exact;
}
.typora-export #write {
break-after: avoid;
}
.typora-export #write::after {
height: 0px;
}
.is-mac table {
break-inside: avoid;
}
.typora-export-show-outline .typora-export-sidebar {
display: none;
}
}
.footnote-line {
margin-top: 0.714em;
font-size: 0.7em;
}
a img,
img a {
cursor: pointer;
}
pre.md-meta-block {
font-size: 0.8rem;
min-height: 0.8rem;
white-space: pre-wrap;
background: rgb(204, 204, 204);
display: block;
overflow-x: hidden;
}
p > .md-image:only-child:not(.md-img-error) img,
p > img:only-child {
display: block;
margin: auto;
}
#write.first-line-indent p > .md-image:only-child:not(.md-img-error) img {
left: -2em;
position: relative;
}
p > .md-image:only-child {
display: inline-block;
width: 100%;
}
#write .MathJax_Display {
margin: 0.8em 0px 0px;
}
.md-math-block {
width: 100%;
}
.md-math-block:not(:empty)::after {
display: none;
}
.MathJax_ref {
fill: currentcolor;
}
[contenteditable='true']:active,
[contenteditable='true']:focus,
[contenteditable='false']:active,
[contenteditable='false']:focus {
outline: 0px;
box-shadow: none;
}
.md-task-list-item {
position: relative;
list-style-type: none;
}
.task-list-item.md-task-list-item {
padding-left: 0px;
}
.md-task-list-item > input {
position: absolute;
top: 0px;
left: 0px;
margin-left: -1.2em;
margin-top: calc(1em - 10px);
border: none;
}
.math {
font-size: 1rem;
}
.md-toc {
min-height: 3.58rem;
position: relative;
font-size: 0.9rem;
border-radius: 10px;
}
.md-toc-content {
position: relative;
margin-left: 0px;
}
.md-toc-content::after,
.md-toc::after {
display: none;
}
.md-toc-item {
display: block;
color: rgb(65, 131, 196);
}
.md-toc-item a {
text-decoration: none;
}
.md-toc-inner:hover {
text-decoration: underline;
}
.md-toc-inner {
display: inline-block;
cursor: pointer;
}
.md-toc-h1 .md-toc-inner {
margin-left: 0px;
font-weight: 700;
}
.md-toc-h2 .md-toc-inner {
margin-left: 2em;
}
.md-toc-h3 .md-toc-inner {
margin-left: 4em;
}
.md-toc-h4 .md-toc-inner {
margin-left: 6em;
}
.md-toc-h5 .md-toc-inner {
margin-left: 8em;
}
.md-toc-h6 .md-toc-inner {
margin-left: 10em;
}
@media screen and (max-width: 48em) {
.md-toc-h3 .md-toc-inner {
margin-left: 3.5em;
}
.md-toc-h4 .md-toc-inner {
margin-left: 5em;
}
.md-toc-h5 .md-toc-inner {
margin-left: 6.5em;
}
.md-toc-h6 .md-toc-inner {
margin-left: 8em;
}
}
a.md-toc-inner {
font-size: inherit;
font-style: inherit;
font-weight: inherit;
line-height: inherit;
}
.footnote-line a:not(.reversefootnote) {
color: inherit;
}
.reversefootnote {
font-family: ui-monospace, sans-serif;
}
.md-attr {
display: none;
}
.md-fn-count::after {
content: '.';
}
code,
pre,
samp,
tt {
font-family: var(--monospace);
}
kbd {
margin: 0px 0.1em;
padding: 0.1em 0.6em;
font-size: 0.8em;
color: rgb(36, 39, 41);
background: rgb(255, 255, 255);
border: 1px solid rgb(173, 179, 185);
border-radius: 3px;
box-shadow:
rgba(12, 13, 14, 0.2) 0px 1px 0px,
rgb(255, 255, 255) 0px 0px 0px 2px inset;
white-space: nowrap;
vertical-align: middle;
}
.md-comment {
color: rgb(162, 127, 3);
opacity: 0.6;
font-family: var(--monospace);
}
code {
text-align: left;
vertical-align: initial;
}
a.md-print-anchor {
white-space: pre !important;
border-width: initial !important;
border-style: none !important;
border-color: initial !important;
display: inline-block !important;
position: absolute !important;
width: 1px !important;
right: 0px !important;
outline: 0px !important;
background: 0px 0px !important;
text-decoration: initial !important;
text-shadow: initial !important;
}
.os-windows.monocolor-emoji .md-emoji {
font-family: 'Segoe UI Symbol', sans-serif;
}
.md-diagram-panel > svg {
max-width: 100%;
}
[lang='flow'] svg,
[lang='mermaid'] svg {
max-width: 100%;
height: auto;
}
[lang='mermaid'] .node text {
font-size: 1rem;
}
table tr th {
border-bottom: 0px;
}
video {
max-width: 100%;
display: block;
margin: 0px auto;
}
iframe {
max-width: 100%;
width: 100%;
border: none;
}
.highlight td,
.highlight tr {
border: 0px;
}
mark {
background: rgb(255, 255, 0);
color: rgb(0, 0, 0);
}
.md-html-inline .md-plain,
.md-html-inline strong,
mark .md-inline-math,
mark strong {
color: inherit;
}
.md-expand mark .md-meta {
opacity: 0.3 !important;
}
mark .md-meta {
color: rgb(0, 0, 0);
}
@media print {
.typora-export h1,
.typora-export h2,
.typora-export h3,
.typora-export h4,
.typora-export h5,
.typora-export h6 {
break-inside: avoid;
}
}
.md-diagram-panel .messageText {
stroke: none !important;
}
.md-diagram-panel .start-state {
fill: var(--node-fill);
}
.md-diagram-panel .edgeLabel rect {
opacity: 1 !important;
}
.md-fences.md-fences-math {
font-size: 1em;
}
.md-fences-advanced:not(.md-focus) {
padding: 0px;
white-space: nowrap;
border: 0px;
}
.md-fences-advanced:not(.md-focus) {
background: inherit;
}
.typora-export-show-outline .typora-export-content {
max-width: 1440px;
margin: auto;
display: flex;
flex-direction: row;
}
.typora-export-sidebar {
width: 300px;
font-size: 0.8rem;
margin-top: 80px;
margin-right: 18px;
}
.typora-export-show-outline #write {
--webkit-flex: 2;
flex: 2 1 0%;
}
.typora-export-sidebar .outline-content {
position: fixed;
top: 0px;
max-height: 100%;
overflow: hidden auto;
padding-bottom: 30px;
padding-top: 60px;
width: 300px;
}
@media screen and (max-width: 1024px) {
.typora-export-sidebar,
.typora-export-sidebar .outline-content {
width: 240px;
}
}
@media screen and (max-width: 800px) {
.typora-export-sidebar {
display: none;
}
}
.outline-content li,
.outline-content ul {
margin-left: 0px;
margin-right: 0px;
padding-left: 0px;
padding-right: 0px;
list-style: none;
}
.outline-content ul {
margin-top: 0px;
margin-bottom: 0px;
}
.outline-content strong {
font-weight: 400;
}
.outline-expander {
width: 1rem;
height: 1.42857rem;
position: relative;
display: table-cell;
vertical-align: middle;
cursor: pointer;
padding-left: 4px;
}
.outline-expander::before {
content: '';
position: relative;
font-family: Ionicons;
display: inline-block;
font-size: 8px;
vertical-align: middle;
}
.outline-item {
padding-top: 3px;
padding-bottom: 3px;
cursor: pointer;
}
.outline-expander:hover::before {
content: '';
}
.outline-h1 > .outline-item {
padding-left: 0px;
}
.outline-h2 > .outline-item {
padding-left: 1em;
}
.outline-h3 > .outline-item {
padding-left: 2em;
}
.outline-h4 > .outline-item {
padding-left: 3em;
}
.outline-h5 > .outline-item {
padding-left: 4em;
}
.outline-h6 > .outline-item {
padding-left: 5em;
}
.outline-label {
cursor: pointer;
display: table-cell;
vertical-align: middle;
text-decoration: none;
color: inherit;
}
.outline-label:hover {
text-decoration: underline;
}
.outline-item:hover {
border-color: rgb(245, 245, 245);
background-color: var(--item-hover-bg-color);
}
.outline-item:hover {
margin-left: -28px;
margin-right: -28px;
border-left: 28px solid transparent;
border-right: 28px solid transparent;
}
.outline-item-single .outline-expander::before,
.outline-item-single .outline-expander:hover::before {
display: none;
}
.outline-item-open > .outline-item > .outline-expander::before {
content: '';
}
.outline-children {
display: none;
}
.info-panel-tab-wrapper {
display: none;
}
.outline-item-open > .outline-children {
display: block;
}
.typora-export .outline-item {
padding-top: 1px;
padding-bottom: 1px;
}
.typora-export .outline-item:hover {
margin-right: -8px;
border-right: 8px solid transparent;
}
.typora-export .outline-expander::before {
content: '+';
font-family: inherit;
top: -1px;
}
.typora-export .outline-expander:hover::before,
.typora-export .outline-item-open > .outline-item > .outline-expander::before {
content: '';
}
.typora-export-collapse-outline .outline-children {
display: none;
}
.typora-export-collapse-outline .outline-item-open > .outline-children,
.typora-export-no-collapse-outline .outline-children {
display: block;
}
.typora-export-no-collapse-outline .outline-expander::before {
content: '' !important;
}
.typora-export-show-outline .outline-item-active > .outline-item .outline-label {
font-weight: 700;
}
.md-inline-math-container mjx-container {
zoom: 0.95;
}
.CodeMirror {
height: auto;
}
.CodeMirror.cm-s-inner {
background: inherit;
}
.CodeMirror-scroll {
overflow: auto hidden;
z-index: 3;
}
.CodeMirror-gutter-filler,
.CodeMirror-scrollbar-filler {
background-color: rgb(255, 255, 255);
}
.CodeMirror-gutters {
border-right: 1px solid rgb(221, 221, 221);
background: inherit;
white-space: nowrap;
}
.CodeMirror-linenumber {
padding: 0px 3px 0px 5px;
text-align: right;
color: rgb(153, 153, 153);
}
.cm-s-inner .cm-keyword {
color: rgb(119, 0, 136);
}
.cm-s-inner .cm-atom,
.cm-s-inner.cm-atom {
color: rgb(34, 17, 153);
}
.cm-s-inner .cm-number {
color: rgb(17, 102, 68);
}
.cm-s-inner .cm-def {
color: rgb(0, 0, 255);
}
.cm-s-inner .cm-variable {
color: rgb(0, 0, 0);
}
.cm-s-inner .cm-variable-2 {
color: rgb(0, 85, 170);
}
.cm-s-inner .cm-variable-3 {
color: rgb(0, 136, 85);
}
.cm-s-inner .cm-string {
color: rgb(170, 17, 17);
}
.cm-s-inner .cm-property {
color: rgb(0, 0, 0);
}
.cm-s-inner .cm-operator {
color: rgb(152, 26, 26);
}
.cm-s-inner .cm-comment,
.cm-s-inner.cm-comment {
color: rgb(170, 85, 0);
}
.cm-s-inner .cm-string-2 {
color: rgb(255, 85, 0);
}
.cm-s-inner .cm-meta {
color: rgb(85, 85, 85);
}
.cm-s-inner .cm-qualifier {
color: rgb(85, 85, 85);
}
.cm-s-inner .cm-builtin {
color: rgb(51, 0, 170);
}
.cm-s-inner .cm-bracket {
color: rgb(153, 153, 119);
}
.cm-s-inner .cm-tag {
color: rgb(17, 119, 0);
}
.cm-s-inner .cm-attribute {
color: rgb(0, 0, 204);
}
.cm-s-inner .cm-header,
.cm-s-inner.cm-header {
color: rgb(0, 0, 255);
}
.cm-s-inner .cm-quote,
.cm-s-inner.cm-quote {
color: rgb(0, 153, 0);
}
.cm-s-inner .cm-hr,
.cm-s-inner.cm-hr {
color: rgb(153, 153, 153);
}
.cm-s-inner .cm-link,
.cm-s-inner.cm-link {
color: rgb(0, 0, 204);
}
.cm-negative {
color: rgb(221, 68, 68);
}
.cm-positive {
color: rgb(34, 153, 34);
}
.cm-header,
.cm-strong {
font-weight: 700;
}
.cm-del {
text-decoration: line-through;
}
.cm-em {
font-style: italic;
}
.cm-link {
text-decoration: underline;
}
.cm-error {
color: red;
}
.cm-invalidchar {
color: red;
}
.cm-constant {
color: rgb(38, 139, 210);
}
.cm-defined {
color: rgb(181, 137, 0);
}
div.CodeMirror span.CodeMirror-matchingbracket {
color: rgb(0, 255, 0);
}
div.CodeMirror span.CodeMirror-nonmatchingbracket {
color: rgb(255, 34, 34);
}
.cm-s-inner .CodeMirror-activeline-background {
background: inherit;
}
.CodeMirror {
position: relative;
overflow: hidden;
}
.CodeMirror-scroll {
height: 100%;
outline: 0px;
position: relative;
box-sizing: content-box;
background: inherit;
}
.CodeMirror-sizer {
position: relative;
}
.CodeMirror-gutter-filler,
.CodeMirror-hscrollbar,
.CodeMirror-scrollbar-filler,
.CodeMirror-vscrollbar {
position: absolute;
z-index: 6;
display: none;
outline: 0px;
}
.CodeMirror-vscrollbar {
right: 0px;
top: 0px;
overflow: hidden;
}
.CodeMirror-hscrollbar {
bottom: 0px;
left: 0px;
overflow: auto hidden;
}
.CodeMirror-scrollbar-filler {
right: 0px;
bottom: 0px;
}
.CodeMirror-gutter-filler {
left: 0px;
bottom: 0px;
}
.CodeMirror-gutters {
position: absolute;
left: 0px;
top: 0px;
padding-bottom: 10px;
z-index: 3;
overflow-y: hidden;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
box-sizing: content-box;
padding-bottom: 30px;
margin-bottom: -32px;
display: inline-block;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: 0px 0px !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0px;
bottom: 0px;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-lines {
cursor: text;
}
.CodeMirror pre {
border-radius: 0px;
border-width: 0px;
background: 0px 0px;
font-family: inherit;
font-size: inherit;
margin: 0px;
white-space: pre;
overflow-wrap: normal;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
}
.CodeMirror-wrap pre {
overflow-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-code pre {
border-right: 30px solid transparent;
width: fit-content;
}
.CodeMirror-wrap .CodeMirror-code pre {
border-right: none;
width: auto;
}
.CodeMirror-linebackground {
position: absolute;
inset: 0px;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
overflow: auto;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0px;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-measure pre {
position: static;
}
.CodeMirror div.CodeMirror-cursor {
position: absolute;
visibility: hidden;
border-right: none;
width: 0px;
}
.CodeMirror div.CodeMirror-cursor {
visibility: hidden;
}
.CodeMirror-focused div.CodeMirror-cursor {
visibility: inherit;
}
.cm-searching {
background: rgba(255, 255, 0, 0.4);
}
span.cm-underlined {
text-decoration: underline;
}
span.cm-strikethrough {
text-decoration: line-through;
}
.cm-tw-syntaxerror {
color: rgb(255, 255, 255);
background-color: rgb(153, 0, 0);
}
.cm-tw-deleted {
text-decoration: line-through;
}
.cm-tw-header5 {
font-weight: 700;
}
.cm-tw-listitem:first-child {
padding-left: 10px;
}
.cm-tw-box {
border-style: solid;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
border-color: inherit;
border-top-width: 0px !important;
}
.cm-tw-underline {
text-decoration: underline;
}
@media print {
.CodeMirror div.CodeMirror-cursor {
visibility: hidden;
}
}
/* 待完善 */
/**
* 1. 代码非等宽字体;
* 2. kbd样式
* 3. 行内代码样式优化
*/
/* 字体引入:鸿蒙字体 */
html {
font-size: 16px;
font-family: 'HarmonyOS_Sans_SC';
}
/* 打印页面设置 */
@media print {
* {
-webkit-print-color-adjust: exact;
/*确保打印颜色一致*/
print-color-adjust: exact;
}
body {
width: 21cm;
/* 设置页面宽度为A4宽度 */
height: 29.7cm;
/* 设置页面高度为A4高度 */
margin: 1cm;
/* 设置页面边距 */
}
p {
line-height: 1.5rem;
/*设置打印内容的行高*/
}
ol,
ul,
figure,
pre {
/*设置一些元素不会被分页截断对应有序列表、无序列表、图片(表格)、代码块*/
page-break-inside: avoid;
break-inside: avoid;
}
}
/* 软件内部Markdown样式 */
#write {
max-width: 950px;
margin: 0 auto;
padding: 15px;
line-height: 2.25;
color: #000;
letter-spacing: 1.1px;
word-break: break-word;
word-wrap: break-word;
text-align: left;
background-image:
linear-gradient(
90deg,
rgba(50, 0, 0, 0.05) calc(3% * var(--bg-grid)),
rgba(0, 0, 0, 0) calc(3% * var(--bg-grid))
),
linear-gradient(
360deg,
rgba(50, 0, 0, 0.05) calc(3% * var(--bg-grid)),
rgba(0, 0, 0, 0) calc(3% * var(--bg-grid))
);
background-size: 20px 20px;
background-position: center center;
}
#write p {
color: #333;
margin: 10px 10px;
font-family:
Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, 'PingFang SC', Cambria,
Cochin, Georgia, Times, 'Times New Roman', serif;
font-size: 1rem;
word-spacing: 2px;
}
#write h3:after,
h4:after,
h5:after,
h6:after {
content: '';
display: inline-block;
margin-left: 0.2em;
height: 2em;
width: 1.2em;
vertical-align: top;
}
#write h3:after {
background: var(--h3-r-graphic);
}
#write h4:after {
background: var(--h4-r-graphic);
}
#write h5:after {
background: var(--h5-r-graphic);
}
#write h6:after {
background: var(--h6-r-graphic);
}
/* 一级标题 */
#write h1:after {
font-size: 1.8rem;
text-align: center;
font-weight: bold;
color: #000;
border-bottom: none;
}
#write h1 {
text-align: center;
}
/* 二级标题 */
#write h2 {
color: var(--head-title-h2-color);
font-size: 1.4rem;
line-height: 1.6;
width: fit-content;
font-weight: bold;
margin: 20px 0;
padding: 1px 12.5px;
border-radius: 4px;
background: var(--head-title-h2-background);
background-size: 200% 100%;
background-position: 0% 0%;
transition: all ease-in-out 0.1s;
}
#write h2.md-heading a {
text-decoration: underline;
border-bottom: 0;
text-decoration-thickness: 1.2px;
text-underline-offset: 2px;
}
#write h2:hover {
background-position: -100% -100%;
transition: all ease-in-out 0.1s;
}
/* 三级标题 */
#write h3 {
width: fit-content;
margin: 20px 0;
font-size: 1.3rem;
text-align: left;
padding-left: 10px;
border-left: 5px solid var(--head-title-color);
}
/* 三级标题内容 */
#write h3 span {
border-bottom: 2px hidden var(--head-title-color);
}
/* #write h3 span:hover {
border-bottom: 2px solid var(--head-title-color);
transition: all linear 0.1s;
} */
#write h4 {
margin: 20px 0;
font-size: 1.15rem;
text-align: left;
}
#write h4::before {
content: '';
margin-right: 7px;
display: inline-block;
background-color: var(--head-title-color);
width: 10px;
height: 10px;
border-radius: 100%;
border: var(--head-title-color) 1px solid;
vertical-align: inherit;
}
#write h5 {
margin: 23px 0;
font-size: 1.1rem;
text-align: left;
}
#write h5::before {
content: '';
margin-right: 7px;
display: inline-block;
background-color: #ffffff;
width: 10px;
height: 10px;
border-radius: 100%;
border: var(--head-title-color) 2px solid;
vertical-align: inherit;
}
#write h6 {
margin: 23px 0;
font-size: 1.1rem;
text-align: left;
}
#write h6::before {
content: '-';
color: var(--head-title-color);
margin-right: 7px;
display: inline-block;
vertical-align: inherit;
}
/* 标题自动编号 */
#write {
counter-reset: h1;
}
h1 {
counter-reset: h2;
}
h2 {
counter-reset: h3;
}
h3 {
counter-reset: h4;
}
h4 {
counter-reset: h5;
}
h5 {
counter-reset: h6;
}
.sidebar-content {
counter-reset: h1;
}
.outline-content {
counter-reset: h1;
}
.outline-h1 {
counter-reset: h2;
}
.outline-h2 {
counter-reset: h3;
}
.outline-h3 {
counter-reset: h4;
}
.outline-h4 {
counter-reset: h5;
}
.outline-h5 {
counter-reset: h6;
}
.md-toc-content {
counter-reset: h1toc;
}
.md-toc-h1 {
counter-reset: h2toc;
}
.md-toc-h2 {
counter-reset: h3toc;
}
.md-toc-h3 {
counter-reset: h4toc;
}
.md-toc-h4 {
counter-reset: h5toc;
}
.md-toc-h5 {
counter-reset: h6toc;
}
#write h1:before {
counter-increment: h1;
content: var(--autonum-h1);
}
#outline-content li.outline-h1 > div > span.outline-label:before {
counter-increment: h1;
content: var(--autonum-h1);
}
.outline-content .outline-h1 > .outline-item > .outline-label:before {
counter-increment: h1;
content: var(--autonum-h1);
}
#write span.md-toc-item.md-toc-h1 > a:before {
counter-increment: h1toc;
content: var(--autonum-h1toc);
}
#write h2:before {
counter-increment: h2;
content: var(--autonum-h2);
color: var(--head-title-h2-color);
}
.outline-content .outline-h2 > .outline-item > .outline-label:before {
counter-increment: h2;
content: var(--autonum-h2);
}
li.outline-h2 > div > a.outline-label:before {
counter-increment: h2;
content: var(--autonum-h2);
}
#write span.md-toc-item.md-toc-h2 > a:before {
counter-increment: h2toc;
content: var(--autonum-h2toc);
}
#write h3 > span:first-of-type::before {
counter-increment: h3;
content: var(--autonum-h3);
color: var(--element-color);
}
#outline-content li.outline-h3 > div > span.outline-label:before {
counter-increment: h3;
content: var(--autonum-h3);
}
.outline-content .outline-h3 > .outline-item > .outline-label:before {
counter-increment: h3;
content: var(--autonum-h3);
}
#write span.md-toc-item.md-toc-h3 > a:before {
counter-increment: h3toc;
content: var(--autonum-h3toc);
}
#write h4 > span:first-of-type::before {
counter-increment: h4;
content: var(--autonum-h4);
color: var(--element-color);
}
#outline-content li.outline-h4 > div > span.outline-label:before {
counter-increment: h4;
content: var(--autonum-h4);
}
.outline-content .outline-h4 > .outline-item > .outline-label:before {
counter-increment: h4;
content: var(--autonum-h4);
}
#write span.md-toc-item.md-toc-h4 > a:before {
counter-increment: h4toc;
content: var(--autonum-h4toc);
}
#write h5 > span:first-of-type::before {
counter-increment: h5;
content: var(--autonum-h5);
color: var(--element-color);
}
#outline-content li.outline-h5 > div > span.outline-label:before {
counter-increment: h5;
content: var(--autonum-h5);
}
.outline-content .outline-h5 > .outline-item > .outline-label:before {
counter-increment: h5;
content: var(--autonum-h5);
}
#write span.md-toc-item.md-toc-h5 > a:before {
counter-increment: h5toc;
content: var(--autonum-h5toc);
}
#write h6 > span:first-of-type::before {
counter-increment: h6;
content: var(--autonum-h6);
color: var(--element-color);
}
#outline-content li.outline-h6 > div > span.outline-label:before {
counter-increment: h6;
content: var(--autonum-h6);
}
.outline-content .outline-h6 > .outline-item > .outline-label:before {
counter-increment: h6;
content: var(--autonum-h6);
}
#write span.md-toc-item.md-toc-h6 > a:before {
counter-increment: h6toc;
content: var(--autonum-h6toc);
}
/* 列表 */
::marker {
color: var(--element-color-deep);
}
li.md-list-item {
margin: 0.4rem 0;
}
#write ul,
#write ol {
margin-top: 0px;
margin-left: 16px;
margin-bottom: 8px;
padding-left: 13px;
}
#write em {
padding: 0 3px 0 0;
}
#write ul {
list-style-type: disc;
}
#write ul ul {
list-style-type: circle;
}
#write ul ul ul {
list-style-type: square;
}
#write ol {
list-style-type: decimal;
}
#write ol ol {
list-style-type: lower-alpha;
}
#write ol ol ol {
list-style-type: lower-roman;
}
#write li section {
margin-top: 5px;
margin-bottom: 5px;
line-height: 1.7rem;
text-align: justify;
color: #000000;
font-weight: 500;
}
#write li:before {
content: '';
height: calc(100% - 50px);
top: 35px;
position: absolute;
border-left: 0.5px solid var(--element-color);
left: -14.5px;
}
/* 任务列表样式 */
.task-list-item input {
width: 1.25rem;
height: 1.25rem;
display: block;
-webkit-appearance: initial;
top: 3px;
left: 4px;
}
.task-list-item input:focus {
outline: none;
box-shadow: none;
}
.task-list-item input:before {
border: 1px solid var(--element-color-deep);
border-radius: 1.2rem;
width: 1.2rem;
height: 1.2rem;
background: #fff;
content: ' ';
transition: background-color 200ms ease-in-out;
display: block;
}
.task-list-item input:checked:before,
.task-list-item input[checked]:before {
background: var(--element-color-soo-shallow);
border-width: 2px;
display: inline-block;
transition: background-color 200ms ease-in-out;
}
.task-list-item input:checked:after,
.task-list-item input[checked]:after {
opacity: 1;
}
/* .task-list-item input[type="checkbox"]:checked + p span {
text-decoration: line-through;
text-decoration-color:var(--element-color)
} */
.task-list-item input[type='checkbox'] + p span {
position: relative;
display: inline-block;
}
.task-list-item input[type='checkbox'] + p span::after {
content: '';
position: absolute;
left: 0;
top: 52%;
width: calc(100% * var(--check-line));
height: 2px;
background: var(--element-color);
transform: scaleX(0);
transform-origin: left center;
transition: transform 0.2s ease-in-out;
}
.task-list-item input[type='checkbox']:checked + p span::after {
transform: scaleX(1);
}
.task-list-item input[type='checkbox']:not(:checked) + p span::after {
transform-origin: right center;
transition-delay: 0.1s;
}
.task-list-item input:after {
opacity: 1;
-webkit-transition: opacity 0.05s ease-in-out;
-moz-transition: opacity 0.05s ease-in-out;
transition: opacity 0.05s ease-in-out;
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
transform: rotate(-45deg);
position: absolute;
top: 0.325rem;
left: 0.28125rem;
width: 0.6375rem;
height: 0.4rem;
border: 3px solid var(--element-color-deep);
border-top: 0;
border-right: 0;
content: ' ';
opacity: 0;
}
/* 引用 */
#write blockquote {
margin-left: 12px;
padding: 12px;
background: var(--element-color-soo-shallow);
border: 0px solid var(--element-color);
border-left-color: var(--element-color);
border-left-width: 4px;
border-radius: 4px;
line-height: 26px;
}
#write blockquote p {
color: #000;
}
/* 超链接 */
#write a {
color: #000;
}
#write a:visited {
color: var(--element-color-deep);
}
#write a:not(.md-toc-inner) {
font-weight: bolder;
text-decoration: none;
transform: all linear 0.1s;
}
#write a:hover:not(.md-toc-inner) {
font-weight: bold;
color: var(--element-color-deep);
border-bottom: 1px solid var(--element-color-deep);
transform: all linear 0.1s;
}
#write p a:not(.md-toc-inner)::before {
content: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'%3E%3Cpath d='M477.934459 330.486594A50.844091 50.844091 0 0 1 406.752731 258.796425L512 152.532274a254.220457 254.220457 0 0 1 359.467726 359.467726L762.66137 618.772592a50.844091 50.844091 0 1 1-71.690168-71.690169l106.772591-106.772592a152.532274 152.532274 0 0 0-215.578947-215.578947z m70.164846 361.501489A50.844091 50.844091 0 1 1 619.789474 762.66137l-107.281033 107.281033A254.220457 254.220457 0 0 1 152.532274 512L259.813307 406.752731a50.844091 50.844091 0 1 1 72.19861 69.656405l-107.789474 107.281033a152.532274 152.532274 0 0 0 215.578947 215.578947z m-126.601788-16.77855a50.844091 50.844091 0 1 1-71.690168-71.690169l251.678252-251.678252a50.844091 50.844091 0 0 1 71.690169 71.690169z'/%3E%3C/svg%3E");
color: #f68800;
display: inline-block;
width: 1em;
height: 1em;
margin-right: 0.2em;
vertical-align: sub;
}
#write a.md-toc-inner:hover {
color: var(--element-color-deep);
font-weight: 700;
text-decoration: none;
}
#write sup a::before {
content: none;
}
/* 加粗 */
#write strong {
color: #000;
font-weight: bold;
}
/* 斜体 */
#write em {
font-style: italic;
color: #000;
}
/* 高亮 */
#write mark {
font-weight: bolder;
color: #000;
background: var(--element-color-so-shallow);
}
/* 删除线 */
#write del {
text-decoration-color: var(--element-color-deep);
}
/* 分隔线*/
#write hr {
height: 1px;
padding: 0;
border: none;
border-top: 2px solid var(--head-title-color);
}
/* 图片*/
#write img {
border-radius: 6px;
margin: 20px auto;
object-fit: contain;
}
/* 图片描述文字 */
#write figcaption {
display: block;
font-size: 13px;
color: #595959;
}
/* Yaml */
pre.md-meta-block {
padding: 8px 15px;
border: 2px dotted var(--element-color);
background-color: var(--element-color-soo-shallow);
}
/* 行内代码 */
#write p code {
padding: 3px 3px 1px;
color: var(--element-color-linecode);
background: var(--element-color-linecode-background);
border-radius: 3px;
font-family: 'CascadiaCode' monospace;
letter-spacing: 0.5px;
}
#write li code {
color: var(--element-color-linecode);
}
/* 代码块 */
.md-fences:not([lang='mermaid'])::before {
content: attr(lang);
font-family: 'CascadiaCode' monospace;
text-align: right;
padding-right: 15px;
color: #7e7e7e;
display: block;
background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNDUwcHgiIGhlaWdodD0iMTMwcHgiPgogIDxlbGxpcHNlIGN4PSI2NSIgY3k9IjY1IiByeD0iNTAiIHJ5PSI1MiIgc3Ryb2tlPSJyZ2IoMjIwLDYwLDU0KSIgc3Ryb2tlLXdpZHRoPSIyIiBmaWxsPSJyZ2IoMjM3LDEwOCw5NikiLz4KICA8ZWxsaXBzZSBjeD0iMjI1IiBjeT0iNjUiIHJ4PSI1MCIgcnk9IjUyIiAgc3Ryb2tlPSJyZ2IoMjE4LDE1MSwzMykiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0icmdiKDI0NywxOTMsODEpIi8+CiAgPGVsbGlwc2UgY3g9IjM4NSIgY3k9IjY1IiByeD0iNTAiIHJ5PSI1MiIgIHN0cm9rZT0icmdiKDI3LDE2MSwzNykiIHN0cm9rZS13aWR0aD0iMiIgZmlsbD0icmdiKDEwMCwyMDAsODYpIi8+Cjwvc3ZnPg==);
height: 30px;
width: 100%;
background-size: 40px;
background-repeat: no-repeat;
background-color: #f8f8f8;
border-radius: 5px 5px 0 0;
background-position: 6px 10px;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: auto;
}
.md-fences .cm-s-inner.CodeMirror {
margin-top: -0.5rem;
}
.cm-s-inner.CodeMirror {
padding: 1.2rem 0.8rem;
color: #4f5467;
font-family: 'CascadiaCode' monospace;
border-radius: 10px;
background-color: #fa0303;
/* border: 1px solid #eef2f5;*/
line-height: 1.6rem;
}
.CodeMirror-gutters {
border-right: 1px solid #9d9d9d52;
background: inherit;
white-space: nowrap;
}
pre.CodeMirror-line {
padding: 0 1.2rem;
}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
text-align: right;
color: #a3a3a3;
}
.cm-s-inner.CodeMirror {
background: #f8f8f8;
border-radius: 0 0 5px 5px;
padding: 20px 10px 20px 10px;
page-break-before: auto;
line-height: 1.8rem;
}
.md-rawblock .md-rawblock-tooltip {
inset: auto 0.3rem auto auto;
transform: translateY(-120%);
}
/* 代码块颜色 */
.cm-keyword {
color: #a626a4 !important;
font-weight: 700 !important;
}
.cm-variable {
color: #b92121 !important;
}
.cm-tag {
color: var(--color-cm-keyword) !important;
font-weight: 700 !important;
}
.cm-variable-3,
.cm-variable-2 {
color: #7aadad !important;
font-weight: 700 !important;
}
.cm-def {
color: #c18401 !important;
}
.cm-attribute {
color: #8f6aa8 !important;
}
.cm-comment,
.md-comment,
.md-meta {
color: #9a9a9a !important;
}
.cm-string {
color: #50a14f !important;
font-variant-ligatures: common-ligatures !important;
}
.cm-link {
color: #e46918 !important;
}
.cm-type {
color: #626161;
}
.cm-property {
color: #800a84 !important;
}
.cm-tag:not(.cm-bracket) {
font-weight: 700 !important;
}
.cm-operator {
color: #0abe00 !important;
}
.cm-number {
color: #1694b6 !important;
}
.cm-meta {
color: #4078f2 !important;
font-weight: 700 !important;
}
.cm-builtin {
color: #fa6060 !important;
}
/* KBD */
kbd {
padding: 2px 4px;
font-size: 90%;
font-weight: bolder;
color: var(--element-color-linecode);
border: var(--element-color) solid 1px;
border-radius: 3px;
transition: all 0.2s linear;
box-shadow: inset 0 -1px 0 var(--element-color-so-shallow);
}
kbd:hover {
background: var(--element-color-so-shallow);
}
/** 表格内的单元格*/
#write table tr th,
#write table tr td {
font-size: 14px;
color: #000;
}
#write .footnotes {
padding: 10px;
font-size: 14px;
border-radius: 6px;
border: 0.8px solid var(--element-color-deep);
}
#write table.md-table {
overflow: hidden;
}
#write table thead {
border-top: 1px solid #dedddd;
border-bottom: 1px solid #dedddd;
}
#write table tbody {
border-bottom: 1px solid #dedddd;
}
/* 脚注文字 */
#write .footnote-word {
font-weight: normal;
color: #595959;
}
/* 脚注上标 */
#write .footnote-ref {
font-weight: normal;
color: #595959;
}
/*脚注链接样式*/
#write .footnote-item em {
font-size: 14px;
color: #595959;
display: block;
background: none;
}
/* 目录 */
.md-toc * {
font-family: 'HarmonyOS_Sans_SC';
}
.md-tooltip-hide > span {
display: none;
}
.md-toc:before {
position: relative;
display: inline-block;
width: 100%;
text-align: center;
content: '目录';
font-size: 20px;
font-weight: 700;
color: #000;
}
.md-toc {
padding: 20px 0;
margin: 0 20px;
background-color: var(--element-color-soo-shallow);
border: 2px solid var(--element-color);
border-radius: 5px;
}
.md-toc-item {
line-height: 1.8em;
display: block;
color: #333;
}
/* 程序UI */
/* 侧边栏 */
#typora-sidebar {
height: 100%;
color: var(--appui-color-text);
font-size: 0.92rem;
background-color: #fff;
}
/* 滑块 */
#outline-content::-webkit-scrollbar {
width: 5px;
}
#file-library::-webkit-scrollbar {
width: 5px;
}
::-webkit-scrollbar-track {
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
background: rgba(179, 179, 179, 0.425);
}
::-webkit-scrollbar {
width: 5px;
}
/* 侧边栏 文件 */
.active-tab-files #info-panel-tab-file .info-panel-tab-border,
.active-tab-outline #info-panel-tab-outline .info-panel-tab-border,
.ty-show-search #info-panel-tab-search .info-panel-tab-border {
border-radius: 10px;
height: 4px;
background-color: var(--appui-color);
}
.file-node-content {
line-height: 1.2rem;
}
.file-tree-node.active > .file-node-content {
color: var(--appui-color);
}
span.file-node-title {
color: var(--appui-color-text);
}
.file-node-icon {
color: var(--appui-color-icon);
padding-right: 0.2rem;
}
.file-tree-node.active > .file-node-background {
font-weight: bolder;
border-left: 4px solid var(--appui-color);
border-color: var(--appui-color);
}
.info-panel-tab-title {
font-weight: bolder;
color: var(--appui-color-text);
}
/* 侧边栏 搜索 */
#file-library-search-panel {
background-color: #fff;
}
#file-library-search-input {
border-radius: 3px;
border-color: var(--appui-color);
}
#file-library-search-input:focus {
border-width: 2px;
}
/* 侧边栏 大纲 */
#outline-content .outline-h1 > .outline-item {
font-size: larger;
font-weight: bold;
color: var(--element-color-deep);
}
#outline-content .outline-h1:not(:first-of-type) > .outline-item {
margin-top: 10px;
}
#outline-content .outline-h2 > .outline-item::before {
content: '';
width: 12px;
height: 12px;
background: var(--element-color);
vertical-align: middle;
float: left;
margin-top: -2px;
margin-right: 11px;
margin-left: -24px;
border-radius: 100%;
border: 3px solid #fff;
z-index: 100;
position: relative;
top: 8px;
left: 8px;
}
#outline-content .outline-h2::after {
content: '';
height: calc(100% - 24px);
width: 1px;
background: var(--element-color);
position: absolute;
left: 3px;
top: 21px;
}
#outline-content .outline-h2 > .outline-item:last-child:after {
display: none;
}
#outline-content .outline-h2 > .outline-item > .outline-label {
line-height: 1.65rem;
margin: 0;
}
#outline-content .outline-h2 > .outline-item {
margin-bottom: -3px;
}
#outline-content .outline-h3 > .outline-item > .outline-label {
border-left: 2px solid var(--element-color);
padding-left: 8px;
}
.outline-item-active:not(.outline-item-wrapper)::after {
content: '';
position: relative;
width: 11px;
height: 8px;
background: var(--element-color-deep);
float: right;
top: -12px;
z-index: 100;
border-radius: 40% 20% 20% 40%;
}
/* 导出HTML的样式 */
body.typora-export {
padding-left: 0px;
}
.typora-export-content .outline-content::before {
content: '目录';
font-size: 20px;
font-weight: bold;
position: absolute;
top: 22px;
left: 15px;
border-radius: 5px;
box-sizing: border-box;
z-index: -1;
}
.typora-export-sidebar .outline-content {
height: 100%;
padding-left: 15px;
border-right: 1px solid #d2d2d2;
}
.typora-export-content .typora-export-content {
padding-left: 0px;
}
.typora-export-content .outline-expander {
width: 0;
}
.typora-export-content .outline-item-active > .outline-item::after {
content: '';
position: relative;
width: 11px;
height: 8px;
background: var(--element-color-deep);
float: right;
right: 5px;
top: -14px;
z-index: 100;
border-radius: 40% 20% 20% 40%;
}
.typora-export-content .outline-label {
max-width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.outline-content .outline-h1 > .outline-item {
font-size: larger;
font-weight: bold;
color: var(--element-color-deep);
}
.outline-content .outline-h1:not(:first-of-type) > .outline-item {
margin-top: 10px;
}
.outline-content .outline-h2 > .outline-item::before {
content: '';
width: 12px;
height: 12px;
background: var(--element-color-deep);
vertical-align: middle;
float: left;
margin-top: -2px;
margin-right: 11px;
margin-left: -24px;
border-radius: 100%;
border: 3px solid #fff;
z-index: 100;
position: relative;
top: 8px;
left: 8px;
}
.outline-content .outline-h2::after {
content: '';
height: calc(100% - 24px);
width: 1px;
background: var(--element-color);
position: absolute;
left: 3px;
top: 21px;
}
.outline-content .outline-h2 > .outline-item:last-child:after {
display: none;
}
.outline-content .outline-h2 > .outline-item > .outline-label {
line-height: 1.65rem;
margin: 0;
}
.outline-content .outline-h2 > .outline-item {
margin-bottom: -3px;
}
.outline-content .outline-h3 > .outline-item > .outline-label {
border-left: 2px solid var(--element-color);
padding-left: 8px;
}
@import url();
:root {
/* 标题后小图标借鉴自思源笔记主题——Savor */
--h1-r-graphic: url("data:image/svg+xml;utf8,<svg fill='rgba(74, 200, 141, 0.5)' height='24' viewBox='0 0 32 32' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M4.8 29.714v0c-1.371 0-2.514-1.143-2.514-2.514v0c0-1.371 1.143-2.514 2.514-2.514v0c1.371 0 2.514 1.143 2.514 2.514v0c0.114 1.371-1.029 2.514-2.514 2.514z'/></svg>")
no-repeat center;
--h2-r-graphic: url("data:image/svg+xml;utf8,<svg fill='rgba(74, 200, 141, 0.5)' height='24' viewBox='0 0 32 32' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M11.429 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM4.571 18.286c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286z'/></svg>")
no-repeat center;
--h3-r-graphic: url("data:image/svg+xml;utf8,<svg fill='rgba(74, 200, 141, 0.5)' height='28' viewBox='0 0 32 32' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M4.571 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM4.571 18.286c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286z'/></svg>")
no-repeat center;
--h4-r-graphic: url("data:image/svg+xml;utf8,<svg fill='rgba(74, 200, 141, 0.5)' height='24' viewBox='0 0 32 32' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M4.571 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM4.571 18.286c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 22.857c1.257 0 2.286-1.029 2.286-2.286s-1.029-2.286-2.286-2.286-2.286 1.029-2.286 2.286 1.029 2.286 2.286 2.286z'/></svg>")
no-repeat center;
--h5-r-graphic: url("data:image/svg+xml;utf8,<svg fill='rgba(74, 200, 141, 0.5)' height='24' viewBox='0 0 32 32' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M4.571 18.286c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 22.857c1.257 0 2.286-1.029 2.286-2.286s-1.029-2.286-2.286-2.286-2.286 1.029-2.286 2.286 1.029 2.286 2.286 2.286zM4.571 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM4.571 11.429c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286z'/></svg>")
no-repeat center;
--h6-r-graphic: url("data:image/svg+xml;utf8,<svg fill='rgba(74, 200, 141, 0.5)' height='24' viewBox='0 0 32 32' width='24' xmlns='http://www.w3.org/2000/svg'><path d='M4.571 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM4.571 18.286c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM4.571 11.429c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 18.286c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 25.143c-1.257 0-2.286 1.029-2.286 2.286s1.029 2.286 2.286 2.286 2.286-1.029 2.286-2.286-1.029-2.286-2.286-2.286zM11.429 16c1.257 0 2.286-1.029 2.286-2.286s-1.029-2.286-2.286-2.286-2.286 1.029-2.286 2.286 1.029 2.286 2.286 2.286z'/></svg>")
no-repeat center;
/* 是否开启网格背景1 是0 否 */
--bg-grid: 0;
/* 已完成的代办事项是否显示删除线1 是0 否 */
--check-line: 1;
/* 自动编号格式设置 无需自动编号可全部注释掉或部分注释掉*/
/* --autonum-h1: counter(h1) ". ";
--autonum-h2: counter(h1) "." counter(h2) ". ";
--autonum-h3: counter(h1) "." counter(h2) "." counter(h3) ". ";
--autonum-h4: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) ". ";
--autonum-h5: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) ". ";
--autonum-h6: counter(h1) "." counter(h2) "." counter(h3) "." counter(h4) "." counter(h5) "." counter(h6) ". "; */
/* 下面是文章内Toc目录自动编号与上面一样即可 */
/* --autonum-h1toc: counter(h1toc) ". ";
--autonum-h2toc: counter(h1toc) "." counter(h2toc) ". ";
--autonum-h3toc: counter(h1toc) "." counter(h2toc) "." counter(h3toc) ". ";
--autonum-h4toc: counter(h1toc) "." counter(h2toc) "." counter(h3toc) "." counter(h4toc) ". ";
--autonum-h5toc: counter(h1toc) "." counter(h2toc) "." counter(h3toc) "." counter(h4toc) "." counter(h5toc) ". ";
--autonum-h6toc: counter(h1toc) "." counter(h2toc) "." counter(h3toc) "." counter(h4toc) "." counter(h5toc) "." counter(h6toc) ". "; */
/* 主题颜色 */
--head-title-color: #3db8bf;
/* 标题主色 */
--head-title-h2-color: #fff;
--head-title-h2-background: linear-gradient(to right, #3db8d3, #80f7c4);
/* 二级标题主色,因为二级标题是背景色的,所以单独设置 */
--element-color: #3db8bf;
/* 元素主色 */
--element-color-deep: #089ba3;
/* 元素深色 */
--element-color-shallow: #7aeaf0;
/* 元素浅色 */
--element-color-so-shallow: #7aeaf077;
/* 元素很浅色 */
--element-color-soo-shallow: #7aeaf018;
/* 元素非常浅色 */
--element-color-linecode: #089ba3;
/* 行内代码文字色 */
--element-color-linecode-background: #7aeaf018;
/* 行内代码背景色 */
/* 程序本体UI */
--appui-color: #3db8bf;
/* 程序UI主题色 */
--appui-color-icon: #3db8bf;
/* 程序UI图标颜色 */
--appui-color-text: #333;
/* 程序UI文字色 */
--primary-color: #3db8bf;
}
</style>
<title>design</title>
</head>
<body class="typora-export os-windows">
<div class="typora-export-content">
<div id="write" class="">
<h1 id="ceru-music-产品设计文档"><span>Ceru Music 产品设计文档</span></h1>
<h2 id="项目概述"><span>项目概述</span></h2>
<p>
<span
>Ceru Music 是一个基于 Electron + Vue 3
的跨平台桌面音乐播放器,支持多音乐平台数据源,提供流畅的音乐播放体验。</span
>
</p>
<h2 id="项目架构"><span>项目架构</span></h2>
<h3 id="技术栈"><span>技术栈</span></h3>
<ul>
<li>
<p>
<strong><span>前端框架</span></strong
><span>: Vue 3 + TypeScript + Composition API</span>
</p>
</li>
<li>
<p>
<strong><span>桌面框架</span></strong
><span>: Electron (v37.2.3)</span>
</p>
</li>
<li>
<p>
<strong><span>UI组件库</span></strong
><span>: TDesign Vue Next (v1.15.2)</span>
</p>
</li>
<li>
<p>
<img
src="D:\code\Ceru-Music\docs\assets\image-20250813180317221.png"
referrerpolicy="no-referrer"
alt="image-20250813180317221"
/>
</p>
</li>
<li>
<p>
<strong><span>状态管理</span></strong
><span>: Pinia (v3.0.3)</span>
</p>
</li>
<li>
<p>
<strong><span>路由管理</span></strong
><span>: Vue Router (v4.5.1)</span>
</p>
</li>
<li>
<p>
<strong><span>构建工具</span></strong
><span>: Vite + electron-vite</span>
</p>
</li>
<li>
<p>
<strong><span>包管理器</span></strong
><span>: PNPM</span>
</p>
</li>
<li>
<p>
<strong><span>Node pnpm 版本</span></strong
><span></span>
</p>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="bash"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="bash"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">PS D:\code\Ceru-Music&gt; <span class="cm-builtin">node</span> <span class="cm-attribute">-v</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">v22.17.0</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">PS D:\code\Ceru-Music&gt; pnpm <span class="cm-attribute">-v</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-number">10</span>.14.0</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 115px;"></div><div class="CodeMirror-gutters" style="display: none; height: 115px;"></div></div></div></pre>
</li>
</ul>
<p><span>-</span></p>
<h3 id="架构设计"><span>架构设计</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="asp"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="asp"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">Ceru Music</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">├── 主进程 (Main Process)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── 应用生命周期管理</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── 窗口管理</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── 系统集成 (托盘、快捷键)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; └── 文件系统操作</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">├── 渲染进程 (Renderer Process)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── Vue 3 应用</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── 用户界面</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── 音乐播放控制</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; └── 数据展示</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">└── 预加载脚本 (Preload Script)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; └── 安全的 IPC 通信桥梁</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 374px;"></div><div class="CodeMirror-gutters" style="display: none; height: 374px;"></div></div></div></pre>
<h3 id="目录结构"><span>目录结构</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang=""
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">src/</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">├── main/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # 主进程代码</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── index.ts &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # 主进程入口</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; ├── window.ts &nbsp; &nbsp; &nbsp; &nbsp; # 窗口管理</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; └── services/ &nbsp; &nbsp; &nbsp; &nbsp; # 主进程服务</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">├── preload/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # 预加载脚本</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">&nbsp; └── index.ts &nbsp; &nbsp; &nbsp; &nbsp; # IPC 通信接口</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">└── renderer/ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # 渲染进程 (Vue 应用)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; ├── src/</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;&nbsp; ├── components/ &nbsp; # Vue 组件</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;&nbsp; ├── views/ &nbsp; &nbsp; &nbsp; &nbsp; # 页面视图</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;&nbsp; ├── stores/ &nbsp; &nbsp; &nbsp; # Pinia 状态管理</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;&nbsp; ├── services/ &nbsp; &nbsp; # API 服务</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;&nbsp; ├── utils/ &nbsp; &nbsp; &nbsp; &nbsp; # 工具函数</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;&nbsp; └── types/ &nbsp; &nbsp; &nbsp; &nbsp; # TypeScript 类型定义</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; └── index.html &nbsp; &nbsp; &nbsp; &nbsp; # 应用入口</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 461px;"></div><div class="CodeMirror-gutters" style="display: none; height: 461px;"></div></div></div></pre>
<h2 id="项目开发使用方式"><span>项目开发使用方式</span></h2>
<h3 id="开发环境启动"><span>开发环境启动</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="bash"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="bash"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 安装依赖</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm install</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 启动开发服务器</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm dev</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 代码检查</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm lint</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 类型检查</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm typecheck</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 317px;"></div><div class="CodeMirror-gutters" style="display: none; height: 317px;"></div></div></div></pre>
<h3 id="构建打包"><span>构建打包</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="bash"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="bash"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 构建当前平台</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm build</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 构建 Windows 版本</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm build:win</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 构建 macOS 版本</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm build:mac</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment"># 构建 Linux 版本</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">pnpm build:linux</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 317px;"></div><div class="CodeMirror-gutters" style="display: none; height: 317px;"></div></div></div></pre>
<h2 id="音乐数据源接口设计"><span>音乐数据源接口设计</span></h2>
<h3 id="接口1-网易云音乐原生接口-主要数据源">
<span>接口1: 网易云音乐原生接口 (主要数据源)</span>
</h3>
<h4 id="获取音乐信息"><span>获取音乐信息</span></h4>
<ul>
<li>
<strong><span>请求地址</span></strong
><span>: </span><code>https://music.163.com/api/song/detail</code>
</li>
<li>
<strong><span>请求参数</span></strong
><span>: </span><code>ids=[ID1,ID2,ID3,...]</code><span> 音乐ID列表</span>
</li>
<li>
<strong><span>示例</span></strong
><span>: </span><code>https://music.163.com/api/song/detail?ids=[36270426]</code>
</li>
</ul>
<h4 id="获取音乐直链"><span>获取音乐直链</span></h4>
<ul>
<li>
<strong><span>请求地址</span></strong
><span>: </span><code>https://music.163.com/song/media/outer/url</code>
</li>
<li>
<strong><span>请求参数</span></strong
><span>: </span><code>id=123</code><span> 音乐ID</span>
</li>
<li>
<strong><span>示例</span></strong
><span>: </span><code>https://music.163.com/song/media/outer/url?id=36270426.mp3</code>
</li>
</ul>
<h4 id="获取歌词"><span>获取歌词</span></h4>
<ul>
<li>
<p>
<strong><span>请求地址</span></strong
><span>: </span><code>https://music.163.com/api/song/lyric</code>
</p>
</li>
<li>
<p>
<strong><span>请求参数</span></strong
><span>:</span>
</p>
<ul>
<li><code>id=123</code><span> 音乐ID</span></li>
<li><code>lv=-1</code><span> 获取歌词</span></li>
<li><code>yv=-1</code><span> 获取逐字歌词</span></li>
<li><code>tv=-1</code><span> 获取歌词翻译</span></li>
</ul>
</li>
<li>
<p>
<strong><span>示例</span></strong
><span>: </span
><code
>https://music.163.com/api/song/lyric?id=36270426&amp;lv=-1&amp;yv=-1&amp;tv=-1</code
>
</p>
</li>
</ul>
<h4 id="搜索歌曲"><span>搜索歌曲</span></h4>
<ul>
<li>
<p>
<strong><span>请求地址</span></strong
><span>: </span><code>https://music.163.com/api/search/get/web</code>
</p>
</li>
<li>
<p>
<strong><span>请求参数</span></strong
><span>:</span>
</p>
<ul>
<li><code>s</code><span> 歌名</span></li>
<li><code>type=1</code><span> 搜索类型</span></li>
<li><code>offset=0</code><span> 偏移量</span></li>
<li><code>limit=10</code><span> 搜索结果数量</span></li>
</ul>
</li>
<li>
<p>
<strong><span>示例</span></strong
><span>: </span
><code
>https://music.163.com/api/search/get/web?s=来自天堂的魔鬼&amp;type=1&amp;offset=0&amp;limit=10</code
>
</p>
</li>
</ul>
<h3 id="接口2-meting-api-备用数据源"><span>接口2: Meting API (备用数据源)</span></h3>
<h4 id="参数说明"><span>参数说明</span></h4>
<ul>
<li>
<p>
<strong><span>server</span></strong
><span>: 数据源</span>
</p>
<ul>
<li><code>netease</code><span> 网易云音乐(默认)</span></li>
<li><code>tencent</code><span> QQ音乐</span></li>
</ul>
</li>
<li>
<p>
<strong><span>type</span></strong
><span>: 类型</span>
</p>
<ul>
<li><code>name</code><span> 歌曲名</span></li>
<li><code>artist</code><span> 歌手</span></li>
<li><code>url</code><span> 链接</span></li>
<li><code>pic</code><span> 封面</span></li>
<li><code>lrc</code><span> 歌词</span></li>
<li><code>song</code><span> 单曲</span></li>
<li><code>playlist</code><span> 歌单</span></li>
</ul>
</li>
<li>
<p>
<strong><span>id</span></strong
><span>: 类型ID封面ID/单曲ID/歌单ID</span>
</p>
</li>
</ul>
<h4 id="使用示例"><span>使用示例</span></h4>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang=""
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.198px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation"><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">https://api.qijieya.cn/meting/?type=url&amp;id=1969519579</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">https://api.qijieya.cn/meting/?type=song&amp;id=591321</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">https://api.qijieya.cn/meting/?type=playlist&amp;id=2619366284</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 173px;"></div><div class="CodeMirror-gutters" style="display: none; height: 173px;"></div></div></div></pre>
<h3 id="接口3-备选接口"><span>接口3: 备选接口</span></h3>
<ul>
<li>
<strong><span>地址</span></strong
><span>: </span
><a href="https://doc.vkeys.cn/api-doc/" target="_blank" class="url"
>https://doc.vkeys.cn/api-doc/</a
>
</li>
<li>
<strong><span>说明</span></strong
><span>: 不建议使用,延迟较高</span>
</li>
</ul>
<h3 id="接口4-自部署接口-备用"><span>接口4: 自部署接口 (备用)</span></h3>
<ul>
<li>
<strong><span>地址</span></strong
><span>: </span
><code>https://music.shiqianjiang.cn?id=你是我的风景&amp;server=netease</code>
</li>
<li>
<strong><span>说明</span></strong
><span>: 不支持分页,用于获取歌曲源、歌词源等</span>
</li>
<li>
<strong><span>文档</span></strong
><span>: </span><a href="./api.md"><span>API文档</span></a>
</li>
</ul>
<h2 id="核心功能设计"><span>核心功能设计</span></h2>
<h3 id="通用请求函数设计"><span>通用请求函数设计</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="typescript"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="typescript"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 音乐服务接口定义</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">interface</span> <span class="cm-def">MusicService</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">search</span>({<span class="cm-property">keyword</span>: <span class="cm-def">string</span>, <span class="cm-def">page</span><span class="cm-operator">?</span>: <span class="cm-variable">number</span>, <span class="cm-variable">limit</span><span class="cm-operator">?</span>: <span class="cm-variable">number</span>}): <span class="cm-type">Promise</span><span class="cm-operator">&lt;</span><span class="cm-type">SearchResult</span><span class="cm-operator">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">getSongDetail</span>({<span class="cm-property">id</span>: <span class="cm-def">string</span>)}: <span class="cm-type">Promise</span><span class="cm-operator">&lt;</span><span class="cm-type">SongDetail</span><span class="cm-operator">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-variable">getSongUrl</span>({<span class="cm-variable">id</span>: <span class="cm-variable-2">string</span>}): <span class="cm-variable">Promise</span><span class="cm-operator">&lt;</span><span class="cm-variable">string</span><span class="cm-operator">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-variable">getLyric</span>({<span class="cm-property">id</span>: <span class="cm-variable">string</span>}): <span class="cm-variable">Promise</span><span class="cm-operator">&lt;</span><span class="cm-variable">LyricData</span><span class="cm-operator">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-variable">getPlaylist</span>({<span class="cm-property">id</span>: <span class="cm-variable">string</span>}): <span class="cm-variable">Promise</span><span class="cm-operator">&lt;</span><span class="cm-variable">PlaylistData</span><span class="cm-operator">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 通用请求函数</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">async</span> <span class="cm-keyword">function</span> <span class="cm-def">request</span>(<span class="cm-def">method</span>: <span class="cm-type">string</span>, <span class="cm-meta">...</span><span class="cm-def">args</span>: <span class="cm-type">any</span>{},<span class="cm-variable">isLoading</span><span class="cm-operator">=</span><span class="cm-atom">false</span>): <span class="cm-variable">Promise</span><span class="cm-operator">&lt;</span><span class="cm-variable">any</span><span class="cm-operator">&gt;</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">try</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">switch</span> (<span class="cm-variable">method</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-string">'search'</span>:</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">await</span> <span class="cm-variable">musicService</span>.<span class="cm-property">search</span>(<span class="cm-variable">args</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-string">'getSongDetail'</span>:</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">await</span> <span class="cm-variable">musicService</span>.<span class="cm-property">getSongDetail</span>(<span class="cm-variable">args</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-string">'getSongUrl'</span>:</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">await</span> <span class="cm-variable">musicService</span>.<span class="cm-property">getSongUrl</span>(<span class="cm-variable">args</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">case</span> <span class="cm-string">'getLyric'</span>:</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-keyword">await</span> <span class="cm-variable">musicService</span>.<span class="cm-property">getLyric</span>(<span class="cm-variable">args</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">default</span>:</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">throw</span> <span class="cm-keyword">new</span> <span class="cm-variable">Error</span>(<span class="cm-string-2">`未知的方法: ${</span><span class="cm-variable">method</span><span class="cm-string-2">}`</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> } <span class="cm-keyword">catch</span> (<span class="cm-def">error</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-variable">console</span>.<span class="cm-property">error</span>(<span class="cm-string-2">`请求失败: ${</span><span class="cm-variable">method</span><span class="cm-string-2">}`</span>, <span class="cm-variable-2">error</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">throw</span> <span class="cm-variable-2">error</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 使用示例</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-variable">request</span>(<span class="cm-string">'search'</span>, <span class="cm-string">'周杰伦'</span>, <span class="cm-number">1</span>, <span class="cm-number">20</span>).<span class="cm-property">then</span>((<span class="cm-def">result</span>) <span class="cm-operator">=&gt;</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-variable">console</span>.<span class="cm-property">log</span>(<span class="cm-string">'搜索结果:'</span>, <span class="cm-variable-2">result</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">})</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1238px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1238px;"></div></div></div></pre>
<h3 id="状态管理设计-pinia--localstorage">
<span>状态管理设计 (Pinia + LocalStorage)</span>
</h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="typescript"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="typescript"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// stores/music.ts</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">import</span> { <span class="cm-def">defineStore</span> } <span class="cm-keyword">from</span> <span class="cm-string">'pinia'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">export</span> <span class="cm-keyword">const</span> <span class="cm-def">useMusicStore</span> <span class="cm-operator">=</span> <span class="cm-variable">defineStore</span>(<span class="cm-string">'music'</span>, {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">state</span>: () <span class="cm-operator">=&gt;</span> ({</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 当前播放歌曲</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">currentSong</span>: <span class="cm-atom">null</span> <span class="cm-keyword">as</span> <span class="cm-type">Song</span> <span class="cm-operator">|</span> <span class="cm-atom">null</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 播放列表</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">playlist</span>: [] <span class="cm-keyword">as</span> <span class="cm-type">Song</span>[],</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 播放状态</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">isPlaying</span>: <span class="cm-atom">false</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 播放模式 (顺序、随机、单曲循环)</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">playMode</span>: <span class="cm-string">'order'</span> <span class="cm-keyword">as</span> <span class="cm-string">'order'</span> <span class="cm-operator">|</span> <span class="cm-string">'random'</span> <span class="cm-operator">|</span> <span class="cm-string">'repeat'</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 音量</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">volume</span>: <span class="cm-number">0.8</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 播放进度</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">currentTime</span>: <span class="cm-number">0</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">duration</span>: <span class="cm-number">0</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">actions</span>: {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 播放歌曲</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">async</span> <span class="cm-property">playSong</span>(<span class="cm-def">song</span>: <span class="cm-type">Song</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">currentSong</span> <span class="cm-operator">=</span> <span class="cm-variable-2">song</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">isPlaying</span> <span class="cm-operator">=</span> <span class="cm-atom">true</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">saveToStorage</span>()</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; },</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 添加到播放列表</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">addToPlaylist</span>(<span class="cm-def">songs</span>: <span class="cm-type">Song</span>[]) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">playlist</span>.<span class="cm-property">push</span>(<span class="cm-meta">...</span><span class="cm-variable-2">songs</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">saveToStorage</span>()</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; },</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 保存到本地存储</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">saveToStorage</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-variable">localStorage</span>.<span class="cm-property">setItem</span>(</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-string">'music-state'</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>({</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-property">currentSong</span>: <span class="cm-keyword">this</span>.<span class="cm-property">currentSong</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-property">playlist</span>: <span class="cm-keyword">this</span>.<span class="cm-property">playlist</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-property">playMode</span>: <span class="cm-keyword">this</span>.<span class="cm-property">playMode</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-property">volume</span>: <span class="cm-keyword">this</span>.<span class="cm-property">volume</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; })</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; )</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; },</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-comment">// 从本地存储恢复</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">loadFromStorage</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">saved</span> <span class="cm-operator">=</span> <span class="cm-variable">localStorage</span>.<span class="cm-property">getItem</span>(<span class="cm-string">'music-state'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable-2">saved</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">state</span> <span class="cm-operator">=</span> <span class="cm-variable">JSON</span>.<span class="cm-property">parse</span>(<span class="cm-variable-2">saved</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Object</span>.<span class="cm-property">assign</span>(<span class="cm-keyword">this</span>, <span class="cm-variable-2">state</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">})</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1728px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1728px;"></div></div></div></pre>
<h3 id="虚拟滚动列表设计"><span>虚拟滚动列表设计</span></h3>
<p><span>使用 TDesign 的虚拟滚动组件展示大量歌曲数据:</span></p>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="vue"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="vue"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-virtual-scroll</span> <span class="cm-attribute">:data</span>=<span class="cm-string">"songList"</span> <span class="cm-attribute">:height</span>=<span class="cm-string">"600"</span> <span class="cm-attribute">:item-height</span>=<span class="cm-string">"60"</span> <span class="cm-attribute">:buffer</span>=<span class="cm-string">"10"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">template</span> <span class="cm-attribute">#default</span>=<span class="cm-string">"{ data: song, index }"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-item"</span> <span class="cm-attribute">@click</span>=<span class="cm-string">"playSong(song)"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-cover"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">img</span> <span class="cm-attribute">:src</span>=<span class="cm-string">"song.pic"</span> <span class="cm-attribute">:alt</span>=<span class="cm-string">"song.name"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-info"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-name"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ song.name }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-artist"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ song.artist }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-duration"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ formatTime(song.duration) }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag cm-error">t-virtual-scroll</span><span class="cm-tag cm-bracket cm-error">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag cm-error">template</span><span class="cm-tag cm-bracket cm-error">&gt;</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 662px;"></div><div class="CodeMirror-gutters" style="display: none; height: 662px;"></div></div></div></pre>
<h3 id="本地数据存储设计"><span>本地数据存储设计</span></h3>
<h4 id="播放列表存储"><span>播放列表存储</span></h4>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="typescript"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="typescript"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 方案1: LocalStorage (简单方案)</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">class</span> <span class="cm-def">PlaylistStorage</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">private</span> <span class="cm-property">key</span> <span class="cm-operator">=</span> <span class="cm-string">'ceru-playlists'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">save</span>(<span class="cm-def">playlists</span>: <span class="cm-type">Playlist</span>[]) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-variable">localStorage</span>.<span class="cm-property">setItem</span>(<span class="cm-keyword">this</span>.<span class="cm-property">key</span>, <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>(<span class="cm-variable-2">playlists</span>))</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">load</span>(): <span class="cm-type">Playlist</span>[] {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">data</span> <span class="cm-operator">=</span> <span class="cm-variable">localStorage</span>.<span class="cm-property">getItem</span>(<span class="cm-keyword">this</span>.<span class="cm-property">key</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable-2">data</span> <span class="cm-operator">?</span> <span class="cm-variable">JSON</span>.<span class="cm-property">parse</span>(<span class="cm-variable-2">data</span>) : []</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// 方案2: Node.js 文件存储 (最优方案,支持分享)</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">class</span> <span class="cm-def">FileStorage</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">private</span> <span class="cm-property">filePath</span> <span class="cm-operator">=</span> <span class="cm-variable">path</span>.<span class="cm-property">join</span>(<span class="cm-variable">app</span>.<span class="cm-property">getPath</span>(<span class="cm-string">'userData'</span>), <span class="cm-string">'playlists.json'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">async</span> <span class="cm-property">save</span>(<span class="cm-def">playlists</span>: <span class="cm-type">Playlist</span>[]) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">await</span> <span class="cm-variable">fs</span>.<span class="cm-property">writeFile</span>(<span class="cm-keyword">this</span>.<span class="cm-property">filePath</span>, <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>(<span class="cm-variable-2">playlists</span>, <span class="cm-atom">null</span>, <span class="cm-number">2</span>))</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">async</span> <span class="cm-property">load</span>(): <span class="cm-type">Promise</span><span class="cm-operator">&lt;</span><span class="cm-type">Playlist</span>[]<span class="cm-operator">&gt;</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">try</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">data</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">fs</span>.<span class="cm-property">readFile</span>(<span class="cm-keyword">this</span>.<span class="cm-property">filePath</span>, <span class="cm-string">'utf-8'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">JSON</span>.<span class="cm-property">parse</span>(<span class="cm-variable-2">data</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; } <span class="cm-keyword">catch</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">return</span> []</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-comment">// 导出播放列表</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">async</span> <span class="cm-property">export</span>(<span class="cm-def">playlist</span>: <span class="cm-type">Playlist</span>, <span class="cm-def">exportPath</span>: <span class="cm-type">string</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">await</span> <span class="cm-variable">fs</span>.<span class="cm-property">writeFile</span>(<span class="cm-variable-2">exportPath</span>, <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>(<span class="cm-variable-2">playlist</span>, <span class="cm-atom">null</span>, <span class="cm-number">2</span>))</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-comment">// 导入播放列表</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">async</span> <span class="cm-property">import</span>(<span class="cm-def">importPath</span>: <span class="cm-type">string</span>): <span class="cm-type">Promise</span><span class="cm-operator">&lt;</span><span class="cm-type">Playlist</span><span class="cm-operator">&gt;</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">data</span> <span class="cm-operator">=</span> <span class="cm-keyword">await</span> <span class="cm-variable">fs</span>.<span class="cm-property">readFile</span>(<span class="cm-variable-2">importPath</span>, <span class="cm-string">'utf-8'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable">JSON</span>.<span class="cm-property">parse</span>(<span class="cm-variable-2">data</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1497px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1497px;"></div></div></div></pre>
<h2 id="用户体验设计"><span>用户体验设计</span></h2>
<h3 id="首次启动流程"><span>首次启动流程</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="typescript"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="typescript"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">// stores/app.ts</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">export</span> <span class="cm-keyword">const</span> <span class="cm-def">useAppStore</span> <span class="cm-operator">=</span> <span class="cm-variable">defineStore</span>(<span class="cm-string">'app'</span>, {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">state</span>: () <span class="cm-operator">=&gt;</span> ({</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">isFirstLaunch</span>: <span class="cm-atom">true</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">hasCompletedWelcome</span>: <span class="cm-atom">false</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">userPreferences</span>: {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-property">theme</span>: <span class="cm-string">'auto'</span> <span class="cm-keyword">as</span> <span class="cm-string">'light'</span> <span class="cm-operator">|</span> <span class="cm-string">'dark'</span> <span class="cm-operator">|</span> <span class="cm-string">'auto'</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-property">language</span>: <span class="cm-string">'zh-CN'</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-property">defaultMusicSource</span>: <span class="cm-string">'netease'</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-property">autoPlay</span>: <span class="cm-atom">false</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }),</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">actions</span>: {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">checkFirstLaunch</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">hasLaunched</span> <span class="cm-operator">=</span> <span class="cm-variable">localStorage</span>.<span class="cm-property">getItem</span>(<span class="cm-string">'has-launched'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">isFirstLaunch</span> <span class="cm-operator">=</span> <span class="cm-operator">!</span><span class="cm-variable-2">hasLaunched</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-keyword">this</span>.<span class="cm-property">isFirstLaunch</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 跳转到欢迎页面</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">router</span>.<span class="cm-property">push</span>(<span class="cm-string">'/welcome'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; } <span class="cm-keyword">else</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-comment">// 加载用户配置</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">loadUserPreferences</span>()</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">router</span>.<span class="cm-property">push</span>(<span class="cm-string">'/home'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; },</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-property">completeWelcome</span>(<span class="cm-def">preferences</span><span class="cm-operator">?</span>: <span class="cm-type">Partial</span><span class="cm-operator">&lt;</span><span class="cm-type">UserPreferences</span><span class="cm-operator">&gt;</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable-2">preferences</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-variable">Object</span>.<span class="cm-property">assign</span>(<span class="cm-keyword">this</span>.<span class="cm-property">userPreferences</span>, <span class="cm-variable-2">preferences</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-keyword">this</span>.<span class="cm-property">hasCompletedWelcome</span> <span class="cm-operator">=</span> <span class="cm-atom">true</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-variable">localStorage</span>.<span class="cm-property">setItem</span>(<span class="cm-string">'has-launched'</span>, <span class="cm-string">'true'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-variable">localStorage</span>.<span class="cm-property">setItem</span>(<span class="cm-string">'user-preferences'</span>, <span class="cm-variable">JSON</span>.<span class="cm-property">stringify</span>(<span class="cm-keyword">this</span>.<span class="cm-property">userPreferences</span>))</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-variable">router</span>.<span class="cm-property">push</span>(<span class="cm-string">'/home'</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">})</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1382px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1382px;"></div></div></div></pre>
<h3 id="欢迎页面设计"><span>欢迎页面设计</span></h3>
<p>
<img
src="D:\code\Ceru-Music\docs\assets\image-20250813180856660.png"
referrerpolicy="no-referrer"
alt="image-20250813180856660"
/>
</p>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="vue"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="vue"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"welcome-container"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-steps</span> <span class="cm-attribute">:current</span>=<span class="cm-string">"currentStep"</span> <span class="cm-attribute">class</span>=<span class="cm-string">"welcome-steps"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-step</span> <span class="cm-attribute">title</span>=<span class="cm-string">"欢迎使用"</span> <span class="cm-attribute">content</span>=<span class="cm-string">"欢迎使用 Ceru Music"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-step</span> <span class="cm-attribute">title</span>=<span class="cm-string">"基础设置"</span> <span class="cm-attribute">content</span>=<span class="cm-string">"配置您的偏好设置"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-step</span> <span class="cm-attribute">title</span>=<span class="cm-string">"完成设置"</span> <span class="cm-attribute">content</span>=<span class="cm-string">"开始您的音乐之旅"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">t-steps</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">transition</span> <span class="cm-attribute">name</span>=<span class="cm-string">"slide"</span> <span class="cm-attribute">mode</span>=<span class="cm-string">"out-in"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">component</span> <span class="cm-attribute">:is</span>=<span class="cm-string">"currentStepComponent"</span> <span class="cm-attribute">@next</span>=<span class="cm-string">"nextStep"</span> <span class="cm-attribute">@skip</span>=<span class="cm-string">"skipWelcome"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">transition</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">script</span> <span class="cm-attribute">setup</span> <span class="cm-attribute">lang</span>=<span class="cm-string">"ts"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">import</span> { <span class="cm-def">ref</span>, <span class="cm-def">computed</span> } <span class="cm-keyword">from</span> <span class="cm-string">'vue'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">import</span> <span class="cm-def">WelcomeStep1</span> <span class="cm-keyword">from</span> <span class="cm-string">'./steps/WelcomeStep1.vue'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">import</span> <span class="cm-def">WelcomeStep2</span> <span class="cm-keyword">from</span> <span class="cm-string">'./steps/WelcomeStep2.vue'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">import</span> <span class="cm-def">WelcomeStep3</span> <span class="cm-keyword">from</span> <span class="cm-string">'./steps/WelcomeStep3.vue'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">const</span> <span class="cm-def">currentStep</span> <span class="cm-operator">=</span> <span class="cm-variable">ref</span>(<span class="cm-number">0</span>)</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">const</span> <span class="cm-def">steps</span> <span class="cm-operator">=</span> [<span class="cm-variable">WelcomeStep1</span>, <span class="cm-variable">WelcomeStep2</span>, <span class="cm-variable">WelcomeStep3</span>]</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">const</span> <span class="cm-def">currentStepComponent</span> <span class="cm-operator">=</span> <span class="cm-variable">computed</span>(() <span class="cm-operator">=&gt;</span> <span class="cm-variable">steps</span>[<span class="cm-variable">currentStep</span>.<span class="cm-property">value</span>])</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">function</span> <span class="cm-def">nextStep</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">if</span> (<span class="cm-variable">currentStep</span>.<span class="cm-property">value</span> <span class="cm-operator">&lt;</span> <span class="cm-variable">steps</span>.<span class="cm-property">length</span> <span class="cm-operator">-</span> <span class="cm-number">1</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-variable">currentStep</span>.<span class="cm-property">value</span><span class="cm-operator">++</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> } <span class="cm-keyword">else</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-variable">completeWelcome</span>()</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> }</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">function</span> <span class="cm-def">skipWelcome</span>() {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-variable">appStore</span>.<span class="cm-property">completeWelcome</span>()</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">style</span> <span class="cm-attribute">scoped</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-enter-active</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-leave-active</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transition</span>: <span class="cm-atom">all</span> <span class="cm-number">0.3s</span> <span class="cm-atom">ease</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-enter-from</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transform</span>: <span class="cm-atom">translateX</span>(<span class="cm-number">30px</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-leave-to</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transform</span>: <span class="cm-atom">translateX</span>(<span class="cm-number">-30px</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">style</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1785px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1785px;"></div></div></div></pre>
<h5 id="界面ui参考"><span>界面UI参考</span></h5>
<p><span>![.\assets\image-20250813180944752.png)</span></p>
<h2 id="页面动画设计"><span>页面动画设计</span></h2>
<h3 id="路由过渡动画"><span>路由过渡动画</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="vue"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="vue"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">router-view</span> <span class="cm-attribute">v-slot</span>=<span class="cm-string">"{ Component, route }"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">transition</span> <span class="cm-attribute">:name</span>=<span class="cm-string">"getTransitionName(route)"</span> <span class="cm-attribute">mode</span>=<span class="cm-string">"out-in"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">component</span> <span class="cm-attribute">:is</span>=<span class="cm-string">"Component"</span> <span class="cm-attribute">:key</span>=<span class="cm-string">"route.path"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">transition</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">router-view</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">script</span> <span class="cm-attribute">setup</span> <span class="cm-attribute">lang</span>=<span class="cm-string">"ts"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-keyword">function</span> <span class="cm-def">getTransitionName</span>(<span class="cm-def">route</span>: <span class="cm-variable">any</span>) {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-comment">// 根据路由层级决定动画方向</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">const</span> <span class="cm-def">depth</span> <span class="cm-operator">=</span> <span class="cm-variable-2">route</span>.<span class="cm-property">path</span>.<span class="cm-property">split</span>(<span class="cm-string">'/'</span>).<span class="cm-property">length</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-keyword">return</span> <span class="cm-variable-2">depth</span> <span class="cm-operator">&gt;</span> <span class="cm-number">2</span> <span class="cm-operator">?</span> <span class="cm-string">'slide-left'</span> : <span class="cm-string">'slide-right'</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">script</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">style</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">/* 滑动动画 */</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-left-enter-active</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-left-leave-active</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-right-enter-active</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-right-leave-active</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transition</span>: <span class="cm-atom">all</span> <span class="cm-number">0.3s</span> <span class="cm-variable">cubic-bezier</span>(<span class="cm-number">0.25</span>, <span class="cm-number">0.8</span>, <span class="cm-number">0.25</span>, <span class="cm-number">1</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-left-enter-from</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transform</span>: <span class="cm-atom">translateX</span>(<span class="cm-number">100%</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-left-leave-to</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transform</span>: <span class="cm-atom">translateX</span>(<span class="cm-number">-100%</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-right-enter-from</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transform</span>: <span class="cm-atom">translateX</span>(<span class="cm-number">-100%</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.slide-right-leave-to</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transform</span>: <span class="cm-atom">translateX</span>(<span class="cm-number">100%</span>);</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-comment">/* 淡入淡出动画 */</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.fade-enter-active</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.fade-leave-active</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">transition</span>: <span class="cm-atom">opacity</span> <span class="cm-number">0.3s</span> <span class="cm-atom">ease</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.fade-enter-from</span>,</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-qualifier">.fade-leave-to</span> {</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-property">opacity</span>: <span class="cm-number">0</span>;</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">}</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">style</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1670px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1670px;"></div></div></div></pre>
<h2 id="核心组件设计"><span>核心组件设计</span></h2>
<h3 id="音乐播放器组件"><span>音乐播放器组件</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang="vue"
style="break-inside: unset"
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang="vue"><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"music-player"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"player-info"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">img</span> <span class="cm-attribute">:src</span>=<span class="cm-string">"currentSong?.pic"</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-cover"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-details"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-name"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ currentSong?.name }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"song-artist"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ currentSong?.artist }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"player-controls"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-button</span> <span class="cm-attribute">variant</span>=<span class="cm-string">"text"</span> <span class="cm-attribute">@click</span>=<span class="cm-string">"previousSong"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-icon</span> <span class="cm-attribute">name</span>=<span class="cm-string">"skip-previous"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">t-button</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-button</span> <span class="cm-attribute">:variant</span>=<span class="cm-string">"isPlaying ? 'filled' : 'outline'"</span> <span class="cm-attribute">@click</span>=<span class="cm-string">"togglePlay"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-icon</span> <span class="cm-attribute">:name</span>=<span class="cm-string">"isPlaying ? 'pause' : 'play'"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">t-button</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-button</span> <span class="cm-attribute">variant</span>=<span class="cm-string">"text"</span> <span class="cm-attribute">@click</span>=<span class="cm-string">"nextSong"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-icon</span> <span class="cm-attribute">name</span>=<span class="cm-string">"skip-next"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">t-button</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span cm-text="" cm-zwsp="">
</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">div</span> <span class="cm-attribute">class</span>=<span class="cm-string">"player-progress"</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">span</span> <span class="cm-attribute">class</span>=<span class="cm-string">"time-current"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ formatTime(currentTime) }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">span</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">t-slider</span> <span class="cm-attribute">v-model</span>=<span class="cm-string">"progress"</span> <span class="cm-attribute">:max</span>=<span class="cm-string">"duration"</span> <span class="cm-attribute">@change</span>=<span class="cm-string">"seekTo"</span> <span class="cm-attribute">class</span>=<span class="cm-string">"progress-slider"</span> <span class="cm-tag cm-bracket">/&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;</span><span class="cm-tag">span</span> <span class="cm-attribute">class</span>=<span class="cm-string">"time-duration"</span><span class="cm-tag cm-bracket">&gt;</span><span class="cm-meta cm-mustache">{{ formatTime(duration) }}</span><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">span</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp; &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"> &nbsp;<span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">div</span><span class="cm-tag cm-bracket">&gt;</span></span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;"><span class="cm-tag cm-bracket">&lt;/</span><span class="cm-tag">template</span><span class="cm-tag cm-bracket">&gt;</span></span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 1152px;"></div><div class="CodeMirror-gutters" style="display: none; height: 1152px;"></div></div></div></pre>
<h2 id="开发规范"><span>开发规范</span></h2>
<h3 id="代码规范"><span>代码规范</span></h3>
<ul>
<li><span>使用 TypeScript 进行类型检查</span></li>
<li><span>遵循 ESLint 配置的代码规范</span></li>
<li><span>使用 Prettier 进行代码格式化</span></li>
<li><span>组件命名使用 PascalCase</span></li>
<li><span>文件命名使用 kebab-case</span></li>
</ul>
<h3 id="git-提交规范"><span>Git 提交规范</span></h3>
<pre
class="md-fences md-end-block ty-contain-cm modeLoaded"
spellcheck="false"
lang=""
><div class="CodeMirror cm-s-inner cm-s-null-scroll CodeMirror-wrap" lang=""><div style="overflow: hidden; position: relative; width: 3px; height: 0px; top: 32.3958px; left: 29.1979px;"><textarea autocorrect="off" autocapitalize="off" spellcheck="false" tabindex="0" style="position: absolute; bottom: -1em; padding: 0px; width: 1000px; height: 1em; outline: none;"></textarea></div><div class="CodeMirror-scrollbar-filler" cm-not-content="true"></div><div class="CodeMirror-gutter-filler" cm-not-content="true"></div><div class="CodeMirror-scroll" tabindex="-1"><div class="CodeMirror-sizer" style="margin-left: 0px; margin-bottom: 0px; border-right-width: 0px; padding-right: 0px; padding-bottom: 0px;"><div style="position: relative; top: 0px;"><div class="CodeMirror-lines" role="presentation"><div role="presentation" style="position: relative; outline: none;"><div class="CodeMirror-measure"><pre><span>xxxxxxxxxx</span></pre></div><div class="CodeMirror-measure"></div><div style="position: relative; z-index: 1;"></div><div class="CodeMirror-code" role="presentation" style=""><div class="CodeMirror-activeline" style="position: relative;"><div class="CodeMirror-activeline-background CodeMirror-linebackground"></div><div class="CodeMirror-gutter-background CodeMirror-activeline-gutter" style="left: 0px; width: 0px;"></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">feat: 新功能</span></pre></div><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">fix: 修复bug</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">docs: 文档更新</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">style: 代码格式调整</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">refactor: 代码重构</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">test: 测试相关</span></pre><pre class=" CodeMirror-line " role="presentation"><span role="presentation" style="padding-right: 0.1px;">chore: 构建过程或辅助工具的变动</span></pre></div></div></div></div></div><div style="position: absolute; height: 0px; width: 1px; border-bottom: 0px solid transparent; top: 202px;"></div><div class="CodeMirror-gutters" style="display: none; height: 202px;"></div></div></div></pre>
<h3 id="性能优化"><span>性能优化</span></h3>
<ul>
<li><span>使用虚拟滚动处理大列表</span></li>
<li><span>图片懒加载</span></li>
<li><span>组件按需加载</span></li>
<li><span>音频预加载和缓存</span></li>
<li><span>防抖和节流优化用户交互</span></li>
</ul>
<h2 id="待补充功能"><span>待补充功能</span></h2>
<ol start="">
<li>
<strong><span>歌词显示</span></strong
><span>: 滚动歌词、逐字高亮</span>
</li>
<li>
<strong><span>音效处理</span></strong
><span>: 均衡器、音效增强</span>
</li>
<li>
<strong><span>主题系统</span></strong
><span>: 多主题切换、自定义主题</span>
</li>
<li>
<strong><span>快捷键</span></strong
><span>: 全局快捷键支持</span>
</li>
<li>
<strong><span>系统集成</span></strong
><span>: 媒体键支持、系统通知</span>
</li>
<li>
<strong><span>云同步</span></strong
><span>: 播放列表云端同步</span>
</li>
<li>
<strong><span>插件系统</span></strong
><span>: 支持第三方插件扩展</span>
</li>
<li>
<strong><span>音乐推荐</span></strong
><span>: 基于听歌历史的智能推荐</span>
</li>
</ol>
<hr />
<p>
<em><span>本设计文档将随着项目开发进度持续更新和完善。</span></em>
</p>
</div>
</div>
</body>
</html>