实验页新建、删除类
- "+ 新建类":输入类名,可以带文件夹(SQLHelpers/SQLBLL),文件夹成为命名空间的一段, 模板和 VS 2012 的类模板一样;检查重名、非法名字 - 自己建的类可以删除(窗体、Program.cs、项目资源类不能用这个删) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
13c3e4c3ec
commit
42e258ddcd
3 files changed
+104
-2
No files matched your search
@@ -3,7 +3,7 @@ import { expect, test } from 'bun:test'
|
||||
import { generateDesigner, parseDesigner } from './document'
|
||||
import { addControl } from './edit'
|
||||
import { controlDef } from './catalog'
|
||||
import { assignmentTemplate, formNameTaken, newFormFiles, nextFormName, projectNamespace, startupForm } from './templates'
|
||||
import { assignmentTemplate, classPathError, formNameTaken, newClassFile, newFormFiles, nextFormName, projectNamespace, startupForm } from './templates'
|
||||
|
||||
const files = [
|
||||
{ path: 'Program.cs', content: 'namespace 登录\n{\n static class Program\n {\n static void Main()\n {\n Application.Run(new Login());\n }\n }\n}\n' },
|
||||
@@ -66,3 +66,17 @@ test('作业起点"空白工程":Program.cs 启动空的 Form1', async () => {
|
||||
expect(program).toContain('Application.Run(new Form1())')
|
||||
expect(files.find((f) => f.path === 'Form1.Designer.cs')!.content).not.toContain('com_port')
|
||||
})
|
||||
|
||||
test('新建类:可以带文件夹,文件夹也是命名空间的一段;名字不能重复', () => {
|
||||
const files = [{ path: 'Program.cs', content: 'namespace 智慧校园\n{\n static class Program { }\n}\n' }, { path: 'AppSet.cs', content: 'class AppSet { }' }]
|
||||
expect(newClassFile('智慧校园', 'SQLHelpers/SQLHelper')).toEqual({
|
||||
path: 'SQLHelpers/SQLHelper.cs',
|
||||
content: 'using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace 智慧校园.SQLHelpers\n{\n class SQLHelper\n {\n }\n}\n',
|
||||
})
|
||||
expect(newClassFile('智慧校园', 'RecordClass.cs').path).toBe('RecordClass.cs')
|
||||
expect(classPathError(files, 'SQLHelpers/SQLBLL')).toBeNull()
|
||||
expect(classPathError(files, 'AppSet')).toBe('已经有 AppSet.cs 了')
|
||||
expect(classPathError(files, 'Other/AppSet')).toBe('已经有名为“AppSet”的类或窗体')
|
||||
expect(classPathError(files, '1abc')).toContain('不能以数字开头')
|
||||
expect(classPathError(files, 'Properties/X')).toContain('平台管理')
|
||||
})
|
||||
@@ -22,6 +22,42 @@ export function formNameTaken(files: SourceFile[], name: string): boolean {
|
||||
return files.some((f) => f.path === `${name}.cs` || f.path === `${name}.Designer.cs` || new RegExp(`(?<![\\p{L}\\p{N}_])class\\s+${name}(?![\\p{L}\\p{N}_])`, 'u').test(f.content))
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建类时输入的名字有什么问题(没有问题返回 null)。可以带文件夹,如 SQLHelpers/SQLHelper:
|
||||
* 和 VS 一样,文件放进文件夹,文件夹也是命名空间的一段。
|
||||
*/
|
||||
export function classPathError(files: SourceFile[], input: string): string | null {
|
||||
const path = input.trim().replace(/\\/g, '/').replace(/\.cs$/i, '')
|
||||
if (!path) return '请输入类名'
|
||||
const parts = path.split('/')
|
||||
if (parts.some((p) => !/^[\p{L}_][\p{L}\p{N}_]*$/u.test(p))) return '类名和文件夹名只能用字母、汉字、数字、下划线,且不能以数字开头'
|
||||
if (parts[0] === 'Properties') return 'Properties 文件夹由平台管理,请换一个文件夹'
|
||||
if (files.some((f) => f.path.toLowerCase() === `${path}.cs`.toLowerCase())) return `已经有 ${path}.cs 了`
|
||||
if (formNameTaken(files, parts.at(-1)!)) return `已经有名为“${parts.at(-1)}”的类或窗体`
|
||||
return null
|
||||
}
|
||||
|
||||
/** 新建类(VS 的"添加 → 类"):和 VS 2012 的模板一样 */
|
||||
export function newClassFile(namespace: string, input: string): SourceFile {
|
||||
const path = input.trim().replace(/\\/g, '/').replace(/\.cs$/i, '')
|
||||
const parts = path.split('/')
|
||||
const name = parts.pop()!
|
||||
const content = `using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ${[namespace, ...parts].join('.')}
|
||||
{
|
||||
class ${name}
|
||||
{
|
||||
}
|
||||
}
|
||||
`
|
||||
return { path: `${path}.cs`, content }
|
||||
}
|
||||
|
||||
/** Program.cs 里 Application.Run(new X()) 启动的窗体类名 */
|
||||
export function startupForm(files: SourceFile[]): string | undefined {
|
||||
const program = files.find((f) => f.path === 'Program.cs')
|
||||
|
||||
@@ -10,7 +10,7 @@ import FormDesigner from '../designer/FormDesigner.vue'
|
||||
import { createDesignerHistory } from '../designer/history'
|
||||
import { gallery, loadGallery, projectResources, resourcePrefix, resourcesFile, RESOURCES_FILE } from '../designer/resources'
|
||||
import ResourcesDialog from '../designer/ResourcesDialog.vue'
|
||||
import { formNameTaken, isFormDesigner, newFormFiles, nextFormName, projectNamespace, startupForm } from '../designer/templates'
|
||||
import { classPathError, formNameTaken, isFormDesigner, newClassFile, newFormFiles, nextFormName, projectNamespace, startupForm } from '../designer/templates'
|
||||
import CodeEditor from '../editor/CodeEditor.vue'
|
||||
import { api } from '../api'
|
||||
import { currentUser, logout } from '../auth'
|
||||
@@ -293,6 +293,41 @@ function saveResources(names: string[]) {
|
||||
filesChanged()
|
||||
}
|
||||
|
||||
// ---------- 新建、删除类 ----------
|
||||
const newClassOpen = ref(false)
|
||||
const newClassName = ref('')
|
||||
const newClassError = computed(() => classPathError(files.value, newClassName.value))
|
||||
|
||||
function openNewClass(show: boolean) {
|
||||
newClassOpen.value = show
|
||||
if (show) newClassName.value = ''
|
||||
}
|
||||
|
||||
/** 新建类:和 VS 的"添加 → 类"一样,可以带文件夹(SQLHelpers/SQLHelper) */
|
||||
function createClass() {
|
||||
if (newClassError.value) return
|
||||
const created = newClassFile(projectNamespace(files.value), newClassName.value)
|
||||
files.value.push(created)
|
||||
filesChanged()
|
||||
newClassOpen.value = false
|
||||
activeTab.value = created.path
|
||||
}
|
||||
|
||||
/** 当前标签页的普通类文件能否删除:窗体文件、Program.cs、平台生成的资源类不在这里删 */
|
||||
const deletableClass = computed(() => {
|
||||
const path = activeTab.value
|
||||
if (!path || activeForm.value || path === 'Program.cs' || path.startsWith('Properties/') || !path.endsWith('.cs')) return undefined
|
||||
return forms.value.some((f) => f.code === path || f.designer === path) ? undefined : path
|
||||
})
|
||||
|
||||
function deleteClass() {
|
||||
const path = deletableClass.value
|
||||
if (!path) return
|
||||
files.value = files.value.filter((f) => f.path !== path)
|
||||
filesChanged()
|
||||
activeTab.value = forms.value[0]?.tab ?? files.value[0]?.path
|
||||
}
|
||||
|
||||
// ---------- 新建、删除窗体 ----------
|
||||
const newFormOpen = ref(false)
|
||||
const newFormName = ref('')
|
||||
@@ -467,6 +502,23 @@ function run() {
|
||||
<NButton size="small" type="primary" :disabled="!!newFormError" @click="createForm">创建</NButton>
|
||||
</div>
|
||||
</NPopover>
|
||||
<NPopover trigger="click" placement="bottom-end" :show="newClassOpen" @update:show="openNewClass">
|
||||
<template #trigger>
|
||||
<NButton size="small" quaternary>+ 新建类</NButton>
|
||||
</template>
|
||||
<div class="new-form">
|
||||
<div>类名(也是文件名;放进文件夹时写成 SQLHelpers/SQLHelper)</div>
|
||||
<NInput v-model:value="newClassName" size="small" placeholder="例如:AppSet" @keydown.enter="createClass" />
|
||||
<div v-if="newClassError && newClassName.trim()" class="new-form-error">{{ newClassError }}</div>
|
||||
<NButton size="small" type="primary" :disabled="!!newClassError" @click="createClass">创建</NButton>
|
||||
</div>
|
||||
</NPopover>
|
||||
<NPopconfirm v-if="deletableClass" positive-text="删除" negative-text="取消" @positive-click="deleteClass">
|
||||
<template #trigger>
|
||||
<NButton size="small" quaternary>删除类</NButton>
|
||||
</template>
|
||||
将删除 {{ deletableClass }},代码里用到它的地方需要自己修改。确定删除吗?
|
||||
</NPopconfirm>
|
||||
<NButton size="small" quaternary title="相当于 VS 的“项目属性 → 资源”:添加图片和声音,代码里用 Properties.Resources.名字" @click="resourcesOpen = true">
|
||||
项目资源{{ resourceNames.length ? `(${resourceNames.length})` : '' }}
|
||||
</NButton>
|
||||
|
||||
Reference in new issue
Block a user