/* ======================================== 统御IP项目管理 · 前端逻辑(本地存储版) 数据层:浏览器 localStorage 后续可升级为云端同步 ======================================== */ const STORE_PREFIX = 'ty_'; const State = { view: 'dashboard', user: null }; function loadData(key) { try { return JSON.parse(localStorage.getItem(STORE_PREFIX + key) || '[]'); } catch { return []; } } function saveData(key, data) { localStorage.setItem(STORE_PREFIX + key, JSON.stringify(data)); } function loadUser() { try { return JSON.parse(localStorage.getItem(STORE_PREFIX + 'user') || 'null'); } catch { return null; } } function saveUser(u) { localStorage.setItem(STORE_PREFIX + 'user', JSON.stringify(u)); } function getNextId(arr) { return arr.length ? Math.max(...arr.map(x => x.id || 0)) + 1 : 1; } function esc(str) { return String(str || '').replace(//g,'>'); } function showToast(msg) { const t = document.getElementById('toast'); t.textContent = msg; t.classList.add('show'); clearTimeout(t._timer); t._timer = setTimeout(() => t.classList.remove('show'), 2500); } function loadingHtml() { return '
'; } function emptyHtml(title, desc) { return `
${title}
${desc}
`; } function badgeHtml(status) { const m = { pending:{t:'待拍摄',c:'badge-pending'}, progress:{t:'拍摄中',c:'badge-progress'}, done:{t:'已发布',c:'badge-done'} }; const s = m[status] || m.pending; return `${s.t}`; } function formatDate(d) { const dt = new Date(d); return `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`; } const VIEWS = { dashboard:{fab:false}, topics:{fab:'topic'}, scripts:{fab:'script'}, calendar:{fab:'schedule'}, materials:{fab:'material'}, benchmarks:{fab:'benchmark'}, videos:{fab:'video'}, more:{fab:false} }; const TYPE_MAP = { show:'晒过程', teach:'教知识', story:'讲故事', opinion:'说观点', ad:'商业植入', pinned:'置顶故事' }; const MATERIAL_TYPE_MAP = { factory:'工厂实拍', product:'产品展示', founder:'创始人出镜', raw:'原料基地', process:'工艺流程', client:'客户场景' }; // === 初始化默认数据 === function initData() { if (!localStorage.getItem(STORE_PREFIX + 'initialized')) { saveData('topics', [ {id:1,title:'13年药食同源老手的告白:为什么我坚持古法熬制',type:'pinned',status:'pending',elements:'信任,故事,反差',scene:'工厂车间',duration:'75秒',notes:'置顶片,基于冯十三文案结构'}, {id:2,title:'带你逛我们4大国家地理标志原料基地',type:'show',status:'pending',elements:'猎奇,信任',scene:'原料基地',duration:'2分钟',notes:'展示源头优势'}, {id:3,title:'为什么市场上药食同源以次充好的这么多',type:'opinion',status:'pending',elements:'痛点,信任',scene:'办公室',duration:'90秒',notes:'行业洞察'}, {id:4,title:'十万级净化车间到底是什么概念',type:'teach',status:'pending',elements:'科普,信任',scene:'净化车间',duration:'2分钟',notes:'专业度展示'}, {id:5,title:'冰淇淋到草本植物饮料,13年3次转型的真实原因',type:'story',status:'pending',elements:'故事,真实',scene:'工厂',duration:'3分钟',notes:'创始人故事'}, {id:6,title:'古法熬制vs工业勾兑,差别到底在哪',type:'teach',status:'pending',elements:'对比,信任',scene:'车间',duration:'90秒',notes:'工艺对比'}, {id:7,title:'没有高科技,只有笨手艺',type:'opinion',status:'pending',elements:'价值观',scene:'工厂',duration:'60秒',notes:'品牌理念'}, {id:8,title:'从帮人代工到自己建厂,走了什么弯路',type:'story',status:'pending',elements:'真实,故事',scene:'工厂',duration:'3分钟',notes:'创业故事'}, {id:9,title:'B端客户复购率80%的秘密',type:'opinion',status:'pending',elements:'信任,数据',scene:'办公室',duration:'90秒',notes:'信任背书'}, {id:10,title:'一支酸梅汤的工艺全过程',type:'show',status:'pending',elements:'过程,信任',scene:'车间',duration:'2分钟',notes:'工艺展示'}, {id:11,title:'做放心给孩子和家人的养生饮食',type:'opinion',status:'pending',elements:'价值观',scene:'工厂',duration:'60秒',notes:'品牌理念'}, {id:12,title:'7大品牌矩阵是怎么搭起来的',type:'story',status:'pending',elements:'商业,信任',scene:'办公室',duration:'2分钟',notes:'商业实力'}, {id:13,title:'UHT杀菌和传统杀菌的区别',type:'teach',status:'pending',elements:'科普,信任',scene:'车间',duration:'90秒',notes:'技术实力'}, {id:14,title:'双体系认证意味着什么',type:'teach',status:'pending',elements:'科普,信任',scene:'办公室',duration:'60秒',notes:'认证背书'}, {id:15,title:'1000+客户为什么选择统御',type:'opinion',status:'pending',elements:'信任,数据',scene:'办公室',duration:'90秒',notes:'信任背书'} ]); saveData('scripts', [ {id:1,title:'统御置顶故事片',type:'pinned',status:'pending',content:'一个品牌能走多远,看它能给客户解决什么样的问题。13年,从一个人跑原料,到有自己的源头工厂,每一次往前走一步,都是因为发现,还有很多客户的问题,没有被真正解决。\n\n食品这个行业,信任窗口期就是这么短,你骗一个客户一次,这个行业就没有人再信你。统御想解决的问题,不是一箱货发不发得出,是你找到的源头,能不能让你不用再找了。\n\n我们这些年,从冰淇淋,到茶饮,到草本植物饮料,13年3次转型,每一步都是在解决客户新的问题。\n\n为什么一定要自己建工厂,因为B端客户要的,不是便宜,是确定。源头直供,品质稳定,不中间加价,这一定是未来这个行业,最大的一个趋势方向。\n\n今天你找一个品牌合作,不是说我拿一个低价,我是需要我拿到的东西,源头是我自己查得到的,品质是我验得过的,价格是没有中间商赚差价的,它既靠谱又稳定,还要透明,那所以,这就是统御,它永远定义的存在。\n\n而且将会颠覆,很多客户,对食养品牌,对供应链,包括对中间商模式的一个刻板印象。\n\n食品行业的终极,不是一个工厂,是一个场景——客户找到靠谱源头品牌的场景,统御,就是这个场景的名字。\n\n后面的故事,咱们做了再讲,做了再说。',storyboard:'0-10s 创始人正面特写\n10-25s 原料仓库翻看\n25-40s 车间全景+检验产品\n40-55s 和客户交流\n55-65s 工厂远景\n65-75s 回到正面特写'} ]); saveData('materials', [ {id:1,title:'工厂全景巡视',type:'factory',status:'pending',description:'从工厂大门到车间全流程展示'}, {id:2,title:'4大原料基地航拍',type:'raw',status:'pending',description:'国家地理标志原料基地实拍'}, {id:3,title:'古法熬制过程',type:'process',status:'pending',description:'从投料到出品的完整熬制'}, {id:4,title:'UHT杀菌设备',type:'process',status:'pending',description:'超高温瞬时杀菌设备运行'}, {id:5,title:'十万级净化车间',type:'factory',status:'pending',description:'净化车间标准展示'}, {id:6,title:'双体系认证证书',type:'factory',status:'pending',description:'认证证书特写'}, {id:7,title:'7大品牌矩阵展示',type:'product',status:'pending',description:'所有品牌产品集中展示'}, {id:8,title:'创始人日常工作',type:'founder',status:'pending',description:'创始人巡厂/开会/检验'}, {id:9,title:'创始人采访片段',type:'founder',status:'pending',description:'前采100问回答片段'}, {id:10,title:'客户参观工厂',type:'client',status:'pending',description:'B端客户到访交流'}, {id:11,title:'产品质检流程',type:'process',status:'pending',description:'从原料到成品的全链路质检'}, {id:12,title:'发货物流场景',type:'client',status:'pending',description:'成品出库装车'} ]); saveData('benchmarks', [ {id:1,account_name:'也白(IP打造)',url:'https://v.douyin.com/Fo42lpPPhzc/',followers:'2.6万',notes:'前采方法论参考,已产出100问提纲',scripts:'第一视角记录代运营怎么前采'}, {id:2,account_name:'冯瑞—冯啟科中医生活化创始人',url:'https://v.douyin.com/9igPHOcC4Ik/',followers:'1.3万',notes:'高度对标:同行业/同年限/创始人IP。价值观宣言类文案参考',scripts:'一个品牌能走多远,看它能解决什么样的问题,创业14年,从第一家店,到182家店,再到大健康平台,每一次跨越,都是因为发现了,还有很多问题,没有真正的去解决。中医生活化这个赛道,窗口期就是这么几年,冯十三想解决的问题,不是一顿饭吃什么,是怎么吃,能让自己吃的更好,更健康,我们一直从茶饮,到零食,到常规家庭厨房的,所有必需品,我们这么多年,从中医馆的迭代,到我们的茶膳小屋,最后到草本食养记,再到茶养堂,经过了五年的迭代,为什么,需要来摸索和创新,轻养,零食,社区便利,这种新业态,这一定是未来主流的,最大的一个消费方向,今天我们吃的东西,不再说我吃一个常规的土豆片,我是需要我吃了一个常规的土豆片,嚼了一个口香糖,喝了一杯怎么样的汽水,它既健康又平价,还要好吃,那所以,这就是冯十三,它永远定义的存在,而且呢将会颠覆,很多未来,大家对零食店,对便利店,包括对烘焙店的一个刻板印象,中医生活化的终极,不是一个产品,是一个场景,冯十三,就是这个场景的名字,后面的故事,咱们做了再讲,做了再说'} ]); saveData('videos', []); saveData('kpi', []); saveData('schedule', []); localStorage.setItem(STORE_PREFIX + 'initialized', '1'); } } // === 路由 === function router() { const hash = location.hash.slice(1) || 'dashboard'; State.view = hash; document.querySelectorAll('[data-view]').forEach(el => el.classList.toggle('active', el.dataset.view === hash)); const cfg = VIEWS[hash]; const fab = document.getElementById('fab'); if (cfg && cfg.fab) { fab.classList.remove('hidden'); fab.dataset.modal = cfg.fab; } else fab.classList.add('hidden'); renderView(hash); } async function renderView(view) { const container = document.getElementById('view-container'); if (!VIEWS[view]) { container.innerHTML = emptyHtml('页面不存在', ''); return; } container.innerHTML = loadingHtml(); const renderers = { dashboard: renderDashboard, topics: renderTopics, scripts: renderScripts, calendar: renderCalendar, materials: renderMaterials, benchmarks: renderBenchmarks, videos: renderVideos, more: renderMore }; const renderer = renderers[view]; if (renderer) renderer(container); else container.innerHTML = emptyHtml('页面不存在', ''); } // === 仪表盘 === function renderDashboard(container) { const kpi = loadData('kpi'); const schedule = loadData('schedule').filter(s => { const now = new Date(); const sDate = new Date(s.date); const diff = (sDate - now) / (1000*60*60*24); return diff >= -1 && diff <= 7; }); const topics = loadData('topics'); const stats = { pending:0, progress:0, done:0 }; topics.forEach(t => { if (stats[t.status] !== undefined) stats[t.status]++; }); const latest = kpi[kpi.length-1] || {}; const prev = kpi[kpi.length-2] || {}; const fanTrend = latest.followers && prev.followers ? latest.followers - prev.followers : 0; const now = new Date(); container.innerHTML = `
${latest.followers || 0}
精准粉丝
${fanTrend ? `
${fanTrend>0?'+':''}${fanTrend}
` : ''}
${latest.consultations || 0}
本月咨询
${latest.visits || 0}
到访客户
${latest.contracts || 0}
签约客户
本周排期
${schedule.length ? schedule.map(s => `
${esc(s.title)||'未命名'}
${s.date}${badgeHtml(s.status)}
`).join('') : emptyHtml('本周暂无排期','点击排期页面添加')}
选题状态
待拍摄
${stats.pending}
拍摄中
${stats.progress}
已发布
${stats.done}
`; } // === 选题库 === function renderTopics(container) { const topics = loadData('topics'); container.innerHTML = `
${topics.map((t,i)=>`
${i+1}
${esc(t.title)}
${TYPE_MAP[t.type]||t.type}${t.elements?t.elements.split(',').map(e=>`${esc(e.trim())}`).join(''):''}
${badgeHtml(t.status)}
`).join('')}
${topics.length===0?emptyHtml('暂无选题','点击右下角 + 添加'):''}
`; } // === 脚本管理 === function renderScripts(container) { const scripts = loadData('scripts'); container.innerHTML = `
${scripts.map(s=>`
${esc(s.title)}
${TYPE_MAP[s.type]||s.type}${badgeHtml(s.status)}
${esc((s.content||'').slice(0,120))}${s.content&&s.content.length>120?'...':''}
`).join('')} ${scripts.length===0?emptyHtml('暂无脚本','点击右下角 + 添加'):''}
`; } // === 日历排期 === function renderCalendar(container) { const schedule = loadData('schedule'); const now = new Date(); const year = now.getFullYear(); const month = now.getMonth(); const firstDay = new Date(year,month,1).getDay(); const daysInMonth = new Date(year,month+1,0).getDate(); const today = now.getDate(); const tasks = {}; schedule.forEach(s => { const d = new Date(s.date); if (d.getMonth()===month && d.getFullYear()===year) { const day = d.getDate(); if (!tasks[day]) tasks[day] = []; tasks[day].push(s); } }); const weekDays = ['日','一','二','三','四','五','六']; let calHtml = '
'; weekDays.forEach(d => calHtml += `
${d}
`); for (let i=0; i${d}
`; } calHtml += ''; let listHtml = ''; Object.entries(tasks).sort((a,b)=>a[0]-b[0]).forEach(([day,items]) => { items.forEach(s => { listHtml += `
${esc(s.title)||'未命名'}
${s.date}${badgeHtml(s.status)}
`; }); }); container.innerHTML = `
${calHtml}
本月排期
${listHtml || emptyHtml('本月暂无排期','点击日历日期添加')}
`; } // === IP素材库 === function renderMaterials(container) { const materials = loadData('materials'); container.innerHTML = `
${materials.map(m => `
${esc(m.title)}
${esc(m.description)||''}
${MATERIAL_TYPE_MAP[m.type]||m.type}
`).join('')} ${materials.length===0?emptyHtml('暂无素材','点击右下角 + 添加'):''}
`; } function toggleMaterial(id) { const materials = loadData('materials'); const m = materials.find(x => x.id === id); if (!m) return; m.status = m.status === 'done' ? 'pending' : 'done'; saveData('materials', materials); showToast(m.status === 'done' ? '已完成' : '已标记为待拍摄'); renderView('materials'); } // === 对标账号 === function renderBenchmarks(container) { const benchmarks = loadData('benchmarks'); container.innerHTML = `
${benchmarks.map(b => `
${esc(b.account_name)}
粉丝 ${esc(b.followers)||'未知'} | ${esc(b.notes)||''}
${b.scripts?`
${esc(b.scripts.slice(0,150))}${b.scripts.length>150?'...':''}
`:''}${b.url?`在抖音查看 →`:''}
`).join('')} ${benchmarks.length===0?emptyHtml('暂无对标账号','点击右下角 + 添加'):''}
`; } // === 发布视频 === function renderVideos(container) { const videos = loadData('videos'); container.innerHTML = `
${videos.map(v => `
${esc(v.title)}
${v.publish_date||''}${v.views?`播放 ${esc(v.views)}`:''}${v.likes?`赞 ${esc(v.likes)}`:''}
在抖音观看 →
`).join('')} ${videos.length===0?emptyHtml('暂无发布视频','点击右下角 + 添加视频链接'):''}
`; } // === 更多 === function renderMore(container) { const items = [{view:'materials',label:'IP素材库'},{view:'benchmarks',label:'对标账号'},{view:'videos',label:'发布视频'}]; container.innerHTML = `
${items.map(i => `
${i.label}
`).join('')}
退出登录
`; } // === 弹窗配置 === const MODAL_CONFIG = { topic: { title:'选题', fields:[ {key:'title',label:'标题',type:'input',placeholder:'选题标题'}, {key:'type',label:'类型',type:'select',options:[['show','晒过程'],['teach','教知识'],['story','讲故事'],['opinion','说观点'],['ad','商业植入'],['pinned','置顶故事']]}, {key:'status',label:'状态',type:'select',options:[['pending','待拍摄'],['progress','拍摄中'],['done','已发布']]}, {key:'elements',label:'爆款元素',type:'input',placeholder:'如:猎奇,反差'}, {key:'scene',label:'拍摄场景',type:'input',placeholder:'如:工厂/车间'}, {key:'duration',label:'时长',type:'input',placeholder:'如:4分钟'}, {key:'notes',label:'备注',type:'textarea',placeholder:'补充说明'} ]}, script: { title:'脚本', fields:[ {key:'title',label:'标题',type:'input',placeholder:'脚本标题'}, {key:'type',label:'类型',type:'select',options:[['show','晒过程'],['teach','教知识'],['story','讲故事'],['opinion','说观点'],['ad','商业植入'],['pinned','置顶故事']]}, {key:'status',label:'状态',type:'select',options:[['pending','待拍摄'],['progress','拍摄中'],['done','已拍摄']]}, {key:'content',label:'脚本内容',type:'textarea',placeholder:'口播文案',rows:8}, {key:'storyboard',label:'分镜备注',type:'textarea',placeholder:'分镜建议'} ]}, schedule: { title:'排期', fields:[ {key:'date',label:'日期',type:'date'}, {key:'title',label:'标题',type:'input',placeholder:'排期标题'}, {key:'status',label:'状态',type:'select',options:[['pending','待拍摄'],['progress','拍摄中'],['done','已发布']]}, {key:'notes',label:'备注',type:'textarea',placeholder:'补充说明'} ]}, material: { title:'素材', fields:[ {key:'title',label:'标题',type:'input',placeholder:'如:工厂全景巡检'}, {key:'type',label:'类型',type:'select',options:[['factory','工厂实拍'],['product','产品展示'],['founder','创始人出镜'],['raw','原料基地'],['process','工艺流程'],['client','客户场景']]}, {key:'status',label:'状态',type:'select',options:[['pending','待拍摄'],['done','已拍摄']]}, {key:'description',label:'描述',type:'textarea',placeholder:'素材说明'} ]}, benchmark: { title:'对标账号', fields:[ {key:'account_name',label:'账号名称',type:'input',placeholder:'如:冯瑞—冯十三'}, {key:'url',label:'抖音链接',type:'input',placeholder:'https://v.douyin.com/...'}, {key:'followers',label:'粉丝数',type:'input',placeholder:'如:1.3万'}, {key:'notes',label:'参考价值',type:'input',placeholder:'对标说明'}, {key:'scripts',label:'文案提取',type:'textarea',placeholder:'提取的对标文案',rows:6} ]}, video: { title:'视频', fields:[ {key:'title',label:'标题',type:'input',placeholder:'视频标题'}, {key:'url',label:'抖音链接',type:'input',placeholder:'https://v.douyin.com/...'}, {key:'publish_date',label:'发布日期',type:'date'}, {key:'views',label:'播放量',type:'input',placeholder:'如:5000'}, {key:'likes',label:'点赞数',type:'input',placeholder:'如:200'} ]}, kpi: { title:'KPI数据', fields:[ {key:'date',label:'日期',type:'date'}, {key:'followers',label:'精准粉丝数',type:'input',inputType:'number',placeholder:'如:3000'}, {key:'consultations',label:'本月咨询量',type:'input',inputType:'number',placeholder:'如:15'}, {key:'visits',label:'到访客户数',type:'input',inputType:'number',placeholder:'如:3'}, {key:'contracts',label:'签约客户数',type:'input',inputType:'number',placeholder:'如:1'} ]} }; const MODAL_STORE = { topic:'topics', script:'scripts', schedule:'schedule', material:'materials', benchmark:'benchmarks', video:'videos', kpi:'kpi' }; function openModal(type, id=null, date=null) { const cfg = MODAL_CONFIG[type]; if (!cfg) return; const storeKey = MODAL_STORE[type]; const isEdit = !!id; const title = isEdit ? '编辑'+cfg.title : '新建'+cfg.title; const fieldsHtml = cfg.fields.map(f => { const fid = 'mf-'+f.key; if (f.type === 'select') return `
`; if (f.type === 'textarea') return `
`; return `
`; }).join(''); document.getElementById('modal-container').innerHTML = ``; document.getElementById('modal-form').addEventListener('submit', e => { e.preventDefault(); saveModalItem(storeKey, id, cfg.fields); }); if (date) { const el = document.getElementById('mf-date'); if (el) el.value = date; } if (isEdit) loadModalData(storeKey, id, cfg.fields); } function closeModal() { document.getElementById('modal-container').innerHTML = ''; } function loadModalData(storeKey, id, fields) { const items = loadData(storeKey); const item = items.find(x => x.id == id); if (!item) return; fields.forEach(f => { const el = document.getElementById('mf-'+f.key); if (el && item[f.key] !== undefined) el.value = item[f.key]; }); } function saveModalItem(storeKey, id, fields) { const data = {}; fields.forEach(f => { const el = document.getElementById('mf-'+f.key); if (el) { let val = el.value; if (f.inputType === 'number') val = val ? parseInt(val) : null; data[f.key] = val; } }); const items = loadData(storeKey); if (id) { const idx = items.findIndex(x => x.id == id); if (idx !== -1) { items[idx] = { ...items[idx], ...data }; } } else { data.id = getNextId(items); items.push(data); } saveData(storeKey, items); closeModal(); renderView(State.view); showToast(id ? '已保存' : '已创建'); } function deleteItem(storeKey, id) { if (!confirm('确认删除?')) return; const items = loadData(storeKey).filter(x => x.id != id); saveData(storeKey, items); closeModal(); renderView(State.view); showToast('已删除'); } // === 登录 === function doLogout() { localStorage.removeItem(STORE_PREFIX + 'user'); State.user = null; showLogin(); } function showLogin() { document.getElementById('login-screen').classList.remove('hidden'); document.getElementById('app').classList.add('hidden'); } function showApp() { document.getElementById('login-screen').classList.add('hidden'); document.getElementById('app').classList.remove('hidden'); router(); } // === 初始化 === initData(); document.getElementById('login-form').addEventListener('submit', e => { e.preventDefault(); const email = document.getElementById('login-email').value.trim(); const password = document.getElementById('login-password').value; const errEl = document.getElementById('login-error'); errEl.textContent = ''; if (!email || !password) { errEl.textContent = '请输入邮箱和密码'; return; } State.user = { email }; saveUser(State.user); showApp(); }); document.getElementById('fab').addEventListener('click', () => { const f = document.getElementById('fab'); if (f.dataset.modal) openModal(f.dataset.modal); }); window.addEventListener('hashchange', router); if ('serviceWorker' in navigator) navigator.serviceWorker.register('/sw.js').catch(()=>{}); State.user = loadUser(); if (State.user) showApp(); else showLogin();