Files
oil-formula-calculator/精油配方计算器.html

2058 lines
126 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🌿 精油配方计算器</title>
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&family=Noto+Sans+SC:wght@300;400;500&display=swap" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js"></script>
<style>
:root {
--cream: #faf6f0;
--warm-white: #fffdf9;
--sage: #7a9e7e;
--sage-dark: #5a7d5e;
--sage-light: #c8ddc9;
--sage-mist: #eef4ee;
--gold: #c9a84c;
--gold-light: #f0e4c0;
--brown: #6b4f3a;
--brown-light: #c4a882;
--text-dark: #2c2416;
--text-mid: #5a4a35;
--text-light: #9a8570;
--border: #e0d4c0;
--shadow: 0 4px 20px rgba(90,60,30,0.08);
--shadow-hover: 0 8px 32px rgba(90,60,30,0.15);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Noto Sans SC', sans-serif;
background: var(--cream);
color: var(--text-dark);
min-height: 100vh;
}
/* Header */
.app-header {
background: linear-gradient(135deg, #3d6b41 0%, #5a7d5e 50%, #7a9e7e 100%);
padding: 28px 32px 24px;
position: relative;
overflow: hidden;
}
.app-header::before {
content: '';
position: absolute; inset: 0;
background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
}
.header-inner { position: relative; z-index: 1; display: flex; align-items: center; gap: 16px; }
.header-icon { font-size: 40px; }
.header-title { color: white; }
.header-title h1 { font-family: 'Noto Serif SC', serif; font-size: 24px; font-weight: 600; letter-spacing: 2px; }
.header-title p { font-size: 13px; opacity: 0.8; margin-top: 4px; letter-spacing: 1px; }
/* Nav tabs */
.nav-tabs {
display: flex;
background: white;
border-bottom: 1px solid var(--border);
padding: 0 24px;
gap: 0;
overflow-x: auto;
}
.nav-tab {
padding: 14px 20px;
font-size: 14px;
font-weight: 500;
color: var(--text-light);
cursor: pointer;
border-bottom: 3px solid transparent;
white-space: nowrap;
transition: all 0.2s;
}
.nav-tab:hover { color: var(--sage-dark); }
.nav-tab.active { color: var(--sage-dark); border-bottom-color: var(--sage); }
/* Main content */
.main { padding: 24px; max-width: 960px; margin: 0 auto; }
/* Section */
.section { display: none; }
.section.active { display: block; }
/* Search box */
.search-box {
background: white;
border-radius: 16px;
padding: 20px 24px;
box-shadow: var(--shadow);
margin-bottom: 20px;
}
.search-label { font-size: 13px; color: var(--text-light); margin-bottom: 10px; letter-spacing: 0.5px; }
.search-row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.search-input {
flex: 1; min-width: 200px;
padding: 11px 16px;
border: 1.5px solid var(--border);
border-radius: 10px;
font-size: 15px;
font-family: inherit;
color: var(--text-dark);
background: var(--cream);
transition: border-color 0.2s;
outline: none;
}
.search-input:focus { border-color: var(--sage); background: white; }
.btn {
padding: 11px 22px;
border-radius: 10px;
border: none;
font-size: 14px;
font-family: inherit;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.btn-primary { background: var(--sage); color: white; }
.btn-primary:hover { background: var(--sage-dark); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(90,125,94,0.3); }
.btn-gold { background: var(--gold); color: white; }
.btn-gold:hover { background: #b8973e; transform: translateY(-1px); }
.btn-outline { background: transparent; color: var(--sage-dark); border: 1.5px solid var(--sage); }
.btn-outline:hover { background: var(--sage-mist); }
.btn-danger { background: transparent; color: #c0392b; border: 1.5px solid #e8b4b0; }
.btn-danger:hover { background: #fdf0ee; }
.btn-sm { padding: 7px 14px; font-size: 13px; }
/* Recipe grid */
.recipe-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.recipe-card {
background: white;
border-radius: 14px;
padding: 18px;
cursor: pointer;
box-shadow: var(--shadow);
border: 2px solid transparent;
transition: all 0.2s;
position: relative;
}
.recipe-card:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); border-color: var(--sage-light); }
.recipe-card.selected { border-color: var(--sage); background: var(--sage-mist); }
.recipe-card-name {
font-family: 'Noto Serif SC', serif;
font-size: 16px;
font-weight: 600;
color: var(--text-dark);
margin-bottom: 8px;
}
.recipe-card-oils { font-size: 12px; color: var(--text-light); line-height: 1.7; }
.recipe-card-price {
margin-top: 12px;
font-size: 13px;
color: var(--sage-dark);
font-weight: 600;
display: flex; align-items: center; gap: 6px;
}
/* Detail panel */
.detail-panel {
background: white;
border-radius: 16px;
padding: 28px;
box-shadow: var(--shadow);
margin-bottom: 24px;
}
.detail-header {
display: flex; justify-content: space-between; align-items: flex-start;
margin-bottom: 24px; flex-wrap: wrap; gap: 12px;
}
.detail-title {
font-family: 'Noto Serif SC', serif;
font-size: 22px; font-weight: 700; color: var(--text-dark);
}
.detail-note {
font-size: 13px; color: var(--text-light);
background: var(--gold-light); border-radius: 8px;
padding: 6px 12px; margin-top: 6px;
display: inline-block;
}
.detail-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* Ingredients table */
.ingredients-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.ingredients-table th {
text-align: left; padding: 10px 14px;
font-size: 12px; font-weight: 600;
color: var(--text-light); letter-spacing: 0.5px;
border-bottom: 2px solid var(--border);
text-transform: uppercase;
}
.ingredients-table td {
padding: 12px 14px;
border-bottom: 1px solid var(--border);
font-size: 14px; vertical-align: middle;
}
.ingredients-table tr:last-child td { border-bottom: none; }
.ingredients-table tr:hover td { background: var(--sage-mist); }
.drops-input {
width: 70px; padding: 6px 10px;
border: 1.5px solid var(--border); border-radius: 8px;
font-size: 14px; font-family: inherit; text-align: center;
outline: none; transition: border-color 0.2s;
}
.drops-input:focus { border-color: var(--sage); }
.oil-select {
padding: 6px 10px;
border: 1.5px solid var(--border); border-radius: 8px;
font-size: 13px; font-family: inherit;
background: white; outline: none;
max-width: 160px;
}
.oil-select:focus { border-color: var(--sage); }
.remove-btn {
background: none; border: none; cursor: pointer;
color: #c0392b; font-size: 18px; padding: 4px 8px;
border-radius: 6px; transition: background 0.2s;
}
.remove-btn:hover { background: #fdf0ee; }
.total-row {
background: var(--sage-mist);
border-radius: 12px; padding: 16px 20px;
display: flex; justify-content: space-between; align-items: center;
margin-top: 16px;
}
.total-label { font-size: 14px; color: var(--text-mid); font-weight: 500; }
.total-price { font-size: 22px; font-weight: 700; color: var(--sage-dark); }
/* Add ingredient */
.add-ingredient-row {
display: flex; gap: 10px; align-items: center;
margin-top: 12px; flex-wrap: wrap;
}
.add-ingredient-row select, .add-ingredient-row input {
padding: 8px 12px; border: 1.5px solid var(--border);
border-radius: 8px; font-size: 13px; font-family: inherit;
outline: none;
}
.add-ingredient-row select:focus, .add-ingredient-row input:focus { border-color: var(--sage); }
/* Card preview for export */
.card-preview-wrapper { margin-top: 20px; }
#recipe-card-export {
background: linear-gradient(145deg, #faf7f0 0%, #f5ede0 100%);
border-radius: 20px;
padding: 36px;
font-family: 'Noto Serif SC', serif;
max-width: 480px;
border: 1px solid #e0ccaa;
position: relative;
overflow: hidden;
}
#recipe-card-export::before {
content: '';
position: absolute; top: -40px; right: -40px;
width: 180px; height: 180px;
background: radial-gradient(circle, rgba(122,158,126,0.15) 0%, transparent 70%);
border-radius: 50%;
}
#recipe-card-export::after {
content: '';
position: absolute; bottom: -30px; left: -30px;
width: 140px; height: 140px;
background: radial-gradient(circle, rgba(201,168,76,0.12) 0%, transparent 70%);
border-radius: 50%;
}
.card-brand {
font-size: 11px; letter-spacing: 3px; color: var(--sage);
text-transform: uppercase; margin-bottom: 8px;
}
.card-title {
font-size: 26px; font-weight: 700; color: var(--text-dark);
margin-bottom: 6px; line-height: 1.3;
}
.card-divider {
width: 48px; height: 2px;
background: linear-gradient(90deg, var(--sage), var(--gold));
border-radius: 2px; margin: 14px 0;
}
.card-note { font-size: 12px; color: var(--brown-light); margin-bottom: 18px; }
.card-ingredients { list-style: none; margin-bottom: 20px; }
.card-ingredients li {
display: flex; align-items: center;
padding: 9px 0; border-bottom: 1px solid rgba(180,150,100,0.15);
font-size: 14px;
}
.card-ingredients li:last-child { border-bottom: none; }
.card-oil-name { flex: 1; color: var(--text-dark); font-weight: 500; }
.card-oil-drops { width: 60px; text-align: right; color: var(--sage-dark); font-size: 13px; }
.card-oil-cost { width: 70px; text-align: right; color: var(--text-light); font-size: 12px; }
.card-total {
background: linear-gradient(135deg, var(--sage), #5a7d5e);
border-radius: 12px; padding: 14px 20px;
display: flex; justify-content: space-between; align-items: center;
margin-top: 8px;
}
.card-total-label { color: rgba(255,255,255,0.85); font-size: 13px; letter-spacing: 1px; }
.card-total-price { color: white; font-size: 20px; font-weight: 700; }
.card-footer {
margin-top: 16px; text-align: center;
font-size: 11px; color: var(--text-light); letter-spacing: 1px;
}
/* Manage section */
.manage-list { display: flex; flex-direction: column; gap: 12px; }
.manage-item {
background: white; border-radius: 14px; padding: 18px 22px;
box-shadow: var(--shadow); display: flex;
justify-content: space-between; align-items: center;
gap: 12px; flex-wrap: wrap;
}
.manage-item-left { flex: 1; }
.manage-item-name { font-weight: 600; font-size: 16px; color: var(--text-dark); }
.manage-item-oils { font-size: 13px; color: var(--text-light); margin-top: 4px; }
.manage-item-actions { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
/* Add recipe form */
.form-card {
background: white; border-radius: 16px;
padding: 28px; box-shadow: var(--shadow); margin-bottom: 24px;
}
.form-title { font-family: 'Noto Serif SC', serif; font-size: 18px; font-weight: 600; margin-bottom: 20px; color: var(--text-dark); }
.form-group { margin-bottom: 16px; }
.form-label { font-size: 13px; color: var(--text-mid); margin-bottom: 6px; display: block; font-weight: 500; }
.form-control {
width: 100%; padding: 10px 14px;
border: 1.5px solid var(--border); border-radius: 10px;
font-size: 14px; font-family: inherit; outline: none;
transition: border-color 0.2s; background: white;
}
.form-control:focus { border-color: var(--sage); }
.new-ing-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.new-ing-row {
display: flex; gap: 8px; align-items: center;
}
.new-ing-row select { flex: 1; }
.new-ing-row input { width: 80px; }
/* Oils section */
.oils-search { margin-bottom: 16px; }
.oils-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
}
.oil-chip {
background: white; border-radius: 10px; padding: 12px 16px;
box-shadow: 0 2px 8px rgba(90,60,30,0.06);
display: flex; justify-content: space-between; align-items: center;
gap: 8px;
}
.oil-chip-name { font-size: 14px; color: var(--text-dark); font-weight: 500; }
.oil-chip-price { font-size: 13px; color: var(--sage-dark); font-weight: 600; }
.oil-chip-actions { display: flex; gap: 4px; }
.oil-chip-btn {
background: none; border: none; cursor: pointer;
font-size: 13px; padding: 3px 6px; border-radius: 6px;
transition: background 0.2s; color: var(--text-light);
}
.oil-chip-btn:hover { background: var(--sage-mist); color: var(--sage-dark); }
.oil-chip-btn.del:hover { background: #fdf0ee; color: #c0392b; }
.oil-edit-input {
width: 90px; padding: 4px 8px; border: 1.5px solid var(--sage);
border-radius: 6px; font-size: 13px; font-family: inherit;
text-align: center; outline: none;
}
.add-oil-form {
background: white; border-radius: 14px; padding: 16px 20px;
box-shadow: var(--shadow); margin-bottom: 16px;
display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
}
.add-oil-form input {
padding: 9px 14px; border: 1.5px solid var(--border);
border-radius: 8px; font-size: 14px; font-family: inherit; outline: none;
}
.add-oil-form input:focus { border-color: var(--sage); }
/* Empty state */
.empty-state { text-align: center; padding: 60px 20px; color: var(--text-light); }
.empty-state-icon { font-size: 48px; margin-bottom: 12px; }
.empty-state-text { font-size: 15px; }
/* Tag */
.tag {
display: inline-block; padding: 3px 10px;
border-radius: 20px; font-size: 12px;
background: var(--sage-mist); color: var(--sage-dark);
margin: 2px;
}
.tag-btn {
display: inline-flex; align-items: center; gap: 4px;
padding: 5px 12px; border-radius: 20px; font-size: 13px;
background: var(--sage-mist); color: var(--sage-dark);
border: 1.5px solid transparent; cursor: pointer;
transition: all 0.2s;
}
.tag-btn:hover { border-color: var(--sage); }
.tag-btn.active { background: var(--sage); color: white; border-color: var(--sage); }
.tag-btn .tag-del {
font-size: 14px; margin-left: 2px; opacity: 0.5;
cursor: pointer; border: none; background: none;
color: inherit; padding: 0 2px;
}
.tag-btn .tag-del:hover { opacity: 1; }
/* Tag picker overlay */
.tag-picker {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.3); z-index: 999;
display: flex; align-items: center; justify-content: center;
}
.tag-picker-card {
background: white; border-radius: 16px; padding: 24px;
box-shadow: 0 8px 40px rgba(0,0,0,0.2); max-width: 400px; width: 90%;
}
.tag-picker-title { font-family: 'Noto Serif SC', serif; font-size: 16px; font-weight: 600; margin-bottom: 14px; }
.tag-picker-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
.tag-pick {
padding: 6px 14px; border-radius: 20px; font-size: 13px;
border: 1.5px solid var(--border); background: white;
color: var(--text-mid); cursor: pointer; transition: all 0.15s;
}
.tag-pick:hover { border-color: var(--sage); }
.tag-pick.selected { background: var(--sage); color: white; border-color: var(--sage); }
/* Tooltip / hint */
.hint { font-size: 12px; color: var(--text-light); margin-top: 6px; }
.section-title {
font-family: 'Noto Serif SC', serif;
font-size: 18px; font-weight: 600; color: var(--text-dark);
margin-bottom: 16px; display: flex; align-items: center; gap: 8px;
}
@media (max-width: 600px) {
.main { padding: 16px; }
.detail-panel { padding: 20px; }
.recipe-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="app-header">
<div class="header-inner">
<div class="header-icon">🌿</div>
<div class="header-title">
<h1>DOTERRA 精油配方计算器Hera</h1>
<p>查询配方 · 计算成本 · 导出卡片</p>
</div>
</div>
</div>
<div class="nav-tabs">
<div class="nav-tab active" onclick="showSection('search')">🔍 配方查询</div>
<div class="nav-tab" onclick="showSection('manage')">📋 管理配方</div>
<div class="nav-tab" onclick="showSection('add')"> 新增配方</div>
<div class="nav-tab" onclick="showSection('oils')">💧 精油价目</div>
</div>
<div class="main">
<!-- ========== SEARCH SECTION ========== -->
<div class="section active" id="section-search">
<div class="search-box">
<div class="search-label">搜索配方名称或关键词</div>
<div class="search-row">
<input type="text" class="search-input" id="searchInput" placeholder="输入配方名称,如:白发转黑、痘痘、一夜好眠…" oninput="filterRecipes()">
<button class="btn btn-outline" onclick="clearSearch()">清除</button>
</div>
</div>
<div id="recipeGrid" class="recipe-grid"></div>
<div id="detailPanel" style="display:none">
<div class="detail-panel">
<div class="detail-header">
<div>
<div class="detail-title" id="detailTitle"></div>
<div id="detailNote"></div>
</div>
<div class="detail-actions">
<button class="btn btn-primary btn-sm" onclick="addIngredientRow()"> 加精油</button>
<button class="btn btn-gold btn-sm" onclick="exportCard()">📷 导出图片</button>
</div>
</div>
<table class="ingredients-table">
<thead>
<tr>
<th>精油</th>
<th>滴数</th>
<th>单价/滴</th>
<th>小计</th>
<th></th>
</tr>
</thead>
<tbody id="ingredientsBody"></tbody>
</table>
<div class="add-ingredient-row" id="addIngRow" style="display:none">
<select id="newOilSelect" class="form-control" style="max-width:180px">
<option value="">— 选择精油 —</option>
</select>
<input type="number" id="newOilDrops" placeholder="滴数" style="width:90px" class="form-control" min="0.5" step="0.5">
<button class="btn btn-primary btn-sm" onclick="confirmAddIngredient()">确认添加</button>
<button class="btn btn-outline btn-sm" onclick="hideAddRow()">取消</button>
</div>
<div class="total-row">
<div class="total-label">配方总成本</div>
<div class="total-price" id="totalPrice">¥ 0.00</div>
</div>
</div>
<!-- Card Preview -->
<div class="card-preview-wrapper">
<div class="section-title">📋 配方卡片预览</div>
<div id="recipe-card-export">
<div class="card-brand">DOTERRA · 精油配方</div>
<div class="card-title" id="cardTitle"></div>
<div class="card-divider"></div>
<div class="card-note" id="cardNote"></div>
<ul class="card-ingredients" id="cardIngredients"></ul>
<div class="card-total">
<div class="card-total-label">配方总成本</div>
<div class="card-total-price" id="cardTotal"></div>
</div>
<div class="card-footer" id="cardDate"></div>
</div>
</div>
</div>
</div>
<!-- ========== MANAGE SECTION ========== -->
<div class="section" id="section-manage">
<!-- Tag management bar -->
<div class="search-box" style="margin-bottom:16px">
<div class="search-label">🏷 标签管理 <span style="font-size:11px;color:var(--text-light)">(点击标签可筛选配方)</span></div>
<div id="tagBar" style="display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:8px"></div>
<div style="display:flex;gap:8px;margin-top:10px;align-items:center">
<input type="text" class="search-input" id="newTagInput" placeholder="新建标签…" style="flex:1;max-width:200px;padding:8px 12px;font-size:13px"
onkeydown="if(event.key==='Enter')addGlobalTag()">
<button class="btn btn-outline btn-sm" onclick="addGlobalTag()">+ 添加标签</button>
</div>
</div>
<div class="section-title" style="justify-content:space-between;flex-wrap:wrap">
<span>📋 配方列表 <span id="manageFilterLabel" style="font-size:13px;font-weight:400;color:var(--text-light)"></span></span>
<div style="display:flex;gap:8px">
<button class="btn btn-outline btn-sm" onclick="toggleSelectAll()">全选/取消</button>
<button class="btn btn-gold btn-sm" onclick="exportExcel()">📥 导出 Excel</button>
</div>
</div>
<div id="manageList" class="manage-list"></div>
</div>
<!-- ========== ADD SECTION ========== -->
<div class="section" id="section-add">
<div class="form-card">
<div class="form-title">✨ 智能粘贴</div>
<div class="form-group">
<label class="form-label">直接输入或粘贴配方,第一个词为配方名,后面跟精油名+滴数</label>
<textarea class="form-control" id="smartPasteInput" rows="3" placeholder="例长高芳香调理8永久花10檀香10乳香15西伯利亚冷杉20"></textarea>
<div class="hint">支持逗号、顿号、换行分隔也支持无分隔直接连写芳香调理8永久花10檀香10自动用数字断句。不需要写椰子油系统会按稀释比例自动计算。</div>
</div>
<div style="display:flex; gap:12px;">
<button class="btn btn-primary" onclick="smartPaste()">🪄 识别并生成</button>
<button class="btn btn-outline" onclick="document.getElementById('smartPasteInput').value=''">清除</button>
</div>
<div id="smartPasteResult" style="margin-top:12px"></div>
</div>
<div class="form-card">
<div class="form-title"> 手动新增配方</div>
<div class="form-group">
<label class="form-label">配方名称 *</label>
<input type="text" class="form-control" id="newRecipeName" placeholder="如:淡斑精华、改善睡眠…">
</div>
<div class="form-group">
<label class="form-label">备注说明(可选)</label>
<input type="text" class="form-control" id="newRecipeNote" placeholder="如:适合晚间使用,有艾草时可加入">
</div>
<div class="form-group">
<label class="form-label">精油成分 *</label>
<div class="new-ing-list" id="newIngList"></div>
<button class="btn btn-outline btn-sm" onclick="addNewIngRow()"> 添加精油</button>
</div>
<div style="margin-top: 24px; display:flex; gap:12px;">
<button class="btn btn-primary" onclick="saveNewRecipe()">💾 保存配方</button>
<button class="btn btn-outline" onclick="clearNewForm()">重置</button>
</div>
</div>
</div>
<!-- ========== OILS SECTION ========== -->
<div class="section" id="section-oils">
<div class="section-title">💧 精油价目表 <span style="font-size:14px;font-weight:400;color:var(--text-light)"><span id="oilCount"></span> 种精油</span></div>
<div class="add-oil-form">
<input type="text" id="newOilName" placeholder="精油名称" style="flex:1;min-width:120px">
<input type="number" id="newOilBottlePrice" placeholder="一瓶价格 (¥)" style="width:130px" step="0.01" min="0">
<select id="newOilVolume" class="form-control" style="width:110px" onchange="onVolumeChange('new')">
<option value="">容量</option>
<option value="2.5">2.5ml (46滴)</option>
<option value="5">5ml (93滴)</option>
<option value="10">10ml (186滴)</option>
<option value="15">15ml (280滴)</option>
<option value="115">115ml (2146滴)</option>
<option value="custom">自定义滴数</option>
</select>
<input type="number" id="newOilDropCount" placeholder="总滴数" style="width:90px;display:none" step="1" min="1">
<button class="btn btn-primary btn-sm" onclick="addNewOil()"> 添加精油</button>
</div>
<div class="search-box oils-search">
<input type="text" class="search-input" id="oilSearchInput" placeholder="搜索精油名称…" oninput="filterOils()" style="width:100%">
</div>
<div id="oilsGrid" class="oils-grid"></div>
</div>
</div>
<script>
// ============ DATA ============
const DEFAULT_OILS = {"小豆蔻": 2.69, "芹菜籽": 1.11, "芫荽": 0.79, "小茴香": 0.52, "生姜": 1.48, "姜黄": 0.98, "缬草": 1.73, "岩兰草": 1.86, "侧柏": 2.1, "桦木": 5.11, "雪松": 0.46, "斯里兰卡肉桂皮": 2.96, "夏威夷檀香": 6.61, "檀香": 7.69, "古巴香脂": 1.0, "乳香": 2.25, "枫香": 0.86, "没药": 2.09, "罗勒": 0.96, "黑云杉": 1.83, "芫荽叶": 0.82, "香茅": 0.61, "丝柏": 0.55, "道格拉斯冷杉": 2.1, "尤加利": 0.66, "扁柏": 2.47, "柠檬尤加利": 0.43, "柠檬草": 0.41, "马郁兰": 0.7, "香蜂草": 8.71, "麦卢卡": 4.62, "西班牙牛至": 0.8, "广藿香": 0.96, "椒样薄荷": 0.75, "苦橙叶": 0.79, "迷迭香": 0.63, "西伯利亚冷杉": 0.61, "西班牙鼠尾草": 0.82, "绿薄荷": 0.89, "茶树": 0.7, "百里香": 1.0, "冬青": 0.84, "蓝艾菊": 7.53, "快乐鼠尾草": 1.16, "丁香花蕾": 0.61, "天竺葵": 1.38, "永久花": 7.15, "茉莉": 26.3, "薰衣草": 0.82, "罗马洋甘菊": 4.52, "依兰依兰": 1.25, "佛手柑": 1.23, "黑胡椒": 2.04, "圆柚": 0.59, "杜松浆果": 2.04, "柠檬": 0.43, "莱姆": 0.48, "山鸡椒": 0.68, "加州胡椒": 2.04, "红橘": 0.46, "野橘": 0.38, "乐释": 1.25, "赋活呼吸": 0.95, "芳香调理": 0.98, "安定情绪": 0.73, "柑橘清新": 0.68, "柑橘绚烂": 0.82, "温柔呵护": 1.59, "完美修护": 1.14, "舒缓": 3.28, "乐活": 1.09, "顺畅呼吸": 0.8, "愈创木": 0.57, "恬家": 0.79, "安宁神气": 1.2, "椰风香草": 3.33, "清醇薄荷": 0.91, "新瑞活力": 0.86, "保卫": 1.13, "净化清新": 0.73, "花样年华焕肤油": 3.66, "天然防护": 0.39, "西洋蓍草": 1.61, "元气": 0.82, "欢欣": 2.31, "抚慰": 3.6, "宽容": 2.1, "鼓舞": 2.2, "热情": 3.82, "静谧": 3.01, "椰子油": 0.05, "植物空胶囊": 0.12, "玫瑰": 28.82, "玫瑰呵护": 2.53, "茉莉呵护": 2.74, "橙花呵护": 2.31, "桂花呵护": 2.58, "木兰呵护": 2.04, "茶树呵护": 1.13, "特瑞活力": 1.25, "全神贯注": 1.72, "新清肌呵护": 1.4, "新清肌调理": 0.93, "当归": 4.84, "月桂叶": 1.0, "橙花": 12.37, "桂花": 10.54, "穗甘松": 4.84, "玫瑰草": 0.64, "罗文莎叶": 0.89, "甜茴香": 0.64, "五味子": 1.0, "印蒿": 1.14, "柠檬香桃木": 0.89};
const DEFAULT_OILS_META = {"小豆蔻": {"bottlePrice": 250, "dropCount": 93}, "芹菜籽": {"bottlePrice": 310, "dropCount": 280}, "芫荽": {"bottlePrice": 220, "dropCount": 280}, "小茴香": {"bottlePrice": 145, "dropCount": 280}, "生姜": {"bottlePrice": 415, "dropCount": 280}, "姜黄": {"bottlePrice": 275, "dropCount": 280}, "缬草": {"bottlePrice": 485, "dropCount": 280}, "岩兰草": {"bottlePrice": 520, "dropCount": 280}, "侧柏": {"bottlePrice": 195, "dropCount": 93}, "桦木": {"bottlePrice": 475, "dropCount": 93}, "雪松": {"bottlePrice": 130, "dropCount": 280}, "斯里兰卡肉桂皮": {"bottlePrice": 275, "dropCount": 93}, "夏威夷檀香": {"bottlePrice": 615, "dropCount": 93}, "檀香": {"bottlePrice": 715, "dropCount": 93}, "古巴香脂": {"bottlePrice": 279, "dropCount": 280}, "乳香": {"bottlePrice": 630, "dropCount": 280}, "枫香": {"bottlePrice": 240, "dropCount": 280}, "没药": {"bottlePrice": 585, "dropCount": 280}, "罗勒": {"bottlePrice": 270, "dropCount": 280}, "黑云杉": {"bottlePrice": 170, "dropCount": 93}, "芫荽叶": {"bottlePrice": 230, "dropCount": 280}, "香茅": {"bottlePrice": 170, "dropCount": 280}, "丝柏": {"bottlePrice": 155, "dropCount": 280}, "道格拉斯冷杉": {"bottlePrice": 195, "dropCount": 93}, "尤加利": {"bottlePrice": 185, "dropCount": 280}, "扁柏": {"bottlePrice": 230, "dropCount": 93}, "柠檬尤加利": {"bottlePrice": 120, "dropCount": 280}, "柠檬草": {"bottlePrice": 115, "dropCount": 280}, "马郁兰": {"bottlePrice": 195, "dropCount": 280}, "香蜂草": {"bottlePrice": 810, "dropCount": 93}, "麦卢卡": {"bottlePrice": 430, "dropCount": 93}, "西班牙牛至": {"bottlePrice": 225, "dropCount": 280}, "广藿香": {"bottlePrice": 270, "dropCount": 280}, "椒样薄荷": {"bottlePrice": 210, "dropCount": 280}, "苦橙叶": {"bottlePrice": 220, "dropCount": 280}, "迷迭香": {"bottlePrice": 175, "dropCount": 280}, "西伯利亚冷杉": {"bottlePrice": 170, "dropCount": 280}, "西班牙鼠尾草": {"bottlePrice": 230, "dropCount": 280}, "绿薄荷": {"bottlePrice": 250, "dropCount": 280}, "茶树": {"bottlePrice": 195, "dropCount": 280}, "百里香": {"bottlePrice": 280, "dropCount": 280}, "冬青": {"bottlePrice": 235, "dropCount": 280}, "蓝艾菊": {"bottlePrice": 700, "dropCount": 93}, "快乐鼠尾草": {"bottlePrice": 325, "dropCount": 280}, "丁香花蕾": {"bottlePrice": 170, "dropCount": 280}, "天竺葵": {"bottlePrice": 385, "dropCount": 280}, "永久花": {"bottlePrice": 665, "dropCount": 93}, "茉莉": {"bottlePrice": 1210, "dropCount": 46}, "薰衣草": {"bottlePrice": 230, "dropCount": 280}, "罗马洋甘菊": {"bottlePrice": 420, "dropCount": 93}, "依兰依兰": {"bottlePrice": 350, "dropCount": 280}, "佛手柑": {"bottlePrice": 345, "dropCount": 280}, "黑胡椒": {"bottlePrice": 190, "dropCount": 93}, "圆柚": {"bottlePrice": 165, "dropCount": 280}, "杜松浆果": {"bottlePrice": 190, "dropCount": 93}, "柠檬": {"bottlePrice": 120, "dropCount": 280}, "莱姆": {"bottlePrice": 135, "dropCount": 280}, "山鸡椒": {"bottlePrice": 190, "dropCount": 280}, "加州胡椒": {"bottlePrice": 190, "dropCount": 93}, "红橘": {"bottlePrice": 130, "dropCount": 280}, "野橘": {"bottlePrice": 105, "dropCount": 280}, "乐释": {"bottlePrice": 350, "dropCount": 280}, "赋活呼吸": {"bottlePrice": 265, "dropCount": 280}, "芳香调理": {"bottlePrice": 275, "dropCount": 280}, "安定情绪": {"bottlePrice": 205, "dropCount": 280}, "柑橘清新": {"bottlePrice": 190, "dropCount": 280}, "柑橘绚烂": {"bottlePrice": 230, "dropCount": 280}, "温柔呵护": {"bottlePrice": 445, "dropCount": 280}, "完美修护": {"bottlePrice": 320, "dropCount": 280}, "舒缓": {"bottlePrice": 305, "dropCount": 93}, "乐活": {"bottlePrice": 305, "dropCount": 280}, "顺畅呼吸": {"bottlePrice": 225, "dropCount": 280}, "愈创木": {"bottlePrice": 160, "dropCount": 280}, "恬家": {"bottlePrice": 220, "dropCount": 280}, "安宁神气": {"bottlePrice": 335, "dropCount": 280}, "椰风香草": {"bottlePrice": 310, "dropCount": 93}, "清醇薄荷": {"bottlePrice": 255, "dropCount": 280}, "新瑞活力": {"bottlePrice": 240, "dropCount": 280}, "保卫": {"bottlePrice": 315, "dropCount": 280}, "净化清新": {"bottlePrice": 205, "dropCount": 280}, "花样年华焕肤油": {"bottlePrice": 680, "dropCount": 186}, "天然防护": {"bottlePrice": 110, "dropCount": 280}, "西洋蓍草": {"bottlePrice": 450, "dropCount": 280}, "元气": {"bottlePrice": 230, "dropCount": 280}, "欢欣": {"bottlePrice": 215, "dropCount": 93}, "抚慰": {"bottlePrice": 335, "dropCount": 93}, "宽容": {"bottlePrice": 195, "dropCount": 93}, "鼓舞": {"bottlePrice": 205, "dropCount": 93}, "热情": {"bottlePrice": 355, "dropCount": 93}, "静谧": {"bottlePrice": 280, "dropCount": 93}, "椰子油": {"bottlePrice": 115, "dropCount": 2146}, "植物空胶囊": {"bottlePrice": 32.73, "dropCount": 280}, "玫瑰": {"bottlePrice": 2680, "dropCount": 93}, "玫瑰呵护": {"bottlePrice": 470, "dropCount": 186}, "茉莉呵护": {"bottlePrice": 510, "dropCount": 186}, "橙花呵护": {"bottlePrice": 430, "dropCount": 186}, "桂花呵护": {"bottlePrice": 480, "dropCount": 186}, "木兰呵护": {"bottlePrice": 380, "dropCount": 186}, "茶树呵护": {"bottlePrice": 210, "dropCount": 186}, "特瑞活力": {"bottlePrice": 350, "dropCount": 280}, "全神贯注": {"bottlePrice": 320, "dropCount": 186}, "新清肌呵护": {"bottlePrice": 260, "dropCount": 186}, "新清肌调理": {"bottlePrice": 260, "dropCount": 280}, "当归": {"bottlePrice": 450, "dropCount": 93}, "月桂叶": {"bottlePrice": 280, "dropCount": 280}, "橙花": {"bottlePrice": 1150, "dropCount": 93}, "桂花": {"bottlePrice": 980, "dropCount": 93}, "穗甘松": {"bottlePrice": 450, "dropCount": 93}, "玫瑰草": {"bottlePrice": 180, "dropCount": 280}, "罗文莎叶": {"bottlePrice": 250, "dropCount": 280}, "甜茴香": {"bottlePrice": 180, "dropCount": 280}, "五味子": {"bottlePrice": 280, "dropCount": 280}, "印蒿": {"bottlePrice": 320, "dropCount": 280}, "柠檬香桃木": {"bottlePrice": 250, "dropCount": 280}};
// OILS stores per-drop price for calculations
// OILS_META stores {bottlePrice, dropCount} for editing
let OILS = {};
let OILS_META = {};
function loadOils() {
try {
const savedMeta = localStorage.getItem('doterra_oils_meta');
if (savedMeta) {
OILS_META = JSON.parse(savedMeta);
// Merge any new oils from DEFAULT_OILS_META
for (const [name, meta] of Object.entries(DEFAULT_OILS_META)) {
if (!(name in OILS_META)) {
OILS_META[name] = JSON.parse(JSON.stringify(meta));
}
}
// Rebuild per-drop prices from meta
OILS = {};
for (const [name, meta] of Object.entries(OILS_META)) {
OILS[name] = meta.bottlePrice / meta.dropCount;
}
saveOils();
} else {
OILS = JSON.parse(JSON.stringify(DEFAULT_OILS));
OILS_META = JSON.parse(JSON.stringify(DEFAULT_OILS_META));
saveOils();
}
} catch(e) {
OILS = JSON.parse(JSON.stringify(DEFAULT_OILS));
}
}
function saveOils() {
localStorage.setItem('doterra_oils_meta', JSON.stringify(OILS_META));
}
const DEFAULT_RECIPES = [
{name:"酸痛包",note:"",ingredients:[{oil:"椒样薄荷",drops:1},{oil:"舒缓",drops:2},{oil:"芳香调理",drops:1},{oil:"冬青",drops:1},{oil:"柠檬草",drops:1},{oil:"生姜",drops:2},{oil:"茶树",drops:1},{oil:"乳香",drops:1},{oil:"椰子油",drops:10}]},
{name:"小v脸",note:"",ingredients:[{oil:"丝柏",drops:2},{oil:"乳香",drops:1},{oil:"西洋蓍草",drops:1},{oil:"永久花",drops:1},{oil:"椰子油",drops:10}]},
{name:"健脾化湿精油浴",note:"",ingredients:[{oil:"西伯利亚冷杉",drops:3},{oil:"芫荽",drops:3},{oil:"红橘",drops:2},{oil:"椰子油",drops:20}]},
{name:"一夜好眠精油浴",note:"",ingredients:[{oil:"安宁神气",drops:1},{oil:"岩兰草",drops:1},{oil:"乐释",drops:1},{oil:"薰衣草",drops:1},{oil:"乳香",drops:1},{oil:"安定情绪",drops:1},{oil:"椰子油",drops:10}]},
{name:"生发",note:"",ingredients:[{oil:"椒样薄荷",drops:1},{oil:"茶树",drops:1},{oil:"迷迭香",drops:1},{oil:"丝柏",drops:2},{oil:"生姜",drops:1},{oil:"雪松",drops:2},{oil:"薰衣草",drops:1},{oil:"乳香",drops:2},{oil:"安定情绪",drops:1},{oil:"椰子油",drops:15}]},
{name:"湿疹舒缓",note:"",ingredients:[{oil:"广藿香",drops:1},{oil:"绿薄荷",drops:1},{oil:"麦卢卡",drops:1},{oil:"永久花",drops:1},{oil:"蓝艾菊",drops:1},{oil:"椰子油",drops:10}]},
{name:"乳腺疏通",note:"",ingredients:[{oil:"乳香",drops:1},{oil:"薰衣草",drops:1},{oil:"丁香花蕾",drops:1},{oil:"柑橘清新",drops:1},{oil:"椰子油",drops:10}]},
{name:"缓解酸痛精油刮痧",note:"",ingredients:[{oil:"椒样薄荷",drops:1},{oil:"舒缓",drops:2},{oil:"芳香调理",drops:1},{oil:"冬青",drops:1},{oil:"柠檬草",drops:1},{oil:"生姜",drops:2},{oil:"茶树",drops:1},{oil:"乳香",drops:1},{oil:"椰子油",drops:10}]},
{name:"灰指甲",note:"",ingredients:[{oil:"西班牙牛至",drops:1},{oil:"椰子油",drops:6}]},
{name:"白发转黑",note:"",ingredients:[{oil:"乳香",drops:2},{oil:"快乐鼠尾草",drops:1},{oil:"依兰依兰",drops:1},{oil:"生姜",drops:1},{oil:"薰衣草",drops:1},{oil:"扁柏",drops:1},{oil:"雪松",drops:1},{oil:"椰子油",drops:10}]},
{name:"紫外线修复",note:"有艾草时可加入艾草",ingredients:[{oil:"乳香",drops:1},{oil:"西洋蓍草",drops:1},{oil:"蓝艾菊",drops:1},{oil:"麦卢卡",drops:1},{oil:"侧柏",drops:1},{oil:"椰子油",drops:15}]},
{name:"瘦身带脉",note:"",ingredients:[{oil:"丝柏",drops:1},{oil:"圆柚",drops:1},{oil:"新瑞活力",drops:1},{oil:"永久花",drops:1},{oil:"黑胡椒",drops:1},{oil:"姜黄",drops:1},{oil:"柠檬",drops:1},{oil:"乳香",drops:1},{oil:"椰子油",drops:15}]},
{name:"私密护理",note:"",ingredients:[{oil:"西洋蓍草",drops:2},{oil:"没药",drops:2},{oil:"快乐鼠尾草",drops:1},{oil:"依兰依兰",drops:1},{oil:"茶树",drops:0.5},{oil:"丝柏",drops:0.5},{oil:"椰子油",drops:8},{oil:"植物空胶囊",drops:1}]},
{name:"脚气/头皮屑",note:"",ingredients:[{oil:"茶树",drops:1},{oil:"椰子油",drops:6}]},
{name:"痘痘",note:"",ingredients:[{oil:"广藿香",drops:1},{oil:"姜黄",drops:1},{oil:"西洋蓍草",drops:2},{oil:"茶树",drops:1},{oil:"麦卢卡",drops:1},{oil:"椰子油",drops:20}]},
{name:"淋巴排毒",note:"",ingredients:[{oil:"乳香",drops:1},{oil:"丝柏",drops:1},{oil:"圆柚",drops:1},{oil:"迷迭香",drops:1},{oil:"杜松浆果",drops:1},{oil:"椰子油",drops:10}]},
{name:"驱寒/祛湿精油浴",note:"有艾草时可加入",ingredients:[{oil:"杜松浆果",drops:3},{oil:"广藿香",drops:3},{oil:"生姜",drops:2},{oil:"椰子油",drops:20}]},
{name:"荷尔蒙调节/更年期护理精油浴",note:"",ingredients:[{oil:"依兰依兰",drops:2},{oil:"温柔呵护",drops:2},{oil:"天竺葵",drops:2},{oil:"快乐鼠尾草",drops:2},{oil:"岩兰草",drops:1},{oil:"椰子油",drops:10}]},
{name:"情绪管理",note:"",ingredients:[{oil:"抚慰",drops:1},{oil:"热情",drops:1},{oil:"欢欣",drops:1},{oil:"鼓舞",drops:1},{oil:"宽容",drops:1},{oil:"静谧",drops:1},{oil:"椰子油",drops:10}]},
{name:"发膜",note:"",ingredients:[{oil:"乳香",drops:2},{oil:"茶树",drops:2},{oil:"生姜",drops:2}]},
{name:"脾胃养护1",note:"",tags:[],ingredients:[{oil:"生姜",drops:10},{oil:"红橘",drops:20},{oil:"乐活",drops:20},{oil:"广藿香",drops:15},{oil:"岩兰草",drops:10}]},
{name:"脾胃养护2",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"红橘",drops:10},{oil:"生姜",drops:10},{oil:"乐活",drops:15}]},
{name:"招财开运油",note:"",tags:[],ingredients:[{oil:"夏威夷檀香",drops:1},{oil:"岩兰草",drops:1},{oil:"天竺葵",drops:2},{oil:"佛手柑",drops:10},{oil:"野橘",drops:20}]},
{name:"植物热玛吉(玫瑰纯油版)",note:"",tags:[],ingredients:[{oil:"西洋蓍草",drops:20},{oil:"玫瑰",drops:5},{oil:"丝柏",drops:10},{oil:"永久花",drops:5},{oil:"乳香",drops:10}]},
{name:"植物热玛吉(玫瑰呵护版)",note:"",tags:[],ingredients:[{oil:"西洋蓍草",drops:20},{oil:"玫瑰",drops:5},{oil:"丝柏",drops:10},{oil:"永久花",drops:5},{oil:"乳香",drops:10}]},
{name:"十全大补",note:"",tags:[],ingredients:[{oil:"快乐鼠尾草",drops:15},{oil:"花样年华焕肤油",drops:10},{oil:"温柔呵护",drops:8},{oil:"玫瑰呵护",drops:15},{oil:"茉莉呵护",drops:15},{oil:"温柔呵护",drops:10}]},
{name:"豪华头疗",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:25},{oil:"丝柏",drops:15},{oil:"夏威夷檀香",drops:10},{oil:"完美修护",drops:20},{oil:"乳香",drops:20},{oil:"生姜",drops:15},{oil:"雪松",drops:15},{oil:"马郁兰",drops:10},{oil:"安定情绪",drops:20},{oil:"依兰依兰",drops:10}]},
{name:"酸痛包1",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"舒缓",drops:10},{oil:"芳香调理",drops:5},{oil:"冬青",drops:10},{oil:"柠檬草",drops:5},{oil:"生姜",drops:5},{oil:"西班牙牛至",drops:5},{oil:"乳香",drops:5}]},
{name:"酸痛包2",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"舒缓",drops:5},{oil:"芳香调理",drops:5},{oil:"柠檬草",drops:5},{oil:"生姜",drops:5},{oil:"茶树",drops:5},{oil:"乳香",drops:5}]},
{name:"明目青睐1",note:"",tags:[],ingredients:[{oil:"柠檬草",drops:6},{oil:"乳香",drops:10},{oil:"永久花",drops:9},{oil:"花样年华焕肤油",drops:10},{oil:"快乐鼠尾草",drops:10}]},
{name:"明目青睐2",note:"",tags:[],ingredients:[{oil:"芳香调理",drops:3},{oil:"柠檬草",drops:3},{oil:"快乐鼠尾草",drops:7},{oil:"乳香",drops:7}]},
{name:"带脉排毒瘦腰1",note:"",tags:[],ingredients:[{oil:"广藿香",drops:5},{oil:"黑胡椒",drops:10},{oil:"天竺葵",drops:20},{oil:"新瑞活力",drops:30},{oil:"丝柏",drops:30},{oil:"乳香",drops:20},{oil:"杜松浆果",drops:20}]},
{name:"带脉瘦腰2",note:"",tags:[],ingredients:[{oil:"杜松浆果",drops:5},{oil:"生姜",drops:5},{oil:"圆柚",drops:10},{oil:"丝柏",drops:10},{oil:"乳香",drops:7},{oil:"天竺葵",drops:5},{oil:"新瑞活力",drops:10}]},
{name:"缓解头痛(强)",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:10},{oil:"薰衣草",drops:10},{oil:"罗勒",drops:15},{oil:"马郁兰",drops:15},{oil:"舒缓",drops:50}]},
{name:"1清咽止咳",note:"",tags:[],ingredients:[{oil:"没药",drops:10},{oil:"乳香",drops:15},{oil:"尤加利",drops:15},{oil:"小豆蔻",drops:10},{oil:"顺畅呼吸",drops:20},{oil:"西伯利亚冷杉",drops:10}]},
{name:"清咽止咳2",note:"",tags:[],ingredients:[{oil:"茶树",drops:5},{oil:"乳香",drops:10},{oil:"保卫",drops:5},{oil:"顺畅呼吸",drops:10}]},
{name:"提升免疫(强)",note:"",tags:[],ingredients:[{oil:"西班牙牛至",drops:5},{oil:"侧柏",drops:10},{oil:"百里香",drops:10},{oil:"柠檬草",drops:10},{oil:"乳香",drops:20},{oil:"茶树",drops:10},{oil:"保卫",drops:10}]},
{name:"护肝排毒(强)",note:"",tags:[],ingredients:[{oil:"柠檬",drops:20},{oil:"元气",drops:20},{oil:"当归",drops:1},{oil:"芹菜籽",drops:10},{oil:"天竺葵",drops:9},{oil:"迷迭香",drops:15}]},
{name:"强心护心(强)",note:"",tags:[],ingredients:[{oil:"百里香",drops:10},{oil:"香蜂草",drops:15},{oil:"古巴香脂",drops:20},{oil:"依兰依兰",drops:15},{oil:"快乐鼠尾草",drops:15}]},
{name:"通鼻消炎1",note:"",tags:[],ingredients:[{oil:"茶树",drops:5},{oil:"椒样薄荷",drops:5},{oil:"乳香",drops:10},{oil:"尤加利",drops:10},{oil:"蓝艾菊",drops:5},{oil:"迷迭香",drops:10},{oil:"顺畅呼吸",drops:20}]},
{name:"通鼻消炎2",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"茶树",drops:5},{oil:"尤加利",drops:10},{oil:"顺畅呼吸",drops:15}]},
{name:"过敏湿疹(强)",note:"",tags:[],ingredients:[{oil:"罗马洋甘菊",drops:10},{oil:"蓝艾菊",drops:20},{oil:"薰衣草",drops:10},{oil:"香蜂草",drops:15},{oil:"绿薄荷",drops:10},{oil:"永久花",drops:10},{oil:"广藿香",drops:15},{oil:"乳香",drops:10}]},
{name:"强肾化水(强)",note:"",tags:[],ingredients:[{oil:"茉莉呵护",drops:10},{oil:"檀香",drops:15},{oil:"黑胡椒",drops:10},{oil:"依兰依兰",drops:10},{oil:"古巴香脂",drops:10},{oil:"杜松浆果",drops:20}]},
{name:"防疫病毒(强)",note:"",tags:[],ingredients:[{oil:"尤加利",drops:5},{oil:"侧柏",drops:5},{oil:"百里香",drops:5},{oil:"茶树",drops:10},{oil:"保卫",drops:10}]},
{name:"消富贵包(强)",note:"",tags:[],ingredients:[{oil:"芳香调理",drops:10},{oil:"永久花",drops:10},{oil:"生姜",drops:10},{oil:"舒缓",drops:5},{oil:"柠檬草",drops:10},{oil:"完美修护",drops:10},{oil:"乳香",drops:10}]},
{name:"淋巴排毒(强)",note:"",tags:[],ingredients:[{oil:"柑橘清新",drops:5},{oil:"丁香花蕾",drops:10},{oil:"柠檬草",drops:5},{oil:"没药",drops:20},{oil:"乳香",drops:20}]},
{name:"祛湿化滞(强)",note:"",tags:[],ingredients:[{oil:"黑胡椒",drops:8},{oil:"斯里兰卡肉桂皮",drops:6},{oil:"圆柚",drops:9},{oil:"岩兰草",drops:8},{oil:"广藿香",drops:9},{oil:"姜黄",drops:10},{oil:"西洋蓍草",drops:10}]},
{name:"肺部结节",note:"",tags:[],ingredients:[{oil:"完美修护",drops:10},{oil:"乳香",drops:10},{oil:"椒样薄荷",drops:5},{oil:"柑橘清新",drops:5},{oil:"赋活呼吸",drops:5},{oil:"天竺葵",drops:10}]},
{name:"尿路感染1",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:10},{oil:"杜松浆果",drops:5},{oil:"西班牙牛至",drops:5},{oil:"柠檬草",drops:5},{oil:"罗勒",drops:5},{oil:"保卫",drops:5},{oil:"柠檬",drops:5}]},
{name:"尿路感染2",note:"",tags:[],ingredients:[{oil:"西班牙牛至",drops:1},{oil:"柠檬草",drops:1}]},
{name:"养前列腺",note:"",tags:[],ingredients:[{oil:"檀香",drops:3},{oil:"杜松浆果",drops:8},{oil:"斯里兰卡肉桂皮",drops:2},{oil:"芳香调理",drops:6},{oil:"茉莉",drops:1},{oil:"快乐鼠尾草",drops:3},{oil:"五味子",drops:15},{oil:"百里香",drops:2}]},
{name:"皮外损伤",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"茶树",drops:10},{oil:"薰衣草",drops:8},{oil:"没药",drops:4},{oil:"永久花",drops:4}]},
{name:"消脂肪肝",note:"",tags:[],ingredients:[{oil:"杜松浆果",drops:5},{oil:"元气",drops:5},{oil:"乳香",drops:2},{oil:"柠檬",drops:3},{oil:"柠檬草",drops:5},{oil:"新瑞活力",drops:10}]},
{name:"平衡血糖",note:"",tags:[],ingredients:[{oil:"新瑞活力",drops:10},{oil:"薰衣草",drops:5},{oil:"斯里兰卡肉桂皮",drops:3},{oil:"迷迭香",drops:3},{oil:"芫荽",drops:3},{oil:"山鸡椒",drops:3},{oil:"天竺葵",drops:3}]},
{name:"肚痛腹泻",note:"",tags:[],ingredients:[{oil:"乐活",drops:16},{oil:"柠檬草",drops:8},{oil:"生姜",drops:6},{oil:"西班牙牛至",drops:6},{oil:"罗勒",drops:4}]},
{name:"韧带扭伤",note:"",tags:[],ingredients:[{oil:"芳香调理",drops:8},{oil:"姜黄",drops:5},{oil:"柠檬草",drops:8},{oil:"西班牙牛至",drops:3},{oil:"永久花",drops:5},{oil:"罗勒",drops:2},{oil:"古巴香脂",drops:6}]},
{name:"血脂稳定",note:"",tags:[],ingredients:[{oil:"新瑞活力",drops:10},{oil:"柠檬草",drops:5},{oil:"永久花",drops:5},{oil:"小茴香",drops:5},{oil:"罗勒",drops:5},{oil:"元气",drops:5}]},
{name:"尿床尿频",note:"",tags:[],ingredients:[{oil:"杜松浆果",drops:16},{oil:"柠檬草",drops:12},{oil:"侧柏",drops:8},{oil:"檀香",drops:4}]},
{name:"冻疮修复",note:"",tags:[],ingredients:[{oil:"乳香",drops:5},{oil:"薰衣草",drops:5},{oil:"黑胡椒",drops:3},{oil:"没药",drops:3},{oil:"迷迭香",drops:5},{oil:"生姜",drops:5},{oil:"天竺葵",drops:10}]},
{name:"皮下囊肿",note:"",tags:[],ingredients:[{oil:"西洋蓍草",drops:5},{oil:"乳香",drops:10},{oil:"百里香",drops:5},{oil:"西班牙牛至",drops:5},{oil:"广藿香",drops:10},{oil:"丁香花蕾",drops:5}]},
{name:"疏肝解郁",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:5},{oil:"芹菜籽",drops:2},{oil:"岩兰草",drops:5},{oil:"元气",drops:8},{oil:"佛手柑",drops:5},{oil:"罗马洋甘菊",drops:5}]},
{name:"大脑抗衰",note:"",tags:[],ingredients:[{oil:"特瑞活力",drops:10},{oil:"完美修护",drops:10},{oil:"乳香",drops:10},{oil:"迷迭香",drops:5},{oil:"乐释",drops:5},{oil:"广藿香",drops:5},{oil:"古巴香脂",drops:5}]},
{name:"神经麻木",note:"",tags:[],ingredients:[{oil:"特瑞活力",drops:10},{oil:"完美修护",drops:10},{oil:"永久花",drops:5},{oil:"百里香",drops:5},{oil:"古巴香脂",drops:10},{oil:"马郁兰",drops:5},{oil:"乳香",drops:5}]},
{name:"口唇疱疹1",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:5},{oil:"罗马洋甘菊",drops:7},{oil:"茶树",drops:5},{oil:"乳香",drops:5},{oil:"玫瑰草",drops:3},{oil:"没药",drops:5}]},
{name:"口唇疱疹2",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:6},{oil:"罗马洋甘菊",drops:6},{oil:"罗文莎叶",drops:6}]},
{name:"春季用油",note:"",tags:[],ingredients:[{oil:"黑云杉",drops:3},{oil:"柠檬草",drops:5},{oil:"杜松浆果",drops:5},{oil:"芹菜籽",drops:5},{oil:"天竺葵",drops:5},{oil:"莱姆",drops:10},{oil:"元气",drops:10}]},
{name:"夏季用油",note:"",tags:[],ingredients:[{oil:"生姜",drops:5},{oil:"杜松浆果",drops:5},{oil:"丝柏",drops:5},{oil:"圆柚",drops:5},{oil:"斯里兰卡肉桂皮",drops:3},{oil:"西伯利亚冷杉",drops:5},{oil:"广藿香",drops:7},{oil:"乐活",drops:10}]},
{name:"秋季用油",note:"",tags:[],ingredients:[{oil:"西伯利亚冷杉",drops:5},{oil:"茶树",drops:5},{oil:"小豆蔻",drops:3},{oil:"丁香花蕾",drops:2},{oil:"迷迭香",drops:3},{oil:"乳香",drops:7},{oil:"顺畅呼吸",drops:10}]},
{name:"冬季用油",note:"",tags:[],ingredients:[{oil:"茉莉呵护",drops:10},{oil:"生姜",drops:5},{oil:"雪松",drops:5},{oil:"黑胡椒",drops:2},{oil:"檀香",drops:3},{oil:"五味子",drops:3},{oil:"杜松浆果",drops:5},{oil:"天竺葵",drops:5},{oil:"乳香",drops:5}]},
{name:"祛湿丸子",note:"",tags:[],ingredients:[{oil:"斯里兰卡肉桂皮",drops:1},{oil:"圆柚",drops:2},{oil:"姜黄",drops:2},{oil:"黑胡椒",drops:1},{oil:"岩兰草",drops:2},{oil:"广藿香",drops:2},{oil:"西洋蓍草",drops:2}]},
{name:"三伏排湿",note:"",tags:[],ingredients:[{oil:"杜松浆果",drops:5},{oil:"芳香调理",drops:5},{oil:"圆柚",drops:5},{oil:"元气",drops:5},{oil:"新瑞活力",drops:5},{oil:"生姜",drops:5},{oil:"广藿香",drops:5}]},
{name:"三伏晒背",note:"",tags:[],ingredients:[{oil:"乳香",drops:5},{oil:"薰衣草",drops:5},{oil:"罗马洋甘菊",drops:5},{oil:"永久花",drops:5},{oil:"蓝艾菊",drops:3},{oil:"西洋蓍草",drops:10}]},
{name:"三伏百会",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"特瑞活力",drops:10},{oil:"古巴香脂",drops:10}]},
{name:"三伏八髎",note:"",tags:[],ingredients:[{oil:"生姜",drops:10},{oil:"乳香",drops:10},{oil:"印蒿",drops:3},{oil:"温柔呵护",drops:4},{oil:"完美修护",drops:6},{oil:"花样年华焕肤油",drops:4}]},
{name:"三伏大椎",note:"",tags:[],ingredients:[{oil:"斯里兰卡肉桂皮",drops:5},{oil:"檀香",drops:5},{oil:"西洋蓍草",drops:10},{oil:"生姜",drops:5},{oil:"乳香",drops:10},{oil:"广藿香",drops:10}]},
{name:"三伏檀中",note:"",tags:[],ingredients:[{oil:"夏威夷檀香",drops:2},{oil:"香蜂草",drops:30}]},
{name:"太伏太溪",note:"",tags:[],ingredients:[{oil:"元气",drops:15},{oil:"特瑞活力",drops:15}]},
{name:"三伏扶阳",note:"",tags:[],ingredients:[{oil:"乳香",drops:8},{oil:"广藿香",drops:3},{oil:"依兰依兰",drops:3},{oil:"生姜",drops:8},{oil:"檀香",drops:5},{oil:"斯里兰卡肉桂皮",drops:4},{oil:"当归",drops:2},{oil:"黑胡椒",drops:8},{oil:"柠檬草",drops:5},{oil:"杜松浆果",drops:8}]},
{name:"三阴交油",note:"",tags:[],ingredients:[{oil:"生姜",drops:6},{oil:"乳香",drops:10},{oil:"杜松浆果",drops:10},{oil:"快乐鼠尾草",drops:4}]},
{name:"八虚用油",note:"",tags:[],ingredients:[{oil:"元气",drops:5},{oil:"圆柚",drops:5},{oil:"新瑞活力",drops:5},{oil:"乳香",drops:5},{oil:"柠檬草",drops:5},{oil:"芳香调理",drops:5}]},
{name:"冬日小火炉",note:"",tags:[],ingredients:[{oil:"山鸡椒",drops:6},{oil:"生姜",drops:6},{oil:"黑胡椒",drops:6},{oil:"茉莉",drops:2},{oil:"柠檬草",drops:6},{oil:"天竺葵",drops:6},{oil:"温柔呵护",drops:10},{oil:"小茴香",drops:4}]},
{name:"女性内分泌",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:5},{oil:"乳香",drops:5},{oil:"依兰依兰",drops:5},{oil:"快乐鼠尾草",drops:5},{oil:"温柔呵护",drops:10},{oil:"温柔呵护",drops:5}]},
{name:"提升免疫力",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"茶树",drops:20},{oil:"保卫",drops:20}]},
{name:"面部精华",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:5},{oil:"丝柏",drops:5},{oil:"雪松",drops:5},{oil:"薰衣草",drops:5},{oil:"乳香",drops:5},{oil:"西洋蓍草",drops:10}]},
{name:"日常头疗",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"茶树",drops:5},{oil:"野橘",drops:5},{oil:"丝柏",drops:10},{oil:"生姜",drops:5},{oil:"雪松",drops:10},{oil:"薰衣草",drops:5},{oil:"乳香",drops:7},{oil:"安定情绪",drops:7}]},
{name:"肝肾保护",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:5},{oil:"生姜",drops:5},{oil:"杜松浆果",drops:5},{oil:"柠檬",drops:10},{oil:"元气",drops:5},{oil:"柠檬草",drops:5}]},
{name:"情绪能量油",note:"",tags:[],ingredients:[{oil:"乳香",drops:1},{oil:"柑橘绚烂",drops:10},{oil:"柑橘清新",drops:10},{oil:"安宁神气",drops:5},{oil:"安定情绪",drops:5}]},
{name:"养心宁神",note:"",tags:[],ingredients:[{oil:"依兰依兰",drops:5},{oil:"古巴香脂",drops:10},{oil:"乳香",drops:15}]},
{name:"安稳睡眠",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"薰衣草",drops:10},{oil:"安定情绪",drops:10}]},
{name:"过敏体质",note:"",tags:[],ingredients:[{oil:"罗马洋甘菊",drops:20},{oil:"椒样薄荷",drops:10},{oil:"薰衣草",drops:10},{oil:"柠檬",drops:10}]},
{name:"淋巴结节",note:"",tags:[],ingredients:[{oil:"完美修护",drops:7},{oil:"丝柏",drops:5},{oil:"柠檬草",drops:7},{oil:"乳香",drops:10},{oil:"芳香调理",drops:5}]},
{name:"甲状腺结节",note:"",tags:[],ingredients:[{oil:"芳香调理",drops:5},{oil:"椒样薄荷",drops:5},{oil:"丁香花蕾",drops:5},{oil:"柠檬草",drops:5},{oil:"完美修护",drops:10},{oil:"乳香",drops:10}]},
{name:"皮肤过敏",note:"",tags:[],ingredients:[{oil:"罗马洋甘菊",drops:8},{oil:"西洋蓍草",drops:10},{oil:"雪松",drops:5},{oil:"侧柏",drops:2},{oil:"茶树",drops:5},{oil:"乳香",drops:5}]},
{name:"止鼾安睡",note:"",tags:[],ingredients:[{oil:"安宁神气",drops:10},{oil:"罗勒",drops:5},{oil:"丝柏",drops:5},{oil:"道格拉斯冷杉",drops:5},{oil:"百里香",drops:5},{oil:"顺畅呼吸",drops:10}]},
{name:"哮喘缓解",note:"",tags:[],ingredients:[{oil:"顺畅呼吸",drops:10},{oil:"蓝艾菊",drops:5},{oil:"柠檬",drops:5},{oil:"薰衣草",drops:5},{oil:"椒样薄荷",drops:5},{oil:"赋活呼吸",drops:10}]},
{name:"带状疱疹",note:"",tags:[],ingredients:[{oil:"广藿香",drops:3},{oil:"佛手柑",drops:5},{oil:"天竺葵",drops:5},{oil:"丁香花蕾",drops:5},{oil:"百里香",drops:5},{oil:"香蜂草",drops:3},{oil:"完美修护",drops:7},{oil:"没药",drops:5},{oil:"乳香",drops:7}]},
{name:"夏日社痱",note:"",tags:[],ingredients:[{oil:"罗马洋甘菊",drops:8},{oil:"椒样薄荷",drops:8},{oil:"薰衣草",drops:6},{oil:"广藿香",drops:8}]},
{name:"耳聋耳鸣",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:5},{oil:"香蜂草",drops:3},{oil:"天竺葵",drops:5},{oil:"罗勒",drops:10},{oil:"永久花",drops:5},{oil:"乳香",drops:10}]},
{name:"减脂瘦身",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:5},{oil:"乳香",drops:8},{oil:"杜松浆果",drops:10},{oil:"圆柚",drops:10},{oil:"迷迭香",drops:7},{oil:"丝柏",drops:15},{oil:"新瑞活力",drops:20}]},
{name:"痔疮用油",note:"",tags:[],ingredients:[{oil:"没药",drops:10},{oil:"乳香",drops:10},{oil:"天竺葵",drops:10},{oil:"永久花",drops:10},{oil:"丝柏",drops:15},{oil:"茶树",drops:20}]},
{name:"静脉曲张1",note:"",tags:[],ingredients:[{oil:"甜茴香",drops:7},{oil:"丝柏",drops:15},{oil:"薰衣草",drops:10},{oil:"柠檬草",drops:8},{oil:"永久花",drops:5},{oil:"芳香调理",drops:5}]},
{name:"静脉曲张2",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:3},{oil:"黑胡椒",drops:5},{oil:"永久花",drops:8},{oil:"丝柏",drops:10}]},
{name:"痛风缓解",note:"",tags:[],ingredients:[{oil:"柠檬草",drops:8},{oil:"西伯利亚冷杉",drops:5},{oil:"芳香调理",drops:10},{oil:"舒缓",drops:5},{oil:"西班牙牛至",drops:3},{oil:"乳香",drops:5},{oil:"香蜂草",drops:3}]},
{name:"滑膜炎症",note:"",tags:[],ingredients:[{oil:"丝柏",drops:5},{oil:"生姜",drops:5},{oil:"椒样薄荷",drops:5},{oil:"罗勒",drops:5},{oil:"冬青",drops:3},{oil:"乳香",drops:10},{oil:"道格拉斯冷杉",drops:5}]},
{name:"腱鞘炎症",note:"",tags:[],ingredients:[{oil:"罗勒",drops:5},{oil:"丝柏",drops:5},{oil:"乳香",drops:5},{oil:"薰衣草",drops:5},{oil:"圆柚",drops:5},{oil:"柠檬草",drops:10}]},
{name:"腰椎滑脱",note:"",tags:[],ingredients:[{oil:"丝柏",drops:5},{oil:"舒缓",drops:5},{oil:"椒样薄荷",drops:5},{oil:"檀香",drops:5},{oil:"冬青",drops:5},{oil:"马郁兰",drops:5},{oil:"乳香",drops:5}]},
{name:"暖宫调经1",note:"",tags:[],ingredients:[{oil:"温柔呵护",drops:10},{oil:"生姜",drops:10},{oil:"黑胡椒",drops:10},{oil:"天竺葵",drops:5},{oil:"温柔呵护",drops:25},{oil:"快乐鼠尾草",drops:15}]},
{name:"暖宫调经2",note:"",tags:[],ingredients:[{oil:"温柔呵护",drops:10},{oil:"生姜",drops:6},{oil:"黑胡椒",drops:6},{oil:"天竺葵",drops:3},{oil:"温柔呵护",drops:17},{oil:"快乐鼠尾草",drops:10}]},
{name:"经期止痛",note:"",tags:[],ingredients:[{oil:"黑胡椒",drops:5},{oil:"舒缓",drops:10},{oil:"生姜",drops:5},{oil:"乳香",drops:10},{oil:"温柔呵护",drops:10},{oil:"温柔呵护",drops:20},{oil:"薰衣草",drops:10}]},
{name:"乳腺增生",note:"",tags:[],ingredients:[{oil:"完美修护",drops:10},{oil:"薰衣草",drops:5},{oil:"迷迭香",drops:5},{oil:"百里香",drops:5},{oil:"丁香花蕾",drops:5},{oil:"柑橘清新",drops:10}]},
{name:"丰胸挺拨",note:"",tags:[],ingredients:[{oil:"小茴香",drops:20},{oil:"依兰依兰",drops:10},{oil:"温柔呵护",drops:30},{oil:"西洋蓍草",drops:30},{oil:"快乐鼠尾草",drops:10}]},
{name:"私密紧致",note:"",tags:[],ingredients:[{oil:"玫瑰呵护",drops:10},{oil:"茉莉呵护",drops:10},{oil:"丝柏",drops:15},{oil:"依兰依兰",drops:10},{oil:"快乐鼠尾草",drops:15}]},
{name:"乳腺结节1",note:"",tags:[],ingredients:[{oil:"完美修护",drops:10},{oil:"柑橘清新",drops:5},{oil:"没药",drops:10},{oil:"姜黄",drops:10},{oil:"丁香花蕾",drops:5},{oil:"乳香",drops:20}]},
{name:"乳腺结节2",note:"",tags:[],ingredients:[{oil:"丁香花蕾",drops:10},{oil:"乳香",drops:5},{oil:"柑橘清新",drops:10},{oil:"完美修护",drops:10},{oil:"柠檬草",drops:5}]},
{name:"手脚冰凉",note:"",tags:[],ingredients:[{oil:"生姜",drops:20},{oil:"天竺葵",drops:10},{oil:"岩兰草",drops:10},{oil:"柠檬草",drops:10},{oil:"黑胡椒",drops:15},{oil:"野橘",drops:10}]},
{name:"修复腹直肌",note:"",tags:[],ingredients:[{oil:"丝柏",drops:10},{oil:"乳香",drops:5},{oil:"永久花",drops:5},{oil:"圆柚",drops:10},{oil:"广藿香",drops:10},{oil:"柠檬草",drops:10}]},
{name:"子宫肌瘤1",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:5},{oil:"完美修护",drops:10},{oil:"安定情绪",drops:5},{oil:"西班牙牛至",drops:3},{oil:"姜黄",drops:10},{oil:"天竺葵",drops:5},{oil:"永久花",drops:5},{oil:"乳香",drops:10}]},
{name:"子宫肌瘤2",note:"",tags:[],ingredients:[{oil:"乳香",drops:5},{oil:"罗勒",drops:6},{oil:"天竺葵",drops:10}]},
{name:"卵巢囊肿1",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:8},{oil:"完美修护",drops:5},{oil:"温柔呵护",drops:50},{oil:"斯里兰卡肉桂皮",drops:5},{oil:"丝柏",drops:8},{oil:"迷迭香",drops:5},{oil:"安定情绪",drops:8},{oil:"乳香",drops:6}]},
{name:"卵巢囊肿2",note:"",tags:[],ingredients:[{oil:"乳香",drops:2},{oil:"温柔呵护",drops:2},{oil:"西班牙牛至",drops:1},{oil:"快乐鼠尾草",drops:1}]},
{name:"美背",note:"",tags:[],ingredients:[{oil:"夏威夷檀香",drops:10},{oil:"黑云杉",drops:15},{oil:"雪松",drops:10},{oil:"依兰依兰",drops:10},{oil:"佛手柑",drops:15},{oil:"圆柚",drops:10},{oil:"玫瑰呵护",drops:15},{oil:"当归",drops:5},{oil:"乳香",drops:10}]},
{name:"减轻妊娠纹",note:"",tags:[],ingredients:[{oil:"永久花",drops:7},{oil:"广藿香",drops:5},{oil:"没药",drops:4},{oil:"乳香",drops:7},{oil:"薰衣草",drops:7},{oil:"完美修护",drops:10},{oil:"花样年华焕肤油",drops:10}]},
{name:"更年期症",note:"",tags:[],ingredients:[{oil:"柠檬",drops:10},{oil:"椒样薄荷",drops:8},{oil:"薰衣草",drops:10},{oil:"天竺葵",drops:10},{oil:"温柔呵护",drops:30},{oil:"快乐鼠尾草",drops:20},{oil:"罗马洋甘菊",drops:12}]},
{name:"妇科炎症",note:"",tags:[],ingredients:[{oil:"没药",drops:10},{oil:"斯里兰卡肉桂皮",drops:10},{oil:"迷迭香",drops:20},{oil:"完美修护",drops:10},{oil:"杜松浆果",drops:10}]},
{name:"备孕调理",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:5},{oil:"生姜",drops:5},{oil:"温柔呵护",drops:10},{oil:"快乐鼠尾草",drops:5},{oil:"温柔呵护",drops:10}]},
{name:"抗衰紧致",note:"",tags:[],ingredients:[{oil:"丝柏",drops:5},{oil:"乳香",drops:5},{oil:"夏威夷檀香",drops:5},{oil:"玫瑰",drops:3},{oil:"永久花",drops:5},{oil:"西洋蓍草",drops:20}]},
{name:"美白淡斑1",note:"",tags:[],ingredients:[{oil:"玫瑰呵护",drops:5},{oil:"丁香花蕾",drops:2},{oil:"夏威夷檀香",drops:5},{oil:"扁柏",drops:5},{oil:"永久花",drops:5},{oil:"岩兰草",drops:5},{oil:"黑云杉",drops:5},{oil:"芹菜籽",drops:8},{oil:"花样年华焕肤油",drops:10}]},
{name:"美白淡斑2",note:"",tags:[],ingredients:[{oil:"广藿香",drops:3},{oil:"芹菜籽",drops:3},{oil:"黑云杉",drops:3},{oil:"西洋蓍草",drops:3},{oil:"橙花呵护",drops:4},{oil:"花样年华焕肤油",drops:3}]},
{name:"水油平衡",note:"",tags:[],ingredients:[{oil:"新清肌调理",drops:15},{oil:"天竺葵",drops:3},{oil:"雪松",drops:5},{oil:"椒样薄荷",drops:5},{oil:"永久花",drops:3},{oil:"麦卢卡",drops:3},{oil:"薰衣草",drops:5},{oil:"茶树",drops:5}]},
{name:"敏感肌",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"夏威夷檀香",drops:2},{oil:"岩兰草",drops:3},{oil:"广藿香",drops:5},{oil:"罗马洋甘菊",drops:10}]},
{name:"防晒修复",note:"",tags:[],ingredients:[{oil:"罗马洋甘菊",drops:10},{oil:"乳香",drops:10},{oil:"椒样薄荷",drops:4},{oil:"永久花",drops:8},{oil:"柠檬草",drops:3},{oil:"薰衣草",drops:10},{oil:"夏威夷檀香",drops:5}]},
{name:"深层净化排毒",note:"",tags:[],ingredients:[{oil:"茶树呵护",drops:20},{oil:"杜松浆果",drops:10},{oil:"薰衣草",drops:20},{oil:"圆柚",drops:5},{oil:"蓝艾菊",drops:3},{oil:"丝柏",drops:3},{oil:"侧柏",drops:5},{oil:"雪松",drops:10}]},
{name:"蓝月光贵妇面霜",note:"",tags:[],ingredients:[{oil:"玫瑰呵护",drops:10},{oil:"芹菜籽",drops:2},{oil:"永久花",drops:2},{oil:"西洋蓍草",drops:10},{oil:"蓝艾菊",drops:5}]},
{name:"唇部保养",note:"",tags:[],ingredients:[{oil:"夏威夷檀香",drops:3},{oil:"没药",drops:3},{oil:"薰衣草",drops:5},{oil:"永久花",drops:3},{oil:"麦卢卡",drops:1},{oil:"乳香",drops:5}]},
{name:"皱纹推土机",note:"",tags:[],ingredients:[{oil:"花样年华焕肤油",drops:10},{oil:"桂花呵护",drops:6},{oil:"麦卢卡",drops:4},{oil:"永久花",drops:4},{oil:"黑云杉",drops:5},{oil:"夏威夷檀香",drops:5},{oil:"乳香",drops:10},{oil:"穗甘松",drops:6},{oil:"依兰依兰",drops:10}]},
{name:"祛除颈纹",note:"",tags:[],ingredients:[{oil:"夏威夷檀香",drops:5},{oil:"薰衣草",drops:5},{oil:"乳香",drops:10},{oil:"依兰依兰",drops:10},{oil:"穗甘松",drops:10}]},
{name:"蓝带神仙水",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"夏威夷檀香",drops:5},{oil:"蓝艾菊",drops:5},{oil:"永久花",drops:5},{oil:"西洋蓍草",drops:10}]},
{name:"全效紧致乳",note:"",tags:[],ingredients:[{oil:"花样年华焕肤油",drops:50},{oil:"橙花呵护",drops:50},{oil:"茉莉呵护",drops:50},{oil:"古巴香脂",drops:5},{oil:"西洋蓍草",drops:5},{oil:"雪松",drops:10},{oil:"玫瑰",drops:5}]},
{name:"眼袋黑眼圈",note:"",tags:[],ingredients:[{oil:"快乐鼠尾草",drops:5},{oil:"永久花",drops:2},{oil:"丝柏",drops:15},{oil:"乳香",drops:5},{oil:"西洋蓍草",drops:5}]},
{name:"清痘无痕",note:"",tags:[],ingredients:[{oil:"新清肌呵护",drops:20},{oil:"净化清新",drops:10},{oil:"薰衣草",drops:10},{oil:"花样年华焕肤油",drops:10},{oil:"茶树",drops:10}]},
{name:"脂肪粒",note:"",tags:[],ingredients:[{oil:"乳香",drops:5},{oil:"永久花",drops:3},{oil:"西班牙牛至",drops:3},{oil:"芫荽",drops:3},{oil:"雪松",drops:3},{oil:"茶树",drops:3},{oil:"罗马洋甘菊",drops:5}]},
{name:"植物蜂皮",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"橙花呵护",drops:10},{oil:"桂花呵护",drops:10},{oil:"永久花",drops:5},{oil:"没药",drops:3},{oil:"麦卢卡",drops:3},{oil:"花样年华焕肤油",drops:10}]},
{name:"植物水光针",note:"",tags:[],ingredients:[{oil:"西洋蓍草",drops:10},{oil:"芹菜籽",drops:5},{oil:"玫瑰",drops:2},{oil:"乳香",drops:5},{oil:"木兰呵护",drops:20}]},
{name:"早C精华",note:"",tags:[],ingredients:[{oil:"西洋蓍草",drops:20},{oil:"永久花",drops:5},{oil:"夏威夷檀香",drops:3},{oil:"穗甘松",drops:3},{oil:"乳香",drops:5},{oil:"玫瑰呵护",drops:10}]},
{name:"晚A精华",note:"",tags:[],ingredients:[{oil:"花样年华焕肤油",drops:10},{oil:"麦卢卡",drops:3},{oil:"芹菜籽",drops:5},{oil:"天竺葵",drops:5},{oil:"茉莉呵护",drops:10},{oil:"橙花呵护",drops:10},{oil:"雪松",drops:5}]},
{name:"消鸡皮肤",note:"",tags:[],ingredients:[{oil:"圆柚",drops:5},{oil:"薰衣草",drops:5},{oil:"枫香",drops:5},{oil:"柠檬香桃木",drops:5},{oil:"安定情绪",drops:5},{oil:"天竺葵",drops:6}]},
{name:"解荨麻疹",note:"",tags:[],ingredients:[{oil:"没药",drops:2},{oil:"乳香",drops:4},{oil:"椒样薄荷",drops:4},{oil:"罗马洋甘菊",drops:6},{oil:"薰衣草",drops:4},{oil:"蓝艾菊",drops:6}]},
{name:"保湿焕肤",note:"",tags:[],ingredients:[{oil:"永久花",drops:5},{oil:"没药",drops:5},{oil:"薰衣草",drops:5},{oil:"夏威夷檀香",drops:5},{oil:"玫瑰",drops:3},{oil:"天竺葵",drops:5},{oil:"玫瑰草",drops:5}]},
{name:"奶油桂花手霜",note:"",tags:[],ingredients:[{oil:"西洋蓍草",drops:10},{oil:"乳香",drops:10},{oil:"桂花",drops:8},{oil:"薰衣草",drops:8},{oil:"天竺葵",drops:8},{oil:"椰风香草",drops:8}]},
{name:"养护指甲",note:"",tags:[],ingredients:[{oil:"夏威夷檀香",drops:10},{oil:"罗马洋甘菊",drops:10},{oil:"没药",drops:10}]},
{name:"皮肤皲裂/脚后跟干裂",note:"",tags:[],ingredients:[{oil:"天竺葵",drops:10},{oil:"乳香",drops:10},{oil:"薰衣草",drops:8},{oil:"没药",drops:8}]},
{name:"黑绷带",note:"",tags:[],ingredients:[{oil:"广藿香",drops:6},{oil:"岩兰草",drops:6},{oil:"穗甘松",drops:6},{oil:"夏威夷檀香",drops:10},{oil:"永久花",drops:10},{oil:"没药",drops:10},{oil:"蓝艾菊",drops:10}]},
{name:"眉毛睫毛增长滋养液",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"迷迭香",drops:8},{oil:"雪松",drops:8},{oil:"依兰依兰",drops:6},{oil:"薰衣草",drops:8}]},
{name:"天鹅颈",note:"",tags:[],ingredients:[{oil:"穗甘松",drops:5},{oil:"柠檬草",drops:5},{oil:"完美修护",drops:10},{oil:"舒缓",drops:10},{oil:"依兰依兰",drops:10},{oil:"丝柏",drops:10},{oil:"芳香调理",drops:10},{oil:"乳香",drops:10}]},
{name:"收缩毛孔",note:"",tags:[],ingredients:[{oil:"迷迭香",drops:5},{oil:"依兰依兰",drops:7},{oil:"天竺葵",drops:10},{oil:"丝柏",drops:10}]},
{name:"酒糟鼻",note:"",tags:[],ingredients:[{oil:"乳香",drops:7},{oil:"薰衣草",drops:5},{oil:"岩兰草",drops:8},{oil:"罗马洋甘菊",drops:5},{oil:"茶树",drops:8}]},
{name:"疤痕修复",note:"",tags:[],ingredients:[{oil:"没药",drops:4},{oil:"乳香",drops:7},{oil:"薰衣草",drops:7},{oil:"永久花",drops:7},{oil:"花样年华焕肤油",drops:10}]},
{name:"太阳油",note:"",tags:[],ingredients:[{oil:"依兰依兰",drops:15},{oil:"广藿香",drops:15},{oil:"雪松",drops:15},{oil:"檀香",drops:15},{oil:"杜松浆果",drops:15}]},
{name:"月亮油",note:"",tags:[],ingredients:[{oil:"温柔呵护",drops:15},{oil:"茉莉呵护",drops:10},{oil:"玫瑰呵护",drops:10},{oil:"佛手柑",drops:5},{oil:"小茴香",drops:5},{oil:"依兰依兰",drops:7},{oil:"快乐鼠尾草",drops:10}]},
{name:"阿育吠陀",note:"",tags:[],ingredients:[{oil:"姜黄",drops:5},{oil:"生姜",drops:5},{oil:"杜松浆果",drops:10},{oil:"苦橙叶",drops:10},{oil:"迷迭香",drops:5},{oil:"柠檬",drops:10},{oil:"圆柚",drops:5}]},
{name:"缓解头痛",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"薰衣草",drops:5},{oil:"生姜",drops:5},{oil:"马郁兰",drops:7},{oil:"舒缓",drops:15}]},
{name:"白发变黑(女士)",note:"",tags:[],ingredients:[{oil:"乳香",drops:7},{oil:"快乐鼠尾草",drops:10},{oil:"依兰依兰",drops:5},{oil:"生姜",drops:7},{oil:"薰衣草",drops:5}]},
{name:"白发变黑(男士)",note:"",tags:[],ingredients:[{oil:"迷迭香",drops:10},{oil:"薰衣草",drops:10},{oil:"雪松",drops:10},{oil:"永久花",drops:5}]},
{name:"头屑清爽",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:10},{oil:"雪松",drops:20},{oil:"迷迭香",drops:10},{oil:"丝柏",drops:15},{oil:"茶树",drops:20}]},
{name:"口腔溃疡1",note:"",tags:[],ingredients:[{oil:"丁香花蕾",drops:5},{oil:"茶树",drops:10},{oil:"薰衣草",drops:5},{oil:"没药",drops:5},{oil:"乳香",drops:5}]},
{name:"口腔溃疡2",note:"",tags:[],ingredients:[{oil:"永久花",drops:5},{oil:"茶树",drops:10},{oil:"麦卢卡",drops:10},{oil:"月桂叶",drops:10},{oil:"佛手柑",drops:20},{oil:"薰衣草",drops:20}]},
{name:"扁桃腺炎",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"百里香",drops:5},{oil:"小豆蔻",drops:5},{oil:"尤加利",drops:5},{oil:"保卫",drops:5},{oil:"顺畅呼吸",drops:5}]},
{name:"慢性阑尾炎",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"柠檬草",drops:5},{oil:"西班牙牛至",drops:3},{oil:"保卫",drops:3},{oil:"乐活",drops:10},{oil:"新瑞活力",drops:5},{oil:"完美修护",drops:5},{oil:"乳香",drops:10}]},
{name:"结石、胆囊炎",note:"",tags:[],ingredients:[{oil:"罗勒",drops:5},{oil:"冬青",drops:5},{oil:"柠檬",drops:10},{oil:"天竺葵",drops:5},{oil:"迷迭香",drops:5},{oil:"圆柚",drops:10},{oil:"莱姆",drops:10},{oil:"椒样薄荷",drops:5},{oil:"乳香",drops:10}]},
{name:"脚气、灰指甲",note:"",tags:[],ingredients:[{oil:"香蜂草",drops:3},{oil:"保卫",drops:7},{oil:"茶树",drops:10},{oil:"西班牙牛至",drops:10},{oil:"广藿香",drops:5}]},
{name:"甲亢、甲减",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:9},{oil:"丁香花蕾",drops:12},{oil:"柠檬草",drops:15},{oil:"没药",drops:12},{oil:"乳香",drops:12}]},
{name:"降压仪式",note:"",tags:[],ingredients:[{oil:"依兰依兰",drops:10},{oil:"香蜂草",drops:8},{oil:"马郁兰",drops:15},{oil:"乳香",drops:15},{oil:"薰衣草",drops:12}]},
{name:"伤筋动骨",note:"",tags:[],ingredients:[{oil:"完美修护",drops:10},{oil:"乳香",drops:10},{oil:"生姜",drops:10},{oil:"西班牙牛至",drops:10},{oil:"冬青",drops:15},{oil:"柠檬草",drops:15},{oil:"舒缓",drops:5}]},
{name:"干眼症",note:"",tags:[],ingredients:[{oil:"薰衣草",drops:5},{oil:"乳香",drops:5},{oil:"罗马洋甘菊",drops:5},{oil:"快乐鼠尾草",drops:10},{oil:"古巴香脂",drops:5},{oil:"完美修护",drops:5}]},
{name:"儿童抚触",note:"",tags:[],ingredients:[{oil:"生姜",drops:3},{oil:"保卫",drops:3},{oil:"西伯利亚冷杉",drops:5},{oil:"乐活",drops:5},{oil:"没药",drops:5},{oil:"乳香",drops:5}]},
{name:"儿童脾胃",note:"",tags:[],ingredients:[{oil:"红橘",drops:10},{oil:"小茴香",drops:2},{oil:"生姜",drops:3},{oil:"乐活",drops:5}]},
{name:"视力养护",note:"",tags:[],ingredients:[{oil:"乳香",drops:3},{oil:"柠檬草",drops:3},{oil:"永久花",drops:2},{oil:"快乐鼠尾草",drops:5}]},
{name:"驱蚊喷雾",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"柠檬尤加利",drops:5},{oil:"尤加利",drops:5},{oil:"香茅",drops:5},{oil:"天竺葵",drops:3},{oil:"薰衣草",drops:10},{oil:"天然防护",drops:20}]},
{name:"个子高高",note:"",tags:[],ingredients:[{oil:"檀香",drops:3},{oil:"永久花",drops:3},{oil:"芳香调理",drops:2},{oil:"乳香",drops:5},{oil:"西伯利亚冷杉",drops:5}]},
{name:"清咽止咳",note:"",tags:[],ingredients:[{oil:"乳香",drops:5},{oil:"西伯利亚冷杉",drops:3},{oil:"尤加利",drops:3},{oil:"保卫",drops:2},{oil:"顺畅呼吸",drops:5}]},
{name:"通鼻消炎",note:"",tags:[],ingredients:[{oil:"茶树",drops:2},{oil:"椒样薄荷",drops:3},{oil:"乳香",drops:3},{oil:"蓝艾菊",drops:1},{oil:"尤加利",drops:3},{oil:"迷迭香",drops:2},{oil:"顺畅呼吸",drops:5}]},
{name:"蚊虫叮咬1",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"茶树",drops:3},{oil:"薰衣草",drops:3},{oil:"乳香",drops:2},{oil:"罗勒",drops:3},{oil:"天然防护",drops:5}]},
{name:"蚊虫叮咬2",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:5},{oil:"香茅",drops:5},{oil:"柠檬草",drops:5},{oil:"天然防护",drops:10}]},
{name:"学霸神助",note:"",tags:[],ingredients:[{oil:"檀香",drops:2},{oil:"完美修护",drops:2},{oil:"椒样薄荷",drops:4},{oil:"全神贯注",drops:8},{oil:"罗勒",drops:2},{oil:"迷迭香",drops:4},{oil:"乳香",drops:4}]},
{name:"磨牙安抚",note:"",tags:[],ingredients:[{oil:"佛手柑",drops:5},{oil:"罗勒",drops:5},{oil:"芳香调理",drops:5},{oil:"古巴香脂",drops:5},{oil:"安定情绪",drops:5}]},
{name:"免疫助力",note:"",tags:[],ingredients:[{oil:"野橘",drops:5},{oil:"茶树",drops:5},{oil:"侧柏",drops:3},{oil:"姜黄",drops:3},{oil:"保卫",drops:5},{oil:"乳香",drops:5}]},
{name:"腺样体肥大",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:3},{oil:"百里香",drops:2},{oil:"茶树",drops:3},{oil:"尤加利",drops:2},{oil:"雪松",drops:3},{oil:"顺畅呼吸",drops:5}]},
{name:"退烧方案",note:"",tags:[],ingredients:[{oil:"顺畅呼吸",drops:3},{oil:"保卫",drops:5},{oil:"椒样薄荷",drops:5},{oil:"茶树",drops:3},{oil:"薰衣草",drops:2},{oil:"乳香",drops:5}]},
{name:"手足口症",note:"",tags:[],ingredients:[{oil:"茶树",drops:3},{oil:"香蜂草",drops:3},{oil:"丁香花蕾",drops:3},{oil:"月桂叶",drops:3},{oil:"保卫",drops:3},{oil:"古巴香脂",drops:3}]},
{name:"湿疹修复",note:"",tags:[],ingredients:[{oil:"蓝艾菊",drops:2},{oil:"茶树",drops:2},{oil:"乳香",drops:3},{oil:"天竺葵",drops:3},{oil:"广藿香",drops:3},{oil:"罗马洋甘菊",drops:5}]},
{name:"抽动症",note:"",tags:[],ingredients:[{oil:"罗马洋甘菊",drops:3},{oil:"安定情绪",drops:5},{oil:"雪松",drops:3},{oil:"岩兰草",drops:3},{oil:"乳香",drops:5}]},
{name:"头疗生发",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:10},{oil:"茶树",drops:10},{oil:"迷迭香",drops:10},{oil:"丝柏",drops:20},{oil:"生姜",drops:10},{oil:"雪松",drops:20},{oil:"薰衣草",drops:10},{oil:"乳香",drops:14},{oil:"安定情绪",drops:14}]},
{name:"1、呼吸系统细胞律动含香蜂草",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"茶树",drops:4},{oil:"芳香调理",drops:4},{oil:"顺畅呼吸",drops:4},{oil:"迷迭香",drops:4},{oil:"尤加利",drops:4},{oil:"香蜂草",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"1、呼吸系统细胞律动不含香蜂草",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"茶树",drops:4},{oil:"芳香调理",drops:4},{oil:"顺畅呼吸",drops:4},{oil:"迷迭香",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"2、神经系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"百里香",drops:4},{oil:"丁香花蕾",drops:4},{oil:"芳香调理",drops:4},{oil:"柠檬草",drops:4},{oil:"香蜂草",drops:4},{oil:"广藿香",drops:4},{oil:"佛手柑",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"3、消化系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"百里香",drops:4},{oil:"芳香调理",drops:4},{oil:"佛手柑",drops:4},{oil:"芫荽",drops:4},{oil:"乐活",drops:4},{oil:"生姜",drops:4},{oil:"天竺葵",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"4、骨骼系统细胞律动炎症控制",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"茶树",drops:4},{oil:"冬青",drops:4},{oil:"芳香调理",drops:4},{oil:"柠檬草",drops:4},{oil:"西伯利亚冷杉",drops:4},{oil:"舒缓",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"5、淋巴系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"迷迭香",drops:4},{oil:"芳香调理",drops:4},{oil:"柠檬草",drops:4},{oil:"新瑞活力",drops:4},{oil:"元气",drops:4},{oil:"柠檬",drops:4},{oil:"圆柚",drops:4},{oil:"生姜",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"6、生殖系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"西班牙牛至",drops:4},{oil:"茶树",drops:4},{oil:"芳香调理",drops:4},{oil:"薰衣草",drops:4},{oil:"广藿香",drops:4},{oil:"芫荽",drops:4},{oil:"快乐鼠尾草",drops:4},{oil:"檀香",drops:4},{oil:"丁香花蕾",drops:4},{oil:"依兰依兰",drops:4},{oil:"天竺葵",drops:4},{oil:"温柔呵护",drops:4},{oil:"元气",drops:4},{oil:"温柔呵护",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"7、免疫系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"西班牙牛至",drops:4},{oil:"茶树",drops:4},{oil:"芳香调理",drops:4},{oil:"西伯利亚冷杉",drops:4},{oil:"丝柏",drops:4},{oil:"柠檬",drops:4},{oil:"莱姆",drops:4},{oil:"圆柚",drops:4},{oil:"保卫",drops:4},{oil:"丁香花蕾",drops:4},{oil:"百里香",drops:4},{oil:"元气",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"8、循环系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"百里香",drops:4},{oil:"芳香调理",drops:4},{oil:"柠檬草",drops:4},{oil:"保卫",drops:4},{oil:"马郁兰",drops:4},{oil:"罗勒",drops:4},{oil:"薰衣草",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"9、内分泌系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"快乐鼠尾草",drops:4},{oil:"芳香调理",drops:4},{oil:"天竺葵",drops:4},{oil:"保卫",drops:4},{oil:"依兰依兰",drops:4},{oil:"小茴香",drops:4},{oil:"薰衣草",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"10、感冒发烧系统细胞律动",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"西班牙牛至",drops:4},{oil:"百里香",drops:4},{oil:"保卫",drops:4},{oil:"芳香调理",drops:4},{oil:"柠檬草",drops:4},{oil:"尤加利",drops:4},{oil:"茶树",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"11、肌肉系统细胞律动缓解疼痛",note:"",tags:[],ingredients:[{oil:"乳香",drops:4},{oil:"生姜",drops:4},{oil:"芳香调理",drops:4},{oil:"冬青",drops:4},{oil:"柠檬草",drops:4},{oil:"西伯利亚冷杉",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"12、芳香调理技术",note:"",tags:[],ingredients:[{oil:"安定情绪",drops:4},{oil:"薰衣草",drops:4},{oil:"茶树",drops:4},{oil:"保卫",drops:4},{oil:"芳香调理",drops:4},{oil:"舒缓",drops:4},{oil:"野橘",drops:4},{oil:"椒样薄荷",drops:4}]},
{name:"芳心调理技术(单瓶购买)",note:"",tags:[],ingredients:[{oil:"静谧",drops:4},{oil:"抚慰",drops:4},{oil:"宽容",drops:4},{oil:"热情",drops:4},{oil:"欢欣",drops:4},{oil:"鼓舞",drops:4}]},
{name:"芳心调理技术(套装购买)",note:"",tags:[],ingredients:[{oil:"静谧",drops:4},{oil:"抚慰",drops:4},{oil:"宽容",drops:4},{oil:"热情",drops:4},{oil:"欢欣",drops:4},{oil:"鼓舞",drops:4}]},
{name:"头疼",note:"",tags:[],ingredients:[{oil:"尤加利",drops:1},{oil:"薰衣草",drops:1},{oil:"冬青",drops:2},{oil:"椒样薄荷",drops:2}]},
{name:"痤疮粉刺",note:"",tags:[],ingredients:[{oil:"茶树",drops:1}]},
{name:"植物热玛吉",note:"",tags:[],ingredients:[{oil:"玫瑰",drops:5},{oil:"丝柏",drops:10},{oil:"西洋蓍草",drops:20},{oil:"永久花",drops:5},{oil:"乳香",drops:10}]},
{name:"近视老花",note:"",tags:[],ingredients:[{oil:"乳香",drops:10},{oil:"快乐鼠尾草",drops:10},{oil:"花样年华焕肤油",drops:10}]},
{name:"记忆力",note:"",tags:[],ingredients:[{oil:"罗勒",drops:1},{oil:"迷迭香",drops:2},{oil:"佛手柑",drops:2}]},
{name:"皮肤老化",note:"",tags:[],ingredients:[{oil:"花样年华焕肤油",drops:3},{oil:"西洋蓍草",drops:3}]},
{name:"生发配方",note:"",tags:[],ingredients:[{oil:"生姜",drops:1},{oil:"迷迭香",drops:1},{oil:"乳香",drops:1},{oil:"雪松",drops:1},{oil:"薰衣草",drops:1},{oil:"扁柏",drops:1}]},
{name:"头皮屑",note:"",tags:[],ingredients:[{oil:"茶树",drops:1},{oil:"丝柏",drops:1},{oil:"迷迭香",drops:1}]},
{name:"生发2",note:"",tags:[],ingredients:[{oil:"生姜",drops:1},{oil:"薰衣草",drops:2},{oil:"迷迭香",drops:3},{oil:"雪松",drops:4}]},
{name:"白发转黑发",note:"",tags:[],ingredients:[{oil:"完美修护",drops:16},{oil:"乳香",drops:16},{oil:"雪松",drops:16},{oil:"薰衣草",drops:16},{oil:"丝柏",drops:16},{oil:"快乐鼠尾草",drops:16},{oil:"生姜",drops:16},{oil:"侧柏",drops:16},{oil:"扁柏",drops:16},{oil:"依兰依兰",drops:16},{oil:"迷迭香",drops:16},{oil:"檀香",drops:10},{oil:"西伯利亚冷杉",drops:10}]},
{name:"鼻炎用油",note:"",tags:[],ingredients:[{oil:"香蜂草",drops:4},{oil:"顺畅呼吸",drops:20},{oil:"椒样薄荷",drops:4},{oil:"罗勒",drops:6},{oil:"尤加利",drops:6}]},
{name:"中耳炎",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:1},{oil:"罗勒",drops:2},{oil:"茶树",drops:2},{oil:"薰衣草",drops:2}]},
{name:"口臭",note:"",tags:[],ingredients:[{oil:"绿薄荷",drops:1}]},
{name:"牙龈牙周",note:"",tags:[],ingredients:[{oil:"茶树",drops:1},{oil:"丁香花蕾",drops:1}]},
{name:"烧烫伤",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:2},{oil:"茶树",drops:2},{oil:"薰衣草",drops:2}]},
{name:"儿童长高",note:"",tags:[],ingredients:[{oil:"西伯利亚冷杉",drops:6},{oil:"乳香",drops:5},{oil:"檀香",drops:3},{oil:"永久花",drops:3},{oil:"芳香调理",drops:2}]},
{name:"湿疹",note:"",tags:[],ingredients:[{oil:"绿薄荷",drops:1},{oil:"侧柏",drops:5},{oil:"蓝艾菊",drops:5},{oil:"广藿香",drops:10},{oil:"乳香",drops:10}]},
{name:"退烧神器",note:"",tags:[],ingredients:[{oil:"杜松浆果",drops:3},{oil:"椒样薄荷",drops:4},{oil:"永久花",drops:3},{oil:"山鸡椒",drops:3},{oil:"保卫",drops:4},{oil:"柠檬",drops:3},{oil:"乳香",drops:4}]},
{name:"感冒咳嗽",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:1},{oil:"柠檬",drops:2},{oil:"乳香",drops:3},{oil:"西班牙牛至",drops:4},{oil:"保卫",drops:5}]},
{name:"腹泻",note:"",tags:[],ingredients:[{oil:"西班牙牛至",drops:1},{oil:"乐活",drops:1},{oil:"生姜",drops:1},{oil:"罗勒",drops:1}]},
{name:"晕车恶心",note:"",tags:[],ingredients:[{oil:"生姜",drops:1},{oil:"尤加利",drops:1},{oil:"佛手柑",drops:1},{oil:"椒样薄荷",drops:1},{oil:"乐活",drops:1}]},
{name:"醉酒",note:"",tags:[],ingredients:[{oil:"乐活",drops:2}]},
{name:"鸡眼/疣/痣",note:"",tags:[],ingredients:[{oil:"丁香花蕾",drops:1},{oil:"西班牙牛至",drops:1}]},
{name:"脚气",note:"",tags:[],ingredients:[{oil:"茶树",drops:1},{oil:"西班牙牛至",drops:1}]},
{name:"灰指甲2",note:"",tags:[],ingredients:[{oil:"茶树",drops:8},{oil:"西班牙牛至",drops:8},{oil:"百里香",drops:8}]},
{name:"丰胸挺拔",note:"",tags:[],ingredients:[{oil:"小茴香",drops:6},{oil:"依兰依兰",drops:4},{oil:"温柔呵护",drops:10},{oil:"西洋蓍草",drops:10},{oil:"快乐鼠尾草",drops:3}]},
{name:"痛经",note:"",tags:[],ingredients:[{oil:"斯里兰卡肉桂皮",drops:1},{oil:"玫瑰",drops:1},{oil:"丁香花蕾",drops:1},{oil:"薰衣草",drops:2}]},
{name:"亲密关系",note:"",tags:[],ingredients:[{oil:"快乐鼠尾草",drops:6},{oil:"茉莉呵护",drops:12},{oil:"檀香",drops:7},{oil:"依兰依兰",drops:6},{oil:"花样年华焕肤油",drops:5},{oil:"玫瑰呵护",drops:12}]},
{name:"前列腺养护",note:"",tags:[],ingredients:[{oil:"西班牙牛至",drops:2},{oil:"檀香",drops:3},{oil:"茉莉呵护",drops:4},{oil:"丝柏",drops:3},{oil:"乳香",drops:4},{oil:"永久花",drops:3},{oil:"迷迭香",drops:3},{oil:"快乐鼠尾草",drops:4}]},
{name:"手脚冰冷",note:"",tags:[],ingredients:[{oil:"生姜",drops:2},{oil:"圆柚",drops:2}]},
{name:"外阴瘙痒",note:"",tags:[],ingredients:[{oil:"茶树",drops:1},{oil:"没药",drops:1},{oil:"玫瑰草",drops:1},{oil:"佛手柑",drops:1}]},
{name:"淋巴排毒2",note:"",tags:[],ingredients:[{oil:"乳香",drops:1},{oil:"丝柏",drops:1},{oil:"迷迭香",drops:1},{oil:"柠檬草",drops:1},{oil:"永久花",drops:1},{oil:"新瑞活力",drops:1}]},
{name:"痔疮",note:"",tags:[],ingredients:[{oil:"没药",drops:1},{oil:"茶树",drops:1},{oil:"丝柏",drops:2},{oil:"永久花",drops:2}]},
{name:"便秘积食",note:"",tags:[],ingredients:[{oil:"乐活",drops:1},{oil:"柠檬",drops:1},{oil:"马郁兰",drops:1},{oil:"椒样薄荷",drops:1}]},
{name:"护肝排毒",note:"",tags:[],ingredients:[{oil:"当归",drops:1},{oil:"芫荽",drops:2},{oil:"元气",drops:3},{oil:"天竺葵",drops:3},{oil:"迷迭香",drops:7},{oil:"柠檬",drops:13}]},
{name:"痛风",note:"",tags:[],ingredients:[{oil:"百里香",drops:6},{oil:"杜松浆果",drops:8},{oil:"天竺葵",drops:10},{oil:"冬青",drops:10}]},
{name:"消除富贵包",note:"",tags:[],ingredients:[{oil:"柠檬草",drops:2},{oil:"乳香",drops:5},{oil:"芳香调理",drops:5},{oil:"生姜",drops:2},{oil:"舒缓",drops:3},{oil:"古巴香脂",drops:2}]},
{name:"焦虑",note:"",tags:[],ingredients:[{oil:"野橘",drops:1},{oil:"薰衣草",drops:2},{oil:"天竺葵",drops:2}]},
{name:"更年期",note:"",tags:[],ingredients:[{oil:"椒样薄荷",drops:2},{oil:"乳香",drops:3},{oil:"薰衣草",drops:3},{oil:"天竺葵",drops:4},{oil:"温柔呵护",drops:8},{oil:"快乐鼠尾草",drops:10},{oil:"罗马洋甘菊",drops:8}]},
{name:"失眠多梦",note:"",tags:[],ingredients:[{oil:"雪松",drops:1},{oil:"野橘",drops:1},{oil:"乳香",drops:1},{oil:"檀香",drops:1},{oil:"橙花",drops:1},{oil:"乐释",drops:1},{oil:"苦橙叶",drops:1},{oil:"薰衣草",drops:1},{oil:"马郁兰",drops:1},{oil:"佛手柑",drops:1},{oil:"岩兰草",drops:1},{oil:"罗马洋甘菊",drops:1}]},
{name:"稳定血糖",note:"",tags:[],ingredients:[{oil:"新瑞活力",drops:2},{oil:"斯里兰卡肉桂皮",drops:2},{oil:"芫荽",drops:2},{oil:"迷迭香",drops:2}]},
{name:"心脑血管护理",note:"",tags:[],ingredients:[{oil:"乳香",drops:2},{oil:"香蜂草",drops:1}]},
{name:"关节疼痛",note:"",tags:[],ingredients:[{oil:"乳香",drops:3},{oil:"舒缓",drops:2},{oil:"道格拉斯冷杉",drops:2},{oil:"柠檬草",drops:1}]},
{name:"高血压保健",note:"",tags:[],ingredients:[{oil:"依兰依兰",drops:10},{oil:"薰衣草",drops:10},{oil:"马郁兰",drops:10},{oil:"乳香",drops:10},{oil:"香蜂草",drops:5}]}
];
// ============ STATE ============
let recipes = [];
let currentRecipe = null;
let currentEditing = [];
function loadRecipes() {
try {
const saved = localStorage.getItem('doterra_recipes');
if (saved) {
recipes = JSON.parse(saved);
// Merge any new DEFAULT_RECIPES not yet in saved data
const existingNames = new Set(recipes.map(r => r.name));
let added = 0;
for (const dr of DEFAULT_RECIPES) {
if (!existingNames.has(dr.name)) {
recipes.push(JSON.parse(JSON.stringify(dr)));
added++;
}
}
if (added > 0) saveRecipes();
} else {
recipes = JSON.parse(JSON.stringify(DEFAULT_RECIPES));
saveRecipes();
}
} catch(e) {
recipes = JSON.parse(JSON.stringify(DEFAULT_RECIPES));
}
}
function saveRecipes() {
localStorage.setItem('doterra_recipes', JSON.stringify(recipes));
}
// ============ UTILS ============
function calcCost(ingredients) {
return ingredients.reduce((sum, ing) => {
const ppd = OILS[ing.oil] || 0;
return sum + ppd * ing.drops;
}, 0);
}
function fmtPrice(n) { return '¥ ' + n.toFixed(2); }
function showSection(name) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.nav-tab').forEach(t => t.classList.remove('active'));
document.getElementById('section-' + name).classList.add('active');
const tabs = document.querySelectorAll('.nav-tab');
const map = {search:0, manage:1, add:2, oils:3};
tabs[map[name]].classList.add('active');
if (name === 'manage') { renderTagBar(); renderManage(); }
if (name === 'oils') renderOils();
if (name === 'add') renderNewIngList();
}
// ============ SEARCH SECTION ============
function filterRecipes() {
const q = document.getElementById('searchInput').value.trim().toLowerCase();
const filtered = q ? recipes.filter(r =>
r.name.toLowerCase().includes(q) ||
r.ingredients.some(i => i.oil.toLowerCase().includes(q)) ||
(r.tags || []).some(t => t.toLowerCase().includes(q))
) : recipes;
renderGrid(filtered);
}
function clearSearch() {
document.getElementById('searchInput').value = '';
filterRecipes();
document.getElementById('detailPanel').style.display = 'none';
}
function renderGrid(list) {
const grid = document.getElementById('recipeGrid');
if (!list.length) {
grid.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🔍</div><div class="empty-state-text">没有找到相关配方</div></div>';
return;
}
grid.innerHTML = list.map((r, idx) => {
const realIdx = recipes.indexOf(r);
const cost = calcCost(r.ingredients);
const oilNames = r.ingredients.map(i => i.oil).join('、');
const tags = (r.tags || []).map(t => `<span class="tag">${t}</span>`).join(' ');
return `<div class="recipe-card" onclick="selectRecipe(${realIdx})">
<div class="recipe-card-name">${r.name}</div>
${tags ? '<div style="margin:4px 0">'+tags+'</div>' : ''}
<div class="recipe-card-oils">${oilNames}</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:8px">
<div class="recipe-card-price" style="margin:0">💰 ${fmtPrice(cost)}</div>
<button class="btn btn-danger btn-sm" onclick="event.stopPropagation();deleteRecipeFromSearch(${realIdx})" style="padding:4px 10px;font-size:12px">🗑 删除</button>
</div>
</div>`;
}).join('');
}
function selectRecipe(idx) {
currentRecipe = idx;
currentEditing = JSON.parse(JSON.stringify(recipes[idx].ingredients));
document.getElementById('detailPanel').style.display = 'block';
renderDetail();
document.getElementById('detailPanel').scrollIntoView({behavior:'smooth', block:'start'});
}
function renderDetail() {
const r = recipes[currentRecipe];
document.getElementById('detailTitle').textContent = r.name;
const noteEl = document.getElementById('detailNote');
noteEl.innerHTML = r.note ? `<div class="detail-note">📝 ${r.note}</div>` : '';
const tbody = document.getElementById('ingredientsBody');
tbody.innerHTML = currentEditing.map((ing, i) => {
const ppd = OILS[ing.oil] || 0;
const sub = ppd * ing.drops;
const oilOptions = Object.keys(OILS).map(o =>
`<option value="${o}" ${o === ing.oil ? 'selected' : ''}>${o}</option>`
).join('');
return `<tr>
<td><select class="oil-select" onchange="changeOil(${i}, this.value)">${oilOptions}</select></td>
<td><input type="number" class="drops-input" value="${ing.drops}" min="0.5" step="0.5" onchange="changeDrops(${i}, this.value)"></td>
<td style="color:var(--text-light);font-size:13px">${ppd > 0 ? '¥'+ppd.toFixed(2) : '—'}</td>
<td style="font-weight:600;color:var(--sage-dark)">${sub > 0 ? fmtPrice(sub) : '—'}</td>
<td><button class="remove-btn" onclick="removeIng(${i})">×</button></td>
</tr>`;
}).join('');
const total = calcCost(currentEditing);
document.getElementById('totalPrice').textContent = fmtPrice(total);
renderCard();
hideAddRow();
}
function changeOil(i, val) {
currentEditing[i].oil = val;
renderDetail();
}
function changeDrops(i, val) {
currentEditing[i].drops = parseFloat(val) || 0;
renderDetail();
}
function removeIng(i) {
currentEditing.splice(i, 1);
renderDetail();
}
function addIngredientRow() {
const row = document.getElementById('addIngRow');
row.style.display = 'flex';
const sel = document.getElementById('newOilSelect');
if (sel.options.length <= 1) {
Object.keys(OILS).forEach(o => {
const opt = document.createElement('option');
opt.value = o; opt.textContent = o;
sel.appendChild(opt);
});
}
document.getElementById('newOilDrops').value = '';
}
function hideAddRow() { document.getElementById('addIngRow').style.display = 'none'; }
function confirmAddIngredient() {
const oil = document.getElementById('newOilSelect').value;
const drops = parseFloat(document.getElementById('newOilDrops').value);
if (!oil || !drops || drops <= 0) { alert('请选择精油并输入滴数'); return; }
currentEditing.push({oil, drops});
renderDetail();
}
function saveCurrentEditing() {
if (currentRecipe === null) return;
recipes[currentRecipe].ingredients = JSON.parse(JSON.stringify(currentEditing));
saveRecipes();
alert('✅ 配方已保存!');
}
// ============ CARD ============
function renderCard() {
const r = recipes[currentRecipe];
document.getElementById('cardTitle').textContent = r.name;
document.getElementById('cardNote').textContent = r.note || '';
document.getElementById('cardDate').textContent = '制作日期:' + new Date().toLocaleDateString('zh-CN');
const ul = document.getElementById('cardIngredients');
ul.innerHTML = currentEditing.map(ing => {
const ppd = OILS[ing.oil] || 0;
const sub = ppd * ing.drops;
return `<li>
<span class="card-oil-name">${ing.oil}</span>
<span class="card-oil-drops">${ing.drops} 滴</span>
<span class="card-oil-cost">${sub > 0 ? fmtPrice(sub) : ''}</span>
</li>`;
}).join('');
const total = calcCost(currentEditing);
document.getElementById('cardTotal').textContent = fmtPrice(total);
}
function exportCard() {
const el = document.getElementById('recipe-card-export');
html2canvas(el, {scale: 3, backgroundColor: null, useCORS: true}).then(canvas => {
const link = document.createElement('a');
link.download = recipes[currentRecipe].name + '_配方卡.png';
link.href = canvas.toDataURL('image/png');
link.click();
});
}
// ============ TAG SYSTEM ============
let allTags = [];
let manageFilterTag = null; // null = show all
let selectedRecipes = new Set();
function loadTags() {
try {
const saved = localStorage.getItem('doterra_tags');
if (saved) allTags = JSON.parse(saved);
else allTags = [];
} catch(e) { allTags = []; }
// Also collect tags from recipes
recipes.forEach(r => {
(r.tags || []).forEach(t => {
if (!allTags.includes(t)) allTags.push(t);
});
});
saveTags();
}
function saveTags() {
localStorage.setItem('doterra_tags', JSON.stringify(allTags));
}
function addGlobalTag() {
const input = document.getElementById('newTagInput');
const name = input.value.trim();
if (!name) return;
// Support multiple separated by comma
const newTags = name.split(/[,,、]/).map(s => s.trim()).filter(Boolean);
newTags.forEach(t => {
if (!allTags.includes(t)) allTags.push(t);
});
saveTags();
input.value = '';
renderTagBar();
}
function deleteGlobalTag(tag) {
if (!confirm(`删除标签「${tag}」?\n(已标记此标签的配方不会被删除,只是移除标签)`)) return;
allTags = allTags.filter(t => t !== tag);
// Remove from all recipes too
recipes.forEach(r => {
if (r.tags) r.tags = r.tags.filter(t => t !== tag);
});
saveTags();
saveRecipes();
renderTagBar();
renderManage();
}
function renderTagBar() {
const bar = document.getElementById('tagBar');
if (!allTags.length) {
bar.innerHTML = '<span style="font-size:12px;color:var(--text-light)">暂无标签,添加一个吧</span>';
return;
}
// "All" button
let html = `<div class="tag-btn ${manageFilterTag === null ? 'active' : ''}" onclick="filterByTag(null)">全部</div>`;
// "Untagged" button
html += `<div class="tag-btn ${manageFilterTag === '__other__' ? 'active' : ''}" onclick="filterByTag('__other__')">其他</div>`;
allTags.forEach(t => {
const count = recipes.filter(r => (r.tags || []).includes(t)).length;
html += `<div class="tag-btn ${manageFilterTag === t ? 'active' : ''}" onclick="filterByTag('${t.replace(/'/g, "\\'")}')">
${t} <span style="opacity:0.6;font-size:11px">${count}</span>
<button class="tag-del" onclick="event.stopPropagation();deleteGlobalTag('${t.replace(/'/g, "\\'")}')" title="删除标签">×</button>
</div>`;
});
bar.innerHTML = html;
}
function filterByTag(tag) {
manageFilterTag = tag;
selectedRecipes.clear();
renderTagBar();
renderManage();
}
// ============ MANAGE SECTION ============
function renderManage() {
const list = document.getElementById('manageList');
const label = document.getElementById('manageFilterLabel');
// Filter by tag
let filtered = recipes.map((r, i) => ({ r, i }));
if (manageFilterTag === '__other__') {
filtered = filtered.filter(({ r }) => !r.tags || r.tags.length === 0);
label.textContent = '— 其他';
} else if (manageFilterTag) {
filtered = filtered.filter(({ r }) => (r.tags || []).includes(manageFilterTag));
label.textContent = '— ' + manageFilterTag;
} else {
label.textContent = '';
}
if (!filtered.length) {
list.innerHTML = '<div class="empty-state"><div class="empty-state-icon">📋</div><div class="empty-state-text">暂无配方</div></div>';
return;
}
list.innerHTML = filtered.map(({ r, i }) => {
const cost = calcCost(r.ingredients);
const oilNames = r.ingredients.map(ing => ing.oil).join('、');
const tags = (r.tags || []).map(t => `<span class="tag">${t}</span>`).join(' ');
const checked = selectedRecipes.has(i) ? 'checked' : '';
return `<div class="manage-item">
<input type="checkbox" ${checked} onchange="toggleSelect(${i}, this.checked)" style="width:18px;height:18px;accent-color:var(--sage);cursor:pointer">
<div class="manage-item-left">
<div class="manage-item-name">${r.name} ${tags}</div>
<div class="manage-item-oils">${oilNames}</div>
<div style="margin-top:6px;font-size:13px;color:var(--sage-dark);font-weight:600">${fmtPrice(cost)}</div>
</div>
<div class="manage-item-actions">
<button class="btn btn-outline btn-sm" onclick="editTags(${i})">🏷 标签</button>
<button class="btn btn-outline btn-sm" onclick="editFromManage(${i})">✏️ 编辑</button>
<button class="btn btn-danger btn-sm" onclick="deleteRecipe(${i})">🗑 删除</button>
</div>
</div>`;
}).join('');
}
function toggleSelect(i, checked) {
if (checked) selectedRecipes.add(i);
else selectedRecipes.delete(i);
}
function toggleSelectAll() {
// Get currently visible recipe indices
let visible = recipes.map((r, i) => ({ r, i }));
if (manageFilterTag === '__other__') {
visible = visible.filter(({ r }) => !r.tags || r.tags.length === 0);
} else if (manageFilterTag) {
visible = visible.filter(({ r }) => (r.tags || []).includes(manageFilterTag));
}
const visibleIds = visible.map(({ i }) => i);
const allSelected = visibleIds.every(i => selectedRecipes.has(i));
if (allSelected) {
visibleIds.forEach(i => selectedRecipes.delete(i));
} else {
visibleIds.forEach(i => selectedRecipes.add(i));
}
renderManage();
}
function editTags(i) {
const r = recipes[i];
const currentTags = new Set(r.tags || []);
const overlay = document.createElement('div');
overlay.className = 'tag-picker';
overlay.innerHTML = `<div class="tag-picker-card">
<div class="tag-picker-title">🏷 为「${r.name}」选择标签</div>
<div class="tag-picker-tags" id="tagPickerTags"></div>
<div style="display:flex;gap:8px;margin-bottom:16px;align-items:center">
<input type="text" id="tagPickerNew" class="form-control" placeholder="新标签…" style="flex:1;font-size:13px;padding:8px 12px"
onkeydown="if(event.key==='Enter'){addTagFromPicker();event.preventDefault()}">
<button class="btn btn-outline btn-sm" onclick="addTagFromPicker()">添加</button>
</div>
<div style="display:flex;gap:10px;justify-content:flex-end">
<button class="btn btn-outline btn-sm" onclick="closeTagPicker()">取消</button>
<button class="btn btn-primary btn-sm" onclick="saveTagPicker(${i})">确认</button>
</div>
</div>`;
document.body.appendChild(overlay);
window._tagPickerSet = currentTags;
renderTagPicker();
}
function renderTagPicker() {
const container = document.getElementById('tagPickerTags');
if (!container) return;
const selected = window._tagPickerSet;
container.innerHTML = allTags.map(t =>
`<div class="tag-pick ${selected.has(t) ? 'selected' : ''}" onclick="toggleTagPick('${t.replace(/'/g, "\\'")}')">${t}</div>`
).join('');
}
function toggleTagPick(tag) {
const s = window._tagPickerSet;
if (s.has(tag)) s.delete(tag);
else s.add(tag);
renderTagPicker();
}
function addTagFromPicker() {
const input = document.getElementById('tagPickerNew');
const name = input.value.trim();
if (!name) return;
const newTags = name.split(/[,,、]/).map(s => s.trim()).filter(Boolean);
newTags.forEach(t => {
if (!allTags.includes(t)) allTags.push(t);
window._tagPickerSet.add(t);
});
saveTags();
input.value = '';
renderTagPicker();
renderTagBar();
}
function saveTagPicker(i) {
recipes[i].tags = [...window._tagPickerSet];
saveRecipes();
// Update allTags
recipes[i].tags.forEach(t => {
if (!allTags.includes(t)) allTags.push(t);
});
saveTags();
closeTagPicker();
renderTagBar();
renderManage();
}
function closeTagPicker() {
const overlay = document.querySelector('.tag-picker');
if (overlay) overlay.remove();
window._tagPickerSet = null;
}
function addRecipeToSheet(ws, r, isFirst, fontTitle, fontHeader, fontBody, fontTotal) {
if (!isFirst) ws.addRow([]); // blank row between recipes
// Title row - merge A:D and add background color
const titleRow = ws.addRow([r.name]);
const rowNum = titleRow.number;
ws.mergeCells(rowNum, 1, rowNum, 4);
titleRow.getCell(1).font = fontTitle;
titleRow.getCell(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFC8DDC9' } };
titleRow.getCell(1).alignment = { horizontal: 'center', vertical: 'middle' };
titleRow.height = 36;
const centerAlign = { horizontal: 'center', vertical: 'middle' };
// Note row
if (r.note) {
const noteRow = ws.addRow(['备注:' + r.note]);
noteRow.eachCell(c => { c.font = fontBody; c.alignment = centerAlign; });
}
// Header row
const headerRow = ws.addRow(['精油', '滴数', '单价/滴', '小计']);
headerRow.eachCell(c => { c.font = fontHeader; c.alignment = centerAlign; });
// Ingredient rows
let total = 0;
r.ingredients.forEach(ing => {
const ppd = OILS[ing.oil] || 0;
const sub = ppd * ing.drops;
total += sub;
const row = ws.addRow([ing.oil, ing.drops, '¥' + ppd.toFixed(2), '¥' + sub.toFixed(2)]);
row.eachCell(c => { c.font = fontBody; c.alignment = centerAlign; });
});
// Total row
const totalRow = ws.addRow(['合计', '', '', '¥' + total.toFixed(2)]);
totalRow.eachCell(c => { c.font = fontTotal; c.alignment = centerAlign; });
}
async function exportExcel() {
const indices = selectedRecipes.size > 0 ? [...selectedRecipes].sort((a,b) => a-b) : recipes.map((_, i) => i);
if (!indices.length) { alert('没有配方可导出'); return; }
const wb = new ExcelJS.Workbook();
const fontTitle = { size: 20 };
const fontHeader = { size: 14 };
const fontBody = { size: 12 };
const fontTotal = { size: 12, bold: true };
const colWidths = [{ width: 20 }, { width: 10 }, { width: 14 }, { width: 14 }];
// Group recipes by tag
const tagGroups = {}; // tag -> [recipe]
const untagged = [];
indices.forEach(idx => {
const r = recipes[idx];
const tags = r.tags && r.tags.length > 0 ? r.tags : null;
if (!tags) {
untagged.push(r);
} else {
tags.forEach(t => {
if (!tagGroups[t]) tagGroups[t] = [];
tagGroups[t].push(r);
});
}
});
// "全部" sheet always comes first
const wsAll = wb.addWorksheet('全部');
wsAll.columns = colWidths;
indices.forEach((idx, n) => {
addRecipeToSheet(wsAll, recipes[idx], n === 0, fontTitle, fontHeader, fontBody, fontTotal);
});
// Then one sheet per tag
const hasAnyTags = Object.keys(tagGroups).length > 0;
if (hasAnyTags) {
for (const [tag, recs] of Object.entries(tagGroups)) {
const sheetName = tag.slice(0, 31).replace(/[\\\/\*\?\[\]:]/g, '_');
const ws = wb.addWorksheet(sheetName);
ws.columns = colWidths;
recs.forEach((r, n) => {
addRecipeToSheet(ws, r, n === 0, fontTitle, fontHeader, fontBody, fontTotal);
});
}
// Untagged → "其他" sheet
if (untagged.length) {
const ws = wb.addWorksheet('其他');
ws.columns = colWidths;
untagged.forEach((r, n) => {
addRecipeToSheet(ws, r, n === 0, fontTitle, fontHeader, fontBody, fontTotal);
});
}
}
// Generate and download
const buf = await wb.xlsx.writeBuffer();
const blob = new Blob([buf], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = '精油配方导出_' + new Date().toLocaleDateString('zh-CN').replace(/\//g, '-') + '.xlsx';
a.click();
URL.revokeObjectURL(url);
}
function editFromManage(i) {
showSection('search');
document.getElementById('searchInput').value = recipes[i].name;
filterRecipes();
selectRecipe(i);
}
function deleteRecipe(i) {
if (!confirm(`确认删除配方「${recipes[i].name}」?`)) return;
recipes.splice(i, 1);
saveRecipes();
renderManage();
}
function deleteRecipeFromSearch(i) {
if (!confirm(`确认删除配方「${recipes[i].name}」?`)) return;
recipes.splice(i, 1);
saveRecipes();
// Hide detail panel if the deleted recipe was selected
if (currentRecipe === i) {
document.getElementById('detailPanel').style.display = 'none';
currentRecipe = null;
} else if (currentRecipe > i) {
currentRecipe--;
}
filterRecipes();
}
// ============ ADD SECTION ============
let newIngredients = [{oil:'', drops:1}];
function renderNewIngList() {
const list = document.getElementById('newIngList');
list.innerHTML = newIngredients.map((ing, i) => {
const oilOptions = Object.keys(OILS).map(o =>
`<option value="${o}" ${o === ing.oil ? 'selected' : ''}>${o}</option>`
).join('');
return `<div class="new-ing-row">
<select class="form-control" style="flex:1" onchange="newIngChange(${i},'oil',this.value)">
<option value="">— 选择精油 —</option>${oilOptions}
</select>
<input type="number" class="form-control" style="width:90px" value="${ing.drops}" min="0.5" step="0.5" placeholder="滴数" onchange="newIngChange(${i},'drops',this.value)">
<button class="remove-btn" onclick="removeNewIng(${i})">×</button>
</div>`;
}).join('');
}
function newIngChange(i, field, val) {
if (field === 'drops') newIngredients[i].drops = parseFloat(val) || 0;
else newIngredients[i].oil = val;
}
function addNewIngRow() {
newIngredients.push({oil:'', drops:1});
renderNewIngList();
}
function removeNewIng(i) {
if (newIngredients.length <= 1) return;
newIngredients.splice(i, 1);
renderNewIngList();
}
// Check if two ingredient lists are the same
function isSameIngredients(a, b) {
if (a.length !== b.length) return false;
const sortA = [...a].sort((x, y) => x.oil.localeCompare(y.oil));
const sortB = [...b].sort((x, y) => x.oil.localeCompare(y.oil));
return sortA.every((ing, i) => ing.oil === sortB[i].oil && ing.drops === sortB[i].drops);
}
// Check duplicate before saving. Returns true if save should proceed.
function checkDuplicateAndSave(name, ingredients, note) {
const existing = recipes.filter(r => r.name === name);
if (existing.length > 0) {
const same = existing.some(r => isSameIngredients(r.ingredients, ingredients));
if (same) {
alert(`配方「${name}」已存在且内容一致,无需重复保存。`);
return false;
} else {
// Different content, suggest new name
let newName = name;
let n = 2;
while (recipes.some(r => r.name === newName)) {
newName = name + n;
n++;
}
const ok = confirm(`已有同名配方「${name}」但内容不一致。\n\n是否保存为「${newName}」?`);
if (!ok) return false;
recipes.push({ name: newName, note: note || '', ingredients: JSON.parse(JSON.stringify(ingredients)) });
saveRecipes();
alert('✅ 配方「' + newName + '」已保存!');
return true;
}
}
// No duplicate
recipes.push({ name, note: note || '', ingredients: JSON.parse(JSON.stringify(ingredients)) });
saveRecipes();
alert('✅ 配方「' + name + '」已保存!');
return true;
}
function saveNewRecipe() {
const name = document.getElementById('newRecipeName').value.trim();
if (!name) { alert('请输入配方名称'); return; }
const ings = newIngredients.filter(i => i.oil && i.drops > 0);
if (!ings.length) { alert('请至少添加一种精油'); return; }
const note = document.getElementById('newRecipeNote').value.trim();
if (checkDuplicateAndSave(name, ings, note)) {
clearNewForm();
}
}
function clearNewForm() {
document.getElementById('newRecipeName').value = '';
document.getElementById('newRecipeNote').value = '';
newIngredients = [{oil:'', drops:1}];
renderNewIngList();
}
// ============ SMART PASTE ============
// Parse a text chunk into [(oilName, drops), ...] pairs
// Handles: "芳香调理8永久花10檀香10" → [("芳香调理",8), ("永久花",10), ("檀香",10)]
function parseOilChunk(text, oilNames) {
const results = []; // {name, drops, found}
const notFound = [];
// First try: split by numbers → [(text, number), ...]
const pairs = [];
const regex = /([^\d]+?)(\d+\.?\d*)/g;
let m;
let lastEnd = 0;
while ((m = regex.exec(text)) !== null) {
pairs.push({ name: m[1].trim(), drops: parseFloat(m[2]) });
lastEnd = regex.lastIndex;
}
// Remaining text after last number (oil with no drops)
const remainder = text.slice(lastEnd).trim();
if (pairs.length > 0) {
// Got number-delimited pairs
pairs.forEach(p => {
if (!p.name) return;
// The name might contain multiple oils concatenated: try greedy match from oil list
const parsed = greedyMatchOils(p.name, oilNames);
if (parsed.length === 0) {
// Whole chunk is one oil name
const oil = findOil(p.name, oilNames);
if (oil) results.push({ oil, drops: p.drops });
else notFound.push(p.name);
} else if (parsed.length === 1) {
results.push({ oil: parsed[0], drops: p.drops });
} else {
// Multiple oils found before this number - last one gets the drops, others get 1
parsed.forEach((oil, idx) => {
results.push({ oil, drops: idx === parsed.length - 1 ? p.drops : 1 });
});
}
});
if (remainder) {
const oil = findOil(remainder, oilNames);
if (oil) results.push({ oil, drops: 1 });
else notFound.push(remainder);
}
} else {
// No numbers at all, try to find oil names in the text
const oil = findOil(text, oilNames);
if (oil) results.push({ oil, drops: 1 });
else notFound.push(text);
}
return { results, notFound };
}
// Try to greedily match known oil names from a concatenated string
// "芳香调理永久花" → ["芳香调理", "永久花"]
function greedyMatchOils(text, oilNames) {
if (!text) return [];
// Sort oil names by length descending (prefer longer matches)
const sorted = [...oilNames].sort((a, b) => b.length - a.length);
const matched = [];
let pos = 0;
while (pos < text.length) {
let found = false;
// Try exact match at current position
for (const name of sorted) {
if (text.startsWith(name, pos)) {
matched.push(name);
pos += name.length;
found = true;
break;
}
}
if (!found) {
// Try fuzzy match: collect chars until we find a match
let end = pos + 1;
let fuzzyFound = false;
while (end <= text.length) {
const segment = text.slice(pos, end);
const oil = findOil(segment, oilNames);
// Check if this segment closely matches an oil (not just substring)
if (oil && editDistance(segment, oil) <= Math.max(1, Math.floor(oil.length / 3))) {
matched.push(oil);
pos = end;
fuzzyFound = true;
break;
}
end++;
}
if (!fuzzyFound) {
// Skip one character and try again
pos++;
}
}
}
return matched;
}
function smartPaste() {
const raw = document.getElementById('smartPasteInput').value.trim();
if (!raw) { alert('请输入配方内容'); return; }
// Split by comma, Chinese comma, newline, tab, semicolons
const parts = raw.split(/[,,、;\n\t]+/).map(s => s.trim()).filter(Boolean);
if (parts.length < 1) { alert('请输入配方内容'); return; }
const oilNames = Object.keys(OILS);
// Sort by length desc for greedy matching
const oilsSorted = [...oilNames].sort((a, b) => b.length - a.length);
let recipeName = '';
let startIdx = 0;
// Check if first part is purely a recipe name (no numbers, not an oil name)
const firstHasNum = /\d/.test(parts[0]);
const firstIsOil = findOil(parts[0], oilNames);
if (!firstHasNum && !firstIsOil) {
recipeName = parts[0];
startIdx = 1;
} else {
// First part contains oils — find where the first oil name starts
// Everything before that is the recipe name
const text = parts[0];
let firstOilPos = text.length;
for (const name of oilsSorted) {
const idx = text.indexOf(name);
if (idx !== -1 && idx < firstOilPos) {
firstOilPos = idx;
}
}
if (firstOilPos > 0) {
recipeName = text.slice(0, firstOilPos).trim();
parts[0] = text.slice(firstOilPos);
}
if (!recipeName) recipeName = '未命名配方';
startIdx = 0;
}
const ingredients = [];
const notFound = [];
for (let i = startIdx; i < parts.length; i++) {
const part = parts[i].trim();
if (!part) continue;
const { results, notFound: nf } = parseOilChunk(part, oilNames);
results.forEach(r => ingredients.push(r));
nf.forEach(n => notFound.push(n));
}
if (!ingredients.length && notFound.length) {
alert('没有识别到精油:' + notFound.join('、'));
return;
}
if (!ingredients.length) {
alert('没有识别到任何精油');
return;
}
const totalEO = ingredients.reduce((s, ing) => s + ing.drops, 0);
const needScale = totalEO > 20;
let scaledIngredients = null;
if (needScale) {
// Preserve user's previous coconut/ratio choices if they exist
const { coconut, ratio } = getScaleParams();
scaledIngredients = scaleRecipe(ingredients, coconut, ratio);
}
// Store data, then render
window._smartPasteData = { name: recipeName, ingredients, scaledIngredients, notFound };
renderSmartPasteResult();
}
function renderSmartPasteResult() {
const data = window._smartPasteData;
if (!data) return;
const { name: recipeName, ingredients, scaledIngredients, notFound } = data;
// Get current values from inputs if they exist, otherwise defaults
const coconutInput = document.getElementById('scaleCoconut');
const ratioSelect = document.getElementById('scaleRatio');
const coconutVal = coconutInput ? (parseFloat(coconutInput.value) || 10) : 10;
const ratioVal = ratioSelect ? (parseInt(ratioSelect.value) || 8) : 8;
const resultDiv = document.getElementById('smartPasteResult');
let html = `<div style="background:var(--sage-mist);border-radius:12px;padding:16px;margin-top:8px">`;
// Original version
html += renderRecipePreview('原始配方', recipeName, ingredients, notFound);
// Scaled version
if (scaledIngredients) {
html += `<div style="border-top:1px dashed var(--border);margin:14px 0"></div>`;
html += `<div style="margin-bottom:8px;display:flex;align-items:center;gap:8px;flex-wrap:wrap">
<span style="font-size:13px;color:var(--text-mid)">🔄 换算单次用量,椰子油</span>
<input type="number" id="scaleCoconut" value="${coconutVal}" min="1" step="1" style="width:60px;padding:4px 8px;border:1.5px solid var(--border);border-radius:6px;font-size:13px;text-align:center" oninput="rescalePreview()">
<span style="font-size:13px;color:var(--text-mid)">滴,稀释比例 1:</span>
<select id="scaleRatio" style="padding:4px 8px;border:1.5px solid var(--border);border-radius:6px;font-size:13px" onchange="rescalePreview()">
<option value="5" ${ratioVal===5?'selected':''}>5较浓</option>
<option value="6" ${ratioVal===6?'selected':''}>6</option>
<option value="7" ${ratioVal===7?'selected':''}>7</option>
<option value="8" ${ratioVal===8?'selected':''}>8推荐</option>
<option value="9" ${ratioVal===9?'selected':''}>9</option>
<option value="10" ${ratioVal===10?'selected':''}>10较淡</option>
</select>
</div>`;
html += `<div id="scaledPreviewContent">` + renderRecipePreview('单次用量', recipeName, scaledIngredients) + `</div>`;
}
html += `<div style="margin-top:14px;display:flex;gap:10px;flex-wrap:wrap">`;
if (scaledIngredients) {
html += `<button class="btn btn-primary btn-sm" onclick="confirmSmartPaste('scaled')">✅ 保存单次用量</button>`;
html += `<button class="btn btn-outline btn-sm" onclick="confirmSmartPaste('original')">保存原始配方</button>`;
} else {
html += `<button class="btn btn-primary btn-sm" onclick="confirmSmartPaste('original')">✅ 确认保存</button>`;
}
html += `<button class="btn btn-outline btn-sm" onclick="document.getElementById('smartPasteResult').innerHTML=''">取消</button>`;
html += `</div></div>`;
resultDiv.innerHTML = html;
}
function renderRecipePreview(label, name, ingredients, notFound) {
let html = `<div style="font-weight:600;margin-bottom:8px">${label}:「${name}」</div>
<div style="font-size:13px;line-height:2">`;
ingredients.forEach(ing => {
const cost = (OILS[ing.oil] || 0) * ing.drops;
html += `<span class="tag">${ing.oil} ${ing.drops}滴 (¥${cost.toFixed(2)})</span> `;
});
const total = ingredients.reduce((s, ing) => s + (OILS[ing.oil] || 0) * ing.drops, 0);
html += `</div><div style="margin-top:6px;font-size:13px;font-weight:600;color:var(--sage-dark)">总成本:¥${total.toFixed(2)}</div>`;
if (notFound && notFound.length) {
html += `<div style="margin-top:6px;font-size:12px;color:#c0392b">⚠️ 未识别:${notFound.join('、')}(精油库中没有找到)</div>`;
}
return html;
}
function scaleRecipe(ingredients, coconutDrops, ratio) {
ratio = ratio || 8; // default EO:coconut = 1:8
const totalEO = ingredients.reduce((s, ing) => s + ing.drops, 0);
// Target total EO based on ratio: totalEO_scaled = coconutDrops / ratio
const targetEO = coconutDrops / ratio;
const factor = targetEO / totalEO;
let scaled = ingredients.map(ing => ({
oil: ing.oil,
drops: Math.round(ing.drops * factor * 2) / 2 // round to 0.5
}));
// Ensure minimum 0.5
scaled = scaled.map(ing => ({ ...ing, drops: Math.max(0.5, ing.drops) }));
// Add coconut oil
scaled.push({ oil: '椰子油', drops: coconutDrops });
return scaled;
}
function getScaleParams() {
const coconut = parseFloat(document.getElementById('scaleCoconut')?.value) || 10;
const ratio = parseInt(document.getElementById('scaleRatio')?.value) || 8;
return { coconut, ratio };
}
function rescalePreview() {
const data = window._smartPasteData;
if (!data) return;
const { coconut, ratio } = getScaleParams();
data.scaledIngredients = scaleRecipe(data.ingredients, coconut, ratio);
// Only update the scaled preview (keeps input focus)
const scaledDiv = document.getElementById('scaledPreviewContent');
if (scaledDiv) {
scaledDiv.innerHTML = renderRecipePreview('单次用量', data.name, data.scaledIngredients);
}
}
function confirmSmartPaste(type) {
const data = window._smartPasteData;
if (!data) return;
const ings = type === 'scaled' ? data.scaledIngredients : data.ingredients;
const note = type === 'scaled' ? '单次用量(已换算)' : '';
if (checkDuplicateAndSave(data.name, ings, note)) {
document.getElementById('smartPasteInput').value = '';
document.getElementById('smartPasteResult').innerHTML = '';
window._smartPasteData = null;
}
}
function editDistance(a, b) {
const m = a.length, n = b.length;
const dp = Array.from({length: m+1}, () => Array(n+1).fill(0));
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++)
for (let j = 1; j <= n; j++)
dp[i][j] = Math.min(
dp[i-1][j] + 1,
dp[i][j-1] + 1,
dp[i-1][j-1] + (a[i-1] === b[j-1] ? 0 : 1)
);
return dp[m][n];
}
function findOil(input, oilNames) {
// Exact match first
if (OILS[input] !== undefined) return input;
// Substring match (prefer longest)
let best = null;
let bestLen = 0;
for (const name of oilNames) {
if (name === input) return name;
if (name.includes(input) || input.includes(name)) {
if (name.length > bestLen) {
best = name;
bestLen = name.length;
}
}
}
if (best) return best;
// Edit distance fuzzy match (for typos like 永久化→永久花)
let bestDist = Infinity;
let bestFuzzy = null;
for (const name of oilNames) {
const dist = editDistance(input, name);
// Allow up to 1 char difference for short names, 2 for longer
const maxDist = Math.max(1, Math.floor(name.length / 3));
if (dist <= maxDist && dist < bestDist) {
bestDist = dist;
bestFuzzy = name;
}
}
return bestFuzzy;
}
// ============ OILS SECTION ============
let editingOil = null;
function renderOils(filter='') {
document.getElementById('oilCount').textContent = Object.keys(OILS).length;
const grid = document.getElementById('oilsGrid');
const entries = Object.entries(OILS).filter(([name]) =>
!filter || name.includes(filter)
).sort((a, b) => a[0].localeCompare(b[0], 'zh'));
grid.innerHTML = entries.map(([name, ppd]) => {
const meta = OILS_META[name] || {};
if (editingOil === name) {
const dc = meta.dropCount || '';
const volMap = {46:'2.5', 93:'5', 186:'10', 280:'15', 2146:'115'};
const vol = volMap[dc] || 'custom';
return `<div class="oil-chip" style="flex-wrap:wrap">
<span class="oil-chip-name" style="width:100%;margin-bottom:6px">${name}</span>
<input type="number" class="oil-edit-input" id="editBottlePrice" value="${meta.bottlePrice || ''}" step="0.01" min="0" placeholder="瓶价"
onkeydown="if(event.key==='Enter')saveOilEdit('${name}')">
<select class="oil-edit-input" id="editVolume" onchange="onVolumeChange('edit')" style="width:110px">
<option value="2.5" ${vol==='2.5'?'selected':''}>2.5ml</option>
<option value="5" ${vol==='5'?'selected':''}>5ml</option>
<option value="10" ${vol==='10'?'selected':''}>10ml</option>
<option value="15" ${vol==='15'?'selected':''}>15ml</option>
<option value="115" ${vol==='115'?'selected':''}>115ml</option>
<option value="custom" ${vol==='custom'?'selected':''}>自定义</option>
</select>
<input type="number" class="oil-edit-input" id="editDropCount" value="${dc}" step="1" min="1" placeholder="滴数"
style="display:${vol==='custom'?'inline-block':'none'}"
onkeydown="if(event.key==='Enter')saveOilEdit('${name}')">
<div class="oil-chip-actions">
<button class="oil-chip-btn" onclick="saveOilEdit('${name}')" title="保存">✓</button>
<button class="oil-chip-btn" onclick="cancelOilEdit()" title="取消">✕</button>
</div>
</div>`;
}
return `<div class="oil-chip">
<span class="oil-chip-name">${name}</span>
<span class="oil-chip-price">¥${ppd.toFixed(2)}/滴</span>
<div class="oil-chip-actions">
<button class="oil-chip-btn" onclick="startOilEdit('${name}')" title="编辑">✏️</button>
<button class="oil-chip-btn del" onclick="deleteOil('${name}')" title="删除">🗑</button>
</div>
</div>`;
}).join('');
}
function filterOils() {
renderOils(document.getElementById('oilSearchInput').value.trim());
}
const VOLUME_DROPS = {'2.5': 46, '5': 93, '10': 186, '15': 280, '115': 2146};
function onVolumeChange(prefix) {
const volSelect = document.getElementById(prefix === 'new' ? 'newOilVolume' : 'editVolume');
const dropInput = document.getElementById(prefix === 'new' ? 'newOilDropCount' : 'editDropCount');
const vol = volSelect.value;
if (VOLUME_DROPS[vol]) {
dropInput.value = VOLUME_DROPS[vol];
dropInput.style.display = 'none';
} else if (vol === 'custom') {
dropInput.value = '';
dropInput.style.display = 'inline-block';
dropInput.focus();
} else {
dropInput.value = '';
dropInput.style.display = 'none';
}
}
function getDropCount(prefix) {
const volSelect = document.getElementById(prefix === 'new' ? 'newOilVolume' : 'editVolume');
const dropInput = document.getElementById(prefix === 'new' ? 'newOilDropCount' : 'editDropCount');
const vol = volSelect.value;
if (VOLUME_DROPS[vol]) return VOLUME_DROPS[vol];
return parseInt(dropInput.value);
}
function addNewOil() {
const nameInput = document.getElementById('newOilName');
const bottlePriceInput = document.getElementById('newOilBottlePrice');
const name = nameInput.value.trim();
const bottlePrice = parseFloat(bottlePriceInput.value);
const dropCount = getDropCount('new');
if (!name) { alert('请输入精油名称'); return; }
if (isNaN(bottlePrice) || bottlePrice < 0) { alert('请输入有效的瓶价'); return; }
if (isNaN(dropCount) || dropCount <= 0) { alert('请选择容量或输入滴数'); return; }
if (OILS[name] !== undefined) {
const meta = OILS_META[name];
if (!confirm(`${name}」已存在(¥${meta.bottlePrice}/${meta.dropCount}滴),要更新吗?`)) return;
}
OILS[name] = bottlePrice / dropCount;
OILS_META[name] = { bottlePrice, dropCount };
saveOils();
nameInput.value = '';
bottlePriceInput.value = '';
document.getElementById('newOilVolume').value = '';
document.getElementById('newOilDropCount').value = '';
document.getElementById('newOilDropCount').style.display = 'none';
filterOils();
}
function startOilEdit(name) {
editingOil = name;
filterOils();
setTimeout(() => {
const input = document.getElementById('editOilPrice');
if (input) { input.focus(); input.select(); }
}, 50);
}
function saveOilEdit(name) {
const bottlePrice = parseFloat(document.getElementById('editBottlePrice').value);
const dropCount = getDropCount('edit');
if (isNaN(bottlePrice) || bottlePrice < 0) { alert('请输入有效的瓶价'); return; }
if (isNaN(dropCount) || dropCount <= 0) { alert('请选择容量或输入滴数'); return; }
OILS[name] = bottlePrice / dropCount;
OILS_META[name] = { bottlePrice, dropCount };
saveOils();
editingOil = null;
filterOils();
}
function cancelOilEdit() {
editingOil = null;
filterOils();
}
function deleteOil(name) {
if (!confirm(`确认删除「${name}」?\n\n注意:已有配方中如果用到此精油,成本将显示为 ¥0`)) return;
delete OILS[name];
delete OILS_META[name];
saveOils();
filterOils();
}
// ============ INIT ============
loadOils();
loadRecipes();
loadTags();
filterRecipes();
</script>
</body>
</html>