vue3 代码拆分最佳实践?

老项目遗留了一些代码,VUE页面里面的代码太长了
想把TS代码拆分出来,请问最佳实践是什么样子的?
旧代码:

<template>
    <div class="table-tree-container">
        <div class="list-tree-wrapper">
            <div class="list-tree-operator">
                <t-input v-model="filterText" placeholder="请输入过滤关键词" @change="onTreeInput">
                    <template #suffix-icon>
                        <search-icon size="var(--td-comp-size-xxxs)" />
                    </template>
                </t-input>
                <t-button block @click="onCleanActive" variant="dashed" size="small" class="mt-2">
                    <template #icon>
                        <i class="ri-close-circle-line mr-1"></i>
                    </template>
                    清空选中项
                </t-button>
                <t-tree class="mt-2" :data="categoryTree" activable v-model:actived="treeActived" @active="onTreeActive" :filter="filterByText" expand-all :keys="{ value: 'id', label: 'cate_name' }" hover expand-on-click-node />
            </div>
            <div class="list-tree-content">
                <div class="index-container">
                    <t-card :bordered="false" :title="cardTitle" class="list-card-container">
                        <template #actions>
                            <t-button @click="onAdd" v-permission="'article/add'">
                                <template #icon>
                                    <i class="ri-add-line mr-1"></i>
                                </template>
                                添加
                            </t-button>
                        </template>
                        <t-space>
                            <t-select :options="REC_OPTIONS" v-model="searchForm.rec" clearable placeholder="请选择推荐位" />
                            <t-select :options="STATUS_OPTIONS" v-model="searchForm.status" clearable placeholder="请选择状态" />
                            <t-input v-model="searchForm.kw" placeholder="请输入你需要搜索的内容" clearable />
                            <t-button @click="onSearch" theme="default">
                                <template #icon>
                                    <i class="ri-search-line mr-1"></i>
                                </template>
                                搜索
                            </t-button>
                            <t-dropdown :options="DROPDOWN_OPTIONS" :max-column-width="200">
                                <t-button variant="outline" theme="success"> 选中项 <i class="ri-arrow-down-s-line ml-1 icon-valign-top"></i> </t-button>
                            </t-dropdown>
                        </t-space>
                        <t-row :gutter="16" class="table-container">
                            <t-col>
                                <t-table :data="data" stripe :columns="TABLE_COLUMNS" :vertical-align="'top'" :hover="true" :pagination="pagination" @select-change="onSelectChange" @page-change="onPageChange" :loading="dataLoading" :row-key="'id'">
                                    <template #id="{ row }">
                                        <var>{{ row['id'] }}</var>
                                    </template>
                                    <template #status="{ row }">
                                        <t-tag v-if="Number(row['status']) === 1" theme="success" variant="light"> 公开</t-tag>
                                        <t-tag v-else-if="Number(row['status']) === 0" theme="danger" variant="light"> 锁定</t-tag>
                                        <t-tag v-else theme="primary" variant="light"> 定时发布</t-tag>
                                    </template>
                                    <template #sort_by="{ row }">
                                        <t-input-number v-permission="'article/sort'" v-model="row.sort_by" @change="(v) => onSortChange(row, v)" theme="column"></t-input-number>
                                    </template>
                                    <template #created_at="{ row }">
                                        {{ formatTime(row['created_at']) }}
                                    </template>
                                    <template #cid="{ row }">
                                        {{ formatCate(row['cid']) }}
                                    </template>
                                    <template #author="{ row }">
                                        {{ row['author'] }}
                                    </template>
                                    <template #op="{ row }">
                                        <a class="t-button-link" v-permission="'article/edit'" @click="onEdit(row)">编辑</a>
                                    </template>
                                </t-table>
                            </t-col>
                        </t-row>
                    </t-card>
                </div>
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue';
import { MessagePlugin, DialogPlugin } from 'tdesign-vue-next';
import { STATUS_OPTIONS, REC_OPTIONS } from '@/config/global';
import helper from '@/utils/helper';
import { getArticleList, updateArticleSort, updateArticleStatus, deleteArticle } from '@/api/article';
import { IApiResponse } from '@/types';
import { TABLE_COLUMNS } from '@/pages/article/constants';
import { useRouter } from 'vue-router';
import { getArticleStore } from '@/store';

const router = useRouter();
const cardTitle = ref(helper.getPageTitle());
const cateId = ref(0);
const categoryTree = ref([]);

const articleStore = getArticleStore();
const fetchCategoryTree = async () => {
    const { treeList } = articleStore;
    if (helper.len(treeList) === 0) {
        try {
            await articleStore.getTreeCategory();
            categoryTree.value = articleStore.treeList;
        } catch (e) {
            return Promise.reject(e);
        }
    } else {
        categoryTree.value = treeList;
    }
};
const formatCate = (v) => {
    if (helper.len(categoryTree.value) <= 0) {
        return '';
    }
    let item = categoryTree.value.find((ele) => {
        if (Number(ele.id) === Number(v)) {
            return ele;
        }
        return null;
    });
    if (!item) {
        return v;
    }
    return item.cate_name;
};
const formatTime = (v) => {
    if (!v) {
        return '';
    }
    return helper.time().dateTimeDisplay(v);
};
const onAdd = () => {
    let url = '/article/add';
    if (cateId.value) {
        url += '?cate_id=' + cateId.value;
    }
    router.push(url);
};
const onEdit = (row) => {
    router.push('/article/edit?id=' + row.id);
};
const onSearch = () => {
    fetchData(searchForm.value);
};

const data = ref([]);
const dataLoading = ref(false);
const pagination = reactive({
    current: 1,
    pageSize: Number(import.meta.env.VITE_APP_PAGE_SIZE),
    total: 0,
    showJumper: true,
});
const searchForm = ref({
    kw: '',
    cid: null,
    rec: null,
    status: null,
    page: pagination.current,
    page_size: pagination.pageSize,
});
const onSortChange = async (row, value) => {
    try {
        helper.fullLoading();
        console.log(value, row);
        const resp: IApiResponse = await updateArticleSort({ id: row.id, value: value });
        if (resp.code === 0) {
            MessagePlugin.success('操作成功');
            await fetchData();
        } else {
            return Promise.reject(new Error(resp.message));
        }
    } catch (e) {
        MessagePlugin.error(e.message);
    } finally {
        helper.hideLoading();
    }
};
const onPageChange = async (pageInfo) => {
    const { current, pageSize } = pageInfo;
    pagination.current = current;
    pagination.pageSize = pageSize;
    await fetchData({ page: current, page_size: pageSize });
};
const onSelectChange = (value) => {
    selection.value = value;
};
const fetchData = async (params = null) => {
    dataLoading.value = true;
    try {
        let def = { ...searchForm.value };
        if (params) {
            def = Object.assign(def, params);
        }
        const res: IApiResponse = await getArticleList(def);
        if (res.code === 0) {
            const { data_list = [] } = res.data;
            data.value = data_list;
            pagination.total = res.data.total_count;
        } else {
            return Promise.reject(new Error(res.message));
        }
    } catch (e) {
        data.value = [];
        console.log(e);
        //MessagePlugin.error(e.message);
        pagination.total = 0;
    } finally {
        dataLoading.value = false;
    }
};
const onTreeActive = async (v, c) => {
    searchForm.value.cid = c.node.data.id;
    router.push({
        query: {
            cate_id: searchForm.value.cid,
        },
    });
    await fetchData();
};
const onCleanActive = async () => {
    router.push({
        query: {},
    });
    searchForm.value.cid = null;
    treeActived.value = [];
    await fetchData();
};
const treeActived = ref([]);
const filterByText = ref();
const filterText = ref();
const onTreeInput = () => {
    filterByText.value = (node) => {
        const rs = node.label.indexOf(filterText.value) >= 0;
        return rs;
    };
};
onMounted(async () => {
    cateId.value = helper.getQueryToNumber('cate_id', router);
    if (cateId.value) {
        searchForm.value.cid = cateId.value;
    }
    await fetchCategoryTree();
    await fetchData();
});
const selection = ref([]);
const DROPDOWN_OPTIONS = [
    {
        content: '删除选中内容',
        value: 2,
        onClick: () => {
            dropdownClick({ value: 2 });
        },
    },
    {
        content: '修改为公开',
        value: 1,
        onClick: () => {
            dropdownClick({ value: 1 });
        },
    },
    {
        content: '修改为隐藏',
        value: 0,
        onClick: () => {
            dropdownClick({ value: 0 });
        },
    },
];
const onStatus = async (params) => {
    try {
        helper.fullLoading();
        const resp: IApiResponse = await updateArticleStatus(params);
        if (resp.code === 0) {
            MessagePlugin.success('操作成功');
            await fetchData();
        } else {
            return Promise.reject(new Error(resp.message));
        }
    } catch (e) {
        MessagePlugin.error(e.message);
    } finally {
        helper.hideLoading();
    }
};
const onDelete = async (params) => {
    const dialogNode = DialogPlugin.confirm({
        header: '系统提示',
        body: '确定要删除吗?',
        theme: 'info',
        onConfirm: async () => {
            dialogNode.hide();
            await deleteAction(params);
        },
    });
};
const deleteAction = async (params) => {
    try {
        helper.fullLoading();
        const resp: IApiResponse = await deleteArticle(params);
        if (resp.code === 0) {
            MessagePlugin.success('操作成功');
            await fetchData();
        } else {
            return Promise.reject(new Error(resp.message));
        }
    } catch (e) {
        MessagePlugin.error(e.message);
    } finally {
        helper.hideLoading();
    }
};
const dropdownClick = (data) => {
    if (data.value === 1 && helper.len(selection.value) === 0) {
        MessagePlugin.warning('请选择要操作的数据');
        return false;
    }
    let params = { ids: selection.value.join(',') };
    if (data.value === 2) {
        onDelete(params);
    } else {
        params['status'] = data.value;
        onStatus(params);
    }
};
</script>

<style lang="less" scoped>
.table-tree-container {
    background-color: var(--td-bg-color-container);
    border-radius: var(--td-radius-medium);
    .t-tree {
        margin-top: var(--td-comp-margin-xxl);
    }
}
.list-tree-wrapper {
    overflow-y: hidden;
    background: #fff;
}
.list-tree-operator {
    width: 210px;
    float: left;
    padding: var(--td-comp-paddingTB-xxl) var(--td-comp-paddingLR-xxl);
}
.list-tree-content {
    border-left: 1px solid var(--td-border-level-1-color);
    overflow: auto;
}
</style>
阅读 3.1k
1 个回答

没仔细看你的代码,说说我的重构经验:

  1. 上下文不相关的函数,抽出来放到工具函数里。包括网络请求,都可以抽出来。
  2. 能够复用的功能,抽出来做成组件。组件的颗粒度以 方便理解 为准,细一点没关系。
  3. 样式我已经完全拥抱 TailwindCSS 了,所以几乎不存在 <style>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题