Files
qitongxue-wx/src/pages/workbench/edit.vue
2026-09-29 10:57:55 +08:00

279 lines
6.9 KiB
Vue
Raw 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.
<script setup lang="ts">
import { onShow } from '@dcloudio/uni-app'
import { computed, ref } from 'vue'
import { useToolsStore } from '@/stores/tools'
import { useUserStore } from '@/stores/user'
import { navPadTop } from '@/utils/safearea'
import { Z } from '@/utils/z-index'
const toolsStore = useToolsStore()
const userStore = useUserStore()
const keys = ref<string[]>([])
const saving = ref(false)
/** 导航栏顶部留白:MP 用真实状态栏高度 */
const navPad = navPadTop()
function goBack() {
uni.navigateBack()
}
onShow(async () => {
await userStore.ensureLogin()
await toolsStore.fetchAll()
keys.value = [...toolsStore.workbenchKeys]
})
const pinned = computed(() =>
keys.value
.map((key) => {
const server = toolsStore.tools.find(t => t.tool_key === key)
const def = toolsStore.libraryByModule(server?.module_key || '').find(d => d.key === key)
return def ? { ...def, locked: server?.locked ?? true } : null
})
.filter((x): x is NonNullable<typeof x> => x !== null),
)
const available = computed(() =>
toolsStore.tools
.filter(t => !t.locked && !keys.value.includes(t.tool_key))
.map(t => ({ key: t.tool_key, name: t.name, icon: t.icon, desc: t.description })),
)
function add(key: string) {
if (!keys.value.includes(key))
keys.value.push(key)
}
function remove(key: string) {
keys.value = keys.value.filter(k => k !== key)
}
function move(index: number, delta: -1 | 1) {
const target = index + delta
if (target < 0 || target >= keys.value.length)
return
const next = [...keys.value]
;[next[index], next[target]] = [next[target], next[index]]
keys.value = next
}
async function save() {
if (!keys.value.length) {
uni.showToast({ title: '至少保留一个工具', icon: 'none' })
return
}
saving.value = true
try {
await toolsStore.saveWorkbench(keys.value)
uni.showToast({ title: '已保存', icon: 'success' })
setTimeout(() => uni.navigateBack(), 500)
}
finally {
saving.value = false
}
}
</script>
<template>
<view class="page">
<view class="nav" :style="{ paddingTop: navPad }">
<view class="nav__back press" hover-class="press-active" @tap="goBack">
<wd-icon name="arrow-left" size="36rpx" color="#3d2a1e" />
</view>
<text class="nav__title">
编辑工作台
</text>
<!-- 与返回键等宽的占位,保证标题居中 -->
<view class="nav__slot" />
</view>
<view class="section">
<text class="tb-section-title">
已固定({{ pinned.length }})
</text>
<view v-if="!pinned.length" class="tb-empty">
<text class="tb-empty__sub">
工作台为空,从下方添加工具
</text>
</view>
<view v-for="(t, i) in pinned" :key="t.key" class="row">
<view class="row__ops">
<view class="op press" hover-class="press-active" @tap="move(i, -1)">
<wd-icon name="arrow-up" size="26rpx" color="#9a7b65" />
</view>
<view class="op press" hover-class="press-active" @tap="move(i, 1)">
<wd-icon name="arrow-down" size="26rpx" color="#9a7b65" />
</view>
</view>
<view class="row__icon">
<wd-icon :name="t.icon" size="36rpx" color="#ff6b35" />
</view>
<view class="row__body">
<text class="row__name">
{{ t.name }}
</text>
<text class="row__desc">
{{ t.desc }}
</text>
</view>
<view class="row__remove press" hover-class="press-active" @tap="remove(t.key)">
<wd-icon name="delete" size="32rpx" color="#DC2626" />
</view>
</view>
</view>
<view class="section">
<text class="tb-section-title">
可添加({{ available.length }})
</text>
<view v-if="!available.length" class="tb-empty">
<text class="tb-empty__sub">
没有更多可添加的工具了
</text>
</view>
<view v-for="t in available" :key="t.key" class="row">
<view class="row__icon">
<wd-icon :name="t.icon" size="36rpx" color="#ff6b35" />
</view>
<view class="row__body">
<text class="row__name">
{{ t.name }}
</text>
<text class="row__desc">
{{ t.desc }}
</text>
</view>
<view class="row__add press" hover-class="press-active" @tap="add(t.key)">
<wd-icon name="add" size="32rpx" color="#ff6b35" />
</view>
</view>
</view>
<!-- 底部悬浮保存栏 -->
<view class="save-bar" :style="{ zIndex: Z.stickyBar }">
<wd-button
type="primary"
block
:loading="saving"
custom-style="border-radius: 999rpx; height: 88rpx; font-size: 30rpx; box-shadow: 0 12rpx 28rpx rgba(251, 78, 20, 0.32);"
@click="save"
>
保存工作台
</wd-button>
</view>
</view>
</template>
<style scoped>
.page {
min-height: 100vh;
/* 底部给悬浮保存栏留出空间 */
padding: 0 32rpx calc(180rpx + env(safe-area-inset-bottom, 0px));
box-sizing: border-box;
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 0 20rpx;
}
.nav__back {
flex-shrink: 0;
width: 64rpx;
height: 64rpx;
border-radius: 50%;
background: #ffffff;
border: 1rpx solid #ffe9dd;
box-shadow: 0 6rpx 16rpx rgba(107, 47, 15, 0.08);
display: flex;
align-items: center;
justify-content: center;
}
.nav__title {
font-size: 34rpx;
font-weight: 700;
color: #3d2a1e;
}
.nav__slot {
flex-shrink: 0;
width: 64rpx;
height: 64rpx;
}
.section {
margin-top: 32rpx;
}
.section > .tb-section-title {
margin-bottom: 20rpx;
}
.row {
display: flex;
align-items: center;
gap: 20rpx;
background: #ffffff;
border: 1rpx solid rgba(255, 195, 155, 0.55);
border-radius: 30rpx;
padding: 22rpx 24rpx;
margin-top: 18rpx;
box-shadow: 0 10rpx 26rpx rgba(107, 47, 15, 0.06);
}
.row__ops {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.op {
width: 52rpx;
height: 52rpx;
border-radius: 16rpx;
background: #fff9f2;
display: flex;
align-items: center;
justify-content: center;
}
.row__icon {
flex-shrink: 0;
width: 72rpx;
height: 72rpx;
border-radius: 22rpx;
background: #fff9f2;
border: 1rpx solid #ffe3d3;
display: flex;
align-items: center;
justify-content: center;
}
.row__body {
flex: 1;
min-width: 0;
}
.row__name {
font-size: 30rpx;
font-weight: 700;
color: #3d2a1e;
}
.row__desc {
display: block;
margin-top: 4rpx;
font-size: 22rpx;
color: #9a7b65;
}
.row__remove,
.row__add {
flex-shrink: 0;
width: 68rpx;
height: 68rpx;
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
}
.row__add {
background: #ffe3d3;
}
/* 底部悬浮保存栏(Home Indicator 之上) */
.save-bar {
position: fixed;
left: 32rpx;
right: 32rpx;
bottom: calc(20rpx + env(safe-area-inset-bottom, 0px));
}
</style>