| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- <template>
- <div>
- <el-dialog
- v-model="dialog.visible.value"
- :title="dialog.title.value"
- width="1200px"
- append-to-body
- destroy-on-close
- >
- <div class="tax-code-container">
- <!-- 左侧树 -->
- <div class="tax-tree-panel">
- <el-tree
- ref="treeRef"
- :data="treeData"
- :props="treeProps"
- node-key="id"
- highlight-current
- :expand-on-click-node="false"
- @node-click="handleNodeClick"
- @node-dblclick="handleTreeDblClick"
- />
- </div>
- <!-- 右侧列表 -->
- <div class="tax-list-panel">
- <!-- 搜索框 -->
- <div class="search-bar">
- <el-input
- v-model="searchKeyword"
- placeholder="请输入名称或编码"
- prefix-icon="Search"
- clearable
- @keyup.enter="handleSearch"
- @clear="handleSearch"
- style="width: 280px"
- />
- <el-button icon="Search" @click="handleSearch" style="margin-left:8px" />
- </div>
- <!-- 表格 -->
- <el-table
- v-loading="loading"
- :data="listData"
- border
- highlight-current-row
- height="360px"
- :row-class-name="rowClassName"
- @row-dblclick="handleRowDblClick"
- >
- <el-table-column label="编码" align="center" prop="taxationNo" min-width="110">
- <template #default="{ row }">
- <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.taxationNo }}</el-link>
- </template>
- </el-table-column>
- <el-table-column label="合并编码" align="center" prop="mergeNo" min-width="130" show-overflow-tooltip>
- <template #default="{ row }">
- <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.mergeNo }}</el-link>
- </template>
- </el-table-column>
- <el-table-column label="名称" align="center" prop="name" min-width="120" show-overflow-tooltip>
- <template #default="{ row }">
- <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.name }}</el-link>
- </template>
- </el-table-column>
- <el-table-column label="简称" align="center" prop="abbreviation" min-width="100" show-overflow-tooltip>
- <template #default="{ row }">
- <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.abbreviation }}</el-link>
- </template>
- </el-table-column>
- <el-table-column label="说明" align="center" prop="remark" min-width="150" show-overflow-tooltip>
- <template #default="{ row }">
- <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ row.remark }}</el-link>
- </template>
- </el-table-column>
- <el-table-column label="税率" align="center" prop="explain" width="90">
- <template #default="{ row }">
- <el-link :type="isRowDisabled(row) ? 'info' : 'primary'" :disabled="isRowDisabled(row)" :underline="false">{{ formatTaxRate(row.taxrate) }}</el-link>
- </template>
- </el-table-column>
- </el-table>
- <!-- 分页 -->
- <pagination
- v-show="total > 0"
- :total="total"
- v-model:page="queryParams.pageNum"
- v-model:limit="queryParams.pageSize"
- @pagination="getList"
- />
- </div>
- </div>
- </el-dialog>
- </div>
- </template>
- <script setup lang="ts">
- import { listTaxCode, getTaxCode, getTaxCodeTree } from '@/api/system/taxCode';
- import { TaxCodeVO, TaxCodeQuery } from '@/api/system/taxCode/types';
- import useDialog from '@/hooks/useDialog';
- const emit = defineEmits<{
- (e: 'select', row: TaxCodeVO): void;
- }>();
- const { proxy } = getCurrentInstance() as ComponentInternalInstance;
- const dialog = useDialog({ title: '税收编码选择 请双击选择税收编码' });
- const treeRef = ref<ElTreeInstance>();
- const treeProps = { label: 'label', children: 'children' };
- const currentEchoId = ref<string | number | undefined>();
- const treeData = ref<any[]>([]);
- const listData = ref<TaxCodeVO[]>([]);
- const loading = ref(false);
- const total = ref(0);
- const searchKeyword = ref('');
- const queryParams = ref<TaxCodeQuery>({
- pageNum: 1,
- pageSize: 10,
- parentId: undefined,
- name: undefined,
- taxationNo: undefined,
- params: {}
- });
- /** 加载树形数据 */
- const loadTreeData = async () => {
- try {
- const res = await getTaxCodeTree();
- treeData.value = res.data ?? [];
- } catch (error) {
- console.error('加载税收编码树失败:', error);
- treeData.value = [];
- }
- };
- /** 等待节点出现在树中并展开 */
- const expandNodeById = (id: string | number): Promise<void> => {
- return new Promise(resolve => {
- const tryExpand = (retry = 0) => {
- const node = treeRef.value?.getNode(id);
- if (node) {
- node.expand(() => resolve(), false);
- } else if (retry < 30) {
- setTimeout(() => tryExpand(retry + 1), 100);
- } else {
- resolve();
- }
- };
- tryExpand();
- });
- };
- /** 根据 id 回显:展开祖先路径并高亮目标节点 */
- const echoById = async (id: string | number) => {
- try {
- const res = await getTaxCode(id);
- const data = res.data;
- // ancestors 格式如 "0,100001,100002",按逗号分割并过滤掉根节点 0
- const ancestors = (data.ancestors ?? '')
- .split(',')
- .filter((a: string) => a && a !== '0');
- for (const ancId of ancestors) {
- await expandNodeById(ancId);
- }
- await nextTick();
- treeRef.value?.setCurrentKey(id);
- } catch (e) {
- console.warn('taxCode echo failed', e);
- }
- };
- /** 查询右侧列表 */
- const getList = async () => {
- loading.value = true;
- try {
- const res = await listTaxCode(queryParams.value);
- listData.value = res.rows ?? [];
- total.value = res.total ?? 0;
- } finally {
- loading.value = false;
- }
- };
- /** 单击树节点 — 刷新右侧列表 */
- const handleNodeClick = (node: any) => {
- queryParams.value.parentId = node.id;
- queryParams.value.pageNum = 1;
- getList();
- };
- /** 双击树节点 — 叶子节点直接选中 */
- const handleTreeDblClick = (data: any, node: any) => {
- if (node.isLeaf) {
- emit('select', data as TaxCodeVO);
- dialog.closeDialog();
- }
- };
- /** 搜索 */
- const handleSearch = () => {
- const kw = searchKeyword.value?.trim();
- queryParams.value.name = kw || undefined;
- queryParams.value.taxationNo = undefined;
- queryParams.value.pageNum = 1;
- getList();
- };
- /** 格式化税率为百分制显示 */
- const formatTaxRate = (val: any): string => {
- if (val === null || val === undefined || val === '') return '';
- const num = Number(val);
- if (isNaN(num)) return String(val);
- return `${Math.round(num * 100)}%`;
- };
- /** 判断行是否禁用:非叶子节点(isBottom === 0)不可选 */
- const isRowDisabled = (row: TaxCodeVO): boolean => {
- return Number((row as any)?.isBottom) === 0;
- };
- /** 行样式类名:禁用行显示为灰色 */
- const rowClassName = ({ row }: { row: TaxCodeVO }): string => {
- return isRowDisabled(row) ? 'tax-code-row-disabled' : '';
- };
- /** 双击行选择 */
- const handleRowDblClick = (row: TaxCodeVO) => {
- if (isRowDisabled(row)) {
- proxy?.$modal.msgWarning('该节点不是叶子节点,不可选择');
- return;
- }
- emit('select', row);
- dialog.closeDialog();
- };
- /** 对外暴露打开方法,可传入 id 用于回显 */
- const open = (id?: string | number) => {
- currentEchoId.value = id;
- searchKeyword.value = '';
- queryParams.value = {
- pageNum: 1,
- pageSize: 10,
- parentId: undefined,
- name: undefined,
- taxationNo: undefined,
- params: {}
- };
- dialog.openDialog();
- };
- watch(
- () => dialog.visible.value,
- async (val) => {
- if (val) {
- await loadTreeData();
- await getList();
- if (currentEchoId.value) {
- await echoById(currentEchoId.value);
- }
- } else {
- listData.value = [];
- total.value = 0;
- }
- }
- );
- defineExpose({ open, close: dialog.closeDialog });
- </script>
- <style scoped>
- .tax-code-container {
- display: flex;
- gap: 16px;
- min-height: 460px;
- }
- .tax-tree-panel {
- width: 300px;
- flex-shrink: 0;
- border: 1px solid var(--el-border-color);
- border-radius: 4px;
- padding: 8px 0;
- overflow-y: auto;
- max-height: 480px;
- }
- .tax-list-panel {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 8px;
- min-width: 0;
- }
- .search-bar {
- display: flex;
- align-items: center;
- }
- :deep(.tax-code-row-disabled) {
- background-color: var(--el-fill-color-light) !important;
- color: var(--el-text-color-disabled);
- cursor: not-allowed;
- }
- :deep(.tax-code-row-disabled:hover > td.el-table__cell) {
- background-color: var(--el-fill-color-light) !important;
- }
- </style>
|