This commit is contained in:
@@ -51,7 +51,12 @@
|
||||
<span>{{ currentConfig?.description || '-' }}</span>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="配置值">
|
||||
<ElSwitch v-if="isBooleanConfig" v-model="editBoolean" />
|
||||
<ElCheckboxGroup v-if="isPaymentConfig" v-model="editPaymentMethods">
|
||||
<ElCheckbox v-for="method in paymentMethods" :key="method" :label="method">
|
||||
{{ method }}
|
||||
</ElCheckbox>
|
||||
</ElCheckboxGroup>
|
||||
<ElSwitch v-else-if="isBooleanConfig" v-model="editBoolean" />
|
||||
<ElInputNumber
|
||||
v-else-if="isIntegerConfig"
|
||||
v-model="editNumber"
|
||||
@@ -101,11 +106,20 @@
|
||||
import { ElMessage, ElTag } from 'element-plus'
|
||||
import { SystemConfigService } from '@/api/modules'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import type { SystemConfigItem, SystemConfigModule } from '@/types/api/systemConfig'
|
||||
import {
|
||||
PAYMENT_CONFIG_KEYS,
|
||||
parsePaymentMethods,
|
||||
type AllowedPaymentMethod,
|
||||
type SystemConfigItem,
|
||||
type SystemConfigModule
|
||||
} from '@/types/api/systemConfig'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { JULY_PERMISSIONS } from '@/config/constants'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
|
||||
defineOptions({ name: 'SystemConfigs' })
|
||||
const { hasAuth } = useAuth()
|
||||
|
||||
const moduleOptions = [
|
||||
{ label: '运营商回调配置', value: 'carrier_callback' },
|
||||
@@ -133,6 +147,8 @@
|
||||
const originalValue = ref('')
|
||||
const editBoolean = ref(false)
|
||||
const editNumber = ref<number | null>(null)
|
||||
const editPaymentMethods = ref<AllowedPaymentMethod[]>([])
|
||||
const paymentMethods: AllowedPaymentMethod[] = ['wallet', 'wechat', 'alipay']
|
||||
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
||||
|
||||
const columnOptions = [
|
||||
@@ -198,6 +214,11 @@
|
||||
const isBooleanConfig = computed(
|
||||
() => currentConfig.value?.value_type === 'bool' || currentConfig.value?.control === 'switch'
|
||||
)
|
||||
const isPaymentConfig = computed(() =>
|
||||
[PAYMENT_CONFIG_KEYS.card, PAYMENT_CONFIG_KEYS.device].includes(
|
||||
currentConfig.value?.config_key as (typeof PAYMENT_CONFIG_KEYS)[keyof typeof PAYMENT_CONFIG_KEYS]
|
||||
)
|
||||
)
|
||||
const isIntegerConfig = computed(
|
||||
() => !isBooleanConfig.value && currentConfig.value?.value_type === 'int'
|
||||
)
|
||||
@@ -222,6 +243,7 @@
|
||||
})
|
||||
|
||||
const getSubmittedValue = () => {
|
||||
if (isPaymentConfig.value) return JSON.stringify(editPaymentMethods.value)
|
||||
if (isBooleanConfig.value) return String(editBoolean.value)
|
||||
if (isIntegerConfig.value) return editNumber.value === null ? '' : String(editNumber.value)
|
||||
return editValue.value
|
||||
@@ -246,12 +268,17 @@
|
||||
return '请输入有效的 JSON'
|
||||
}
|
||||
}
|
||||
if (isPaymentConfig.value) {
|
||||
if (editPaymentMethods.value.length === 0) return '至少保留一种支付方式'
|
||||
}
|
||||
if (value === '') return '配置值不能为空'
|
||||
return ''
|
||||
}
|
||||
|
||||
const getActions = (row: SystemConfigItem) =>
|
||||
row.readonly
|
||||
row.readonly ||
|
||||
([PAYMENT_CONFIG_KEYS.card, PAYMENT_CONFIG_KEYS.device].includes(row.config_key as never) &&
|
||||
!hasAuth(JULY_PERMISSIONS.systemConfig.payment))
|
||||
? []
|
||||
: [{ label: '编辑', handler: () => showEditDialog(row), type: 'primary' as const }]
|
||||
|
||||
@@ -280,6 +307,7 @@
|
||||
editValue.value = row.value
|
||||
editBoolean.value = row.value === 'true'
|
||||
editNumber.value = /^-?\d+$/.test(row.value) ? Number(row.value) : null
|
||||
editPaymentMethods.value = parsePaymentMethods(row.value)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
|
||||
253
src/views/settings/wecom/applications/detail.vue
Normal file
253
src/views/settings/wecom/applications/detail.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div class="wecom-application-detail-page">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">{{ isEdit ? '编辑企微应用' : '新增企微应用' }}</div>
|
||||
<div class="page-description"
|
||||
>应用凭据由平台管理员维护,保存后可在应用列表测试连接。</div
|
||||
>
|
||||
</div>
|
||||
<ElButton @click="goBack">返回应用列表</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElForm
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="140px"
|
||||
class="application-form"
|
||||
>
|
||||
<ElRow :gutter="24">
|
||||
<ElCol :xs="24" :sm="12">
|
||||
<ElFormItem label="企业 ID" prop="corp_id">
|
||||
<ElInput v-model="form.corp_id" placeholder="请输入企业微信 CorpID" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12">
|
||||
<ElFormItem label="AgentID" prop="agent_id">
|
||||
<ElInputNumber
|
||||
v-model="form.agent_id"
|
||||
:min="1"
|
||||
controls-position="right"
|
||||
class="full-width"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12">
|
||||
<ElFormItem label="应用名称" prop="name">
|
||||
<ElInput v-model="form.name" placeholder="请输入应用名称" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12">
|
||||
<ElFormItem label="Secret" prop="secret">
|
||||
<ElInput
|
||||
v-model="form.secret"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="请输入应用 Secret"
|
||||
/>
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElDivider content-position="left">回调配置</ElDivider>
|
||||
<ElRow :gutter="24">
|
||||
<ElCol :xs="24" :sm="12">
|
||||
<ElFormItem label="回调 Token" prop="callback_token">
|
||||
<ElInput v-model="form.callback_token" placeholder="请输入回调 Token" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
<ElCol :xs="24" :sm="12">
|
||||
<ElFormItem label="EncodingAESKey" prop="encoding_aes_key">
|
||||
<ElInput v-model="form.encoding_aes_key" placeholder="请输入 43 位 EncodingAESKey" />
|
||||
</ElFormItem>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
|
||||
<ElFormItem label="启用状态">
|
||||
<ElSwitch v-model="form.enabled" active-text="启用" inactive-text="禁用" />
|
||||
</ElFormItem>
|
||||
|
||||
<div class="form-actions">
|
||||
<ElButton @click="goBack">取消</ElButton>
|
||||
<ElButton
|
||||
v-if="isEdit"
|
||||
v-permission="JULY_PERMISSIONS.wecom.application"
|
||||
:loading="testing"
|
||||
@click="testApplication"
|
||||
>
|
||||
测试连接
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.application"
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
@click="saveApplication"
|
||||
>
|
||||
保存应用
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { WecomService } from '@/api/modules'
|
||||
import { JULY_PERMISSIONS } from '@/config/constants'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import type { WecomApplication } from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'WecomApplicationDetail' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const formRef = ref<FormInstance>()
|
||||
const saving = ref(false)
|
||||
const testing = ref(false)
|
||||
const applicationId = computed(() => Number(route.params.id) || 0)
|
||||
const isEdit = computed(() => applicationId.value > 0)
|
||||
const form = reactive({
|
||||
corp_id: '',
|
||||
agent_id: 0,
|
||||
name: '',
|
||||
secret: '',
|
||||
callback_token: '',
|
||||
encoding_aes_key: '',
|
||||
enabled: true
|
||||
})
|
||||
|
||||
const rules = reactive<FormRules>({
|
||||
corp_id: [{ required: true, message: '请输入企业 ID', trigger: 'blur' }],
|
||||
agent_id: [{ required: true, message: '请输入 AgentID', trigger: 'change' }],
|
||||
name: [{ required: true, message: '请输入应用名称', trigger: 'blur' }],
|
||||
secret: [{ required: true, message: '请输入 Secret', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
const fillForm = (application?: WecomApplication) => {
|
||||
Object.assign(
|
||||
form,
|
||||
application
|
||||
? {
|
||||
corp_id: application.corp_id,
|
||||
agent_id: application.agent_id,
|
||||
name: application.name,
|
||||
secret: application.secret || '',
|
||||
callback_token: application.callback_token || '',
|
||||
encoding_aes_key: application.encoding_aes_key || '',
|
||||
enabled: application.status === 1
|
||||
}
|
||||
: {
|
||||
corp_id: '',
|
||||
agent_id: 0,
|
||||
name: '',
|
||||
secret: '',
|
||||
callback_token: '',
|
||||
encoding_aes_key: '',
|
||||
enabled: true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const loadApplication = async () => {
|
||||
if (!isEdit.value) {
|
||||
fillForm()
|
||||
return
|
||||
}
|
||||
const response = await WecomService.getApplications({ page: 1, page_size: 100 })
|
||||
if (response.code === 0) {
|
||||
const application = response.data.items?.find((item) => item.id === applicationId.value)
|
||||
if (application) fillForm(application)
|
||||
else {
|
||||
ElMessage.error('未找到对应的企微应用')
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const saveApplication = async () => {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
saving.value = true
|
||||
try {
|
||||
const response = await WecomService.saveApplication({
|
||||
corp_id: form.corp_id,
|
||||
agent_id: form.agent_id,
|
||||
name: form.name,
|
||||
secret: form.secret,
|
||||
callback_token: form.callback_token,
|
||||
encoding_aes_key: form.encoding_aes_key,
|
||||
status: form.enabled ? 1 : 0
|
||||
})
|
||||
if (response.code === 0) {
|
||||
ElMessage.success('企微应用保存成功')
|
||||
await router.push(RoutesAlias.WecomApplications)
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const testApplication = async () => {
|
||||
if (!applicationId.value) return
|
||||
testing.value = true
|
||||
try {
|
||||
const response = await WecomService.testApplication(applicationId.value)
|
||||
if (response.code === 0) {
|
||||
if (response.data.success) ElMessage.success('连接成功,已取得 access_token')
|
||||
else ElMessage.warning('连接失败,请检查应用凭据')
|
||||
}
|
||||
} finally {
|
||||
testing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => void router.push(RoutesAlias.WecomApplications)
|
||||
|
||||
onMounted(() => void loadApplication())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wecom-application-detail-page {
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: 6px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.application-form {
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
171
src/views/settings/wecom/applications/index.vue
Normal file
171
src/views/settings/wecom/applications/index.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div class="wecom-applications-page">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">企微应用管理</div>
|
||||
<div class="page-description">管理企业微信应用凭据,并查看连接和默认发起人状态。</div>
|
||||
</div>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.application"
|
||||
type="primary"
|
||||
@click="goToCreate"
|
||||
>
|
||||
新增应用
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElTable v-loading="loading" :data="applications" border>
|
||||
<ElTableColumn prop="name" label="应用名称" min-width="160" show-overflow-tooltip />
|
||||
<ElTableColumn label="凭据状态" width="110">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.credentials_set ? 'success' : 'warning'" size="small">
|
||||
{{ scope.row.credentials_set ? '已完整' : '未完整' }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="启用状态" width="100">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ scope.row.status_name }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="默认发起人" min-width="150">
|
||||
<template #default="scope">{{ scope.row.default_creator_name || '未设置' }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="最近连接" min-width="170">
|
||||
<template #default="scope">{{ formatDate(scope.row.last_connected_at) }}</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn label="操作" width="300" fixed="right">
|
||||
<template #default="scope">
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.application"
|
||||
link
|
||||
type="primary"
|
||||
@click="testApplication(scope.row.id)"
|
||||
>
|
||||
测试连接
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.member"
|
||||
link
|
||||
type="primary"
|
||||
@click="goToMembers(scope.row.id)"
|
||||
>
|
||||
成员管理
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.application"
|
||||
link
|
||||
@click="goToDetail(scope.row.id)"
|
||||
>
|
||||
编辑配置
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<ElPagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="loadApplications"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { WecomService } from '@/api/modules'
|
||||
import { JULY_PERMISSIONS } from '@/config/constants'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import type { WecomApplication } from '@/types/api'
|
||||
import { formatDateTime } from '@/utils/business/format'
|
||||
|
||||
defineOptions({ name: 'WecomApplications' })
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const applications = ref<WecomApplication[]>([])
|
||||
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
||||
|
||||
const formatDate = (value?: string | null) => (value ? formatDateTime(value) : '-')
|
||||
|
||||
const loadApplications = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await WecomService.getApplications({
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size
|
||||
})
|
||||
if (response.code === 0) {
|
||||
applications.value = response.data.items || []
|
||||
pagination.total = response.data.total || 0
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.page_size = size
|
||||
pagination.page = 1
|
||||
void loadApplications()
|
||||
}
|
||||
|
||||
const goToCreate = () => void router.push(`${RoutesAlias.WecomApplications}/create`)
|
||||
const goToDetail = (id: number) => void router.push(`${RoutesAlias.WecomApplicationDetail}/${id}`)
|
||||
const goToMembers = (id: number) =>
|
||||
void router.push({ path: RoutesAlias.WecomMembers, query: { application_id: String(id) } })
|
||||
|
||||
const testApplication = async (id: number) => {
|
||||
const response = await WecomService.testApplication(id)
|
||||
if (response.code === 0) {
|
||||
if (response.data.success) ElMessage.success('连接成功,已取得 access_token')
|
||||
else ElMessage.warning('连接失败,请检查应用凭据')
|
||||
await loadApplications()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void loadApplications())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wecom-applications-page {
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: 6px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
424
src/views/settings/wecom/members/index.vue
Normal file
424
src/views/settings/wecom/members/index.vue
Normal file
@@ -0,0 +1,424 @@
|
||||
<template>
|
||||
<div class="wecom-members-page">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">企微成员管理</div>
|
||||
<div class="page-description"
|
||||
>同步应用可见成员,设置默认审批发起人,并完成平台账号绑定。</div
|
||||
>
|
||||
</div>
|
||||
<ElButton @click="goToApplications">返回应用列表</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElForm inline class="search-form" @submit.prevent>
|
||||
<ElFormItem label="企微应用">
|
||||
<ElSelect
|
||||
v-model="selectedApplicationId"
|
||||
filterable
|
||||
class="application-select"
|
||||
placeholder="请选择企微应用"
|
||||
@change="handleApplicationChange"
|
||||
>
|
||||
<ElOption
|
||||
v-for="application in applications"
|
||||
:key="application.id"
|
||||
:label="application.name"
|
||||
:value="application.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="成员搜索">
|
||||
<ElInput
|
||||
v-model="keyword"
|
||||
clearable
|
||||
placeholder="姓名"
|
||||
class="keyword-input"
|
||||
@keyup.enter="handleSearch"
|
||||
/>
|
||||
</ElFormItem>
|
||||
<ElFormItem>
|
||||
<ElButton type="primary" @click="handleSearch">查询</ElButton>
|
||||
<ElButton @click="handleReset">重置</ElButton>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
|
||||
<div class="toolbar">
|
||||
<div class="selected-application">
|
||||
当前应用:<span>{{ selectedApplication?.name || '未选择' }}</span>
|
||||
<ElTag v-if="selectedApplication?.default_creator_name" type="success" size="small">
|
||||
默认发起人:{{ selectedApplication.default_creator_name }}
|
||||
</ElTag>
|
||||
</div>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.member"
|
||||
:disabled="!selectedApplicationId"
|
||||
:loading="syncing"
|
||||
@click="syncMembers"
|
||||
>
|
||||
同步成员
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<ElTable
|
||||
v-loading="loading"
|
||||
:data="members"
|
||||
row-key="userid"
|
||||
highlight-current-row
|
||||
border
|
||||
@row-click="selectMember"
|
||||
>
|
||||
<ElTableColumn label="选择" width="70" align="center">
|
||||
<template #default="scope">
|
||||
<ElRadio v-model="selectedUserid" :label="scope.row.userid">
|
||||
<span class="sr-only">选择 {{ scope.row.name }}</span>
|
||||
</ElRadio>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="name" label="成员姓名" min-width="150" />
|
||||
<ElTableColumn prop="synced_at" label="同步时间" min-width="180" />
|
||||
<ElTableColumn label="操作" width="230" fixed="right">
|
||||
<template #default="scope">
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.member"
|
||||
link
|
||||
type="primary"
|
||||
@click.stop="setDefaultCreator(scope.row)"
|
||||
>
|
||||
设为默认发起人
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.binding"
|
||||
link
|
||||
@click.stop="selectMember(scope.row)"
|
||||
>
|
||||
绑定账号
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
|
||||
<div class="pagination-wrapper">
|
||||
<ElPagination
|
||||
v-model:current-page="pagination.page"
|
||||
v-model:page-size="pagination.page_size"
|
||||
:total="pagination.total"
|
||||
:page-sizes="[20, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="loadMembers"
|
||||
/>
|
||||
</div>
|
||||
</ElCard>
|
||||
|
||||
<ElCard shadow="never" class="operation-card">
|
||||
<template #header>当前成员操作</template>
|
||||
<ElAlert
|
||||
v-if="selectedMember"
|
||||
:title="`已选择:${selectedMember.name}`"
|
||||
type="info"
|
||||
:closable="false"
|
||||
/>
|
||||
<ElEmpty v-else description="请先在上方列表选择成员" :image-size="70" />
|
||||
|
||||
<div class="operation-row">
|
||||
<span class="operation-label">绑定平台账号</span>
|
||||
<ElInputNumber
|
||||
v-model="bindingAccountId"
|
||||
:min="1"
|
||||
:disabled="!selectedMember"
|
||||
controls-position="right"
|
||||
placeholder="账号 ID"
|
||||
/>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.binding"
|
||||
type="primary"
|
||||
:disabled="!selectedMember || !bindingAccountId"
|
||||
:loading="binding"
|
||||
@click="bindAccount"
|
||||
>
|
||||
绑定账号
|
||||
</ElButton>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.member"
|
||||
:disabled="!selectedMember"
|
||||
:loading="savingDefault"
|
||||
@click="saveSelectedDefaultCreator"
|
||||
>
|
||||
保存为默认发起人
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { AccountService, WecomService } from '@/api/modules'
|
||||
import { JULY_PERMISSIONS } from '@/config/constants'
|
||||
import { RoutesAlias } from '@/router/routesAlias'
|
||||
import type { WecomApplication, WecomMember } from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'WecomMembers' })
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const syncing = ref(false)
|
||||
const savingDefault = ref(false)
|
||||
const binding = ref(false)
|
||||
const applications = ref<WecomApplication[]>([])
|
||||
const members = ref<WecomMember[]>([])
|
||||
const selectedApplicationId = ref<number>()
|
||||
const selectedUserid = ref('')
|
||||
const bindingAccountId = ref<number>()
|
||||
const keyword = ref('')
|
||||
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
||||
|
||||
const selectedApplication = computed(() =>
|
||||
applications.value.find((application) => application.id === selectedApplicationId.value)
|
||||
)
|
||||
const selectedMember = computed(() =>
|
||||
members.value.find((member) => member.userid === selectedUserid.value)
|
||||
)
|
||||
|
||||
const loadApplications = async () => {
|
||||
const response = await WecomService.getApplications({ page: 1, page_size: 100 })
|
||||
if (response.code !== 0) return
|
||||
applications.value = response.data.items || []
|
||||
const queryApplicationId = Number(route.query.application_id)
|
||||
selectedApplicationId.value =
|
||||
applications.value.find((application) => application.id === queryApplicationId)?.id ||
|
||||
applications.value[0]?.id
|
||||
}
|
||||
|
||||
const loadMembers = async () => {
|
||||
if (!selectedApplicationId.value) {
|
||||
members.value = []
|
||||
pagination.total = 0
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await WecomService.getMembers(selectedApplicationId.value, {
|
||||
page: pagination.page,
|
||||
page_size: pagination.page_size,
|
||||
keyword: keyword.value.trim() || undefined
|
||||
})
|
||||
if (response.code === 0) {
|
||||
members.value = response.data.items || []
|
||||
pagination.total = response.data.total || 0
|
||||
if (!members.value.some((member) => member.userid === selectedUserid.value)) {
|
||||
selectedUserid.value = ''
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleApplicationChange = () => {
|
||||
pagination.page = 1
|
||||
selectedUserid.value = ''
|
||||
void loadMembers()
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
pagination.page = 1
|
||||
void loadMembers()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
keyword.value = ''
|
||||
pagination.page = 1
|
||||
void loadMembers()
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pagination.page_size = size
|
||||
pagination.page = 1
|
||||
void loadMembers()
|
||||
}
|
||||
|
||||
const selectMember = (member: WecomMember) => {
|
||||
selectedUserid.value = member.userid
|
||||
}
|
||||
|
||||
const syncMembers = async () => {
|
||||
if (!selectedApplicationId.value) return
|
||||
syncing.value = true
|
||||
try {
|
||||
const response = await WecomService.syncMembers(selectedApplicationId.value)
|
||||
if (response.code === 0) {
|
||||
ElMessage.success(`已同步 ${response.data.synced_count} 名成员`)
|
||||
await loadMembers()
|
||||
}
|
||||
} finally {
|
||||
syncing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const setDefaultCreator = async (member: WecomMember) => {
|
||||
if (!selectedApplicationId.value) return
|
||||
selectedUserid.value = member.userid
|
||||
await saveSelectedDefaultCreator()
|
||||
}
|
||||
|
||||
const saveSelectedDefaultCreator = async () => {
|
||||
if (!selectedApplicationId.value || !selectedMember.value) {
|
||||
ElMessage.warning('请选择要设置的成员')
|
||||
return
|
||||
}
|
||||
savingDefault.value = true
|
||||
try {
|
||||
const response = await WecomService.setDefaultCreator(
|
||||
selectedApplicationId.value,
|
||||
selectedMember.value.userid
|
||||
)
|
||||
if (response.code === 0) {
|
||||
ElMessage.success('默认发起人已保存')
|
||||
const application = selectedApplication.value
|
||||
if (application) {
|
||||
application.default_creator_userid = selectedMember.value.userid
|
||||
application.default_creator_name = selectedMember.value.name
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
savingDefault.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const bindAccount = async () => {
|
||||
if (!selectedApplicationId.value || !selectedMember.value || !bindingAccountId.value) {
|
||||
ElMessage.warning('请选择成员并输入账号 ID')
|
||||
return
|
||||
}
|
||||
binding.value = true
|
||||
try {
|
||||
const response = await AccountService.bindWecom(bindingAccountId.value, {
|
||||
application_id: selectedApplicationId.value,
|
||||
userid: selectedMember.value.userid
|
||||
})
|
||||
if (response.code === 0) ElMessage.success('账号企微绑定成功')
|
||||
} finally {
|
||||
binding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goToApplications = () => void router.push(RoutesAlias.WecomApplications)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadApplications()
|
||||
await loadMembers()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wecom-members-page {
|
||||
.page-header,
|
||||
.toolbar,
|
||||
.operation-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.toolbar {
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: 6px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
margin-top: 4px;
|
||||
padding: 14px 16px 0;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.application-select {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.keyword-input {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
margin: 20px 0 12px;
|
||||
|
||||
.selected-application {
|
||||
color: var(--el-text-color-secondary);
|
||||
|
||||
span {
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.operation-card {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.operation-row {
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.operation-label {
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header,
|
||||
.toolbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.application-select,
|
||||
.keyword-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
375
src/views/settings/wecom/scenes/index.vue
Normal file
375
src/views/settings/wecom/scenes/index.vue
Normal file
@@ -0,0 +1,375 @@
|
||||
<template>
|
||||
<div class="wecom-scenes-page">
|
||||
<ElCard shadow="never">
|
||||
<template #header>
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">企微审批场景</div>
|
||||
<div class="page-description"
|
||||
>分别维护退款和线下代充值审批模板,并提交后由后端校验控件映射。</div
|
||||
>
|
||||
</div>
|
||||
<ElButton v-permission="JULY_PERMISSIONS.wecom.scene" type="primary" @click="createScene">
|
||||
新增场景配置
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="scene-layout">
|
||||
<div class="scene-list">
|
||||
<div class="section-heading">场景配置列表</div>
|
||||
<ElTable
|
||||
v-loading="loading"
|
||||
:data="scenes"
|
||||
row-key="business_type"
|
||||
highlight-current-row
|
||||
border
|
||||
@row-click="selectScene"
|
||||
>
|
||||
<ElTableColumn prop="business_type_name" label="业务类型" min-width="150" />
|
||||
<ElTableColumn
|
||||
prop="template_name"
|
||||
label="模板名称"
|
||||
min-width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<ElTableColumn label="状态" width="90">
|
||||
<template #default="scope">
|
||||
<ElTag :type="scope.row.status === 1 ? 'success' : 'info'" size="small">
|
||||
{{ scope.row.status_name }}
|
||||
</ElTag>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn prop="last_verified_at" label="最近校验" min-width="170" />
|
||||
<ElTableColumn label="操作" width="90" fixed="right">
|
||||
<template #default="scope">
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.scene"
|
||||
link
|
||||
type="primary"
|
||||
@click.stop="selectScene(scope.row)"
|
||||
>
|
||||
编辑
|
||||
</ElButton>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
</ElTable>
|
||||
</div>
|
||||
|
||||
<ElCard shadow="never" class="editor-card">
|
||||
<template #header>
|
||||
<div class="editor-header">
|
||||
<span>{{ editing ? '编辑审批场景' : '新增审批场景' }}</span>
|
||||
<ElTag v-if="editing" size="small" type="info">{{ sceneForm.business_type }}</ElTag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<ElForm :model="sceneForm" label-width="110px">
|
||||
<ElFormItem label="业务类型" required>
|
||||
<ElSelect v-model="sceneForm.business_type" :disabled="editing" class="full-width">
|
||||
<ElOption label="退款审批" value="refund_approval" />
|
||||
<ElOption label="线下代充值审批" value="offline_recharge_approval" />
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="企微应用">
|
||||
<ElSelect v-model="sceneForm.application_id" clearable filterable class="full-width">
|
||||
<ElOption
|
||||
v-for="application in applications"
|
||||
:key="application.id"
|
||||
:label="application.name"
|
||||
:value="application.id"
|
||||
/>
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="模板 ID" required>
|
||||
<ElInput v-model="sceneForm.template_id" placeholder="请输入企微后台模板 ID" />
|
||||
</ElFormItem>
|
||||
<ElFormItem label="启用状态">
|
||||
<ElSwitch v-model="sceneForm.enabled" active-text="启用" inactive-text="禁用" />
|
||||
</ElFormItem>
|
||||
|
||||
<ElDivider content-position="left">控件映射</ElDivider>
|
||||
<div class="mapping-heading">
|
||||
<span>业务字段与模板控件的对应关系</span>
|
||||
<ElButton text type="primary" @click="addMapping()">新增映射</ElButton>
|
||||
</div>
|
||||
<div v-if="mappingRows.length" class="mapping-list">
|
||||
<div v-for="(mapping, index) in mappingRows" :key="mapping.key" class="mapping-row">
|
||||
<ElInput v-model="mapping.business_field" placeholder="业务字段,如 refund_no" />
|
||||
<ElInput v-model="mapping.control_id" placeholder="控件 ID" />
|
||||
<ElInput v-model="mapping.control_type" placeholder="控件类型,如 Text" />
|
||||
<ElInput
|
||||
v-model="mapping.option_mapping"
|
||||
placeholder="选择项映射 JSON,可为空对象"
|
||||
/>
|
||||
<ElButton text type="danger" @click="removeMapping(index)">删除</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
<ElEmpty v-else description="暂无控件映射,请新增一条" :image-size="60" />
|
||||
|
||||
<div class="editor-actions">
|
||||
<ElButton @click="createScene">清空表单</ElButton>
|
||||
<ElButton
|
||||
v-permission="JULY_PERMISSIONS.wecom.scene"
|
||||
type="primary"
|
||||
:loading="saving"
|
||||
@click="saveScene"
|
||||
>
|
||||
保存并校验
|
||||
</ElButton>
|
||||
</div>
|
||||
</ElForm>
|
||||
</ElCard>
|
||||
</div>
|
||||
</ElCard>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { WecomService } from '@/api/modules'
|
||||
import { JULY_PERMISSIONS } from '@/config/constants'
|
||||
import type {
|
||||
WecomApplication,
|
||||
WecomBusinessType,
|
||||
WecomScene,
|
||||
WecomSceneControlMapping
|
||||
} from '@/types/api'
|
||||
|
||||
defineOptions({ name: 'WecomScenes' })
|
||||
|
||||
interface MappingEditor {
|
||||
key: number
|
||||
business_field: string
|
||||
control_id: string
|
||||
control_type: string
|
||||
option_mapping: string
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const applications = ref<WecomApplication[]>([])
|
||||
const scenes = ref<WecomScene[]>([])
|
||||
const selectedBusinessType = ref<WecomBusinessType>()
|
||||
const nextMappingKey = ref(1)
|
||||
const sceneForm = reactive({
|
||||
business_type: 'refund_approval' as WecomBusinessType,
|
||||
application_id: undefined as number | undefined,
|
||||
template_id: '',
|
||||
enabled: true
|
||||
})
|
||||
const mappingRows = ref<MappingEditor[]>([])
|
||||
const editing = computed(() => Boolean(selectedBusinessType.value))
|
||||
|
||||
const addMapping = (mapping?: Partial<MappingEditor>) => {
|
||||
mappingRows.value.push({
|
||||
key: nextMappingKey.value++,
|
||||
business_field: mapping?.business_field || '',
|
||||
control_id: mapping?.control_id || '',
|
||||
control_type: mapping?.control_type || '',
|
||||
option_mapping: mapping?.option_mapping || '{}'
|
||||
})
|
||||
}
|
||||
|
||||
const removeMapping = (index: number) => mappingRows.value.splice(index, 1)
|
||||
|
||||
const fillEditor = (scene?: WecomScene) => {
|
||||
if (!scene) {
|
||||
selectedBusinessType.value = undefined
|
||||
Object.assign(sceneForm, {
|
||||
business_type: 'refund_approval' as WecomBusinessType,
|
||||
application_id: applications.value[0]?.id,
|
||||
template_id: '',
|
||||
enabled: true
|
||||
})
|
||||
mappingRows.value = []
|
||||
addMapping()
|
||||
return
|
||||
}
|
||||
selectedBusinessType.value = scene.business_type
|
||||
Object.assign(sceneForm, {
|
||||
business_type: scene.business_type,
|
||||
application_id: scene.application_id,
|
||||
template_id: scene.template_id,
|
||||
enabled: scene.status === 1
|
||||
})
|
||||
mappingRows.value = (scene.control_mapping || []).map((mapping) => ({
|
||||
key: nextMappingKey.value++,
|
||||
business_field: mapping.business_field,
|
||||
control_id: mapping.control_id,
|
||||
control_type: mapping.control_type,
|
||||
option_mapping: JSON.stringify(mapping.option_mapping || {})
|
||||
}))
|
||||
}
|
||||
|
||||
const selectScene = (scene: WecomScene) => fillEditor(scene)
|
||||
const createScene = () => fillEditor()
|
||||
|
||||
const loadData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const [applicationResponse, sceneResponse] = await Promise.all([
|
||||
WecomService.getApplications({ page: 1, page_size: 100 }),
|
||||
WecomService.getScenes({ page: 1, page_size: 100 })
|
||||
])
|
||||
if (applicationResponse.code === 0) applications.value = applicationResponse.data.items || []
|
||||
if (sceneResponse.code === 0) {
|
||||
scenes.value = sceneResponse.data.items || []
|
||||
const current = scenes.value.find(
|
||||
(scene) => scene.business_type === selectedBusinessType.value
|
||||
)
|
||||
fillEditor(current || scenes.value[0])
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const parseMapping = (): WecomSceneControlMapping[] | undefined => {
|
||||
const result: WecomSceneControlMapping[] = []
|
||||
for (const mapping of mappingRows.value) {
|
||||
if (!mapping.business_field && !mapping.control_id && !mapping.control_type) continue
|
||||
if (!mapping.business_field || !mapping.control_id || !mapping.control_type) {
|
||||
ElMessage.warning('请完整填写控件映射字段')
|
||||
return
|
||||
}
|
||||
let optionMapping: Record<string, string>
|
||||
try {
|
||||
const parsed = JSON.parse(mapping.option_mapping || '{}')
|
||||
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object')
|
||||
throw new Error('invalid')
|
||||
optionMapping = parsed as Record<string, string>
|
||||
} catch {
|
||||
ElMessage.warning(`第 ${result.length + 1} 条选择项映射不是有效 JSON`)
|
||||
return
|
||||
}
|
||||
result.push({
|
||||
business_field: mapping.business_field,
|
||||
control_id: mapping.control_id,
|
||||
control_type: mapping.control_type,
|
||||
option_mapping: optionMapping
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const saveScene = async () => {
|
||||
if (!sceneForm.template_id.trim()) {
|
||||
ElMessage.warning('请输入模板 ID')
|
||||
return
|
||||
}
|
||||
const controlMapping = parseMapping()
|
||||
if (!controlMapping) return
|
||||
saving.value = true
|
||||
try {
|
||||
const response = await WecomService.saveScene(sceneForm.business_type, {
|
||||
application_id: sceneForm.application_id,
|
||||
template_id: sceneForm.template_id.trim(),
|
||||
control_mapping: controlMapping,
|
||||
status: sceneForm.enabled ? 1 : 0
|
||||
})
|
||||
if (response.code === 0) {
|
||||
ElMessage.success('场景保存并校验成功')
|
||||
await loadData()
|
||||
}
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => void loadData())
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.wecom-scenes-page {
|
||||
.page-header,
|
||||
.editor-header,
|
||||
.mapping-heading,
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.page-header,
|
||||
.editor-header,
|
||||
.mapping-heading {
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: var(--el-text-color-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-description {
|
||||
margin-top: 6px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.scene-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(480px, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.editor-card {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mapping-heading {
|
||||
margin-bottom: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.mapping-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mapping-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1.05fr 1fr 0.8fr 1.3fr auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.scene-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.page-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mapping-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user