modify ui
This commit is contained in:
160
frontend/wechat/package-lock.json
generated
160
frontend/wechat/package-lock.json
generated
@@ -11,20 +11,20 @@
|
||||
"@tailwindcss/vite": "^4.1.4",
|
||||
"@vueuse/core": "^13.1.0",
|
||||
"axios": "^1.8.4",
|
||||
"dplayer": "^1.27.1",
|
||||
"pinia": "^3.0.2",
|
||||
"plyr": "^3.7.8",
|
||||
"sha1": "^1.1.1",
|
||||
"tailwindcss": "^4.1.4",
|
||||
"vue": "^3.5.13",
|
||||
"vue-icons-plus": "^0.1.8",
|
||||
"vue-router": "^4.5.0",
|
||||
"wechat-js-sdk": "^1.3.3",
|
||||
"weixin-js-sdk": "^1.6.5",
|
||||
"xgplayer": "^3.0.22",
|
||||
"xgplayer-mp4": "^3.0.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vitejs/plugin-vue": "^5.2.2",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.3",
|
||||
@@ -176,6 +176,29 @@
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@tailwindcss/aspect-ratio": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.2.tgz",
|
||||
"integrity": "sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/forms": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/@tailwindcss/forms/-/forms-0.5.10.tgz",
|
||||
"integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mini-svg-data-uri": "^1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.1.4",
|
||||
"license": "MIT",
|
||||
@@ -235,6 +258,22 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
||||
"integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash.castarray": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"postcss-selector-parser": "6.0.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.1.4",
|
||||
"license": "MIT",
|
||||
@@ -496,10 +535,6 @@
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balloon-css": {
|
||||
"version": "1.2.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/birpc": {
|
||||
"version": "2.3.0",
|
||||
"license": "MIT",
|
||||
@@ -675,6 +710,7 @@
|
||||
"version": "3.41.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/core-js"
|
||||
@@ -691,14 +727,23 @@
|
||||
"version": "1.2.7",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/custom-event-polyfill": {
|
||||
"version": "1.0.7",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d": {
|
||||
"version": "1.0.2",
|
||||
"license": "ISC",
|
||||
@@ -774,24 +819,6 @@
|
||||
"version": "1.4.7",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dplayer": {
|
||||
"version": "1.27.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "1.2.3",
|
||||
"balloon-css": "^1.0.3",
|
||||
"promise-polyfill": "8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dplayer/node_modules/axios": {
|
||||
"version": "1.2.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"license": "MIT",
|
||||
@@ -1302,8 +1329,25 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/loadjs": {
|
||||
"version": "4.3.0",
|
||||
"node_modules/lodash.castarray": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
|
||||
"integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
@@ -1337,6 +1381,16 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mini-svg-data-uri": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
|
||||
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mini-svg-data-uri": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/mitt": {
|
||||
"version": "3.0.1",
|
||||
"license": "MIT"
|
||||
@@ -1436,17 +1490,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/plyr": {
|
||||
"version": "3.7.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"core-js": "^3.26.1",
|
||||
"custom-event-polyfill": "^1.0.7",
|
||||
"loadjs": "^4.2.0",
|
||||
"rangetouch": "^2.0.1",
|
||||
"url-polyfill": "^1.1.12"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.3",
|
||||
"funding": [
|
||||
@@ -1473,23 +1516,29 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
"util-deprecate": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/promise-polyfill": {
|
||||
"version": "8.3.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rangetouch": {
|
||||
"version": "2.0.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -1766,8 +1815,11 @@
|
||||
"browserslist": ">= 4.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-polyfill": {
|
||||
"version": "1.1.13",
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://npm.hub.ipao.vip/repository/npm/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
@@ -1900,10 +1952,6 @@
|
||||
"version": "6.6.4",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wechat-js-sdk": {
|
||||
"version": "1.3.3",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/weixin-js-sdk": {
|
||||
"version": "1.6.5",
|
||||
"license": "MIT"
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
"xgplayer-mp4": "^3.0.22"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/aspect-ratio": "^0.4.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@vitejs/plugin-vue": "^5.2.2",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"postcss": "^8.5.3",
|
||||
@@ -31,4 +34,4 @@
|
||||
"vite": "^6.3.0",
|
||||
"vite-plugin-compression": "^0.5.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
<template>
|
||||
<div class="mx-auto max-w-[480px] w-full min-h-screen bg-white">
|
||||
<router-view />
|
||||
<div class="mx-auto max-w-[480px] w-full min-h-screen bg-gray-50 font-sans antialiased">
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -30,49 +30,39 @@ const showArticle = (article) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white rounded-lg shadow overflow-hidden hover:shadow transition-all cursor-pointer"
|
||||
<div class="bg-white rounded-xl shadow-sm overflow-hidden hover:shadow-md transition-shadow duration-200 cursor-pointer"
|
||||
@click="showArticle(article)">
|
||||
<div v-if="article.head_images && article.head_images.length > 0" class="w-full h-32 bg-gray-200">
|
||||
<img :src="article.head_images[0]" class="mask-b-from-50% w-full h-full object-cover brightness-50"
|
||||
<div v-if="article.head_images && article.head_images.length > 0"
|
||||
class="relative w-full aspect-video bg-gray-100 overflow-hidden">
|
||||
<img :src="article.head_images[0]"
|
||||
class="w-full h-full object-cover transition-transform duration-300 hover:scale-105"
|
||||
:alt="article.title" />
|
||||
<div class="absolute inset-0 bg-gradient-to-b from-transparent to-black/30"></div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h3 class="text-lg break-all font-medium text-gray-800 line-clamp-2">
|
||||
<div class="p-4 space-y-3">
|
||||
<h3 class="text-lg font-semibold text-gray-800 line-clamp-2">
|
||||
{{ article.title }}
|
||||
</h3>
|
||||
|
||||
<p v-if="article.description" class="text-gray-600 text-sm mb-2 line-clamp-2">
|
||||
<p v-if="article.description"
|
||||
class="text-gray-600 text-sm line-clamp-2">
|
||||
{{ article.description }}
|
||||
</p>
|
||||
|
||||
<div class="py-2 flex items-center gap-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span v-for="tag in article.tags" :key="tag"
|
||||
class="px-1.5 py-0.5 text-xs bg-gray-100 text-gray-600 rounded">
|
||||
<span class="mr-1">#</span>{{ tag }}
|
||||
class="px-2 py-1 text-xs bg-blue-50 text-blue-600 rounded-full">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between mt-auto text-sm">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<span class="flex items-center gap-1 text-gray-700">
|
||||
<AiOutlineEye class="w-3.5 h-3.5" />
|
||||
{{ article.views }}
|
||||
</span>
|
||||
<!-- <span class="flex items-center gap-1 text-gray-700">
|
||||
<AiOutlineLike class="w-3.5 h-3.5" />
|
||||
{{ article.likes }}
|
||||
</span> -->
|
||||
<div class="flex items-center justify-between pt-2 text-sm">
|
||||
<div class="flex items-center gap-2 text-gray-500">
|
||||
<AiOutlineEye class="w-4 h-4" />
|
||||
<span>{{ article.view_count || 0 }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4 flex-shrink-0 font-mono">
|
||||
<span v-if="article.bought" class="px-2 py-0.5 text-xs bg-green-50 text-green-600 rounded">
|
||||
已购买
|
||||
</span>
|
||||
<template v-else>
|
||||
<span v-if="article.discount < 100" class="text-xs line-through text-gray-500">
|
||||
¥{{ (article.price / 100).toFixed(2) }}
|
||||
</span>
|
||||
<span class="text-xl text-orange-600">¥{{ discountPrice }}</span>
|
||||
</template>
|
||||
<div class="text-primary-600 font-medium">
|
||||
¥{{ discountPrice }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,22 +22,26 @@ const switchTab = (index, route) => {
|
||||
<div class="h-screen flex flex-col bg-gray-50">
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<transition name="fade" mode="out-in">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
<nav class="flex-none bg-white border-t border-gray-200">
|
||||
<div class="flex justify-around items-center h-14">
|
||||
<div v-for="(tab, index) in tabs" :key="index"
|
||||
class="flex flex-col items-center justify-center w-full h-full py-1 focus:outline-none" :class="[
|
||||
<nav class="flex-none bg-white border-t border-gray-200 shadow-[0_-1px_3px_rgba(0,0,0,0.05)]">
|
||||
<div class="flex justify-around items-center h-14 max-w-[480px] mx-auto px-2">
|
||||
<div v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
class="flex flex-col items-center justify-center w-full h-full py-1 select-none"
|
||||
:class="[
|
||||
activeTab === index
|
||||
? 'text-blue-600'
|
||||
: 'text-gray-600 hover:text-blue-600 active:text-blue-700'
|
||||
]" @click="switchTab(index, tab.route)">
|
||||
<component :is="tab.icon" class="w-6 h-6" />
|
||||
<span class="mt-1 text-xs">{{ tab.label }}</span>
|
||||
? 'text-primary-600'
|
||||
: 'text-gray-500 hover:text-primary-500 active:text-primary-700'
|
||||
]"
|
||||
@click="switchTab(index, tab.route)">
|
||||
<component :is="tab.icon"
|
||||
class="w-6 h-6 transition-colors duration-200" />
|
||||
<span class="mt-1 text-xs font-medium">{{ tab.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -45,5 +49,13 @@ const switchTab = (index, route) => {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Remove all existing styles */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,37 @@
|
||||
@import "tailwindcss";
|
||||
@import "tailwindcss/preflight";
|
||||
@import "tailwindcss/components";
|
||||
@import "tailwindcss/utilities";
|
||||
|
||||
html, body {
|
||||
@layer base {
|
||||
html, body {
|
||||
touch-action: manipulation;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.btn {
|
||||
@apply px-4 py-2 rounded-lg font-medium transition-colors duration-200;
|
||||
}
|
||||
.btn-primary {
|
||||
@apply bg-primary-500 text-white hover:bg-primary-600 active:bg-primary-700;
|
||||
}
|
||||
.btn-secondary {
|
||||
@apply bg-secondary-100 text-secondary-900 hover:bg-secondary-200 active:bg-secondary-300;
|
||||
}
|
||||
.input {
|
||||
@apply px-3 py-2 border border-secondary-200 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-transparent;
|
||||
}
|
||||
.card {
|
||||
@apply bg-white rounded-xl shadow-lg p-6;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
.text-pretty {
|
||||
text-wrap: pretty;
|
||||
}
|
||||
}
|
||||
@@ -69,29 +69,54 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="flex-none bg-white border-b border-gray-200 z-50 shadow">
|
||||
<div class="h-full flex flex-col bg-gray-50">
|
||||
<div class="flex-none bg-white border-b border-gray-200 sticky top-0 z-40 shadow-sm">
|
||||
<div class="p-4">
|
||||
<input type="search" v-model="searchInput" @keyup="handleKeyup" placeholder="搜索"
|
||||
class="w-full px-4 py-2 border border-gray-300 rounded-full focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
||||
<div class="relative">
|
||||
<input type="search"
|
||||
v-model="searchInput"
|
||||
@keyup="handleKeyup"
|
||||
placeholder="搜索曲谱..."
|
||||
class="w-full pl-10 pr-4 py-2.5 text-gray-600 bg-gray-50 border border-gray-200 rounded-full
|
||||
focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent
|
||||
placeholder:text-gray-400">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-400">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="p-4">
|
||||
<ArticleListItem v-for="article in articles" :key="article.id" :article="article" class="mb-4" />
|
||||
<div class="container max-w-2xl mx-auto p-4">
|
||||
<div class="grid gap-4">
|
||||
<ArticleListItem v-for="article in articles"
|
||||
:key="article.id"
|
||||
:article="article" />
|
||||
</div>
|
||||
|
||||
<div ref="loadingTrigger" class="py-4 text-center" v-show="hasMore || loading">
|
||||
<div ref="loadingTrigger"
|
||||
class="py-4 text-center"
|
||||
v-show="hasMore || loading">
|
||||
<div v-if="loading"
|
||||
class="animate-spin rounded-full h-8 w-8 border-4 border-gray-200 border-t-blue-600 mx-auto">
|
||||
class="w-8 h-8 mx-auto border-2 border-gray-200 border-t-primary-500 rounded-full animate-spin">
|
||||
</div>
|
||||
<div v-else class="h-8">
|
||||
<!-- 空白占位,保持触发器可见 -->
|
||||
<!-- Empty space for intersection observer -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!hasMore && articles.length > 0" class="text-center text-gray-500 py-4">
|
||||
没有更多了
|
||||
<div v-if="!hasMore && articles.length > 0"
|
||||
class="text-center text-gray-500 py-4">
|
||||
没有更多曲谱了
|
||||
</div>
|
||||
|
||||
<div v-if="!hasMore && articles.length === 0"
|
||||
class="text-center text-gray-500 py-12">
|
||||
暂无曲谱
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,30 +35,55 @@ const menuGroups = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-white p-4 shadow-lg border-b border-gray-300">
|
||||
<div class="flex items-center gap-4 p-4 border-gray-100">
|
||||
<div class="w-16 h-16 border rounded-full border-gray-200 bg-gray-200 overflow-hidden">
|
||||
<img :src="userInfo.avatar" :alt="userInfo.username" class="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 用户信息卡片 -->
|
||||
<div class="bg-white shadow-sm">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="flex items-center gap-4 p-6">
|
||||
<div class="w-20 h-20 rounded-full border-2 border-white shadow-lg overflow-hidden bg-gray-100">
|
||||
<img :src="userInfo.avatar"
|
||||
:alt="userInfo.username"
|
||||
class="w-full h-full object-cover" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-medium">{{ userInfo.username }}</h3>
|
||||
<div class="text-gray-500">用户编号: {{ userInfo.id }}</div>
|
||||
<div class="text-gray-500">账户余额: {{ (userInfo.balance / 100).toFixed(2) }}</div>
|
||||
<div class="space-y-1">
|
||||
<h3 class="text-xl font-semibold text-gray-800">
|
||||
{{ userInfo.username }}
|
||||
</h3>
|
||||
<div class="text-gray-500 text-sm">
|
||||
用户编号: {{ userInfo.id }}
|
||||
</div>
|
||||
<div class="text-primary-600 font-medium">
|
||||
账户余额: ¥{{ (userInfo.balance / 100).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="menus space-y-4 mt-4 px-4 hidden">
|
||||
<div v-for="(group, groupIndex) in menuGroups" :key="groupIndex" class="bg-white rounded-lg overflow-hidden">
|
||||
<div class="px-4 py-2 text-sm text-gray-500">{{ group.title }}</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div v-for="(item, itemIndex) in group.items" :key="itemIndex"
|
||||
class="flex items-center px-4 py-3 hover:bg-gray-50 active:bg-gray-100 cursor-pointer">
|
||||
<component :is="item.icon" class="mr-3 text-xl text-gray-600" />
|
||||
<span class="flex-1">{{ item.label }}</span>
|
||||
<span class="text-gray-400">›</span>
|
||||
<!-- 菜单列表 -->
|
||||
<div class="max-w-2xl mx-auto px-4 py-6 space-y-4">
|
||||
<div v-for="(group, groupIndex) in menuGroups"
|
||||
:key="groupIndex"
|
||||
class="bg-white rounded-xl overflow-hidden shadow-sm">
|
||||
<div class="px-4 py-2 text-sm font-medium text-gray-500 bg-gray-50">
|
||||
{{ group.title }}
|
||||
</div>
|
||||
<div class="divide-y divide-gray-100">
|
||||
<div v-for="(item, itemIndex) in group.items"
|
||||
:key="itemIndex"
|
||||
class="flex items-center px-4 py-3.5 hover:bg-gray-50 active:bg-gray-100
|
||||
cursor-pointer transition-colors duration-150">
|
||||
<component :is="item.icon"
|
||||
class="mr-3 text-xl text-gray-500" />
|
||||
<span class="flex-1 text-gray-700">{{ item.label }}</span>
|
||||
<span class="text-gray-400">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
68
frontend/wechat/tailwind.config.js
Normal file
68
frontend/wechat/tailwind.config.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#f0f9ff',
|
||||
100: '#e0f2fe',
|
||||
200: '#bae6fd',
|
||||
300: '#7dd3fc',
|
||||
400: '#38bdf8',
|
||||
500: '#0ea5e9',
|
||||
600: '#0284c7',
|
||||
700: '#0369a1',
|
||||
800: '#075985',
|
||||
900: '#0c4a6e',
|
||||
950: '#082f49',
|
||||
},
|
||||
secondary: {
|
||||
50: '#f8fafc',
|
||||
100: '#f1f5f9',
|
||||
200: '#e2e8f0',
|
||||
300: '#cbd5e1',
|
||||
400: '#94a3b8',
|
||||
500: '#64748b',
|
||||
600: '#475569',
|
||||
700: '#334155',
|
||||
800: '#1e293b',
|
||||
900: '#0f172a',
|
||||
950: '#020617',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter var', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
},
|
||||
spacing: {
|
||||
'128': '32rem',
|
||||
'144': '36rem',
|
||||
},
|
||||
borderRadius: {
|
||||
'4xl': '2rem',
|
||||
},
|
||||
animation: {
|
||||
'fade-in': 'fade-in 0.5s ease-out',
|
||||
'slide-in': 'slide-in 0.5s ease-out',
|
||||
},
|
||||
keyframes: {
|
||||
'fade-in': {
|
||||
'0%': { opacity: '0' },
|
||||
'100%': { opacity: '1' },
|
||||
},
|
||||
'slide-in': {
|
||||
'0%': { transform: 'translateY(100%)' },
|
||||
'100%': { transform: 'translateY(0)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/aspect-ratio'),
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user