Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,6 @@ pnpm tauri dev
pnpm tauri build
```

## 使用

1. 在左侧编辑器输入代码
2. 点击 "执行代码" 按钮执行
3. 在右侧面板查看输出结果

## 技术栈

- **前端:** Vue 3 + TypeScript + Tailwind CSS
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ async fn execute_code(
for cmd in plugin.get_commands() {
let args = plugin.get_execute_args(file_path.to_str().unwrap());
debug!(
"执行插件代码 -> 执行命令: {} 携带参数: {:?} 语言: {}",
cmd, args, request.language
"执行 {:?} 代码 -> 执行命令: {:?} 携带参数: {:?}",
request.language, cmd, args
);

let output = Command::new(cmd)
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"windows": [
{
"title": "CodeForge",
"minWidth": 1000,
"minHeight": 600,
"width": 1800,
"height": 1200,
"additionalBrowserArgs": "--disable-context-menu"
Expand Down
4 changes: 2 additions & 2 deletions src/components/About.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<!-- 关闭按钮 -->
<div class="flex justify-end mb-4">
<button
class="text-gray-400 hover:cursor-pointer hover:text-gray-600 dark:hover:text-gray-200 transition-all duration-200 hover:scale-110 rounded-full p-1 hover:bg-gray-100 dark:hover:bg-gray-700"
class="text-gray-400 hover:cursor-pointer dark:hover:text-gray-200 transition-all duration-200 hover:scale-110 rounded-full p-1 hover:bg-gray-300 dark:hover:bg-gray-700"
@click="closeAbout">
<X class="w-5 h-5"></X>
</button>
Expand Down Expand Up @@ -61,7 +61,7 @@
<!-- 技术栈 -->
<div class="mb-6 transition-all duration-600 delay-500">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">技术栈</h3>
<hr class="border-gray-200/50 dark:border-gray-600/50 my-3" />
<hr class="border-gray-200/50 dark:border-gray-600/50 my-3"/>
<div class="flex flex-wrap gap-2">
<span v-for="(tech, index) in techStack"
class="px-3 py-1 text-xs rounded-full font-medium transform transition-all duration-200 hover:scale-110 hover:-translate-y-1 cursor-pointer"
Expand Down
23 changes: 10 additions & 13 deletions src/components/AppHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<!-- 自定义下拉选择器 -->
<div class="relative">
<Select v-model="selectedLanguage"
class="w-48"
:options="supportedLanguages as any"
:disabled="isRunning"
placeholder="选择语言"
Expand All @@ -17,23 +18,18 @@
</div>

<div class="flex items-center space-x-3">
<button @click="$emit('run-code')"
<Button @click="$emit('run-code')"
:disabled="isRunning || !envInstalled"
:class="['flex items-center space-x-2 px-3 py-1.5 rounded-md font-medium transition-all duration-200',
isRunning || !envInstalled
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'btn-success shadow-sm hover:shadow-md cursor-pointer'
]">
<component :is="isRunning ? Square : Play" class="w-3 h-3"/>
:icon="isRunning ? Square : Play">
<span>{{ isRunning ? '运行中...' : '运行代码' }}</span>
</button>
</Button>

<button class="btn-danger px-3 py-2.5 rounded-md font-medium transition-all duration-200"
<Button @click="$emit('clear-output')"
:disabled="isRunning || !envInstalled"
:class="[isRunning || !envInstalled ? 'cursor-not-allowed' : 'cursor-pointer']"
@click="$emit('clear-output')">
<Trash2 class="w-4 h-4"/>
</button>
type="danger"
:icon-only="true"
:icon="Trash2">
</Button>
</div>
</div>
</template>
Expand All @@ -42,6 +38,7 @@
import { ref, watch } from 'vue'
import { Play, Square, Trash2 } from 'lucide-vue-next'
import Select from '../ui/Select.vue'
import Button from '../ui/Button.vue'

interface Language
{
Expand Down
52 changes: 20 additions & 32 deletions src/components/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="fixed inset-0 backdrop-blur-sm bg-white/20 dark:bg-gray-900/20 flex items-center justify-center z-50 transition-all duration-300 ease-out"
:class="{ 'opacity-0': !isVisible, 'opacity-100': isVisible }">
<div
class="bg-white/95 dark:bg-gray-800/95 backdrop-blur-md rounded-xl shadow-2xl w-4xl p-6 transform transition-all duration-300 ease-out border border-white/20 dark:border-gray-700/30 max-h-[80vh] overflow-y-auto"
class="bg-white/95 dark:bg-gray-800/95 backdrop-blur-md rounded-xl shadow-2xl w-6xl h-screen p-6 transform transition-all duration-300 ease-out border border-white/20 dark:border-gray-700/30 max-h-[80vh] overflow-y-auto"
:class="{
'scale-95 opacity-0 translate-y-4': !isVisible,
'scale-100 opacity-100 translate-y-0': isVisible
Expand Down Expand Up @@ -47,32 +47,29 @@
placeholder="选择或输入日志目录路径"
class="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
readonly/>
<button
class="px-4 py-2 cursor-pointer bg-blue-500 hover:bg-blue-600 text-white text-sm font-medium rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
@click="selectLogDirectory">
<Folder class="w-4 h-4"/>
</button>

<Button type="primary"
:icon-only="true"
:icon="Folder"
@click="selectLogDirectory">
</Button>
</div>
</div>

<!-- 操作按钮 -->
<div class="flex gap-3 pt-2">
<button
@click="applyLogDirChange"
:disabled="!newLogDir || newLogDir === currentLogDir"
class="cursor-pointer px-4 py-2 bg-green-500 hover:bg-green-600 disabled:bg-gray-300 disabled:cursor-not-allowed text-white text-sm font-medium rounded-md transition-colors">
<div class="flex gap-3 pt-0.5">
<Button @click="applyLogDirChange"
:disabled="!newLogDir || newLogDir === currentLogDir"
type="secondary">
应用更改
</button>
<button
@click="openLogDirectory"
class="cursor-pointer px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white text-sm font-medium rounded-md transition-colors">
</Button>
<Button @click="openLogDirectory" type="secondary">
打开日志目录
</button>
<button
@click="resetLogDirectory"
class="cursor-pointer px-4 py-2 bg-orange-500 hover:bg-orange-600 text-white text-sm font-medium rounded-md transition-colors">
</Button>
<Button @click="resetLogDirectory"
class="cursor-pointer px-4 py-2 bg-orange-500 hover:bg-orange-600 text-white text-sm font-medium rounded-md transition-colors">
重置为默认
</button>
</Button>
</div>

<!-- 日志文件列表 -->
Expand Down Expand Up @@ -110,23 +107,13 @@
:options="keepDaysOptions"
placeholder="选择保留天数">
</Select>
<button class="cursor-pointer px-4 py-2 bg-red-500 hover:bg-red-600 text-white text-sm font-medium rounded-md transition-colors"
@click="clearLogs">
<Button type="danger" @click="clearLogs">
立即清理
</button>
</Button>
</div>
</div>
</div>
</div>

<!-- 底部按钮 -->
<div class="flex justify-end gap-3 pt-4 border-t border-gray-200/50 dark:border-gray-600/50">
<button
@click="closeSettings"
class="cursor-pointer px-4 py-2 bg-gray-500 hover:bg-gray-600 text-white text-sm font-medium rounded-md transition-colors">
关闭
</button>
</div>
</div>
</div>
</template>
Expand All @@ -139,6 +126,7 @@ import { openPath } from '@tauri-apps/plugin-opener'
import { FileText, Folder, Settings2, X } from 'lucide-vue-next'
import Select from '../ui/Select.vue'
import { useToast } from '../plugins/toast'
import Button from '../ui/Button.vue'

const toast = useToast()
const isVisible = ref(false)
Expand Down
206 changes: 206 additions & 0 deletions src/ui/Button.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
<template>
<button :type="htmlType"
:disabled="disabled || loading"
:class="buttonClasses"
class="flex items-center space-x-2 justify-center rounded-md transition-all duration-200 cursor-pointer"
@click="handleClick"
v-bind="$attrs">
<!-- 加载状态图标 -->
<div v-if="loading" class="animate-spin mr-2">
<Loader2 class="w-4 h-4"/>
</div>

<!-- 左侧图标 -->
<component v-else-if="icon && iconPosition === 'left'"
:is="icon"
:class="iconClasses"/>

<!-- 按钮文本 -->
<span v-if="$slots.default || text" :class="textClasses">
<slot>{{ text }}</slot>
</span>

<!-- 右侧图标 -->
<component v-if="icon && iconPosition === 'right' && !loading"
:is="icon"
:class="iconClasses"/>
</button>
</template>

<script setup lang="ts">
import type { Component } from 'vue'
import { computed } from 'vue'
import { Loader2 } from 'lucide-vue-next'

interface Props
{
// 按钮类型
type?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'ghost' | 'link'
// 按钮尺寸
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
// 按钮变体
variant?: 'solid' | 'outline' | 'ghost' | 'soft'
// HTML 按钮类型
htmlType?: 'button' | 'submit' | 'reset'
// 禁用状态
disabled?: boolean
// 加载状态
loading?: boolean
// 圆形按钮
circle?: boolean
// 圆角按钮
round?: boolean
// 块级按钮
block?: boolean
// 图标
icon?: Component | string
// 图标位置
iconPosition?: 'left' | 'right'
// 仅图标按钮
iconOnly?: boolean
// 按钮文本
text?: string
// 自定义类名
customClass?: string | string[]
}

const props = withDefaults(defineProps<Props>(), {
type: 'primary',
size: 'md',
variant: 'solid',
htmlType: 'button',
disabled: false,
loading: false,
circle: false,
round: false,
block: false,
iconPosition: 'left',
iconOnly: false
})

const emit = defineEmits<{
click: [event: MouseEvent]
}>()

// 基础样式
const baseClasses = computed(() => [
'inline-flex items-center justify-center font-medium transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:cursor-not-allowed disabled:opacity-50',
// 圆形按钮
props.circle ? 'rounded-full' : props.round ? 'rounded-full' : 'rounded-lg',
// 块级按钮
props.block ? 'w-full' : '',
// 仅图标按钮
props.iconOnly ? 'p-0' : ''
])

// 尺寸样式
const sizeClasses = computed(() => {
const sizes = {
xs: props.iconOnly ? 'w-6 h-6' : 'px-2 py-1 text-xs',
sm: props.iconOnly ? 'w-8 h-8' : 'px-3 py-1.5 text-sm',
md: props.iconOnly ? 'w-10 h-10' : 'px-4 py-2 text-sm',
lg: props.iconOnly ? 'w-12 h-12' : 'px-6 py-3 text-base',
xl: props.iconOnly ? 'w-14 h-14' : 'px-8 py-4 text-lg'
}
return sizes[props.size]
})

// 类型和变体样式
const typeClasses = computed(() => {
const { type, variant } = props

const styles = {
primary: {
solid: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500 border border-transparent',
outline: 'border border-blue-600 text-blue-600 hover:bg-blue-50 focus:ring-blue-500',
ghost: 'text-blue-600 hover:bg-blue-50 focus:ring-blue-500 border border-transparent',
soft: 'bg-blue-50 text-blue-600 hover:bg-blue-100 focus:ring-blue-500 border border-transparent'
},
secondary: {
solid: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500 border border-transparent',
outline: 'border border-gray-300 text-gray-700 hover:bg-gray-50 focus:ring-gray-500',
ghost: 'text-gray-700 hover:bg-gray-50 focus:ring-gray-500 border border-transparent',
soft: 'bg-gray-50 text-gray-700 hover:bg-gray-100 focus:ring-gray-500 border border-transparent'
},
success: {
solid: 'bg-green-600 text-white hover:bg-green-700 focus:ring-green-500 border border-transparent',
outline: 'border border-green-600 text-green-600 hover:bg-green-50 focus:ring-green-500',
ghost: 'text-green-600 hover:bg-green-50 focus:ring-green-500 border border-transparent',
soft: 'bg-green-50 text-green-600 hover:bg-green-100 focus:ring-green-500 border border-transparent'
},
warning: {
solid: 'bg-yellow-500 text-white hover:bg-yellow-600 focus:ring-yellow-500 border border-transparent',
outline: 'border border-yellow-500 text-yellow-600 hover:bg-yellow-50 focus:ring-yellow-500',
ghost: 'text-yellow-600 hover:bg-yellow-50 focus:ring-yellow-500 border border-transparent',
soft: 'bg-yellow-50 text-yellow-600 hover:bg-yellow-100 focus:ring-yellow-500 border border-transparent'
},
danger: {
solid: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500 border border-transparent',
outline: 'border border-red-600 text-red-600 hover:bg-red-50 focus:ring-red-500',
ghost: 'text-red-600 hover:bg-red-50 focus:ring-red-500 border border-transparent',
soft: 'bg-red-50 text-red-600 hover:bg-red-100 focus:ring-red-500 border border-transparent'
},
info: {
solid: 'bg-cyan-600 text-white hover:bg-cyan-700 focus:ring-cyan-500 border border-transparent',
outline: 'border border-cyan-600 text-cyan-600 hover:bg-cyan-50 focus:ring-cyan-500',
ghost: 'text-cyan-600 hover:bg-cyan-50 focus:ring-cyan-500 border border-transparent',
soft: 'bg-cyan-50 text-cyan-600 hover:bg-cyan-100 focus:ring-cyan-500 border border-transparent'
},
link: {
solid: 'text-blue-600 hover:text-blue-700 underline focus:ring-blue-500 border border-transparent bg-transparent p-0',
outline: 'text-blue-600 hover:text-blue-700 underline focus:ring-blue-500 border border-transparent bg-transparent p-0',
ghost: 'text-blue-600 hover:text-blue-700 underline focus:ring-blue-500 border border-transparent bg-transparent p-0',
soft: 'text-blue-600 hover:text-blue-700 underline focus:ring-blue-500 border border-transparent bg-transparent p-0'
}
}

return styles[type as keyof typeof styles]?.[variant] || styles.primary.solid
})

// 图标样式
const iconClasses = computed(() => {
const hasText = props.text || !!(props as any).$slots?.default
const sizeMap = {
xs: 'w-3 h-3',
sm: 'w-4 h-4',
md: 'w-4 h-4',
lg: 'w-5 h-5',
xl: 'w-6 h-6'
}

const marginMap = {
left: hasText && !props.iconOnly ? 'mr-2' : '',
right: hasText && !props.iconOnly ? 'ml-2' : ''
}

return [
sizeMap[props.size],
marginMap[props.iconPosition]
].filter(Boolean)
})

// 文本样式
const textClasses = computed(() => {
if (props.iconOnly) {
return 'sr-only'
}
return ''
})

// 最终样式组合
const buttonClasses = computed(() => [
...baseClasses.value,
sizeClasses.value,
typeClasses.value,
...(Array.isArray(props.customClass) ? props.customClass : [props.customClass]).filter(Boolean)
])

// 点击处理
const handleClick = (event: MouseEvent) => {
if (!props.disabled && !props.loading) {
emit('click', event)
}
}
</script>
Loading
Loading