|
|
@@ -0,0 +1,393 @@
|
|
|
+<template>
|
|
|
+ <div class="category-link" v-loading="loading">
|
|
|
+ <div class="btnBox">
|
|
|
+ <div class="primary-btn" @click="openAdd" v-log="{ describe: { action: '点击新增分类关联' } }">新增</div>
|
|
|
+ </div>
|
|
|
+ <el-table :data="links" border max-height="420" style="width: 100%" empty-text="暂无分类关联">
|
|
|
+ <el-table-column prop="category_name" label="分类名称" />
|
|
|
+ <el-table-column label="左右脚" width="120">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ footText(row) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="程序配置">
|
|
|
+ <template #default="{ row }">
|
|
|
+ {{ configText(row) }}
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="140">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <a class="mar-right-10 cursor-pointer" @click="openEdit(row, $index)" v-log="{ describe: { action: '点击编辑分类关联', category_name: row.category_name } }">编辑</a>
|
|
|
+ <a class="cursor-pointer" @click="removeLink(row, $index)" v-log="{ describe: { action: '点击删除分类关联', category_name: row.category_name } }">删除</a>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="editingIndex >= 0 ? '编辑分类关联' : '新增分类关联'"
|
|
|
+ width="480px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ append-to-body
|
|
|
+ >
|
|
|
+ <el-form label-width="90px">
|
|
|
+ <el-form-item label="分类名称">
|
|
|
+ <el-input
|
|
|
+ v-model="form.category_name"
|
|
|
+ :disabled="editingIndex >= 0"
|
|
|
+ maxlength="128"
|
|
|
+ placeholder="请输入分类名称"
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="左右脚">
|
|
|
+ <el-radio-group v-model="form.foot" @change="onFootChange">
|
|
|
+ <el-radio :label="0">左脚</el-radio>
|
|
|
+ <el-radio :label="1">右脚</el-radio>
|
|
|
+ </el-radio-group>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="程序配置">
|
|
|
+ <el-select v-model="form.tab_id" placeholder="请选择程序" style="width: 100%">
|
|
|
+ <el-option
|
|
|
+ v-for="tab in tabOptions"
|
|
|
+ :key="tab.id"
|
|
|
+ :label="tab.mode_name"
|
|
|
+ :value="tab.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <div class="btn-row">
|
|
|
+ <div class="normal-btn" @click="dialogVisible = false">取消</div>
|
|
|
+ <div class="primary-btn" :class="{ disabled: saving }" @click="saveLink">
|
|
|
+ {{ saving ? '保存中' : '保存' }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import client from '@/stores/modules/client'
|
|
|
+import tokenInfo from '@/stores/modules/token'
|
|
|
+import useUserInfo from '@/stores/modules/user'
|
|
|
+import icpList from '@/utils/ipc'
|
|
|
+
|
|
|
+const clientStore = client()
|
|
|
+const tokenInfoStore = tokenInfo()
|
|
|
+const userInfoStore = useUserInfo()
|
|
|
+
|
|
|
+type TabItem = {
|
|
|
+ id: number
|
|
|
+ mode_name: string
|
|
|
+}
|
|
|
+
|
|
|
+type LinkItem = {
|
|
|
+ category_name: string
|
|
|
+ tab_name?: string
|
|
|
+ tab_id?: number
|
|
|
+ mode_type?: number
|
|
|
+ left_tab_id?: number
|
|
|
+ left_tab_name?: string
|
|
|
+ right_tab_id?: number
|
|
|
+ right_tab_name?: string
|
|
|
+}
|
|
|
+
|
|
|
+const loading = ref(false)
|
|
|
+const saving = ref(false)
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const editingIndex = ref(-1)
|
|
|
+const links = ref<LinkItem[]>([])
|
|
|
+const leftTabs = ref<TabItem[]>([])
|
|
|
+const rightTabs = ref<TabItem[]>([])
|
|
|
+
|
|
|
+const form = reactive({
|
|
|
+ category_name: '',
|
|
|
+ foot: 0 as 0 | 1,
|
|
|
+ tab_id: undefined as number | undefined,
|
|
|
+})
|
|
|
+
|
|
|
+function tabNameById(tabId: number, storedName?: string) {
|
|
|
+ const tab = [...leftTabs.value, ...rightTabs.value].find(item => item.id === tabId)
|
|
|
+ return tab?.mode_name || storedName || ''
|
|
|
+}
|
|
|
+
|
|
|
+function footOf(tabId: number) {
|
|
|
+ if (leftTabs.value.some(tab => tab.id === tabId)) return 0
|
|
|
+ if (rightTabs.value.some(tab => tab.id === tabId)) return 1
|
|
|
+ return -1
|
|
|
+}
|
|
|
+
|
|
|
+function rowSides(row: LinkItem) {
|
|
|
+ const sides: { foot: 0 | 1, id: number, name: string }[] = []
|
|
|
+ const tabId = Number(row.tab_id || 0)
|
|
|
+ if (tabId) {
|
|
|
+ const inferred = footOf(tabId)
|
|
|
+ const foot: 0 | 1 = row.mode_type === 0 || row.mode_type === 1
|
|
|
+ ? row.mode_type
|
|
|
+ : inferred === 1 ? 1 : 0
|
|
|
+ sides.push({ foot, id: tabId, name: tabNameById(tabId, row.tab_name) })
|
|
|
+ return sides
|
|
|
+ }
|
|
|
+ const leftId = Number(row.left_tab_id || 0)
|
|
|
+ const rightId = Number(row.right_tab_id || 0)
|
|
|
+ if (leftId) sides.push({ foot: 0, id: leftId, name: tabNameById(leftId, row.left_tab_name) })
|
|
|
+ if (rightId) sides.push({ foot: 1, id: rightId, name: tabNameById(rightId, row.right_tab_name) })
|
|
|
+ return sides
|
|
|
+}
|
|
|
+
|
|
|
+function footText(row: LinkItem) {
|
|
|
+ const sides = rowSides(row)
|
|
|
+ if (!sides.length) return '未设置'
|
|
|
+ return sides.map(side => (side.foot === 0 ? '左脚' : '右脚')).join('、')
|
|
|
+}
|
|
|
+
|
|
|
+function configText(row: LinkItem) {
|
|
|
+ const sides = rowSides(row)
|
|
|
+ if (!sides.length) return '未设置'
|
|
|
+ return sides.map(side => side.name || '未设置').join('、')
|
|
|
+}
|
|
|
+
|
|
|
+function usedTabIds() {
|
|
|
+ const used = new Set<number>()
|
|
|
+ links.value.forEach((item, index) => {
|
|
|
+ if (index === editingIndex.value) return
|
|
|
+ rowSides(item).forEach(side => used.add(side.id))
|
|
|
+ })
|
|
|
+ return used
|
|
|
+}
|
|
|
+
|
|
|
+const tabOptions = computed(() => {
|
|
|
+ const used = usedTabIds()
|
|
|
+ const tabs = form.foot === 0 ? leftTabs.value : rightTabs.value
|
|
|
+ return tabs.filter(tab => !used.has(tab.id) || tab.id === form.tab_id)
|
|
|
+})
|
|
|
+
|
|
|
+function onFootChange(foot: 0 | 1) {
|
|
|
+ const tabs = foot === 0 ? leftTabs.value : rightTabs.value
|
|
|
+ if (!tabs.some(tab => tab.id === form.tab_id)) {
|
|
|
+ form.tab_id = undefined
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function applyList(list: LinkItem[] | undefined) {
|
|
|
+ links.value = Array.isArray(list) ? list : []
|
|
|
+}
|
|
|
+
|
|
|
+function resultMessage(result: any, fallback: string) {
|
|
|
+ return result?.message || result?.msg || result?.err_msg || fallback
|
|
|
+}
|
|
|
+
|
|
|
+function currentCompanyId() {
|
|
|
+ const companyId = Number((userInfoStore.userInfo as any)?.brand_company_id)
|
|
|
+ return Number.isFinite(companyId) ? companyId : 0
|
|
|
+}
|
|
|
+
|
|
|
+async function loadTabs() {
|
|
|
+ const [left, right] = await Promise.all([
|
|
|
+ clientStore.ipc.invoke(icpList.setting.getDeviceTabs, { type: 0 }),
|
|
|
+ clientStore.ipc.invoke(icpList.setting.getDeviceTabs, { type: 1 }),
|
|
|
+ ])
|
|
|
+ if (left?.code === 0) leftTabs.value = left.data?.tabs || []
|
|
|
+ if (right?.code === 0) rightTabs.value = right.data?.tabs || []
|
|
|
+}
|
|
|
+
|
|
|
+async function loadLinks() {
|
|
|
+ const companyId = currentCompanyId()
|
|
|
+ if (!companyId) {
|
|
|
+ throw { msg: '企业ID不能为空' }
|
|
|
+ }
|
|
|
+ const result = await clientStore.ipc.invoke(icpList.setting.getCategoryLinks, {
|
|
|
+ company_id: companyId,
|
|
|
+ })
|
|
|
+ if (result?.code !== 0) {
|
|
|
+ throw result
|
|
|
+ }
|
|
|
+ applyList(result.data?.list)
|
|
|
+}
|
|
|
+
|
|
|
+async function refresh() {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ await Promise.all([loadTabs(), loadLinks()])
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error(resultMessage(error, '获取分类关联失败'))
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function openAdd() {
|
|
|
+ editingIndex.value = -1
|
|
|
+ form.category_name = ''
|
|
|
+ form.foot = 0
|
|
|
+ form.tab_id = undefined
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+function openEdit(row: LinkItem, index: number) {
|
|
|
+ const side = rowSides(row)[0]
|
|
|
+ editingIndex.value = index
|
|
|
+ form.category_name = row.category_name
|
|
|
+ form.foot = side?.foot ?? 0
|
|
|
+ form.tab_id = side?.id
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+async function saveLink() {
|
|
|
+ if (saving.value) return
|
|
|
+ const categoryName = (form.category_name || '').trim()
|
|
|
+ if (!categoryName) {
|
|
|
+ ElMessage.warning('分类名称不能为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!form.tab_id) {
|
|
|
+ ElMessage.warning('请选择左右脚程序')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ saving.value = true
|
|
|
+ try {
|
|
|
+ const companyId = currentCompanyId()
|
|
|
+ if (!companyId) {
|
|
|
+ ElMessage.warning('企业ID不能为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const result = await clientStore.ipc.invoke(icpList.setting.saveCategoryLink, {
|
|
|
+ company_id: companyId,
|
|
|
+ category_name: categoryName,
|
|
|
+ tab_id: form.tab_id,
|
|
|
+ token: tokenInfoStore.getToken || '',
|
|
|
+ })
|
|
|
+ if (result?.code !== 0) {
|
|
|
+ ElMessage.error(resultMessage(result, '保存失败'))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ applyList(result.data?.list)
|
|
|
+ dialogVisible.value = false
|
|
|
+ ElMessage.success(result.msg || '操作成功')
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error(resultMessage(error, '保存失败'))
|
|
|
+ } finally {
|
|
|
+ saving.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function removeLink(row: LinkItem, index: number) {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm(`确定删除分类「${row.category_name}」的关联吗?`, '删除分类关联', {
|
|
|
+ confirmButtonText: '删除',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const companyId = currentCompanyId()
|
|
|
+ if (!companyId) {
|
|
|
+ ElMessage.warning('企业ID不能为空')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const result = await clientStore.ipc.invoke(icpList.setting.deleteCategoryLink, {
|
|
|
+ company_id: companyId,
|
|
|
+ index,
|
|
|
+ token: tokenInfoStore.getToken || '',
|
|
|
+ })
|
|
|
+ if (result?.code !== 0) {
|
|
|
+ ElMessage.error(resultMessage(result, '删除失败'))
|
|
|
+ return
|
|
|
+ }
|
|
|
+ applyList(result.data?.list)
|
|
|
+ ElMessage.success(result.msg || '删除成功')
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error(resultMessage(error, '删除失败'))
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ refresh()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.category-link {
|
|
|
+ .btnBox {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+ .primary-btn {
|
|
|
+ width: 80px;
|
|
|
+ height: 30px;
|
|
|
+ background: linear-gradient(135deg, #2FB0FF 0%, #B863FB 100%);
|
|
|
+ border-radius: 4px;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 14px;
|
|
|
+ text-align: center;
|
|
|
+ cursor: pointer;
|
|
|
+ line-height: 30px;
|
|
|
+ margin-right: 10px;
|
|
|
+ &.disabled {
|
|
|
+ opacity: 0.6;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .cursor-pointer {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .mar-right-10 {
|
|
|
+ margin-right: 10px;
|
|
|
+ }
|
|
|
+ :deep(.el-table .el-table__header) {
|
|
|
+ .el-table__cell {
|
|
|
+ background: #F1F4FF;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ :deep(.el-table .el-table__cell) {
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ :deep(.el-table__row:nth-child(even)) {
|
|
|
+ background: #F1F4FF;
|
|
|
+ }
|
|
|
+}
|
|
|
+.btn-row {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+ .primary-btn,
|
|
|
+ .normal-btn {
|
|
|
+ width: 100px;
|
|
|
+ height: 30px;
|
|
|
+ border-radius: 4px;
|
|
|
+ font-size: 14px;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 30px;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+ .primary-btn {
|
|
|
+ background: linear-gradient(135deg, #2FB0FF 0%, #B863FB 100%);
|
|
|
+ color: #fff;
|
|
|
+ &.disabled {
|
|
|
+ opacity: 0.6;
|
|
|
+ pointer-events: none;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .normal-btn {
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #CCCCCC;
|
|
|
+ }
|
|
|
+}
|
|
|
+.empty-tip {
|
|
|
+ margin-top: 6px;
|
|
|
+ color: #999;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.4;
|
|
|
+}
|
|
|
+</style>
|