chore: restructure project and add i18n support

- Reorganize project structure with new electron and shared directories
- Add comprehensive i18n support with Chinese, English, and Japanese locales
- Update build configurations and TypeScript paths for new structure
- Add various UI components including chat interface and task management
- Include Windows release binaries and localization files
- Update dependencies and fix import paths throughout the codebase
This commit is contained in:
duanshuwen
2026-04-06 14:39:06 +08:00
parent e76b034d50
commit 6615d11dd6
311 changed files with 823682 additions and 4460 deletions

View File

@@ -0,0 +1,13 @@
<template>
<div class="drag-region">
<slot>
<span class="_placeholder">_hidden</span>
</slot>
</div>
</template>
<style scoped>
._placeholder {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,73 @@
<template>
<header class="flex items-start justify-between h-[40px]">
<div class="title-bar-main flex-auto">
<slot>{{ title ?? '' }}</slot>
</div>
<div class="title-bar-controls w-[168px] flex items-center justify-end text-tx-secondary">
<native-tooltip :content="t('window.minimize')">
<button v-show="isMinimizable" class="flex items-center justify-center cursor-pointer w-[40px] h-[40px]"
@click="minimizeWindow">
<iconify-icon icon="material-symbols:check-indeterminate-small" :color="color" :width="btnSize"
:height="btnSize" />
</button>
</native-tooltip>
<native-tooltip :content="isMaximized ? t('window.restore') : t('window.maximize')">
<button v-show="isMaximizable" class="flex items-center justify-center cursor-pointer w-[40px] h-[40px]"
@click="maximizeWindow">
<iconify-icon icon="material-symbols:chrome-maximize-outline-sharp" :color="color" :width="btnSize"
:height="btnSize" v-show="!isMaximized" />
<iconify-icon icon="material-symbols:chrome-restore-outline-sharp" :color="color" :width="btnSize"
:height="btnSize" v-show="isMaximized" />
</button>
</native-tooltip>
<native-tooltip :content="t('window.close')">
<button v-show="isClosable" class="flex items-center justify-center cursor-pointer w-[40px] h-[40px]"
@click="handleClose">
<iconify-icon icon="material-symbols:close" :color="color" :width="btnSize" :height="btnSize" />
</button>
</native-tooltip>
</div>
</header>
</template>
<script setup lang="ts">
import { Icon as IconifyIcon } from '@iconify/vue'
import { useWinManager } from '@hooks/useWinManager'
import NativeTooltip from '@components/NativeTooltip/index.vue'
interface HeaderBarProps {
title?: string;
isMaximizable?: boolean;
isMinimizable?: boolean;
isClosable?: boolean;
color?: string;
}
defineOptions({ name: 'HeaderBar', color: '#525866' })
withDefaults(defineProps<HeaderBarProps>(), {
isMaximizable: true,
isMinimizable: true,
isClosable: true,
})
const emit = defineEmits(['close']);
const { t } = useI18n();
const btnSize = 16;
const {
isMaximized,
closeWindow,
minimizeWindow,
maximizeWindow
} = useWinManager();
function handleClose() {
emit('close');
closeWindow();
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,24 @@
<template>
<div class="bg-color h-screen flex flex-col">
<header-bar>
<drag-region class="w-full" />
</header-bar>
<main class="box-border w-full h-[calc(100vh-40px)] flex pt-[8px] pb-[8px] pl-[8px] ">
<div class="flex-1 flex">
<slot />
</div>
<SideMenus />
</main>
</div>
</template>
<script setup lang="ts" name="Layout">
import SideMenus from '@src/components/SideMenus/index.vue'
</script>
<style scoped>
.bg-color {
background: linear-gradient(180deg, #EFF6FF 0%, #F5F7FA 40%);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<template v-if="slots.default()[0].el">
<slot></slot>
</template>
<template v-else>
<span :title="content">
<slot></slot>
</span>
</template>
</template>
<script setup lang="ts">
import { logger } from '@utils/logger'
interface Props {
content: string;
}
defineOptions({ name: 'NativeTooltip' });
const props = defineProps<Props>();
const slots = defineSlots()
if (slots?.default?.().length > 1) {
logger.warn('NativeTooltip only support one slot.')
}
const updateTooltipContent = (content: string) => {
const defaultSlot = slots?.default?.();
if (defaultSlot) {
const slotElement = defaultSlot[0]?.el
if (slotElement && slotElement instanceof HTMLElement) {
slotElement.title = content;
}
}
}
onMounted(() => updateTooltipContent(props.content))
watch(() => props.content, (val: string) => updateTooltipContent(val));
</script>

View File

@@ -0,0 +1,49 @@
<template>
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" class="mt-[15px]"
:pager-count="5" :page-sizes="pageSizes" :current-page="current" background :page-size="size" :layout="layout"
:total="total">
</el-pagination>
</template>
<script setup lang="ts" name="pagination">
const emit = defineEmits(['sizeChange', 'currentChange']);
const props = defineProps({
current: {
type: Number,
default: 1,
},
size: {
type: Number,
default: 10,
},
total: {
type: Number,
default: 0,
},
pageSizes: {
type: Array as () => number[],
default: () => {
return [10, 20, 50, 100, 200];
},
},
layout: {
type: String,
default: 'total, sizes, prev, pager, next, jumper',
},
});
// 分页改变
const sizeChangeHandle = (val: number) => {
emit('sizeChange', val);
};
// 分页改变
const currentChangeHandle = (val: number) => {
emit('currentChange', val);
};
</script>
<style scoped>
:deep(.el-pagination__sizes) {
margin-left: auto;
}
</style>

View File

@@ -0,0 +1,37 @@
<template>
<div class="w-[80px] h-full box-border flex flex-col items-center pb-[8px]">
<div :class="['flex flex-col gap-[16px]', { 'mt-auto mb-[8px] shrink-1': item.id === 4 }]"
v-for="(item) in menus" :key="item.id">
<div :class="['cursor-pointer flex flex-col items-center justify-center']" @click="handleClick(item)">
<div :class="['box-border rounded-[16px] p-[8px]', { 'bg-white': item.id === currentId }]">
<component :is="item.icon" :color="item.id === currentId ? item.activeColor : item.color"
:class="['w-[32px] h-[32px]']" />
</div>
<div
:class="['text-[14px] mt-[4px] mb-[8px]', item.id === currentId ? `text-[${item.activeColor}]` : item.color]">
{{ item.name }}
</div>
</div>
</div>
<div class="w-[48px] h-[48px] rounded-full overflow-hidden">
<img class="w-full h-full object-cover" src="@assets/images/login/black_logo.png" />
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { menus, type MenuItem } from '@constant/menus'
const currentId = ref(1)
const router = useRouter()
const handleClick = async (item: MenuItem) => {
console.log("🚀 ~ handleClick ~ item:", item)
currentId.value = item.id
router.push(item.url)
}
</script>
<style></style>

View File

@@ -0,0 +1,63 @@
<!--
* @Author: kongbeiwu lishaohua-520@qq.com
* @Date: 2025-12-21 23:02:06
* @LastEditors: kongbeiwu lishaohua-520@qq.com
* @LastEditTime: 2025-12-28 11:09:00
* @FilePath: /project/zn-ai/src/renderer/components/TaskList/Card.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div v-for="item in task" :key="item.id"
class="border border-solid border-[#E5E8EE] rounded-[12px] p-[12px] mb-[12px] task">
<div class="flex items-center pb-[12px]" style="border-bottom: 1px dashed #E5E8EE;">
<!-- <img class="w-[24px] h-[24px] rounded-[8px]] mr-[4px]" src="@assets/images/task/xc.png" /> -->
<div
class="w-[24px] h-[24px] rounded-[4px] bg-[#EFF6FF] text-[#2B7FFF] text-[14px] font-bold border border-solid border-[#BEDBFF] flex justify-center items-center">
{{ item.name[0] }}</div>
<div class="text-[16px] text-[#171717] font-bold mr-[8px] ml-[4px]">{{ item.name }}</div>
<div class="pl-[8px] pr-[8px] text-[12px] rounded-[100px]" :class="item.statusColor">{{
item.statusText }}</div>
</div>
<div class="flex items-center mt-[12px]">
<component :is="item.desIcon" :color="item.color" class="w-[15px] mr-[4px]" />
<div class="text-[14px]" :class="`text-[${item.color}]`" :style="{ color: item.color }">{{ item.des }}</div>
</div>
<div class="mt-[24px]">
<button class="w-[100%] h-[40px] bg-[#2B7FFF] text-white text-[14px] rounded-[12px]">{{ item.statusColor !==
'error' ? '查看' : '处理' }}</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from "vue";
import { task } from '@constant/task'
</script>
<style scoped>
.task {
position: relative;
z-index: 1;
transition: all .2s linear;
}
.task .success {
background-color: #E0FAEC;
color: #1FC16B;
}
.task .error {
background-color: #FFEBEC;
color: #FB3748;
}
.task .warning {
background-color: #FFF3EB;
color: #FA7319;
}
.task:hover {
z-index: 2;
box-shadow: 0 10px 20px rgba(0, 0, 0, .1);
transform: translate3d(0, -2px, 0);
}</style>

View File

@@ -0,0 +1,59 @@
<template>
<div class="task p-[12px]">
<div class="flex border border-[#BEDBFF] h-[48px] p-[4px] rounded-[10px] bg-[#EFF6FF] task-tab">
<div v-for="item in tabs" :key="item.value" class="flex-1 flex text-center items-center h-full align-middle text" :class="active === item.value && 'active'" @click="changeTab(item.value)">
<div class="flex-1">{{ item.name }}<span v-if="item.total">{{`${item.total > 98 && item.total + '+' || item.total}`}}</span></div>
</div>
</div>
<div class="flex justify-between mt-[12px] mb-[12px] text-[14px]">
<div class="text-[#171717]">今天</div>
<div class="text-[#99A0AE]">02:32:05</div>
</div>
<div>
<TaskCard />
</div>
</div>
</template>
<script setup lang="ts">
import TaskCard from './Card.vue';
import { ref, reactive } from "vue";
const tabs = reactive([{
name: '待处理',
value: 1,
total: 10,
},{
name: '已处理',
value: 2,
total: 99,
}])
const active = ref(1);
const changeTab = (val:number) => {
active.value = val;
};
</script>
<style scoped>
.task-tab .text {
color: #525866;
font-size: 14px;
cursor: pointer;
}
.task-tab .active {
position: relative;
color: #2B7FFF;
background: #FFFFFF;
border-radius: 8px;
}
.task-tab .active::after {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
content: '';
border-radius: 8px;
border: 1px solid #2B7FFF;
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div class="task-list w-[392px] h-full rounded-[16px] bg-white">
<slot>
<TaskList />
</slot>
</div>
</template>
<script setup lang="ts">
import TaskList from './List.vue';
import { ref, reactive } from "vue";
const tabs = reactive([{
name: '待处理',
value: 1,
total: 10,
}, {
name: '已处理',
value: 2,
total: 99,
}])
const active = ref(1);
const changeTab = (val: number) => {
active.value = val;
};
</script>
<style scoped>
.task-tab .text {
color: #525866;
font-size: 14px;
cursor: pointer;
}
.task-tab .active {
position: relative;
color: #2B7FFF;
background: #FFFFFF;
border-radius: 8px;
}
.task-tab .active::after {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
content: '';
border-radius: 8px;
border: 1px solid #2B7FFF;
}
</style>

View File

@@ -0,0 +1,44 @@
<!--
* @Author: kongbeiwu lishaohua-520@qq.com
* @Date: 2025-12-21 23:02:06
* @LastEditors: kongbeiwu lishaohua-520@qq.com
* @LastEditTime: 2026-01-04 10:35:46
* @FilePath: /project/zn-ai/src/renderer/components/TitleSection/index.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div class="flex justify-between items-center box-border border-b-[1px] border-b-[#E5E8EE] mb-[20px] pb-[20px]">
<div class="flex">
<div class="flex items-center">
<el-icon v-if="attrs.onBackTo" @click="emits('back-to', true)" size="18px" color="#525866" class="mr-[15px] cursor-pointer">
<ArrowLeftBold />
</el-icon>
<span class="text-[24px] font-500 text-[#171717] leading-[32px] mr-[8px]">
{{ title }}
</span>
</div>
<span class="text-[12px] font-400 text-[#99A0AE] leading-[16px]" style="align-self: flex-end;">
{{ desc }}
</span>
</div>
<div class="flex items-center">
<slot name="right"></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { useAttrs, defineEmits } from 'vue';
import { ArrowLeftBold } from '@element-plus/icons-vue';
interface TitleSectionProps {
title?: string
desc?: string
}
withDefaults(
defineProps<TitleSectionProps>(),
{ title: '', desc: '' }
)
const emits = defineEmits(["update:back-to"]);
const attrs = useAttrs();
</script>