feat: 权限细化、商业认证跳转、UI改进

权限:
- viewer 管理配方页只显示我的配方,隐藏公共配方库和工具栏
- 高级编辑者可看到精油价目信息不全的红色提示
- 商业核算删除按钮仅管理员可见
- 搜索未收录通知只发管理员和高级编辑者

Tab 可见性:
- 所有用户可见:配方查询、管理配方、个人库存、精油价目、商业核算
- 需登录的 tab 点击弹登录框,登录后跳转
- 操作日志/Bug/用户管理仅管理员可见

商业核算:
- 未认证用户可看项目列表,点详情提示去认证
- 跳转到我的账户页商业认证区域并自动滚动

其他:
- 我的配方和收藏配方默认折叠

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 21:49:37 +00:00
parent a8e91dc384
commit 1d424984e0
6 changed files with 63 additions and 32 deletions

View File

@@ -241,7 +241,7 @@
</div>
<!-- Business Verification -->
<div v-if="!auth.isBusiness" class="section-card">
<div v-if="!auth.isBusiness" ref="bizCertRef" class="section-card">
<h4>💼 商业认证</h4>
<p class="hint-text">申请商业认证后可使用商业核算功能</p>
<div class="form-group">
@@ -259,8 +259,8 @@
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ref, nextTick, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { useOilsStore } from '../stores/oils'
import { useDiaryStore } from '../stores/diary'
@@ -274,8 +274,10 @@ const oils = useOilsStore()
const diaryStore = useDiaryStore()
const ui = useUiStore()
const router = useRouter()
const route = useRoute()
const bizCertRef = ref(null)
const activeTab = ref('brand')
const activeTab = ref(route.query.tab || 'brand')
const pasteText = ref('')
const selectedDiaryId = ref(null)
const returnRecipeId = ref(null)
@@ -305,6 +307,11 @@ onMounted(async () => {
displayName.value = auth.user.display_name || ''
await loadBrandSettings()
returnRecipeId.value = localStorage.getItem('oil_return_recipe_id') || null
// 从商业核算跳转过来,滚到商业认证区域
if (route.query.section === 'biz-cert') {
await nextTick()
bizCertRef.value?.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
})
function goBackToRecipe() {

View File

@@ -132,7 +132,7 @@
v-for="name in filteredOilNames"
:key="name + '-' + cardVersion"
class="oil-chip"
:class="{ 'oil-chip--inactive': getMeta(name)?.isActive === false, 'oil-chip--incomplete': auth.isAdmin && isIncomplete(name) }"
:class="{ 'oil-chip--inactive': getMeta(name)?.isActive === false, 'oil-chip--incomplete': auth.canManage && isIncomplete(name) }"
:style="chipStyle(name)"
@click="openOilDetail(name)"
>

View File

@@ -3,7 +3,7 @@
<!-- Project List -->
<div class="toolbar">
<h3 class="page-title">💼 商业核算</h3>
<button class="btn-primary" @click="createProject">+ 新建项目</button>
<button v-if="auth.isBusiness" class="btn-primary" @click="createProject">+ 新建项目</button>
</div>
<div v-if="!selectedProject" class="project-list">
@@ -23,7 +23,7 @@
成本 {{ oils.fmtPrice(oils.calcCost(p.ingredients)) }}
</span>
</div>
<div class="proj-actions" @click.stop>
<div v-if="auth.isAdmin" class="proj-actions" @click.stop>
<button class="btn-icon-sm" @click="deleteProject(p)" title="删除">🗑</button>
</div>
</div>
@@ -177,6 +177,7 @@
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { useOilsStore } from '../stores/oils'
import { useRecipesStore } from '../stores/recipes'
@@ -188,6 +189,14 @@ const auth = useAuthStore()
const oils = useOilsStore()
const recipeStore = useRecipesStore()
const ui = useUiStore()
const router = useRouter()
async function showCertPrompt() {
const ok = await showConfirm('此功能需要商业认证,是否前往申请认证?', { okText: '去认证', cancelText: '取消' })
if (ok) {
router.push('/mydiary?tab=account&section=biz-cert')
}
}
const projects = ref([])
const selectedProject = ref(null)
@@ -237,6 +246,10 @@ async function createProject() {
}
function selectProject(p) {
if (!auth.isBusiness) {
showCertPrompt()
return
}
selectedProject.value = {
...p,
ingredients: (p.ingredients || []).map(i => ({ ...i })),

View File

@@ -189,8 +189,8 @@ const selectedCategory = ref(null)
const categories = ref([])
const selectedRecipeIndex = ref(null)
const selectedDiaryRecipe = ref(null)
const showMyRecipes = ref(true)
const showFavorites = ref(true)
const showMyRecipes = ref(false)
const showFavorites = ref(false)
const catIdx = ref(0)
const sharedCount = ref(0)