759 lines
45 KiB
HTML
759 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN" data-theme="light">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=1280" />
|
||
<title>Fonrey 房源管理 · 新增房源(任务02)</title>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script>
|
||
tailwind.config = {
|
||
theme: {
|
||
extend: {
|
||
colors: {
|
||
primary: { 50:'#F0FDFA', 100:'#CCFBF1', 200:'#99F6E4', 500:'#14B8A6', 600:'#0F766E', 700:'#115E59', 800:'#134E4A' },
|
||
neutral: { 50:'#F8FAFC',100:'#F1F5F9',200:'#E2E8F0',300:'#CBD5E1',400:'#94A3B8',500:'#64748B',600:'#475569',700:'#334155',800:'#1E293B',900:'#0F172A' },
|
||
success: { 50:'#F0FDF4', 600:'#16A34A' },
|
||
warning: { 50:'#FFFBEB', 600:'#D97706' },
|
||
danger: { 50:'#FEF2F2', 600:'#DC2626' },
|
||
info: { 50:'#EFF6FF', 600:'#2563EB' }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
<style>
|
||
:root {
|
||
--bg-page:#F8FAFC; --bg-panel:#FFF; --bg-subtle:#F1F5F9; --text-main:#1E293B; --text-sub:#64748B;
|
||
--border:#E2E8F0; --input-bg:#FFF; --input-text:#1E293B; --header-bg:#FFF;
|
||
}
|
||
[data-theme="dark"] {
|
||
--bg-page:#0F172A; --bg-panel:#1E293B; --bg-subtle:#334155; --text-main:#E2E8F0; --text-sub:#94A3B8;
|
||
--border:#334155; --input-bg:#0F172A; --input-text:#E2E8F0; --header-bg:#0F172A;
|
||
}
|
||
[data-theme="system"] {
|
||
--bg-page:#F8FAFC; --bg-panel:#FFF; --bg-subtle:#F1F5F9; --text-main:#1E293B; --text-sub:#64748B;
|
||
--border:#E2E8F0; --input-bg:#FFF; --input-text:#1E293B; --header-bg:#FFF;
|
||
}
|
||
@media (prefers-color-scheme: dark) {
|
||
[data-theme="system"] {
|
||
--bg-page:#0F172A; --bg-panel:#1E293B; --bg-subtle:#334155; --text-main:#E2E8F0; --text-sub:#94A3B8;
|
||
--border:#334155; --input-bg:#0F172A; --input-text:#E2E8F0; --header-bg:#0F172A;
|
||
}
|
||
}
|
||
|
||
body { background:var(--bg-page); color:var(--text-main); transition:all .2s ease; }
|
||
.page-header { background:var(--header-bg); border-bottom:1px solid var(--border); backdrop-filter:blur(8px); }
|
||
.panel { background:var(--bg-panel); border:1px solid var(--border); }
|
||
.subtle { background:var(--bg-subtle); border:1px solid var(--border); }
|
||
.text-main { color:var(--text-main); }
|
||
.text-sub { color:var(--text-sub); }
|
||
|
||
.input { background:var(--input-bg); color:var(--input-text); border:1px solid var(--border); }
|
||
.input::placeholder { color:#94A3B8; }
|
||
.input:focus { outline:none; border-color:#0F766E; box-shadow:0 0 0 2px rgba(15,118,110,.2); }
|
||
.input.error { border-color:#DC2626!important; box-shadow:0 0 0 2px rgba(220,38,38,.14)!important; }
|
||
|
||
.seg { border:1px solid var(--border); background:var(--bg-panel); }
|
||
.seg-btn { color:var(--text-sub); border:1px solid transparent; }
|
||
.seg-btn.active { background:#0F766E; border-color:#0F766E; color:#FFF; }
|
||
|
||
.chip { border:1px solid var(--border); background:var(--bg-panel); color:var(--text-sub); }
|
||
.chip.active { border-color:#0F766E; color:#0F766E; background:#F0FDFA; }
|
||
[data-theme="dark"] .chip.active, [data-theme="system"] .chip.active { background:rgba(20,184,166,.12); }
|
||
|
||
.anchor-link { color:var(--text-sub); border-bottom:2px solid transparent; }
|
||
.anchor-link.active { color:#0F766E; border-bottom-color:#0F766E; font-weight:600; }
|
||
|
||
.error-msg { min-height:18px; font-size:12px; color:#DC2626; }
|
||
.toast-in { animation:toastIn .2s ease-out; }
|
||
@keyframes toastIn { from{opacity:0;transform:translateY(-8px)} to{opacity:1;transform:translateY(0)} }
|
||
</style>
|
||
</head>
|
||
<body class="antialiased">
|
||
<header class="fixed top-0 left-0 right-0 h-14 z-20 bg-primary-800 flex items-center justify-between">
|
||
<div class="flex items-center gap-2 px-4 w-60 shrink-0">
|
||
<div class="w-7 h-7 rounded-md bg-primary-500 flex items-center justify-center text-white text-sm font-semibold">F</div>
|
||
<span class="text-base font-semibold text-white">Fonrey</span>
|
||
</div>
|
||
<nav class="flex items-center gap-1 flex-1 px-2" aria-label="主导航">
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">工作台</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md bg-primary-600 text-white font-medium">房源</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">客源</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">营销</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">交易</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">数据</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">人事</a>
|
||
<a class="px-3 py-1.5 text-sm rounded-md text-primary-100 hover:bg-primary-700 hover:text-white">系统</a>
|
||
</nav>
|
||
<div class="flex items-center gap-1 px-4 shrink-0">
|
||
<button class="p-1.5 text-primary-200 hover:bg-primary-700 hover:text-white rounded-md" aria-label="消息">
|
||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.8" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022 23.848 23.848 0 0 0 5.454 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"/></svg>
|
||
</button>
|
||
<div class="flex items-center gap-2 pl-3 ml-1 border-l border-primary-700">
|
||
<div class="w-8 h-8 rounded-full bg-primary-600 text-white flex items-center justify-center text-sm font-semibold">魏</div>
|
||
<span class="text-sm font-medium text-primary-100">魏深</span>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<aside class="fixed left-0 top-14 h-[calc(100vh-56px)] w-60 z-20 border-r border-neutral-200 bg-white">
|
||
<nav class="p-3 space-y-0.5">
|
||
<div class="px-2 pt-2 pb-1 text-xs font-medium text-neutral-500 uppercase tracking-wide">房源管理</div>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md bg-primary-50 text-primary-700 font-medium">全部房源</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">我的房源</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">公海池</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">成交房源</a>
|
||
<a class="flex items-center gap-2 px-2 py-1.5 rounded-md text-neutral-700 hover:bg-neutral-100">已删房源</a>
|
||
</nav>
|
||
</aside>
|
||
|
||
<main class="ml-60 pt-[72px] min-h-screen px-6 py-5">
|
||
<div class="mx-auto max-w-[1240px] space-y-4">
|
||
<div class="panel rounded-lg p-4">
|
||
<div class="flex items-start justify-between gap-4 flex-wrap">
|
||
<div>
|
||
<nav class="flex items-center gap-1 text-xs text-sub mb-2" aria-label="面包屑">
|
||
<a href="javascript:void(0)" class="hover:text-neutral-700">房源</a><span>/</span>
|
||
<a href="./房源列表_UI.html" class="hover:text-neutral-700">二手/租赁</a><span>/</span>
|
||
<span class="text-main font-medium">新增房源</span>
|
||
</nav>
|
||
<h1 class="text-xl font-semibold text-main">新增房源</h1>
|
||
</div>
|
||
<div class="flex items-center gap-3">
|
||
<a href="./房源列表_UI.html" class="inline-flex items-center px-4 py-2 text-sm font-medium bg-white border border-neutral-300 text-neutral-700 rounded-md hover:bg-neutral-50 hover:border-neutral-400">返回列表</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<section class="panel rounded-lg p-4">
|
||
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||
<div>
|
||
<div class="text-xs text-sub mb-2">房源类型</div>
|
||
<div id="propertyTypeGroup" class="flex items-center gap-2 flex-wrap">
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-type="residential">住宅 <span class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-neutral-100 text-neutral-500">P0</span></button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-type="villa">别墅 <span class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-neutral-100 text-neutral-500">P1</span></button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-type="commercial_residential">商住 <span class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-neutral-100 text-neutral-500">P2</span></button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-type="shop">商铺 <span class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-neutral-100 text-neutral-500">P2</span></button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-type="office">写字楼 <span class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-neutral-100 text-neutral-500">P2</span></button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-type="other">其他 <span class="ml-1 text-[10px] px-1.5 py-0.5 rounded bg-neutral-100 text-neutral-500">P2</span></button>
|
||
</div>
|
||
<p class="mt-2 text-xs text-sub">当前类型:<span id="currentTypeLabel" class="font-medium text-main">住宅</span> <span id="p2Badge" class="ml-2 hidden inline-flex items-center px-2 py-0.5 rounded bg-warning-50 text-warning-600 border border-warning-600/20">v2 预留类型</span></p>
|
||
</div>
|
||
<div class="subtle rounded-lg px-3 py-2 text-xs text-sub max-w-xl">
|
||
<div class="font-medium text-main mb-1">验收重点(US-PROPERTY-001)</div>
|
||
<ul class="list-disc pl-4 space-y-1">
|
||
<li>必填缺失时红色提示并定位</li>
|
||
<li>状态与价格字段联动(出售/出租/租售/暂缓)</li>
|
||
<li>保存成功给出反馈(原型中模拟详情跳转)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<nav class="panel rounded-lg px-4 sticky top-[74px] z-30">
|
||
<div class="flex items-center gap-5 overflow-x-auto text-sm">
|
||
<a href="#section-core" class="anchor-link py-3" data-anchor-link="core">房源核心信息</a>
|
||
<a href="#section-contact" class="anchor-link py-3" data-anchor-link="contact">业主/联系人</a>
|
||
<a href="#section-basic" class="anchor-link py-3" data-anchor-link="basic">基础信息</a>
|
||
<a href="#section-trade" class="anchor-link py-3" data-anchor-link="trade" id="tradeAnchor">交易信息</a>
|
||
<a href="#section-related" class="anchor-link py-3" data-anchor-link="related">相关方</a>
|
||
</div>
|
||
</nav>
|
||
|
||
<form id="propertyForm" class="space-y-4">
|
||
<section id="section-core" class="panel rounded-lg p-5 space-y-4" data-section-key="core">
|
||
<h2 class="text-base font-semibold text-main">房源核心信息</h2>
|
||
|
||
<div class="grid grid-cols-12 gap-4">
|
||
<div class="col-span-6" data-field="status">
|
||
<label class="block text-sm font-medium text-main mb-1.5">状态 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center gap-2 flex-wrap" id="statusGroup">
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-status="for_sale">出售</button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-status="for_rent">出租</button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-status="for_sale_rent">租售</button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-status="suspended">暂缓</button>
|
||
</div>
|
||
<p class="error-msg" data-error="status"></p>
|
||
</div>
|
||
|
||
<div class="col-span-6" data-field="attribute">
|
||
<label class="block text-sm font-medium text-main mb-1.5">房源属性 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center gap-2" id="attributeGroup">
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-attr="public">公盘</button>
|
||
<button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-attr="private">私盘</button>
|
||
</div>
|
||
<p class="error-msg" data-error="attribute"></p>
|
||
</div>
|
||
|
||
<div class="col-span-6" data-field="usage">
|
||
<label class="block text-sm font-medium text-main mb-1.5">用途</label>
|
||
<div class="flex items-center gap-2 flex-wrap" id="usageGroup"></div>
|
||
<p id="usageHint" class="text-xs text-sub rounded-md subtle px-2 py-1.5 inline-block hidden">当前类型用途选项待补充(按 PRD 预留)</p>
|
||
</div>
|
||
|
||
<div class="col-span-6" data-field="complexName">
|
||
<label class="block text-sm font-medium text-main mb-1.5">小区名称 <span class="text-danger-600">*</span></label>
|
||
<input id="complexName" type="text" class="input w-full px-3 py-2 rounded-md text-sm" placeholder="请输入小区名称(联想搜索)" data-error-input="complexName" />
|
||
<p class="error-msg" data-error="complexName"></p>
|
||
</div>
|
||
|
||
<div class="col-span-12">
|
||
<label class="block text-sm font-medium text-main mb-1.5">户室号</label>
|
||
<div class="grid grid-cols-3 gap-3">
|
||
<input id="blockNo" type="text" class="input w-full px-3 py-2 rounded-md text-sm" placeholder="栋/幢/弄/胡同" />
|
||
<input id="unitNo" type="text" class="input w-full px-3 py-2 rounded-md text-sm" placeholder="单元/号" />
|
||
<input id="roomNo" type="text" class="input w-full px-3 py-2 rounded-md text-sm" placeholder="门牌/室号" />
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-span-6" data-field="floors">
|
||
<label class="block text-sm font-medium text-main mb-1.5">所在楼层 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center gap-2">
|
||
<input id="floor" type="number" min="1" class="input w-28 px-3 py-2 rounded-md text-sm" placeholder="请输入" data-error-input="floors" />
|
||
<span class="text-sub">楼,共</span>
|
||
<input id="totalFloors" type="number" min="1" class="input w-28 px-3 py-2 rounded-md text-sm" placeholder="请输入" data-error-input="floors" />
|
||
<span class="text-sub">层</span>
|
||
</div>
|
||
<p class="error-msg" data-error="floors"></p>
|
||
</div>
|
||
|
||
<div class="col-span-6" data-field="area">
|
||
<label class="block text-sm font-medium text-main mb-1.5">建筑面积 <span class="text-danger-600">*</span></label>
|
||
<div class="relative">
|
||
<input id="area" type="number" min="0" step="0.01" class="input w-full px-3 py-2 rounded-md text-sm pr-12" placeholder="请输入" data-error-input="area" />
|
||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-sub text-sm">m²</span>
|
||
</div>
|
||
<p class="error-msg" data-error="area"></p>
|
||
</div>
|
||
|
||
<div class="col-span-12" id="layoutWrap" data-field="layout">
|
||
<label class="block text-sm font-medium text-main mb-1.5">户型 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center flex-wrap gap-2">
|
||
<select id="bedroom" class="input px-3 py-2 rounded-md text-sm" data-error-input="layout"><option value="">室</option></select>
|
||
<select id="living" class="input px-3 py-2 rounded-md text-sm" data-error-input="layout"><option value="">厅</option></select>
|
||
<select id="bathroom" class="input px-3 py-2 rounded-md text-sm" data-error-input="layout"><option value="">卫</option></select>
|
||
<select id="kitchen" class="input px-3 py-2 rounded-md text-sm" data-error-input="layout"><option value="">厨</option></select>
|
||
<select id="balcony" class="input px-3 py-2 rounded-md text-sm" data-error-input="layout"><option value="">阳台</option></select>
|
||
</div>
|
||
<p class="error-msg" data-error="layout"></p>
|
||
</div>
|
||
|
||
<div id="shopFields" class="col-span-12 hidden">
|
||
<div class="grid grid-cols-12 gap-4">
|
||
<div class="col-span-4" data-field="shopFrontage">
|
||
<label class="block text-sm font-medium text-main mb-1.5">开间 <span class="text-danger-600">*</span></label>
|
||
<div class="relative"><input id="shopFrontage" type="number" min="0" step="0.01" class="input w-full px-3 py-2 rounded-md text-sm pr-10" placeholder="请输入" data-error-input="shopFrontage" /><span class="absolute right-3 top-1/2 -translate-y-1/2 text-sub text-sm">米</span></div>
|
||
<p class="error-msg" data-error="shopFrontage"></p>
|
||
</div>
|
||
<div class="col-span-4" data-field="shopDepth">
|
||
<label class="block text-sm font-medium text-main mb-1.5">进深 <span class="text-danger-600">*</span></label>
|
||
<div class="relative"><input id="shopDepth" type="number" min="0" step="0.01" class="input w-full px-3 py-2 rounded-md text-sm pr-10" placeholder="请输入" data-error-input="shopDepth" /><span class="absolute right-3 top-1/2 -translate-y-1/2 text-sub text-sm">米</span></div>
|
||
<p class="error-msg" data-error="shopDepth"></p>
|
||
</div>
|
||
<div class="col-span-4" data-field="shopHeight">
|
||
<label class="block text-sm font-medium text-main mb-1.5">层高 <span class="text-danger-600">*</span></label>
|
||
<div class="relative"><input id="shopHeight" type="number" min="0" step="0.01" class="input w-full px-3 py-2 rounded-md text-sm pr-10" placeholder="请输入" data-error-input="shopHeight" /><span class="absolute right-3 top-1/2 -translate-y-1/2 text-sub text-sm">米</span></div>
|
||
<p class="error-msg" data-error="shopHeight"></p>
|
||
</div>
|
||
<div class="col-span-12" data-field="shopLocation">
|
||
<label class="block text-sm font-medium text-main mb-1.5">位置 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center gap-2 flex-wrap" id="shopLocationGroup"></div>
|
||
<p class="error-msg" data-error="shopLocation"></p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-span-6" id="salePriceWrap" data-field="salePrice">
|
||
<label class="block text-sm font-medium text-main mb-1.5">售价 <span class="text-danger-600">*</span></label>
|
||
<div class="relative"><input id="salePrice" type="number" min="0" step="0.01" class="input w-full px-3 py-2 rounded-md text-sm pr-10" placeholder="请输入" data-error-input="salePrice" /><span class="absolute right-3 top-1/2 -translate-y-1/2 text-sub text-sm">万</span></div>
|
||
<p class="error-msg" data-error="salePrice"></p>
|
||
</div>
|
||
|
||
<div class="col-span-6 hidden" id="rentPriceWrap" data-field="rentPrice">
|
||
<label class="block text-sm font-medium text-main mb-1.5">租价 <span class="text-danger-600">*</span></label>
|
||
<div class="relative"><input id="rentPrice" type="number" min="0" step="1" class="input w-full px-3 py-2 rounded-md text-sm pr-16" placeholder="请输入" data-error-input="rentPrice" /><span class="absolute right-3 top-1/2 -translate-y-1/2 text-sub text-sm">元/月</span></div>
|
||
<p class="error-msg" data-error="rentPrice"></p>
|
||
</div>
|
||
|
||
<div id="suspendHint" class="col-span-12 hidden"><p class="text-xs text-warning-600 bg-warning-50 border border-warning-600/20 rounded-md px-3 py-2">当前状态为“暂缓”,售价/租价字段已隐藏。</p></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="section-contact" class="panel rounded-lg p-5 space-y-4" data-section-key="contact">
|
||
<div class="flex items-center justify-between">
|
||
<h2 class="text-base font-semibold text-main">业主/联系人</h2>
|
||
<button id="addContactBtn" type="button" class="px-3 py-1.5 rounded-md text-sm bg-primary-600 text-white hover:bg-primary-700">+ 添加联系人</button>
|
||
</div>
|
||
<div id="contactsContainer"></div>
|
||
</section>
|
||
|
||
<section id="section-basic" class="panel rounded-lg p-5 space-y-4" data-section-key="basic">
|
||
<h2 class="text-base font-semibold text-main">基础信息</h2>
|
||
<div>
|
||
<label class="block text-sm font-medium text-main mb-1.5">朝向 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center gap-2 flex-wrap" id="orientationGroup"></div>
|
||
<p class="error-msg" data-error="orientation"></p>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-main mb-1.5">装修 <span class="text-danger-600">*</span></label>
|
||
<div class="flex items-center gap-2 flex-wrap" id="decorationGroup"></div>
|
||
<p class="error-msg" data-error="decoration"></p>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="section-trade" class="panel rounded-lg p-5 space-y-4" data-section-key="trade">
|
||
<h2 class="text-base font-semibold text-main">交易信息</h2>
|
||
<div class="grid grid-cols-12 gap-3" data-field="ownershipYears">
|
||
<div class="col-span-3">
|
||
<label class="block text-sm font-medium text-main mb-1.5">房本年限 <span class="text-danger-600">*</span></label>
|
||
<select id="ownershipYears" class="input w-full px-3 py-2 rounded-md text-sm" data-error-input="ownershipYears">
|
||
<option value="">具体年限</option><option value="lt2">不满2年</option><option value="gte2">满2年</option><option value="gte5">满5年</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-span-3 pt-6">
|
||
<select id="ownershipYearsDetail" class="input w-full px-3 py-2 rounded-md text-sm" data-error-input="ownershipYears">
|
||
<option value="">请选择</option><option value="full5">满五</option><option value="not5">不满五</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-span-6 flex items-end"><p class="text-xs text-sub">商住类型仅保留房本年限;商铺/写字楼不展示该区块。</p></div>
|
||
</div>
|
||
<p class="error-msg" data-error="ownershipYears"></p>
|
||
</section>
|
||
|
||
<section id="section-related" class="panel rounded-lg p-5 space-y-4" data-section-key="related">
|
||
<h2 class="text-base font-semibold text-main">相关方</h2>
|
||
<div class="grid grid-cols-12 gap-3">
|
||
<div class="col-span-4">
|
||
<label class="block text-sm font-medium text-main mb-1">首录方</label>
|
||
<input id="firstRecorder" type="text" readonly value="杜利强 - 系统管理组" class="input w-full px-3 py-2 rounded-md text-sm bg-neutral-100 cursor-not-allowed" />
|
||
</div>
|
||
<div class="col-span-4" data-field="numberHolder">
|
||
<label class="block text-sm font-medium text-main mb-1">号码方 <span class="text-danger-600">*</span></label>
|
||
<div class="flex gap-2"><select id="numberHolder" class="input flex-1 px-3 py-2 rounded-md text-sm" data-error-input="numberHolder"></select><button type="button" id="clearNumberHolder" class="px-2 rounded-md border border-neutral-300 text-sub hover:bg-neutral-50">⊗</button></div>
|
||
<p class="error-msg" data-error="numberHolder"></p>
|
||
</div>
|
||
<div class="col-span-4" data-field="sellerAgent">
|
||
<label class="block text-sm font-medium text-main mb-1">出售方 <span class="text-danger-600">*</span></label>
|
||
<div class="flex gap-2"><select id="sellerAgent" class="input flex-1 px-3 py-2 rounded-md text-sm" data-error-input="sellerAgent"></select><button type="button" id="clearSellerAgent" class="px-2 rounded-md border border-neutral-300 text-sub hover:bg-neutral-50">⊗</button></div>
|
||
<p class="error-msg" data-error="sellerAgent"></p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel rounded-lg p-4 sticky bottom-4 z-20">
|
||
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||
<div id="dirtyText" class="text-xs text-sub">当前内容已保存或未变更</div>
|
||
<div class="flex items-center gap-3">
|
||
<button type="button" id="cancelBtn" class="inline-flex items-center px-4 py-2 text-sm font-medium bg-white border border-neutral-300 text-neutral-700 rounded-md hover:bg-neutral-50 hover:border-neutral-400 disabled:opacity-60">取消</button>
|
||
<button type="button" id="saveContinueBtn" class="inline-flex items-center px-4 py-2 text-sm font-medium bg-white border border-neutral-300 text-neutral-700 rounded-md hover:bg-neutral-50 hover:border-neutral-400 disabled:opacity-60">保存并继续新增</button>
|
||
<button type="submit" id="saveBtn" class="inline-flex items-center gap-1.5 px-6 py-2 text-sm font-medium bg-primary-600 text-white rounded-md hover:bg-primary-700 active:bg-primary-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-600/40 disabled:opacity-70 disabled:cursor-wait">保存</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</form>
|
||
</div>
|
||
</main>
|
||
|
||
<div id="toastContainer" class="fixed top-4 right-4 z-[70] space-y-2 w-[340px]" aria-live="polite"></div>
|
||
|
||
<template id="contactTemplate">
|
||
<div class="subtle rounded-lg p-4 space-y-3 contact-item">
|
||
<div class="flex items-center justify-between"><div class="text-sm font-medium text-main contact-title">业主/联系人1</div><button type="button" class="text-xs text-danger-600 hover:underline remove-contact hidden">删除</button></div>
|
||
<div class="grid grid-cols-12 gap-3">
|
||
<div class="col-span-3" data-field="contactName"><label class="block text-sm font-medium text-main mb-1">姓名 <span class="text-danger-600">*</span></label><input type="text" class="input w-full px-3 py-2 rounded-md text-sm contact-name" placeholder="请输入" /><p class="error-msg contact-error-name"></p></div>
|
||
<div class="col-span-3"><label class="block text-sm font-medium text-main mb-1">性别 <span class="text-danger-600">*</span></label><div class="flex items-center gap-2 gender-group"><button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-gender="male">先生</button><button type="button" class="chip px-3 py-1.5 rounded-md text-sm" data-gender="female">女士</button></div></div>
|
||
<div class="col-span-2"><label class="block text-sm font-medium text-main mb-1">身份 <span class="text-danger-600">*</span></label><select class="input w-full px-3 py-2 rounded-md text-sm contact-identity"><option value="owner">业主</option><option value="contact">联系人</option><option value="agent">代理人</option><option value="tenant">租客</option><option value="subletter">二房东</option></select></div>
|
||
<div class="col-span-2"><label class="block text-sm font-medium text-main mb-1">电话1</label><input type="text" class="input w-full px-3 py-2 rounded-md text-sm contact-phone1" placeholder="手机号或座机号" /></div>
|
||
<div class="col-span-2"><label class="block text-sm font-medium text-main mb-1">电话2</label><input type="text" class="input w-full px-3 py-2 rounded-md text-sm contact-phone2" placeholder="手机号或座机号" /></div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
const state = {
|
||
propertyType: 'residential', status: 'for_sale', attribute: 'public', usage: '', shopLocation: '',
|
||
orientation: '', decoration: '', dirty: false, saving: false,
|
||
contacts: [{ name:'', gender:'male', identity:'owner', phone1:'', phone2:'' }],
|
||
errors: {},
|
||
options: {
|
||
usage: {
|
||
residential: [['normal_residential','普通住宅'], ['garden_house','花园洋房']],
|
||
villa: [['townhouse','联排别墅'], ['detached','独栋别墅'], ['semi_detached','双拼别墅'], ['stacked','叠加别墅']],
|
||
commercial_residential: [], shop: [], office: [],
|
||
other: [['garage','车库'], ['parking','车位'], ['bungalow','平房'], ['siheyuan','四合院'], ['warehouse','仓库'], ['factory','厂房'], ['land','地皮'], ['shop_factory','铺厂'], ['outlet','网点'], ['office_factory','写厂']]
|
||
},
|
||
shopLocation: [['street','临街'], ['mall','商场'], ['residential','小区'], ['ground_floor','底商'], ['complex','商业综合体']],
|
||
orientation: [['east','东'],['south','南'],['west','西'],['north','北'],['southeast','东南'],['northeast','东北'],['east_west','东西'],['south_north','南北'],['southwest','西南'],['northwest','西北']],
|
||
decoration: [['rough','毛坯'],['plain','清水'],['simple','简装'],['medium','中装'],['fine','精装'],['luxury','豪装']],
|
||
staff: [['','请选择'], ['du_liqiang','杜利强 - 系统管理组'], ['wei_shen','魏深 - 房源一组'], ['li_min','李敏 - 房源二组']]
|
||
}
|
||
}
|
||
|
||
const byId = id => document.getElementById(id)
|
||
const q = s => document.querySelector(s)
|
||
const qa = s => Array.from(document.querySelectorAll(s))
|
||
|
||
function setDirty(v = true) {
|
||
state.dirty = v
|
||
byId('dirtyText').textContent = state.dirty ? '你有未保存的更改' : '当前内容已保存或未变更'
|
||
}
|
||
|
||
function markActive(groupSelector, attr, value) {
|
||
qa(`${groupSelector} [${attr}]`).forEach(btn => btn.classList.toggle('active', btn.getAttribute(attr) === value))
|
||
}
|
||
|
||
function buildNumberSelects() {
|
||
;['bedroom','living','bathroom','kitchen','balcony'].forEach(id => {
|
||
const sel = byId(id)
|
||
for (let i = 0; i <= 9; i++) {
|
||
const op = document.createElement('option')
|
||
op.value = String(i)
|
||
op.textContent = `${i}${id==='bedroom'?'室':id==='living'?'厅':id==='bathroom'?'卫':id==='kitchen'?'厨':'阳台'}`
|
||
sel.appendChild(op)
|
||
}
|
||
})
|
||
}
|
||
|
||
function buildOptions() {
|
||
const usage = byId('usageGroup')
|
||
usage.innerHTML = ''
|
||
const arr = state.options.usage[state.propertyType] || []
|
||
if (!arr.length) {
|
||
byId('usageHint').classList.remove('hidden')
|
||
} else {
|
||
byId('usageHint').classList.add('hidden')
|
||
arr.forEach(([v, t]) => {
|
||
const b = document.createElement('button')
|
||
b.type = 'button'; b.className = 'chip px-3 py-1.5 rounded-md text-sm'; b.textContent = t; b.dataset.usage = v
|
||
if (state.usage === v) b.classList.add('active')
|
||
b.addEventListener('click', () => { state.usage = v; markActive('#usageGroup','data-usage',v); setDirty() })
|
||
usage.appendChild(b)
|
||
})
|
||
}
|
||
|
||
const shopLoc = byId('shopLocationGroup')
|
||
shopLoc.innerHTML = ''
|
||
state.options.shopLocation.forEach(([v, t]) => {
|
||
const b = document.createElement('button')
|
||
b.type = 'button'; b.className = 'chip px-3 py-1.5 rounded-md text-sm'; b.textContent = t; b.dataset.shoploc = v
|
||
if (state.shopLocation === v) b.classList.add('active')
|
||
b.addEventListener('click', () => { state.shopLocation = v; markActive('#shopLocationGroup','data-shoploc',v); clearError('shopLocation'); setDirty() })
|
||
shopLoc.appendChild(b)
|
||
})
|
||
|
||
const orientation = byId('orientationGroup')
|
||
orientation.innerHTML = ''
|
||
state.options.orientation.forEach(([v, t]) => {
|
||
const b = document.createElement('button')
|
||
b.type = 'button'; b.className = 'chip px-3 py-1.5 rounded-md text-sm'; b.textContent = t; b.dataset.orient = v
|
||
if (state.orientation === v) b.classList.add('active')
|
||
b.addEventListener('click', () => { state.orientation = v; markActive('#orientationGroup','data-orient',v); clearError('orientation'); setDirty() })
|
||
orientation.appendChild(b)
|
||
})
|
||
|
||
const decoration = byId('decorationGroup')
|
||
decoration.innerHTML = ''
|
||
state.options.decoration.forEach(([v, t]) => {
|
||
const b = document.createElement('button')
|
||
b.type = 'button'; b.className = 'chip px-3 py-1.5 rounded-md text-sm'; b.textContent = t; b.dataset.deco = v
|
||
if (state.decoration === v) b.classList.add('active')
|
||
b.addEventListener('click', () => { state.decoration = v; markActive('#decorationGroup','data-deco',v); clearError('decoration'); setDirty() })
|
||
decoration.appendChild(b)
|
||
})
|
||
|
||
;['numberHolder','sellerAgent'].forEach(id => {
|
||
const sel = byId(id)
|
||
const old = sel.value
|
||
sel.innerHTML = ''
|
||
state.options.staff.forEach(([v, t]) => {
|
||
const op = document.createElement('option'); op.value = v; op.textContent = id==='numberHolder' && v==='' ? '请选择号码方' : id==='sellerAgent' && v==='' ? '请选择出售方' : t
|
||
sel.appendChild(op)
|
||
})
|
||
sel.value = old || 'du_liqiang'
|
||
})
|
||
}
|
||
|
||
function renderContacts() {
|
||
const wrap = byId('contactsContainer')
|
||
wrap.innerHTML = ''
|
||
state.contacts.forEach((c, idx) => {
|
||
const el = byId('contactTemplate').content.firstElementChild.cloneNode(true)
|
||
el.querySelector('.contact-title').textContent = `业主/联系人${idx + 1}`
|
||
const removeBtn = el.querySelector('.remove-contact')
|
||
if (idx > 0) {
|
||
removeBtn.classList.remove('hidden')
|
||
removeBtn.addEventListener('click', () => { state.contacts.splice(idx, 1); renderContacts(); setDirty() })
|
||
}
|
||
|
||
const name = el.querySelector('.contact-name'); name.value = c.name
|
||
name.addEventListener('input', e => { state.contacts[idx].name = e.target.value; setDirty() })
|
||
|
||
const genderBtns = el.querySelectorAll('[data-gender]')
|
||
genderBtns.forEach(btn => {
|
||
btn.classList.toggle('active', btn.dataset.gender === c.gender)
|
||
btn.addEventListener('click', () => {
|
||
state.contacts[idx].gender = btn.dataset.gender
|
||
genderBtns.forEach(b => b.classList.toggle('active', b === btn)); setDirty()
|
||
})
|
||
})
|
||
|
||
const identity = el.querySelector('.contact-identity'); identity.value = c.identity
|
||
identity.addEventListener('change', e => { state.contacts[idx].identity = e.target.value; setDirty() })
|
||
const p1 = el.querySelector('.contact-phone1'); p1.value = c.phone1; p1.addEventListener('input', e => { state.contacts[idx].phone1 = e.target.value; setDirty() })
|
||
const p2 = el.querySelector('.contact-phone2'); p2.value = c.phone2; p2.addEventListener('input', e => { state.contacts[idx].phone2 = e.target.value; setDirty() })
|
||
|
||
if (idx === 0 && state.errors['contacts.0.name']) {
|
||
name.classList.add('error')
|
||
el.querySelector('.contact-error-name').textContent = state.errors['contacts.0.name']
|
||
}
|
||
|
||
wrap.appendChild(el)
|
||
})
|
||
}
|
||
|
||
function isShop() { return state.propertyType === 'shop' }
|
||
function showLayout() { return !['shop','office'].includes(state.propertyType) }
|
||
function showTrade() { return !['shop','office'].includes(state.propertyType) }
|
||
|
||
function refreshTypeUI() {
|
||
markActive('#propertyTypeGroup', 'data-type', state.propertyType)
|
||
const map = { residential:'住宅', villa:'别墅', commercial_residential:'商住', shop:'商铺', office:'写字楼', other:'其他' }
|
||
byId('currentTypeLabel').textContent = map[state.propertyType]
|
||
byId('p2Badge').classList.toggle('hidden', !['commercial_residential','shop','office','other'].includes(state.propertyType))
|
||
|
||
byId('layoutWrap').classList.toggle('hidden', !showLayout())
|
||
byId('shopFields').classList.toggle('hidden', !isShop())
|
||
byId('section-trade').classList.toggle('hidden', !showTrade())
|
||
byId('tradeAnchor').classList.toggle('hidden', !showTrade())
|
||
buildOptions()
|
||
updateStatusUI()
|
||
}
|
||
|
||
function updateStatusUI() {
|
||
markActive('#statusGroup', 'data-status', state.status)
|
||
byId('salePriceWrap').classList.toggle('hidden', !['for_sale','for_sale_rent'].includes(state.status))
|
||
byId('rentPriceWrap').classList.toggle('hidden', !['for_rent','for_sale_rent'].includes(state.status))
|
||
byId('suspendHint').classList.toggle('hidden', state.status !== 'suspended')
|
||
}
|
||
|
||
function clearErrors() {
|
||
state.errors = {}
|
||
qa('.error-msg').forEach(e => e.textContent = '')
|
||
qa('.input.error').forEach(i => i.classList.remove('error'))
|
||
}
|
||
|
||
function clearError(key) {
|
||
state.errors[key] = ''
|
||
const msg = q(`[data-error="${CSS.escape(key)}"]`)
|
||
if (msg) msg.textContent = ''
|
||
qa(`[data-error-input="${CSS.escape(key)}"]`).forEach(i => i.classList.remove('error'))
|
||
}
|
||
|
||
function setError(key, msg) {
|
||
state.errors[key] = msg
|
||
const msgEl = q(`[data-error="${CSS.escape(key)}"]`)
|
||
if (msgEl) msgEl.textContent = msg
|
||
qa(`[data-error-input="${CSS.escape(key)}"]`).forEach(i => i.classList.add('error'))
|
||
}
|
||
|
||
function firstErrorKey() {
|
||
return Object.keys(state.errors).find(k => state.errors[k])
|
||
}
|
||
|
||
function validate() {
|
||
clearErrors()
|
||
const v = id => (byId(id)?.value || '').trim()
|
||
|
||
if (!state.status) setError('status', '请选择状态')
|
||
if (!state.attribute) setError('attribute', '请选择房源属性')
|
||
if (!v('complexName')) setError('complexName', '请输入小区名称')
|
||
|
||
const floor = Number(v('floor')), total = Number(v('totalFloors'))
|
||
if (!floor || !total) setError('floors', '请输入所在楼层与总楼层')
|
||
else if (floor > total) setError('floors', '所在楼层不能大于总楼层')
|
||
|
||
if (!Number(v('area')) || Number(v('area')) <= 0) setError('area', '请输入建筑面积')
|
||
|
||
if (showLayout()) {
|
||
const keys = ['bedroom','living','bathroom','kitchen','balcony']
|
||
if (keys.some(k => v(k) === '')) setError('layout', '请完整填写户型(室/厅/卫/厨/阳台)')
|
||
}
|
||
|
||
if (['for_sale','for_sale_rent'].includes(state.status) && (!Number(v('salePrice')) || Number(v('salePrice')) <= 0)) {
|
||
setError('salePrice', '请输入有效售价')
|
||
}
|
||
if (['for_rent','for_sale_rent'].includes(state.status) && (!Number(v('rentPrice')) || Number(v('rentPrice')) <= 0)) {
|
||
setError('rentPrice', '请输入有效租价')
|
||
}
|
||
|
||
if (isShop()) {
|
||
if (!Number(v('shopFrontage')) || Number(v('shopFrontage')) <= 0) setError('shopFrontage', '请输入开间')
|
||
if (!Number(v('shopDepth')) || Number(v('shopDepth')) <= 0) setError('shopDepth', '请输入进深')
|
||
if (!Number(v('shopHeight')) || Number(v('shopHeight')) <= 0) setError('shopHeight', '请输入层高')
|
||
if (!state.shopLocation) setError('shopLocation', '请选择位置')
|
||
}
|
||
|
||
if (!state.contacts[0]?.name?.trim()) setError('contacts.0.name', '联系人1姓名必填')
|
||
if (!state.orientation) setError('orientation', '请选择朝向')
|
||
if (!state.decoration) setError('decoration', '请选择装修')
|
||
|
||
if (showTrade()) {
|
||
if (!v('ownershipYears') || !v('ownershipYearsDetail')) setError('ownershipYears', '请选择完整房本年限')
|
||
}
|
||
|
||
if (!v('numberHolder')) setError('numberHolder', '请选择号码方')
|
||
if (!v('sellerAgent')) setError('sellerAgent', '请选择出售方')
|
||
|
||
renderContacts()
|
||
return !firstErrorKey()
|
||
}
|
||
|
||
function scrollToFirstError() {
|
||
const key = firstErrorKey()
|
||
if (!key) return
|
||
const selector = key === 'contacts.0.name' ? '#section-contact' : `[data-field="${CSS.escape(key)}"]`
|
||
const target = q(selector)
|
||
if (target) target.scrollIntoView({ behavior:'smooth', block:'center' })
|
||
}
|
||
|
||
function toast(type, title, message) {
|
||
const c = byId('toastContainer')
|
||
const el = document.createElement('div')
|
||
const cls = type==='success' ? 'border-success-600/30 bg-success-50' : type==='warning' ? 'border-warning-600/30 bg-warning-50' : type==='error' ? 'border-danger-600/30 bg-danger-50' : 'border-info-600/30 bg-info-50'
|
||
el.className = `panel rounded-lg px-3 py-2 shadow-lg toast-in ${cls}`
|
||
el.innerHTML = `<div class="flex items-start gap-2"><div class="text-sm font-medium">${title}</div><button class="ml-auto text-xs text-sub">✕</button></div><p class="text-xs mt-1 text-sub">${message}</p>`
|
||
el.querySelector('button').addEventListener('click', ()=> el.remove())
|
||
c.appendChild(el)
|
||
setTimeout(()=>el.remove(), 3200)
|
||
}
|
||
|
||
function setButtonsDisabled(disabled) {
|
||
;['saveBtn','saveContinueBtn','cancelBtn'].forEach(id => byId(id).disabled = disabled)
|
||
byId('saveBtn').textContent = disabled ? '保存中...' : '保存'
|
||
}
|
||
|
||
async function submit(mode) {
|
||
if (!validate()) {
|
||
toast('error','保存失败','请先修正必填项与格式错误')
|
||
scrollToFirstError()
|
||
return
|
||
}
|
||
|
||
setButtonsDisabled(true)
|
||
await new Promise(r => setTimeout(r, 650))
|
||
|
||
if (['commercial_residential','shop','office','other'].includes(state.propertyType)) {
|
||
toast('warning','类型预留提醒','该房源类型在 PRD 中为 v2 预留,当前原型仅用于评审。')
|
||
setButtonsDisabled(false)
|
||
setDirty(false)
|
||
return
|
||
}
|
||
|
||
if (mode === 'save_continue') {
|
||
byId('propertyForm').reset()
|
||
state.contacts = [{ name:'', gender:'male', identity:'owner', phone1:'', phone2:'' }]
|
||
state.orientation = ''; state.decoration = ''; state.usage = ''; state.shopLocation = ''
|
||
state.status = 'for_sale'; state.attribute = 'public'
|
||
buildOptions(); renderContacts(); updateStatusUI(); clearErrors()
|
||
toast('success','保存成功','已保存当前房源,表单已重置,可继续新增。')
|
||
window.scrollTo({ top:0, behavior:'smooth' })
|
||
} else {
|
||
toast('success','保存成功','已完成保存。原型阶段将于任务03接入详情页跳转。')
|
||
}
|
||
|
||
setButtonsDisabled(false)
|
||
setDirty(false)
|
||
}
|
||
|
||
function bindAnchors() {
|
||
const links = {
|
||
core:q('[data-anchor-link="core"]'), contact:q('[data-anchor-link="contact"]'), basic:q('[data-anchor-link="basic"]'),
|
||
trade:q('[data-anchor-link="trade"]'), related:q('[data-anchor-link="related"]')
|
||
}
|
||
const obs = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (!entry.isIntersecting) return
|
||
const key = entry.target.dataset.sectionKey
|
||
if (key==='trade' && byId('section-trade').classList.contains('hidden')) return
|
||
Object.values(links).forEach(a => a && a.classList.remove('active'))
|
||
if (links[key]) links[key].classList.add('active')
|
||
})
|
||
}, { rootMargin:'-35% 0px -55% 0px', threshold:0 })
|
||
|
||
;['section-core','section-contact','section-basic','section-trade','section-related'].forEach(id => {
|
||
const el = byId(id); if (el) obs.observe(el)
|
||
})
|
||
links.core.classList.add('active')
|
||
}
|
||
|
||
function initTheme() {
|
||
const saved = localStorage.getItem('fonrey_theme') || 'light'
|
||
document.documentElement.setAttribute('data-theme', saved)
|
||
qa('[data-theme-btn]').forEach(btn => {
|
||
btn.classList.toggle('active', btn.dataset.themeBtn === saved)
|
||
btn.addEventListener('click', () => {
|
||
const t = btn.dataset.themeBtn
|
||
document.documentElement.setAttribute('data-theme', t)
|
||
localStorage.setItem('fonrey_theme', t)
|
||
qa('[data-theme-btn]').forEach(x => x.classList.toggle('active', x === btn))
|
||
})
|
||
})
|
||
}
|
||
|
||
function init() {
|
||
initTheme()
|
||
buildNumberSelects()
|
||
buildOptions()
|
||
renderContacts()
|
||
bindAnchors()
|
||
|
||
markActive('#statusGroup', 'data-status', state.status)
|
||
markActive('#attributeGroup', 'data-attr', state.attribute)
|
||
markActive('#propertyTypeGroup', 'data-type', state.propertyType)
|
||
refreshTypeUI()
|
||
|
||
qa('#propertyTypeGroup [data-type]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
state.propertyType = btn.dataset.type
|
||
state.usage = ''; state.shopLocation = ''
|
||
clearErrors(); refreshTypeUI(); setDirty()
|
||
})
|
||
})
|
||
|
||
qa('#statusGroup [data-status]').forEach(btn => btn.addEventListener('click', () => { state.status = btn.dataset.status; updateStatusUI(); clearError('status'); setDirty() }))
|
||
qa('#attributeGroup [data-attr]').forEach(btn => btn.addEventListener('click', () => { state.attribute = btn.dataset.attr; markActive('#attributeGroup','data-attr',state.attribute); clearError('attribute'); setDirty() }))
|
||
|
||
byId('addContactBtn').addEventListener('click', () => { state.contacts.push({ name:'', gender:'male', identity:'contact', phone1:'', phone2:'' }); renderContacts(); setDirty() })
|
||
|
||
byId('numberHolder').addEventListener('change', () => { clearError('numberHolder'); setDirty() })
|
||
byId('sellerAgent').addEventListener('change', () => { clearError('sellerAgent'); setDirty() })
|
||
byId('clearNumberHolder').addEventListener('click', () => { byId('numberHolder').value=''; setDirty() })
|
||
byId('clearSellerAgent').addEventListener('click', () => { byId('sellerAgent').value=''; setDirty() })
|
||
|
||
qa('input,select,textarea').forEach(el => {
|
||
el.addEventListener('input', () => setDirty())
|
||
el.addEventListener('change', () => setDirty())
|
||
})
|
||
|
||
byId('propertyForm').addEventListener('submit', e => { e.preventDefault(); submit('save') })
|
||
byId('saveContinueBtn').addEventListener('click', () => submit('save_continue'))
|
||
byId('cancelBtn').addEventListener('click', () => {
|
||
if (!state.dirty) { window.location.href = './房源列表_UI.html'; return }
|
||
if (window.confirm('当前有未保存内容,确认放弃并离开吗?')) {
|
||
state.dirty = false
|
||
window.location.href = './房源列表_UI.html'
|
||
}
|
||
})
|
||
|
||
window.addEventListener('beforeunload', (e) => {
|
||
if (state.dirty) { e.preventDefault(); e.returnValue = '' }
|
||
})
|
||
|
||
setDirty(false)
|
||
}
|
||
|
||
init()
|
||
</script>
|
||
</body>
|
||
</html>
|