| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820 |
- <template>
- <div :class="prefixCls" class="ui-scheme-page">
- <div class="page-header">
- <div class="page-title">UI组件示例</div>
- <div class="page-subtitle">使用 mock 数据展示当前系统常见的筛选、统计卡片、表格、详情弹窗和图表视图。</div>
- </div>
- <div class="page-content">
- <Alert
- message="页面说明"
- description="这个页面只做组件示意,不接接口,方便在一个界面里查看常用的业务展示组件效果。"
- type="info"
- show-icon
- />
- <Tabs v-model:activeKey="activeTab" class="section-tabs">
- <Tabs.TabPane key="modal" tab="弹窗组件" :forceRender="true">
- <div class="tab-pane">
- <Card class="modal-gallery-card" title="项目弹窗组件">
- <div class="modal-gallery">
- <div v-for="item in modalExamples" :key="item.key" class="modal-item">
- <div class="modal-item__title">{{ item.title }}</div>
- <div class="modal-item__desc">{{ item.description }}</div>
- <div class="modal-item__meta">
- <span class="modal-item__meta-label">来源文件</span>
- <span class="modal-item__meta-value">{{ item.sourceFile }}</span>
- </div>
- <div class="modal-item__meta">
- <span class="modal-item__meta-label">适用场景</span>
- <span class="modal-item__meta-value">{{ item.scene }}</span>
- </div>
- <div class="modal-item__meta">
- <span class="modal-item__meta-label">使用组件</span>
- <span class="modal-item__meta-value">{{ item.component }}</span>
- </div>
- <div class="modal-item__meta">
- <span class="modal-item__meta-label">数据来源</span>
- <span class="modal-item__meta-value">{{ item.dataSource }}</span>
- </div>
- <Button :type="item.buttonType" @click="openModalExample(item.key)">{{ item.buttonText }}</Button>
- </div>
- </div>
- </Card>
- </div>
- </Tabs.TabPane>
- <Tabs.TabPane key="table" tab="表格视图" :forceRender="true">
- <div class="tab-pane">
- <div class="filter-bar">
- <div class="filter-item">
- <span class="filter-label">分析场景:</span>
- <Select v-model:value="filters.scene" class="filter-input" :options="sceneOptions" />
- </div>
- <div class="filter-item">
- <span class="filter-label">风险等级:</span>
- <Select v-model:value="filters.riskLevel" class="filter-input" :options="riskOptions" />
- </div>
- <div class="filter-item">
- <span class="filter-label">关键词:</span>
- <Input
- v-model:value="filters.keyword"
- class="filter-input filter-input-wide"
- placeholder="对象 / 对手方 / 行业"
- />
- </div>
- <div class="filter-item">
- <span class="filter-label">日期范围:</span>
- <RangePicker v-model:value="filters.dateRange" class="filter-date" />
- </div>
- <div class="filter-actions">
- <Button type="primary" @click="handleSearch">查询</Button>
- <Button @click="handleReset">重置</Button>
- </div>
- </div>
- <div class="stats-grid">
- <Card v-for="item in statCards" :key="item.title" class="stat-card" :bordered="true">
- <Statistic :title="item.title" :value="item.value" :precision="item.precision" :suffix="item.suffix" />
- </Card>
- </div>
- <div class="main-grid">
- <Card class="table-card" title="表格数据">
- <div class="table-panel">
- <BasicTable
- :columns="columns"
- :data-source="tableData"
- :pagination="false"
- :can-resize="true"
- :is-can-resize-parent="true"
- :rowClassName="rowClassName"
- row-key="id"
- @row-click="handleRowClick"
- >
- <template #riskLevel="{ record }">
- <Tag :color="riskColorMap[record.riskLevel]">{{ record.riskLevel }}</Tag>
- </template>
- <template #amount="{ record }">{{ formatAmount(record.amount) }}</template>
- <template #status="{ record }">
- <Tag :color="statusColorMap[record.status]">{{ record.status }}</Tag>
- </template>
- <template #action="{ record }">
- <Button type="link" size="small" @click.stop="handleRowClick(record)">查看详情</Button>
- </template>
- </BasicTable>
- </div>
- </Card>
- <Card class="detail-card" title="详情面板">
- <div v-if="selectedRow" class="detail-panel">
- <Descriptions bordered size="small" :column="1">
- <DescriptionsItem label="对象">{{ selectedRow.personName }}</DescriptionsItem>
- <DescriptionsItem label="对手方">{{ selectedRow.peerName }}</DescriptionsItem>
- <DescriptionsItem label="分析场景">{{ selectedRow.scene }}</DescriptionsItem>
- <DescriptionsItem label="所属行业">{{ selectedRow.industry }}</DescriptionsItem>
- <DescriptionsItem label="所在地区">{{ selectedRow.region }}</DescriptionsItem>
- <DescriptionsItem label="风险等级">
- <Tag :color="riskColorMap[selectedRow.riskLevel]">{{ selectedRow.riskLevel }}</Tag>
- </DescriptionsItem>
- <DescriptionsItem label="近30日命中">{{ selectedRow.togetherCount }} 次</DescriptionsItem>
- <DescriptionsItem label="涉及金额">{{ formatAmount(selectedRow.amount) }}</DescriptionsItem>
- <DescriptionsItem label="最新时间">{{ selectedRow.latestDate }}</DescriptionsItem>
- <DescriptionsItem label="状态">
- <Tag :color="statusColorMap[selectedRow.status]">{{ selectedRow.status }}</Tag>
- </DescriptionsItem>
- </Descriptions>
- <div class="tag-list">
- <Tag v-for="tag in selectedRow.tags" :key="tag">{{ tag }}</Tag>
- </div>
- <div class="detail-summary">
- {{ selectedRow.summary }}
- </div>
- <div class="detail-actions">
- <Button type="primary" @click="openDetailModal">详情弹窗</Button>
- <Button @click="openFormModal">表单弹窗</Button>
- <Button @click="openDrawerExample">详情抽屉</Button>
- <Button @click="openTransRecordModal">交易记录弹窗</Button>
- </div>
- </div>
- <Empty v-else description="暂无选中数据" />
- </Card>
- </div>
- </div>
- </Tabs.TabPane>
- <Tabs.TabPane key="chart" tab="图表视图" :forceRender="true">
- <div class="tab-pane">
- <div class="stats-grid">
- <Card v-for="item in statCards" :key="`chart-${item.title}`" class="stat-card" :bordered="true">
- <Statistic :title="item.title" :value="item.value" :precision="item.precision" :suffix="item.suffix" />
- </Card>
- </div>
- <Card title="图表说明">
- <div class="detail-summary">
- 当前图表使用筛选后的 mock 数据汇总生成,左侧折线图展示近 6 个月趋势,右侧环图展示风险等级分布。
- </div>
- </Card>
- <div class="chart-grid">
- <Card class="chart-card" title="可视化视图 - 近6个月命中趋势">
- <div ref="trendChartRef" class="chart-box"></div>
- </Card>
- <Card class="chart-card" title="可视化视图 - 风险等级分布">
- <div ref="riskChartRef" class="chart-box"></div>
- </Card>
- </div>
- </div>
- </Tabs.TabPane>
- <Tabs.TabPane key="form" tab="表单组件" :forceRender="true">
- <div class="tab-pane">
- <div class="form-grid">
- <Card title="常用表单组件">
- <Form layout="vertical" class="mock-form">
- <Form.Item label="分析场景">
- <Select v-model:value="formModalModel.scene" :options="formSceneOptions" />
- </Form.Item>
- <Form.Item label="负责人员">
- <Input v-model:value="formModalModel.owner" placeholder="请输入负责人员" />
- </Form.Item>
- <Form.Item label="研判日期">
- <DatePicker v-model:value="formModalModel.judgeDate" value-format="YYYY-MM-DD" class="form-date" />
- </Form.Item>
- <Form.Item label="同步轨迹数据">
- <Switch v-model:checked="formModalModel.syncTrack" />
- </Form.Item>
- <Form.Item label="对象选择">
- <ListSelect
- ref="formListSelectRef"
- selectType="userSelect"
- :checkbox="true"
- @select="handleListSelectSelect"
- />
- </Form.Item>
- <Form.Item label="上传附件">
- <BasicUpload
- v-model:value="uploadFormValue"
- :api="mockUploadApi"
- :apiParams="mockUploadParamsApi"
- bizType="uiSchemeDemo"
- :maxNumber="3"
- :emptyHidePreview="false"
- :showPreview="true"
- :showPreviewNumber="true"
- :helpText="'演示上传弹窗与预览能力,最多 3 个文件'"
- @change="handleBasicUploadChange"
- />
- </Form.Item>
- <Form.Item label="备注说明">
- <Input.TextArea v-model:value="formModalModel.remark" :rows="4" placeholder="请输入补充说明" />
- </Form.Item>
- </Form>
- <div class="form-actions">
- <Button type="primary" @click="openFormModal">弹窗预览</Button>
- <Button @click="openUploadModalExample">上传弹窗</Button>
- </div>
- </Card>
- <Card title="表单结果与说明">
- <Descriptions bordered size="small" :column="1">
- <DescriptionsItem label="适用场景">新增、编辑、选择、上传等表单类交互</DescriptionsItem>
- <DescriptionsItem label="使用组件">Form、ListSelect、BasicUpload、BasicModal</DescriptionsItem>
- <DescriptionsItem label="所选用户">{{ selectedUsersText }}</DescriptionsItem>
- <DescriptionsItem label="上传结果">{{ uploadedFilesText }}</DescriptionsItem>
- <DescriptionsItem label="裁剪结果">{{ cropperResult || '暂无' }}</DescriptionsItem>
- </Descriptions>
- <div class="detail-summary">
- 表单页除了展示基础输入组件,也串上了项目里的 `ListSelectModal`、`UploadModal` 这类通用弹窗。
- </div>
- </Card>
- </div>
- </div>
- </Tabs.TabPane>
- </Tabs>
- </div>
- <Modal
- v-model:open="detailModalOpen"
- title="详情弹窗示例"
- :width="760"
- :footer="null"
- :centered="true"
- destroy-on-close
- >
- <div v-if="selectedRow" class="detail-modal">
- <Descriptions bordered size="small" :column="2">
- <DescriptionsItem label="对象">{{ selectedRow.personName }}</DescriptionsItem>
- <DescriptionsItem label="对手方">{{ selectedRow.peerName }}</DescriptionsItem>
- <DescriptionsItem label="分析场景">{{ selectedRow.scene }}</DescriptionsItem>
- <DescriptionsItem label="所属行业">{{ selectedRow.industry }}</DescriptionsItem>
- <DescriptionsItem label="所在地区">{{ selectedRow.region }}</DescriptionsItem>
- <DescriptionsItem label="联系电话">{{ selectedRow.mobile }}</DescriptionsItem>
- <DescriptionsItem label="开户行">{{ selectedRow.bankName }}</DescriptionsItem>
- <DescriptionsItem label="最新时间">{{ selectedRow.latestDate }}</DescriptionsItem>
- <DescriptionsItem label="风险等级">
- <Tag :color="riskColorMap[selectedRow.riskLevel]">{{ selectedRow.riskLevel }}</Tag>
- </DescriptionsItem>
- <DescriptionsItem label="状态">
- <Tag :color="statusColorMap[selectedRow.status]">{{ selectedRow.status }}</Tag>
- </DescriptionsItem>
- <DescriptionsItem label="近30日命中">{{ selectedRow.togetherCount }} 次</DescriptionsItem>
- <DescriptionsItem label="涉及金额">{{ formatAmount(selectedRow.amount) }}</DescriptionsItem>
- </Descriptions>
- <div class="detail-summary">
- {{ selectedRow.summary }}
- </div>
- <div class="modal-actions">
- <Button type="primary" @click="detailModalOpen = false">关闭</Button>
- </div>
- </div>
- </Modal>
- <BasicModal
- v-model:open="formModalOpen"
- title="BasicModal 表单弹窗示例"
- :width="720"
- :showFooter="true"
- :confirmLoading="formModalSubmitting"
- wrapClassName="ui-scheme-form-modal"
- @ok="handleFormSubmit"
- @cancel="formModalOpen = false"
- >
- <div class="form-modal-body">
- <Alert
- message="适用场景"
- description="用于新增、编辑、配置维护等需要表单提交的弹窗场景。"
- type="info"
- show-icon
- />
- <Form layout="vertical" class="mock-form">
- <Form.Item label="分析场景">
- <Select v-model:value="formModalModel.scene" :options="formSceneOptions" />
- </Form.Item>
- <Form.Item label="负责人员">
- <Input v-model:value="formModalModel.owner" placeholder="请输入负责人员" />
- </Form.Item>
- <Form.Item label="研判日期">
- <DatePicker v-model:value="formModalModel.judgeDate" value-format="YYYY-MM-DD" class="form-date" />
- </Form.Item>
- <Form.Item label="同步轨迹数据">
- <Switch v-model:checked="formModalModel.syncTrack" />
- </Form.Item>
- <Form.Item label="备注说明">
- <Input.TextArea v-model:value="formModalModel.remark" :rows="4" placeholder="请输入补充说明" />
- </Form.Item>
- </Form>
- </div>
- </BasicModal>
- <BasicDrawer
- v-model:open="drawerOpen"
- title="BasicDrawer 抽屉示例"
- :width="520"
- :showFooter="true"
- @ok="drawerOpen = false"
- @close="drawerOpen = false"
- >
- <div v-if="selectedRow" class="drawer-panel">
- <Alert
- message="适用场景"
- description="适合侧边详情、轻量编辑、上下文不希望被完全打断的查看场景。"
- type="info"
- show-icon
- />
- <Descriptions bordered size="small" :column="1">
- <DescriptionsItem label="对象">{{ selectedRow.personName }}</DescriptionsItem>
- <DescriptionsItem label="对手方">{{ selectedRow.peerName }}</DescriptionsItem>
- <DescriptionsItem label="分析场景">{{ selectedRow.scene }}</DescriptionsItem>
- <DescriptionsItem label="所属行业">{{ selectedRow.industry }}</DescriptionsItem>
- <DescriptionsItem label="风险等级">
- <Tag :color="riskColorMap[selectedRow.riskLevel]">{{ selectedRow.riskLevel }}</Tag>
- </DescriptionsItem>
- <DescriptionsItem label="状态">
- <Tag :color="statusColorMap[selectedRow.status]">{{ selectedRow.status }}</Tag>
- </DescriptionsItem>
- <DescriptionsItem label="最新时间">{{ selectedRow.latestDate }}</DescriptionsItem>
- </Descriptions>
- <div class="drawer-summary">
- {{ selectedRow.summary }}
- </div>
- </div>
- </BasicDrawer>
- <QueryTableModal
- v-model:open="queryTableModalOpen"
- title="QueryTableModal 示例"
- :loading="false"
- :filters="queryTableFilters"
- :filter-schema="queryTableFilterSchema"
- :columns="queryTableColumns"
- :data-source="queryTableData"
- :pagination="queryTablePagination"
- :total-pages="queryTableTotalPages"
- :page-size-options="queryTablePageSizeOptions"
- :show-table-setting="false"
- :table-setting="{ size: true, setting: true, redo: false, fullScreen: false }"
- :can-fullscreen="true"
- wrap-class-name="ui-scheme-query-table-modal"
- @search="handleQueryTableSearch"
- @reset="handleQueryTableReset"
- @prev-page="handleQueryTablePrevPage"
- @next-page="handleQueryTableNextPage"
- @page-size-change="handleQueryTablePageSizeChange"
- @page-change="handleQueryTablePageChange"
- @sort-change="handleQueryTableSortChange"
- />
- <TransRecordModal
- v-model:open="transRecordModalOpen"
- title="交易记录"
- :mock-mode="true"
- :base-query="transRecordBaseQuery"
- />
- <CallRecordModal v-model:open="callRecordModalOpen" title="通信记录" :base-query="callRecordBaseQuery" />
- <AppSearchModal :open="appSearchModalOpen" @close="appSearchModalOpen = false" />
- <UploadModal
- :api="mockUploadApi"
- :apiParams="mockUploadParamsApi"
- bizKey="ui-scheme-upload"
- bizType="uiSchemeDemo"
- :isLazy="true"
- :maxNumber="3"
- :helpText="'演示上传弹窗,支持选择文件后点击开始上传'"
- @register="registerUploadModal"
- @change="handleUploadModalChange"
- />
- <UploadPreviewModal
- :value="previewFileList"
- :readonly="true"
- :emptyHidePreview="false"
- :apiDownloadUrl="'/api/mock-download'"
- @register="registerUploadPreviewModal"
- />
- <CopperModal
- :value="cropperPreviewSource"
- @register="registerCropperModal"
- @upload-success="handleCropperSuccess"
- />
- <ErrorLogDetailModal :info="errorLogDetailInfo" @register="registerErrorLogModal" />
- <LockModal @register="registerLockModal" />
- </div>
- </template>
- <script lang="ts" setup name="ViewsUiSchemeIndex">
- import { computed, onMounted, reactive, ref, type Ref } from 'vue';
- import type { Dayjs } from 'dayjs';
- import type { EChartsOption } from 'echarts';
- import {
- Alert,
- Button,
- Card,
- DatePicker,
- Descriptions,
- Empty,
- Form,
- Input,
- Modal,
- Select,
- Statistic,
- Switch,
- Tag,
- Tabs,
- } from 'ant-design-vue';
- import { ListSelect } from '@jeesite/core/components/ListSelect';
- import { BasicDrawer } from '@jeesite/core/components/Drawer';
- import { BasicModal, useModal } from '@jeesite/core/components/Modal';
- import { BasicTable, type BasicColumn } from '@jeesite/core/components/Table';
- import { BasicUpload } from '@jeesite/core/components/Upload';
- import AppSearchModal from '@jeesite/core/components/Application/src/search/AppSearchModal.vue';
- import CopperModal from '@jeesite/core/components/Cropper/src/CopperModal.vue';
- import UploadPreviewModal from '@jeesite/core/components/Upload/src/UploadPreviewModal.vue';
- import ErrorLogDetailModal from '@jeesite/core/layouts/views/errorLog/DetailModal.vue';
- import { ErrorTypeEnum } from '@jeesite/core/enums/exceptionEnum';
- import { useDesign } from '@jeesite/core/hooks/web/useDesign';
- import { useECharts } from '@jeesite/core/hooks/web/useECharts';
- import { useMessage } from '@jeesite/core/hooks/web/useMessage';
- import LockModal from '@jeesite/core/layouts/default/header/components/lock/LockModal.vue';
- import componentSetting from '@jeesite/core/settings/componentSetting';
- import type { UploadParams } from '@jeesite/core/api/sys/upload';
- import type { ErrorLogInfo } from '@jeesite/types/store';
- import UploadModal from '@jeesite/core/components/Upload/src/UploadModal.vue';
- import CallRecordModal from '@jeesite/call/views/components/CallRecordModal.vue';
- import QueryTableModal from '@jeesite/case/views/dm/components/QueryTableModal.vue';
- import TransRecordModal from '../components/TransRecordModal.vue';
- interface DemoRow {
- id: number;
- personName: string;
- peerName: string;
- scene: string;
- industry: string;
- region: string;
- riskLevel: '高' | '中' | '低';
- status: '关注中' | '待研判' | '已核查';
- togetherCount: number;
- amount: number;
- latestDate: string;
- bankName: string;
- mobile: string;
- tags: string[];
- summary: string;
- trend: number[];
- }
- type RangeValue = [Dayjs, Dayjs] | undefined;
- const RangePicker = DatePicker.RangePicker;
- const DescriptionsItem = Descriptions.Item;
- const { createConfirm, createMessage } = useMessage();
- const { prefixCls } = useDesign('ui-scheme');
- const activeTab = ref('modal');
- const filters = ref({
- scene: 'all',
- riskLevel: 'all',
- keyword: '',
- dateRange: undefined as RangeValue,
- });
- const sceneOptions = [
- { label: '全部', value: 'all' },
- { label: '同行分析', value: '同行分析' },
- { label: '交易分析', value: '交易分析' },
- { label: '话单分析', value: '话单分析' },
- ];
- const riskOptions = [
- { label: '全部', value: 'all' },
- { label: '高', value: '高' },
- { label: '中', value: '中' },
- { label: '低', value: '低' },
- ];
- const riskColorMap: Record<DemoRow['riskLevel'], string> = {
- 高: 'red',
- 中: 'orange',
- 低: 'green',
- };
- const formSceneOptions = [
- { label: '同行分析', value: '同行分析' },
- { label: '交易分析', value: '交易分析' },
- { label: '话单分析', value: '话单分析' },
- ];
- const statusColorMap: Record<DemoRow['status'], string> = {
- 关注中: 'processing',
- 待研判: 'warning',
- 已核查: 'default',
- };
- const monthLabels = ['2025-10', '2025-11', '2025-12', '2026-01', '2026-02', '2026-03'];
- const mockSourceData: DemoRow[] = [
- {
- id: 1,
- personName: '张三',
- peerName: '杭州云创科技',
- scene: '同行分析',
- industry: '科技服务',
- region: '杭州',
- riskLevel: '高',
- status: '关注中',
- togetherCount: 12,
- amount: 128.5,
- latestDate: '2026-03-22',
- bankName: '中国银行',
- mobile: '13800001234',
- tags: ['高频同行', '近7日活跃', '需复核'],
- summary: '近一个月多次与重点企业共同出行,同时存在资金往来,适合用于展示详情面板和弹窗内容。',
- trend: [3, 4, 5, 6, 8, 12],
- },
- {
- id: 2,
- personName: '李四',
- peerName: '上海远航贸易',
- scene: '交易分析',
- industry: '贸易流通',
- region: '上海',
- riskLevel: '中',
- status: '待研判',
- togetherCount: 8,
- amount: 96.2,
- latestDate: '2026-03-18',
- bankName: '建设银行',
- mobile: '13900004567',
- tags: ['交易频繁', '跨地区往来'],
- summary: '该对象与对手方交易频次较高,金额稳定,适合展示表格、标签和统计卡片的组合效果。',
- trend: [2, 3, 4, 4, 6, 8],
- },
- {
- id: 3,
- personName: '王五',
- peerName: '深圳星河商贸',
- scene: '话单分析',
- industry: '商贸零售',
- region: '深圳',
- riskLevel: '中',
- status: '关注中',
- togetherCount: 10,
- amount: 88.7,
- latestDate: '2026-03-25',
- bankName: '工商银行',
- mobile: '13700007890',
- tags: ['夜间联系', '跨城轨迹'],
- summary: '话单与轨迹存在交叉命中,适合在右侧详情面板中展示基础信息和标签。',
- trend: [1, 3, 3, 5, 7, 10],
- },
- {
- id: 4,
- personName: '赵六',
- peerName: '宁波融信服务',
- scene: '同行分析',
- industry: '企业服务',
- region: '宁波',
- riskLevel: '低',
- status: '已核查',
- togetherCount: 5,
- amount: 42.3,
- latestDate: '2026-03-09',
- bankName: '农业银行',
- mobile: '13600002345',
- tags: ['历史留痕'],
- summary: '该对象已完成初步核查,可用于展示状态标签和较轻量的详情说明。',
- trend: [1, 1, 2, 2, 4, 5],
- },
- {
- id: 5,
- personName: '张三',
- peerName: '上海远航贸易',
- scene: '交易分析',
- industry: '贸易流通',
- region: '上海',
- riskLevel: '高',
- status: '关注中',
- togetherCount: 14,
- amount: 166.8,
- latestDate: '2026-03-28',
- bankName: '中国银行',
- mobile: '13800001234',
- tags: ['大额转账', '重点对象'],
- summary: '同一对象在不同场景下可以复用相同组件,适合模拟列表切换后的详情联动。',
- trend: [4, 5, 7, 8, 10, 14],
- },
- {
- id: 6,
- personName: '李四',
- peerName: '宁波融信服务',
- scene: '话单分析',
- industry: '企业服务',
- region: '宁波',
- riskLevel: '低',
- status: '待研判',
- togetherCount: 4,
- amount: 31.4,
- latestDate: '2026-02-26',
- bankName: '建设银行',
- mobile: '13900004567',
- tags: ['短时联系'],
- summary: '样本较轻,适合用来观察筛选条件变化后表格、图表和详情面板的同步更新。',
- trend: [0, 1, 1, 2, 3, 4],
- },
- ];
- const queryTableMockSourceData: DemoRow[] = Array.from({ length: 48 }, (_, index) => {
- const base = mockSourceData[index % mockSourceData.length];
- const batch = Math.floor(index / mockSourceData.length);
- const peerGroup = ['一组', '二组', '三组', '四组'][batch % 4];
- const latestDay = String(((index * 2) % 28) + 1).padStart(2, '0');
- return {
- ...base,
- id: index + 1,
- peerName: `${base.peerName}-${peerGroup}`,
- togetherCount: base.togetherCount + batch * 2 + (index % 3),
- amount: Number((base.amount + batch * 15.6 + (index % 4) * 3.2).toFixed(1)),
- latestDate: `2026-03-${latestDay}`,
- tags: [...base.tags, `批次${batch + 1}`],
- summary: `${base.summary} 当前为 QueryTableModal 的第 ${batch + 1} 批 mock 数据。`,
- trend: base.trend.map((value, trendIndex) => value + batch + (trendIndex === 5 ? index % 3 : 0)),
- };
- });
- const columns: BasicColumn[] = [
- { title: '序号', dataIndex: 'id', key: 'id', width: 70, align: 'center' },
- { title: '对象', dataIndex: 'personName', key: 'personName', width: 100, align: 'center' },
- { title: '对手方', dataIndex: 'peerName', key: 'peerName', width: 160, align: 'center' },
- { title: '分析场景', dataIndex: 'scene', key: 'scene', width: 120, align: 'center' },
- { title: '所属行业', dataIndex: 'industry', key: 'industry', width: 120, align: 'center' },
- {
- title: '风险等级',
- dataIndex: 'riskLevel',
- key: 'riskLevel',
- width: 110,
- align: 'center',
- slots: { customRender: 'riskLevel' },
- },
- { title: '近30日命中', dataIndex: 'togetherCount', key: 'togetherCount', width: 120, align: 'center' },
- {
- title: '涉及金额',
- dataIndex: 'amount',
- key: 'amount',
- width: 120,
- align: 'center',
- slots: { customRender: 'amount' },
- },
- { title: '最新时间', dataIndex: 'latestDate', key: 'latestDate', width: 120, align: 'center' },
- {
- title: '状态',
- dataIndex: 'status',
- key: 'status',
- width: 110,
- align: 'center',
- slots: { customRender: 'status' },
- },
- {
- title: '操作',
- dataIndex: 'action',
- key: 'action',
- width: 100,
- align: 'center',
- slots: { customRender: 'action' },
- },
- ];
- const tableData = ref<DemoRow[]>([]);
- const selectedRow = ref<DemoRow | null>(null);
- const detailModalOpen = ref(false);
- const formModalOpen = ref(false);
- const formModalSubmitting = ref(false);
- const drawerOpen = ref(false);
- const queryTableModalOpen = ref(false);
- const transRecordModalOpen = ref(false);
- const callRecordModalOpen = ref(false);
- const appSearchModalOpen = ref(false);
- const formListSelectRef = ref<any>(null);
- const selectedUsers = ref<Record<string, any>[]>([]);
- const uploadFormValue = ref<Record<string, any>>({});
- const uploadedFiles = ref<string[]>([]);
- const cropperResult = ref('');
- const cropperPreviewSource =
- 'data:image/svg+xml;charset=UTF-8,' +
- encodeURIComponent(
- '<svg xmlns="http://www.w3.org/2000/svg" width="480" height="320" viewBox="0 0 480 320"><rect width="480" height="320" fill="#eaf2ff"/><rect x="24" y="24" width="432" height="272" rx="24" fill="#cfe0ff"/><circle cx="160" cy="140" r="42" fill="#7aa2ff"/><rect x="228" y="104" width="120" height="20" rx="10" fill="#5b7cce"/><rect x="228" y="138" width="160" height="16" rx="8" fill="#8da8df"/><rect x="228" y="166" width="96" height="16" rx="8" fill="#8da8df"/><text x="240" y="228" font-size="24" fill="#3a4b7a">UI Scheme</text></svg>',
- );
- const previewFileList = ref<any[]>([
- {
- id: 'mock-preview-1',
- fileName: 'tonghang-analysis.png',
- fileType: 'png',
- fileUrl: cropperPreviewSource,
- createDate: '2026-04-04 10:30:00',
- bizKey: 'ui-scheme-preview',
- bizType: 'uiSchemePreview',
- bizKeyIsLike: '',
- fileEntity: {
- fileExtension: 'png',
- fileSize: 245760,
- },
- },
- {
- id: 'mock-preview-2',
- fileName: 'peer-analysis-report.pdf',
- fileType: 'pdf',
- fileUrl: '',
- createDate: '2026-04-04 10:42:00',
- bizKey: 'ui-scheme-preview',
- bizType: 'uiSchemePreview',
- bizKeyIsLike: '',
- fileEntity: {
- fileExtension: 'pdf',
- fileSize: 1048576,
- },
- },
- ]);
- const errorLogDetailInfo = ref<ErrorLogInfo>({
- type: ErrorTypeEnum.PROMISE,
- url: '/trans/uiScheme',
- time: '2026-04-04 11:20:36',
- file: 'frontend/packages/trans/views/uiScheme/index.vue:512:14',
- name: 'MockUiError',
- message: 'Cannot read properties of undefined (reading "amount")',
- detail: '打开弹窗示例时传入了空数据,当前内容仅用于统一管理时查看异常详情弹窗的结构。',
- stack:
- 'TypeError: Cannot read properties of undefined (reading "amount") at openModalExample (uiScheme/index.vue:512:14)',
- });
- const formModalModel = reactive({
- scene: '同行分析',
- owner: '研判一组',
- judgeDate: '2026-04-04',
- syncTrack: true,
- remark: '该弹窗用于演示项目里基于 BasicModal 的表单录入场景。',
- });
- const [registerUploadModal, { openModal: openUploadModal }] = useModal();
- const [registerUploadPreviewModal, { openModal: openUploadPreviewModal }] = useModal();
- const [registerCropperModal, { openModal: openCropperModal }] = useModal();
- const [registerErrorLogModal, { openModal: openErrorLogModal }] = useModal();
- const [registerLockModal, { openModal: openLockModal }] = useModal();
- const modalExamples = [
- {
- key: 'basic',
- title: '基础详情弹窗',
- description: '使用 Ant Design Vue `Modal` 展示详情信息,适合详情查看、确认说明等场景。',
- sourceFile: '当前页内联示例(ant-design-vue Modal)',
- scene: '详情查看 / 结果说明',
- component: 'Modal + Descriptions',
- dataSource: '本地 mock',
- buttonText: '打开弹窗',
- buttonType: 'primary' as const,
- },
- {
- key: 'form',
- title: 'BasicModal 表单弹窗',
- description: '项目基础弹窗包装,适合新增、编辑、配置维护等表单提交场景。',
- sourceFile: 'frontend/packages/core/components/Modal/src/BasicModal.vue',
- scene: '新增 / 编辑 / 配置维护',
- component: 'BasicModal + Form',
- dataSource: '本地 mock',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'drawer',
- title: 'BasicDrawer 抽屉',
- description: '抽屉适合从右侧承载详情或轻量编辑,不打断当前列表上下文。',
- sourceFile: '当前页内联示例(BasicDrawer)',
- scene: '侧边详情 / 轻量编辑',
- component: 'BasicDrawer + Descriptions',
- dataSource: '本地 mock',
- buttonText: '打开抽屉',
- buttonType: 'default' as const,
- },
- {
- key: 'upload',
- title: 'UploadModal',
- description: '项目通用上传弹窗,适合附件、证据材料、批量文件上传等场景。',
- sourceFile: 'frontend/packages/core/components/Upload/src/UploadModal.vue',
- scene: '附件上传 / 材料补充',
- component: 'UploadModal',
- dataSource: '本地 mock 上传',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'listSelect',
- title: 'ListSelectModal',
- description: '通过 ListSelect 触发的数据选择弹窗,常用于人员、机构、字典等对象选择。',
- sourceFile: 'frontend/packages/core/components/ListSelect/src/ListSelectModal.vue',
- scene: '对象选择 / 关系绑定',
- component: 'ListSelect -> ListSelectModal',
- dataSource: '真实接口',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'queryTable',
- title: 'QueryTableModal',
- description: '项目里常见的“查询区 + 表格 + 分页”复用弹窗,当前用 mock 数据演示。',
- sourceFile: 'frontend/packages/case/views/dm/components/QueryTableModal.vue',
- scene: '列表查询 / 记录检索',
- component: 'QueryTableModal',
- dataSource: '本地 mock',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'transRecord',
- title: 'TransRecordModal',
- description: '交易分析页面复用的记录弹窗,当前使用组件内置 `mock-mode` 展示交易记录。',
- sourceFile: 'frontend/packages/trans/views/components/TransRecordModal.vue',
- scene: '交易明细 / 资金记录',
- component: 'TransRecordModal',
- dataSource: '组件 mock',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'callRecord',
- title: 'CallRecordModal',
- description: '话单分析页面复用的通话记录弹窗,展示项目里的真实弹窗组件结构。',
- sourceFile: 'frontend/packages/call/views/components/CallRecordModal.vue',
- scene: '通话明细 / 话单记录',
- component: 'CallRecordModal',
- dataSource: '真实接口',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'confirm',
- title: '确认弹窗',
- description: '通过 `useMessage().createConfirm` 拉起确认框,适合删除、重算、批量操作前确认。',
- sourceFile: '当前页交互示例(useMessage.createConfirm)',
- scene: '删除确认 / 重算确认 / 批量动作',
- component: 'useMessage.createConfirm',
- dataSource: '本地交互',
- buttonText: '打开确认框',
- buttonType: 'default' as const,
- },
- {
- key: 'appSearch',
- title: 'AppSearchModal',
- description: '头部全局搜索弹层,适合快速查找菜单、页面入口和系统导航。',
- sourceFile: 'frontend/packages/core/components/Application/src/search/AppSearchModal.vue',
- scene: '全局菜单搜索 / 快捷跳转',
- component: 'AppSearchModal',
- dataSource: '当前系统菜单',
- buttonText: '打开搜索',
- buttonType: 'default' as const,
- },
- {
- key: 'uploadPreview',
- title: 'UploadPreviewModal',
- description: '上传后的文件预览弹窗,适合附件列表查看、图片缩略图展示和文件清单校验。',
- sourceFile: 'frontend/packages/core/components/Upload/src/UploadPreviewModal.vue',
- scene: '文件预览 / 附件清单查看',
- component: 'UploadPreviewModal',
- dataSource: '本地 mock 文件',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'cropper',
- title: 'CopperModal',
- description: '头像/图片裁剪弹窗,项目中用于上传前裁剪、旋转、缩放和预览。',
- sourceFile: 'frontend/packages/core/components/Cropper/src/CopperModal.vue',
- scene: '图片裁剪 / 头像编辑',
- component: 'CopperModal',
- dataSource: '本地 mock 图片',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'errorLog',
- title: 'ErrorLog DetailModal',
- description: '系统错误日志详情弹窗,用于查看异常类型、堆栈、地址和触发时间。',
- sourceFile: 'frontend/packages/core/layouts/views/errorLog/DetailModal.vue',
- scene: '异常详情 / 问题排查',
- component: 'DetailModal',
- dataSource: '本地 mock 异常信息',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- {
- key: 'lock',
- title: 'LockModal',
- description: '系统锁屏弹窗,用于输入锁屏密码并进入锁定状态,适合临时离岗保护场景。',
- sourceFile: 'frontend/packages/core/layouts/default/header/components/lock/LockModal.vue',
- scene: '锁屏 / 暂离保护',
- component: 'LockModal',
- dataSource: '当前登录用户 + 本地锁屏状态',
- buttonText: '打开弹窗',
- buttonType: 'default' as const,
- },
- ];
- const selectedUsersText = computed(() => {
- if (!selectedUsers.value.length) return '暂无';
- return selectedUsers.value.map((item) => item.userName || item.refName || item.userCode || '-').join('、');
- });
- const uploadedFilesText = computed(() => {
- const names = uploadedFiles.value.length
- ? uploadedFiles.value
- : String(uploadFormValue.value?.uiSchemeDemo || '')
- .split(',')
- .filter(Boolean);
- return names.length ? names.join('、') : '暂无';
- });
- const statCards = computed(() => {
- const totalCount = tableData.value.length;
- const highRiskCount = tableData.value.filter((item) => item.riskLevel === '高').length;
- const totalHits = tableData.value.reduce((sum, item) => sum + item.togetherCount, 0);
- const totalAmount = Number(tableData.value.reduce((sum, item) => sum + item.amount, 0).toFixed(1));
- return [
- { title: '当前样本', value: totalCount, precision: 0, suffix: '条' },
- { title: '高风险对象', value: highRiskCount, precision: 0, suffix: '条' },
- { title: '近30日命中', value: totalHits, precision: 0, suffix: '次' },
- { title: '涉及金额', value: totalAmount, precision: 1, suffix: '万' },
- ];
- });
- const transRecordBaseQuery = computed(() => ({
- personName: selectedRow.value?.personName || undefined,
- otherName: selectedRow.value?.peerName || undefined,
- bankName: selectedRow.value?.bankName || undefined,
- }));
- const callRecordBaseQuery = computed(() => ({
- personName: selectedRow.value?.personName || undefined,
- otherName: selectedRow.value?.peerName || undefined,
- personPhones: selectedRow.value?.mobile ? [selectedRow.value.mobile] : undefined,
- }));
- const queryTableFilters = reactive({
- personName: '',
- otherName: '',
- scene: '',
- riskLevel: '',
- startDate: '',
- endDate: '',
- orderKey: '',
- sort: '' as '' | 'ASC' | 'DESC',
- });
- const queryTablePagination = reactive({
- page: 1,
- limit: componentSetting.table.defaultPageSize,
- total: 0,
- });
- const queryTablePageSizeOptions = componentSetting.table.pageSizeOptions.map((value) => ({
- label: value,
- value: Number(value),
- }));
- const queryTableTotalPages = computed(() => {
- if (!queryTablePagination.total) return 1;
- return Math.max(1, Math.ceil(queryTablePagination.total / queryTablePagination.limit));
- });
- const queryTableFilterSchema = [
- { field: 'personName', label: '对象', type: 'input', placeholder: '请输入对象', width: 180 },
- { field: 'otherName', label: '对手方', type: 'input', placeholder: '请输入对手方', width: 180 },
- {
- field: 'scene',
- label: '场景',
- type: 'select',
- width: 160,
- options: [
- { label: '全部', value: '' },
- { label: '同行分析', value: '同行分析' },
- { label: '交易分析', value: '交易分析' },
- { label: '话单分析', value: '话单分析' },
- ],
- },
- {
- field: 'riskLevel',
- label: '风险等级',
- type: 'select',
- width: 160,
- options: [
- { label: '全部', value: '' },
- { label: '高', value: '高' },
- { label: '中', value: '中' },
- { label: '低', value: '低' },
- ],
- },
- { field: 'startDate', label: '开始日期', type: 'date', width: 170 },
- { field: 'endDate', label: '结束日期', type: 'date', width: 170 },
- ] as const;
- const queryTableColumns: BasicColumn[] = [
- { title: '排序', dataIndex: 'id', key: 'id', width: 70, align: 'center' },
- { title: '对象', dataIndex: 'personName', key: 'personName', width: 120, align: 'center' },
- { title: '对手方', dataIndex: 'peerName', key: 'peerName', width: 160, align: 'center' },
- { title: '分析场景', dataIndex: 'scene', key: 'scene', width: 120, align: 'center' },
- { title: '风险等级', dataIndex: 'riskLevel', key: 'riskLevel', width: 110, align: 'center' },
- { title: '命中次数', dataIndex: 'togetherCount', key: 'togetherCount', width: 110, align: 'center', sorter: true },
- { title: '涉及金额', dataIndex: 'amountText', key: 'amount', width: 120, align: 'center', sorter: true },
- { title: '最新时间', dataIndex: 'latestDate', key: 'latestDate', width: 120, align: 'center', sorter: true },
- { title: '状态', dataIndex: 'status', key: 'status', width: 110, align: 'center' },
- ];
- const queryTableData = ref<any[]>([]);
- const trendChartRef = ref<HTMLDivElement | null>(null);
- const riskChartRef = ref<HTMLDivElement | null>(null);
- const { setOptions: setTrendOptions } = useECharts(trendChartRef as Ref<HTMLDivElement>);
- const { setOptions: setRiskOptions } = useECharts(riskChartRef as Ref<HTMLDivElement>);
- const formatAmount = (amount: number) => `${amount.toFixed(1)}万`;
- const ensureSelectedRow = () => {
- if (selectedRow.value) return true;
- createMessage.warning('请先在表格中选择一条数据');
- return false;
- };
- const loadQueryTableData = () => {
- let filtered = [...queryTableMockSourceData];
- if (queryTableFilters.personName) {
- filtered = filtered.filter((item) => item.personName.includes(queryTableFilters.personName));
- }
- if (queryTableFilters.otherName) {
- filtered = filtered.filter((item) => item.peerName.includes(queryTableFilters.otherName));
- }
- if (queryTableFilters.scene) {
- filtered = filtered.filter((item) => item.scene === queryTableFilters.scene);
- }
- if (queryTableFilters.riskLevel) {
- filtered = filtered.filter((item) => item.riskLevel === queryTableFilters.riskLevel);
- }
- if (queryTableFilters.startDate) {
- filtered = filtered.filter((item) => item.latestDate >= queryTableFilters.startDate);
- }
- if (queryTableFilters.endDate) {
- filtered = filtered.filter((item) => item.latestDate <= queryTableFilters.endDate);
- }
- if (queryTableFilters.orderKey && queryTableFilters.sort) {
- filtered.sort((left, right) => {
- const leftValue = left[queryTableFilters.orderKey as keyof DemoRow];
- const rightValue = right[queryTableFilters.orderKey as keyof DemoRow];
- const result = String(leftValue ?? '').localeCompare(String(rightValue ?? ''), 'zh-CN', { numeric: true });
- return queryTableFilters.sort === 'ASC' ? result : -result;
- });
- }
- queryTablePagination.total = filtered.length;
- const start = (queryTablePagination.page - 1) * queryTablePagination.limit;
- queryTableData.value = filtered.slice(start, start + queryTablePagination.limit).map((item, index) => ({
- ...item,
- rowKey: item.id,
- id: start + index + 1,
- amountText: formatAmount(item.amount),
- }));
- };
- const applyFilters = () => {
- const keyword = filters.value.keyword.trim();
- const range = filters.value.dateRange;
- tableData.value = mockSourceData.filter((item) => {
- if (filters.value.scene !== 'all' && item.scene !== filters.value.scene) return false;
- if (filters.value.riskLevel !== 'all' && item.riskLevel !== filters.value.riskLevel) return false;
- if (
- keyword &&
- ![item.personName, item.peerName, item.industry, item.region]
- .map((value) => String(value || ''))
- .some((value) => value.includes(keyword))
- ) {
- return false;
- }
- if (range?.[0] && item.latestDate < range[0].format('YYYY-MM-DD')) return false;
- if (range?.[1] && item.latestDate > range[1].format('YYYY-MM-DD')) return false;
- return true;
- });
- if (!tableData.value.length) {
- selectedRow.value = null;
- renderTrendChart([]);
- renderRiskChart([]);
- return;
- }
- const currentId = selectedRow.value?.id;
- selectedRow.value = tableData.value.find((item) => item.id === currentId) || tableData.value[0];
- renderTrendChart(tableData.value);
- renderRiskChart(tableData.value);
- };
- const renderTrendChart = (list: DemoRow[]) => {
- const seriesData = monthLabels.map((_, index) => list.reduce((sum, item) => sum + (item.trend[index] || 0), 0));
- const empty = !list.length;
- const options: EChartsOption = {
- tooltip: { trigger: 'axis' },
- grid: {
- left: '3%',
- right: '3%',
- top: '12%',
- bottom: '8%',
- outerBoundsMode: 'same',
- outerBoundsContain: 'axisLabel',
- },
- xAxis: {
- type: 'category',
- data: empty ? ['暂无数据'] : monthLabels,
- axisTick: { show: false },
- axisLine: { lineStyle: { color: '#d9d9d9' } },
- },
- yAxis: {
- type: 'value',
- axisTick: { show: false },
- splitLine: { lineStyle: { color: '#f0f0f0' } },
- },
- series: [
- {
- name: '命中次数',
- type: 'line',
- smooth: true,
- data: empty ? [0] : seriesData,
- symbol: 'circle',
- symbolSize: 8,
- lineStyle: { color: '#1677ff', width: 2 },
- itemStyle: { color: '#1677ff' },
- areaStyle: { color: 'rgba(22, 119, 255, 0.12)' },
- },
- ],
- };
- setTrendOptions(options);
- };
- const renderRiskChart = (list: DemoRow[]) => {
- const riskData = [
- { name: '高风险', value: list.filter((item) => item.riskLevel === '高').length },
- { name: '中风险', value: list.filter((item) => item.riskLevel === '中').length },
- { name: '低风险', value: list.filter((item) => item.riskLevel === '低').length },
- ].filter((item) => item.value > 0);
- const options: EChartsOption = {
- tooltip: { trigger: 'item' },
- legend: { bottom: 0, icon: 'circle' },
- series: [
- {
- type: 'pie',
- radius: ['42%', '68%'],
- center: ['50%', '45%'],
- label: { formatter: '{b}: {c}' },
- data: riskData.length
- ? riskData
- : [
- {
- name: '暂无数据',
- value: 1,
- itemStyle: { color: '#d9d9d9' },
- },
- ],
- },
- ],
- };
- setRiskOptions(options);
- };
- const handleSearch = () => {
- applyFilters();
- };
- const handleReset = () => {
- filters.value = {
- scene: 'all',
- riskLevel: 'all',
- keyword: '',
- dateRange: undefined,
- };
- applyFilters();
- };
- const handleRowClick = (record: DemoRow) => {
- selectedRow.value = record;
- };
- const rowClassName = (record: any, _index: number): string => {
- const row = record?.record || record;
- return selectedRow.value?.id === row?.id ? 'summary-row-selected' : '';
- };
- const openTransRecordModal = () => {
- if (!ensureSelectedRow()) return;
- transRecordModalOpen.value = true;
- };
- const openDetailModal = () => {
- if (!ensureSelectedRow()) return;
- detailModalOpen.value = true;
- };
- const openFormModal = () => {
- if (!ensureSelectedRow()) return;
- formModalModel.scene = selectedRow.value?.scene || '同行分析';
- formModalModel.owner = '研判一组';
- formModalModel.judgeDate = selectedRow.value?.latestDate || '2026-04-04';
- formModalModel.syncTrack = true;
- formModalModel.remark = `当前对象:${selectedRow.value?.personName || '-'},该表单用于演示项目里的配置或录入类弹窗。`;
- formModalOpen.value = true;
- };
- const openQueryTableModal = () => {
- queryTablePagination.page = 1;
- loadQueryTableData();
- queryTableModalOpen.value = true;
- };
- const openUploadModalExample = () => {
- openUploadModal(true, {});
- };
- const openUploadPreviewExample = () => {
- createMessage.info('预览弹窗当前使用本地 mock 文件,仅用于统一查看组件结构。');
- openUploadPreviewModal(true);
- };
- const openCallRecordModal = () => {
- if (!ensureSelectedRow()) return;
- callRecordModalOpen.value = true;
- };
- const openDrawerExample = () => {
- if (!ensureSelectedRow()) return;
- drawerOpen.value = true;
- };
- const openConfirmExample = () => {
- createConfirm({
- title: '确认弹窗示例',
- content: '是否继续执行这次 mock 研判动作?该确认框用于演示项目里的二次确认交互。',
- iconType: 'warning',
- onOk() {
- createMessage.success('已确认执行');
- },
- });
- };
- const openAppSearchExample = () => {
- appSearchModalOpen.value = true;
- };
- const openCropperExample = () => {
- openCropperModal(true);
- };
- const handleCropperSuccess = (payload: Record<string, any>) => {
- cropperResult.value = payload?.filename || payload?.source || '已完成裁剪';
- createMessage.success('裁剪弹窗已返回结果');
- };
- const openErrorLogExample = () => {
- openErrorLogModal(true);
- };
- const openLockModalExample = () => {
- openLockModal(true);
- };
- const openModalExample = (key: string) => {
- const actionMap: Record<string, () => void> = {
- basic: openDetailModal,
- form: openFormModal,
- drawer: openDrawerExample,
- appSearch: openAppSearchExample,
- upload: openUploadModalExample,
- uploadPreview: openUploadPreviewExample,
- listSelect: () => formListSelectRef.value?.openSelectModal?.(),
- cropper: openCropperExample,
- queryTable: openQueryTableModal,
- transRecord: openTransRecordModal,
- callRecord: openCallRecordModal,
- errorLog: openErrorLogExample,
- lock: openLockModalExample,
- confirm: openConfirmExample,
- };
- actionMap[key]?.();
- };
- const handleFormSubmit = () => {
- formModalSubmitting.value = true;
- window.setTimeout(() => {
- formModalSubmitting.value = false;
- formModalOpen.value = false;
- createMessage.success('表单提交成功(mock)');
- }, 300);
- };
- const handleListSelectSelect = (values: Record<string, any>[]) => {
- selectedUsers.value = values || [];
- };
- const mockUploadParamsApi = async (): Promise<UploadParams> => ({
- maxFileSize: 20 * 1024 * 1024,
- imageAllowSuffixes: 'jpg,jpeg,png,gif,bmp,webp',
- mediaAllowSuffixes: 'mp3,mp4,wav,mov',
- fileAllowSuffixes: 'txt,pdf,doc,docx,xls,xlsx,ppt,pptx,zip,rar,7z',
- });
- const mockUploadApi = (params: any, onUploadProgress?: (event: any) => void) => {
- return new Promise<any>((resolve) => {
- onUploadProgress?.({ loaded: 100, total: 100 });
- window.setTimeout(() => {
- const fileName = params?.fileName || params?.file?.name || 'demo.txt';
- resolve({
- data: {
- fileUpload: {
- id: `mock-${Date.now()}`,
- fileName,
- fileType: String(fileName).split('.').pop() || 'txt',
- fileSort: 0,
- bizKey: String(params?.bizKey || 'ui-scheme-upload'),
- bizType: params?.bizType || 'uiSchemeDemo',
- bizKeyIsLike: '',
- fileEntity: {},
- fileUrl: '',
- },
- },
- });
- }, 240);
- });
- };
- const handleBasicUploadChange = (_value: any, fileList?: Array<Record<string, any>>) => {
- uploadedFiles.value = (fileList || []).map((item) => item?.fileName || '-');
- };
- const handleUploadModalChange = (records: any[]) => {
- uploadedFiles.value = (records || []).map((item) => item?.fileName || '-');
- if (uploadedFiles.value.length) {
- createMessage.success(`已模拟上传 ${uploadedFiles.value.length} 个文件`);
- }
- };
- const handleQueryTableSearch = () => {
- queryTablePagination.page = 1;
- loadQueryTableData();
- };
- const handleQueryTableReset = () => {
- queryTableFilters.personName = '';
- queryTableFilters.otherName = '';
- queryTableFilters.scene = '';
- queryTableFilters.riskLevel = '';
- queryTableFilters.startDate = '';
- queryTableFilters.endDate = '';
- queryTableFilters.orderKey = '';
- queryTableFilters.sort = '';
- queryTablePagination.page = 1;
- loadQueryTableData();
- };
- const handleQueryTablePrevPage = () => {
- if (queryTablePagination.page <= 1) return;
- queryTablePagination.page -= 1;
- loadQueryTableData();
- };
- const handleQueryTableNextPage = () => {
- if (queryTablePagination.page >= queryTableTotalPages.value) return;
- queryTablePagination.page += 1;
- loadQueryTableData();
- };
- const handleQueryTablePageChange = (page: number, pageSize: number) => {
- if (pageSize !== queryTablePagination.limit) {
- handleQueryTablePageSizeChange(pageSize);
- return;
- }
- if (page === queryTablePagination.page - 1) {
- handleQueryTablePrevPage();
- return;
- }
- if (page === queryTablePagination.page + 1) {
- handleQueryTableNextPage();
- return;
- }
- if (page === queryTablePagination.page) return;
- queryTablePagination.page = page;
- loadQueryTableData();
- };
- const handleQueryTablePageSizeChange = (size: number) => {
- queryTablePagination.limit = Number(size);
- queryTablePagination.page = 1;
- loadQueryTableData();
- };
- const handleQueryTableSortChange = (payload: { orderKey: string; sort: 'ASC' | 'DESC' }) => {
- queryTableFilters.orderKey = payload.orderKey;
- queryTableFilters.sort = payload.sort;
- queryTablePagination.page = 1;
- loadQueryTableData();
- };
- onMounted(() => {
- applyFilters();
- loadQueryTableData();
- });
- </script>
- <style lang="less">
- @prefix-cls: ~'jeesite-ui-scheme';
- .@{prefix-cls}.ui-scheme-page {
- background: #fff;
- padding: 12px 16px 24px;
- min-height: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- box-sizing: border-box;
- overflow: hidden;
- .page-header {
- margin-bottom: 8px;
- }
- .page-title {
- font-size: 16px;
- font-weight: 600;
- color: #111827;
- padding-bottom: 8px;
- border-bottom: 1px solid #e5e7eb;
- }
- .page-subtitle {
- margin-top: 10px;
- color: #6b7280;
- font-size: 12px;
- line-height: 20px;
- }
- .page-content {
- flex: 1 1 auto;
- min-height: 0;
- display: flex;
- flex-direction: column;
- gap: 12px;
- overflow: auto;
- padding-right: 2px;
- }
- .section-tabs {
- flex: 1 1 auto;
- min-height: 0;
- :deep(.ant-tabs-content-holder) {
- min-height: 0;
- }
- :deep(.ant-tabs-content) {
- height: 100%;
- }
- :deep(.ant-tabs-tabpane) {
- height: 100%;
- }
- }
- .tab-pane {
- display: flex;
- flex-direction: column;
- gap: 12px;
- min-height: 0;
- }
- .filter-bar {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12px;
- padding: 12px 16px;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- background: #fafafa;
- }
- .filter-item {
- display: flex;
- align-items: center;
- gap: 8px;
- }
- .filter-label {
- color: #6b7280;
- white-space: nowrap;
- font-size: 12px;
- }
- .filter-input {
- width: 160px;
- }
- .filter-input-wide {
- width: 220px;
- }
- .filter-date {
- width: 320px;
- }
- .filter-actions {
- margin-left: auto;
- display: flex;
- gap: 8px;
- }
- .stats-grid {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 12px;
- }
- .modal-gallery {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
- gap: 12px;
- }
- .modal-item {
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 16px;
- border: 1px solid #e5e7eb;
- border-radius: 8px;
- background: #fafafa;
- }
- .modal-item__title {
- font-size: 14px;
- font-weight: 600;
- color: #111827;
- }
- .modal-item__desc {
- min-height: 44px;
- color: #6b7280;
- font-size: 12px;
- line-height: 20px;
- }
- .modal-item__meta {
- display: flex;
- gap: 8px;
- font-size: 12px;
- line-height: 20px;
- }
- .modal-item__meta-label {
- color: #6b7280;
- flex: 0 0 56px;
- }
- .modal-item__meta-value {
- color: #111827;
- flex: 1 1 auto;
- min-width: 0;
- }
- .stat-card {
- :deep(.ant-card-body) {
- padding: 16px;
- }
- }
- .main-grid {
- display: grid;
- grid-template-columns: minmax(0, 1.6fr) minmax(360px, 1fr);
- gap: 12px;
- min-height: 460px;
- }
- .table-card,
- .detail-card {
- height: 460px;
- :deep(.ant-card-body) {
- height: calc(100% - 57px);
- display: flex;
- flex-direction: column;
- min-height: 0;
- }
- }
- .table-panel {
- flex: 1 1 auto;
- min-height: 0;
- overflow: hidden;
- }
- .detail-panel {
- height: 100%;
- display: flex;
- flex-direction: column;
- gap: 12px;
- min-height: 0;
- }
- .tag-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- }
- .detail-summary,
- .detail-modal .detail-summary {
- padding: 12px 14px;
- border-radius: 6px;
- background: #fafafa;
- color: #4b5563;
- line-height: 22px;
- }
- .detail-actions,
- .modal-actions {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
- }
- .detail-actions {
- margin-top: auto;
- }
- .chart-grid {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
- }
- .form-grid {
- display: grid;
- grid-template-columns: minmax(0, 1.2fr) minmax(320px, 0.8fr);
- gap: 12px;
- }
- .form-actions {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
- }
- .chart-box {
- height: 280px;
- }
- .detail-modal {
- display: flex;
- flex-direction: column;
- gap: 12px;
- }
- .form-modal-body,
- .drawer-panel {
- display: flex;
- flex-direction: column;
- gap: 12px;
- padding: 16px;
- }
- .mock-form {
- .form-date {
- width: 100%;
- }
- :deep(.ant-form-item) {
- margin-bottom: 14px;
- }
- }
- .drawer-summary {
- padding: 12px 14px;
- border-radius: 6px;
- background: #fafafa;
- color: #4b5563;
- line-height: 22px;
- }
- @media screen and (max-width: 1360px) {
- .stats-grid,
- .modal-gallery,
- .chart-grid,
- .form-grid,
- .main-grid {
- grid-template-columns: 1fr;
- }
- .table-card,
- .detail-card {
- height: auto;
- min-height: 400px;
- }
- }
- @media screen and (max-width: 992px) {
- .filter-actions {
- width: 100%;
- margin-left: 0;
- }
- .filter-input,
- .filter-input-wide,
- .filter-date {
- width: 100%;
- }
- }
- }
- .ui-scheme-query-table-modal {
- .query-table-wrap {
- gap: 14px;
- }
- .query-table-search {
- padding: 14px 16px;
- border: 1px solid #e2e8f0;
- border-radius: 12px;
- background: linear-gradient(180deg, #f8fafc 0%, #ffffff 100%);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8);
- .label {
- color: #475569;
- font-size: 12px;
- font-weight: 600;
- }
- .ant-input,
- .ant-input-affix-wrapper,
- .ant-picker,
- .ant-select-selector {
- border-radius: 8px !important;
- }
- .ant-btn {
- height: 32px;
- padding: 0 16px;
- border-radius: 8px;
- }
- }
- .ant-modal-body > .scroll-container,
- .ant-modal-body > .scroll-container > .scrollbar__wrap,
- .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view,
- .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view > div {
- height: 100%;
- min-height: 0;
- }
- .ant-modal-body > .scroll-container > .scrollbar__wrap {
- overflow: hidden;
- }
- .ant-modal-body > .scroll-container > .scrollbar__wrap > .scrollbar__view {
- margin: 0 !important;
- }
- .ant-modal-body > .scroll-container > .scrollbar__bar.is-vertical,
- .ant-modal-body > .scroll-container > .is-vertical {
- display: none !important;
- opacity: 0 !important;
- pointer-events: none;
- }
- }
- @import '../styles/trans-analysis-table-ui.less';
- </style>
|