| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <template>
- <view class="app-library-page">
- <view class="page-header">
- <view class="header-left" @click="goBack">
- <text class="header-icon">←</text>
- <text class="header-text">应用库</text>
- </view>
- <text class="header-title">自定义首页快捷功能</text>
- </view>
- <!-- 已在首页有显著入口的功能 -->
- <view class="section" v-if="fixedApps.length > 0">
- <view class="section-title">已在首页显著位置</view>
- <view class="fixed-list">
- <view class="fixed-item" v-for="app in fixedApps" :key="app.key">
- <view class="fixed-info">
- <view class="fixed-icon">{{ app.icon }}</view>
- <view class="fixed-detail">
- <text class="fixed-name">{{ app.name }}</text>
- <text class="fixed-desc">{{ app.description }}</text>
- </view>
- </view>
- <view class="fixed-badge">已在首页</view>
- </view>
- </view>
- </view>
- <!-- 可配置的功能 -->
- <view class="section" v-if="configurableApps.length > 0">
- <view class="section-title">可配置的功能</view>
- <view class="configurable-list">
- <view class="configurable-item" v-for="app in configurableApps" :key="app.key" @click="toggleApp(app.key)">
- <view class="configurable-icon">{{ app.icon }}</view>
- <view class="configurable-detail">
- <text class="configurable-name">{{ app.name }}</text>
- <view v-if="isAppInQuickApps(app.key)" class="status-badge">已在快捷区</view>
- <view v-else class="status-badge">不在快捷区</view>
- </view>
- </view>
- </view>
- </view>
- <!-- 暂无配置 -->
- <view class="empty-state" v-if="fixedApps.length === 0 && configurableApps.length === 0">
- <text class="empty-icon">📦</text>
- <text class="empty-title">暂无应用</text>
- <text class="empty-desc">所有功能均在首页显著位置,无需额外配置</text>
- </view>
- <!-- 底部操作栏 -->
- <view class="bottom-bar">
- <view class="bottom-action">
- <text class="action-text">快捷区当前排序:</text>
- <view class="sort-info">
- <view v-for="(app, idx) in quickApps" :key="app.key" class="sort-chip">
- <text class="chip-icon">{{ app.icon }}</text>
- <text class="chip-label">{{ app.name }}</text>
- <text v-if="app.locked" class="chip-lock">🔒</text>
- </view>
- </view>
- </view>
- <view class="bottom-btn" @click="resetToDefault">恢复默认</view>
- </view>
- </view>
- </template>
- <script>
- import { APP_LIBRARY, getShortcutableApps, findAppByKey } from '@/utils/app-library'
- import { getAppLibrary, saveShortcutConfig, resetShortcutConfig } from '@/utils/api.js'
- export default {
- data() {
- return {
- fixedApps: [],
- configurableApps: [],
- quickApps: [],
- myShortcuts: [],
- appUsage: {}
- }
- },
- onShow() {
- this.loadAppLibrary()
- this.loadQuickApps()
- },
- methods: {
- goBack() {
- uni.navigateBack()
- },
- loadAppLibrary() {
- const allApps = APP_LIBRARY
- this.fixedApps = allApps.filter(app => app.canShortcut === false).slice(0, 20)
- this.configurableApps = allApps.filter(app => app.canShortcut === true).slice(0, 20)
- },
- loadQuickApps() {
- var self = this
- getAppLibrary().then(function(res) {
- if (res && res.code === 200 && res.data) {
- self.myShortcuts = res.data.myShortcuts || []
- self.appUsage = res.data.usage || {}
- // 计算 quickApps:锁定占位 + 频度填充
- var lockedApps = (self.myShortcuts || []).filter(function(s) { return s.locked === 1 })
- .sort(function(a, b) { return a.sortOrder - b.sortOrder })
- if (lockedApps.length >= 6) {
- self.quickApps = lockedApps.slice(0, 6).map(function(s) {
- var app = findAppByKey(s.appKey)
- return app ? { key: app.key, name: app.name, icon: app.icon, route: app.route, locked: s.locked } : null
- }).filter(function(x) { return x })
- return
- }
- var remaining = 6 - lockedApps.length
- var lockedKeys = {}
- lockedApps.forEach(function(s) { lockedKeys[s.appKey] = true })
- var candidates = getShortcutableApps().filter(function(app) { return !lockedKeys[app.key] })
- candidates.sort(function(a, b) {
- var ca = (self.appUsage && self.appUsage[a.key]) || 0
- var cb = (self.appUsage && self.appUsage[b.key]) || 0
- return cb - ca
- })
- var shown = lockedApps.concat(candidates.slice(0, remaining))
- self.quickApps = shown.map(function(item) {
- var app = findAppByKey(item.appKey)
- return app ? { key: app.key, name: app.name, icon: app.icon, route: app.route, locked: item.locked } : null
- }).filter(function(x) { return x })
- }
- }).catch(function() {
- self.loadAppLibrary()
- })
- },
- isAppInQuickApps: function(appKey) {
- return this.quickApps.some(function(app) { return app.key === appKey })
- },
- toggleApp: function(appKey) {
- var isInQuick = this.isAppInQuickApps(appKey)
- var items = []
- // 保留其他已配置项,更新当前项
- var existingItems = this.myShortcuts.map(function(s) { return { appKey: s.appKey, locked: s.locked, sortOrder: s.sortOrder } })
- var idx = existingItems.findIndex(function(s) { return s.appKey === appKey })
- if (idx >= 0) {
- existingItems[idx].locked = !isInQuick
- } else {
- existingItems.push({ appKey: appKey, locked: !isInQuick, sortOrder: existingItems.length })
- }
- saveShortcutConfig(existingItems).then(function(res) {
- if (res && res.code === 200) {
- this.loadAppLibrary()
- this.loadQuickApps()
- uni.showToast({ title: isInQuick ? '已移出快捷区' : '已添加至快捷区', icon: 'none' })
- }
- }.bind(this)).catch(function() {
- uni.showToast({ title: '操作失败', icon: 'none' })
- })
- },
- resetToDefault() {
- resetShortcutConfig().then(function(res) {
- if (res && res.code === 200) {
- this.loadAppLibrary()
- this.loadQuickApps()
- uni.showToast({ title: '已恢复默认配置', icon: 'success' })
- }
- }.bind(this)).catch(function() {
- uni.showToast({ title: '恢复失败', icon: 'none' })
- })
- }
- }
- }
- </script>
- <style>
- .app-library-page {
- padding: 20rpx;
- background: #F8F8F8;
- min-height: 100vh;
- box-sizing: border-box;
- }
- .page-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 30rpx;
- padding-bottom: 20rpx;
- border-bottom: 1rpx solid #E8E8E8;
- }
- .header-left {
- display: flex;
- align-items: center;
- gap: 8rpx;
- color: #333;
- font-size: 30rpx;
- }
- .header-icon {
- font-size: 32rpx;
- }
- .header-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- .section {
- margin-bottom: 30rpx;
- }
- .section-title {
- font-size: 28rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 16rpx;
- padding-left: 8rpx;
- }
- .fixed-list,
- .configurable-list {
- display: flex;
- flex-wrap: wrap;
- gap: 16rpx;
- }
- .fixed-item,
- .configurable-item {
- width: calc(50% - 8rpx);
- background: #fff;
- border-radius: 16rpx;
- padding: 20rpx;
- box-sizing: border-box;
- position: relative;
- }
- .fixed-info,
- .configurable-detail {
- display: flex;
- flex-direction: column;
- gap: 8rpx;
- }
- .fixed-icon,
- .configurable-icon {
- width: 48rpx;
- height: 48rpx;
- background: #F5FAFE;
- border-radius: 12rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 28rpx;
- }
- .fixed-name,
- .configurable-name {
- font-size: 26rpx;
- font-weight: bold;
- color: #333;
- }
- .fixed-desc,
- .configurable-desc {
- font-size: 20rpx;
- color: #999;
- line-height: 1.4;
- }
- .fixed-badge {
- position: absolute;
- top: 12rpx;
- right: 12rpx;
- background: #F0F0F0;
- color: #666;
- font-size: 18rpx;
- padding: 4rpx 12rpx;
- border-radius: 20rpx;
- }
- .status-badge {
- font-size: 20rpx;
- color: #999;
- margin-top: 8rpx;
- }
- .empty-state {
- padding: 120rpx 40rpx;
- text-align: center;
- }
- .empty-icon {
- font-size: 80rpx;
- display: block;
- margin-bottom: 20rpx;
- }
- .empty-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 10rpx;
- }
- .empty-desc {
- font-size: 24rpx;
- color: #999;
- }
- .bottom-bar {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 24rpx 0;
- border-top: 1rpx solid #E8E8E8;
- margin-top: 20rpx;
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- }
- .bottom-action {
- flex: 1;
- margin-right: 20rpx;
- }
- .action-text {
- font-size: 24rpx;
- color: #666;
- display: block;
- margin-bottom: 12rpx;
- }
- .sort-info {
- display: flex;
- flex-wrap: wrap;
- gap: 8rpx;
- }
- .sort-chip {
- background: #F5FAFE;
- border-radius: 20rpx;
- padding: 8rpx 16rpx;
- font-size: 22rpx;
- color: #333;
- display: flex;
- align-items: center;
- gap: 6rpx;
- }
- .chip-icon {
- font-size: 22rpx;
- }
- .chip-label {
- font-size: 22rpx;
- }
- .chip-lock {
- font-size: 20rpx;
- }
- .bottom-btn {
- flex: 0 0 auto;
- background: linear-gradient(135deg, #F97316, #FB923C);
- color: #fff;
- font-size: 26rpx;
- font-weight: bold;
- padding: 16rpx 32rpx;
- border-radius: 30rpx;
- white-space: nowrap;
- }
- </style>
|