Init: WIth Vue

初始化项目
This commit is contained in:
奇趣保罗 2025-09-16 17:22:18 +08:00
commit bc700c414a
19 changed files with 3912 additions and 0 deletions

8
.editorconfig Normal file
View File

@ -0,0 +1,8 @@
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
charset = utf-8
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
max_line_length = 100

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"endOfLine": "auto",
"semi": true,
"singleAttributePerLine": false,
"printWidth": 200
}

7
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,7 @@
{
"recommendations": [
"Vue.volar",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig"
]
}

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# 摄影接单
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
pnpm install
```
### Compile and Hot-Reload for Development
```sh
pnpm dev
```
### Type-Check, Compile and Minify for Production
```sh
pnpm build
```
### Lint with [ESLint](https://eslint.org/)
```sh
pnpm lint
```

1
env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

20
eslint.config.ts Normal file
View File

@ -0,0 +1,20 @@
import { globalIgnores } from "eslint/config";
import { defineConfigWithVueTs, vueTsConfigs } from "@vue/eslint-config-typescript";
import pluginVue from "eslint-plugin-vue";
// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
// configureVueProject({ scriptLangs: ['ts', 'tsx'] })
// More info at https://github.com/vuejs/eslint-config-typescript/#advanced-setup
export default defineConfigWithVueTs(
{
name: "app/files-to-lint",
files: ["**/*.{ts,mts,tsx,vue}"],
},
globalIgnores(["**/dist/**", "**/dist-ssr/**", "**/coverage/**"]),
pluginVue.configs["flat/essential"],
vueTsConfigs.recommended
);

16
index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>晓螺|摄影接单</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100..900&display=swap" rel="stylesheet">
</head>
<body class="noto-sans-sc-400">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "-take-order",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build",
"lint": "eslint . --fix"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.13",
"@zumer/snapdom": "^1.9.11",
"lucide-vue-next": "^0.543.0",
"tailwindcss": "^4.1.13",
"vue": "^3.5.18"
},
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.16.5",
"@vitejs/plugin-vue": "^6.0.1",
"@vitejs/plugin-vue-jsx": "^5.0.1",
"@vue/eslint-config-typescript": "^14.6.0",
"@vue/tsconfig": "^0.7.0",
"eslint": "^9.31.0",
"eslint-plugin-vue": "~10.3.0",
"jiti": "^2.4.2",
"npm-run-all2": "^8.0.4",
"typescript": "~5.8.0",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^3.0.4"
}
}

3458
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

200
src/App.vue Normal file
View File

@ -0,0 +1,200 @@
<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import { snapdom } from "@zumer/snapdom";
import { Camera, Palette, Star, Aperture, Zap, User, Lightbulb, HeartHandshake, Users2, MapPin, WandSparkles } from "lucide-vue-next";
interface MediaItem {
id: number;
title: string;
content: string;
url: string;
thumb_url: string;
meta: string;
take_time: string;
}
const photos = ref<MediaItem[]>([]);
const loading = ref(true);
const columns = ref<MediaItem[][]>([[], [], []]);
const posterRef = ref<HTMLElement>();
const isCapturing = ref(false);
const fetchPhotos = async () => {
try {
const response = await fetch("https://paul.ren/api/media?cate=9&starred=1&size=30");
const data = await response.json();
// URL
photos.value = data.data.map((photo: MediaItem) => ({
...photo,
url: getImageUrl(photo.url),
thumb_url: getImageUrl(photo.thumb_url),
}));
loading.value = false;
await nextTick();
arrangePhotos();
} catch (error) {
console.error("Failed to fetch photos:", error);
loading.value = false;
}
};
const arrangePhotos = () => {
columns.value = [[], [], []];
photos.value.forEach((photo, index) => {
const columnIndex = index % 3;
columns.value[columnIndex].push(photo);
});
};
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
return date.toLocaleDateString("zh-CN", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
};
const getImageUrl = (url: string) => {
// 使
if (import.meta.env.DEV) {
return url.replace("https://legacy.paul.ren", "/proxy-images");
}
return url;
};
const captureScreenshot = async () => {
if (!posterRef.value || isCapturing.value) return;
isCapturing.value = true;
try {
const result = await snapdom(posterRef.value, {
width: 1024,
// format: 'png',
quality: 1,
// scale: 2
});
result.download({
format: "webp",
filename: `晓螺摄影_${new Date().getTime()}.webp`,
});
} catch (error) {
console.error("Screenshot failed:", error);
} finally {
isCapturing.value = false;
}
};
onMounted(() => {
fetchPhotos();
});
</script>
<template>
<div class="min-h-screen bg-gradient-to-br from-gray-100 to-gray-200 px-4 py-8">
<div class="fixed top-4 right-4 z-50 flex gap-2">
<button
class="cursor-pointer bg-white/90 backdrop-blur-sm text-gray-700 px-4 py-2 rounded-full shadow-lg hover:bg-white transition-all duration-300 flex items-center gap-2 hover:scale-105"
:disabled="isCapturing"
@click="captureScreenshot"
>
<Camera :size="20" />
<span>{{ isCapturing ? "截图中..." : "保存海报" }}</span>
</button>
</div>
<div class="max-w-screen-md mx-auto">
<div ref="posterRef" class="bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 shadow-2xl overflow-hidden p-8">
<header class="mt-8 mb-16">
<h1 class="text-yellow-400 flex items-center justify-center gap-8 text-7xl font-bold mb-8 tracking-widest">
<svg class="size-24" viewBox="0 0 200 200">
<path
fill="currentColor"
d="M22.8,42.4c0,0,14.4,2.4,20.8,2S60,39.6,60,39.6s0.8-13.6,44.4-14c58-0.4,71.6,61.6,71.6,61.6s4.4,19.2,12,27.6c5.2,3.6,11.6,2,11.6,2s-9.2,6-14.4,7.2c-5.2,0.8-12.8-1.6-12.8-1.6L166,148l-6-6.8l-2,0.8l-4.4,9.2V142l-2,0.8c0,0-1.6,8-2.8,12s-4.8,8.8-4.8,8.8l-3.6-4.8c0,0-0.4,2.4-1.6,3.6c-1.2,1.2-2.8,2.8-2.8,2.8l-2-3.6c0,0-12,10.8-37.2,10.8s-40-12-40-12l-2.8,2.8l-6-12.8c0,0-5.2-3.6-7.2-7.6s-0.8-7.2-0.8-7.2l-7.2-10l-4.4,5.6c0,0-3.2-6.4-2.8-9.2c0.4-2.8,0.8-7.2,0.8-7.2S19.2,114,12,112c-6.8-2-12-9.2-12-9.2s6,2,11.6-2s20.8-28.4,20.8-28.4l12.8-16.8c0,0-5.6-0.4-10.8-2.8C29.2,50.4,22.8,42.4,22.8,42.4z M55.2,90l-4.4,40.4c0,0,3.2,7.6,5.2,12c1.6,4.4,2,7.6,2,7.6s6,14,39.6,14.8c42-0.8,53.2-32,53.2-32s-6-14.8-10.4-18.8c-4-4-27.2-16.4-27.2-16.4s3.2,8,4.4,15.6c1.2,7.6,1.6,13.6,1.6,13.6s-11.6-3.2-15.2-6.4c-3.6-3.2-8.8-10-8.8-10s0,3.6,0.8,7.2s2.8,5.6,2.8,5.6s-10.4-5.2-15.6-14.8c-5.6-9.6-9.2-24-9.2-24l-6.4,36.8L55.2,90z"
></path>
</svg>
<span>摄影接单</span>
</h1>
<section>
<div class="grid gap-4 grid-cols-2 text-gray-200 text-xl leading-[1.8]">
<ul>
<li class="flex items-center gap-2">
<User class="opacity-60" />
<span class="flex-1">CN: 晓螺</span>
</li>
<li class="flex items-center gap-2">
<MapPin class="opacity-60" />
<span class="flex-1">IP: 广东 · 珠海</span>
</li>
<li class="flex items-center gap-2">
<Lightbulb class="opacity-60" />
<span class="flex-1">擅长亮调萌系</span>
</li>
<li class="flex items-center gap-2">
<Users2 class="opacity-60" />
<span class="flex-1">可动作指导建议自带动作</span>
</li>
<li class="flex items-center gap-2">
<HeartHandshake class="opacity-60" />
<span class="flex-1">审题材妆造互勉</span>
</li>
</ul>
<ul>
<li class="flex items-center gap-2">
<Camera class="opacity-60" />
<span class="flex-1">索尼 A6700</span>
</li>
<li class="flex items-center gap-2">
<Aperture class="opacity-60" />
<span class="flex-1">配备大光圈与长焦镜头</span>
</li>
<li class="flex items-center gap-2">
<Zap class="opacity-60" />
<span class="flex-1">配备双灯阵柔光箱可打顶光</span>
</li>
<li class="flex items-center gap-2">
<Palette class="opacity-60" />
<span class="flex-1">默认后期调色可选人像简修</span>
</li>
</ul>
</div>
<div class="text-gray-200 text-xl leading-[1.8]">
<p class="flex items-center gap-2">
<WandSparkles class="shrink-0 opacity-60" />
<span class="flex-1">互勉角色: 原神崩坏星穹铁道绝区零鸣潮蔚蓝档案碧蓝航线等</span>
</p>
</div>
</section>
</header>
<main v-if="loading" class="flex justify-center items-center h-64 mb-8">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-yellow-400"></div>
</main>
<main v-else>
<div class="flex items-center justify-center gap-2 mb-8">
<Star :size="24" class="text-yellow-400" fill="currentColor" />
<div class="text-3xl font-bold text-yellow-400">近期例图</div>
<Star :size="24" class="text-yellow-400" fill="currentColor" />
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 mb-8">
<div v-for="(column, colIndex) in columns" :key="colIndex" class="space-y-2">
<div v-for="photo in column" :key="photo.id" class="relative rounded-lg overflow-hidden group">
<img :src="photo.url" :alt="photo.title" class="w-full h-auto block rounded-lg" />
<div class="absolute bg-gradient-to-t from-black to-transparent bottom-0 left-0 right-0 p-4 pt-8 text-white transition-opacity duration-300 opacity-0 group-hover:opacity-100">
<h3 class="font-semibold text-white mb-1 text-lg">{{ photo.title }}</h3>
<p class="text-xs opacity-60">{{ formatDate(photo.take_time) }}</p>
</div>
</div>
</div>
</div>
</main>
<footer class="text-center text-gray-400">
<p class="text-xs opacity-75">© 2025 晓螺摄影</p>
</footer>
</div>
</div>
</div>
</template>

13
src/main.css Normal file
View File

@ -0,0 +1,13 @@
@import "tailwindcss";
body {
margin: 0;
padding: 0;
}
.noto-sans-sc-400 {
font-family: "Noto Sans SC", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
}

5
src/main.ts Normal file
View File

@ -0,0 +1,5 @@
import { createApp } from "vue";
import App from "./App.vue";
import "./main.css";
createApp(App).mount("#app");

12
tsconfig.app.json Normal file
View File

@ -0,0 +1,12 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"paths": {
"@/*": ["./src/*"]
}
}
}

11
tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tsconfig.node.json Normal file
View File

@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node22/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*",
"eslint.config.*"
],
"compilerOptions": {
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

26
vite.config.ts Normal file
View File

@ -0,0 +1,26 @@
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
import vueDevTools from "vite-plugin-vue-devtools";
import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [vue(), vueJsx(), vueDevTools(), tailwindcss()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
proxy: {
"/proxy-images": {
target: "https://legacy.paul.ren",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/proxy-images/, ""),
},
},
},
});