For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 改造 pay.html 实现管理模式与预览模式,通过 URL 参数切换,支持编辑、图片上传、生成分享链接
Architecture: 单文件实现,保留原有 HTML/CSS/JS,新增模式判定、状态管理、渲染逻辑
Tech Stack: 原生 JavaScript, HTML, CSS, Fetch API, URLSearchParams
| 操作 | 文件 | 说明 |
|---|---|---|
| 修改 | docs/.vuepress/public/pay.html | 主文件,完整改造 |
Files:
Read: docs/.vuepress/public/pay.html
[ ] Step 1: 读取原文件内容
已在对话开始时读取,确认文件包含:
HTML 结构: 导航栏、企业信息、加班管理、套餐列表、支付按钮、评分区
硬编码内容: "郑焕辉"、"广东纽瑞芯"、base64 图片
现有 JS: 套餐选择、评分、支付弹窗、时钟
[ ] Step 2: 确定需要动态化的元素位置
需替换内容:
.company-name.avatar-wrap img.pay-title#modalDescFiles:
Modify: docs/.vuepress/public/pay.html (在 <script> 标签最开头添加)
[ ] Step 1: 添加状态对象与工具函数
在现有 <script> 标签最开始插入:
// 状态管理
const state = {
name: '',
company: '',
image: ''
};
// 默认值(原有内容)
const defaultState = {
name: '郑焕辉',
company: '广东纽瑞芯',
image: 'data:image/jpeg;base64,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'
};
// 从 URL 读取参数
function getParams() {
const params = new URLSearchParams(window.location.search);
return {
name: params.get('name') || '',
company: params.get('company') || '',
image: params.get('image') || ''
};
}
// 判定当前模式
function getMode() {
const params = getParams();
const hasValidParam = params.name || params.company || params.image;
return hasValidParam ? 'preview' : 'admin';
}
用浏览器打开 pay.html,确认控制台无错误,原有功能正常
git add docs/.vuepress/public/pay.html
git commit -m "feat(pay): add state management and mode detection framework"
Files:
Modify: docs/.vuepress/public/pay.html
[ ] Step 1: 添加内容渲染函数
在 Task 2 添加的代码后面继续添加:
// 获取当前生效的值(state 有值用 state,否则用默认值)
function getValue(key) {
return state[key] || defaultState[key];
}
// 渲染页面内容
function renderContent(container = document) {
// 渲染公司名称
const companyEl = container.querySelector('.company-name');
if (companyEl) companyEl.textContent = getValue('company');
// 渲染头像
const avatarImg = container.querySelector('.avatar-wrap img');
if (avatarImg) {
avatarImg.src = getValue('image');
avatarImg.onerror = function() {
this.style.display = 'none';
this.parentElement.style.background = '#d9d9d9';
};
avatarImg.onload = function() {
this.style.display = 'block';
this.parentElement.style.background = 'none';
};
}
// 渲染标题中的姓名
const payTitle = container.querySelector('.pay-title');
if (payTitle) payTitle.textContent = `支付加班费,让${getValue('name')}继续工作`;
// 更新模态框描述(需要修改原有 showPayModal 函数)
// 这里先保存原有函数引用,后面重写
}
// 更新状态并重新渲染
function updateState(key, value) {
state[key] = value;
renderContent();
}
找到原有 showPayModal 函数并替换为:
function showPayModal() {
var plan = plans[selectedIndex];
document.getElementById('modalDesc').textContent =
'您选择了"' + plan.name + '"套餐,' + getValue('name') + '将持续工作至 ' + plan.endTime + '.';
document.getElementById('modalPrice').textContent = plan.price;
document.getElementById('payModal').classList.add('show');
}
在所有 JS 代码最后(原 updateTime(); setInterval... 之后)添加:
// 初始化页面
function init() {
const mode = getMode();
const params = getParams();
if (mode === 'preview') {
// 预览模式:从 URL 读取参数
state.name = params.name;
state.company = params.company;
state.image = params.image;
renderContent();
} else {
// 管理模式:留待 Task 4 实现
}
}
// 启动
init();
在浏览器访问 pay.html?name=张三&company=某某公司,确认:
公司名称显示为"某某公司"
标题显示"支付加班费,让张三继续工作"
原有功能正常(套餐选择、支付弹窗等)
[ ] Step 5: Commit
git add docs/.vuepress/public/pay.html
git commit -m "feat(pay): implement preview mode with URL params"
Files:
Modify: docs/.vuepress/public/pay.html
[ ] Step 1: 添加管理模式 CSS
在 <style> 标签最后添加:
/* 管理模式样式 */
.admin-layout {
display: flex;
min-height: 100vh;
}
.admin-panel {
width: 50%;
min-height: 100vh;
overflow-y: auto;
}
.admin-edit-panel {
background: #f5f7fa;
padding: 24px;
border-right: 1px solid #e8e8e8;
}
.admin-edit-panel h2 {
font-size: 18px;
margin-bottom: 20px;
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
font-size: 14px;
}
.form-input {
width: 100%;
padding: 10px 12px;
border: 1px solid #d9d9d9;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.form-input:focus {
outline: none;
border-color: #1677ff;
}
.file-upload {
border: 2px dashed #d9d9d9;
border-radius: 8px;
padding: 24px;
text-align: center;
background: #fff;
cursor: pointer;
}
.file-upload:hover {
border-color: #1677ff;
}
.file-upload input {
display: none;
}
.upload-hint {
color: #999;
font-size: 13px;
margin-top: 8px;
}
.copy-btn {
width: 100%;
padding: 12px;
background: #52c41a;
color: #fff;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
margin-top: 12px;
transition: opacity 0.2s;
}
.copy-btn:hover {
opacity: 0.9;
}
.copy-btn.copied {
background: #87d068;
}
.upload-status {
margin-top: 8px;
font-size: 13px;
color: #666;
}
.upload-status.error {
color: #ff4d4f;
}
在 JS 中继续添加:
// 保存原始 body HTML(用于预览面板)
let originalBodyHTML = '';
// 渲染管理模式
function renderAdminMode() {
originalBodyHTML = document.body.innerHTML;
document.body.innerHTML = `
<div class="admin-layout">
<div class="admin-panel admin-edit-panel">
<h2>管理面板</h2>
<div class="form-group">
<label class="form-label">姓名</label>
<input type="text" class="form-input" id="input-name" placeholder="请输入姓名" value="${state.name}">
</div>
<div class="form-group">
<label class="form-label">公司名称</label>
<input type="text" class="form-input" id="input-company" placeholder="请输入公司名称" value="${state.company}">
</div>
<div class="form-group">
<label class="form-label">图片上传</label>
<div class="file-upload" id="upload-area">
<span style="color: #1677ff; font-weight: 500;">点击选择图片</span>
<input type="file" id="input-image" accept="image/*">
<div class="upload-hint">支持 jpg、png、gif 格式</div>
<div class="upload-status" id="upload-status"></div>
</div>
</div>
<button class="copy-btn" id="copy-btn">复制链接</button>
</div>
<div class="admin-panel admin-preview-panel" id="preview-panel">
${originalBodyHTML}
</div>
</div>
`;
// 绑定事件
bindAdminEvents();
// 在预览面板中渲染内容
const previewPanel = document.getElementById('preview-panel');
renderContent(previewPanel);
}
// 绑定管理模式事件
function bindAdminEvents() {
// 姓名输入
const inputName = document.getElementById('input-name');
inputName.addEventListener('input', (e) => {
updateState('name', e.target.value);
updatePreview();
});
// 公司输入
const inputCompany = document.getElementById('input-company');
inputCompany.addEventListener('input', (e) => {
updateState('company', e.target.value);
updatePreview();
});
// 图片上传
const uploadArea = document.getElementById('upload-area');
const inputImage = document.getElementById('input-image');
uploadArea.addEventListener('click', () => inputImage.click());
inputImage.addEventListener('change', handleImageUpload);
// 复制链接
const copyBtn = document.getElementById('copy-btn');
copyBtn.addEventListener('click', handleCopyLink);
}
// 更新预览面板
function updatePreview() {
const previewPanel = document.getElementById('preview-panel');
if (!previewPanel) return;
// 重新填充内容
renderContent(previewPanel);
}
找到 init 函数并修改:
// 初始化页面
function init() {
const mode = getMode();
const params = getParams();
if (mode === 'preview') {
// 预览模式:从 URL 读取参数
state.name = params.name;
state.company = params.company;
state.image = params.image;
renderContent();
} else {
// 管理模式
renderAdminMode();
}
}
访问 pay.html(无参数),确认:
显示左右分栏
左侧有姓名、公司、上传控件
右侧显示完整支付页面
[ ] Step 5: Commit
git add docs/.vuepress/public/pay.html
git commit -m "feat(pay): implement admin mode layout"
Files:
Modify: docs/.vuepress/public/pay.html
[ ] Step 1: 添加图片上传处理函数
在 JS 中继续添加:
// 处理图片上传
async function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
const statusEl = document.getElementById('upload-status');
statusEl.textContent = '上传中...';
statusEl.className = 'upload-status';
try {
const formData = new FormData();
formData.append('image', file);
const response = await fetch('https://img.scdn.io/api/v1.php', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error('上传失败: ' + response.status);
}
// 尝试解析响应
let imageUrl = '';
const text = await response.text();
try {
const json = JSON.parse(text);
imageUrl = json.url || json;
} catch {
// 不是 JSON,直接使用响应文本
imageUrl = text;
}
if (!imageUrl || typeof imageUrl !== 'string') {
throw new Error('无法解析图片 URL');
}
// 更新状态
updateState('image', imageUrl);
updatePreview();
statusEl.textContent = '上传成功';
statusEl.className = 'upload-status';
} catch (err) {
console.error('Upload error:', err);
statusEl.textContent = '上传失败: ' + err.message;
statusEl.className = 'upload-status error';
alert('图片上传失败: ' + err.message);
}
}
访问 pay.html,选择图片上传,确认:
显示"上传中..."
成功后显示"上传成功"
右侧预览显示新图片
[ ] Step 3: Commit
git add docs/.vuepress/public/pay.html
git commit -m "feat(pay): implement image upload"
Files:
Modify: docs/.vuepress/public/pay.html
[ ] Step 1: 添加复制链接处理函数
在 JS 中继续添加:
// 处理复制链接
async function handleCopyLink() {
const params = new URLSearchParams();
if (state.name) params.set('name', state.name);
if (state.company) params.set('company', state.company);
if (state.image) params.set('image', state.image);
const baseUrl = window.location.origin + window.location.pathname;
const url = params.toString() ? baseUrl + '?' + params.toString() : baseUrl;
const copyBtn = document.getElementById('copy-btn');
const originalText = copyBtn.textContent;
try {
await navigator.clipboard.writeText(url);
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.classList.remove('copied');
}, 2000);
} catch (err) {
// 剪贴板不可用,降级到 prompt
window.prompt('请手动复制链接:', url);
}
}
访问 pay.html,输入内容后点击"复制链接",确认:
按钮变为"已复制"
2秒后恢复
粘贴链接包含正确参数
[ ] Step 3: Commit
git add docs/.vuepress/public/pay.html
git commit -m "feat(pay): implement copy link function"
Files:
No changes
[ ] Step 1: 完整测试管理模式
访问 pay.html:
输入姓名: "张三" → 右侧实时更新
输入公司: "ABC公司" → 右侧实时更新
上传图片 → 右侧显示新图片
点击复制 → URL 正确
套餐选择、评分、支付弹窗正常
[ ] Step 2: 完整测试预览模式
访问刚才复制的链接:
显示正确的姓名、公司、图片
套餐选择、评分、支付弹窗正常
支付弹窗描述包含正确姓名
[ ] Step 3: 测试默认值回退
访问 pay.html?name=李四,确认未提供的字段使用默认值
?name=) → 管理模式