| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- <template>
- <div class="app-container">
- <el-alert
- title="以下配置对全站所有直播间生效。"
- type="warning"
- :closable="false"
- show-icon
- class="mb16"
- />
- <!-- App 端需监听 WebSocket cmd=liveCommentConfig,data 为 JSON(仅全局 config),与总后台无直接耦合 -->
- <el-card shadow="never">
- <el-form
- ref="form"
- v-loading="loading"
- :model="form"
- :rules="rules"
- label-width="160px"
- >
- <div class="feature-section section-threshold">
- <el-divider content-position="left">库存提示阈值</el-divider>
- <el-form-item label="阈值配置">
- <el-input
- v-model.trim="thresholdInput"
- placeholder="请输入1~9999"
- style="width: 220px"
- @keyup.enter.native="saveStockThreshold"
- />
- <el-button
- type="primary"
- size="mini"
- :loading="thresholdSaving"
- style="margin-left: 12px"
- @click="saveStockThreshold"
- >保存</el-button>
- <span class="form-tip">当库存小于等于该阈值时,App端显示“仅剩X件”。</span>
- </el-form-item>
- </div>
- <div class="feature-section section-float">
- <el-divider content-position="left">飘屏</el-divider>
- <el-form-item label="飘屏总开关" prop="floatEnabled">
- <el-switch
- v-model="form.floatEnabled"
- :active-value="1"
- :inactive-value="0"
- />
- </el-form-item>
- <el-form-item label="用户飘屏冷却" prop="floatCooldownSec">
- <el-input-number
- v-model="form.floatCooldownSec"
- :min="0"
- :precision="0"
- controls-position="right"
- placeholder="秒"
- />
- <span class="form-tip">同一用户连续飘屏的最小间隔(秒)</span>
- </el-form-item>
- <el-form-item label="可飘屏角色" prop="floatRoleCodes">
- <el-select
- v-model="floatRoleList"
- multiple
- filterable
- placeholder="请选择角色"
- style="width: 420px"
- :loading="rolesLoading"
- @visible-change="ensureRolesLoaded"
- >
- <el-option
- v-for="r in roleOptions"
- :key="r"
- :label="r"
- :value="r"
- />
- </el-select>
- <span class="form-tip">选项来自企业角色;保存为英文逗号分隔的 role_name</span>
- </el-form-item>
- </div>
- <div class="feature-section section-pin">
- <el-divider content-position="left">评论置顶</el-divider>
- <el-form-item label="单房间最大置顶数" prop="pinMaxPerRoom">
- <el-input-number
- v-model="form.pinMaxPerRoom"
- :min="1"
- :precision="0"
- controls-position="right"
- />
- </el-form-item>
- <el-form-item label="可选置顶时长" prop="pinDurationOptions">
- <el-select
- v-model="pinDurationList"
- multiple
- placeholder="分钟;-1 表示永久"
- style="width: 420px"
- >
- <el-option label="5 分钟" :value="5" />
- <el-option label="10 分钟" :value="10" />
- <el-option label="15 分钟" :value="15" />
- <el-option label="30 分钟" :value="30" />
- <el-option label="60 分钟" :value="60" />
- <el-option label="永久 (-1)" :value="-1" />
- </el-select>
- <span class="form-tip">保存为逗号分隔数字,如 5,10,30,-1</span>
- </el-form-item>
- <el-form-item label="可置顶角色" prop="pinRoleCodes">
- <el-select
- v-model="pinRoleList"
- multiple
- filterable
- placeholder="请选择角色"
- style="width: 420px"
- :loading="rolesLoading"
- @visible-change="ensureRolesLoaded"
- >
- <el-option
- v-for="r in roleOptions"
- :key="r"
- :label="r"
- :value="r"
- />
- </el-select>
- </el-form-item>
- </div>
- <div class="feature-section section-other">
- <el-divider content-position="left">其他</el-divider>
- <el-form-item label="备注" prop="remark">
- <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="备注" />
- </el-form-item>
- <el-form-item label="最近更新人">
- <span>{{ form.updateBy || '—' }}</span>
- </el-form-item>
- <el-form-item label="最近更新时间">
- <span>{{ form.updateTime ? parseTime(form.updateTime) : '—' }}</span>
- </el-form-item>
- </div>
- <el-form-item>
- <el-button
- type="primary"
- :loading="saving"
- @click="submitForm"
- v-hasPermi="['live:commentFeature:edit']"
- >保 存</el-button>
- <el-button @click="loadConfig">重 置</el-button>
- </el-form-item>
- </el-form>
- </el-card>
- </div>
- </template>
- <script>
- import {
- getCommentFeatureConfig,
- updateCommentFeatureConfig,
- getCommentFeatureRoles
- } from '@/api/live/commentFeature'
- import { getStockHintThreshold, updateStockHintThreshold } from '@/api/live/liveGoods'
- const splitCodes = (s) => {
- if (s == null || String(s).trim() === '') return []
- return String(s)
- .split(',')
- .map((x) => x.trim())
- .filter(Boolean)
- }
- const joinCodes = (arr) => (Array.isArray(arr) ? arr.map((x) => String(x).trim()).filter(Boolean) : []).join(',')
- export default {
- name: 'LiveCommentGlobalConfig',
- data() {
- return {
- loading: false,
- saving: false,
- thresholdSaving: false,
- thresholdInput: '',
- /** 下拉候选项(GET /live/commentFeature/roles),并与已选值合并以便回显 */
- roleOptions: [],
- rolesLoaded: false,
- rolesLoading: false,
- floatRoleList: [],
- pinRoleList: [],
- pinDurationList: [],
- form: {
- configId: 1,
- floatEnabled: 0,
- floatCooldownSec: 0,
- floatRoleCodes: '',
- pinMaxPerRoom: 1,
- pinDurationOptions: '',
- pinRoleCodes: '',
- remark: '',
- updateBy: '',
- updateTime: null
- },
- rules: {
- floatCooldownSec: [{ required: true, message: '请输入冷却秒数', trigger: 'blur' }],
- pinMaxPerRoom: [{ required: true, message: '请输入单房间最大置顶数', trigger: 'blur' }]
- }
- }
- },
- created() {
- this.loadConfig()
- this.loadStockThreshold()
- },
- methods: {
- isValidThreshold(value) {
- return /^[1-9]\d{0,3}$/.test(String(value))
- },
- loadStockThreshold() {
- getStockHintThreshold()
- .then((response) => {
- const threshold = response && response.threshold
- this.thresholdInput = threshold != null ? String(threshold) : ''
- })
- .catch((error) => {
- this.$message.error((error && error.msg) || (error && error.message) || '阈值加载失败')
- })
- },
- saveStockThreshold() {
- if (!this.isValidThreshold(this.thresholdInput)) {
- this.$message.error('阈值范围需在1~9999')
- return
- }
- this.thresholdSaving = true
- updateStockHintThreshold(Number(this.thresholdInput))
- .then(() => {
- this.msgSuccess('保存成功')
- this.loadStockThreshold()
- })
- .catch((error) => {
- this.$message.error((error && error.msg) || (error && error.message) || '保存失败')
- })
- .finally(() => {
- this.thresholdSaving = false
- })
- },
- /** 将已选 role_name 合并进 options,避免接口未返回历史已选项时标签不显示 */
- mergeSelectedIntoRoleOptions() {
- const set = new Set(Array.isArray(this.roleOptions) ? this.roleOptions : [])
- ;(this.floatRoleList || []).forEach((x) => {
- const s = x != null ? String(x).trim() : ''
- if (s) set.add(s)
- })
- ;(this.pinRoleList || []).forEach((x) => {
- const s = x != null ? String(x).trim() : ''
- if (s) set.add(s)
- })
- this.roleOptions = Array.from(set)
- },
- /** 首次展开下拉时拉取角色列表;空数据不报错 */
- ensureRolesLoaded(visible) {
- if (!visible) return
- if (this.rolesLoaded) {
- this.mergeSelectedIntoRoleOptions()
- return
- }
- this.rolesLoading = true
- getCommentFeatureRoles()
- .then((response) => {
- const list = response && response.data
- this.roleOptions = Array.isArray(list) ? list.slice() : []
- this.rolesLoaded = true
- this.mergeSelectedIntoRoleOptions()
- })
- .catch(() => {
- this.roleOptions = []
- this.rolesLoaded = true
- this.mergeSelectedIntoRoleOptions()
- })
- .finally(() => {
- this.rolesLoading = false
- })
- },
- loadConfig() {
- this.loading = true
- getCommentFeatureConfig()
- .then((response) => {
- const d = response.data || {}
- this.form = {
- configId: d.configId != null ? d.configId : 1,
- floatEnabled: Number(d.floatEnabled) === 1 ? 1 : 0,
- floatCooldownSec: d.floatCooldownSec != null ? Number(d.floatCooldownSec) : 0,
- floatRoleCodes: d.floatRoleCodes || '',
- pinMaxPerRoom: d.pinMaxPerRoom != null ? Number(d.pinMaxPerRoom) : 1,
- pinDurationOptions: d.pinDurationOptions || '',
- pinRoleCodes: d.pinRoleCodes || '',
- remark: d.remark || '',
- updateBy: d.updateBy || '',
- updateTime: d.updateTime
- }
- this.floatRoleList = splitCodes(this.form.floatRoleCodes)
- this.pinRoleList = splitCodes(this.form.pinRoleCodes)
- this.pinDurationList = splitCodes(this.form.pinDurationOptions).map((x) => {
- const n = Number(x)
- return Number.isNaN(n) ? x : n
- })
- this.mergeSelectedIntoRoleOptions()
- })
- .finally(() => {
- this.loading = false
- })
- },
- submitForm() {
- this.$refs.form.validate((valid) => {
- if (!valid) return
- const pinDur = joinCodes(this.pinDurationList)
- const payload = {
- configId: this.form.configId,
- floatEnabled: this.form.floatEnabled,
- floatCooldownSec: this.form.floatCooldownSec,
- floatRoleCodes: joinCodes(this.floatRoleList),
- pinMaxPerRoom: this.form.pinMaxPerRoom,
- pinDurationOptions: pinDur,
- pinRoleCodes: joinCodes(this.pinRoleList),
- remark: (this.form.remark || '').trim()
- }
- this.saving = true
- updateCommentFeatureConfig(payload)
- .then(() => {
- this.msgSuccess('保存成功')
- this.loadConfig()
- })
- .finally(() => {
- this.saving = false
- })
- })
- }
- }
- }
- </script>
- <style scoped>
- .mb16 {
- margin-bottom: 16px;
- }
- .form-tip {
- margin-left: 12px;
- color: #909399;
- font-size: 12px;
- }
- .feature-section {
- padding: 10px 12px 4px;
- margin-bottom: 14px;
- border-radius: 6px;
- border: 1px solid #ebeef5;
- background: #fafafa;
- }
- .section-threshold {
- border-left: 4px solid #e6a23c;
- background: #fffaf2;
- }
- .section-float {
- border-left: 4px solid #409eff;
- background: #f4f8ff;
- }
- .section-pin {
- border-left: 4px solid #67c23a;
- background: #f6fcf2;
- }
- .section-other {
- border-left: 4px solid #909399;
- background: #f8f8f9;
- }
- </style>
|