init
This commit is contained in:
84
.drone.yml
Normal file
84
.drone.yml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
kind: pipeline # 定义一个管道
|
||||||
|
type: docker # 当前管道的类型
|
||||||
|
name: test # 当前管道的名称
|
||||||
|
|
||||||
|
steps:
|
||||||
|
|
||||||
|
# 第一步:构建项目
|
||||||
|
- name: 构建项目
|
||||||
|
image: node:18-alpine
|
||||||
|
commands:
|
||||||
|
- rm -rf node_modules
|
||||||
|
- npm ci
|
||||||
|
- npm run build
|
||||||
|
|
||||||
|
# 第二步:上传静态资源到腾讯云COS (使用另一个插件)
|
||||||
|
- name: 静态资源上传到cos
|
||||||
|
image: ccr.ccs.tencentyun.com/xiaoqidun/gocos:latest
|
||||||
|
settings:
|
||||||
|
secret_id:
|
||||||
|
from_secret: cos_secret_id
|
||||||
|
secret_key:
|
||||||
|
from_secret: cos_secret_key
|
||||||
|
bucket_url: https://files-1302416092.cos.ap-shanghai.myqcloud.com
|
||||||
|
source_path: dist
|
||||||
|
target_path: /studio
|
||||||
|
strip_prefix: dist
|
||||||
|
|
||||||
|
# 第三步:部署到服务器
|
||||||
|
- name: 清除服务器缓存
|
||||||
|
image: appleboy/drone-ssh
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
from_secret: server_host
|
||||||
|
username:
|
||||||
|
from_secret: server_username
|
||||||
|
password:
|
||||||
|
from_secret: server_password
|
||||||
|
# 或者使用SSH密钥
|
||||||
|
# key:
|
||||||
|
# from_secret: server_ssh_key
|
||||||
|
port: 22
|
||||||
|
script:
|
||||||
|
- rm -rf /www/wwwroot/studio.zguiy.com/*
|
||||||
|
- mkdir -p /www/wwwroot/studio.zguiy.com/
|
||||||
|
- chmod 755 /www/wwwroot/studio.zguiy.com/
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
|
||||||
|
# 第四步:上传构建文件
|
||||||
|
- name: 上传构建文件
|
||||||
|
image: appleboy/drone-scp
|
||||||
|
settings:
|
||||||
|
host:
|
||||||
|
from_secret: server_host
|
||||||
|
username:
|
||||||
|
from_secret: server_username
|
||||||
|
password:
|
||||||
|
from_secret: server_password
|
||||||
|
# 或者使用SSH密钥
|
||||||
|
# key:
|
||||||
|
# from_secret: server_ssh_key
|
||||||
|
port: 22
|
||||||
|
source: dist/*
|
||||||
|
target: /www/wwwroot/studio.zguiy.com/
|
||||||
|
strip_components: 1
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
|
||||||
|
|
||||||
|
# 触发条件
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- dev
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
1
.env.development
Normal file
1
.env.development
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_PUBLIC = /
|
||||||
1
.env.production
Normal file
1
.env.production
Normal file
@ -0,0 +1 @@
|
|||||||
|
VITE_PUBLIC = https://cdn.files.zguiy.com/studio/
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/dist
|
||||||
|
/node_modules
|
||||||
|
/.claude
|
||||||
178
cursor.md
Normal file
178
cursor.md
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# AI Code Generation Prompts: Startup Website Clone
|
||||||
|
**Tech Stack:** Vite + Vue 3 (Script Setup) + Tailwind CSS + Lucide Icons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟢 Step 0: Project Setup (Global System Prompt)
|
||||||
|
*在开始编写具体页面前,先发送这条指令,建立全局的设计规范和技术栈。*
|
||||||
|
|
||||||
|
**Prompt:**
|
||||||
|
|
||||||
|
> **Role:** Senior Frontend Developer specializing in Pixel-Perfect UI cloning.
|
||||||
|
>
|
||||||
|
> **Tech Stack Requirements:**
|
||||||
|
> - **Framework:** Vue 3 (Composition API with `<script setup>`) + typescript.
|
||||||
|
> - **Build Tool:** Vite.
|
||||||
|
> - **Styling:** Tailwind CSS (Mobile-first approach).
|
||||||
|
> - **Icons:** `lucide-vue-next` (You must use this library).
|
||||||
|
> - **Language:** TypeScript.
|
||||||
|
>
|
||||||
|
> **Design System (Strict Adherence):**
|
||||||
|
> - **Visual Style:** Minimalist, "Vercel-like" aesthetic. High contrast, clean lines.
|
||||||
|
> - **Typography:** Inter or system-ui. Headings must use `tracking-tight` (letter-spacing: -0.025em).
|
||||||
|
> - **Colors:**
|
||||||
|
> - Background: `#ffffff` (White).
|
||||||
|
> - Surface/Card: `#ffffff` (White) with `border-gray-200`.
|
||||||
|
> - Primary Text: `#0f172a` (Slate-900).
|
||||||
|
> - Muted Text: `#64748b` (Slate-500).
|
||||||
|
> - Accents: Black buttons, Light gray backgrounds (`bg-gray-50`) for secondary elements.
|
||||||
|
> - **Borders & Radius:** Delicate borders (`border`, `border-gray-100`). Card radius is `rounded-xl` or `rounded-2xl`.
|
||||||
|
> - **Shadows:** Very subtle `shadow-sm`, hover states use `shadow-md`.
|
||||||
|
>
|
||||||
|
> **Instruction:** I will provide tasks to build 3 specific pages. You must replicate the layout, spacing, and Chinese copy exactly as described.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🟡 Step 1: Pricing Page (定价页)
|
||||||
|
*复制此段生成定价页面组件。*
|
||||||
|
|
||||||
|
**Prompt:**
|
||||||
|
|
||||||
|
> **Task:** Create a **`PricingPage.vue`** component. Clone the provided "Pricing" design exactly.
|
||||||
|
>
|
||||||
|
> **1. Page Header:**
|
||||||
|
> - Layout: Centered.
|
||||||
|
> - Overline: "PRICING" (uppercase, tracking-widest, text-xs, text-gray-400).
|
||||||
|
> - Title: "产品购买页面" (text-3xl font-bold tracking-tight).
|
||||||
|
> - Subtitle: "按需套餐付费,尊享 Codex 与 Claude Code 两大产品线" (text-gray-500 mt-2).
|
||||||
|
>
|
||||||
|
> **2. Section 1: Codex Plans (2-Column Grid):**
|
||||||
|
> - **Heading:** "Codex 套餐" (Bold, text-lg, mb-4).
|
||||||
|
> - **Layout:** Grid `grid-cols-1 md:grid-cols-2 gap-6`.
|
||||||
|
> - **Card Design:** `bg-white border border-gray-200 rounded-xl p-6 relative`.
|
||||||
|
> - **Left Card (Standard):**
|
||||||
|
> - Top: Icon + "Codex" + "codex标准套餐". Badge: "工效效率" (Top Right, gray bg, text-xs).
|
||||||
|
> - Price: "¥79.9/月" (text-4xl font-bold). Subtext: "性价比首选 | 强大智能".
|
||||||
|
> - Features: List with blue bullet points (daily budget $90, etc.).
|
||||||
|
> - **Right Card (Enterprise):**
|
||||||
|
> - Price: "¥ 999". Badge: "定制高频版".
|
||||||
|
> - Features: "个人/企业 定制高险套餐", "高频需求首选".
|
||||||
|
>
|
||||||
|
> **3. Section 2: Claude Code Plans (3-Column Grid):**
|
||||||
|
> - **Heading:** "Claude Code 套餐" (Bold, text-lg, mt-10 mb-4).
|
||||||
|
> - **Layout:** Grid `grid-cols-1 md:grid-cols-3 gap-6`.
|
||||||
|
> - **Cards:**
|
||||||
|
> - Card 1: ¥228/月. Badge: "轻量性价比".
|
||||||
|
> - Card 2: ¥398/月. Badge: "适合中等规模". (Highlight: slightly darker border or shadow).
|
||||||
|
> - Card 3: ¥520/月. Badge: "深度运算".
|
||||||
|
> - **Bottom Wide Card:** A full-width card for "Claude Code 定制套餐" (¥999).
|
||||||
|
>
|
||||||
|
> **4. CRITICAL COMPONENT - Bottom Action Button:**
|
||||||
|
> - Inside EVERY card, pinned to the bottom.
|
||||||
|
> - Container: `bg-gray-50` (light gray), `rounded-lg`, `p-4`, `mt-6`.
|
||||||
|
> - Layout: Flex row, `justify-between`, `items-center`.
|
||||||
|
> - Text Left: "联系顾问" (font-medium text-sm).
|
||||||
|
> - Text Right: Small gray caption (e.g., "codex标准套餐 · ¥79.9/月").
|
||||||
|
> - Icon: Small `ChevronDown` or `ChevronRight` on the right side.
|
||||||
|
>
|
||||||
|
> **5. Copy:** Use the exact Chinese text provided in the description.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔵 Step 2: Documentation Page (使用文档页)
|
||||||
|
*复制此段生成文档页面组件。*
|
||||||
|
|
||||||
|
**Prompt:**
|
||||||
|
|
||||||
|
> **Task:** Create a **`DocsPage.vue`** component. Clone the "Usage Guide" design exactly.
|
||||||
|
>
|
||||||
|
> **1. Header & Controls:**
|
||||||
|
> - Title: "使用说明". Subtitle: "新产品与插件安装的快速指南".
|
||||||
|
> - **Tabs (Model):** A pill-shaped toggle container. Items: [Codex | Claude Code | GLM].
|
||||||
|
> - Active State: Black background, White text.
|
||||||
|
> - Inactive State: Transparent background, Gray text.
|
||||||
|
> - **Tabs (OS):** Underline style. [Windows | macOS | Linux]. Active item has a black underline.
|
||||||
|
>
|
||||||
|
> **2. Vertical Stepper Layout (The Core Visual):**
|
||||||
|
> - **Container:** Max-width 800px, centered.
|
||||||
|
> - **Structure:** A vertical list of steps.
|
||||||
|
> - **Left Column:** A Step Indicator.
|
||||||
|
> - A black circle (`w-6 h-6 bg-black text-white rounded-full flex items-center justify-center text-xs font-bold`) with the number (1, 2, 3).
|
||||||
|
> - A thin gray vertical line (`w-px bg-gray-200`) connecting the numbers.
|
||||||
|
> - **Right Column:** The content.
|
||||||
|
>
|
||||||
|
> **3. Step Content Details:**
|
||||||
|
> - **Step 1 (Install Node.js):**
|
||||||
|
> - Title: "安装 Node.js 18+ (通用)".
|
||||||
|
> - Link: "官网下载 (推荐)" in blue.
|
||||||
|
> - Warning Box: A `bg-gray-50` rounded box containing bullet points about permissions.
|
||||||
|
> - Version Check: A dark bar `node --version`.
|
||||||
|
> - **Step 2 (Install CLI):**
|
||||||
|
> - Title: "安装 Codex CLI".
|
||||||
|
> - **Code Block:** A dark container (`bg-[#0f172a]`), `rounded-lg`, `p-4`, `shadow-inner`.
|
||||||
|
> - Text: `npm install -g @openai/codex` (White text, font-mono).
|
||||||
|
> - **Step 3 (Config):**
|
||||||
|
> - Title: "配置环境 (CodeX)".
|
||||||
|
> - **File Editor UI:** Create a mock code editor.
|
||||||
|
> - Filename: "config.toml文件" (gray text above block).
|
||||||
|
> - Code Content: Multi-line TOML config. Use specific colors for syntax highlighting (e.g., Strings in green, Keys in purple/white).
|
||||||
|
>
|
||||||
|
> **4. Component Abstraction:**
|
||||||
|
> - Please create a separate `<CodeBlock :code="code" :lang="lang" />` component for reusability.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔴 Step 3: Features Page (功能特性页)
|
||||||
|
*复制此段生成功能着陆页组件。*
|
||||||
|
|
||||||
|
**Prompt:**
|
||||||
|
|
||||||
|
> **Task:** Create a **`FeaturesPage.vue`** component. Clone the "Features" design exactly.
|
||||||
|
>
|
||||||
|
> **1. Hero Section:**
|
||||||
|
> - Alignment: Center.
|
||||||
|
> - Headline: "更快、更智能地构建软件" (text-5xl font-bold tracking-tight mb-6).
|
||||||
|
> - Subheadline: "聚合多模型,深度理解你的代码库..." (text-gray-500 max-w-2xl mx-auto).
|
||||||
|
> - Action Buttons:
|
||||||
|
> - Primary: "开始使用" (Black bg, white text, rounded-full, px-8 py-2.5).
|
||||||
|
> - Secondary: "查看文档" (White bg, border border-gray-200, black text, rounded-full, px-8 py-2.5).
|
||||||
|
>
|
||||||
|
> **2. The Bento Grid (Critical Visual):**
|
||||||
|
> - Section Title: "覆盖从编码到交付的全流程".
|
||||||
|
> - **Grid System:** `grid-cols-1 md:grid-cols-3 gap-6`.
|
||||||
|
> - **Card Anatomy:**
|
||||||
|
> - `bg-white border border-gray-200 rounded-xl p-6 flex flex-col h-full overflow-hidden hover:shadow-md transition-all`.
|
||||||
|
> - **Icon:** Top-left. Small Lucide icon inside a square `bg-gray-50` rounded container.
|
||||||
|
> - **Text:** Title (font-bold mt-4) + Description (text-sm text-gray-500 mt-2).
|
||||||
|
> - **The Mockup Area (Must Have):** At the bottom of each card, create a visual placeholder.
|
||||||
|
> - *Implementation:* A `div` with `bg-gray-50/50 mt-6 h-32 w-full rounded border border-dashed border-gray-200`. Put a small "skeleton" UI inside (e.g., a few gray bars representing code lines) to mimic the screenshots.
|
||||||
|
>
|
||||||
|
> **3. Stats Bar:**
|
||||||
|
> - Layout: Flex row, wide container.
|
||||||
|
> - Items:
|
||||||
|
> - "2x" (交付效率提升)
|
||||||
|
> - "40%" (Bug 修复加速)
|
||||||
|
> - "60%" (重复劳动减少)
|
||||||
|
> - Style: Numbers are huge (`text-5xl font-bold tracking-tighter`). Labels are small and gray.
|
||||||
|
>
|
||||||
|
> **4. Footer CTA:**
|
||||||
|
> - Text: "准备好升级研发效率了吗?"
|
||||||
|
> - Button: "免费开始" (White button, border, rounded-full).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💡 Developer Tips (For You)
|
||||||
|
|
||||||
|
1. **Global Font Fix:** To get the crisp look from the screenshots, add this to your `index.css` or `style.css`:
|
||||||
|
```css
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
2. **Icon Installation:** Ensure you have the icons installed:
|
||||||
|
```bash
|
||||||
|
npm install lucide-vue-next
|
||||||
|
```
|
||||||
|
3. **Refining:** If the AI output looks "too loose", tell it: *"Reduce the border-radius to 12px and make the text colors sharper (slate-900 instead of gray-800)."*
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>自由职业开发 · 前端/全栈/Web3D · 交付型合作</title>
|
||||||
|
<meta name="description" content="承接企业官网、后台管理、全栈业务系统与 Web3D 互动展示(Three.js / Babylon.js),支持按里程碑交付与长期维护。" />
|
||||||
|
</head>
|
||||||
|
<body class="bg-white text-slate-900">
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2579
package-lock.json
generated
Normal file
2579
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "studio",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/prismjs": "^1.26.5",
|
||||||
|
"@vueuse/core": "^14.1.0",
|
||||||
|
"lucide-vue-next": "^0.400.0",
|
||||||
|
"marked": "^17.0.1",
|
||||||
|
"prismjs": "^1.30.0",
|
||||||
|
"typeit": "^8.8.7",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^25.0.3",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
|
"autoprefixer": "^10.4.16",
|
||||||
|
"postcss": "^8.4.33",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typescript": "5.4.5",
|
||||||
|
"vite": "^5.0.10",
|
||||||
|
"vue-tsc": "^1.8.26"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
39
public/docs/case-template.md
Normal file
39
public/docs/case-template.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 案例模板(可直接复制改内容)
|
||||||
|
|
||||||
|
> 这是一个 Markdown 案例模板,你可以复制成 `case-xxx.md`,然后在页面里直接预览。
|
||||||
|
|
||||||
|
## 项目背景
|
||||||
|
|
||||||
|
- 行业 / 场景:
|
||||||
|
- 目标(转化/展示/提效):
|
||||||
|
- 时间周期:
|
||||||
|
- 我的角色:前端 / 全栈 / Web3D / 小程序
|
||||||
|
|
||||||
|
## 交付内容
|
||||||
|
|
||||||
|
- 核心页面:
|
||||||
|
- 核心功能:
|
||||||
|
- 技术栈:
|
||||||
|
- 部署方式:
|
||||||
|
|
||||||
|
## 难点与解决方案
|
||||||
|
|
||||||
|
1. 难点:
|
||||||
|
2. 方案:
|
||||||
|
3. 结果:
|
||||||
|
|
||||||
|
## 代码片段(示例)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type ApiResponse<T> = {
|
||||||
|
code: number
|
||||||
|
message: string
|
||||||
|
data: T
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 复盘
|
||||||
|
|
||||||
|
- 做得好的:
|
||||||
|
- 下次会更好的:
|
||||||
|
|
||||||
16
public/docs/contact.md
Normal file
16
public/docs/contact.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 联系方式
|
||||||
|
|
||||||
|
> 建议把这里的联系方式换成你的真实信息。
|
||||||
|
|
||||||
|
- 微信:`TyTinSx`
|
||||||
|
- 邮箱:`14154666@qq.com`
|
||||||
|
|
||||||
|
## 发需求时建议包含
|
||||||
|
|
||||||
|
1. 项目类型:官网/后台/全栈/Web3D/小程序
|
||||||
|
2. 目标:要达成什么效果或指标
|
||||||
|
3. 范围:页面/功能清单(越具体越好)
|
||||||
|
4. 参考:竞品/参考链接、设计稿、文案素材
|
||||||
|
5. 时间:期望上线时间、是否有阶段节点
|
||||||
|
6. 现状:是否已有代码、接口、服务器、域名等
|
||||||
|
|
||||||
22
public/docs/services.md
Normal file
22
public/docs/services.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 服务清单
|
||||||
|
|
||||||
|
## 我能做什么
|
||||||
|
|
||||||
|
- 企业官网 / 产品官网 / 落地页:品牌展示、动效、响应式、SEO 基础、上线部署。
|
||||||
|
- 后台管理系统:权限、表格、筛选、导入导出、图表看板、审批流(按需)。
|
||||||
|
- 全栈业务开发:接口设计、鉴权、数据库建模、联调、部署(按需)。
|
||||||
|
- Web3D 互动展示:Three.js / Babylon.js,模型加载、交互热点、性能优化与业务联动。
|
||||||
|
- 3D 建模与渲染:建模、UV、贴图烘焙、PBR 材质、灯光渲染与出片(静帧/视频)。
|
||||||
|
- 小程序开发:微信小程序 / uni-app(按需),常见业务页面与接口联调发布。
|
||||||
|
|
||||||
|
## 你需要提供什么
|
||||||
|
|
||||||
|
- 目标与范围:要解决什么问题?核心流程是什么?
|
||||||
|
- 参考与素材:参考链接、设计稿(Figma/蓝湖/Sketch)、文案、图片、Logo 等。
|
||||||
|
- 约束条件:时间节点、预算范围、技术限制、已有接口/账号/服务器(如有)。
|
||||||
|
|
||||||
|
## 我会交付什么
|
||||||
|
|
||||||
|
- 可运行项目(源码 + 构建产物)
|
||||||
|
- 交付说明(部署/环境变量/使用说明)
|
||||||
|
- 里程碑验收清单(对照验收更省心)
|
||||||
63
public/docs/web3d.md
Normal file
63
public/docs/web3d.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Web3D 案例展示
|
||||||
|
|
||||||
|
基于 Three.js / Babylon.js 开发的 3D 互动展示项目。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 小型货车 3D 配置器
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
蓝擎X1小型货车在线配置器,支持颜色切换(极光蓝/珠光白)、外观/内饰/行驶模式预览,可触发车门开关、车灯等交互动画。
|
||||||
|
|
||||||
|
🔗 暂无分享链接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SUV 汽车 3D 展示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
星仓系列 SUV 3D 展厅,支持宝石蓝、水晶紫、冰川银、岩石灰等多种车漆颜色实时切换,外观与内饰双视角浏览。
|
||||||
|
|
||||||
|
🔗 暂无分享链接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 虚拟数字展厅
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
企业虚拟展馆,第三人称漫游模式,集成产品陈列、数据大屏、中国地图可视化,支持虚拟人物导览。
|
||||||
|
|
||||||
|
🔗 暂无分享链接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 摩托车 3D 展示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
高精度摩托车模型展示,展厅灯光环境,支持 360° 旋转查看细节。
|
||||||
|
|
||||||
|
🔗 暂无分享链接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 运动鞋 3D 配置器
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
运动鞋在线定制工具,支持多种配色方案实时切换,材质纹理清晰可见。
|
||||||
|
|
||||||
|
🔗 暂无分享链接
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 自行车 3D 展示
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
山地自行车户外场景展示,支持车型配置选择,实时渲染骑行效果预览。
|
||||||
|
|
||||||
|
🔗 暂无分享链接
|
||||||
33
public/docs/建模与渲染.md
Normal file
33
public/docs/建模与渲染.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# 建模与渲染(PBR 流程)
|
||||||
|
|
||||||
|
面向「产品 / 角色 / 场景」资产制作:建模 → UV → 烘焙 → PBR 材质 → 灯光渲染 → 出片(静帧/视频)。流程可按你的项目预算与时间裁剪。
|
||||||
|
|
||||||
|
## 适合的需求
|
||||||
|
|
||||||
|
- 产品展示:电商主图、详情图、爆炸图、360 展示、质感渲染
|
||||||
|
- 角色/道具:游戏或宣传视觉的资产制作与渲染出片
|
||||||
|
- 场景/小片段:镜头分镜、氛围灯光、简单动画与剪辑出片(按需)
|
||||||
|
|
||||||
|
## 标准流程(可按项目裁剪)
|
||||||
|
|
||||||
|
1. 需求对齐:用途(电商/游戏/交互/视频)、风格参考、镜头与交付规格(分辨率/FPS/时长)
|
||||||
|
2. 建模:高模/低模(按需)、拓扑优化、模型命名与层级整理
|
||||||
|
3. UV:拆分、展开、打包;为贴图与烘焙做准备
|
||||||
|
4. 烘焙:Normal / AO / Curvature / ID 等(按项目需要)
|
||||||
|
5. 材质(PBR):BaseColor / Metallic / Roughness / Normal / AO(+ Emissive/Opacity 按需)
|
||||||
|
6. 灯光与渲染:HDRI/三点布光、材质校色、渲染参数与降噪(按引擎与预算)
|
||||||
|
7. 合成与出片:通道(AOV)合成、调色、字幕/转场、输出静帧或视频
|
||||||
|
8. 交付:源文件 + 贴图 + 成片;需要适配引擎/平台也可输出 glTF/FBX(按需)
|
||||||
|
|
||||||
|
## 你需要提供什么
|
||||||
|
|
||||||
|
- 参考与目标:风格图/竞品链接、用途与核心卖点
|
||||||
|
- 素材:Logo/贴纸/花纹(如有)、尺寸/结构信息(如有)
|
||||||
|
- 交付规格:分辨率、画幅、背景(透明/纯色/场景)、视频时长与帧率
|
||||||
|
|
||||||
|
## 我会交付什么
|
||||||
|
|
||||||
|
- 源文件(按工具):工程文件/模型文件/材质节点(按约定)
|
||||||
|
- 贴图文件:PBR 贴图与烘焙贴图(按约定打包)
|
||||||
|
- 成片:静帧(PNG/JPG)或视频(MP4),可提供可复用的镜头与灯光设置(按需)
|
||||||
|
|
||||||
34
public/docs/按天.md
Normal file
34
public/docs/按天.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# 按天合作
|
||||||
|
|
||||||
|
适合短期支援、技术咨询或灵活用工需求。
|
||||||
|
|
||||||
|
## 合作方式
|
||||||
|
|
||||||
|
1. 确认工作内容与时间
|
||||||
|
2. 按天计费,灵活安排
|
||||||
|
3. 每日同步进度
|
||||||
|
4. 按实际工作天数结算
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- 项目紧急支援
|
||||||
|
- 技术难题攻关
|
||||||
|
- 代码审查与优化
|
||||||
|
- 架构设计咨询
|
||||||
|
|
||||||
|
## 日费参考
|
||||||
|
|
||||||
|
| 服务类型 | 日费 |
|
||||||
|
|---------|------|
|
||||||
|
| 前端开发 | ¥800 - ¥1,500 |
|
||||||
|
| 后端开发 | ¥1,000 - ¥2,000 |
|
||||||
|
| 全栈开发 | ¥1,200 - ¥2,500 |
|
||||||
|
| 技术咨询 | ¥1,500 - ¥3,000 |
|
||||||
|
|
||||||
|
## 工作时间
|
||||||
|
|
||||||
|
- 标准:8小时/天
|
||||||
|
- 支持远程协作
|
||||||
|
- 可根据需求调整
|
||||||
|
|
||||||
|
> 长期合作可享优惠价格
|
||||||
27
public/docs/按项目.md
Normal file
27
public/docs/按项目.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# 按项目合作
|
||||||
|
|
||||||
|
适合有明确需求、完整功能模块的客户。
|
||||||
|
|
||||||
|
## 合作方式
|
||||||
|
|
||||||
|
1. 需求沟通与评估
|
||||||
|
2. 报价与合同签订
|
||||||
|
3. 分阶段交付与验收
|
||||||
|
4. 项目上线与维护
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
- 企业官网开发
|
||||||
|
- 小程序/App 开发
|
||||||
|
- 后台管理系统
|
||||||
|
- 定制化功能模块
|
||||||
|
|
||||||
|
## 报价参考
|
||||||
|
|
||||||
|
| 项目类型 | 周期 | 价格区间 |
|
||||||
|
|---------|------|---------|
|
||||||
|
| 企业官网 | 1-2周 | ¥3,000 - ¥8,000 |
|
||||||
|
| 小程序 | 2-4周 | ¥5,000 - ¥15,000 |
|
||||||
|
| 管理后台 | 3-6周 | ¥10,000 - ¥30,000 |
|
||||||
|
|
||||||
|
> 具体报价根据实际需求评估
|
||||||
BIN
public/案例图片/web3d/5b86f5fffbab10515fa800a435620437.png
Normal file
BIN
public/案例图片/web3d/5b86f5fffbab10515fa800a435620437.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
BIN
public/案例图片/web3d/de96ab4609d15338c15f7eb97ec82cc1.png
Normal file
BIN
public/案例图片/web3d/de96ab4609d15338c15f7eb97ec82cc1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 594 KiB |
BIN
public/案例图片/web3d/dfcac2db610f8005a823d0dc93d8cea9.png
Normal file
BIN
public/案例图片/web3d/dfcac2db610f8005a823d0dc93d8cea9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
BIN
public/案例图片/web3d/xw_20251231091651.png
Normal file
BIN
public/案例图片/web3d/xw_20251231091651.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
BIN
public/案例图片/web3d/xw_20251231091744.png
Normal file
BIN
public/案例图片/web3d/xw_20251231091744.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 393 KiB |
BIN
public/案例图片/web3d/xw_20251231091809.png
Normal file
BIN
public/案例图片/web3d/xw_20251231091809.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
141
src/App.vue
Normal file
141
src/App.vue
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex min-h-screen flex-col bg-white text-slate-900">
|
||||||
|
<header
|
||||||
|
:class="[
|
||||||
|
'sticky top-0 z-10 border-b transition-[border-color,background,backdrop-filter] duration-[400ms] ease-[cubic-bezier(0.4,0,0.2,1)]',
|
||||||
|
isScrolled ? 'border-transparent bg-transparent [backdrop-filter:none]' : 'border-gray-200 bg-white/90 backdrop-blur-[6px]',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx-auto max-w-[1200px] transition-[width] ease-[cubic-bezier(0.34,1.56,0.64,1)]"
|
||||||
|
:style="{ width: `${capsuleWidth}%`, transitionDuration: isScrolled ? '1000ms' : '1500ms' }"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'flex items-center justify-between transition-[height,margin-top,padding,background,border-radius,box-shadow] duration-[400ms] ease-[cubic-bezier(0.22,1,0.36,1)] max-[640px]:h-auto max-[640px]:flex-col max-[640px]:items-start max-[640px]:gap-3 max-[640px]:px-0 max-[640px]:py-3',
|
||||||
|
isScrolled
|
||||||
|
? 'mt-3 h-14 rounded-full bg-white/[0.98] px-5 shadow-[0_4px_24px_rgba(0,0,0,0.08),0_1px_2px_rgba(0,0,0,0.04)]'
|
||||||
|
: 'mt-0 h-[72px] rounded-none bg-transparent px-6 shadow-none',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="inline-flex items-center gap-2.5">
|
||||||
|
<div class="h-5 w-5 rotate-45 rounded-[4px] border-2 border-slate-900 shadow-[0_2px_6px_rgba(0,0,0,0.08)]" />
|
||||||
|
<div class="grid leading-tight">
|
||||||
|
<span class="text-sm font-semibold">{{ site.brand }}</span>
|
||||||
|
<!-- <span class="text-[11px] text-gray-500">{{ site.subtitle }}</span> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="flex items-center gap-[18px] max-[900px]:hidden">
|
||||||
|
<button
|
||||||
|
v-for="tab in tabs"
|
||||||
|
:key="tab.id"
|
||||||
|
:class="[
|
||||||
|
'border-b-2 border-transparent pb-1.5 text-sm text-gray-500 transition-colors hover:text-gray-900',
|
||||||
|
activeId === tab.id ? 'border-black text-black' : '',
|
||||||
|
]"
|
||||||
|
@click="handleNav(tab.path)"
|
||||||
|
>
|
||||||
|
{{ tab.label }}
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3 max-[640px]:w-full max-[640px]:justify-between">
|
||||||
|
<a
|
||||||
|
class="rounded-full border border-slate-900 bg-slate-900 px-4 py-2.5 text-sm font-semibold text-white shadow-[0_10px_24px_rgba(0,0,0,0.12)] transition-[transform,box-shadow,background,color] duration-200 hover:shadow-[0_12px_28px_rgba(0,0,0,0.16)] active:translate-y-px"
|
||||||
|
:href="`mailto:${site.contact.email}`"
|
||||||
|
>
|
||||||
|
咨询合作
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="mx-auto w-full max-w-[1200px] flex-1 px-6 pt-8 pb-12 max-[640px]:px-4 max-[640px]:pt-6 max-[640px]:pb-10">
|
||||||
|
<RouterView />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="border-t border-gray-200 bg-white">
|
||||||
|
<div class="mx-auto grid w-full max-w-[1200px] grid-cols-5 gap-6 px-6 pt-10 pb-6 max-[900px]:grid-cols-2 max-[640px]:grid-cols-1">
|
||||||
|
<div class="col-span-2 grid gap-3 max-[900px]:col-span-2 max-[640px]:col-span-1">
|
||||||
|
<div class="inline-flex items-center gap-2.5">
|
||||||
|
<div class="h-5 w-5 rotate-45 rounded-[4px] border-2 border-slate-900 shadow-[0_2px_6px_rgba(0,0,0,0.08)]" />
|
||||||
|
<span class="text-sm font-semibold">{{ site.brand }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="m-0 leading-relaxed text-gray-600">{{ site.tagline }}</p>
|
||||||
|
<div class="grid gap-1 text-xs text-gray-500">
|
||||||
|
<div>微信:{{ site.contact.wechat }}</div>
|
||||||
|
<div>邮箱:{{ site.contact.email }}</div>
|
||||||
|
</div>
|
||||||
|
<p class="m-0 text-xs text-gray-400">© 2025 {{ site.brand }}. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<div class="font-bold text-gray-900">服务</div>
|
||||||
|
<ul class="m-0 grid list-none gap-1.5 p-0 text-gray-600">
|
||||||
|
<li>企业官网 / 落地页</li>
|
||||||
|
<li>后台管理系统</li>
|
||||||
|
<li>全栈业务开发</li>
|
||||||
|
<li>Web3D 互动展示</li>
|
||||||
|
<li>3D建模与渲染(PBR)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<div class="font-bold text-gray-900">技术栈</div>
|
||||||
|
<ul class="m-0 grid list-none gap-1.5 p-0 text-gray-600">
|
||||||
|
<li>Vue / React / TypeScript</li>
|
||||||
|
<li>Node.js / NestJS / Express</li>
|
||||||
|
<li>MySQL / PostgreSQL / Redis</li>
|
||||||
|
<li>Three.js / Babylon.js</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-2">
|
||||||
|
<div class="font-bold text-gray-900">合作</div>
|
||||||
|
<ul class="m-0 grid list-none gap-1.5 p-0 text-gray-600">
|
||||||
|
<li>按需求评估报价</li>
|
||||||
|
<li>按里程碑验收交付</li>
|
||||||
|
<li>支持远程协作</li>
|
||||||
|
<li>可提供维护服务</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-200 py-3 text-center text-xs text-gray-400">合作条款 · 交付说明</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useScroll } from '@vueuse/core'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import { site } from '@/config/site'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{ id: 'features', label: '能力与服务', path: '/' },
|
||||||
|
// { id: 'pricing', label: '报价', path: '/' },
|
||||||
|
{ id: 'cooperation', label: '合作流程', path: '/cooperation' },
|
||||||
|
{ id: 'markdown', label: '案例/文章', path: '/markdown' },
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const activeId = computed(() => {
|
||||||
|
if (route.name === 'features') return 'features'
|
||||||
|
if (route.name === 'cooperation') return 'cooperation'
|
||||||
|
if (route.name === 'markdown') return 'markdown'
|
||||||
|
return 'pricing'
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleNav = (path: string) => {
|
||||||
|
if (route.path !== path) router.push(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { y } = useScroll(window, { throttle: 16 })
|
||||||
|
const isScrolled = computed(() => y.value > 50)
|
||||||
|
const capsuleWidth = computed(() => (isScrolled.value ? 40 : 100))
|
||||||
|
</script>
|
||||||
18
src/components/Button/index.vue
Normal file
18
src/components/Button/index.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:class="[
|
||||||
|
'rounded-full px-5 py-3 text-sm font-semibold transition-[transform,box-shadow,background,color,border-color] duration-200 active:translate-y-px',
|
||||||
|
variant === 'secondary'
|
||||||
|
? 'border border-gray-200 bg-white text-slate-900 shadow-[0_8px_18px_rgba(0,0,0,0.08)]'
|
||||||
|
: 'border border-slate-900 bg-slate-900 text-white shadow-[0_12px_30px_rgba(0,0,0,0.16)]',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
variant?: 'primary' | 'secondary'
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
16
src/components/Card/index.vue
Normal file
16
src/components/Card/index.vue
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="[
|
||||||
|
'rounded-[18px] border border-gray-200 bg-white p-[22px] shadow-[0_14px_26px_rgba(0,0,0,0.06)]',
|
||||||
|
hoverable ? 'transition-[transform,box-shadow,border-color] duration-200 hover:-translate-y-1 hover:border-gray-300 hover:shadow-[0_20px_40px_rgba(0,0,0,0.1)]' : '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
hoverable?: boolean
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
37
src/components/CodeBlock.vue
Normal file
37
src/components/CodeBlock.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="overflow-hidden rounded-xl bg-slate-900 p-3 text-gray-200 shadow-[inset_0_1px_0_rgba(255,255,255,0.05)]"
|
||||||
|
>
|
||||||
|
<div class="mb-2 flex items-center justify-between text-[11px] uppercase tracking-[0.08em] text-gray-400">
|
||||||
|
<span>{{ lang || 'text' }}</span>
|
||||||
|
<button
|
||||||
|
class="rounded-full border border-white/10 bg-white/10 px-2.5 py-1 text-[11px] text-gray-50 transition-colors hover:bg-white/15"
|
||||||
|
@click="handleCopy"
|
||||||
|
>
|
||||||
|
{{ copied ? '已复制' : '复制' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre class="m-0 whitespace-pre-wrap font-mono text-[13px] leading-relaxed"><code>{{ code }}</code></pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
code: string
|
||||||
|
lang?: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const copied = ref(false)
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(props.code)
|
||||||
|
copied.value = true
|
||||||
|
setTimeout(() => (copied.value = false), 1500)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
60
src/components/PlanCard/index.vue
Normal file
60
src/components/PlanCard/index.vue
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<article
|
||||||
|
:class="[
|
||||||
|
'relative grid gap-3 rounded-2xl border border-gray-200 bg-white p-[18px] shadow-[0_14px_32px_rgba(0,0,0,0.08)] transition-[transform,box-shadow,border-color] duration-200 hover:-translate-y-1 hover:border-gray-300 hover:shadow-[0_20px_44px_rgba(0,0,0,0.12)]',
|
||||||
|
highlight ? 'border-gray-300 shadow-[0_20px_48px_rgba(0,0,0,0.14)]' : '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="badge"
|
||||||
|
class="absolute right-4 top-3 rounded-full border border-gray-200 bg-gray-100 px-2.5 py-1.5 text-xs text-gray-700"
|
||||||
|
>
|
||||||
|
{{ badge }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex items-center gap-2.5 text-gray-600">
|
||||||
|
<div class="grid h-8 w-8 place-items-center rounded-full border border-gray-200 bg-gray-100 font-bold text-gray-800">
|
||||||
|
A
|
||||||
|
</div>
|
||||||
|
<div class="font-semibold text-slate-900">{{ title }}</div>
|
||||||
|
<div class="text-gray-400">· {{ subtitle }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-3xl font-extrabold tracking-[-0.02em]">{{ price }}</div>
|
||||||
|
<div v-if="note" class="text-gray-500">{{ note }}</div>
|
||||||
|
<ul class="m-0 grid list-none gap-2 p-0">
|
||||||
|
<li v-for="feature in features" :key="feature">
|
||||||
|
<div class="grid grid-cols-[20px_1fr] items-center gap-2 text-sm text-gray-600">
|
||||||
|
<CheckCircle2 class="h-4 w-4 text-blue-600" />
|
||||||
|
<span>{{ feature }}</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<a
|
||||||
|
class="mt-1.5 flex items-center justify-between rounded-xl border border-gray-200 bg-gray-50 p-3 text-gray-600 transition-colors hover:bg-gray-100"
|
||||||
|
:href="actionHref ?? `mailto:${site.contact.email}`"
|
||||||
|
>
|
||||||
|
<div class="font-semibold text-slate-900">{{ actionLabel ?? '咨询合作' }}</div>
|
||||||
|
<div class="inline-flex items-center gap-1.5 text-xs text-gray-500">
|
||||||
|
<span>{{ footerNote }}</span>
|
||||||
|
<ChevronRight class="h-4 w-4 text-gray-400" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CheckCircle2, ChevronRight } from 'lucide-vue-next'
|
||||||
|
import { site } from '@/config/site'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
badge?: string
|
||||||
|
price: string
|
||||||
|
note?: string
|
||||||
|
features: string[]
|
||||||
|
footerNote: string
|
||||||
|
highlight?: boolean
|
||||||
|
actionLabel?: string
|
||||||
|
actionHref?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
18
src/components/StepItem/index.vue
Normal file
18
src/components/StepItem/index.vue
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<template>
|
||||||
|
<div class="grid grid-cols-[32px_1fr] items-start gap-4 max-[640px]:grid-cols-1">
|
||||||
|
<div
|
||||||
|
class="grid h-8 w-8 place-items-center rounded-full bg-slate-900 text-[13px] font-bold text-white max-[640px]:h-7 max-[640px]:w-7"
|
||||||
|
>
|
||||||
|
{{ step }}
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-3 rounded-2xl border border-gray-200 bg-white p-4 shadow-[0_12px_28px_rgba(0,0,0,0.08)]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
step: number
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
111
src/components/TypeItTerminal.vue
Normal file
111
src/components/TypeItTerminal.vue
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="target" :class="rootClass" :style="rootStyle" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import TypeIt from 'typeit'
|
||||||
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
export type TypeItTerminalSegment = {
|
||||||
|
html: string
|
||||||
|
speed?: number
|
||||||
|
pauseBefore?: number
|
||||||
|
pauseAfter?: number
|
||||||
|
deleteAfter?: boolean
|
||||||
|
deleteSpeed?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
segments?: TypeItTerminalSegment[]
|
||||||
|
minLines?: number
|
||||||
|
lineHeightEm?: number
|
||||||
|
align?: 'top' | 'center' | 'bottom'
|
||||||
|
startOnVisible?: boolean
|
||||||
|
startDelay?: number
|
||||||
|
cursor?: boolean
|
||||||
|
cursorChar?: string
|
||||||
|
loop?: boolean
|
||||||
|
loopDelay?: number | [number, number]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
minLines: 2,
|
||||||
|
lineHeightEm: 1.65,
|
||||||
|
align: 'top',
|
||||||
|
startOnVisible: true,
|
||||||
|
startDelay: 250,
|
||||||
|
cursor: true,
|
||||||
|
cursorChar: '\u258c',
|
||||||
|
loop: true,
|
||||||
|
loopDelay: () => [900, 1100],
|
||||||
|
})
|
||||||
|
|
||||||
|
const target = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const segments = computed<TypeItTerminalSegment[]>(
|
||||||
|
() =>
|
||||||
|
props.segments ?? [
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">$</span> <span class="text-green-600">npm</span> <span class="text-blue-600">run</span> build</div>',
|
||||||
|
`<div><span class="text-gray-500">${'\u2713'}</span> <span class="text-orange-500">Compiling</span> <span class="text-gray-500">src/main.ts...</span></div>`,
|
||||||
|
].join(''),
|
||||||
|
speed: 32,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
const rootClass = computed(() => {
|
||||||
|
const alignClass =
|
||||||
|
props.align === 'bottom' ? 'justify-end' : props.align === 'center' ? 'justify-center' : 'justify-start'
|
||||||
|
return ['flex flex-col', alignClass].join(' ')
|
||||||
|
})
|
||||||
|
|
||||||
|
const rootStyle = computed(() => {
|
||||||
|
if (!props.minLines) return undefined
|
||||||
|
return {
|
||||||
|
minHeight: `calc(${props.lineHeightEm}em * ${props.minLines})`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let typeit: TypeIt | null = null
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (!target.value) return
|
||||||
|
target.value.innerHTML = ''
|
||||||
|
|
||||||
|
const instance = new TypeIt(target.value, {
|
||||||
|
startDelay: props.startDelay,
|
||||||
|
lifeLike: true,
|
||||||
|
cursor: props.cursor,
|
||||||
|
cursorChar: props.cursorChar,
|
||||||
|
html: true,
|
||||||
|
waitUntilVisible: props.startOnVisible,
|
||||||
|
loop: props.loop,
|
||||||
|
loopDelay: props.loopDelay,
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const segment of segments.value) {
|
||||||
|
if (segment.pauseBefore) {
|
||||||
|
instance.pause(segment.pauseBefore)
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.type(segment.html, segment.speed ? { speed: segment.speed } : {})
|
||||||
|
|
||||||
|
if (segment.pauseAfter) {
|
||||||
|
instance.pause(segment.pauseAfter)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment.deleteAfter) {
|
||||||
|
instance.delete(undefined, segment.deleteSpeed ? { deleteSpeed: segment.deleteSpeed } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeit = instance.go()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
typeit?.destroy()
|
||||||
|
typeit = null
|
||||||
|
})
|
||||||
|
</script>
|
||||||
10
src/config/site.ts
Normal file
10
src/config/site.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export const site = {
|
||||||
|
brand: '子归云工作室',
|
||||||
|
// subtitle: '前端 · 全栈 · Web3D · 3D建模/渲染 · 官网/后台/小程序',
|
||||||
|
tagline:
|
||||||
|
'承接企业官网、后台管理、全栈业务系统、Web3D 互动展示(Three.js / Babylon.js)与 3D 建模渲染(PBR 流程)。支持按需求评估、按里程碑交付、长期维护。',
|
||||||
|
contact: {
|
||||||
|
wechat: 'TyTinSx',
|
||||||
|
email: '1415466602@qq.com',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
6
src/main.ts
Normal file
6
src/main.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { createApp } from 'vue'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { router } from './router'
|
||||||
|
import './style.css'
|
||||||
|
|
||||||
|
createApp(App).use(router).mount('#app')
|
||||||
16
src/router/index.ts
Normal file
16
src/router/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import PricingPage from '@/views/PricingPage.vue'
|
||||||
|
import FeaturesPage from '@/views/FeaturesPage.vue'
|
||||||
|
import MarkdownPage from '@/views/MarkdownPage.vue'
|
||||||
|
import CooperationPage from '@/views/CooperationPage.vue'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ path: '/', name: 'features', component: FeaturesPage },
|
||||||
|
{ path: '/markdown', name: 'markdown', component: MarkdownPage },
|
||||||
|
{ path: '/cooperation', name: 'cooperation', component: CooperationPage },
|
||||||
|
]
|
||||||
|
|
||||||
|
export const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
9
src/style.css
Normal file
9
src/style.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
body {
|
||||||
|
@apply font-sans antialiased;
|
||||||
|
}
|
||||||
|
}
|
||||||
116
src/views/CooperationPage.vue
Normal file
116
src/views/CooperationPage.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<section class="grid gap-6 pt-3 pb-10">
|
||||||
|
<header class="grid gap-2.5">
|
||||||
|
<h1 class="m-0 text-[28px] font-extrabold tracking-[-0.01em]">合作流程</h1>
|
||||||
|
<p class="m-0 text-gray-600">灵活的合作模式,满足不同项目需求</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
v-for="file in mdFiles"
|
||||||
|
:key="file"
|
||||||
|
:class="[
|
||||||
|
'rounded-full px-4 py-2 text-sm font-semibold transition-colors',
|
||||||
|
currentFile === file ? 'bg-slate-900 text-white' : 'border border-gray-200 bg-white text-gray-600 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
@click="loadFile(file)"
|
||||||
|
>
|
||||||
|
{{ file.replace('.md', '') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="htmlContent"
|
||||||
|
ref="contentRef"
|
||||||
|
class="prose prose-slate max-w-none rounded-2xl border border-gray-200 bg-white p-6 shadow-[0_14px_26px_rgba(0,0,0,0.06)]"
|
||||||
|
v-html="htmlContent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { marked } from 'marked'
|
||||||
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const BASE_URL = import.meta.env.BASE_URL
|
||||||
|
|
||||||
|
// 合作流程文件列表
|
||||||
|
const cooperationFiles = ['按项目.md', '按天.md']
|
||||||
|
|
||||||
|
const mdFiles = ref<string[]>([])
|
||||||
|
const currentFile = ref('')
|
||||||
|
const htmlContent = ref('')
|
||||||
|
const contentRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
mdFiles.value = cooperationFiles
|
||||||
|
if (mdFiles.value.length > 0) {
|
||||||
|
await loadFile(mdFiles.value[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const addCopyButtons = () => {
|
||||||
|
if (!contentRef.value) return
|
||||||
|
contentRef.value.querySelectorAll('pre').forEach((pre) => {
|
||||||
|
if (pre.querySelector('.copy-btn')) return
|
||||||
|
const button = document.createElement('button')
|
||||||
|
button.className = 'copy-btn'
|
||||||
|
button.textContent = '复制'
|
||||||
|
button.onclick = async () => {
|
||||||
|
const code = pre.querySelector('code')
|
||||||
|
const text = code?.textContent || ''
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
} catch {
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = text
|
||||||
|
textarea.style.cssText = 'position:fixed;opacity:0'
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
textarea.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(textarea)
|
||||||
|
}
|
||||||
|
button.textContent = '已复制'
|
||||||
|
setTimeout(() => (button.textContent = '复制'), 1500)
|
||||||
|
}
|
||||||
|
pre.style.position = 'relative'
|
||||||
|
pre.appendChild(button)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(htmlContent, () => nextTick(addCopyButtons))
|
||||||
|
|
||||||
|
const loadFile = async (filename: string) => {
|
||||||
|
currentFile.value = filename
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}docs/${filename}`)
|
||||||
|
if (!res.ok) return
|
||||||
|
const content = await res.text()
|
||||||
|
htmlContent.value = marked(content) as string
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载文档失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.prose h1 { font-size: 1.875rem; font-weight: 800; margin-bottom: 1rem; }
|
||||||
|
.prose h2 { font-size: 1.5rem; font-weight: 700; margin-top: 1.5rem; margin-bottom: 0.75rem; }
|
||||||
|
.prose h3 { font-size: 1.25rem; font-weight: 600; margin-top: 1.25rem; margin-bottom: 0.5rem; }
|
||||||
|
.prose p { margin-bottom: 1rem; line-height: 1.7; color: #4b5563; }
|
||||||
|
.prose ul, .prose ol { margin-bottom: 1rem; padding-left: 1.5rem; }
|
||||||
|
.prose li { margin-bottom: 0.25rem; color: #4b5563; }
|
||||||
|
.prose code { background: #f1f5f9; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875rem; }
|
||||||
|
.prose pre { background: #1e293b; color: #e2e8f0; padding: 1rem; padding-top: 2.5rem; border-radius: 0.75rem; overflow-x: auto; margin-bottom: 1rem; position: relative; }
|
||||||
|
.prose pre::before { content: ''; position: absolute; top: 12px; left: 12px; width: 12px; height: 12px; border-radius: 50%; background: #ff5f56; box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f; }
|
||||||
|
.prose pre code { background: transparent; padding: 0; }
|
||||||
|
.prose a { color: #2563eb; text-decoration: underline; }
|
||||||
|
.prose blockquote { border-left: 4px solid #e5e7eb; padding-left: 1rem; color: #6b7280; font-style: italic; }
|
||||||
|
.prose table { width: 100%; border-collapse: collapse; margin-bottom: 1rem; }
|
||||||
|
.prose th, .prose td { border: 1px solid #e5e7eb; padding: 0.5rem 1rem; text-align: left; }
|
||||||
|
.prose th { background: #f9fafb; font-weight: 600; }
|
||||||
|
.copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; padding: 0.25rem 0.75rem; font-size: 0.75rem; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 9999px; color: #e2e8f0; cursor: pointer; transition: background 0.2s; }
|
||||||
|
.copy-btn:hover { background: rgba(255,255,255,0.2); }
|
||||||
|
</style>
|
||||||
341
src/views/FeaturesPage.vue
Normal file
341
src/views/FeaturesPage.vue
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
<template>
|
||||||
|
<section class="grid gap-10 pt-3 pb-12">
|
||||||
|
<header class="grid gap-3 text-center">
|
||||||
|
<div class="text-[13px] text-gray-500">能力与服务 / 交付型合作 / 可长期维护</div>
|
||||||
|
<h1 class="m-0 text-[clamp(28px,4vw,44px)] font-extrabold tracking-[-0.02em]">把需求做成可交付的产品</h1>
|
||||||
|
<p class="m-0 mx-auto max-w-[680px] leading-[1.7] text-gray-600">
|
||||||
|
我能做前端、全栈、Web3D(Babylon.js / Three.js)、3D 建模/渲染(PBR)、官网与后台管理、小程序开发等。你给目标和素材,我负责方案、实现、联调、上线。
|
||||||
|
</p>
|
||||||
|
<div class="inline-flex justify-center gap-3 max-[640px]:w-full max-[640px]:justify-center">
|
||||||
|
<Button @click="router.push('/cooperation')">查看报价</Button>
|
||||||
|
<Button variant="secondary" @click="router.push('/cooperation')">合作流程</Button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid gap-[18px]">
|
||||||
|
<div class="mt-2 text-center text-[22px] font-bold">覆盖从设计对接到上线交付的全流程</div>
|
||||||
|
<div class="grid grid-cols-3 gap-5 max-[1024px]:grid-cols-2 max-[640px]:grid-cols-1">
|
||||||
|
<Card v-for="(feature, index) in features" :key="feature.title" hoverable class="grid content-start gap-3">
|
||||||
|
<div
|
||||||
|
class="-mt-0.5 -ml-0.5 inline-flex h-12 w-12 items-center justify-center rounded-[14px] border border-gray-200 bg-gray-50 shadow-[0_8px_18px_rgba(0,0,0,0.08)]"
|
||||||
|
>
|
||||||
|
<component :is="feature.icon" class="h-6 w-6 text-gray-700" />
|
||||||
|
</div>
|
||||||
|
<h3 class="mt-2 text-[17px] font-bold">{{ feature.title }}</h3>
|
||||||
|
<p class="m-0 text-sm leading-relaxed text-gray-600">{{ feature.description }}</p>
|
||||||
|
<div
|
||||||
|
class="mt-3 grid gap-2 rounded-2xl border border-slate-100 bg-slate-50 px-3 py-2.5 shadow-[inset_0_1px_0_rgba(255,255,255,0.6),0_10px_24px_rgba(0,0,0,0.05)]"
|
||||||
|
>
|
||||||
|
<div class="flex gap-1.5">
|
||||||
|
<span class="h-2.5 w-2.5 rounded-full bg-red-400 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]"></span>
|
||||||
|
<span class="h-2.5 w-2.5 rounded-full bg-amber-400 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]"></span>
|
||||||
|
<span class="h-2.5 w-2.5 rounded-full bg-emerald-400 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]"></span>
|
||||||
|
</div>
|
||||||
|
<TypeItTerminal
|
||||||
|
:segments="featureTerminalScripts[index]"
|
||||||
|
:align="featureTerminalAlign[index]"
|
||||||
|
:minLines="featureTerminalMinLines[index]"
|
||||||
|
class="rounded-xl bg-white/80 px-3.5 py-2.5 font-mono text-[13px] leading-relaxed whitespace-pre-wrap text-slate-900 shadow-[inset_0_1px_2px_rgba(0,0,0,0.05)]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-[18px]">
|
||||||
|
<div class="text-center font-semibold text-gray-700">适合的项目类型</div>
|
||||||
|
<div class="grid grid-cols-2 gap-5 max-[1024px]:grid-cols-1">
|
||||||
|
<Card v-for="item in scenarios" :key="item" hoverable class="grid grid-cols-[32px_1fr] items-center gap-3">
|
||||||
|
<div
|
||||||
|
class="grid h-7 w-7 place-items-center rounded-[10px] border border-gray-200 bg-gray-50 text-base text-gray-700 shadow-[inset_0_1px_0_rgba(255,255,255,0.8),0_6px_12px_rgba(0,0,0,0.08)]"
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</div>
|
||||||
|
<p class="m-0 leading-relaxed text-gray-600">{{ item }}</p>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-[18px]">
|
||||||
|
<div class="text-center font-semibold text-gray-700">合作优势</div>
|
||||||
|
<div class="grid grid-cols-3 gap-5 max-[1024px]:grid-cols-2 max-[640px]:grid-cols-1">
|
||||||
|
<Card v-for="stat in stats" :key="stat.value" class="text-center">
|
||||||
|
<div class="text-[38px] font-extrabold tracking-[-0.02em]">{{ stat.value }}</div>
|
||||||
|
<div class="mt-1.5 text-sm text-gray-600">{{ stat.label }}</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card hoverable class="text-center">
|
||||||
|
<div class="text-xl font-bold">想把需求尽快落地?</div>
|
||||||
|
<p class="m-0 text-gray-500">把你的目标、参考链接、时间节点发我,我会给出方案与排期建议。</p>
|
||||||
|
<div class="mt-4 inline-flex gap-3 max-[640px]:w-full max-[640px]:justify-center">
|
||||||
|
<Button @click="router.push('/cooperation')">获取报价</Button>
|
||||||
|
<Button variant="secondary" @click="router.push('/markdown')">查看案例</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import {
|
||||||
|
Boxes,
|
||||||
|
Cloud,
|
||||||
|
Code2,
|
||||||
|
Cpu,
|
||||||
|
Cuboid,
|
||||||
|
LayoutTemplate,
|
||||||
|
Layers,
|
||||||
|
Sparkles,
|
||||||
|
ShieldCheck,
|
||||||
|
} from 'lucide-vue-next'
|
||||||
|
import Button from '@/components/Button/index.vue'
|
||||||
|
import Card from '@/components/Card/index.vue'
|
||||||
|
import TypeItTerminal, { type TypeItTerminalSegment } from '@/components/TypeItTerminal.vue'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
type Feature = {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
icon: any
|
||||||
|
}
|
||||||
|
|
||||||
|
const features: Feature[] = [
|
||||||
|
{ title: '前端工程化', description: 'Vue/React + TypeScript + 构建与规范,让项目可维护、可迭代。', icon: Code2 },
|
||||||
|
{ title: '后台管理', description: '权限、表格、图表、导入导出与业务流程,按习惯做出“顺手”的后台。', icon: LayoutTemplate },
|
||||||
|
{ title: '全栈开发', description: '接口设计、鉴权、数据库建模与联调,打通前后端的交付链路。', icon: Layers },
|
||||||
|
{ title: 'Web3D', description: 'Three.js/Babylon.js 场景搭建、交互、性能优化与业务 UI 联动。', icon: Cuboid },
|
||||||
|
{ title: '官网/落地页', description: '品牌展示 + 动效 + SEO 基础,支持多端适配与上线部署。', icon: Sparkles },
|
||||||
|
{ title: '小程序开发', description: '微信小程序/uni-app(按需),从页面到接口联调与发布。', icon: Boxes },
|
||||||
|
{ title: '性能优化', description: '首屏、图片、缓存、包体与渲染优化,提升体验与转化。', icon: Cpu },
|
||||||
|
{ title: '部署上线', description: 'Docker/服务器部署(按需),支持环境隔离与灰度发布方案。', icon: Cloud },
|
||||||
|
{ title: '质量与安全', description: '边界处理、日志与监控建议,交付稳定可靠的线上版本。', icon: ShieldCheck },
|
||||||
|
]
|
||||||
|
|
||||||
|
const featureTerminalScripts: TypeItTerminalSegment[][] = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">$</span> pnpm create <span class="text-emerald-600">vite</span> my-app</div>',
|
||||||
|
'<div><span class="text-gray-500">$</span> pnpm i <span class="text-amber-600">eslint</span> <span class="text-amber-600">prettier</span></div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> <span class="text-emerald-600">tsconfig</span> + <span class="text-emerald-600">lint</span> ready</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 28,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-blue-600">export</span> <span class="text-blue-600">type</span> Use r = { id: <span class="text-blue-600">string</span>; name: <span class="text-blue-600">string</span> }</div>',
|
||||||
|
'<div><span class="text-blue-600">const</span> state = reactive<{ user?: User }>({})</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 18,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">roles</span> → <span class="text-gray-500">menus</span> → <span class="text-gray-500">actions</span></div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> route guard + permission check</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 22,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-slate-900">columns</span> = [<span class="text-amber-600">"ID"</span>, <span class="text-amber-600">"Name"</span>, <span class="text-amber-600">"Status"</span>]</div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> filter · export · batch action</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 26,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-blue-600">@Get</span>(<span class="text-amber-600">"/users"</span>)</div>',
|
||||||
|
'<div><span class="text-blue-600">async</span> list(@Query() q) { <span class="text-blue-600">return</span> svc.list(q) }</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 18,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">POST /auth/login</span> → <span class="text-emerald-600">JWT</span></div>',
|
||||||
|
'<div><span class="text-gray-500">DB</span>: users · roles · sessions</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 22,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-blue-600">import</span> { Engine, Scene } <span class="text-blue-600">from</span> <span class="text-amber-600">"@babylonjs/core"</span></div>',
|
||||||
|
'<div><span class="text-blue-600">const</span> engine = <span class="text-blue-600">new</span> Engine(canvas)</div>',
|
||||||
|
'<div><span class="text-blue-600">const</span> scene = <span class="text-blue-600">new</span> Scene(engine)</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 20,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">glTF</span> + <span class="text-gray-500">DRACO</span> + <span class="text-gray-500">LOD</span></div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> interaction · hotspot · UI sync</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 24,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500"><title></span>品牌官网<span class="text-gray-500"></title></span></div>',
|
||||||
|
'<div><span class="text-gray-500"><meta</span> name=<span class="text-amber-600">"description"</span> ... <span class="text-gray-500">/></span></div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 26,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">hero</span> · <span class="text-gray-500">features</span> · <span class="text-gray-500">pricing</span> · <span class="text-gray-500">contact</span></div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> responsive · animation · deploy</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 18,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-blue-600">Page</span>({ data })</div>',
|
||||||
|
'<div>onLoad(() => fetchList())</div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> tab · list · detail</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 22,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">subpackages</span> · <span class="text-gray-500">request</span> · <span class="text-gray-500">upload</span></div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> publish checklist</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 18,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">Lighthouse</span>: <span class="text-emerald-600">95+</span></div>',
|
||||||
|
'<div><span class="text-gray-500">-</span> image optimize (webp/avif)</div>',
|
||||||
|
'<div><span class="text-gray-500">-</span> code split + cache</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 16,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 14,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">TTFB</span> ↓ · <span class="text-gray-500">LCP</span> ↓ · <span class="text-gray-500">CLS</span> ↓</div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> perf budget applied</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 20,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-purple-600">services</span>:</div>',
|
||||||
|
'<div> <span class="text-slate-900">web</span>: <span class="text-amber-600">nginx</span></div>',
|
||||||
|
'<div> <span class="text-slate-900">api</span>: <span class="text-amber-600">node</span></div>',
|
||||||
|
'<div> <span class="text-slate-900">db</span>: <span class="text-amber-600">postgres</span></div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 18,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">env</span>: dev · staging · prod</div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> deploy + rollback plan</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 22,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">fix</span>: edge case for empty list</div>',
|
||||||
|
'<div><span class="text-gray-500">test</span>: add regression</div>',
|
||||||
|
'<div><span class="text-gray-500">✓</span> release notes ready</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 20,
|
||||||
|
pauseAfter: 900,
|
||||||
|
deleteAfter: true,
|
||||||
|
deleteSpeed: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
html: [
|
||||||
|
'<div><span class="text-gray-500">v1.2.0</span> · new feature</div>',
|
||||||
|
'<div><span class="text-gray-500">v1.2.1</span> · hotfix</div>',
|
||||||
|
].join(''),
|
||||||
|
speed: 16,
|
||||||
|
pauseAfter: 1200,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
const featureTerminalAlign: Array<'top' | 'center' | 'bottom'> = [
|
||||||
|
'bottom',
|
||||||
|
'top',
|
||||||
|
'bottom',
|
||||||
|
'top',
|
||||||
|
'bottom',
|
||||||
|
'top',
|
||||||
|
'bottom',
|
||||||
|
'top',
|
||||||
|
'center',
|
||||||
|
]
|
||||||
|
|
||||||
|
const featureTerminalMinLines = [4, 3, 3, 4, 3, 3, 4, 4, 3]
|
||||||
|
|
||||||
|
const scenarios = [
|
||||||
|
'品牌官网/产品官网:高还原 + 动效 + SEO 基础',
|
||||||
|
'运营活动页:周期紧、改动频繁、注重转化',
|
||||||
|
'后台管理:权限/表格/导入导出/图表看板',
|
||||||
|
'全栈业务:接口 + 数据库 + 管理后台一体化交付',
|
||||||
|
'Web3D 展示:模型加载、交互热点、镜头与动效',
|
||||||
|
'3D 建模/渲染:建模、材质、灯光与出片(PBR 流程)',
|
||||||
|
'小程序:列表/详情/下单/登录/授权等常见场景',
|
||||||
|
'存量项目:重构、性能优化、修 Bug、继续迭代',
|
||||||
|
'上线交付:部署方案、环境配置、监控建议(按需)',
|
||||||
|
]
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ value: '快', label: '沟通明确后快速推进' },
|
||||||
|
{ value: '稳', label: '注重边界与线上稳定' },
|
||||||
|
{ value: '清晰', label: '按里程碑可验收交付' },
|
||||||
|
{ value: '可维护', label: '工程化与可读结构' },
|
||||||
|
{ value: '可扩展', label: '考虑后续迭代成本' },
|
||||||
|
{ value: '可协作', label: '对接设计/后端/运营' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
118
src/views/MarkdownPage.vue
Normal file
118
src/views/MarkdownPage.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<section class="grid gap-6 pt-3 pb-10">
|
||||||
|
<header class="grid gap-2.5">
|
||||||
|
<h1 class="m-0 text-[28px] font-extrabold tracking-[-0.01em]">案例 / 文章</h1>
|
||||||
|
<p class="m-0 text-gray-600">用 Markdown 维护你的案例、服务说明与技术文章(支持代码复制)。</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
v-for="file in mdFiles"
|
||||||
|
:key="file"
|
||||||
|
:class="[
|
||||||
|
'rounded-full px-4 py-2 text-sm font-semibold transition-colors',
|
||||||
|
currentFile === file ? 'bg-slate-900 text-white' : 'border border-gray-200 bg-white text-gray-600 hover:bg-gray-50',
|
||||||
|
]"
|
||||||
|
@click="loadFile(file)"
|
||||||
|
>
|
||||||
|
{{ file.replace('.md', '') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="htmlContent"
|
||||||
|
ref="contentRef"
|
||||||
|
class="prose prose-slate max-w-none rounded-2xl border border-gray-200 bg-white p-6 shadow-[0_14px_26px_rgba(0,0,0,0.06)]"
|
||||||
|
v-html="htmlContent"
|
||||||
|
/>
|
||||||
|
<div v-else class="rounded-2xl border border-gray-200 bg-gray-50 p-6 text-center text-gray-500">
|
||||||
|
请选择一个文档查看
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { marked } from 'marked'
|
||||||
|
import { nextTick, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
const BASE_URL = import.meta.env.BASE_URL
|
||||||
|
|
||||||
|
// 案例文件列表,后期可在此添加新文件
|
||||||
|
const caseFiles = ['web3d.md', '建模与渲染.md', 'services.md', 'case-template.md', 'contact.md']
|
||||||
|
|
||||||
|
const mdFiles = ref<string[]>([])
|
||||||
|
const currentFile = ref('')
|
||||||
|
const htmlContent = ref('')
|
||||||
|
const contentRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
mdFiles.value = caseFiles
|
||||||
|
if (mdFiles.value.length > 0) {
|
||||||
|
await loadFile(mdFiles.value[0])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const addCopyButtons = () => {
|
||||||
|
if (!contentRef.value) return
|
||||||
|
contentRef.value.querySelectorAll('pre').forEach((pre) => {
|
||||||
|
if (pre.querySelector('.copy-btn')) return
|
||||||
|
const button = document.createElement('button')
|
||||||
|
button.className = 'copy-btn'
|
||||||
|
button.textContent = '复制'
|
||||||
|
button.onclick = async () => {
|
||||||
|
const code = pre.querySelector('code')
|
||||||
|
const text = code?.textContent || ''
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
} catch {
|
||||||
|
const textarea = document.createElement('textarea')
|
||||||
|
textarea.value = text
|
||||||
|
textarea.style.cssText = 'position:fixed;opacity:0'
|
||||||
|
document.body.appendChild(textarea)
|
||||||
|
textarea.select()
|
||||||
|
document.execCommand('copy')
|
||||||
|
document.body.removeChild(textarea)
|
||||||
|
}
|
||||||
|
button.textContent = '已复制'
|
||||||
|
setTimeout(() => (button.textContent = '复制'), 1500)
|
||||||
|
}
|
||||||
|
pre.style.position = 'relative'
|
||||||
|
pre.appendChild(button)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(htmlContent, () => nextTick(addCopyButtons))
|
||||||
|
|
||||||
|
const loadFile = async (filename: string) => {
|
||||||
|
currentFile.value = filename
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${BASE_URL}docs/${filename}`)
|
||||||
|
if (!res.ok) return
|
||||||
|
const content = await res.text()
|
||||||
|
htmlContent.value = marked(content) as string
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载文档失败:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.prose h1 { font-size: 1.875rem; font-weight: 800; margin-bottom: 1rem; }
|
||||||
|
.prose h2 { font-size: 1.5rem; font-weight: 700; margin-top: 1.5rem; margin-bottom: 0.75rem; }
|
||||||
|
.prose h3 { font-size: 1.25rem; font-weight: 600; margin-top: 1.25rem; margin-bottom: 0.5rem; }
|
||||||
|
.prose p { margin-bottom: 1rem; line-height: 1.7; color: #4b5563; }
|
||||||
|
.prose ul, .prose ol { margin-bottom: 1rem; padding-left: 1.5rem; }
|
||||||
|
.prose li { margin-bottom: 0.25rem; color: #4b5563; }
|
||||||
|
.prose code { background: #f1f5f9; padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875rem; }
|
||||||
|
.prose pre { background: #1e293b; color: #e2e8f0; padding: 1rem; padding-top: 2.5rem; border-radius: 0.75rem; overflow-x: auto; margin-bottom: 1rem; position: relative; }
|
||||||
|
.prose pre::before { content: ''; position: absolute; top: 12px; left: 12px; width: 12px; height: 12px; border-radius: 50%; background: #ff5f56; box-shadow: 20px 0 0 #ffbd2e, 40px 0 0 #27c93f; }
|
||||||
|
.prose pre code { background: transparent; padding: 0; }
|
||||||
|
.prose a { color: #2563eb; text-decoration: underline; }
|
||||||
|
.prose blockquote { border-left: 4px solid #e5e7eb; padding-left: 1rem; color: #6b7280; font-style: italic; }
|
||||||
|
.prose img { max-width: 100%; border-radius: 0.75rem; margin: 1rem 0; box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
|
||||||
|
.prose hr { border: none; border-top: 1px solid #e5e7eb; margin: 2rem 0; }
|
||||||
|
.copy-btn { position: absolute; top: 0.5rem; right: 0.5rem; padding: 0.25rem 0.75rem; font-size: 0.75rem; background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2); border-radius: 9999px; color: #e2e8f0; cursor: pointer; transition: background 0.2s; }
|
||||||
|
.copy-btn:hover { background: rgba(255,255,255,0.2); }
|
||||||
|
</style>
|
||||||
127
src/views/PricingPage.vue
Normal file
127
src/views/PricingPage.vue
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<section class="grid gap-10 pt-3 pb-12">
|
||||||
|
<div class="grid gap-2 text-center">
|
||||||
|
<div class="inline-flex flex-wrap justify-center gap-3.5 text-xs text-gray-500">
|
||||||
|
<span>前端开发</span>
|
||||||
|
<span>全栈开发</span>
|
||||||
|
<span>后台管理</span>
|
||||||
|
<span>Web3D(Three/Babylon)</span>
|
||||||
|
<span>3D建模/渲染(PBR)</span>
|
||||||
|
<span>小程序</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-[11px] tracking-[0.3em] text-gray-400">SERVICES</div>
|
||||||
|
<h1 class="m-0 text-[clamp(26px,4vw,36px)] font-extrabold tracking-[-0.02em]">服务与报价</h1>
|
||||||
|
<p class="m-0 mx-auto max-w-[680px] leading-relaxed text-gray-500">
|
||||||
|
按需求评估、按里程碑交付。可从 0 到 1 搭建,也可接手存量项目重构/优化/继续迭代。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<h2 class="m-0 text-lg font-bold">常见合作套餐</h2>
|
||||||
|
<div class="grid grid-cols-3 gap-[18px] max-[1024px]:grid-cols-2 max-[640px]:grid-cols-1">
|
||||||
|
<PlanCard v-for="plan in projectPlans" :key="plan.subtitle" v-bind="plan" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid gap-4">
|
||||||
|
<h2 class="m-0 text-lg font-bold">长期合作</h2>
|
||||||
|
<div class="grid grid-cols-2 gap-[18px] max-[1024px]:grid-cols-1">
|
||||||
|
<PlanCard v-for="plan in retainerPlans" :key="plan.subtitle" v-bind="plan" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 gap-[18px]">
|
||||||
|
<PlanCard v-bind="customPlan" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import PlanCard from '@/components/PlanCard/index.vue'
|
||||||
|
|
||||||
|
type Plan = {
|
||||||
|
title: string
|
||||||
|
subtitle: string
|
||||||
|
badge?: string
|
||||||
|
price: string
|
||||||
|
note?: string
|
||||||
|
features: string[]
|
||||||
|
footerNote: string
|
||||||
|
highlight?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectPlans: Plan[] = [
|
||||||
|
{
|
||||||
|
title: '企业官网 / 落地页',
|
||||||
|
subtitle: '品牌展示 · SEO 基础',
|
||||||
|
badge: '快速交付',
|
||||||
|
price: '¥ 按需求报价',
|
||||||
|
note: '适合活动页、产品官网、品牌介绍、营销落地。',
|
||||||
|
features: ['响应式适配 · 兼容主流浏览器', '高还原 UI + 动效', 'SEO/OG/站点地图(按需)', '表单/埋点/转化链路(按需)'],
|
||||||
|
footerNote: '交付:源码 + 部署说明',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '后台管理系统',
|
||||||
|
subtitle: '权限 · 表格 · 图表',
|
||||||
|
badge: '业务提效',
|
||||||
|
price: '¥ 按需求报价',
|
||||||
|
note: '适合数据看板、运营后台、CRM/ERP 子系统。',
|
||||||
|
features: ['账号/角色/权限模型', '列表/筛选/导出/批处理', '接口联调 + 错误处理', '可维护的组件与路由结构'],
|
||||||
|
footerNote: '交付:可部署版本 + 使用说明',
|
||||||
|
highlight: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Web3D 互动展示',
|
||||||
|
subtitle: 'Three.js / Babylon.js',
|
||||||
|
badge: '更强体验',
|
||||||
|
price: '¥ 按需求报价',
|
||||||
|
note: '适合产品展示、展厅导览、互动营销、3D 组件开发。',
|
||||||
|
features: ['模型加载/压缩(glTF/DRACO)', '交互与镜头控制', '性能优化(LOD/合批/纹理)', '与页面业务联动(UI/数据)'],
|
||||||
|
footerNote: '交付:可复用组件 + 性能建议',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '3D建模与渲染',
|
||||||
|
subtitle: '建模 · 材质 · PBR · 出片',
|
||||||
|
badge: '可出视频',
|
||||||
|
price: '¥ 按需求报价',
|
||||||
|
note: '适合产品/角色/场景资产制作,支持从建模到渲染出片(静帧/视频)。',
|
||||||
|
features: [
|
||||||
|
'高模/低模建模 + 拓扑优化(按需)',
|
||||||
|
'UV 展开与贴图烘焙(Normal/AO 等)',
|
||||||
|
'PBR 材质流程(Substance/Blender 等)',
|
||||||
|
'灯光/渲染/合成与视频输出(按需)',
|
||||||
|
],
|
||||||
|
footerNote: '交付:源文件 + 贴图 + 成片',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const retainerPlans: Plan[] = [
|
||||||
|
{
|
||||||
|
title: '长期迭代(按月)',
|
||||||
|
subtitle: '稳定排期 · 迭代交付',
|
||||||
|
badge: '长期合作',
|
||||||
|
price: '¥ 按月报价',
|
||||||
|
note: '适合长期运营产品、版本迭代、活动频繁的团队。',
|
||||||
|
features: ['每周同步进度与风险', '按里程碑验收与交付', '代码规范/CI/质量把控', '线上问题响应(按约定)'],
|
||||||
|
footerNote: '方式:远程协作 · 里程碑结算',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '全栈开发(按需求)',
|
||||||
|
subtitle: '接口 · 数据库 · 部署',
|
||||||
|
badge: '从 0 到 1',
|
||||||
|
price: '¥ 按需求报价',
|
||||||
|
note: '适合需要后端接口、数据建模、部署上线的一体化项目。',
|
||||||
|
features: ['API 设计与鉴权(JWT/OAuth)', '数据库建模与迁移', '日志/监控/告警(按需)', 'Docker/服务器部署(按需)'],
|
||||||
|
footerNote: '交付:接口文档 + 部署脚本(按需)',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const customPlan: Plan = {
|
||||||
|
title: '定制合作',
|
||||||
|
subtitle: '按需求评估报价',
|
||||||
|
badge: '欢迎咨询',
|
||||||
|
price: '¥ 面议',
|
||||||
|
note: '把你的需求发我:目标、时间、参考风格、已有资料/接口。我会给出排期与报价建议。',
|
||||||
|
features: ['支持接手存量项目:重构/性能优化/修 Bug', '支持小程序开发:微信小程序/uni-app(按需)', '支持设计对接:Figma/蓝湖/Sketch', '可签 NDA/合同与发票(按需)'],
|
||||||
|
footerNote: '微信/邮箱咨询 · 先评估再开工',
|
||||||
|
}
|
||||||
|
</script>
|
||||||
16
tailwind.config.js
Normal file
16
tailwind.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto', 'sans-serif'],
|
||||||
|
mono: ['"SFMono-Regular"', 'Consolas', '"Liberation Mono"', 'Menlo', 'monospace'],
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
subtle: '0 10px 30px rgba(0,0,0,0.04)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
38
tsconfig.json
Normal file
38
tsconfig.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"vite/client"
|
||||||
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
12
tsconfig.node.json
Normal file
12
tsconfig.node.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"vite.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
47
vite.config.ts
Normal file
47
vite.config.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import { resolve } from 'path'
|
||||||
|
|
||||||
|
export default defineConfig(({ mode }) => {
|
||||||
|
const env = loadEnv(mode, process.cwd());
|
||||||
|
|
||||||
|
return {
|
||||||
|
base: env.VITE_PUBLIC || './', //使用环境变量设置资源路径,默认为相对路径
|
||||||
|
|
||||||
|
server: {
|
||||||
|
host: true,
|
||||||
|
port: 8080, //vite项目启动时自定义端口
|
||||||
|
|
||||||
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
// 正则表达式写法
|
||||||
|
'^/api': {
|
||||||
|
target: 'http://192.168.3.13:3000/api', // 后端服务实际地址
|
||||||
|
changeOrigin: true, //开启代理
|
||||||
|
rewrite: (path) => path.replace(/^\/api/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
],
|
||||||
|
|
||||||
|
resolve: {
|
||||||
|
//别名
|
||||||
|
alias: {
|
||||||
|
'@': resolve(__dirname, './src'),
|
||||||
|
components: resolve(__dirname, './src/components'),
|
||||||
|
script: resolve(__dirname, './src/script'),
|
||||||
|
utils: resolve(__dirname, './src/utils'),
|
||||||
|
stores: resolve(__dirname, './src/stores')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
assetsDir: 'static', //打包后的公共文件夹名
|
||||||
|
target: 'es2015',
|
||||||
|
cssTarget: ['chrome61'],
|
||||||
|
chunkSizeWarningLimit: 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user