| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591 |
- <script lang="ts" setup>
- import { addQuestionReportData,REPORTDATA, getQuestionTypeList, pageQuestionReportData,getPerson } from '@/api/questionReqort'
- import { AddQuestionReportDataReq, PageQuestionReportDataReq, QuestionListRes, QuestionType, QuestionTypeListRes } from '@/api/questionReqort/types'
- import type { ApiResponse, PageResp } from '@/api/types'
- import { getCachedWecomVisitorProfile, getWecomInternalUser } from '@/api/wecom'
- import { computed, onMounted, ref, watch } from 'vue'
- import GridAddress from '@/components/address/gridAddress/index.vue'
- import { showNotify } from 'vant'
- import dayjs from 'dayjs';
- import { onLoad } from '@dcloudio/uni-app'
- import useUserState from '@/store/userState'
- const active = ref(0)
- const userState = useUserState()
- interface ReportFormData extends AddQuestionReportDataReq {
- location?: string
- contactPhone?: string
- }
- interface GridPerson {
- areaName: string
- areaCode: string
- }
- interface AddressOption {
- areaName: string
- areaCode: string
- }
- interface AddressSelection {
- value: string
- tabIndex: number
- selectedOptions: AddressOption[]
- }
- const createInitialFormData = (): ReportFormData => ({
- chargerName: '',
- chargerCode: '',
- contactPerson: '',
- contactPersonPhone: '',
- contactPhone: '',
- questionContent: '',
- questionType: '',
- addrName: '',
- addrDetailName: '',
- })
- const formData = ref<ReportFormData>(createInitialFormData())
- const range = ref<{ text: string; value: string }[]>([])
- const rangePerson = ref([]) as any
- const rules = ref({
- questionType: [{ required: true, message: '请选择类型' }],
- // questionTitle: [{ required: true, message: '请输入标题' }],
- contactPerson: [{ required: true, message: '请输入联系人' }],
- contactPhone: [{ required: true, message: '请输入联系人电话' }],
- questionContent: [{ required: true, message: '请输入内容' }],
- addrName: [{ required: true, message: '请选择所在地' }],
- })
- /** 上报问题 */
- const personData = ref<GridPerson[]>([])
- const addressList = ref<AddressSelection | null>(null)
- const handleAddressFinish = async(value: AddressSelection) => {
- formData.value.chargerName = ''
- formData.value.chargerCode = ''
- formData.value.addrName = value.selectedOptions[value.tabIndex]?.areaName || ''
- addressList.value = value
- const { data } = await getPerson({parentCode:value.value})
- personData.value = (data || []) as GridPerson[]
- rangePerson.value = personData.value.map((item) => ({
- text: item.areaName,
- value: item.areaName,
- }))
- }
- /* 获取群主手机号 */
- const ownerId = ref(''); // 群主id
- onLoad((option) => {
- if (option && option.owner) {
- ownerId.value = option.owner
- }
- })
- const ownerPhone = ref('')
- const visitorPhone = ref('')
- const fillVisitorName = () => {
- const profile = getCachedWecomVisitorProfile()
- if (!profile?.name) return
- visitorPhone.value = profile.mobile || ''
- formData.value.contactPerson = profile.name
- }
- const fillVisitorPhone = () => {
- const profile = getCachedWecomVisitorProfile()
- const mobile = (profile?.mobile || visitorPhone.value || '').trim()
- if (!mobile) {
- showNotify({ type: 'warning', message: '暂未获取到您的手机号,请手动填写' })
- return
- }
- visitorPhone.value = mobile
- formData.value.contactPhone = mobile
- formData.value.contactPersonPhone = mobile
- }
- const getOwnerPhone = async () => {
- if (!ownerId.value) return
- const res = await getWecomInternalUser(ownerId.value)
- ownerPhone.value = res.mobile || ''
- }
- const submit = async () => {
- console.log(`output->formData.value`,formData.value)
- if (!formData.value.questionType) {
- showNotify('请选择问题类型')
- return
- }
- if (!formData.value.questionContent) {
- showNotify('请输入内容')
- return
- }
- if (!formData.value.contactPerson) {
- showNotify('请输入联系人姓名')
- return
- }
- if (!formData.value.contactPhone) {
- showNotify('请输入联系人电话')
- return
- }
- if (!addressList.value || addressList.value.selectedOptions.length === 0) {
- showNotify('请选择所在地')
- return
- }
- formData.value.chargerCode = personData.value.find(
- item => item.areaName === formData.value.chargerName
- )?.areaCode || '' // 添加兜底处理
- formData.value.contactPersonPhone = formData.value.contactPhone || ''
- console.log('formData.value',formData.value)
-
- /* 获取群主手机号(不阻塞流程,报错也继续执行) */
- try {
- await getOwnerPhone();
- } catch (e) {
- console.error('获取群主手机号失败:', e);
- }
- const { data } = await addQuestionReportData({
- ...formData.value,
- ...(ownerPhone.value ? { ownerPhone: ownerPhone.value } : {}),
- // personList:formData.value.person1+','+formData.value.person2,
- streetCode: addressList.value.selectedOptions[1] ? addressList.value.selectedOptions[1].areaCode : '',
- streetName: addressList.value.selectedOptions[1] ? addressList.value.selectedOptions[1].areaName : '',
- communityCode: addressList.value.selectedOptions[2] ? addressList.value.selectedOptions[2].areaCode : '',
- communityName: addressList.value.selectedOptions[2] ? addressList.value.selectedOptions[2].areaName : '',
- grid: addressList.value.selectedOptions[3] ? addressList.value.selectedOptions[3].areaCode : '',
- gridName: addressList.value.selectedOptions[3] ? addressList.value.selectedOptions[3].areaName : '',
- })
- if (data) {
- uni.showToast({
- title: '上报成功',
- icon: 'success',
- duration: 1000,
- })
- formData.value = createInitialFormData()
- visitorPhone.value = ''
- addressList.value = null
- data.districtCode = '330205'
- data.districtName = '江北区'
- // reportCY(data)
- }
- }
- // 上报到城运
- const reportCY = async (params:any) => {
- const { data } = await REPORTDATA({
- data:{
- otherTaskNum:params.uuid.replace(/-/g,''),
- // eventTitle:params.questionTitle,
- eventDesc:params.questionContent,
- eventTypeCode:'109000',
- eventTypeName:'民事纠纷',
- subTypeCode:'109002',
- subTypeName:'邻里纠纷',
- reportTime: dayjs(new Date()).format('YYYY-MM-DD HH:mm:ss'),
- reporter:params.contactPerson,
- address:params.districtName+params.streetName+params.communityName,
-
- districtCode:'330205',
- districtName:'江北区',
- gridCode:params.grid,
- gridName:params.gridName,
- communityCode:params.communityCode,
- communityName:params.communityName,
- streetCode:params.streetCode,
- streetName:params.streetName,
- },
- dataId:params.uuid.replace(/-/g,''),
- dataType:'main',
- timestamp:new Date().getTime(),
- })
- console.log(data)
- if (data.code== '200'){
- }
- }
- /**
- * 已上报问题
- */
- const list = ref<QuestionListRes[]>([])
- const pageInfo = ref({
- pageNumber: 1,
- pageSize: 10,
- })
- const loading = ref(false)
- const finished = ref(false)
- const listInitialized = ref(false)
- const listRequesting = ref(false)
- /** 列表重置代数:reset 后递增,用于丢弃 reset 之前发出的请求的响应,避免乱序写回 */
- const listLoadVersion = ref(0)
- /** 合并并发 init,避免 watch 与重复进入导致多次 reset/请求交错 */
- let listInitPromise: Promise<void> | null = null
- /** 列表接口调试:最近一次请求参数与完整响应 */
- const showListRawJsonPopup = ref(false)
- const showApiFullUrlPopup = ref(false)
- const listLastReqParams = ref<PageQuestionReportDataReq | null>(null)
- const listLastApiResponse = ref<ApiResponse<PageResp<QuestionListRes>> | null>(null)
- const listReqParamsJson = computed(() => {
- if (!listLastReqParams.value) return '{}'
- return JSON.stringify(listLastReqParams.value, null, 2)
- })
- const listApiRawJson = computed(() => {
- if (!listLastApiResponse.value) return '{}'
- return JSON.stringify(listLastApiResponse.value, null, 2)
- })
- const httpsOrigin = computed(() => {
- if (typeof window === 'undefined') return ''
- const host = window.location.host || ''
- return host ? `https://${host}` : ''
- })
- const listApiFullUrl = computed(() => `${httpsOrigin.value}/api/questionReport/pageQuestionReportData`)
- const detailApiFullUrl = computed(() => `${httpsOrigin.value}/api/questionReport/detailsQuestionReportData/{id}`)
- const currentPageUrl = computed(() => {
- if (typeof window === 'undefined') return ''
- return window.location.href || ''
- })
- const detailPageUrl = computed(() => `${httpsOrigin.value}/subPages/pages/reportProblems/detail?id={id}`)
- const resetQuestionList = () => {
- list.value = []
- pageInfo.value.pageNumber = 1
- finished.value = false
- loading.value = false
- listRequesting.value = false
- listLoadVersion.value += 1
- }
- const questList = async () => {
- if (listRequesting.value || finished.value) return
- const requestVersion = listLoadVersion.value
- listRequesting.value = true
- loading.value = true
- try {
- const currentPage = pageInfo.value.pageNumber
- const reqParams: PageQuestionReportDataReq = {
- pageNumber: currentPage,
- pageSize: pageInfo.value.pageSize,
- contactPerson: formData.value.contactPerson || '',
- }
- const res = await pageQuestionReportData(reqParams)
- if (requestVersion !== listLoadVersion.value) return
- listLastReqParams.value = reqParams
- listLastApiResponse.value = res
- const records = res.data.records || []
- list.value = currentPage === 1 ? records : list.value.concat(records)
- pageInfo.value.pageNumber = currentPage + 1
- finished.value = records.length < pageInfo.value.pageSize
- } finally {
- listRequesting.value = false
- loading.value = false
- }
- }
- const initQuestionList = (): Promise<void> => {
- if (listInitPromise) return listInitPromise
- listInitPromise = (async () => {
- resetQuestionList()
- await questList()
- listInitialized.value = true
- })().finally(() => {
- listInitPromise = null
- })
- return listInitPromise
- }
- /** 跳转问题详情 */
- const handleGoDetail = (item: QuestionListRes) => {
- uni.navigateTo({
- url: `/subPages/pages/reportProblems/detail?id=${item.uuid}&t=${Date.now()}`,
- })
- }
- const areaShow = ref(false)
- const hanleSelectArea = () => {
- areaShow.value = true
- }
- const changeQuestionType = (e: any) => {
- const { contactPerson, contactPhone, contactPersonPhone } = formData.value
- formData.value = createInitialFormData()
- formData.value.questionType = e
- formData.value.contactPerson = contactPerson
- formData.value.contactPhone = contactPhone
- formData.value.contactPersonPhone = contactPersonPhone
- addressList.value = null
- personData.value = []
- rangePerson.value = []
- areaShow.value = false
- }
- onMounted(async () => {
- fillVisitorName()
- // 问题类型下拉
- const { data } = await getQuestionTypeList({
- page: 1,
- limit: 9999,
- category: QuestionType.QUESTION,
- })
- if (data.records) {
- range.value = data.records.map((item) => {
- return {
- text: item.typeName,
- value: item.typeName,
- }
- })
- } else {
- range.value = []
- }
- })
- watch(active, async (value) => {
- if (value === 1 && !listInitialized.value) {
- await initQuestionList()
- }
- })
- </script>
- <template>
- <van-tabs v-model:active="active" type="card" class="top-tabs">
- <van-tab title="问题上报">
- <view class="tab-container">
- <uni-forms ref="questionReportFormRef" :modelValue="formData" :rules="rules" label-align="right" label-width="80">
- <uni-forms-item label="问题类型" required name="questionType">
- <uni-data-select v-model="formData.questionType" :localdata="range" @change="changeQuestionType"></uni-data-select>
- </uni-forms-item>
- <!-- 问题标题 -->
- <!-- <uni-forms-item label="问题标题" required name="questionTitle">
- <uni-easyinput v-model="formData.questionTitle" placeholder="请输入标题"></uni-easyinput>
- </uni-forms-item> -->
- <!-- 当事人 -->
- <!-- <uni-forms-item v-if="formData.questionType=='矛盾纠纷'" label="当事人1" required name="person1">
- <uni-easyinput v-model="formData.person1" placeholder="请输入当事人"></uni-easyinput>
- </uni-forms-item>
- <uni-forms-item v-if="formData.questionType=='矛盾纠纷'" label="当事人2" required name="person2">
- <uni-easyinput v-model="formData.person2" placeholder="请输入当事人"></uni-easyinput>
- </uni-forms-item> -->
- <!-- 问题内容 -->
- <uni-forms-item label="问题描述" required name="questionContent">
- <uni-easyinput v-model="formData.questionContent" :maxlength="-1" type="textarea" placeholder="请输入问题描述"></uni-easyinput>
- </uni-forms-item>
- <!-- 问题所在地 -->
- <uni-forms-item label="所在地" required name="addrName">
- <uni-easyinput v-model="formData.addrName" placeholder="请选择所在地" @focus="hanleSelectArea"></uni-easyinput>
- <GridAddress v-model="areaShow" @finish="handleAddressFinish"></GridAddress>
- </uni-forms-item>
- <!-- 详细地址-->
- <uni-forms-item label="详细位置" name="addrDetailName">
- <uni-easyinput v-model="formData.addrDetailName" placeholder="请输入"></uni-easyinput>
- </uni-forms-item>
- <!-- 负责人下拉 -->
- <!-- <uni-forms-item label="负责人" required name="person">
- <uni-data-select v-model="formData.chargerName" :localdata="rangePerson"></uni-data-select>
- </uni-forms-item> -->
- <!-- 联系人姓名 -->
- <uni-forms-item label="您的姓名" required name="contactPerson">
- <uni-easyinput v-model="formData.contactPerson" placeholder="请输入"></uni-easyinput>
- </uni-forms-item>
- <uni-forms-item label="手机号" required name="contactPhone">
- <view class="contact-phone-field">
- <uni-easyinput v-model="formData.contactPhone" placeholder="请输入"></uni-easyinput>
- <text class="contact-phone-field__action" @click="fillVisitorPhone">自动填写</text>
- </view>
- </uni-forms-item>
- </uni-forms>
- <van-button class="w-full mb-3" type="primary" @click="submit">提交</van-button>
- </view>
- </van-tab>
- <van-tab title="问题答复">
- <view class="tab-container">
- <view class="mb-[8px]">
- <van-button size="small" plain type="primary" @click="showListRawJsonPopup = true">查看接口原始JSON</van-button>
- <van-button class="ml-[8px]" size="small" plain type="primary" @click="showApiFullUrlPopup = true">查看完整访问地址</van-button>
- </view>
- <van-list
- v-model:loading="loading"
- :finished="finished"
- :immediate-check="false"
- finished-text="没有更多了"
- @load="questList"
- >
- <view v-for="item in list" :key="item.uuid" class="list-item-style">
- <view class="top">
- <view class="title">{{ item.questionContent }}</view>
- <van-button plain hairline size="mini" type="primary" @click="handleGoDetail(item)">查看详情</van-button>
- </view>
- <view>所在地:{{ item.addrName }}</view>
- <view>详细位置:{{ item.addrDetailName }}</view>
- <view class="flex gap-3">
- <view class="type">类型</view>
- <van-tag
- class="max-w-[100px] text-ellipsis overflow-hidden text-nowrap !block !leading-[25px]"
- color="#CFFECE"
- text-color="black"
- >{{ item.questionType }}</van-tag>
- <van-tag v-if="item?.status === 0" color="red">未解答</van-tag>
- <van-tag v-if="item?.status === 1" color="green">已解答</van-tag>
- </view>
- </view>
- </van-list>
- <van-popup
- v-model:show="showListRawJsonPopup"
- round
- position="bottom"
- :style="{ height: '75%' }"
- >
- <view class="json-popup">
- <view class="json-title">请求参数</view>
- <scroll-view scroll-y class="json-scroll json-scroll--small">
- <text class="json-text">{{ listReqParamsJson }}</text>
- </scroll-view>
- <view class="json-title">列表接口完整JSON</view>
- <scroll-view scroll-y class="json-scroll">
- <text class="json-text">{{ listApiRawJson }}</text>
- </scroll-view>
- </view>
- </van-popup>
- <van-popup
- v-model:show="showApiFullUrlPopup"
- round
- position="bottom"
- :style="{ height: '55%' }"
- >
- <view class="json-popup">
- <view class="json-title">当前HTTPS域名</view>
- <scroll-view scroll-y class="json-scroll json-scroll--small">
- <text class="json-text">{{ httpsOrigin || '当前环境未获取到浏览器域名' }}</text>
- </scroll-view>
- <view class="json-title">问题答复列表接口</view>
- <scroll-view scroll-y class="json-scroll json-scroll--small">
- <text class="json-text">{{ listApiFullUrl }}</text>
- </scroll-view>
- <view class="json-title">详情接口</view>
- <scroll-view scroll-y class="json-scroll">
- <text class="json-text">{{ detailApiFullUrl }}</text>
- </scroll-view>
- <view class="json-title">当前页面访问地址</view>
- <scroll-view scroll-y class="json-scroll json-scroll--small">
- <text class="json-text">{{ currentPageUrl || '当前环境未获取到页面地址' }}</text>
- </scroll-view>
- <view class="json-title">详情页访问地址</view>
- <scroll-view scroll-y class="json-scroll">
- <text class="json-text">{{ detailPageUrl }}</text>
- </scroll-view>
- </view>
- </van-popup>
- </view>
- </van-tab>
- </van-tabs>
- </template>
- <style lang="scss" scoped>
- .top-tabs {
- :deep(.van-tabs__nav) {
- margin: 0;
- }
- }
- .tab-container {
- padding: 20px;
- padding-bottom: 30px;
- min-height: calc(100vh - var(--van-tabs-card-height));
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- > uni-view:first-child {
- flex: 1;
- }
- }
- .contact-phone-field {
- display: flex;
- align-items: center;
- gap: 12px;
- :deep(.uni-easyinput) {
- flex: 1;
- }
- &__action {
- flex-shrink: 0;
- color: #1989fa;
- font-size: 14px;
- line-height: 20px;
- }
- }
- .list-item-style {
- padding: 20px;
- margin-bottom: 10px;
- background-color: white;
- > view {
- margin-bottom: 10px;
- }
- .top {
- display: flex;
- align-items: center;
- .title {
- font-size: 20px;
- font-weight: bold;
- flex: 1;
- word-break: break-word;
- display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 */
- -webkit-box-orient: vertical; /* 设置或检索伸缩盒对象的子元素的排列方式 */
- -webkit-line-clamp: 3; /* 限制在一个块元素显示的文本的行数 */
- overflow: hidden; /* 隐藏溢出的内容 */
- }
- .type {
- margin-right: 10px;
- }
- }
- }
- .json-popup {
- height: 100%;
- display: flex;
- flex-direction: column;
- padding: 12px;
- }
- .json-title {
- font-size: 16px;
- font-weight: 500;
- margin-bottom: 10px;
- }
- .json-scroll {
- flex: 1;
- overflow: hidden;
- background: #f7f8fa;
- border-radius: 8px;
- padding: 12px;
- }
- .json-scroll--small {
- flex: 0 0 auto;
- max-height: 120px;
- margin-bottom: 10px;
- }
- .json-text {
- white-space: pre-wrap;
- word-break: break-all;
- font-size: 13px;
- line-height: 20px;
- }
- </style>
|