| src/pages/page-pilot/pilot-home.vue | ●●●●● patch | view | raw | blame | history | |
| src/pages/page-pilot/pilot-index.vue | ●●●●● patch | view | raw | blame | history | |
| src/pages/page-pilot/pilot-liveshare.vue | ●●●●● patch | view | raw | blame | history | |
| src/pages/page-pilot/pilot-media.vue | ●●●●● patch | view | raw | blame | history |
src/pages/page-pilot/pilot-home.vue
@@ -19,49 +19,46 @@ </div> <a-drawer placement="right" v-model:visible="drawerVisible" width="340px"> <div class="mb10 flex-row flex-justify-center flex-align-center"> <p class="fz14" style="font-weight: 100;">Module State</p> <p class="fz14" style="font-weight: 100;">模块状态</p> </div> <div class= "width-100 mb10 flex-align-start" v-for="m in modules" :key="m.name" style="height: 30px;"> <div class="ml5" style="float: left; color: #000000;">{{m.name}}:</div> <div class="width-100 mb10 flex-align-start" v-for="m in modules" :key="m.name" style="height: 30px;"> <div class="ml5" style="float: left; color: #000000;">{{ m.name }}:</div> <div class="ml10" style="float: right; margin-bottom: 8px;"> <span :key="m.state" :class="m.state.value === EStatusValue.CONNECTED ? 'green' : 'red'">{{ m.state.value }} </span> <a-button-group > <a-button class="ml5" type="primary" size="small" @click.stop="moduleInstall(m)">install</a-button> <a-button class="ml5 mr5" type="danger" size="small" @click.stop="moduleUninstall(m)">uninstall</a-button> <a-button class="ml5" type="primary" size="small" @click.stop="moduleInstall(m)">安装</a-button> <a-button class="ml5 mr5" type="danger" size="small" @click.stop="moduleUninstall(m)">卸载</a-button> </a-button-group> </div> <a-divider /> </div> </a-drawer> </a-layout-content> </a-layout> <a-divider style="height: 2px; background-color: #f5f5f5; margin-top: 3vh;" /> <a-button id="exitBtn" class="fz18" @click="confirmAgain" style="width: 10vw; height: 10vh; position: fixed; bottom: 13vh; left: 15vw; background-color: #e6e6e6; color: red; border: 0;" type="primary">Exit type="primary">退出 </a-button> <a-modal v-model:visible="exitVisible" width="300px" :closable="false"> <template #footer> <a-button type="text" style="width: 48%; float: left;" @click="onBack">Cancel</a-button> <a-button type="text" style="width: 48%;" @click="onExit">Exit</a-button> <a-button type="text" style="width: 48%; float: left;" @click="onBack">取消</a-button> <a-button type="text" style="width: 48%;" @click="onExit">退出</a-button> </template> <p>Data will not be synchronized between DJI Pilot and this server after exiting.</p> <p>退出后,DJI Pilot 与服务器之间将不会同步数据。</p> </a-modal> </div> </a-layout-sider> <a-layout-content class="right flex-column"> <div class="mb5"> <span class="ml5" style="color: #939393;">Serial Number</span> <span class="ml5" style="color: #939393;">序列号</span> </div> <div class="fz16" style="background-color: white; border-radius: 4px;"> <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle"> <a-col :span="1"></a-col> <a-col :span="9"> Remote Control Sn 遥控器序列号 </a-col> <a-col :span="13" class="flex-align-end flex-column"> <span style="color: #737373">{{ device.data.gateway_sn }}</span> @@ -69,24 +66,24 @@ </a-row> <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" v-if="device.data.online_status && device.data.sn"> <a-col :span="1"></a-col> <a-col :span="9">Aircraft Sn</a-col> <a-col :span="9">飞行器序列号</a-col> <a-col :span="13" class="flex-align-end flex-column" > <span style="color: #737373">{{ device.data.sn }}</span> </a-col> </a-row> </div> <div class="mt5 mb5"> <span class="ml5" style="color: #939393;">Settings</span> <span class="ml5" style="color: #939393;">设置</span> </div> <div class="fz16" style="background-color: white; border-radius: 4px;"> <a-row v-if="device.data.online_status && device.data.sn" style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="bindingDevice"> <a-col :span="1"></a-col> <a-col :span="11"> Device Binding 设备绑定 </a-col> <a-col :span="10" style="text-align: right"> <span v-if="device.data.bound_status" style="color: #737373">Aircraft bound</span> <span v-else style="color: #737373">Aircraft not bound</span> <span v-if="device.data.bound_status" style="color: #737373">飞行器已绑定</span> <span v-else style="color: #737373">飞行器未绑定</span> </a-col> <a-col :span="2" class="flex-align-center flex-column" > <RightOutlined style="color: #8894a0; font-size: 20px;" /> @@ -95,7 +92,7 @@ <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onMediaSetting"> <a-col :span="1"></a-col> <a-col :span="21"> Media File Upload 媒体文件上传 </a-col> <a-col :span="2" class="flex-align-center flex-column" > <RightOutlined style="color: #8894a0; font-size: 20px;" /> @@ -103,14 +100,14 @@ </a-row> <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onLiveshareSetting"> <a-col :span="1"></a-col> <a-col :span="21">Livestream Manually</a-col> <a-col :span="21">手动直播</a-col> <a-col :span="2" class="flex-align-center flex-column"> <RightOutlined style="color: #8894a0; font-size: 20px;" /> </a-col> </a-row> <a-row style="border-bottom: 1px solid #f4f8f9; height: 45px;" align="middle" @click="onOpen3rdApp"> <a-col :span="1"></a-col> <a-col :span="21">Open 3rd Party APP</a-col> <a-col :span="21">打开第三方应用</a-col> <a-col :span="2" class="flex-align-center flex-column"> <RightOutlined style="color: #8894a0; font-size: 20px;" /> </a-col> @@ -119,6 +116,7 @@ </a-layout-content> </a-layout> </template> <script lang="ts" setup> import { message, Popconfirm } from 'ant-design-vue' import { onMounted, onUnmounted, reactive, ref, watch } from 'vue' @@ -175,35 +173,35 @@ } const modules = [{ name: 'Cloud', name: '上云模块', state: thingState, module: EComponentName.Thing }, { name: 'Api', name: 'Api模块', state: apiState, module: EComponentName.Api }, { name: 'Live', name: '直播模块', state: liveState, module: EComponentName.Liveshare }, { name: 'Ws', name: 'Ws模块', state: wsState, module: EComponentName.Ws }, { name: 'Map', name: '地图模块', state: mapState, module: EComponentName.Map }, { name: 'Tsa', name: '态势感知', state: tsaState, module: EComponentName.Tsa }, { name: 'Media', name: '媒体文件', state: mediaState, module: EComponentName.Media }, { name: 'Wayline', name: '航线模块', state: waylineState, module: EComponentName.Mission }] src/pages/page-pilot/pilot-index.vue
@@ -55,13 +55,35 @@ import { UserOutlined, LockOutlined } from '@ant-design/icons-vue' import djiLogo from '/@/assets/icons/dji_logo.png' import { useMyStore } from '/@/store' // const root = getRoot() const root = getRoot() const store = useMyStore() // const formState: UnwrapRef<LoginBody> = reactive({ // username: 'pilot', // password: 'pilot123', // flag: EUserType.Pilot, // }) const formState: UnwrapRef<LoginBody> = reactive({ // 租户ID tenantId: '000000', // 部门ID deptId: '', // 角色ID roleId: '', // 用户名 username: '', // 密码 password: '', // 账号类型 type: 'account', // 验证码的值 code: '', // 验证码的索引 key: '', // 预加载白色背景 image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', flag: EUserType.Web, }) const isVerified = ref<boolean>(false) onMounted(async () => { verifyLicense() @@ -75,6 +97,7 @@ if (token) { await refreshToken({}) .then(res => { console.log('e' + CURRENT_CONFIG.baseURL) apiPilot.setComponentParam(EComponentName.Api, { host: CURRENT_CONFIG.baseURL, token: res.data.access_token @@ -93,38 +116,63 @@ }) } }) const root = getRoot() const onSubmit = async (e: any) => { try { // 调用登录方法 const loginResponse = await store.dispatch('LoginByUsername', formState) const store = useMyStore() if (loginResponse) { // 调用 refreshToken 方法获取 access_token const refreshTokenResponse = await refreshToken({}) console.log(refreshTokenResponse) if (refreshTokenResponse && refreshTokenResponse.data.access_token) { const accessToken = refreshTokenResponse.data.access_token const formState: UnwrapRef<LoginBody> = reactive({ // 租户ID tenantId: '000000', // 部门ID deptId: '', // 角色ID roleId: '', // 用户名 username: 'pilot', // 密码 password: 'pilot123', // 账号类型 type: 'account', // 验证码的值 code: '', // 验证码的索引 key: '', // 预加载白色背景 image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', flag: EUserType.Pilot, }) // const onSubmit = async (e: any) => { console.log('Current Base URL:', CURRENT_CONFIG.baseURL) console.log('Current Base URL:', accessToken) // 配置 API 组件参数 apiPilot.setComponentParam(EComponentName.Api, { host: CURRENT_CONFIG.baseURL, token: accessToken, }) // 加载 API 组件 const jsres = apiPilot.loadComponent( EComponentName.Api, apiPilot.getComponentParam(EComponentName.Api) ) if (!jsres) { message.error('加载 API 模块失败') return } // 设置 token apiPilot.setToken(accessToken) // 存储 token 到 localStorage localStorage.setItem(ELocalStorageKey.Token, accessToken) // 跳转到首页 root.$router.push(ERouterName.PILOT_HOME) } else { throw new Error('Failed to retrieve access token from refreshToken method') } } } catch (err) { // 错误处理 console.error(err) message.error(err.message || 'An error occurred during login') } } // const onSubmit = async () => { // await login(formState) // .then(res => { // if (!isVerified.value) { // message.error('请先核实许可证.') // return // } // if (!isVerified.value) { // message.error('请先核实许可证.') // return // } // console.log('login res:', res) // if (res.code === 0) { // apiPilot.setComponentParam(EComponentName.Api, { @@ -135,46 +183,26 @@ // EComponentName.Api, // apiPilot.getComponentParam(EComponentName.Api) // ) // console.log('load api module res:', jsres) // apiPilot.setToken(res.data.access_token) // localStorage.setItem(ELocalStorageKey.Token, res.data.access_token) // localStorage.setItem(ELocalStorageKey.WorkspaceId, res.data.workspace_id) // localStorage.setItem(ELocalStorageKey.UserId, res.data.user_id) // localStorage.setItem(ELocalStorageKey.Username, res.data.username) // localStorage.setItem(ELocalStorageKey.Flag, EUserType.Pilot.toString()) // message.success('登录成功') // root.$router.push(ERouterName.PILOT_HOME) // } // }) // .catch(err => { // message.error(err) // }) // store // .dispatch('LoginByUsername', formState) // .then((res) => { // root.$router.push(ERouterName.PILOT_HOME) // }) // .catch(() => { // }) // const password = encrypt(formState.password) // const result = await login(formState) // if (result.code === 0) { // localStorage.setItem(ELocalStorageKey.Token, result.data.access_token) // localStorage.setItem(ELocalStorageKey.WorkspaceId, result.data.workspace_id) // localStorage.setItem(ELocalStorageKey.Username, result.data.username) // localStorage.setItem(ELocalStorageKey.UserId, result.data.user_id) // localStorage.setItem(ELocalStorageKey.Flag, EUserType.Web.toString()) // root.$router.push(ERouterName.PROJECT_LIST) // } else { // message.error(result.message) // } const onSubmit = async () => { if (!isVerified.value) { message.error('请先核实许可证.') return } store .dispatch('LoginByUsername', formState) .then((res) => { root.$router.push(ERouterName.PILOT_HOME) }) .catch(() => { }) // const password = encrypt(formState.password) // const result = await login(formState) // if (result.code === 0) { // localStorage.setItem(ELocalStorageKey.Token, result.data.access_token) // localStorage.setItem(ELocalStorageKey.WorkspaceId, result.data.workspace_id) // localStorage.setItem(ELocalStorageKey.Username, result.data.username) // localStorage.setItem(ELocalStorageKey.UserId, result.data.user_id) // localStorage.setItem(ELocalStorageKey.Flag, EUserType.Web.toString()) // root.$router.push(ERouterName.PROJECT_LIST) // } else { // message.error(result.message) // } } // } function verifyLicense () { isVerified.value = apiPilot.platformVerifyLicense(CURRENT_CONFIG.appId, CURRENT_CONFIG.appKey, CURRENT_CONFIG.appLicense) && apiPilot.isPlatformVerifySuccess() src/pages/page-pilot/pilot-liveshare.vue
@@ -1,18 +1,16 @@ <template> <div class="width100 flex-column flex-justify-start flex-align-start" style="background-color: white;"> <div class="width100 flex-column flex-justify-start flex-align-start" style="background-color: white;"> <!-- 页面顶部提示信息 --> <p class="fz16 ml10 mt15 mb10 color-text-title color-font-bold" style="color: #939393"> 在手动开始前,请选择发布模式和直播类型 </p> <p class="fz16 ml10 mt15 mb10 color-text-title color-font-bold" style="color: #939393"> Before starting manually, please select the publish mode and livestream type </p> <div class="mt15 flex-row flex-align-center flex-justify-between" style="width: 100%;"> <p class="ml10 mb0 fz16" style="color: black"> Select Video Publish Mode: </p> <!-- 选择视频发布模式 --> <div class="mt15 flex-row flex-align-center flex-justify-between" style="width: 100%;"> <p class="ml10 mb0 fz16" style="color: black">选择视频发布模式:</p> <a-select style="width: 200px; margin-right: 20px;" placeholder="Select Mode" placeholder="选择模式" @select="onPublishModeSelect" > <a-select-option @@ -25,17 +23,17 @@ </a-select> </div> <!-- 分割线 --> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <div class="flex-row flex-align-center flex-justify-between" style="width: 100%; margin-top: -10px;" > <p class="ml10 mb0 fz16">Select Livestream Type:</p> <!-- 选择直播类型 --> <div class="flex-row flex-align-center flex-justify-between" style="width: 100%; margin-top: -10px;"> <p class="ml10 mb0 fz16">选择直播类型:</p> <a-select style="width: 200px; margin-right: 20px;" placeholder="Select Live Type" placeholder="选择直播类型" :value="liveStreamStatus.type" @select="onLiveTypeSelect" > @@ -48,62 +46,75 @@ </a-select-option> </a-select> </div> <!-- 分割线 --> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <!-- 参数显示 --> <div class="width-100" style="margin-top: -10px;"> <div class="ml10" style="width: 97%;"> <span class="fz16">Param: </span> <span class="fz16">参数: </span> <!-- 根据不同的直播类型显示不同的参数 --> <span v-if="liveStreamStatus.type === ELiveTypeValue.Agora" style="word-break: break-all; color: #75c5f6;">{{ agoraParam }}</span> <span v-else-if="liveStreamStatus.type === ELiveTypeValue.RTMP" style="word-break: break-all; color: #75c5f6;">{{ rtmpParam }}</span> <span v-else-if="liveStreamStatus.type === ELiveTypeValue.RTSP" style="word-break: break-all; color: #75c5f6;">{{ rtspParam }}</span> <span v-else-if="liveStreamStatus.type === ELiveTypeValue.GB28181" style="word-break: break-all; color: #75c5f6;">{{ gb28181Param }}</span> <span v-else></span> </div> </div> <!-- 分割线 --> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <div class="mb20 flex-row flex-align-center flex-justify-center" style="width: 100%; "> <a-button class="flex-column fz20 flex-align-center flex-justify-center" style="width: 100px;" type="ghost" @click="onPlay">Play</a-button> <a-button class="flex-column fz20 flex-align-center flex-justify-center ml40" style="width: 100px;" type="ghost" @click="onStop">Stop</a-button> <!-- 播放和停止按钮 --> <div class="mb20 flex-row flex-align-center flex-justify-center" style="width: 100%;"> <a-button class="flex-column fz20 flex-align-center flex-justify-center" style="width: 100px;" type="ghost" @click="onPlay">播放</a-button> <a-button class="flex-column fz20 flex-align-center flex-justify-center ml40" style="width: 100px;" type="ghost" @click="onStop">停止</a-button> </div> <!-- 固定位置的状态展示按钮 --> <a-button v-if="playVisiable" class="flex-column flex-align-center" shape="circle" @click="showLivingStatus" style="position: fixed; top: 13vh; left: 5vw; opacity: 0.8; background-color: rgb(0,0,0,0)"> <template #icon><CaretRightFilled style="font-size: 26px; color: " /></template> </a-button> <a-drawer placement="right" v-model:visible="drawerVisible" width="280px" :mask="false" @close="closeDrawer"> <!-- 直播状态信息抽屉 --> <a-drawer placement="right" v-model:visible="drawerVisible" width="280px" :mask="false" @close="closeDrawer"> <div class="fz16 width-100"> <!-- 直播状态 --> <div class="mt20" style=" margin-bottom: -10px;"> <span class="fz20 flex-row flex-align-center flex-justify-center"> <font :color="liveState === EStatusValue.LIVING ? 'green' : liveState === EStatusValue.CONNECTED ? 'blue' : 'red'">{{ liveState }}</font></span> </div> <a-divider /> <!-- 直播详细信息 --> <div style=" margin-top: -10px; margin-bottom: -15px;"> <span>Frame Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.fps }}<span v-if="liveStreamStatus.fps != -1"> fps</span></span><br/> <span>帧率:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.fps }}<span v-if="liveStreamStatus.fps != -1"> fps</span></span><br/> </div> <a-divider /> <div style=" margin-top: -10px; margin-bottom: -10px;"> <span>Video Bit Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.videoBitRate }}<span v-if="liveStreamStatus.videoBitRate != -1"> kbps</span></span><br/> <span>视频比特率:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.videoBitRate }}<span v-if="liveStreamStatus.videoBitRate != -1"> kbps</span></span><br/> </div> <a-divider /> <div style=" margin-top: -10px; margin-bottom: -10px;"> <span>Audio Bit Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.audioBitRate }}<span v-if="liveStreamStatus.audioBitRate != -1"> kbps</span></span><br/> <span>音频比特率:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.audioBitRate }}<span v-if="liveStreamStatus.audioBitRate != -1"> kbps</span></span><br/> </div> <a-divider /> <div style=" margin-top: -10px; margin-bottom: -10px;"> <span>Packet Loss Rate:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.dropRate }}<span v-if="liveStreamStatus.dropRate != -1"> %</span></span><br/> <span>丢包率:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.dropRate }}<span v-if="liveStreamStatus.dropRate != -1"> %</span></span><br/> </div> <a-divider /> <div style=" margin-top: -10px; margin-bottom: -10px;"> <span>RTT:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.rtt }}<span v-if="liveStreamStatus.rtt != -1"> ms</span></span><br/> <span>RTT:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.rtt }}<span v-if="liveStreamStatus.rtt != -1"> ms</span></span><br/> </div> <a-divider /> <div style=" margin-top: -10px;"> <span >Jitter:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.jitter }}</span><br/> <span >抖动:</span><span style="float: right; color: #75c5f6;">{{ liveStreamStatus.jitter }}</span><br/> </div> </div> </a-drawer> src/pages/page-pilot/pilot-media.vue
@@ -1,69 +1,69 @@ <template> <a-layout> <div class="width100 flex-column flex-justify-start flex-align-start" style="background-color: white;"> <div class="width100 flex-column flex-justify-start flex-align-start" style="background-color: white;"> <p class="fz16 ml10 mt15 mb10 color-text-title color-font-bold" style="color: #939393"> When enabled, photos and videos will be automatically uploaded to this server </p> <div class="flex-row flex-align-center mt20" style="width: 100%;" > <p class="ml10 mb0 fz16" style="margin-right: 73vw;">Auto Photo Upload</p> <a-switch v-model:checked="enablePhotoUpload" @change="onPhotoUpload" ></a-switch> </div> <div class="flex-row flex-align-center flex-justify-between" style="width: 100%" > <a-radio-group class="mt10 ml20" v-if="enablePhotoUpload === true" v-model:value="photoType" defaultChecked="0" @change="onPhototype" > <a-radio :value="EPhotoType.Original">Original Photo</a-radio> <a-radio class="ml20" :value="EPhotoType.Preview">Preview Photo</a-radio> </a-radio-group> </div> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <div class="flex-row flex-align-center" style="width: 100%; margin-top: -10px;" > <p class="ml10 mb0 fz16" style="margin-right: 73vw;">Auto Video Upload</p> <a-switch @change="onVideoUpload" v-model:checked="enableVideoUpload" ></a-switch> </div> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <div class="flex-row flex-align-center flex-justify-between mb15" style="width: 100%; margin-top: -10px;" > <p class="ml10 mb0 fz16 color-font-bold"> Path for uploading media resources in dual-controller mode <p class="fz16 ml10 mt15 mb10 color-text-title color-font-bold" style="color: #939393"> 启用后,照片和视频将自动上传到此服务器 </p> <a-radio-group class="mt0 mb0" v-model:value="uploadPath" button-style="solid" @change="onUploadPath" <div class="flex-row flex-align-center mt20" style="width: 100%;" > <a-radio-button :value="EDownloadOwner.Mine">Mine</a-radio-button> <a-radio-button :value="EDownloadOwner.Others">Another</a-radio-button> </a-radio-group> <p class="ml10 mb0 fz16" style="margin-right: 73vw;">自动上传照片</p> <a-switch v-model:checked="enablePhotoUpload" @change="onPhotoUpload" ></a-switch> </div> <div class="flex-row flex-align-center flex-justify-between" style="width: 100%" > <a-radio-group class="mt10 ml20" v-if="enablePhotoUpload === true" v-model:value="photoType" defaultChecked="0" @change="onPhototype" > <a-radio :value="EPhotoType.Original">原始照片</a-radio> <a-radio class="ml20" :value="EPhotoType.Preview">预览照片</a-radio> </a-radio-group> </div> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <div class="flex-row flex-align-center" style="width: 100%; margin-top: -10px;" > <p class="ml10 mb0 fz16" style="margin-right: 73vw;">自动上传视频</p> <a-switch @change="onVideoUpload" v-model:checked="enableVideoUpload" ></a-switch> </div> <div class="ml10 mr10" style="width: 96%; margin-top: -10px;"> <a-divider /> </div> <div class="flex-row flex-align-center flex-justify-between mb15" style="width: 100%; margin-top: -10px;" > <p class="ml10 mb0 fz16 color-font-bold"> 双控模式下媒体资源上传路径 </p> <a-radio-group class="mt0 mb0" v-model:value="uploadPath" button-style="solid" @change="onUploadPath" > <a-radio-button :value="EDownloadOwner.Mine">我的</a-radio-button> <a-radio-button :value="EDownloadOwner.Others">其他</a-radio-button> </a-radio-group> </div> </div> </div> </a-layout> </template>