二、LLMOPs前端搭建关联聊天机器人API

1. ArcoDesign与TailwindCSS简化UI界面

typescript 复制代码
安装:
# npm
npm install --save-dev @arco-design/web-vue
# yarn
yarn add --dev @arco-design/web-vue
# 在 main.js 中完整引入
import { createApp } from 'vue'
import ArcoVue from '@arco-design/web-vue'
import App from './App.vue';
import '@arco-design/web-vue/dist/arco.css'
const app = createApp(App)
app.use(ArcoVue)
app.mount('#app')
在组件中即可使用 <a-xxx></a-xxx> 的方式来使用 ArcoDesign 设计好的组件,例如
<template>
  <a-space>
    <a-button type="primary">Primary</a-button>
    <a-button>Secondary</a-button>
    <a-button type="dashed">Dashed</a-button>
    <a-button type="outline">Outline</a-button>
    <a-button type="text">Text</a-button>
  </a-space>
</template>

views---AboutView

xml 复制代码
<template>
  <div class="about">
    <h1>This is an about page</h1>
    <h1 class="text-3xl font-bold underline text-blue-700">Hello world!</h1>
    <p>程序员的梦工厂</p>
    <a-button type="primary">注册登录</a-button>
    <a-avatar :style="{ backgroundColor: '#3370ff' }">
      <icon-user />
    </a-avatar>
  </div>
</template>

<style>
@media (min-width: 1024px) {
  .about {
    min-height: 100vh;
    display: flex;
    align-items: center;
  }
}
</style>

TailwindCSS 安装

csharp 复制代码
yarn add tailwindcss@3 postcss autoprefixer -D
npx tailwindcss init -p

tailwind.config.js

css 复制代码
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};

@/assets/styles/main.css

less 复制代码
@tailwind base;
@tailwind components;
@tailwind utilities;

2. 项目页面模板与路由配置,实现路由守卫功能

javascript 复制代码
import { createRouter, createWebHistory } from 'vue-router'
import { isLogin } from '@/utils/auth'
import DefaultLayout from '@/views/layouts/DefaultLayout.vue'
import BlankLayout from '@/views/layouts/BlankLayout.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      component: DefaultLayout,
      children: [
        {
          path: '',
          redirect: 'home',
        },
        {
          path: 'home',
          name: 'pages-home',
          component: () => import('@/views/pages/HomeView.vue'),
        },
        {
          path: 'space/apps',
          name: 'space-apps-list',
          component: () => import('@/views/space/apps/ListView.vue'),
        },
        {
          path: 'space/apps/:app_id',
          name: 'space-apps-detail',
          component: () => import('@/views/space/apps/DetailView.vue'),
        },
      ],
    },
    {
      path: '/',
      component: BlankLayout,
      children: [
        {
          path: 'auth/login',
          name: 'auth-login',
          component: () => import('@/views/auth/LoginView.vue'),
        },
      ],
    },
  ],
})

// todo:路由守卫逻辑还未实现
router.beforeEach(async (to, from) => {
  if (!isLogin() && to.name != 'auth-login') {
    return { path: '/auth/login' }
  }
})
export default router

3. Pinia实现多页面共享数据状态

src---views---HomeView

xml 复制代码
<script setup lang="ts">
import { useAccountStore } from '@/stores/account'

const accountStore = useAccountStore()

function updateName() {
  accountStore.update({ name: '小小课' })
}
</script>

<template>
  <p>这是主页</p>
  <p>当前登录的账号是:{{ accountStore.account.name }}</p>
  <p>当前登录的邮箱是:{{ accountStore.account.email }}</p>
  <a-button type="primary" @click="updateName">更新名字</a-button>
  <a-button @click="accountStore.clear">重置</a-button>
  <router-link to="/space/apps">跳转到应用列表</router-link>
</template>

<style scoped></style>

src---stores---account

javascript 复制代码
import { defineStore } from 'pinia'
import { ref } from 'vue'

// 初始值
const initAccount = {
  name: '慕小课',
  email: 'imooc@163.com',
  avatar: '',
}

export const useAccountStore = defineStore('account', () => {
  // 1.定义数据
  const account = ref({ ...initAccount })

  // 2.函数/动作
  function update(params: any) {
    Object.assign(account.value, params)
  }

  function clear() {
    account.value = { ...initAccount }
  }

  return { account, update, clear }
})

4. 前端接口请求Fetch方法封装

src---utils---request.ts

typescript 复制代码
import { apiPrefix } from '@/config'

// 1.超时时间为100s
const TIME_OUT = 100000

// 2.基础的配置
const baseFetchOptions = {
  method: 'GET',
  mode: 'cors',
  credentials: 'include',
  headers: new Headers({
    'Content-Type': 'application/json',
  }),
  redirect: 'follow',
}

// 3.fetch参数类型
type FetchOptionType = Omit<RequestInit, 'body'> & {
  params?: Record<string, any>
  body?: BodyInit | Record<string, any> | null
}

// 4.封装基础的fetch请求
const baseFetch = <T>(url: string, fetchOptions: FetchOptionType): Promise<T> => {
  // 5.将所有的配置信息合并起来
  const options: typeof baseFetchOptions & FetchOptionType = Object.assign(
    {},
    baseFetchOptions,
    fetchOptions,
  )

  // 6.组装url
  let urlWithPrefix = `${apiPrefix}${url.startsWith('/') ? url : `/${url}`}`

  // 7.解构出对应的请求方法、params、body参数
  const { method, params, body } = options

  // 8.如果请求是GET方法,并且传递了params参数
  if (method === 'GET' && params) {
    const paramsArray: string[] = []
    Object.keys(params).forEach((key) => {
      paramsArray.push(`${key}=${encodeURIComponent(params[key])}`)
    })
    if (urlWithPrefix.search(/\?/) === -1) {
      urlWithPrefix += `?${paramsArray.join('&')}`
    } else {
      urlWithPrefix += `&${paramsArray.join('&')}`
    }

    delete options.params
  }

  // 9.处理post传递的数据
  if (body) {
    options.body = JSON.stringify(body)
  }

  // 10.同时发起两个Promise(或者是说两个操作,看谁先返回,就先结束)
  return Promise.race([
    // 11.使用定时器来检测是否超时
    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('接口已超时')
      }, TIME_OUT)
    }),
    // 12.发起一个正常请求
    new Promise((resolve, reject) => {
      globalThis
        .fetch(urlWithPrefix, options as RequestInit)
        .then((res) => {
          resolve(res.json())
        })
        .catch((err) => {
          reject(err)
        })
    }),
  ]) as Promise<T>
}

export const request = <T>(url: string, options = {}) => {
  return baseFetch<T>(url, options)
}

export const get = <T>(url: string, options = {}) => {
  return request<T>(url, Object.assign({}, options, { method: 'GET' }))
}

export const post = <T>(url: string, options = {}) => {
  return request<T>(url, Object.assign({}, options, { method: 'POST' }))
}

5. 解决前后端分离接口跨域问题

vite.config.ts

javascript 复制代码
export default defineConfig({

  server: {

    proxy: {

      '/api': {

        target: 'http://localhost:5000',

        changeOrigin: true,

        rewrite(path) {

          return path.replace(/^\/api/, '')

        },

      },

    },

  },

})

internal---server---http.py

python 复制代码
CORS(self, resources={
    r"/*": {
        "origins": "*",
        "supports_credentials": True,
        # "methods": ["GET", "POST"],
        # "allow_headers": ["Content-Type"],
    }
})

6. 应用编排页面结构与样式设计

相关推荐
daguanren2 小时前
LMRing 实测榜:GPT-5.4 登顶?Claude 4.6 还能打吗?
github·aigc
trashwbin3 小时前
Agent 帮不了你,不是因为它不够聪明
aigc
树獭叔叔3 小时前
内存价格被Google打下来了?: TurboQuant对KVCache的量化
算法·aigc·openai
qq_454245033 小时前
时空尺度与物理公式的统一:从固体与流体的互变到跨尺度换算
aigc
code小生5 小时前
OpenClaw 多智能体配置不同的文生图模型
aigc
DO_Community5 小时前
如何使用DigitalOcean Gradient 平台上的无服务器推理
人工智能·aigc·ai编程·ai推理
大灰狼来喽7 小时前
OpenClaw 自动化工作流实战:用 Hooks + 定时任务 + Multi-MCP 构建“数字员工“
大数据·运维·人工智能·自动化·aigc·ai编程
NikoAI编程7 小时前
从 Claude Code 到 Agent 工程:两篇万字长文里的架构共识
aigc·ai编程·claude
sin°θ_陈7 小时前
前馈式3D Gaussian Splatting 研究地图(路线一):像素对齐高斯的起点——pixelSplat 与 latentSplat 在解决什么
python·深度学习·3d·aigc·webgl·3dgs·空间智能
AI_Ming7 小时前
程序员转行学习 AI 大模型: 第一次如何调用大模型API | 附完整可运行代码
aigc·openai·ai编程