diff --git a/members/feligo887/task1/.gitignore b/members/feligo887/task1/.gitignore deleted file mode 100644 index a547bf36d..000000000 --- a/members/feligo887/task1/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/members/feligo887/task1/README.md b/members/feligo887/task1/README.md deleted file mode 100644 index 74872fd4a..000000000 --- a/members/feligo887/task1/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# React + TypeScript + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: - -- Configure the top-level `parserOptions` property like this: - -```js -export default tseslint.config({ - languageOptions: { - // other options... - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - }, -}) -``` - -- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` -- Optionally add `...tseslint.configs.stylisticTypeChecked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: - -```js -// eslint.config.js -import react from 'eslint-plugin-react' - -export default tseslint.config({ - // Set the react version - settings: { react: { version: '18.3' } }, - plugins: { - // Add the react plugin - react, - }, - rules: { - // other rules... - // Enable its recommended rules - ...react.configs.recommended.rules, - ...react.configs['jsx-runtime'].rules, - }, -}) -``` diff --git a/members/feligo887/task1/eslint.config.js b/members/feligo887/task1/eslint.config.js deleted file mode 100644 index 092408a9f..000000000 --- a/members/feligo887/task1/eslint.config.js +++ /dev/null @@ -1,28 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' - -export default tseslint.config( - { ignores: ['dist'] }, - { - extends: [js.configs.recommended, ...tseslint.configs.recommended], - files: ['**/*.{ts,tsx}'], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - }, - plugins: { - 'react-hooks': reactHooks, - 'react-refresh': reactRefresh, - }, - rules: { - ...reactHooks.configs.recommended.rules, - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, - }, -) diff --git a/members/feligo887/task1/index.html b/members/feligo887/task1/index.html deleted file mode 100644 index e4b78eae1..000000000 --- a/members/feligo887/task1/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - Vite + React + TS - - -
- - - diff --git a/members/feligo887/task1/package.json b/members/feligo887/task1/package.json deleted file mode 100644 index 12345696f..000000000 --- a/members/feligo887/task1/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "vite-project", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@emotion/react": "^11.13.3", - "@emotion/styled": "^11.13.0", - "@mui/icons-material": "^6.1.0", - "@mui/material": "^6.1.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "valtio": "^2.0.0" - }, - "devDependencies": { - "@eslint/js": "^9.9.0", - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "eslint": "^9.9.0", - "eslint-plugin-react-hooks": "^5.1.0-rc.0", - "eslint-plugin-react-refresh": "^0.4.9", - "globals": "^15.9.0", - "typescript": "^5.5.3", - "typescript-eslint": "^8.0.1", - "vite": "^5.4.1" - } -} diff --git a/members/feligo887/task1/public/vite.svg b/members/feligo887/task1/public/vite.svg deleted file mode 100644 index e7b8dfb1b..000000000 --- a/members/feligo887/task1/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/members/feligo887/task1/src/App.tsx b/members/feligo887/task1/src/App.tsx deleted file mode 100644 index 757ea107a..000000000 --- a/members/feligo887/task1/src/App.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Header from './components/Header' -import AddToDo from './components/AddToDo' -import ToDoList from './components/ToDoList' - -function App() { - - return ( -
-
-
- - -
-
- ) -} - -export default App diff --git a/members/feligo887/task1/src/assets/react.svg b/members/feligo887/task1/src/assets/react.svg deleted file mode 100644 index 6c87de9bb..000000000 --- a/members/feligo887/task1/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/members/feligo887/task1/src/components/AddToDo/index.tsx b/members/feligo887/task1/src/components/AddToDo/index.tsx deleted file mode 100644 index 2ce883b40..000000000 --- a/members/feligo887/task1/src/components/AddToDo/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useState } from 'react' -import { Input, Button } from '@mui/material' - -import { addToDoList } from '../../store/modules/toDoList.ts' - -const AddToDo = () => { - const [ inpVal, setInpVal ] = useState( '' ) - const handleButton = () => { - addToDoList( inpVal ) - setInpVal( '' ) - } - - return ( -
- ) => setInpVal( event.target.value ) }/> - -
- ) -} - -export default AddToDo diff --git a/members/feligo887/task1/src/components/Header/index.tsx b/members/feligo887/task1/src/components/Header/index.tsx deleted file mode 100644 index b72826511..000000000 --- a/members/feligo887/task1/src/components/Header/index.tsx +++ /dev/null @@ -1,9 +0,0 @@ -const Header = () => { - return ( -

- To Do List -

- ) -} - -export default Header \ No newline at end of file diff --git a/members/feligo887/task1/src/components/ToDoList/index.tsx b/members/feligo887/task1/src/components/ToDoList/index.tsx deleted file mode 100644 index 893357cb8..000000000 --- a/members/feligo887/task1/src/components/ToDoList/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Checkbox } from '@mui/material' -import { DeleteForever } from '@mui/icons-material' - -// store -import { useSnapshot } from 'valtio' -import store from '../../store' -import { removeToDoList, changeToDoList } from '../../store/modules/toDoList.ts' -// style -import style from './style.module.css' -// type -import type { ToDoList } from '../../store/modules/toDoList.ts' - -const ToDoItem = ( props: { item: ToDoList['list'][0], index: number } ) => { - const { item, index } = props - return ( -
  • - changeToDoList( index ) }/> - { item.text } - removeToDoList( index ) }/> -
  • - ) -} - -const ToDoList = () => { - const snapshot = useSnapshot( store ) - const { toDoList } = snapshot - return ( - - ) -} - -export default ToDoList diff --git a/members/feligo887/task1/src/components/ToDoList/style.module.css b/members/feligo887/task1/src/components/ToDoList/style.module.css deleted file mode 100644 index ce841bffb..000000000 --- a/members/feligo887/task1/src/components/ToDoList/style.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.to-do-item { - display: flex; - align-items: center; - justify-content: space-between; - margin-top: 8px; - padding-right: 8px; - position: relative; -} -.to-do-item::after { - content: ''; - display: block; - width: 0; - height: 1px; - background-color: #666; - position: absolute; - top: 50%; - left: 0; - transform: translateY(-50%); - transition: all 0.3s; -} -.to-do-item.check::after { - width: 100%; -} diff --git a/members/feligo887/task1/src/index.css b/members/feligo887/task1/src/index.css deleted file mode 100644 index 6119ad9a8..000000000 --- a/members/feligo887/task1/src/index.css +++ /dev/null @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/members/feligo887/task1/src/main.tsx b/members/feligo887/task1/src/main.tsx deleted file mode 100644 index 79346cf8b..000000000 --- a/members/feligo887/task1/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import App from './App.tsx' -import './style/reset.css' - -createRoot(document.getElementById('root')!).render( - - - , -) diff --git a/members/feligo887/task1/src/store/index.ts b/members/feligo887/task1/src/store/index.ts deleted file mode 100644 index 6a4d5efe5..000000000 --- a/members/feligo887/task1/src/store/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { proxy } from 'valtio' -import { toDoList } from './modules/toDoList.ts' - -const store = proxy({ - toDoList -}) - -export default store diff --git a/members/feligo887/task1/src/store/modules/index.ts b/members/feligo887/task1/src/store/modules/index.ts deleted file mode 100644 index 1c9ec27f6..000000000 --- a/members/feligo887/task1/src/store/modules/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { toDoList as default } from './toDoList' diff --git a/members/feligo887/task1/src/store/modules/toDoList.ts b/members/feligo887/task1/src/store/modules/toDoList.ts deleted file mode 100644 index e50299091..000000000 --- a/members/feligo887/task1/src/store/modules/toDoList.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { proxy, subscribe } from 'valtio' - -export interface ToDoList { - list: Array<{ isChecked: boolean, text: string }> -} - -const loadState = () => { - const toDoList = localStorage.getItem( 'toDoList' ) - return toDoList ? JSON.parse( toDoList ) : { list: [] } -} - -// store -export const toDoList = proxy( loadState() ) - -// 增加数据 -export const addToDoList = ( text: string ) => { - toDoList.list.push( { - isChecked: false, - text, - } ) -} -// 修改数据选中状态数据 -export const changeToDoList = ( index: number ) => { - const record = toDoList.list[ index ] - record.isChecked = !record.isChecked - toDoList.list[ index ] = record -} -// 删除数据 -export const removeToDoList = ( index: number ) => { - toDoList.list.splice( index, 1 ) -} - -subscribe( toDoList, () => { - localStorage.setItem( 'toDoList', JSON.stringify( toDoList ) ) -} ) diff --git a/members/feligo887/task1/src/style/reset.css b/members/feligo887/task1/src/style/reset.css deleted file mode 100644 index 59a52051c..000000000 --- a/members/feligo887/task1/src/style/reset.css +++ /dev/null @@ -1,48 +0,0 @@ -/* http://meyerweb.com/eric/tools/css/reset/ - v2.0 | 20110126 - License: none (public domain) -*/ - -html, body, div, span, applet, object, iframe, -h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, -fieldset, form, label, legend, -table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, -time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; -} -/* HTML5 display-role reset for older browsers */ -article, aside, details, figcaption, figure, -footer, header, hgroup, menu, nav, section { - display: block; -} -body { - line-height: 1; -} -ol, ul { - list-style: none; -} -blockquote, q { - quotes: none; -} -blockquote:before, blockquote:after, -q:before, q:after { - content: ''; - content: none; -} -table { - border-collapse: collapse; - border-spacing: 0; -} diff --git a/members/feligo887/task1/src/vite-env.d.ts b/members/feligo887/task1/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2a..000000000 --- a/members/feligo887/task1/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/members/feligo887/task1/tsconfig.app.json b/members/feligo887/task1/tsconfig.app.json deleted file mode 100644 index f0a235055..000000000 --- a/members/feligo887/task1/tsconfig.app.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/members/feligo887/task1/tsconfig.json b/members/feligo887/task1/tsconfig.json deleted file mode 100644 index 1ffef600d..000000000 --- a/members/feligo887/task1/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] -} diff --git a/members/feligo887/task1/tsconfig.node.json b/members/feligo887/task1/tsconfig.node.json deleted file mode 100644 index 0d3d71446..000000000 --- a/members/feligo887/task1/tsconfig.node.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "lib": ["ES2023"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "isolatedModules": true, - "moduleDetection": "force", - "noEmit": true, - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["vite.config.ts"] -} diff --git a/members/feligo887/task1/vite.config.ts b/members/feligo887/task1/vite.config.ts deleted file mode 100644 index 8394b47c2..000000000 --- a/members/feligo887/task1/vite.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - server: { - port: 1753 - } -}) diff --git a/members/feligo887/task3/CustomERC20.sol b/members/feligo887/task3/CustomERC20.sol new file mode 100644 index 000000000..a5681daca --- /dev/null +++ b/members/feligo887/task3/CustomERC20.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract CustomERC20 is ERC20 { + constructor() ERC20("CustomToken", "CTK") { + _mint(msg.sender, 1000000 * 10 ** decimals()); // 初始铸造 100 万代币 + } + // 铸造更多代币(仅供合约所有者使用) + function mint(address to,uint256 amount) external { + _mint(to,amount); + } + +} \ No newline at end of file diff --git a/members/feligo887/task3/CustomNFT.sol b/members/feligo887/task3/CustomNFT.sol new file mode 100644 index 000000000..c3576b208 --- /dev/null +++ b/members/feligo887/task3/CustomNFT.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; + +contract CustomNFT is ERC721URIStorage { + uint256 public tokenCounter; + constructor() ERC721("CustomNFT", "CNFT") { + tokenCounter = 0; + } + + function createNFT(string memory tokenURI) public payable returns (uint256) { + uint256 newTokenId = tokenCounter; + _safeMint(msg.sender,newTokenId); + _setTokenURI(newTokenId,tokenURI); + tokenCounter++; + return newTokenId; + } +} \ No newline at end of file diff --git a/members/feligo887/task3/NFTMarketplace.sol b/members/feligo887/task3/NFTMarketplace.sol new file mode 100644 index 000000000..428334711 --- /dev/null +++ b/members/feligo887/task3/NFTMarketplace.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; // 导入ERC721接口,用于NFT交互 +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; // 导入ERC20接口,用于自定义代币交互 + +// 创建一个简单的NFT市场合约 +contract NFTMarketplace { + + // 定义一个结构体,用于保存每个上架的NFT信息 + struct Listing { + address seller; // 卖家地址 + address nftContract; // NFT合约地址 + uint256 tokenId; // NFT的Token ID + uint256 price; // NFT价格,使用自定义的ERC20代币 + } + + // 保存所有上架的NFT,每个上架项通过一个自增的ID来标识 + mapping(uint256 => Listing) public listings; + uint256 public listingCounter; // 记录当前有多少个NFT上架,作为列表的ID + + // 自定义ERC20代币,用于购买NFT + IERC20 public erc20Token; + + // 定义两个事件:一个用于NFT上架,另一个用于NFT被购买 + event NFTListed(address indexed seller, address indexed nftContract, uint256 indexed tokenId, uint256 price); + event NFTBought(address indexed buyer, address indexed nftContract, uint256 indexed tokenId, uint256 price); + + // 构造函数:初始化时传入自定义的ERC20代币合约地址 + constructor(address _erc20Token) { + erc20Token = IERC20(_erc20Token); // 初始化自定义ERC20代币 + listingCounter = 0; // 初始化上架ID计数器,从0开始 + } + + // 上架NFT函数,允许用户将自己的NFT上架到市场 + // 参数:_nftContract 是NFT合约地址,_tokenId 是NFT的Token ID,_price 是上架的价格 + function listNFT(address _nftContract, uint256 _tokenId, uint256 _price) external { + IERC721 nft = IERC721(_nftContract); // 通过NFT合约地址构造ERC721接口实例 + + // 检查调用者是否是该NFT的所有者 + require(nft.ownerOf(_tokenId) == msg.sender, "You are not the owner of this NFT"); + + // 检查市场合约是否被授权操作该NFT + require(nft.isApprovedForAll(msg.sender, address(this)) || nft.getApproved(_tokenId) == address(this), "Marketplace contract is not approved"); + + // 将NFT上架信息保存到映射中 + listings[listingCounter] = Listing({ + seller: msg.sender, // 卖家为当前调用者 + nftContract: _nftContract, // NFT的合约地址 + tokenId: _tokenId, // NFT的Token ID + price: _price // 上架价格(使用ERC20代币) + }); + + // 触发NFT上架事件,方便外部系统监听 + emit NFTListed(msg.sender, _nftContract, _tokenId, _price); + + // 增加上架ID计数器,为下一个NFT上架分配唯一ID + listingCounter++; + } + + // 购买NFT函数,允许用户购买市场上架的NFT + // 参数:_listingId 是上架的NFT ID + function buyNFT(uint256 _listingId) external { + // 获取上架的NFT信息 + Listing memory listing = listings[_listingId]; + + // 确保NFT已经上架,价格必须大于0 + require(listing.price > 0, "This NFT is not listed"); + + // 使用自定义的ERC20代币支付,检查是否代币转移成功 + require(erc20Token.transferFrom(msg.sender, listing.seller, listing.price), "ERC20 token transfer failed"); + + // 调用NFT合约,安全地将NFT从卖家转移给买家 + IERC721(listing.nftContract).safeTransferFrom(listing.seller, msg.sender, listing.tokenId); + + // 触发NFT购买事件 + emit NFTBought(msg.sender, listing.nftContract, listing.tokenId, listing.price); + + // 删除该NFT的上架信息,防止重复购买 + delete listings[_listingId]; + } +} \ No newline at end of file