从零开始写一套广告组件【二】项目规范和项目管理

简介: 在这一章我们进行一个简单的项目规范和项目管理,为了更好的代码协同,我们选择使用 Git 对代码进行管理并通过一系列 npm 包配置相应的规范约束。

前言

在这一章我们进行一个简单的项目规范和项目管理,为了更好的代码协同,我们选择使用 Git 对代码进行管理并通过一系列 npm 包配置相应的规范约束。

内容

本章内容涉及到的文档如下:

githttps://git-scm.com/

editorconfighttps://editorconfig.org/

EditorConfig for VS CodeEditorConfig for VS Code

配置Git

让我们打开项目,运行终端,现在开始执行第一个命令,让我们初始化 Git 仓库:

$ git init   
已初始化空的 Git 仓库于 /Users/wangyang/Documents/eaui/.git/

接下来,让我们来添加远程仓库地址,并进行第一次初始化提交:

$ git remote add origin git@github.com:oyo-cool/eaui.git
$ git add .
$ git commit -m "init"  
$ git push --set-upstream origin main

如果没有出现意外的话,您将会看到类似以下的输出:

枚举对象中: 47, 完成.
对象计数中: 100% (47/47), 完成.
使用 12 个线程进行压缩
压缩对象中: 100% (42/42), 完成.
写入对象中: 100% (47/47), 64.76 KiB | 9.25 MiB/s, 完成.
总共 47(差异 0),复用 31(差异 0),包复用 0(来自  0 个包)
To github.com:oyo-cool/eaui.git
 + 150853f...d4e3320 main -> main (forced update)
分支 'main' 设置为跟踪 'origin/main'。

恭喜您,现在您已经成功的将代码推送到了远程的仓库上!不过这里还没有结束,我们避免不同系统之间的换行符的差异,所以这里我们增加个配置 .gitattributes,内容如下:

*.ts    text eol=lf
*.vue   text eol=lf
*.tsx   text eol=lf
*.jsx   text eol=lf
*.html  text eol=lf
*.json  text eol=lf

配置规则

现在 Git 已经配置完成,接下来让我们一起来配置下项目的规范和约束,当前在最开始初始化项目的时候,vue脚手架已经帮我们配置好了ESlintPrettier ,不过那样远远不够,现在让我们根据自己的规则再来进行完善。

配置.editorconfig

EditorConfig 有助于为跨各种编辑器和 IDE 处理同一项目的多个开发人员保持一致的编码样式。

root = true

[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

[*.{ts,js,vue,css}]
indent_size = 2

配置Prettier

首先我们先对 Prettier 进行更新,强迫症患者又加上是新项目,刚好看看有没有坑,可以提前踩踩。

$ pnpm up prettier

更新完成后,我们移除当前项目中的配置文件.prettierrc.json ,创建一个新的配置文件 .prettierrc.js ,配置内容如下:

export default {
   
  // 一行最多 120 字符..
  printWidth: 120,
  // 使用 2 个空格缩进
  tabWidth: 2,
  // 不使用缩进符,而使用空格
  useTabs: false,
  // 行尾需要有分号
  semi: true,
  // 使用单引号
  singleQuote: true,
  // 对象的 key 仅在必要时用引号
  quoteProps: 'as-needed',
  // jsx 不使用单引号,而使用双引号
  jsxSingleQuote: false,
  // 末尾需要有逗号
  trailingComma: 'all',
  // 大括号内的首尾需要空格
  bracketSpacing: true,
  // jsx 标签的反尖括号需要换行
  jsxBracketSameLine: false,
  // 箭头函数,只有一个参数的时候,也需要括号
  arrowParens: 'always',
  // 每个文件格式化的范围是文件的全部内容
  rangeStart: 0,
  rangeEnd: Infinity,
  // 不需要写文件开头的 @prettier
  requirePragma: false,
  // 不需要自动在文件开头插入 @prettier
  insertPragma: false,
  // 使用默认的折行标准
  proseWrap: 'preserve',
  // 根据显示样式决定 html 要不要折行
  htmlWhitespaceSensitivity: 'css',
  // vue 文件中的 script 和 style 内不用缩进
  vueIndentScriptAndStyle: false,
  // 换行符使用 lf
  endOfLine: 'lf',
};

配置好之后,让我们来试试运行命令会有什么样的结果:

$ pnpm format 
> eaui@0.0.0 format /Users/wangyang/Documents/eaui
> prettier --write src/

src/App.vue 57ms (unchanged)
src/assets/base.css 6ms (unchanged)
src/assets/main.css 2ms (unchanged)
src/components/__tests__/HelloWorld.spec.ts 7ms (unchanged)
src/components/HelloWorld.vue 21ms (unchanged)
src/components/icons/IconCommunity.vue 3ms (unchanged)
src/components/icons/IconDocumentation.vue 1ms (unchanged)
src/components/icons/IconEcosystem.vue 2ms (unchanged)
src/components/icons/IconSupport.vue 1ms (unchanged)
src/components/icons/IconTooling.vue 1ms (unchanged)
src/components/TheWelcome.vue 7ms (unchanged)
src/components/WelcomeItem.vue 5ms (unchanged)
src/main.ts 3ms (unchanged)
src/stores/counter.ts 4ms (unchanged)

好的,很完美,目前看来好像没有出现什么坑。

配置ESlint

这里我们需要安装的插件比较多,不过其中有一些是脚手架已经安装过的,每个插件具体的用处,可查看对应的文档来了解,这里不一一解释。

eslint-config-prettierhttps://www.npmjs.com/package/eslint-config-prettier

eslint-plugin-prettierhttps://www.npmjs.com/package/eslint-plugin-prettier

eslint-plugin-playwrighthttps://www.npmjs.com/package/eslint-plugin-playwright

eslint-plugin-vue-scoped-csshttps://www.npmjs.com/package/eslint-plugin-vue-scoped-css

eslint-plugin-vuehttps://www.npmjs.com/package/eslint-plugin-vue

eslint-config-airbnb-basehttps://www.npmjs.com/package/eslint-config-airbnb-base

eslint-config-typescripthttps://www.npmjs.com/package/@vue/eslint-config-typescript

@typescript-eslint/eslint-pluginhttps://www.npmjs.com/package/@typescript-eslint/eslint-plugin

eslint-plugin-simple-import-sorthttps://www.npmjs.com/package/eslint-plugin-simple-import-sort

@typescript-eslint/parserhttps://www.npmjs.com/package/@typescript-eslint/parser

@typescript-eslint/eslint-pluginhttps://www.npmjs.com/package/@typescript-eslint/eslint-plugin

pnpm add -D eslint-config-prettier eslint-plugin-prettier eslint-plugin-playwright eslint-plugin-vue-scoped-css eslint-plugin-vue eslint-config-airbnb-base @vue/eslint-config-typescript @typescript-eslint/eslint-plugin eslint-plugin-simple-import-sort @typescript-eslint/parser @typescript-eslint/eslint-plugin

安装完插件后,我们先配置下忽略文件 .eslintignore

dist
lib
es
esm
node_modules
static
cypress
playwright-report
tests-results
static/
!.prettierrc.js

配置好忽略文件之后,先移除当前的配置文件 .eslintrc.cjs,再创建一个全新的配置文件 .eslintrc ,内容如下:

{
   
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "eslint-config-airbnb-base",
    "@vue/typescript/recommended",
    "plugin:vue/vue3-recommended",
    "plugin:vue-scoped-css/base",
    "plugin:prettier/recommended",
  ],
  "env": {
   
    "browser": true,
    "node": true,
    "jest": true,
    "es6": true,
  },
  "globals": {
   
    "defineProps": "readonly",
    "defineEmits": "readonly",
  },
  "plugins": ["vue", "@typescript-eslint", "simple-import-sort"],
  "parserOptions": {
   
    "parser": "@typescript-eslint/parser",
    "sourceType": "module",
    "allowImportExportEverywhere": true,
    "ecmaFeatures": {
   
      "jsx": true,
    },
  },
  "settings": {
   
    "import/extensions": [".js", ".jsx", ".ts", ".tsx"],
    "eslint.packageManager": "pnpm",
  },
  "rules": {
   
    "no-console": "off",
    "no-continue": "off",
    "no-restricted-syntax": "off",
    "no-plusplus": "off",
    "no-param-reassign": "off",
    "no-shadow": "off",
    "guard-for-in": "off",
    "camelcase": [
      "error",
      {
   
        "ignoreDestructuring": true,
        "properties": "never",
      },
    ],

    "import/extensions": "off",
    "import/no-unresolved": "off",
    "import/no-extraneous-dependencies": "off",
    "import/prefer-default-export": "off",
    "import/first": "off", // https://github.com/vuejs/vue-eslint-parser/issues/58
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "vue/first-attribute-linebreak": 0,
    "class-methods-use-this": "off",

    "@typescript-eslint/no-unused-vars": [
      "error",
      {
   
        "argsIgnorePattern": "^_",
        "varsIgnorePattern": "^_",
      },
    ],
    "no-unused-vars": [
      "error",
      {
   
        "argsIgnorePattern": "^_",
        "varsIgnorePattern": "^_",
      },
    ],
    "no-use-before-define": "off",
    "@typescript-eslint/no-use-before-define": "off",
    "@typescript-eslint/ban-ts-comment": "off",
    "@typescript-eslint/ban-types": "off",
    "simple-import-sort/imports": "error",
    "simple-import-sort/exports": "error",
  },
  "overrides": [
    {
   
      "files": [
        "e2e/**/*.{test,spec}.{js,ts,jsx,tsx}"
      ],
      "extends": [
        "plugin:playwright/recommended"
      ]
    },
    {
   
      "files": ["*.vue"],
      "rules": {
   
        "vue/component-name-in-template-casing": [2, "kebab-case"],
        "vue/require-default-prop": 0,
        "vue/multi-word-component-names": 0,
        "vue/no-reserved-props": 0,
        "vue/no-v-html": 0,
        "vue-scoped-css/enforce-style-type": ["error", {
    "allows": ["scoped"] }],
      },
    },
    {
   
      "files": ["*.ts", "*.tsx"], // https://github.com/typescript-eslint eslint-recommended
      "rules": {
   
        "constructor-super": "off", // ts(2335) & ts(2377)
        "getter-return": "off", // ts(2378)
        "no-const-assign": "off", // ts(2588)
        "no-dupe-args": "off", // ts(2300)
        "no-dupe-class-members": "off", // ts(2393) & ts(2300)
        "no-dupe-keys": "off", // ts(1117)
        "no-func-assign": "off", // ts(2539)
        "no-import-assign": "off", // ts(2539) & ts(2540)
        "no-new-symbol": "off", // ts(2588)
        "no-obj-calls": "off", // ts(2349)
        "no-redeclare": "off", // ts(2451)
        "no-setter-return": "off", // ts(2408)
        "no-this-before-super": "off", // ts(2376)
        "no-undef": "off", // ts(2304)
        "no-unreachable": "off", // ts(7027)
        "no-unsafe-negation": "off", // ts(2365) & ts(2360) & ts(2358)
        "no-var": "error", // ts transpiles let/const to var, so no need for vars any more
        "prefer-const": "error", // ts provides better types with const
        "prefer-rest-params": "error", // ts provides better types with rest args over arguments
        "prefer-spread": "error", // ts transpiles spread to apply, so no need for manual apply
        "valid-typeof": "off", // ts(2367)
      },
    },
  ],
}

然后我们故意在代码中,做一些违反规则的代码,然后在 package.json 中的 scripts 增加以下命令:

{
   
"scripts": {
   
    "lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
    "lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
 }
}

然后我们在终端中运行命令 pnpm lint 进行验证:

 $ pnpm lint                                                                                                  
> eaui@0.0.0 lint /Users/wangyang/Documents/eaui
> eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./ --max-warnings 0

/Users/wangyang/Documents/eaui/src/App.vue
  7:7  error  Identifier 't_s' is not in camel case                                           camelcase
  7:7  error  't_s' is assigned a value but never used. Allowed unused vars must match /^_/u  @typescript-eslint/no-unused-vars
  7:7  error  't_s' is assigned a value but never used. Allowed unused vars must match /^_/u  no-unused-vars

✖ 3 problems (3 errors, 0 warnings)

 ELIFECYCLE  Command failed with exit code 1.

很好,现在看来我们配置的 ESlint 规则已经生效,那么我们接下来可以继续配置其他的规则了。

配置Stylelint

在配置之前,我们同样需要安装一些插件,插件列表如下:

stylelinthttps://stylelint.io/

stylelinthttps://www.npmjs.com/package/stylelint

stylelint-config-standardhttps://www.npmjs.com/package/stylelint-config-standard

stylelint-orderhttps://www.npmjs.com/package/stylelint-order

postcss-htmlhttps://www.npmjs.com/package/postcss-html

postcss-lesshttps://www.npmjs.com/package/postcss-less

$ pnpm add -D stylelint stylelint-config-standard stylelint-order postcss-html postcss-less

安装完成后,我们先增加一个忽略配置 .stylelintignore ,内容如下:

*.min.css

# 其他类型文件
*.js
*.jpg
*.woff

接下来就是正主了,添加配置文件 stylelint.config.js,内容如下:

export default {
   
  defaultSeverity: 'error',
  extends: ['stylelint-config-standard'],
  plugins: ['stylelint-less'],
  rules: {
   
    'no-descending-specificity': null,
    'import-notation': 'string',
    'no-empty-source': null,
    'custom-property-pattern': null,
    'selector-class-pattern': null,
    'selector-pseudo-class-no-unknown': [
      true,
      {
   
        ignorePseudoClasses: ['deep'],
      },
    ],
    'media-query-no-invalid': null, // https://stylelint.io/user-guide/rules/media-query-no-invalid/#options
  },
  overrides: [
    {
   
      files: ['**/*.html', '**/*.vue'],
      customSyntax: 'postcss-html',
    },
    {
   
      files: ['**/*.less'],
      customSyntax: 'postcss-less',
    },
  ],
};

最后在 package.json 中增加以下命令:

{
   
   "scripts": {
   
    "stylelint": "stylelint src/**/*.{html,vue,sass,less}",
    "stylelint:fix": "stylelint --fix src/**/*.{html,vue,vss,sass,less}",
   }
}

同样的,我们在代码中搞一些不符合规则的东西,然后再运行命令检查:

$ pnpm stylelint       

> eaui@0.0.0 stylelint /Users/wangyang/Documents/eaui
> stylelint src/**/*.{html,vue,sass,less}


src/components/WelcomeItem.vue
  34:3  ✖  Unexpected empty line before declaration  declaration-empty-line-before
  42:3  ✖  Unexpected empty line before declaration  declaration-empty-line-before

✖ 2 problems (2 errors, 0 warnings)
  2 errors potentially fixable with the "--fix" option.

 ELIFECYCLE  Command failed with exit code 2.

现在再让我们尝试下,运行修复命令看看是否可以正常修复:

$  pnpm stylelint:fix       

> eaui@0.0.0 stylelint:fix /Users/wangyang/Documents/eaui
> stylelint --fix src/**/*.{html,vue,css,sass,less}

好了,现在让我们继续开始下面的配置。

配置commitlint

老生常谈了,先安装插件,插件内容如下:

commitlint.jshttps://commitlint.js.org/

conventionalcommitshttps://www.conventionalcommits.org/zh-hans/v1.0.0/

clihttps://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/cli

config-conventionalhttps://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional

$ pnpm add -D  @commitlint/{cli,config-conventional}

安装完成后,我们来配置一下:

echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

为了更好的限制,我们增加下类型枚举,具体的含义建议直接访问 conventionalcommits 进行查看,配置内容如下:

export default {
   
  extends: ['@commitlint/config-conventional'],
  rules: {
   
    'type-enum': [
      2,
      'always',
      ['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', 'types'],
    ],
  },
};

不过这样好像还不够,接下来我们再通过 commitizen 来进行限制。

配置commitizen

cz-clihttps://commitizen.github.io/cz-cli/

$  pnpm add -D commitizen 
$  npx commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact

好了,接下来要到我们最关键的配置了,husky

配置husky

huskyhttps://github.com/typicode/husky

huskyhttps://www.npmjs.com/package/husky

lint-stagedhttps://github.com/lint-staged/lint-staged

lint-stagedhttps://www.npmjs.com/package/lint-staged

$ pnpm add -D husky lint-staged
$ pnpm exec husky init
$ echo "pnpm lint-staged" > .husky/pre-commit  
$ echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg
$ echo "exec < /dev/tty && npx cz --hook || true" > .husky/prepare-commit-msg

package.json 中增加 lint-staged 的配置,配置内容如下:

{
   
 "lint-staged": {
   
    "*.{js,jsx,vue,ts,tsx}": [
      "prettier --write",
      "pnpm lint:fix"
    ],
    "*.{html,vue,vss,sass,less}": [
      "pnpm stylelint:fix"
    ]
  }
}

测试

现在基本的已经配置完成,我们来进行看看。

$ git add .
$ git commit -m "this will fail"

运行结果如下:

✔ Preparing lint-staged...
⚠ Running tasks for staged files...
  ❯ package.json — 27 files
    ❯ *.{js,jsx,vue,ts,tsx} — 14 files
      ✔ prettier --write
      ✖ pnpm lint:fix [FAILED]
    ✔ *.{html,vue,vss,sass,less} — 4 files
↓ Skipped because of errors from tasks.
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...

✖ pnpm lint:fix:

> eaui@0.0.0 lint:fix /Users/wangyang/Documents/eaui
> eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix "/Users/wangyang/Documents/eaui/.prettierrc.js" "/Users/wangyang/Documents/eaui/commitlint.config.js" "/Users/wangyang/Documents/eaui/e2e/vue.spec.ts" "/Users/wangyang/Documents/eaui/playwright.config.ts" "/Users/wangyang/Documents/eaui/src/App.vue" "/Users/wangyang/Documents/eaui/src/components/HelloWorld.vue" "/Users/wangyang/Documents/eaui/src/components/TheWelcome.vue" "/Users/wangyang/Documents/eaui/src/components/WelcomeItem.vue" "/Users/wangyang/Documents/eaui/src/components/__tests__/HelloWorld.spec.ts" "/Users/wangyang/Documents/eaui/src/main.ts" "/Users/wangyang/Documents/eaui/src/stores/counter.ts" "/Users/wangyang/Documents/eaui/stylelint.config.js" "/Users/wangyang/Documents/eaui/vite.config.ts" "/Users/wangyang/Documents/eaui/vitest.config.ts"


/Users/wangyang/Documents/eaui/src/App.vue
  7:7  error  Identifier 't_s' is not in camel case                                           camelcase
  7:7  error  't_s' is assigned a value but never used. Allowed unused vars must match /^_/u  @typescript-eslint/no-unused-vars
  7:7  error  't_s' is assigned a value but never used. Allowed unused vars must match /^_/u  no-unused-vars

✖ 3 problems (3 errors, 0 warnings)

 ELIFECYCLE  Command failed with exit code 1.
husky - pre-commit script failed (code 1)

从结果来看已经生效了,现在让我们按着提示先把检测出来的问题进行修复,再次运行命令进行查看:

$ git add .
$ git commit -m "this will fail"
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
cz-cli@4.3.0, cz-conventional-changelog@3.3.0

? Select the type of change that you're committing: feat:     A new feature
? What is the scope of this change (e.g. component or file name): (press enter to skip) 
? Write a short, imperative tense description of the change (max 94 chars):
 (20) code and style rules
? Provide a longer description of the change: (press enter to skip)
 some rules for project 
? Are there any breaking changes? No
? Does this change affect any open issues? No
Packages: +108
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 108, reused 107, downloaded 1, added 108, done
[main 10f75d9] feat: code and style rules
 29 files changed, 3356 insertions(+), 239 deletions(-)
 create mode 100644 .editorconfig
 create mode 100644 .eslintignore
 create mode 100644 .eslintrc
 delete mode 100644 .eslintrc.cjs
 create mode 100644 .gitattributes
 create mode 100644 .husky/commit-msg
 create mode 100644 .husky/pre-commit
 create mode 100644 .husky/prepare-commit-msg
 create mode 100644 .prettierrc.js
 delete mode 100644 .prettierrc.json
 create mode 100644 .stylelintignore
 create mode 100644 commitlint.config.js
 create mode 100644 stylelint.config.js

 $ git push

  枚举对象中: 59, 完成.
  对象计数中: 100% (59/59), 完成.
  使用 12 个线程进行压缩
  压缩对象中: 100% (31/31), 完成.
  写入对象中: 100% (36/36), 43.00 KiB | 7.17 MiB/s, 完成.
  总共 36(差异 16),复用 3(差异 0),包复用 0(来自  0 个包)
  remote: Resolving deltas: 100% (16/16), completed with 16 local objects.
  To github.com:oyo-cool/eaui.git
     d4e3320..10f75d9  main -> main

完美,现在我们配置的所有规则都可以通过 husky 来进行触发了。

总结

在这一章里面,我们根据自己的情况配置了git.editorconfigPrettierESlintStylelint

commitlintcommitizenhusky ,在这些规则的约束下能让我们更好的协同,也能写出更好的代码。

目录
相关文章
|
JSON JavaScript 前端开发
OA会议管理系统之会议发布(内含原型图&项目介绍&多功能下拉框&源码)(三)
OA会议管理系统之会议发布(内含原型图&项目介绍&多功能下拉框&源码)(三)
76 0
|
JavaScript 前端开发 Java
OA会议管理系统之会议发布(内含原型图&项目介绍&多功能下拉框&源码)(二)
OA会议管理系统之会议发布(内含原型图&项目介绍&多功能下拉框&源码)(二)
68 0
|
2月前
|
敏捷开发 搜索推荐 小程序
项目管理神器呀!YesDev这款客户端太爱了!轻松管理上百个项目
对抗项目延期的利器:YesDev - YesDev是一款强大的项目管理工具,适合敏捷开发/DevOps/软件项目管理,提供网页在线版、PC桌面版(Windows/Mac)及微信小程序,支持多部门及团队协作。 - 特别推荐YesDev桌面客户端,便于快速启动应用并保持与在线版数据同步。 特性亮点 - 任务工时管理: 高效填写、报告和计算工时。 - 项目集管理: 方便地管理多个项目及其子项目,并可设置权限。 - 单个项目管理: 灵活配置项目组件,如文档、需求、问题追踪等。 - 多项目甘特图: 即时合成多个项目的甘特图,进行宏观分析和资源调配。 - 个性化工作台: 根据角色定制工作台显示内容。
|
4月前
|
存储 数据采集 机器学习/深度学习
知识管理:从文档到数据的技术之旅
【6月更文挑战第25天】知识管理正由文档转向数据,克服传统方式如信息碎片化和检索效率低下的问题。借助大数据和AI,实现知识体系化、智能检索和数据价值挖掘。技术路径涉及数据采集、存储、挖掘、分析及可视化。未来,知识图谱、智能问答系统和个性化推荐将推动知识管理进一步发展,提升企业竞争力。
|
3月前
|
人工智能 运维
学习若依的好地方,若依社区,好的运维,社区,也可以运营自己的社区,可以用于投放软件产品和海报展示,有空可以研究怎样运行社区,好的标题设计
学习若依的好地方,若依社区,好的运维,社区,也可以运营自己的社区,可以用于投放软件产品和海报展示,有空可以研究怎样运行社区,好的标题设计
|
5月前
|
JSON JavaScript 前端开发
仿造问卷星--开发一套调查问卷设计工具(1/3)--完整流程
仿造问卷星--开发一套调查问卷设计工具(1/3)--完整流程
83 0
|
5月前
|
JSON 数据格式
仿造问卷星--开发一套调查问卷设计工具(3/3)--完整流程
仿造问卷星--开发一套调查问卷设计工具(3/3)--完整流程
72 0
|
5月前
如何使用敏捷相关知识管理好自己的装修过程?
如何使用敏捷相关知识管理好自己的装修过程?
如何使用敏捷相关知识管理好自己的装修过程?
NFT卡牌游戏链游系统开发(开发方案)/详情规则/成熟技术/设计界面/案例项目/源码程序
NFT (Non Homogeneous Token) card chain game refers to a game based on blockchain technology where NFT is used as the card in the game. NFT is a unique and non interchangeable digital asset that can represent various virtual cards, props, or characters in the game.
|
前端开发 JavaScript Java
OA会议管理系统之会议发布(内含原型图&项目介绍&多功能下拉框&源码)(一)
OA会议管理系统之会议发布(内含原型图&项目介绍&多功能下拉框&源码)
250 0
下一篇
无影云桌面