diff --git a/README.md b/README.md index 9cdeb14af08e80e48461b96fa83f49f96fdd997f..40d9225362f865aeef0d9d8d3611827151a6f0bc 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@

iStock Shell v0.1

一个金融数据查询命令终端

它简单、高效、灵活,并很容易定制自己的金融数据查询工具。

+

+ 演示 + 文档 +

--- @@ -22,7 +26,7 @@ #### 技术栈 -`svelte + typescript + vite` +`typescript + svelte + vite` ### 内置功能 @@ -37,14 +41,13 @@ ### 迭代计划 -**03月04日-03月10日:** +**03月18日-03月24日:** | 任务项目 | 描述 | 该迭代是否上线 | | :--------------: | :----------------------------: | :------------: | | 金融网站导航命令 | 用命令获取对应分类金融网站列表 | 是 | -| 添加命令别名 | 命令输入返回后可添加命令别名 | 是 | -**03月11日-03月17日预计任务项:** +**03月25日-03月31日预计任务项:** 暂无安排 diff --git a/docs/.vitepress/config/theme-nav.ts b/docs/.vitepress/config/theme-nav.ts index eaa52a4d0c18e129fa6180f3934c0cb2bbd683b6..fbc1a6419ef2e73b5ae54f04f00ee411d40d5058 100644 --- a/docs/.vitepress/config/theme-nav.ts +++ b/docs/.vitepress/config/theme-nav.ts @@ -36,7 +36,7 @@ export default [ { text: '更多', items: [ - { text: '更新日志', link: 'https://gitee.com/xcbclc/istock-shell', target: '_blank' }, + { text: '更新日志', link: 'https://gitee.com/xcbclc/istock-shell/releases', target: '_blank' }, /*{ text: '沟通交流', link: 'https://share.istock.red/' },*/ { text: '博客', diff --git a/docs/use/quick-start/easy-to-use.md b/docs/use/quick-start/easy-to-use.md index 95255890042937914ce950b8c1902d20ec9e81c3..8251cd0b60148a2ed3ed5d1a57589f2251a01685 100644 --- a/docs/use/quick-start/easy-to-use.md +++ b/docs/use/quick-start/easy-to-use.md @@ -50,3 +50,17 @@ 图例待补充 通过上述步骤,您应该能够开始使用`iStock Shell`进行基本的数据查询和分析。随着对命令的熟悉,您将能够发挥出`iStock Shell`的更多潜力,满足您对金融数据查询和分析的需求。 + +## 命令别名 + +有时候,我们需要频繁输入的命令可能会很长,每次都输入会降低效率。这时候,给这些长命令取一个别名就很有用。 + +举个例子,假设你经常需要输入这样一段命令:`lshqsj -gpmc 贵州茅台 -ksrq 20100101 -sjzq 月 | tb gplzt -x 日期 -y1 开盘,收盘 -y2 最高,最低`, +这段命令有点长,如果每次都要输入,未免太费事了。我们可以给这个命令取一个别名,比如叫做`行情茅台`。 +设置别名的方法很简单: +1. 在命令输出区域,鼠标右键选中`添加别名`菜单,或者使用快捷键`ctrl+alt+a`。 +2. 弹出别名设置菜单,设置别名为`行情茅台`。 + +设置成功后,下次你只需要在命令输入框中输入`:行情`,按`tab`键就能在自动补全列表选择`行情茅台`别名,选择该别名后就会输入对应的命令了。 + +通过设置命令别名,你可以更轻松地使用iStock Shell,不再需要反复输入那些繁长的命令,提高你的工作效率。 diff --git a/src/packages/cli/src/action/cmd-init.mjs b/src/packages/cli/src/action/cmd-init.mjs index ef789438374d4d6e1f039f2ff8b39b8833c0d530..41a843781bb00d6860b27137f6ab81a2b73b4b17 100644 --- a/src/packages/cli/src/action/cmd-init.mjs +++ b/src/packages/cli/src/action/cmd-init.mjs @@ -23,7 +23,8 @@ export default async () => { { type: 'input', name: 'name', - message: '您期望命令相关文件名为?', + message: '您期望命令相关文件名为?(文件名用-符号分割)', + validate: (input) => { if (!input) return false; return nameReg.test(input); @@ -34,6 +35,7 @@ export default async () => { const templatePath = path.resolve(cwdPath, './src/packages/cli/src/template/cmd'); const outputDir = path.resolve(cwdPath, `./src/worker/domains/${domain}`); const className = toClassName(name); + const instanceName = className[0].toLowerCase() + className.slice(1); const fileName = toFileName(name); const dirPath = path.resolve(outputDir, fileName); @@ -60,8 +62,9 @@ export default async () => { const renderedContent = ejs.render(templateContent, { name, className, + classAlias: instanceName, fileName, - instanceName: className[0].toLowerCase() + className.slice(1), + instanceName, modelName: toModelName(name), }); fs.writeFileSync(filePath, renderedContent); diff --git a/src/packages/cli/src/template/cmd/tpl.controller.ejs b/src/packages/cli/src/template/cmd/tpl.controller.ejs index 20ac02738098dca078ba79bb9cfe78d6195c31e1..2cdf97fda9993baf580d3804797680d03016093b 100644 --- a/src/packages/cli/src/template/cmd/tpl.controller.ejs +++ b/src/packages/cli/src/template/cmd/tpl.controller.ejs @@ -4,7 +4,7 @@ import { <%= className %>Service } from './<%= fileName %>.service'; import cmdJson from './<%= fileName %>.cmd'; @Controller({ - alias: '<%= fileName %>', + alias: '<%= classAlias %>', }) export class <%= className %>Controller { constructor(private readonly <%= instanceName %>Service: <%= className %>Service) {} diff --git a/src/packages/cli/src/utils/name.mjs b/src/packages/cli/src/utils/name.mjs index de0b49da493cf9ccf59082e38b4ccf8f2fb3ea4f..64af352eb1e7922979fb3f5eb8e78c2432defa1d 100644 --- a/src/packages/cli/src/utils/name.mjs +++ b/src/packages/cli/src/utils/name.mjs @@ -4,7 +4,7 @@ * @returns {*} */ export function toFileName(str) { - if (!str) return; + if (!str) return ''; // 使用正则表达式将驼峰命名转换为破折号分隔 return str.replace(/[A-Z]/g, (match, index) => { // 如果匹配到的大写字母是字符串的首字母,则直接返回小写字母 @@ -22,7 +22,7 @@ export function toFileName(str) { * @returns {*} */ export function toClassName(str) { - if (!str) return; + if (!str) return ''; return str .split('-') .map((v) => { @@ -33,11 +33,11 @@ export function toClassName(str) { } /** - * m转换成模型名 + * 转换成模型名 * @param str * @returns {*} */ export function toModelName(str) { - if (!str) return; - return str.replace('-', '_'); + if (!str) return ''; + return str.replaceAll('-', '_'); } diff --git a/src/packages/command-parser/src/tokenizer.ts b/src/packages/command-parser/src/tokenizer.ts index 43bd1916b477bb8ea7f61025f61f881396cb2aef..c7015c81ad33815403c090bf9e5a135224f59ec9 100644 --- a/src/packages/command-parser/src/tokenizer.ts +++ b/src/packages/command-parser/src/tokenizer.ts @@ -23,6 +23,21 @@ export type TTokenMethodResult = { index: number; }; +export const keyCommand = { + ai: { + command: 'ai:', + content: /^.$/, + }, + search: { + command: 'ss:', + content: /^.$/, + }, + alias: { + command: ':', + content: /^.$/, + }, +}; + export class Tokenizer { // 空格 // eslint-disable-next-line no-irregular-whitespace @@ -33,16 +48,7 @@ export class Tokenizer { readonly #lineR = /^\r$/; readonly #lineN = /^\n$/; // 关键词 - readonly #keyCommand = { - ai: { - command: 'ai:', - content: /^[^()()|&]$/, - }, - search: { - command: 'ss:', - content: /^[^()()|&]$/, - }, - }; + readonly #keyCommand = keyCommand; // 圆括号字符 readonly #parenthesesLeft = /^[((]$/; @@ -306,10 +312,26 @@ export class Tokenizer { * @private */ #tokenizerKeywords(tokens: TToken[], input: string, index: number): TTokenMethodResult { - const { ai, search } = this.#keyCommand; + const { ai, search, alias } = this.#keyCommand; let char = input[index]; + if (index === 0 && alias.command[0] === char) { + tokens.push({ type: ETokenType.keyCommand, value: alias.command }); + index += alias.command.length; + char = input[index]; + if (char) { + let content = ''; + while (alias.content.test(char)) { + content += char; + char = input[++index]; + } + if (content) { + tokens.push({ type: ETokenType.keyCommandContent, value: content.trim() }); + } + } + return { isContinue: true, index }; + } // ai关键字匹配 - if (ai.command[0] === char) { + if (index === 0 && ai.command[0] === char) { const command = input.substring(index, ai.command.length); if (command === ai.command) { tokens.push({ type: ETokenType.keyCommand, value: command }); @@ -330,7 +352,7 @@ export class Tokenizer { } // search关键字匹配 - if (search.command[0] === char) { + if (index === 0 && search.command[0] === char) { const command = input.substring(index, search.command.length); if (command === search.command) { tokens.push({ type: ETokenType.keyCommand, value: command }); diff --git a/src/packages/editor/src/command-editor.ts b/src/packages/editor/src/command-editor.ts index 59303ae301025aec7e1572808700fff7fd6c1e1a..3a72ac591c33ea45b12a9161ab6d35a36934f956 100644 --- a/src/packages/editor/src/command-editor.ts +++ b/src/packages/editor/src/command-editor.ts @@ -266,7 +266,6 @@ export class CommandEditor { * @param str */ handleCommandInputAppend(str: string) { - this.commandInput.focus(); let input = this.input; let offsetText = this.getCursorOffsetText(); const isInputEl = this.#cursor.getOneRange().endContainer === this.#commandInput; diff --git a/src/packages/iswork/src/cmdp/abstract-cmdp.ts b/src/packages/iswork/src/cmdp/abstract-cmdp.ts index 0e3f6b1a0724e58f7517303d29d170e11e4328bb..c901176cc03c32c876682d2e14a3fc2024a144d4 100644 --- a/src/packages/iswork/src/cmdp/abstract-cmdp.ts +++ b/src/packages/iswork/src/cmdp/abstract-cmdp.ts @@ -64,7 +64,10 @@ export abstract class AbstractCmdp { */ protected initByAddress(address: string) { if (!this.check(address)) { - throw new ScopeError(`iswork.${this.constructor.name}`, `校验地址失败,请传入正确的"${this.protocol}"协议地址`); + throw new ScopeError( + `iswork.${this.constructor.name}`, + `校验地址失败,请传入正确的"${this.protocol}"协议地址,地址:${address}` + ); } const info = this.parse(address); this.address = address; diff --git a/src/packages/iswork/src/orm/driver/indexedDB/indexedDB-runner.ts b/src/packages/iswork/src/orm/driver/indexedDB/indexedDB-runner.ts index 0da498b7ff16613e9dbd8b7a9b6d1db00c23c5e3..de429c8d7572eba598eb7409c5ec581b726bf395 100644 --- a/src/packages/iswork/src/orm/driver/indexedDB/indexedDB-runner.ts +++ b/src/packages/iswork/src/orm/driver/indexedDB/indexedDB-runner.ts @@ -163,6 +163,9 @@ export class IndexedDBRunner extends AbstractRunner { return data[field] <= value; case 'in': return isArray(value) && value.includes(data[field]); + case 'cont': + if (isString(value) && isString(data[field])) return data[field].includes(value); + return false; default: return false; } diff --git a/src/packages/iswork/src/orm/model/base-model.ts b/src/packages/iswork/src/orm/model/base-model.ts index 3f60af6f9f51f3000d84d0ec38c4b8c7630324ee..b0d69affe529401e5b620901ba7b00da6ede9dec 100644 --- a/src/packages/iswork/src/orm/model/base-model.ts +++ b/src/packages/iswork/src/orm/model/base-model.ts @@ -129,4 +129,12 @@ export class BaseModel implements IBaseModel { const rep = await this.getRepository(); return await rep.deleteById(this, id); } + + static async findOneById( + this: Model, + id: string | number + ): Promise>> { + const rep = await this.getRepository(); + return (await rep.findOneById(this, id)) as TModelData>; + } } diff --git a/src/packages/iswork/src/orm/repository/repository.ts b/src/packages/iswork/src/orm/repository/repository.ts index aedc9fc5fe1824bd98971464b40797090435297e..8d4630f172429c0b6eeccb88a8b744f912fda27b 100644 --- a/src/packages/iswork/src/orm/repository/repository.ts +++ b/src/packages/iswork/src/orm/repository/repository.ts @@ -47,4 +47,11 @@ export class Repository { filter: ['id', 'eq', id], }); } + + async findOneById(model: TModelType, id: string | number): Promise { + const list = await this.#repositoryManager.query(model, { + filter: ['id', 'eq', id], + }); + return list?.length ? list[0] : null; + } } diff --git a/src/packages/shell-ui/src/button/Button.svelte b/src/packages/shell-ui/src/button/Button.svelte new file mode 100644 index 0000000000000000000000000000000000000000..c39591844701f69e331baec3689a99a7bc307700 --- /dev/null +++ b/src/packages/shell-ui/src/button/Button.svelte @@ -0,0 +1,88 @@ + + + + + + diff --git a/src/packages/shell-ui/src/button/index.ts b/src/packages/shell-ui/src/button/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e34d1bb8128032855f978068a0dd9bcbeaf66396 --- /dev/null +++ b/src/packages/shell-ui/src/button/index.ts @@ -0,0 +1,3 @@ +import Button from './Button.svelte'; +export const ShButton = Button; +export default ShButton; diff --git a/src/packages/shell-ui/src/formitem/FormItem.svelte b/src/packages/shell-ui/src/formitem/FormItem.svelte index f4c0c44fc4749180bcc441273cf0c351ac1946ec..1eadee585aa361abba99ab020fbec27c4199d320 100644 --- a/src/packages/shell-ui/src/formitem/FormItem.svelte +++ b/src/packages/shell-ui/src/formitem/FormItem.svelte @@ -1,3 +1,15 @@ -
+
+ + diff --git a/src/packages/shell-ui/src/index.ts b/src/packages/shell-ui/src/index.ts index 4b18798c2c2a9e6f7393fee71132757e25205a8a..599f65250490e23217d4404b4e07b0ab549c71ca 100644 --- a/src/packages/shell-ui/src/index.ts +++ b/src/packages/shell-ui/src/index.ts @@ -14,3 +14,5 @@ export * from './select/index'; export * from './table/index'; export * from './text/index'; export * from './textarea/index'; +export * from './modal/index'; +export * from './button/index'; diff --git a/src/packages/shell-ui/src/input/Input.svelte b/src/packages/shell-ui/src/input/Input.svelte index da5200456cee42fe950470f1791425a4a5f6e25d..c01c1a27faa3a6ab193378ed9bbd1444715b6131 100644 --- a/src/packages/shell-ui/src/input/Input.svelte +++ b/src/packages/shell-ui/src/input/Input.svelte @@ -4,7 +4,30 @@ export let attribute: Record = {}; -