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