<template>
  <div class="contradiction-div">
    <div class="search-div">
      <Row type="flex">
        <Col span="4">
          <span>姓名：</span>
          <Input v-model="filters.name" placeholder="请输入" style="width: 65%" />
        </Col>
        <Col span="4">
          <span>身份证号：</span>
          <Input v-model="filters.card_no" placeholder="请输入" style="width: 55%" />
        </Col>
        <Col span="4">
          <span>选择市：</span>
          <Select v-model="filters.city_id" clearable style="width: 55%" @on-change="changeArea('2', filters.city_id)">
            <Option v-for="c in cityList" :key="c.parent_id" :value="c.parent_id">{{ c.name }}</Option>
          </Select>
        </Col>
        <Col span="4">
          <span>选择区：</span>
          <Select v-model="filters.area_id" clearable style="width: 55%" @on-change="changeArea('3', filters.area_id)">
            <Option v-for="c in areaList" :key="c.parent_id" :value="c.parent_id">{{ c.name }}</Option>
          </Select>
        </Col>
        <Col span="4">
          <span>选择街道：</span>
          <Select v-model="filters.street_id" clearable style="width: 55%" @on-change="changeArea('4', filters.street_id)">
            <Option v-for="c in streetList" :key="c.parent_id" :value="c.parent_id">{{ c.name }}</Option>
          </Select>
        </Col>
        <Col span="4">
          <span>选择社区：</span>
          <Select v-model="filters.community_id" clearable style="width: 55%">
            <Option v-for="c in communityList" :key="c.parent_id" :value="c.parent_id">{{ c.name }}</Option>
          </Select>
        </Col>
      </Row>
      <Row type="flex" style="margin-top: 15px">
        <Col span="4">
          <span>办理状态：</span>
          <Select v-model="filters.do_status" style="width: 49%">
            <Option value="0">待办</Option>
            <Option value="1">已办</Option>
            <Option value="2">超期未办</Option>
          </Select>
        </Col>
        <Col span="4">
          <span>是否临期：</span>
          <Select v-model="filters.is_overdue" clearable style="width: 49%">
            <Option value="1">是</Option>
            <Option value="0">否</Option>
          </Select>
        </Col>
        <Col span="4">
          <span>风险等级：</span>
          <Select v-model="filters.rating_type" clearable style="width: 55%">
            <Option v-for="opt in ratingTypeOptions" :key="opt.value" :value="opt.value">{{ opt.label }}</Option>
          </Select>
        </Col>
        <Col style="text-align: right">
          <Button type="primary" @click="search" style="margin-right: 10px">搜索</Button>
          <Button type="primary" @click="reset">重置</Button>
          <Button style="margin-left: 10px;" @click="$router.go(-1)" v-if="this.$route.query.do_status">返回重点人员统计</Button>
        </Col>
      </Row>
    </div>

    <div class="pending-stats">
      <span v-if="false">正常待办：{{ statsPending.normal }}</span>
      <span>临期待办：{{ statsPending.nearDue }}</span>
    </div>

    <Table border :loading="tableLoading" :columns="tableColumns" :data="tableData">
        <template slot="action" slot-scope="{ row }">
          <Button v-if="canShowVisit(row) && String(row.do_status) === '0'" type="primary" size="small" style="margin-right:6px" @click="openVisitModal(row)">回访</Button>
          <Button v-if="hasHistory(row)" type="info" size="small" @click="openHistoryModal(row)">查看历史</Button>
        </template>
    </Table>
    <Page class="page_style" :total="params.totalRecord" :current="params.pageNo" :page-size="params.pageSize" show-total show-sizer @on-change="changePageNo" @on-page-size-change="size" />

    <!-- 回访弹窗（参照样式：基本信息 + 计划信息 + 历史回访信息 + 填写回访记录） -->
    <Modal v-model="visitModal.visible" title="回访办理" width="900">
      <div>
        <h4 class="section-title">人员基本信息</h4>
        <Form :label-width="100" inline class="info-form">
          <FormItem label="姓名">
            <Input :value="detail.name || '-'" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="性别">
            <Input :value="renderSex(detail.sex)" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="身份证号">
            <Input :value="detail.card_no || '-'" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="民族">
            <Input :value="renderFolk(detail.folk) || '-'" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="本人联系方式">
            <Input :value="detail.phone || '-'" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="现住址" style="width: 100%">
            <Input :value="`${detail.city || '-'} / ${detail.area || '-'} / ${detail.street || '-'} / ${detail.community || '-'}`" disabled />
          </FormItem>
        </Form>

        <h4 class="section-title">计划信息</h4>
        <Form :label-width="100" inline class="info-form">
          <FormItem label="计划名称">
            <Input :value="detail.plan_name || '-'" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="计划类别">
            <Input :value="detail.plan_type || '-'" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="重点人员类别">
            <Input :value="renderRatingDictType(currentCategory || filters.rating_dict_type)" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="风险等级">
            <Input :value="renderRatingType(detail.record_rating_type || detail.rating_type)" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="计划开始日期">
            <Input :value="formatDate(detail.plan_start_data)" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="计划结束日期">
            <Input :value="formatDate(detail.plan_end_data)" disabled style="width: 220px" />
          </FormItem>
          <FormItem label="剩余天数">
            <Input :value="renderRemainDays(detail.remain_days)" disabled style="width: 220px" />
          </FormItem>
        </Form>

        <h4 class="section-title">填写回访记录</h4>
        <Form :model="visitForm" ref="visitForm" :label-width="100" :rules="visitRules">
          <FormItem label="回访时间" prop="visit_time">
            <DatePicker type="datetime" format="yyyy-MM-dd HH:mm:ss" :value="visitForm.visit_time" @on-change="onVisitTimeChange" style="width: 220px" placeholder="选择时间" />
          </FormItem>
          <FormItem label="回访记录" prop="visit_record">
            <Input type="textarea" :rows="4" v-model="visitForm.visit_record" placeholder="请输入" />
          </FormItem>
          <FormItem label="回访图片">
            <Upload
              :show-upload-list="false"
              :before-upload="beforeUploadVisitImg"
              :format="['jpg','jpeg','png']"
              accept=".jpg,.jpeg,.png"
              :max-size="4096">
              <Button icon="ios-cloud-upload-outline">上传图片</Button>
            </Upload>
            <div class="img-list">
              <img v-for="(img,idx) in visitForm.imgs" :key="idx" :src="img" style="width:80px;height:80px;margin-right:8px" />
            </div>
          </FormItem>
        </Form>
      </div>
      <div slot="footer">
        <Button @click="visitModal.visible=false">取消</Button>
        <Poptip confirm title="确认保存？" placement="top" transfer @on-ok="submitVisit">
          <Button type="primary" :loading="visitModal.submitting">保存</Button>
        </Poptip>
      </div>
    </Modal>

    <VisitHistoryModal ref="historyModal"
      :rating_dict_type="String(currentCategory || filters.rating_dict_type || '')"
      :key_person_id="historyParams.key_person_id"
      :jl_key_person_visit_record_id="historyParams.current_record_id" />

  </div>
</template>

<script>
import { findVisitRecordList, getVisitRecordDetail, insertVisitRecord, getVisitButtonPower, getPendingStats } from '@/api/key-person'
import { getCardNoById, uploadFile, selectCityList } from '@/api/contradiction'
import { getDictList } from '@/api/common'
import { findMZList } from '@/api/peopleInfo'
import { mapState } from 'vuex'
import VisitHistoryModal from './components/VisitHistoryModal.vue'
export default {
  name: 'key-person-visit-record-index',
  props: { category: { type: String, default: '' } },
  data () {
    return {
      tableLoading: false,
      tableData: [],
      cardNoCache: {},
      cityList: [],
      areaList: [],
      streetList: [],
      communityList: [],
      ratingTypeOptions: [],
      mzList: [],
      nationMap: {},
      ratingDictTypeOptions: [
        { label: '刑满释放人员', value: '2' },
        { label: '严重精神障碍患者', value: '3' },
        { label: '重点未成年人', value: '6' }
      ],
      filters: {
        rating_dict_type: '',
        name: '',
        card_no: '',
        city_id: '',
        area_id: '',
        street_id: '',
        community_id: '',
        do_status: '0',
        rating_type: '',
        is_overdue: ''
      },
      params: { pageNo: 1, pageSize: 10, totalRecord: 0 },
      statsPending: { normal: 0, nearDue: 0 },
      tableColumns: [
        { type: 'index', title: '序号', align: 'center', width: 80 },
        { title: '市（州）', key: 'city_name', align: 'center', width: 100 },
        { title: '县（市、区）', key: 'area_name', align: 'center', width: 100 },
        { title: '街道（乡镇）', key: 'street_name', align: 'center', width: 120 },
        { title: '社区（村）', key: 'community_name', align: 'center', width: 120 },
        { title: '姓名', key: 'name', align: 'center', width: 100 },
        { title: '身份证号', key: 'card_no', align: 'center', width: 220, render: (h, { row }) => h('span', this.getDecryptedCardNo(row.key_person_id, row.card_no)) },
        { title: '联系方式', key: 'phone', align: 'center', width: 180 },
        { title: '计划开始日期', key: 'plan_start_data', align: 'center', width: 140, render: (h, { row }) => h('span', this.formatDate(row.plan_start_data)) },
        { title: '计划结束日期', key: 'plan_end_data', align: 'center', width: 140, render: (h, { row }) => h('span', this.formatDate(row.plan_end_data)) },
        { title: '剩余办理天数', key: 'remain_days', align: 'center', width: 120, render: (h, { row }) => h('span', this.renderRemainDays(row.remain_days)) },
        { title: '是否临期', key: 'is_overdue', align: 'center', width: 100, render: (h, { row }) => h('span', (String(row.is_overdue) === '1') ? '是' : '否') },
        { title: '风险等级', key: 'rating_type', align: 'center', width: 120, render: (h, { row }) => h('span', this.renderRatingType(row.rating_type)) },
        { title: '办理状态', key: 'do_status', align: 'center', width: 120, render: (h, { row }) => h('span', this.renderDoStatus(row.do_status)) },
        { title: '操作', slot: 'action', align: 'center', width: 200, fixed: 'right' }
      ],
      visitModal: { visible: false, submitting: false },
      historyModal: { visible: false },
      historyParams: { pageNo: 1, pageSize: 10, totalRecord: 0, key_person_id: '', current_record_id: '' },
      recordModal: { visible: false, data: {} },
      detail: {},
      visitButtonPower: {},
      visitForm: { key_person_id: '', key_person_visit_id: '', id: '', visit_time: '', visit_record: '', imgs: [] },
      visitRules: {
        visit_time: [{ required: true, message: '请选择回访时间', trigger: 'change' }],
        visit_record: [{ required: true, message: '请输入回访记录', trigger: 'blur' }]
      },
      historyData: [],
      visitHistoryColumns: []
    }
  },
  components: { VisitHistoryModal },
  computed: {
    ...mapState({ userInfo: state => state.user.userInfo || {} }),
    currentCategory () {
      const route = this.$route || {}
      const q = (route.query || {})
      const p = (route.params || {})
      const fromRoute = q.category || q.rating_dict_type || p.category || p.rating_dict_type || ''
      const fromProp = this.category || ''
      return String(fromProp || fromRoute || '')
    },
    recordModalImgs () {
      const data = this.recordModal && this.recordModal.data ? this.recordModal.data : {}
      const img = data.img
      if (!img) return []
      if (Array.isArray(img)) return img.filter(Boolean)
      if (typeof img === 'string') return img.split(',').map(s => s.trim()).filter(Boolean)
      return []
    }
  },
  created () {
    this.filters.rating_dict_type = this.currentCategory || ''
    this.loadRatingLevelDict()
    // 读取 URL 参数中的 do_status / is_overdue，并赋值到筛选项
    this.applyUrlFilterParams(false)
    this.fetchVisitButtonPower().then(() => this.init())
    this.loadNationDict()
    // 载入市级下拉
    selectCityList({}).then((res) => {
      if (res && res.data && res.data.errcode === 0) {
        this.cityList = res.data.data || []
      }
    })
  },
  watch: {
    category () { this.applyDefaultCategory(true) },
    '$route.query.category' () { this.applyDefaultCategory(true) },
    '$route.query.rating_dict_type' () { this.applyDefaultCategory(true) },
    // 监听 URL 参数变化，同步到筛选项并刷新
    '$route.query.do_status' () { this.applyUrlFilterParams(true) },
    '$route.query.is_overdue' () { this.applyUrlFilterParams(true) }
  },
  methods: {
    applyUrlFilterParams (triggerInit) {
      const q = (this.$route && this.$route.query) || {}
      const hasVal = (v) => v !== undefined && v !== null && String(v) !== ''
      let changed = false
      if (hasVal(q.do_status)) {
        this.filters.do_status = String(q.do_status)
        changed = true
      }
      if (hasVal(q.is_overdue)) {
        this.filters.is_overdue = String(q.is_overdue)
        changed = true
      }
      if (triggerInit && changed) this.changePageNo(1)
    },
    fetchVisitButtonPower () {
      const payload = {}
      return getVisitButtonPower(payload).then((ret) => {
        if (ret && ret.data && ret.data.errcode === 0) {
          this.visitButtonPower = ret.data.data || {}
        } else {
          this.visitButtonPower = {}
        }
      }).catch(() => { this.visitButtonPower = {} })
    },
    applyDefaultCategory (triggerInit) {
      const cat = this.currentCategory || ''
      this.filters.rating_dict_type = cat
      if (triggerInit) this.changePageNo(1)
    },
    loadRatingLevelDict () {
      getDictList({ type: 'rating_level' }).then((ret) => {
        if (ret.data && ret.data.errcode === 0) {
          const results = ret.data.data && ret.data.data.results ? ret.data.data.results : []
          this.ratingTypeOptions = results.map(it => ({ label: it.label || it.name, value: it.value || it.dictValue }))
        }
      })
    },
    async changeArea (level, id) {
      // 清空当前及以下级联的值与列表
      const chain = ['', 'city_id', 'area_id', 'street_id', 'community_id']
      const lists = ['', 'cityList', 'areaList', 'streetList', 'communityList']
      const lv = Number(level)
      if (lv > 1 && !id) {
        for (let i = lv; i < chain.length; i++) {
          this.$set(this.filters, chain[i], '')
          this[lists[i]] = []
        }
        return
      }
      if (lv === 2) {
        // 选择市 -> 加载区
        const ret = await selectCityList({ parent_id: id })
        const data = (ret && ret.data && ret.data.data) || []
        this.areaList = data
        // 级联清空下级
        this.filters.area_id = ''
        this.filters.street_id = ''
        this.filters.community_id = ''
        this.streetList = []
        this.communityList = []
      } else if (lv === 3) {
        // 选择区 -> 加载街道
        const ret = await selectCityList({ parent_id: id })
        const data = (ret && ret.data && ret.data.data) || []
        this.streetList = data
        this.filters.street_id = ''
        this.filters.community_id = ''
        this.communityList = []
      } else if (lv === 4) {
        // 选择街道 -> 加载社区
        const ret = await selectCityList({ parent_id: id })
        const data = (ret && ret.data && ret.data.data) || []
        this.communityList = data
        this.filters.community_id = ''
      }
    },
    loadNationDict () {
      findMZList().then((ret) => {
        if (ret && ret.data && ret.data.errcode === 0) {
          const list = ret.data.data || []
          this.mzList = list
          const map = {}
          list.forEach(it => {
            if (it && (it.mzcode || it.value)) {
              map[String(it.mzcode || it.value)] = it.mzname || it.label || it.name
            }
          })
          this.nationMap = map
        }
      })
    },
    init () {
      this.tableLoading = true
      const enforced = Object.assign({}, this.filters, { rating_dict_type: this.currentCategory || this.filters.rating_dict_type || '' })
      const query = Object.assign({ params: enforced }, this.params)
      findVisitRecordList(query).then((ret) => {
        if (ret.data.errcode === 0) {
          this.params.totalRecord = ret.data.data.totalRecord
          this.tableData = ret.data.data.results
          // 异步补全身份证号明文（按需加载）
          this.prefetchCardNos(this.tableData)
        } else {
          this.$Notice.error({ title: '查询失败！', desc: ret.data.errmsg })
        }
      }).finally(() => { this.tableLoading = false })
      // 同步刷新顶部统计：按当前筛选条件统计待办（正常/临期）数量
      this.fetchPendingStats(enforced)
    },
    async fetchPendingStats (baseFilters) {
      // 新接口一次返回 { normal, nearDue }
      const payload = Object.assign({}, baseFilters, { do_status: '0' })
      try {
        const ret = await getPendingStats(payload)
        if (ret && ret.data && ret.data.errcode === 0 && ret.data.data) {
          const d = ret.data.data
          this.statsPending.normal = Number(d.normal) || 0
          this.statsPending.nearDue = Number(d.nearDue) || 0
        } else {
          this.statsPending.normal = 0
          this.statsPending.nearDue = 0
        }
      } catch (e) {
        this.statsPending.normal = 0
        this.statsPending.nearDue = 0
      }
    },
    extractCountFromStatsResponse (ret) {
      if (!ret || !ret.data) return 0
      if (ret.data.errcode !== 0) return 0
      const d = ret.data.data
      if (d == null) return 0
      // 兼容多种后端返回结构
      const possible = [
        d.count,
        d.total,
        d.totalRecord,
        d.value,
        Array.isArray(d.results) ? d.results.length : undefined
      ]
      const num = possible.find(v => typeof v === 'number')
      if (typeof num === 'number') return num
      const strNum = possible.find(v => typeof v === 'string')
      return Number(strNum) || 0
    },
    async prefetchCardNos (rows) {
      // 批量拉取明文，接口是逐个查；缓存到本地 map，渲染器读取
      const ids = (rows || []).map(r => r.key_person_id).filter(Boolean)
      const uniq = Array.from(new Set(ids))
      for (const id of uniq) {
        if (this.cardNoCache[id]) continue
        try {
          const ret = await getCardNoById({ id })
          if (ret && ret.data && ret.data.errcode === 0) {
            const real = (ret.data.data && ret.data.data.cardno) || ''
            this.$set(this.cardNoCache, id, real)
          }
        } catch (e) {}
      }
    },
    getDecryptedCardNo (id, fallback) {
      const cache = this.cardNoCache || {}
      if (!id) return fallback || '-'
      return cache[id] || fallback || '-'
    },
    formatDate (val) {
      if (!val) return '-'
      const d = new Date(val)
      if (Number.isNaN(d.getTime())) {
        const m = /^\d{4}-\d{2}-\d{2}/.exec(String(val))
        return m ? m[0] : String(val)
      }
      const y = d.getFullYear()
      const mm = String(d.getMonth() + 1).padStart(2, '0')
      const dd = String(d.getDate()).padStart(2, '0')
      return `${y}-${mm}-${dd}`
    },
    formatDateTime (val) {
      if (!val) return '-'
      const d = new Date(val)
      if (Number.isNaN(d.getTime())) {
        const m = /^(\d{4}-\d{2}-\d{2})[ T]?(\d{2}:\d{2}:\d{2})?/.exec(String(val))
        if (m) {
          return `${m[1]}${m[2] ? ' ' + m[2] : ' 00:00:00'}`
        }
        return String(val)
      }
      const y = d.getFullYear()
      const mm = String(d.getMonth() + 1).padStart(2, '0')
      const dd = String(d.getDate()).padStart(2, '0')
      const HH = String(d.getHours()).padStart(2, '0')
      const MM = String(d.getMinutes()).padStart(2, '0')
      const SS = String(d.getSeconds()).padStart(2, '0')
      return `${y}-${mm}-${dd} ${HH}:${MM}:${SS}`
    },
    renderVisitStatus (val) {
      const s = String(val)
      if (s === '2') return '超期未回访'
      if (s === '1') return '已回访'
      return '待回访'
    },
    renderDoStatus (val) {
      const s = String(val)
      if (s === '2') return '超期未办'
      if (s === '1') return '已办'
      return '待办'
    },
    search () { this.changePageNo(1) },
    reset () {
      this.filters = { rating_dict_type: this.currentCategory || '', name: '', card_no: '', city_id: '', area_id: '', street_id: '', community_id: '', do_status: '0', rating_type: '', is_overdue: '' }
      this.changePageNo(1)
    },
    size (pageSize) { this.params.pageSize = pageSize; this.changePageNo(1) },
    changePageNo (pageNo) { this.params.pageNo = pageNo; this.init() },
    sizeHistory (pageSize) { this.historyParams.pageSize = pageSize; this.changeHistoryPageNo(1) },
    changeHistoryPageNo (pageNo) { this.historyParams.pageNo = pageNo; this.loadHistoryPage() },
    renderSex (s) { return s === '1' ? '男' : s === '2' ? '女' : '-' },
    renderRatingType (val) {
      const found = this.ratingTypeOptions.find(it => it.value === val)
      return found ? found.label : val || '-'
    },
    renderRatingDictType (val) {
      const found = this.ratingDictTypeOptions.find(it => String(it.value) === String(val))
      return found ? found.label : val || '-'
    },
    renderFolk (val) {
      if (val === null || val === undefined || val === '') return '-'
      const s = String(val)
      const name = this.nationMap && this.nationMap[s]
      return name || s
    },
    renderRemainDays (val) {
      if (val === null || val === undefined || val === '') return '-'
      const n = Number(val)
      if (Number.isNaN(n)) return String(val)
      return String(Math.max(0, n))
    },
    hasHistory (row) {
      const s = String(row && row.do_status)
      // 待办(0)、已办(1)、超期未办(2) 都应当可以查看历史
      return s === '0' || s === '1' || s === '2'
    },
    canShowVisit (row) {
      const isAdmin = (this.userInfo.login_name === 'admin')
      const power = this.visitButtonPower || {}
      const type = String(row.sys_type || '')
      if (isAdmin) return true
      if (type === '2') return !!power['rucs_visit']
      if (type === '3') return !!power['psmd_visit']
      if (type === '6') return !!power['km_visit']
      return false
    },
    openVisitModal (row) {
      this.detail = {}
      this.visitForm = { key_person_id: row.key_person_id, key_person_visit_id: row.key_person_visit_id || row.key_person_visit_id, id: row.id || row.visit_record_id || '', visit_time: '', visit_record: '', imgs: [] }
      getVisitRecordDetail({ id: row.id || row.visit_record_id, rating_dict_type: String(row.sys_type || this.currentCategory || this.filters.rating_dict_type || '') }).then((ret) => {
        if (ret.data.errcode === 0) {
          const d = ret.data.data || {}
          // 计划信息展示补充：取当前计划记录中的字段
          this.detail = Object.assign({}, d, {
            record_rating_type: (row && row.rating_type) || d.rating_type,
            plan_name: (row && row.plan_name) || d.plan_name,
            plan_type: (row && row.plan_type) || d.plan_type,
            plan_start_data: (row && row.plan_start_data) || d.plan_start_data,
            plan_end_data: (row && row.plan_end_data) || d.plan_end_data,
            remain_days: (row && row.remain_days) || d.remain_days
          })
          this.visitForm.key_person_visit_id = this.detail.key_person_visit_id || this.visitForm.key_person_visit_id
          this.visitForm.id = this.detail.id || this.visitForm.id || this.detail.current_visit_record_id
          // 历史回访信息
          this.historyData = ((this.detail.history) || []).map(it => ({
            rating_type: it.rating_type,
            visit_user: it.visit_user,
            visit_user_company: it.visit_user_company,
            visit_time: it.visit_time,
            visit_record: it.visit_record,
            img: it.img,
            do_status: it.do_status
          }))
          this.visitModal.visible = true
          this.$nextTick(() => { this.$refs.visitForm && this.$refs.visitForm.clearValidate() })
        } else {
          this.$Notice.error({ title: '加载详情失败', desc: ret.data.errmsg })
        }
      })
    },
    openHistoryModal (row) {
      // 通过回访记录主键展示：显示本次回访信息 + 历史按现有逻辑
      this.historyParams.key_person_id = row.key_person_id
      this.historyParams.current_record_id = row.id || row.visit_record_id
      this.$nextTick(() => { this.$refs.historyModal && this.$refs.historyModal.open() })
    },
    async beforeUploadVisitImg (file) {
      const name = (file && file.name) || ''
      const lower = name.toLowerCase()
      const validExt = /(\.jpg|\.jpeg|\.png)$/i.test(lower)
      const validMime = file && (file.type === 'image/jpeg' || file.type === 'image/png')
      if (!(validExt || validMime)) {
        this.$Message.error('仅支持 jpg/jpeg/png 格式图片')
        return false
      }
      // 本地预览占位
      let previewIndex = -1
      try {
        await new Promise((resolve) => {
          const reader = new FileReader()
          reader.onload = (e) => {
            this.visitForm.imgs.push(e.target.result)
            previewIndex = this.visitForm.imgs.length - 1
            resolve()
          }
          reader.readAsDataURL(file)
        })
        // 构建上传表单并调用通用上传服务
        const formdata = new FormData()
        formdata.append('file', file)
        formdata.append('fileName', file.name)
        const meta = { business_type: 'jlEvent' }
        formdata.append('json', JSON.stringify(meta))
        const ret = await uploadFile(formdata, meta)
        if (ret && ret.data && ret.data.errcode === 0 && ret.data.data) {
          const d = ret.data.data
          const url = d.url || (`/api/ac/jilinsscgsdp/uploadService/getObsObject?uuid=${d.uuid}&&suffix=${d.suffix}`)
          this.$set(this.visitForm.imgs, previewIndex, url)
        } else {
          // 失败回滚预览
          if (previewIndex > -1) this.visitForm.imgs.splice(previewIndex, 1)
          this.$Notice.error({ title: '上传失败', desc: (ret && ret.data && ret.data.errmsg) || '' })
        }
      } catch (e) {
        if (previewIndex > -1) this.visitForm.imgs.splice(previewIndex, 1)
        this.$Notice.error({ title: '上传失败' })
      }
      // 阻止组件默认上传
      return false
    },
    submitVisit () {
      this.$refs.visitForm.validate((valid) => {
        if (!valid) return
        this.visitModal.submitting = true
        if (!this.visitForm.id) {
          this.$Notice.error({ title: '保存失败', desc: '缺少回访记录主键，请刷新后重试' })
          this.visitModal.submitting = false
          return
        }
        const payload = {
          key_person_id: this.visitForm.key_person_id,
          key_person_visit_id: this.visitForm.key_person_visit_id,
          id: this.visitForm.id,
          plan_name: this.detail.plan_name,
          plan_type: this.detail.plan_type,
          rating_type: this.detail.record_rating_type || this.detail.rating_type,
          plan_start_data: this.detail.plan_start_data,
          plan_end_data: this.detail.plan_end_data,
          visit_time: this.visitForm.visit_time,
          visit_record: this.visitForm.visit_record,
          img: (this.visitForm.imgs || []).join(',')
        }
        insertVisitRecord(payload).then((ret) => {
          if (ret.data && ret.data.errcode === 0) {
            this.$Notice.success({ title: '保存成功' })
            this.visitModal.visible = false
            this.init()
          } else {
            this.$Notice.error({ title: '保存失败', desc: (ret && ret.data && ret.data.errmsg) || '' })
          }
        }).finally(() => { this.visitModal.submitting = false })
      })
    },
    onVisitTimeChange (val) {
      // DatePicker on-change returns formatted string per format
      this.visitForm.visit_time = val || ''
      // proactively clear error once value selected
      if (val && this.$refs.visitForm) this.$refs.visitForm.validateField('visit_time')
    }
  }
}
</script>

<style scoped>
.contradiction-div { padding: 10px; }
.contradiction-div .search-div { width: 100%; padding: 15px; border: 1px solid skyblue; background-color: azure; margin-bottom: 15px; }
.page_style { margin-top: 10px; text-align: right; }
.grid { display: grid; grid-template-columns: repeat(4, 1fr); grid-gap: 10px; margin-bottom: 12px; }
.section-title { margin: 10px 0; font-weight: 600; }
.info-form { margin-bottom: 8px; }
.img-list { margin-top: 8px; }
::v-deep .history table {
  width: 100% !important;
  border-collapse: collapse;
}
</style>
