fix(admin): unify settings save controls
This commit is contained in:
1 parent
7fe26d3fff
commit
f3dd1ec3b1
8 files changed
+113
-68
No files matched your search
@@ -103,57 +103,76 @@
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<div class="jio-form-footer">
|
||||
<n-button type="primary" :loading="saving" @click="saveCfg">保存配置</n-button>
|
||||
</div>
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="saveCfg" @reload="loadCfg" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { computed, ref, reactive, onMounted } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
// ---------- 配置 ----------
|
||||
const cfg = reactive({
|
||||
enabled: true,
|
||||
currency_name: '',
|
||||
register_bonus: 10,
|
||||
daily_signin: { enabled: true, base: 1, streak: { 3: 2, 7: 5, 30: 20 } },
|
||||
comment_reward: { enabled: true, amount: 1, daily_cap: 5, min_length: 10, on_approved: true },
|
||||
referral_reward: {
|
||||
function defaultCfg() {
|
||||
return {
|
||||
enabled: true,
|
||||
promoter_amount: 5,
|
||||
referred_amount: 2,
|
||||
level_points: 0,
|
||||
require_verify: true,
|
||||
ip_daily_cap: 3,
|
||||
},
|
||||
download_default_cost: 0,
|
||||
})
|
||||
currency_name: '',
|
||||
register_bonus: 10,
|
||||
daily_signin: { enabled: true, base: 1, streak: { 3: 2, 7: 5, 30: 20 } },
|
||||
comment_reward: { enabled: true, amount: 1, daily_cap: 5, min_length: 10, on_approved: true },
|
||||
referral_reward: {
|
||||
enabled: true,
|
||||
promoter_amount: 5,
|
||||
referred_amount: 2,
|
||||
level_points: 0,
|
||||
require_verify: true,
|
||||
ip_daily_cap: 3,
|
||||
},
|
||||
download_default_cost: 0,
|
||||
}
|
||||
}
|
||||
const cfg = reactive(defaultCfg())
|
||||
const streakJson = ref('{}')
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const initial = ref('')
|
||||
const snapshot = computed(() => JSON.stringify({ cfg, streakJson: streakJson.value }))
|
||||
const dirty = computed(() => !loading.value && initial.value !== '' && snapshot.value !== initial.value)
|
||||
|
||||
async function loadCfg() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await api.getCreditSettings()
|
||||
const data = response?.data || response || {}
|
||||
Object.assign(cfg, data)
|
||||
Object.assign(cfg, defaultCfg(), data)
|
||||
streakJson.value = JSON.stringify(cfg.daily_signin?.streak || {}, null, 0)
|
||||
initial.value = snapshot.value
|
||||
} catch (e) {
|
||||
message.error(e.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveCfg() {
|
||||
let streak
|
||||
try {
|
||||
streak = JSON.parse(streakJson.value || '{}')
|
||||
} catch (error) {
|
||||
message.error('连续奖励 JSON 格式错误')
|
||||
return
|
||||
}
|
||||
if (!streak || typeof streak !== 'object' || Array.isArray(streak)) {
|
||||
message.error('连续奖励必须是 JSON 对象')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
let streak = {}
|
||||
try { streak = JSON.parse(streakJson.value || '{}') } catch (_) { /* ignore */ }
|
||||
await api.saveCreditSettings({ ...cfg, daily_signin: { ...cfg.daily_signin, streak } })
|
||||
cfg.daily_signin.streak = streak
|
||||
await api.saveCreditSettings(cfg)
|
||||
initial.value = snapshot.value
|
||||
message.success('已保存')
|
||||
} catch (e) {
|
||||
message.error(e.message || '保存失败')
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div class="jio-page-container content-page">
|
||||
<PageHeader title="底部Tab配置" desc="自定义移动端底部导航栏的按钮,支持排序、图标配置和返回顶部功能。">
|
||||
<template #actions>
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存</n-button>
|
||||
</template>
|
||||
</PageHeader>
|
||||
<PageHeader title="底部Tab配置" desc="自定义移动端底部导航栏的按钮,支持排序、图标配置和返回顶部功能。" />
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||
<!-- 基础设置 -->
|
||||
@@ -99,12 +95,10 @@
|
||||
</draggable>
|
||||
<n-empty v-else description="暂无Tab按钮,请点击上方按钮添加" />
|
||||
</n-card>
|
||||
|
||||
<div class="jio-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存底部Tab配置</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="loadSettings" />
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
@@ -260,6 +254,7 @@ import { NButton, NTag, useDialog, useMessage } from 'naive-ui'
|
||||
import draggable from 'vuedraggable'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||
import FaIconPicker from '@/components/form/FaIconPicker.vue'
|
||||
import IconValue from '@/components/form/IconValue.vue'
|
||||
import JioSortHandle from '@/components/form/JioSortHandle.vue'
|
||||
@@ -280,6 +275,12 @@ const form = reactive({
|
||||
footer_tabbar_single: 'default',
|
||||
items: [],
|
||||
})
|
||||
const snapshot = ref('')
|
||||
const serialize = () => JSON.stringify(form)
|
||||
const dirty = computed(() => !loading.value && snapshot.value !== '' && serialize() !== snapshot.value)
|
||||
function markClean() {
|
||||
snapshot.value = serialize()
|
||||
}
|
||||
|
||||
const editingItem = reactive({
|
||||
id: '',
|
||||
@@ -552,6 +553,7 @@ async function loadSettings() {
|
||||
add_items: Array.isArray(item.add_items) ? item.add_items : [],
|
||||
sort_order: item.sort_order ?? idx,
|
||||
}))
|
||||
markClean()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[FooterTabbar] load failed:', e)
|
||||
@@ -581,6 +583,7 @@ async function handleSave() {
|
||||
}
|
||||
const res = await api.saveFooterTabbarSettings(payload)
|
||||
if (res.code === 'success') {
|
||||
markClean()
|
||||
message.success(res.message || '保存成功')
|
||||
} else {
|
||||
message.error(res.message || '保存失败')
|
||||
|
||||
@@ -47,20 +47,19 @@
|
||||
:scroll-x="1200"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<div class="jio-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存能力矩阵</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="load" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h, onMounted, reactive, ref } from 'vue'
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import { NInputNumber, NSwitch, NTag } from 'naive-ui'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||
|
||||
const message = useMessage()
|
||||
const loading = ref(true)
|
||||
@@ -74,6 +73,7 @@ const summary = reactive({
|
||||
})
|
||||
const userCaps = reactive({})
|
||||
const defaultsSnapshot = ref({})
|
||||
const dirty = computed(() => !loading.value && JSON.stringify(userCaps) !== JSON.stringify(defaultsSnapshot.value))
|
||||
|
||||
onMounted(load)
|
||||
|
||||
@@ -186,18 +186,11 @@ function resetDefaults() {
|
||||
message.info('已恢复默认矩阵')
|
||||
}
|
||||
|
||||
function expandAll() {
|
||||
message.info('当前矩阵为平铺展示,无需展开')
|
||||
}
|
||||
|
||||
function collapseAll() {
|
||||
message.info('当前矩阵为平铺展示,无需折叠')
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
await api.saveCapabilities(JSON.parse(JSON.stringify(userCaps)))
|
||||
const res = await api.saveCapabilities(JSON.parse(JSON.stringify(userCaps)))
|
||||
if (res?.data?.user_caps) replaceReactive(userCaps, res.data.user_caps)
|
||||
defaultsSnapshot.value = JSON.parse(JSON.stringify(userCaps))
|
||||
message.success('能力矩阵已保存')
|
||||
} catch (error) {
|
||||
@@ -241,10 +234,4 @@ async function handleSave() {
|
||||
.jio-cap-matrix__empty {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
.jio-form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -70,11 +70,8 @@
|
||||
</n-grid>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<div class="jio-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="handleSave" @reload="load" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -84,6 +81,7 @@ import { useRouter } from 'vue-router'
|
||||
import { NInput, NInputNumber, useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||
import {
|
||||
INTEGRAL_CATALOG,
|
||||
defaultIntegralOpt,
|
||||
@@ -104,6 +102,13 @@ const form = reactive({
|
||||
user_level_notify: true,
|
||||
})
|
||||
|
||||
const snapshot = ref('')
|
||||
const serialize = () => JSON.stringify(form)
|
||||
const dirty = computed(() => !loading.value && snapshot.value !== '' && serialize() !== snapshot.value)
|
||||
function markClean() {
|
||||
snapshot.value = serialize()
|
||||
}
|
||||
|
||||
const integralGroups = computed(() => [...new Set(INTEGRAL_CATALOG.map((r) => r.group))])
|
||||
|
||||
const levelRows = computed(() => {
|
||||
@@ -245,6 +250,7 @@ async function load() {
|
||||
form.user_level_notify = d.user_level_notify !== false
|
||||
form.user_level_opt = normalizeLevelOpt(d.user_level_opt, form.user_level_max)
|
||||
form.user_integral_opt = normalizeIntegral(d.user_integral_opt)
|
||||
markClean()
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载用户等级配置失败')
|
||||
} finally {
|
||||
@@ -270,6 +276,7 @@ async function handleSave() {
|
||||
user_level_opt: payload.user_level_opt,
|
||||
user_integral_opt: payload.user_integral_opt,
|
||||
})
|
||||
markClean()
|
||||
message.success('用户等级配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
|
||||
@@ -18,11 +18,8 @@
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
<div class="jio-form-actions">
|
||||
<n-button :disabled="!dirty || saving" @click="load">放弃更改</n-button>
|
||||
<n-button type="primary" :disabled="!dirty" :loading="saving" @click="save">保存设置</n-button>
|
||||
</div>
|
||||
</n-spin>
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="save" @reload="load" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -30,6 +27,7 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||
import api from '@/api'
|
||||
|
||||
const defaults = {
|
||||
@@ -45,7 +43,7 @@ const initial = ref('')
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const snapshot = computed(() => JSON.stringify(form))
|
||||
const dirty = computed(() => initial.value !== '' && snapshot.value !== initial.value)
|
||||
const dirty = computed(() => !loading.value && initial.value !== '' && snapshot.value !== initial.value)
|
||||
|
||||
function extractData(response) {
|
||||
return response?.data ?? response ?? {}
|
||||
|
||||
@@ -53,11 +53,6 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="jio-form-actions">
|
||||
<n-button :disabled="!dirty || saving" @click="load">放弃更改</n-button>
|
||||
<n-button type="primary" :disabled="!dirty || loading" :loading="saving" @click="save">保存设置</n-button>
|
||||
</div>
|
||||
|
||||
<section class="wechat-section wechat-test">
|
||||
<h3>发送测试</h3>
|
||||
<p class="wechat-muted">先保存配置,再选择已启用的模板。测试消息会发送到当前管理员绑定的公众号账号。</p>
|
||||
@@ -68,6 +63,7 @@
|
||||
</section>
|
||||
</template>
|
||||
</n-spin>
|
||||
<JioSaveBar :dirty="dirty" :saving="saving" @save="save" @reload="load" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -75,6 +71,7 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import JioSaveBar from '@/components/form/JioSaveBar.vue'
|
||||
import api from '@/api'
|
||||
|
||||
const message = useMessage()
|
||||
@@ -90,7 +87,7 @@ const form = reactive({ enabled: false, templates: {} })
|
||||
const initial = ref('')
|
||||
const testType = ref(null)
|
||||
const snapshot = computed(() => JSON.stringify(form))
|
||||
const dirty = computed(() => initial.value !== '' && initial.value !== snapshot.value)
|
||||
const dirty = computed(() => !loading.value && initial.value !== '' && initial.value !== snapshot.value)
|
||||
|
||||
const groups = computed(() => {
|
||||
const grouped = new Map()
|
||||
|
||||
Reference in new issue
Block a user