新增Editor的Demo
Some checks failed
Monaco Editor checks / Monaco Editor checks (push) Failing after 19m8s

This commit is contained in:
mxwj 2024-11-21 02:12:20 +08:00
parent 499648a90b
commit 42f411fc64
9 changed files with 563 additions and 70 deletions

@ -421,6 +421,7 @@ export interface IFileEditorFactory {
} }
export interface IEditorFactoryRegistry { export interface IEditorFactoryRegistry {
[x: string]: any;
/** /**
* Registers the file editor factory to use for file editors. * Registers the file editor factory to use for file editors.

@ -34,6 +34,14 @@ export const defaultViewIcon = registerIcon('default-view-icon', Codicon.window,
export namespace Extensions { export namespace Extensions {
export const ViewContainersRegistry = 'workbench.registry.view.containers'; export const ViewContainersRegistry = 'workbench.registry.view.containers';
export const ViewsRegistry = 'workbench.registry.view'; export const ViewsRegistry = 'workbench.registry.view';
export function InstantiationService<T>(InstantiationService: any) {
throw new Error('Function not implemented.');
}
export function EditorService<T>(EditorService: any) {
throw new Error('Function not implemented.');
}
} }
export const enum ViewContainerLocation { export const enum ViewContainerLocation {

@ -14,9 +14,15 @@ import { ILocalizedString, localize } from '../../../../nls.js';
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js'; import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js'; import { registerIcon } from '../../../../platform/theme/common/iconRegistry.js';
import { Codicon } from '../../../../base/common/codicons.js'; import { Codicon } from '../../../../base/common/codicons.js';
import { ExampleViewContainer } from './exampleViewContainer.js'; import { ExampleViewContainer } from './views/exampleViewContainer.js';
import { ExampleView } from './exampleView.js'; import { ExampleView } from './views/exampleView.js';
import { IViewPaneOptions } from '../../../browser/parts/views/viewPane.js'; import { IViewPaneOptions } from '../../../browser/parts/views/viewPane.js';
import { EditorPaneDescriptor, IEditorPaneRegistry } from '../../../browser/editor.js';
import { EditorExtensions } from '../../../common/editor.js';
import { StockDetailsEditor } from './views/StockDetailsEditor.js';
import { StockDetailsInput } from './views/StockDetailsInput.js';
// 如果需要为 Extensions 使用别名,可以在此处赋值 // 如果需要为 Extensions 使用别名,可以在此处赋值
@ -34,7 +40,7 @@ const exampleIcon = registerIcon(
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer( const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(
{ {
id: 'workbench.view.example', id: 'workbench.view.example',
title: { value: localize('example', '示例'), original: 'Example' }, title: { value: localize('example', '侧边栏'), original: 'Example' },
ctorDescriptor: new SyncDescriptor(ExampleViewContainer), ctorDescriptor: new SyncDescriptor(ExampleViewContainer),
icon: exampleIcon, icon: exampleIcon,
order: 6, order: 6,
@ -66,5 +72,17 @@ Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews(
viewContainer viewContainer
); );
// 注册 EditorPane
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
EditorPaneDescriptor.create(
StockDetailsEditor, // 编辑器的类
StockDetailsEditor.ID, // 编辑器唯一标识符
localize('stockDetails', 'Stock Details') // 编辑器的本地化名称
),
[new SyncDescriptor(StockDetailsInput)] // 绑定的 EditorInput 类型
);
console.log('StockDetailsEditor 注册完成');
console.log('初始化完成'); console.log('初始化完成');

@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IViewPaneOptions, ViewPane } from '../../../browser/parts/views/viewPane.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IViewDescriptorService } from '../../../common/views.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { IAccessibleViewService } from '../../../../platform/accessibility/browser/accessibleView.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
export class ExampleView extends ViewPane {
constructor(
options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IInstantiationService instantiationService: IInstantiationService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IHoverService hoverService: IHoverService,
@IAccessibleViewService accessibleViewService: IAccessibleViewService
) {
super(
options,
keybindingService,
contextMenuService,
configurationService,
contextKeyService,
viewDescriptorService,
instantiationService,
openerService,
themeService,
telemetryService,
hoverService,
);
}
// 您可以在这里添加自定义的视图逻辑
}

@ -0,0 +1,292 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// import { EditorPane } from '../../../../browser/parts/editor/editorPane.js';
// import { Dimension } from '../../../../../base/browser/dom.js';
// import { StockDetailsInput } from './StockDetailsInput.js';
// import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
// import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
// import { IStorageService } from '../../../../../platform/storage/common/storage.js';
// import { CancellationToken } from '../../../../../base/common/cancellation.js';
// import { IEditorOptions } from '../../../../../platform/editor/common/editor.js';
// import { IEditorOpenContext } from '../../../../common/editor.js';
// export class StockDetailsEditor extends EditorPane {
// static readonly ID = 'workbench.editor.stockDetails';
// private container: HTMLElement | null = null;
// constructor(
// id: string,
// group: any, // 实际类型是 IEditorGroup
// @ITelemetryService telemetryService: ITelemetryService,
// @IThemeService themeService: IThemeService,
// @IStorageService storageService: IStorageService
// ) {
// super(id, group, telemetryService, themeService, storageService);
// }
// protected createEditor(parent: HTMLElement): void {
// this.container = document.createElement('div');
// this.container.className = 'stock-details-editor';
// parent.appendChild(this.container);
// }
// override async setInput(
// input: StockDetailsInput,
// options: IEditorOptions | undefined,
// context: IEditorOpenContext,
// token: CancellationToken
// ): Promise<void> {
// super.setInput(input, options, context, token);
// if (input.resource) {
// const stockCode = input.resource.path;
// this.container!.innerHTML = `加载股票详情: ${stockCode}`;
// // 加载和渲染股票数据逻辑
// }
// }
// override layout(dimension: Dimension): void {
// // 实现布局逻辑
// }
// override clearInput(): void {
// if (this.container) {
// this.container.innerHTML = '';
// }
// super.clearInput();
// }
// }
// import { EditorPane } from '../../../../browser/parts/editor/editorPane.js';
// import { Dimension } from '../../../../../base/browser/dom.js';
// import { StockDetailsInput } from './StockDetailsInput.js';
// import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
// import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
// import { IStorageService } from '../../../../../platform/storage/common/storage.js';
// import { CancellationToken } from '../../../../../base/common/cancellation.js';
// import { IEditorOptions } from '../../../../../platform/editor/common/editor.js';
// import { IEditorOpenContext } from '../../../../common/editor.js';
// import { IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js';
// export class StockDetailsEditor extends EditorPane {
// static readonly ID = 'workbench.editor.stockDetails';
// private container: HTMLElement | null = null;
// constructor(
// group: IEditorGroup, // 修改第一个参数为 IEditorGroup 类型
// @ITelemetryService telemetryService: ITelemetryService,
// @IThemeService themeService: IThemeService,
// @IStorageService storageService: IStorageService
// ) {
// super(StockDetailsEditor.ID, group, telemetryService, themeService, storageService); // ID 移动到 super 调用
// }
// protected createEditor(parent: HTMLElement): void {
// this.container = document.createElement('div');
// this.container.className = 'stock-details-editor';
// parent.appendChild(this.container);
// }
// override async setInput(
// input: StockDetailsInput,
// options: IEditorOptions | undefined,
// context: IEditorOpenContext,
// token: CancellationToken
// ): Promise<void> {
// super.setInput(input, options, context, token);
// if (input.resource) {
// const stockCode = input.resource.path;
// // 清空之前的内容
// this.container!.textContent = '';
// // 创建一个新的 div 节点
// const stockDetailsDiv = document.createElement('div');
// stockDetailsDiv.className = 'stock-details';
// stockDetailsDiv.textContent = `加载股票详情: ${stockCode}`;
// // 将节点插入到容器中
// this.container!.appendChild(stockDetailsDiv);
// } else {
// this.container!.textContent = '未提供有效的股票代码';
// }
// }
// override layout(dimension: Dimension): void {
// // 实现布局逻辑
// }
// override clearInput(): void {
// if (this.container) {
// this.container.innerHTML = '';
// }
// super.clearInput();
// }
// }
import { EditorPane } from '../../../../browser/parts/editor/editorPane.js';
import { Dimension } from '../../../../../base/browser/dom.js';
import { StockDetailsInput } from './StockDetailsInput.js';
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
import { IStorageService } from '../../../../../platform/storage/common/storage.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { IEditorOptions } from '../../../../../platform/editor/common/editor.js';
import { IEditorOpenContext } from '../../../../common/editor.js';
import { IEditorGroup } from '../../../../services/editor/common/editorGroupsService.js';
export class StockDetailsEditor extends EditorPane {
static readonly ID = 'workbench.editor.stockDetails';
private container: HTMLElement | null = null;
constructor(
group: IEditorGroup,
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService
) {
super(StockDetailsEditor.ID, group, telemetryService, themeService, storageService);
}
/**
*
*/
protected createEditor(parent: HTMLElement): void {
this.container = document.createElement('div');
this.container.className = 'stock-details-editor';
// 添加占位符
const placeholder = document.createElement('div');
placeholder.className = 'stock-placeholder';
placeholder.textContent = '请选择一只股票查看详情';
this.container.appendChild(placeholder);
parent.appendChild(this.container);
}
/**
*
*/
// override async setInput(
// input: StockDetailsInput,
// options: IEditorOptions | undefined,
// context: IEditorOpenContext,
// token: CancellationToken
// ): Promise<void> {
// await super.setInput(input, options, context, token);
// if (input.resource) {
// const stockCode = input.resource.path;
// // 清空现有内容并加载新的股票详情。
// this.renderStockDetails(stockCode);
// } else {
// this.renderErrorMessage('未提供有效的股票代码');
// }
// }
override async setInput(
input: StockDetailsInput,
options: IEditorOptions | undefined,
context: IEditorOpenContext,
token: CancellationToken
): Promise<void> {
await super.setInput(input, options, context, token);
if (input.resource) {
console.log(`setInput 接收到资源: ${input.resource.toString()}`);
const stockCode = input.resource.toString();
if (!stockCode || stockCode.trim() === '') {
this.renderErrorMessage('股票代码为空,请选择有效的股票。');
return;
}
this.renderStockDetails(stockCode);
} else {
this.renderErrorMessage('未提供有效的股票代码');
}
}
/**
*
*/
private renderStockDetails(stockCode: string): void {
if (!this.container) {
return;
}
// 清空容器内容
this.container.textContent = ''; // 不使用 innerHTML
// 创建标题
const title = document.createElement('h3');
title.textContent = '股票详情';
// 创建股票代码段
const codeInfo = document.createElement('p');
codeInfo.textContent = `代码: ${stockCode}`;
// 模拟价格信息
const priceInfo = document.createElement('p');
priceInfo.textContent = '价格: ¥123.45';
// 模拟涨跌幅信息
const changeInfo = document.createElement('p');
changeInfo.textContent = '涨跌幅: +3.21%';
// 添加到容器
this.container.appendChild(title);
this.container.appendChild(codeInfo);
this.container.appendChild(priceInfo);
this.container.appendChild(changeInfo);
}
/**
*
*/
private renderErrorMessage(message: string): void {
if (!this.container) {
return;
}
this.container.textContent = ''; // 清空之前的内容
const errorDiv = document.createElement('div');
errorDiv.className = 'stock-error';
errorDiv.textContent = message;
this.container.appendChild(errorDiv);
}
/**
*
*/
override layout(dimension: Dimension): void {
if (this.container) {
this.container.style.width = `${dimension.width}px`;
this.container.style.height = `${dimension.height}px`;
}
}
/**
*
*/
override clearInput(): void {
if (this.container) {
this.container.textContent = ''; // 不使用 innerHTML
}
super.clearInput();
}
}

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from '../../../../../base/common/uri.js';
import { EditorInput } from '../../../../common/editor/editorInput.js';
export class StockDetailsInput extends EditorInput {
static readonly ID = 'workbench.stockDetails.input';
// 修改为 public
public readonly resource: URI;
constructor(resource: URI) {
super();
// 打印传入的 URI无论是否有效
console.log(`StockDetailsInput 创建: ${resource ? resource.toString() : 'undefined'}`);
if (!resource || !resource.path || resource.path.trim() === '') {
// 如果 URI 无效,仅输出警告,不抛出异常
console.warn(`StockDetailsInput: 收到无效的资源路径 ${resource ? resource.toString() : 'undefined'}`);
}
this.resource = resource;
}
override get typeId(): string {
return StockDetailsInput.ID;
}
override getName(): string {
return `Stock Details (${this.resource.path})`;
}
override matches(other: EditorInput): boolean {
return other instanceof StockDetailsInput && this.resource.toString() === other.resource.toString();
}
}

@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IViewPaneOptions, ViewPane } from '../../../../browser/parts/views/viewPane.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { IViewDescriptorService } from '../../../../common/views.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
import { IAccessibleViewService } from '../../../../../platform/accessibility/browser/accessibleView.js';
import { IHoverService } from '../../../../../platform/hover/browser/hover.js';
import * as DOM from '../../../../../base/browser/dom.js';
import { URI } from '../../../../../base/common/uri.js';
import './media/exampleViewContainer.css';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { StockDetailsInput } from './StockDetailsInput.js';
export class ExampleView extends ViewPane {
constructor(
options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IInstantiationService instantiationService: IInstantiationService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@ITelemetryService telemetryService: ITelemetryService,
@IHoverService hoverService: IHoverService,
@IAccessibleViewService accessibleViewService: IAccessibleViewService
) {
super(
options,
keybindingService,
contextMenuService,
configurationService,
contextKeyService,
viewDescriptorService,
instantiationService,
openerService,
themeService,
telemetryService,
hoverService,
);
}
// 您可以在这里添加自定义的视图逻辑
// override renderBody(container: HTMLElement): void {
// super.renderBody(container);
// // 创建一个用于股票列表的容器
// const stockListContainer = DOM.$('.stock-list-container');
// stockListContainer.style.padding = '10px';
// stockListContainer.style.overflowY = 'auto';
// stockListContainer.style.maxHeight = '100%';
// // 模拟股票列表
// const mockStockData = [
// { name: 'Stock A', code: '000001', price: '100.00' },
// { name: 'Stock B', code: '000002', price: '200.00' },
// { name: 'Stock C', code: '000003', price: '300.00' },
// { name: 'Stock D', code: '000004', price: '400.00' },
// { name: 'Stock E', code: '000005', price: '500.00' },
// ];
// // 创建列表元素
// const ul = DOM.$('ul');
// ul.style.listStyleType = 'none';
// ul.style.margin = '0';
// ul.style.padding = '0';
// // 遍历伪数据并创建每个列表项
// mockStockData.forEach(stock => {
// const li = DOM.$('li');
// li.style.display = 'flex';
// li.style.justifyContent = 'space-between';
// li.style.padding = '8px';
// li.style.borderBottom = '1px solid #ccc';
// const name = DOM.$('span');
// name.textContent = `${stock.name} (${stock.code})`;
// const price = DOM.$('span');
// price.textContent = `¥${stock.price}`;
// li.appendChild(name);
// li.appendChild(price);
// ul.appendChild(li);
// });
// // 将列表添加到容器中
// stockListContainer.appendChild(ul);
// container.appendChild(stockListContainer);
// }
override renderBody(container: HTMLElement): void {
super.renderBody(container);
const ul = DOM.$('ul'); // 创建一个无序列表
ul.classList.add('stock-list'); // 为列表添加样式类
const data = [
{ name: 'Stock A', code: '000001', price: 100 },
{ name: 'Stock B', code: '000002', price: 200 },
{ name: 'Stock C', code: '000003', price: 300 },
{ name: 'Stock D', code: '000004', price: 400 },
{ name: 'Stock E', code: '000005', price: 500 },
]; // 假数据
for (const stock of data) {
const li = DOM.$('li');
li.textContent = `${stock.name} (${stock.code}) ¥${stock.price.toFixed(2)}`;
li.classList.add('stock-item');
li.addEventListener('click', () => {
this.openStockDetails(stock); // 点击时打开股票详情
});
ul.appendChild(li);
}
container.appendChild(ul); // 将列表附加到容器中
}
// private openStockDetails(stock: { name: string; code: string; price: number }): void {
// const uri = URI.parse(`stock-details://${stock.code}`); // 自定义 URI
// const editorInput = new StockDetailsInput(uri); // 创建新的输入实例
// const editorService = this.instantiationService.invokeFunction(accessor => accessor.get(IEditorService)); // 获取编辑器服务
// editorService.openEditor(editorInput, { pinned: true }).then(() => { // 打开编辑器
// console.log(`Opened details for ${stock.name}`);
// });
// }
private openStockDetails(stock: { name: string; code: string; price: number }): void {
if (!stock.code || stock.code.trim() === '') {
console.error('无法打开股票详情:股票代码为空');
return;
}
console.log(`准备打开股票详情: ${stock.name} (${stock.code})`);
const uri = URI.parse(`stock-details://${stock.code}`); // 自定义 URI
const editorInput = new StockDetailsInput(uri); // 创建新的输入实例
const editorService = this.instantiationService.invokeFunction(accessor => accessor.get(IEditorService)); // 获取编辑器服务
editorService.openEditor(editorInput, { pinned: true }).then(() => {
console.log(`成功打开股票详情: ${stock.name} (${stock.code})`);
}).catch(err => {
console.error(`打开股票详情失败: ${err.message}`);
});
}
override layoutBody(height: number, width: number): void {
// 布局逻辑,如果需要调整高度和宽度
}
}

@ -3,17 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ViewPaneContainer } from '../../../browser/parts/views/viewPaneContainer.js'; import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { IExtensionService } from '../../../services/extensions/common/extensions.js'; import { IExtensionService } from '../../../../services/extensions/common/extensions.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js'; import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
import { IThemeService } from '../../../../platform/theme/common/themeService.js'; import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
import { IStorageService } from '../../../../platform/storage/common/storage.js'; import { IStorageService } from '../../../../../platform/storage/common/storage.js';
import { IWorkbenchLayoutService } from '../../../services/layout/browser/layoutService.js'; import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js'; import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
import { IViewDescriptorService } from '../../../common/views.js'; import { IViewDescriptorService } from '../../../../common/views.js';
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
export class ExampleViewContainer extends ViewPaneContainer { export class ExampleViewContainer extends ViewPaneContainer {
@ -30,7 +30,7 @@ export class ExampleViewContainer extends ViewPaneContainer {
@IWorkspaceContextService contextService: IWorkspaceContextService @IWorkspaceContextService contextService: IWorkspaceContextService
) { ) {
super( super(
'exampleViewContainer', 'workbench.view.example',
{ mergeViewWithContainerWhenSingleView: true }, { mergeViewWithContainerWhenSingleView: true },
instantiationService, instantiationService,
configurationService, configurationService,
@ -44,4 +44,9 @@ export class ExampleViewContainer extends ViewPaneContainer {
viewDescriptorService viewDescriptorService
); );
} }
override getTitle(): string {
return ''; // 返回空字符串,确保没有容器标题和冒号
}
} }

@ -0,0 +1,16 @@
.stock-list {
list-style: none;
padding: 0;
margin: 0;
}
.stock-item {
padding: 10px;
border-bottom: 1px solid #ddd;
cursor: pointer;
transition: background 0.2s;
}
.stock-item:hover {
background: #f0f0f0;
}