当前位置: 首页 > wzjs >正文

建设官方网站e路护航成都最新疫情

建设官方网站e路护航,成都最新疫情,哈尔滨座做网站的,动画网站模板官网 https://www.electronforge.io/ 技术栈:Vue3.5Electron 本期最终效果预览 创建并启动项目 配置国内下载源 打开用户目录 C:\Users\60309 (60309 改成自己电脑的用户名)打开 .npmrc 文件添加国内下载源 registryhttps://registry.npmm…

官网 https://www.electronforge.io/

技术栈:Vue3.5+Electron

本期最终效果预览

在这里插入图片描述

创建并启动项目

配置国内下载源

  • 打开用户目录 C:\Users\60309 (60309 改成自己电脑的用户名)
  • 打开 .npmrc 文件
  • 添加国内下载源
registry=https://registry.npmmirror.com/
ELECTRON_MIRROR=https://npmmirror.com/mirrors/electron/

在目标目录(如 E:\编程\electron)下创建项目

npm init electron-app@latest electron-vue3-AIchat -- --template=vite-typescript

electron-vue3-AIchat 为自定义的项目名称

在这里插入图片描述
打开空值校验,在 tsconfig.json 中添加

"strictNullChecks": true

用 vscode 打开,并运行项目

在这里插入图片描述
得到

在这里插入图片描述

集成必要的依赖

集成 vue3

npm install vue
npm install --save-dev @vitejs/plugin-vue
  • vite.renderer.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";export default defineConfig({plugins: [vue()],
});
  • 新建 src\App.vue
<template><div class="bg-amber-500">vue3</div>
</template><script setup></script>
  • src\renderer.ts
import { createApp } from "vue";
import App from "./App.vue";import "./index.css";createApp(App).mount("#app");
  • index.html
<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>AI聊天</title></head><body><div id="app"></div><script type="module" src="/src/renderer.ts"></script></body>
</html>

重启项目,效果如下

在这里插入图片描述

集成 tailwindcss

npm install tailwindcss @tailwindcss/vite

vite.renderer.config.ts 改名为 vite.renderer.config.mts ,内容修改为

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({plugins: [vue(), tailwindcss()],
});

forge.config.ts 中 ,将 vite.renderer.config.ts 改名为 vite.renderer.config.mts

src\index.css 中添加

@import "tailwindcss";

vscode 安装插件

Tailwind CSS IntelliSense

在这里插入图片描述

重启项目,效果如下

在这里插入图片描述

集成 iconify

npm install --save-dev @iconify/vue

src\App.vue 改为

<template><div class="bg-amber-500">vue3</div><Icon icon="mdi-light:home" />
</template><script setup>
import { Icon } from "@iconify/vue";
</script>

效果如下

在这里插入图片描述

集成 reka-ui

官网 https://www.radix-vue.com/

npm add radix-vue

集成 vue-router4

npm install vue-router@4

src\renderer.ts 改为

import { createApp } from "vue";
import App from "./App.vue";
import "./index.css";// 因没有地址栏,此处使用 createMemoryHistory 模式
import { createRouter, createMemoryHistory } from "vue-router";
import Home from "./views/Home.vue";
import Conversation from "./views/Conversation.vue";
import Settings from "./views/Settings.vue";const routes = [{ path: "/", component: Home },{ path: "/conversation/:id", component: Conversation },{ path: "/settings", component: Settings },
];
const router = createRouter({history: createMemoryHistory(),routes,
});const app = createApp(App);
app.use(router);
app.mount("#app");

src\App.vue 改为

<template><div class="flex items-center justify-between h-screen"><div class="w-[300px] bg-gray-200 h-full border-r border-gray-300"><div class="h-[90%] overflow-y-auto"></div><div class="h-[10%] grid grid-cols-2 gap-2 p-2"><RouterLink to="/"><Button icon-name="radix-icons:chat-bubble" class="w-full">新建聊天</Button></RouterLink><RouterLink to="/settings"><Button icon-name="radix-icons:gear" plain class="w-full">应用配置</Button></RouterLink></div></div><div class="h-full flex-1"><RouterView /></div></div>
</template><script setup>
import { Icon } from "@iconify/vue";
import Button from "./components/Button.vue";
</script>

新建 src\components\Button.vue

<template><buttonclass="vk-button shadow-sm inline-flex items-center justify-center disabled:opacity-50 disabled:pointer-events-none":class="[colorClasses, sizeClasses]":disabled="disabled || loading"><Icon :icon="iconWithLoading" class="mr-2" v-if="iconWithLoading" /><slot></slot></button>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { Icon } from "@iconify/vue";export type ButtonColor = "green" | "purple";
export type ButtonSize = "large" | "small";export interface ButtonProps {color?: ButtonColor;size?: ButtonSize;plain?: boolean;disabled?: boolean;loading?: boolean;iconName?: string;
}defineOptions({name: "VkButton",
});const props = withDefaults(defineProps<ButtonProps>(), {color: "green",
});
const colorVariants: Record<ButtonColor, any> = {green: {plain:"bg-green-50 text-green-700 hover:bg-green-700 border border-green-700 hover:text-white",normal:"bg-green-700 text-white hover:bg-green-700/90 border border-green-700",},purple: {plain:"bg-purple-50 text-purple-700 hover:bg-purple-700 border border-purple-700 hover:text-white",normal:"bg-purple-700 text-white hover:bg-purple-700/90 border border-purple-700",},
};
const iconWithLoading = computed(() => {if (props.loading) {return "line-md:loading-loop";} else {return props.iconName;}
});
const colorClasses = computed(() => {if (props.plain) {return colorVariants[props.color].plain;} else {return colorVariants[props.color].normal;}
});
const sizeClasses = computed(() => {if (!props.size) {return "h-[32px] py-[8px] px-[15px] text-sm rounded-[4px]";} else {if (props.size === "large") {return "h-[40px] py-[12px] px-[19px] rounded-[4px] text-base";} else {return "h-[24px] py-[11px] px-[5px] rounded-[3px] text-xs";}}
});
</script>

新建 src\views\Conversation.vue 内容为 对话
新建 src\views\Home.vue 内容为 首页
新建 src\views\Settings.vue 内容为 设置

<script lang="ts" setup></script><template><div>设置</div>
</template><style scoped></style>

重启项目,效果如下

在这里插入图片描述
点击按钮应用设置(可见右侧内容发生了路由切换)

在这里插入图片描述

前置准备

类型定义

src\types.ts

export interface ConversationProps {id: number;title: string;selectedModel: string;createdAt: string;updatedAt: string;providerId: number;
}
export interface ProviderProps {id: number;name: string;title?: string;desc?: string;avatar?: string;createdAt: string;updatedAt: string;models: string[];
}
export type MessageStatus = "loading" | "streaming" | "finished" | "error";export interface MessageProps {id: number;content: string;type: "question" | "answer";conversationId: number;status?: MessageStatus;createdAt: string;updatedAt: string;imagePath?: string;
}

测试数据

src\testData.ts

import { MessageProps, ConversationProps } from "./types";
export const messages: MessageProps[] = [{id: 1,content: "什么是光合作用",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "question",conversationId: 1,},{id: 2,content: "你的说法很请正确,理解的很不错,你的说法很请正确,理解的很不错",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "answer",conversationId: 1,},{id: 3,content: "还有更多的信息吗",createdAt: "2024-07-03",type: "question",updatedAt: "2024-07-03",conversationId: 1,},{id: 4,content: "",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "answer",status: "loading",conversationId: 1,},{id: 7,content: "2 什么是光合作用",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "question",conversationId: 2,},{id: 8,content: "你的说法很请正确",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "answer",conversationId: 2,},{id: 9,content: "请告诉我更多",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "question",conversationId: 2,},{id: 10,content: "你的说法很请正确,理解的很不错,你的说法很请正确,理解的很不错",createdAt: "2024-07-03",updatedAt: "2024-07-03",type: "answer",conversationId: 2,},
];
export const conversations: ConversationProps[] = [{id: 1,selectedModel: "GPT-3.5-Turbo",title: "1 什么是光合作用",createdAt: "2024-07-03",updatedAt: "2024-07-03",providerId: 1,},{id: 2,selectedModel: "GPT-3.5-Turbo",title: "2 什么是光合作用",createdAt: "2024-07-03",updatedAt: "2024-07-03",providerId: 1,},
];export const providers: ProviderProps[] = [{id: 1,name: "qianfan",title: "百度千帆",desc: "文心一言 百度出品的大模型",models: ["ERNIE-4.0-8K", "ERNIE-3.5-8K", "ERNIE-Speed-128K"],avatar:"https://aip-static.cdn.bcebos.com/landing/product/ernie-bote321e5.png",createdAt: "2024-07-03",updatedAt: "2024-07-03",},{id: 2,name: "dashscope",title: "阿里灵积",desc: "通义千问",// https://help.aliyun.com/zh/dashscope/developer-reference/api-details?spm=a2c4g.11186623.0.0.5bf41507xgULX5#b148acc634pfcmodels: ["qwen-turbo", "qwen-plus", "qwen-max", "qwen-vl-plus"],avatar:"https://qph.cf2.poecdn.net/main-thumb-pb-4160791-200-qlqunomdvkyitpedtghnhsgjlutapgfl.jpeg",createdAt: "2024-07-03",updatedAt: "2024-07-03",},{id: 3,name: "deepseek",title: "DeepSeek",desc: "DeepSeek",// https://api-docs.deepseek.com/zh-cn/models: ["deepseek-chat"],avatar:"https://qph.cf2.poecdn.net/main-thumb-pb-4981273-200-phhqenmywlkiybehuaqvsxpfekviajex.jpeg",createdAt: "2024-12-27",updatedAt: "2024-12-27",},
];

核心组件封装

AI模型选择 ConversationList.vue

在这里插入图片描述
在这里插入图片描述
src\components\ProviderSelect.vue

<template><div class="provider-select w-full"><SelectRoot v-model="currentModel"><SelectTriggerclass="flex w-full items-center justify-between rounded-md py-1.5 px-3 shadow-sm border outline-none data-[placeholder]:text-gray-400"><SelectValue placeholder="请选择AI模型" /><Icon icon="radix-icons:chevron-down" class="h-5 w-5" /></SelectTrigger><SelectPortal><SelectContent class="bg-white rounded-md shadow-md z-[100] border"><SelectViewport class="p-2"><div v-for="provider in items"><SelectLabel class="flex items-center px-6 h-7 text-gray-500"><img:src="provider.avatar":alt="provider.name"class="h-5 w-5 mr-2 rounded"/>{{ provider.title }}</SelectLabel><SelectGroup><SelectItemv-for="(model, index) in provider.models":key="index":value="`${provider.id}/${model}`"class="outline-none rounded flex items-center h-7 px-6 relative text-green-700 cursor-pointer data-[highlighted]:bg-green-700 data-[highlighted]:text-white"><SelectItemIndicator class="absolute left-2 w-6"><Icon icon="radix-icons:check" /></SelectItemIndicator><SelectItemText>{{ model }}</SelectItemText></SelectItem></SelectGroup><SelectSeparator class="h-[1px] my-2 bg-gray-300" /></div></SelectViewport></SelectContent></SelectPortal></SelectRoot></div>
</template><script lang="ts" setup>
import {SelectContent,SelectGroup,SelectItem,SelectItemIndicator,SelectItemText,SelectLabel,SelectPortal,SelectRoot,SelectSeparator,SelectTrigger,SelectValue,SelectViewport,
} from "radix-vue";
import { Icon } from "@iconify/vue";import { ProviderProps } from "../types";defineProps<{ items: ProviderProps[] }>();
const currentModel = defineModel<string>();
</script>

会话列表 ConversationList.vue

在这里插入图片描述

src\components\ConversationList.vue

<template><div class="conversation-list"><divclass="item border-gray-300 border-t cursor-pointer p-2":class="{'bg-gray-100 hover:bg-gray-300': selectedId === item.id,'bg-white hover:bg-gray-200': selectedId !== item.id,}"v-for="item in items":key="item.id"><a @click.prevent="goToConversation(item.id)"><divclass="flex justify-between items-center text-sm leading-5 text-gray-500"><span>{{ item.selectedModel }}</span><span>{{ item.updatedAt }}</span></div><h2 class="font-semibold leading-6 text-gray-900 truncate">{{ item.title }}</h2></a></div></div>
</template><script lang="ts" setup>
import { ref } from "vue";
import { useRouter } from "vue-router";
import { ConversationProps } from "../types";defineProps<{ items: ConversationProps[] }>();
const router = useRouter();const selectedId = ref(0);const goToConversation = (id: number) => {router.push({ path: `/conversation/${id}` });selectedId.value = id;
};
</script>

聊天记录 MessageList.vue

在这里插入图片描述

src\components\MessageList.vue

<template><div class="message-list" ref="_ref"><divclass="message-item mb-3"v-for="message in messages":key="message.id"><div class="flex" :class="{ 'justify-end': message.type === 'question' }"><div><divclass="text-sm text-gray-500 mb-2":class="{ 'text-right': message.type === 'question' }">{{ message.createdAt }}</div><divclass="message-question bg-green-700 text-white p-2 rounded-md"v-if="message.type === 'question'"><imgv-if="message.imagePath":src="`safe-file://${message.imagePath}`"alt="Message image"class="h-24 w-24 object-cover rounded block"/>{{ message.content }}</div><divclass="message-answer p-2 rounded-md"v-else:class="{'bg-red-100 text-red-700': message.status === 'error','bg-gray-200 text-gray-700': message.status !== 'error',}"><template v-if="message.status === 'loading'"><Icon icon="eos-icons:three-dots-loading"></Icon></template><template v-else-if="message.status === 'error'"><span>{{ message.content }}</span></template><divv-elseclass="prose prose-slate prose-headings:my-2 prose-li:my-0 prose-ul:my-1 prose-p:my-1 prose-pre:p-0">{{ message.content }}</div></div></div></div></div></div>
</template><script lang="ts" setup>
import { ref } from "vue";
import { Icon } from "@iconify/vue";type MessageStatus = "loading" | "streaming" | "finished" | "error";interface MessageProps {id: number;content: string;type: "question" | "answer";conversationId: number;status?: MessageStatus;createdAt: string;updatedAt: string;imagePath?: string;
}defineProps<{ messages: MessageProps[] }>();
</script>

发送消息 MessageInput.vue

在这里插入图片描述

<template><divclass="message-input w-full shadow-sm border rounded-lg border-gray-300 py-1 px-2 focus-within:border-green-700"><div v-if="imagePreview" class="mb-2 relative flex items-center"><img:src="imagePreview"alt="Preview"class="h-24 w-24 object-cover rounded"/></div><div class="flex items-center"><inputtype="file"accept="image/*"ref="fileInput"class="hidden"@change="handleImageUpload"/><Iconicon="radix-icons:image"width="24"height="24":class="['mr-2',disabled? 'text-gray-300 cursor-not-allowed': 'text-gray-400 cursor-pointer hover:text-gray-600',]"@click="triggerFileInput"/><inputclass="outline-none border-0 flex-1 bg-white focus:ring-0"type="text"v-model="model":disabled="disabled"/><Buttonicon-name="radix-icons:paper-plane"@click="onCreate":disabled="disabled">发送</Button></div></div>
</template><script lang="ts" setup>
import { ref } from "vue";
import { Icon } from "@iconify/vue";import Button from "./Button.vue";const props = defineProps<{disabled?: boolean;
}>();
const emit = defineEmits<{create: [value: string, imagePath?: string];
}>();
const model = defineModel<string>();
const fileInput = ref<HTMLInputElement | null>(null);
const imagePreview = ref("");
const triggerFileInput = () => {if (!props.disabled) {fileInput.value?.click();}
};
let selectedImage: File | null = null;
const handleImageUpload = (event: Event) => {const target = event.target as HTMLInputElement;if (target.files && target.files.length > 0) {selectedImage = target.files[0];const reader = new FileReader();reader.onload = (e) => {imagePreview.value = e.target?.result as string;};reader.readAsDataURL(selectedImage);}
};
const onCreate = () => {if (model.value && model.value.trim() !== "") {emit("create", model.value, selectedImage?.path || undefined);selectedImage = null;imagePreview.value = "";}
};
</script>

页面中使用

src\App.vue

引入 ConversationList.vue

<template><div class="flex items-center justify-between h-screen"><div class="w-[300px] bg-gray-200 h-full border-r border-gray-300"><div class="h-[90%] overflow-y-auto"><ConversationList :items="conversations" /></div><div class="h-[10%] grid grid-cols-2 gap-2 p-2"><RouterLink to="/"><Button icon-name="radix-icons:chat-bubble" class="w-full">新建聊天</Button></RouterLink><RouterLink to="/settings"><Button icon-name="radix-icons:gear" plain class="w-full">应用配置</Button></RouterLink></div></div><div class="h-full flex-1"><RouterView /></div></div>
</template><script setup>
import { Icon } from "@iconify/vue";
import Button from "./components/Button.vue";
import ConversationList from "./components/ConversationList.vue";
import { conversations } from "./testData";
</script>

src\views\Home.vue

引入ProviderSelect.vue 和 MessageInput.vue

<template><div class="w-[80%] mx-auto h-full"><div class="flex items-center h-[85%]"><ProviderSelect :items="providers" v-model="currentProvider" /></div><div class="flex items-center h-[15%]"><MessageInput@create="createConversation":disabled="currentProvider === ''"/></div></div>
</template>
<script lang="ts" setup>
import ProviderSelect from "../components/ProviderSelect.vue";
import MessageInput from "../components/MessageInput.vue";
import { ref } from "vue";
import { providers } from "../testData";const currentProvider = ref("");
const createConversation = async (question: string, imagePath?: string) => {};
</script><style scoped></style>

src\views\Conversation.vue

引入 MessageList.vue 和 MessageInput.vue

<template><divclass="h-[10%] bg-gray-200 border-b border-gray-300 flex items-center px-3 justify-between"v-if="convsersation"><h3 class="font-semibold text-gray-900">{{ convsersation.title }}</h3><span class="text-sm text-gray-500">{{ convsersation.updatedAt }}</span></div><div class="w-[80%] mx-auto h-[75%] overflow-y-auto pt-2"><MessageList :messages="filteredMessages" /></div><div class="w-[80%] mx-auto h-[15%] flex items-center"><MessageInput @create="sendNewMessage" v-model="inputValue" /></div>
</template><script lang="ts" setup>
import MessageList from "../components/MessageList.vue";
import MessageInput from "../components/MessageInput.vue";
import { messages, conversations } from "../testData";
import { ref, computed, watch } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
let conversationId = ref(parseInt(route.params.id as string));const convsersation = computed(() =>conversations.find((item) => item.id === conversationId.value)
);
const filteredMessages = computed(() =>messages.filter((message) => message.conversationId === conversationId.value)
);watch(() => route.params.id,async (newId: string) => {conversationId.value = parseInt(newId);}
);const sendNewMessage = async (question: string, imagePath?: string) => {};const inputValue = ref("");
</script><style scoped></style>
http://www.dtcms.com/wzjs/177279.html

相关文章:

  • 网站后台无法更新缓存石家庄百度搜索引擎优化
  • 在哪个网站去租地方做收废站企业网站优化报告
  • 网站代理访问是什么意思免费网站可以下载
  • 科技部做财务决算的网站是什么uc推广登录入口
  • 网站怎么看是谁做的做外贸网站的公司
  • 牛商网抖音培训网站推广seo
  • 公司自己做网站流程和备案如何宣传推广
  • 驻马店网站优化百度快照推广
  • 什么设计师前景最好关键词优化推广
  • 江苏省句容建设局网站北京广告公司
  • 如何在对方网站上做外链百度推广个人怎么开户
  • 财务公司的主要业务关键词优化策略有哪些
  • 新手做网站详细步骤手机怎么制作网页
  • 做网站用python好还是PHP好营销型网站建设案例
  • b2c电子商务网站关键技术前端seo是什么意思
  • 网站你的后台管理系统用什么做深圳网络营销公司
  • 残疾人服务平台seo网站推广经理
  • 政府网站建设指引简述什么是网络营销
  • 网站开发技能seo搜索推广
  • 佛山网站开发哪家专业搜狐财经峰会直播
  • 被国家禁止访问的网站怎么打开市场推广计划方案模板
  • 自己做网站挣钱吗优化大师软件下载
  • 虚拟网站建设指导百度指数教程
  • 百度网站开发合同范本企业建站公司
  • 青岛互联网企业廊坊seo关键词优化
  • wap网站设计规范上海搜索引擎优化公司排名
  • 临淄网站制作首选专家项目推广平台排行榜
  • 东莞网页制作最新招聘信息安全优化大师
  • 工信部网站备案查询 验证码错误免费推广
  • 学服装设计后悔死了手机网站搜索优化