Frontend Development 16 min read

Full-Stack Frontend Implementation of a Publishing Platform: Pagination, CRUD, and Vue‑Element‑Plus Integration

This tutorial walks through the front‑end portion of a publishing platform series, demonstrating how to build a Vue 3 and Element‑plus UI with TailwindCSS, implement server‑side pagination via Koa, integrate Axios with proxy and interceptors, and add complete CRUD operations—including create, edit, and delete dialogs—while showcasing the essential code snippets.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Full-Stack Frontend Implementation of a Publishing Platform: Pagination, CRUD, and Vue‑Element‑Plus Integration

The article continues a series on building a full‑stack publishing platform, focusing on the front‑end implementation using Vue , Element‑plus , TailwindCSS and a Koa back‑end.

Core Points

Front‑end and back‑end data pagination display

Full‑stack CRUD functionality

1. Building the Static UI

Using Element‑plus components such as el-card , el-form , el-table and TailwindCSS classes, a static page layout is created. The following snippet shows the initial markup:

...

2. Back‑End Pagination API

The Koa route implements a paginated query. The key function getConfigList extracts query parameters, calls the service layer, and returns the result.

export async function getConfigList(ctx, next) {
  try {
    const { pageNo: page, pageSize, projectName } = ctx.request.query;
    const pageData = await services.findJobPage(page, pageSize, { projectName });
    ctx.state.apiResponse = { code: RESPONSE_CODE.SUC, data: pageData };
  } catch (e) {
    ctx.state.apiResponse = { code: RESPONSE_CODE.ERR, msg: '配置分页查询失败' };
  }
  next();
}

The service layer uses Mongoose to perform find , skip , limit , and count operations:

export async function findJobPage(page, pageSize, params) {
  Object.keys(params).forEach(key => {
    if (!params[key]) Reflect.deleteProperty(params, key);
  });
  const DocumentUserList = await JobModel.find(params)
    .skip((page - 1) * pageSize)
    .limit(pageSize);
  return DocumentUserList.map(_ => _.toObject());
}

export function countJob(params) {
  Object.keys(params).forEach(key => {
    if (!params[key]) Reflect.deleteProperty(params, key);
  });
  return JobModel.count(params);
}

3. Front‑End Integration

Axios is used to request the paginated data. A proxy is configured in vite.config to forward /api calls to the back‑end.

proxy: {
  '/api': {
    target: 'http://localhost:3200/', // 代理到后端地址
    changeOrigin: true,
    rewrite: path => {
      return path.replace(/^/api/, '');
    }
  }
}

An interceptor simplifies the response format:

axios.interceptors.response.use(function (response) {
  const data = response.data;
  if (data.code === 0) {
    return data.data; // 业务层直接拿到数据
  }
  data.message = data.message || data.msg;
  return Promise.reject(data);
});

The request function used in the component:

export async function getConfig(params) {
  return axios.get('/job', { params });
}

4. CRUD Implementation

Create : A button opens an el-dialog with a form. The submit handler onSubmit calls postSave and refreshes the table.

const formData = reactive({
  projectName: '',
  gitUrl: '',
  gitBranch: '',
  buildCommand: '',
  uploadPath: ''
});

const onSubmit = async () => {
  try {
    await postSave(formData);
    ElMessage.success('配置保存成功');
    await initData();
    dialogVisible.value = false;
  } catch (e) {
    ElMessage.error('配置保存失败');
  }
};

Edit : Clicking the edit button fills the form with the selected row and sets isEdit . The same onSubmit decides whether to call postUpdate or postSave .

const onEdit = rowData => {
  isEdit.value = true;
  dialogVisible.value = true;
  Object.keys(formData).forEach(key => {
    formData[key] = rowData[key];
  });
  formData.id = rowData._id;
};

const onSubmit = async () => {
  try {
    isEdit.value ? await postUpdate(formData) : await postSave(formData);
    ElMessage.success(isEdit.value ? '配置编辑成功' : '配置保存成功');
    await initData();
    dialogVisible.value = false;
  } catch (e) {
    ElMessage.error(isEdit.value ? '配置编辑失败' : '配置保存失败');
  }
};

Delete : An el-popconfirm wraps a delete button. The handler onDel calls postDelete with the record id.

const onDel = async rowData => {
  try {
    await postDelete({ id: rowData._id });
    ElMessage.success('配置删除成功');
    await initData();
  } catch (e) {
    ElMessage.error('配置删除失败');
  }
};

Conclusion

The article completes the front‑end pagination display and full CRUD cycle for the publishing platform. The next installment will cover triggering builds from the front‑end and streaming Jenkins logs via WebSocket.

frontendVuepaginationCRUDKoafull-stackElement-Plus
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.