├── .eslintcache ├── .github └── workflows │ └── deploy-site.yml ├── .gitignore ├── .prettierrc ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── index.html ├── package.json ├── pnpm-lock.yaml ├── src ├── assets │ ├── css │ │ ├── index.scss │ │ └── var.css │ ├── favicon.ico │ ├── fonts │ │ ├── AaLingGanHei55J-2.ttf │ │ ├── SourceCodePro-Medium.ttf │ │ └── UbuntuMono-R-Powerline.otf │ ├── icons │ │ ├── arrow-left-s-line.svg │ │ ├── arrow-up-line.svg │ │ ├── delete-bin-line.svg │ │ ├── download-2-line.svg │ │ ├── file-code-line.svg │ │ ├── file-pdf-line.svg │ │ ├── file-png-line.svg │ │ ├── fonts │ │ │ ├── icon.css │ │ │ ├── icon.eot │ │ │ ├── icon.less │ │ │ ├── icon.module.less │ │ │ ├── icon.svg │ │ │ ├── icon.symbol.svg │ │ │ ├── icon.ttf │ │ │ ├── icon.woff │ │ │ ├── icon.woff2 │ │ │ ├── index.html │ │ │ ├── symbol.html │ │ │ └── unicode.html │ │ └── github-fill.svg │ └── image │ │ ├── icon │ │ ├── jpeg.png │ │ └── pdf.png │ │ ├── index-head-bg.jpg │ │ └── qrcode-wechat.png ├── components │ ├── colorSelect │ │ ├── index.module.scss │ │ └── index.tsx │ ├── floatingBottom │ │ ├── index.module.scss │ │ └── index.tsx │ ├── icon │ │ └── index.tsx │ ├── popups │ │ └── exportPDFHelp │ │ │ ├── images │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ └── 3.png │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ ├── templateCard │ │ ├── index.module.scss │ │ └── index.tsx │ └── working │ │ ├── index.module.scss │ │ └── index.tsx ├── config │ └── index.ts ├── index.tsx ├── pages │ ├── editor │ │ ├── aside │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── exportMenu │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── floatingActions │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── header │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── index.module.scss │ │ ├── index.tsx │ │ ├── main │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── route.tsx │ │ └── store.tsx │ └── index │ │ ├── index.module.scss │ │ ├── index.tsx │ │ └── route.tsx ├── router.tsx ├── shared │ ├── formily │ │ ├── datas │ │ │ ├── developer │ │ │ │ ├── form.tsx │ │ │ │ └── index.ts │ │ │ └── normal-sidebar │ │ │ │ ├── form.tsx │ │ │ │ └── index.ts │ │ ├── form │ │ │ ├── basic │ │ │ │ └── index.tsx │ │ │ ├── titleContentArray │ │ │ │ ├── index.module.scss │ │ │ │ └── index.tsx │ │ │ └── titleContentTimeRangeArray │ │ │ │ ├── index.module.scss │ │ │ │ └── index.tsx │ │ ├── index.ts │ │ └── template.tsx │ ├── func │ │ ├── chooseFile.ts │ │ └── copyToClipboard.ts │ ├── hooks │ │ └── useScrollFloating.ts │ ├── popupManager │ │ └── index.ts │ ├── storeProvider │ │ └── index.tsx │ └── toast │ │ └── index.tsx ├── stores │ └── app.ts └── templates │ ├── developer-concise-1 │ ├── assets │ │ ├── images │ │ │ ├── bg1.png │ │ │ ├── bg10.png │ │ │ ├── bg11.png │ │ │ ├── bg2.png │ │ │ ├── bg3.png │ │ │ ├── bg4.png │ │ │ ├── bg5.png │ │ │ ├── bg6.png │ │ │ ├── bg7.png │ │ │ ├── bg8.png │ │ │ └── bg9.png │ │ └── poster.jpeg │ ├── configForm │ │ ├── index.module.scss │ │ └── index.tsx │ ├── index.ts │ └── view │ │ ├── header │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── index.module.scss │ │ ├── index.tsx │ │ ├── ownerProjects │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── title │ │ ├── index.module.scss │ │ └── index.tsx │ │ └── workingHistory │ │ ├── index.module.scss │ │ └── index.tsx │ ├── developer-concise-2 │ ├── assets │ │ └── poster.jpeg │ ├── configForm │ │ ├── index.module.scss │ │ └── index.tsx │ ├── index.ts │ └── view │ │ ├── header │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── index.module.scss │ │ ├── index.tsx │ │ ├── ownerProjects │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── titles │ │ ├── style1 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── style2 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── style3 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── style4 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── style5 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── style6 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── style7 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ └── style8 │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ └── workingHistory │ │ ├── index.module.scss │ │ └── index.tsx │ ├── index.tsx │ ├── simple-one-page-1 │ ├── assets │ │ └── poster.jpeg │ ├── configForm │ │ ├── index.module.scss │ │ └── index.tsx │ ├── index.ts │ └── view │ │ ├── baseInfo │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── header │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── index.module.scss │ │ ├── index.tsx │ │ ├── leftItem │ │ ├── index.module.scss │ │ └── index.tsx │ │ └── title │ │ ├── index.module.scss │ │ └── index.tsx │ ├── simple-one-page-2 │ ├── assets │ │ └── poster.jpeg │ ├── configForm │ │ ├── index.module.scss │ │ └── index.tsx │ ├── index.ts │ └── view │ │ ├── header │ │ ├── index.module.scss │ │ └── index.tsx │ │ ├── index.module.scss │ │ ├── index.tsx │ │ ├── leftItem │ │ ├── index.module.scss │ │ └── index.tsx │ │ └── title │ │ ├── index.module.scss │ │ └── index.tsx │ └── template.tsx ├── tsconfig.json ├── typing └── global.d.ts └── vite.config.ts /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"E:\\pro\\tp\\react.tp\\src\\index.tsx":"1","E:\\pro\\tp\\react.tp\\src\\reportWebVitals.ts":"2","E:\\pro\\tp\\react.tp\\src\\App.tsx":"3"},{"size":500,"mtime":1608694914978,"results":"4","hashOfConfig":"5"},{"size":425,"mtime":1608694914978,"results":"6","hashOfConfig":"5"},{"size":556,"mtime":1608694914978,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"n587vw",{"filePath":"10","messages":"11","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"12","messages":"13","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"E:\\pro\\tp\\react.tp\\src\\index.tsx",[],"E:\\pro\\tp\\react.tp\\src\\reportWebVitals.ts",[],"E:\\pro\\tp\\react.tp\\src\\App.tsx",[]] -------------------------------------------------------------------------------- /.github/workflows/deploy-site.yml: -------------------------------------------------------------------------------- 1 | name: deploy-site 2 | on: 3 | push: 4 | tags: 5 | - 'v*.*.*' 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Install Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16.16.0 18 | 19 | - uses: pnpm/action-setup@v2.0.1 20 | name: Install pnpm 21 | id: pnpm-install 22 | with: 23 | version: 8 24 | run_install: false 25 | 26 | - name: Get pnpm store directory 27 | id: pnpm-cache 28 | run: | 29 | echo "::set-output name=pnpm_cache_dir::$(pnpm store path)" 30 | 31 | - uses: actions/cache@v3 32 | name: Setup pnpm cache 33 | with: 34 | path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }} 35 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 36 | restore-keys: | 37 | ${{ runner.os }}-pnpm-store- 38 | 39 | - name: Install dependencies 40 | run: pnpm install 41 | 42 | - run: pnpm run build 43 | 44 | - name: Deploy to Netlify 45 | uses: nwtgck/actions-netlify@v1.2 46 | with: 47 | publish-dir: './dist' 48 | production-deploy: true 49 | env: 50 | NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} 51 | NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | /dist 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "tabWidth": 2, 5 | "printWidth": 120 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | WORKDIR /app 3 | COPY . /app 4 | RUN npm install --force 5 | EXPOSE 5173 6 | CMD ["npm", "run-script", "dev"] 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # open-resume 2 | 3 | 纯前端的在线简历编辑工具,支持导入导出 JSON 配置,导出 PDF 和图片 4 | 5 | ## 编译&运行 6 | ```shell 7 | #编译镜像 8 | docker build -t openresume . 9 | #通过Compose启动 10 | docker-compose up -d 11 | #通过命令启动 12 | docker run --it -name openresume -p 5173:5173 -d openresume 13 | ``` 14 | 15 | [✍️ 点击开始制作属于你的简历!](https://open-resume.netlify.app) 16 | 17 | [![xwuVPO.png](https://s1.ax1x.com/2022/10/14/xwuVPO.png)](https://imgse.com/i/xwuVPO) 18 | 19 | 目前内置了 4 套简历模板: 20 | 21 | [模板一](https://open-resume.netlify.app/#/editor/developer-concise-1): 22 | [![xwJyYn.png](https://s1.ax1x.com/2022/10/14/xwJyYn.png)](https://imgse.com/i/xwJyYn) 23 | 24 | [模板二](https://open-resume.netlify.app/#/editor/developer-concise-2): 25 | [![xwJsFs.png](https://s1.ax1x.com/2022/10/14/xwJsFs.png)](https://imgse.com/i/xwJsFs) 26 | 27 | [模板三](https://open-resume.netlify.app/#/editor/simple-one-page): 28 | [![xwJDoj.png](https://s1.ax1x.com/2022/10/14/xwJDoj.png)](https://imgse.com/i/xwJDoj) 29 | 30 | [模板四](https://open-resume.netlify.app/#/editor/simple-one-page2): 31 | [![xwJBwQ.png](https://s1.ax1x.com/2022/10/14/xwJBwQ.png)](https://imgse.com/i/xwJBwQ) 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | openresume: 3 | stdin_open: true 4 | tty: true 5 | container_name: openresume 6 | ports: 7 | - 5173:5173 8 | image: openresume 9 | restart: always 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Open Resume 免费的在线简历制作工具 10 | 11 | 12 | 13 | 14 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react.tp", 3 | "version": "1.5.3", 4 | "private": true, 5 | "homepage": ".", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "test": "vite test" 10 | }, 11 | "dependencies": { 12 | "@emoji-mart/data": "^1.0.6", 13 | "@emoji-mart/react": "^1.0.1", 14 | "@formily/antd": "^2.2.5", 15 | "@formily/core": "^2.2.5", 16 | "@formily/react": "^2.2.5", 17 | "@formily/reactive": "^2.2.5", 18 | "antd": "^4.23.2", 19 | "clsx": "^1.2.1", 20 | "emoji-mart": "^5.2.2", 21 | "file-saver": "^2.0.5", 22 | "html-to-image": "^1.10.8", 23 | "mobx": "^6.6.2", 24 | "mobx-react-lite": "^3.4.0", 25 | "moment": "^2.29.4", 26 | "oh-popup": "^0.1.0", 27 | "oh-popup-react": "^0.1.2", 28 | "oh-router": "^0.3.0", 29 | "oh-router-react": "^0.0.9", 30 | "react": "^18.2.0", 31 | "react-dom": "^18.2.0" 32 | }, 33 | "devDependencies": { 34 | "@types/file-saver": "^2.0.5", 35 | "@types/node": "^18.7.14", 36 | "@types/react": "^18.0.18", 37 | "@types/react-dom": "^18.0.6", 38 | "@vitejs/plugin-react": "^3.0.0", 39 | "less": "^4.1.3", 40 | "sass": "^1.30.0", 41 | "typescript": "^4.8.2", 42 | "vite": "^4.0.0" 43 | }, 44 | "eslintConfig": { 45 | "extends": [ 46 | "react-app", 47 | "react-app/jest" 48 | ] 49 | }, 50 | "browserslist": { 51 | "production": [ 52 | ">0.2%", 53 | "not dead", 54 | "not op_mini all" 55 | ], 56 | "development": [ 57 | "last 1 chrome version", 58 | "last 1 firefox version", 59 | "last 1 safari version" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assets/css/index.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'UbuntuMono-R-Powerline'; 3 | src: url('../fonts/UbuntuMono-R-Powerline.otf'); 4 | } 5 | 6 | @font-face { 7 | font-family: 'AaLingGanHei55J-2'; 8 | src: url('../fonts/AaLingGanHei55J-2.ttf'); 9 | } 10 | 11 | body { 12 | margin: 0; 13 | font-family: 'UbuntuMono-R-Powerline', 'AaLingGanHei55J-2'; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | img { 19 | display: block; 20 | } 21 | 22 | * { 23 | box-sizing: border-box; 24 | } 25 | 26 | @media print { 27 | body { 28 | background-color: #fff; 29 | } 30 | } 31 | 32 | code { 33 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 34 | } 35 | -------------------------------------------------------------------------------- /src/assets/css/var.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --color-primary: #1890ff; 3 | --shadow-1-down: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d; 4 | --ant-primary-color: var(--color-primary); 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/favicon.ico -------------------------------------------------------------------------------- /src/assets/fonts/AaLingGanHei55J-2.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/fonts/AaLingGanHei55J-2.ttf -------------------------------------------------------------------------------- /src/assets/fonts/SourceCodePro-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/fonts/SourceCodePro-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/UbuntuMono-R-Powerline.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/fonts/UbuntuMono-R-Powerline.otf -------------------------------------------------------------------------------- /src/assets/icons/arrow-left-s-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/arrow-up-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/delete-bin-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/download-2-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/file-code-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/file-pdf-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/file-png-line.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "icon"; 3 | src: url('icon.eot?t=1663660774701'); /* IE9*/ 4 | src: url('icon.eot?t=1663660774701#iefix') format('embedded-opentype'), /* IE6-IE8 */ 5 | url("icon.woff2?t=1663660774701") format("woff2"), 6 | url("icon.woff?t=1663660774701") format("woff"), 7 | url('icon.ttf?t=1663660774701') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 8 | url('icon.svg?t=1663660774701#icon') format('svg'); /* iOS 4.1- */ 9 | } 10 | 11 | [class^="icon-"], [class*=" icon-"] { 12 | font-family: 'icon' !important; 13 | font-size:16px; 14 | font-style:normal; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | 20 | .icon-arrow-left-s-line:before { content: "\ea01"; } 21 | .icon-arrow-up-line:before { content: "\ea02"; } 22 | .icon-delete-bin-line:before { content: "\ea03"; } 23 | .icon-download-2-line:before { content: "\ea04"; } 24 | .icon-file-code-line:before { content: "\ea05"; } 25 | .icon-file-pdf-line:before { content: "\ea06"; } 26 | .icon-file-png-line:before { content: "\ea07"; } 27 | .icon-github-fill:before { content: "\ea08"; } 28 | -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/icons/fonts/icon.eot -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.less: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "icon"; 2 | src: url('icon.eot?t=1663660774701'); /* IE9*/ 3 | src: url('icon.eot?t=1663660774701#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url("icon.woff2?t=1663660774701") format("woff2"), 5 | url("icon.woff?t=1663660774701") format("woff"), 6 | url('icon.ttf?t=1663660774701') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('icon.svg?t=1663660774701#icon') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | [class^="icon-"], [class*=" icon-"] { 11 | font-family: 'icon' !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | .icon-arrow-left-s-line:before { content: "\ea01"; } 19 | .icon-arrow-up-line:before { content: "\ea02"; } 20 | .icon-delete-bin-line:before { content: "\ea03"; } 21 | .icon-download-2-line:before { content: "\ea04"; } 22 | .icon-file-code-line:before { content: "\ea05"; } 23 | .icon-file-pdf-line:before { content: "\ea06"; } 24 | .icon-file-png-line:before { content: "\ea07"; } 25 | .icon-github-fill:before { content: "\ea08"; } 26 | -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.module.less: -------------------------------------------------------------------------------- 1 | @font-face {font-family: "icon"; 2 | src: url('icon.eot?t=1663660774701'); /* IE9*/ 3 | src: url('icon.eot?t=1663660774701#iefix') format('embedded-opentype'), /* IE6-IE8 */ 4 | url("icon.woff2?t=1663660774701") format("woff2"), 5 | url("icon.woff?t=1663660774701") format("woff"), 6 | url('icon.ttf?t=1663660774701') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ 7 | url('icon.svg?t=1663660774701#icon') format('svg'); /* iOS 4.1- */ 8 | } 9 | 10 | [class^="icon-"], [class*=" icon-"] { 11 | font-family: 'icon' !important; 12 | font-size:16px; 13 | font-style:normal; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | :global { 19 | .icon-arrow-left-s-line:before { content: "\ea01"; } 20 | .icon-arrow-up-line:before { content: "\ea02"; } 21 | .icon-delete-bin-line:before { content: "\ea03"; } 22 | .icon-download-2-line:before { content: "\ea04"; } 23 | .icon-file-code-line:before { content: "\ea05"; } 24 | .icon-file-pdf-line:before { content: "\ea06"; } 25 | .icon-file-png-line:before { content: "\ea07"; } 26 | .icon-github-fill:before { content: "\ea08"; } 27 | 28 | } -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.symbol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/icons/fonts/icon.ttf -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/icons/fonts/icon.woff -------------------------------------------------------------------------------- /src/assets/icons/fonts/icon.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/icons/fonts/icon.woff2 -------------------------------------------------------------------------------- /src/assets/icons/fonts/index.html: -------------------------------------------------------------------------------- 1 | 查看图标

查看图标1

File icons in the file tree.

Font Class Demo · Symbol Demo · Unicode Demo

  • arrow-left-s-line

  • arrow-up-line

  • delete-bin-line

  • download-2-line

  • file-code-line

  • file-pdf-line

  • file-png-line

  • github-fill

-------------------------------------------------------------------------------- /src/assets/icons/fonts/symbol.html: -------------------------------------------------------------------------------- 1 | 查看图标

查看图标1

File icons in the file tree.

Font Class Demo · Symbol Demo · Unicode Demo

  • icon-arrow-left-s-line

  • icon-arrow-up-line

  • icon-delete-bin-line

  • icon-download-2-line

  • icon-file-code-line

  • icon-file-pdf-line

  • icon-file-png-line

  • icon-github-fill

-------------------------------------------------------------------------------- /src/assets/icons/fonts/unicode.html: -------------------------------------------------------------------------------- 1 | 查看图标

查看图标1

File icons in the file tree.

Font Class Demo · Symbol Demo · Unicode Demo

  • arrow-left-s-line

    
  • arrow-up-line

    
  • delete-bin-line

    
  • download-2-line

    
  • file-code-line

    
  • file-pdf-line

    
  • file-png-line

    
  • github-fill

    
-------------------------------------------------------------------------------- /src/assets/icons/github-fill.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/image/icon/jpeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/image/icon/jpeg.png -------------------------------------------------------------------------------- /src/assets/image/icon/pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/image/icon/pdf.png -------------------------------------------------------------------------------- /src/assets/image/index-head-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/image/index-head-bg.jpg -------------------------------------------------------------------------------- /src/assets/image/qrcode-wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/assets/image/qrcode-wechat.png -------------------------------------------------------------------------------- /src/components/colorSelect/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | line-height: 1; 5 | gap: 8px; 6 | font-family: '宋体'; 7 | > div { 8 | width: 100%; 9 | height: 108px; 10 | border-radius: 5px; 11 | cursor: pointer; 12 | display: flex; 13 | justify-content: flex-end; 14 | align-items: flex-end; 15 | color: #fff; 16 | padding: 16px; 17 | font-style: italic; 18 | font-size: 18px; 19 | font-weight: bold; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/colorSelect/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Color { 5 | name: string 6 | value: string 7 | } 8 | 9 | interface ColorSelectProps { 10 | colors?: Color[] 11 | onSelect: (color: Color, index: number) => void 12 | } 13 | 14 | export const ColorSelect: FC = ({ colors, onSelect }) => { 15 | return ( 16 |
17 | {colors!.map((it, i) => { 18 | return ( 19 |
onSelect(it, i)}> 20 | {it.name} 21 |
22 | ) 23 | })} 24 |
25 | ) 26 | } 27 | 28 | ColorSelect.defaultProps = { 29 | colors: [ 30 | { name: '克莱因蓝', value: '#002EA6' }, 31 | { name: '爱马仕橙', value: '#FF770F' }, 32 | { name: '深蓝色', value: '#000026' }, 33 | { name: '蒂芙尼蓝', value: '#80D1C8' }, 34 | { name: '普鲁士蓝', value: '#003152' }, 35 | { name: '中国红', value: '#E60000' }, 36 | { name: '提香红', value: '#B05923' }, 37 | { name: '勃艮第红', value: '#900021' }, 38 | { name: '木乃伊棕', value: '#8F4B28' }, 39 | { name: '凡戴克棕', value: '#492D22' }, 40 | { name: '马尔斯绿', value: '#01847F' }, 41 | { name: '舍勒绿', value: '#3C7A18' }, 42 | ], 43 | } 44 | -------------------------------------------------------------------------------- /src/components/floatingBottom/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | position: absolute; 3 | bottom: 24px; 4 | left: 50%; 5 | z-index: 100; 6 | transform: translateX(-50%); 7 | transition: all 0.3s; 8 | } 9 | 10 | .hidden { 11 | bottom: 0; 12 | transform: translateX(-50%) translateY(100%); 13 | } 14 | -------------------------------------------------------------------------------- /src/components/floatingBottom/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import React, { FC, ReactNode } from 'react' 3 | import { useScrollFloating } from 'src/shared/hooks/useScrollFloating' 4 | import styles from './index.module.scss' 5 | 6 | interface FloatActionsProps { 7 | target: React.MutableRefObject 8 | children: ReactNode 9 | } 10 | 11 | export const FloatingBottom: FC = ({ target, children }) => { 12 | const { visible } = useScrollFloating(target) 13 | 14 | return
{children}
15 | } 16 | -------------------------------------------------------------------------------- /src/components/icon/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, useMemo } from 'react' 2 | 3 | interface IconProps { 4 | value: string 5 | className?: string 6 | style?: React.CSSProperties | undefined 7 | onClick?: (e: React.MouseEvent) => void 8 | } 9 | 10 | export const Icon: FC = (props) => { 11 | const { style, onClick } = props 12 | 13 | const value = useMemo(() => { 14 | if (props.value.startsWith('icon')) { 15 | return props.value 16 | } else { 17 | return 'icon-' + props.value 18 | } 19 | }, [props.value]) 20 | 21 | return ( 22 | 30 | 31 | 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /src/components/popups/exportPDFHelp/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/components/popups/exportPDFHelp/images/1.png -------------------------------------------------------------------------------- /src/components/popups/exportPDFHelp/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/components/popups/exportPDFHelp/images/2.png -------------------------------------------------------------------------------- /src/components/popups/exportPDFHelp/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/components/popups/exportPDFHelp/images/3.png -------------------------------------------------------------------------------- /src/components/popups/exportPDFHelp/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | min-width: 660px; 3 | max-width: 80vw; 4 | width: 50vw; 5 | height: 75vh; 6 | background: #fff; 7 | border-radius: 12px; 8 | display: flex; 9 | flex-direction: column; 10 | header, 11 | footer { 12 | padding: 16px; 13 | line-height: 1; 14 | } 15 | main { 16 | flex: 1; 17 | overflow-y: auto; 18 | .content { 19 | padding: 16px; 20 | img { 21 | max-width: 60%; 22 | } 23 | } 24 | } 25 | header { 26 | border-bottom: 1px solid rgba($color: #000000, $alpha: 0.03); 27 | font-size: 18px; 28 | } 29 | footer { 30 | border-top: 1px solid rgba($color: #000000, $alpha: 0.03); 31 | display: flex; 32 | justify-content: flex-end; 33 | align-items: center; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/popups/exportPDFHelp/index.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, Button, Space } from 'antd' 2 | import { useController } from 'oh-popup-react' 3 | import { popupManager } from 'src/shared/popupManager' 4 | import image1 from './images/1.png' 5 | import image2 from './images/2.png' 6 | import image3 from './images/3.png' 7 | import styles from './index.module.scss' 8 | 9 | const ExportPDFHelp = () => { 10 | const ctl = useController() 11 | return ( 12 |
13 |
导出 PDF 帮助
14 |
15 |
16 |

17 | 18 |

19 |

请按以下步骤检查导出配置,避免导出与预览不一致。

20 |

1️⃣ 打印机选项中选择「另存为PDF」

21 |

22 | 23 |

24 |

2️⃣ 在更多设置中勾选「背景图形」选项

25 |

点击「更多设置」

26 |

27 | 28 |

29 |

然后勾选「背景图形」选项

30 |

31 | 32 |

33 |
34 |
35 |
36 | 37 | 45 | 48 | 49 |
50 |
51 | ) 52 | } 53 | 54 | function setFlag() { 55 | localStorage.setItem('openExportPDFHelp', 'true') 56 | } 57 | 58 | function getFlag() { 59 | return localStorage.getItem('openExportPDFHelp') 60 | } 61 | 62 | export function openExportPDFHelp() { 63 | if (getFlag()) return 64 | return popupManager.open({ 65 | el: , 66 | position: 'center', 67 | maskClosable: false, 68 | }) 69 | } 70 | -------------------------------------------------------------------------------- /src/components/templateCard/index.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 0; 4 | padding-bottom: 150%; 5 | position: relative; 6 | 7 | .content { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | width: 100%; 12 | height: 100%; 13 | overflow: hidden; 14 | 15 | .poster { 16 | width: 100%; 17 | height: 100%; 18 | overflow: hidden; 19 | img { 20 | width: 100%; 21 | } 22 | } 23 | 24 | .mask { 25 | position: absolute; 26 | left: 0; 27 | bottom: 0; 28 | width: 100%; 29 | background: rgba($color: #fff, $alpha: 0.96); 30 | padding: 8px; 31 | box-shadow: var(--shadow-1-down); 32 | 33 | > * + * { 34 | margin-top: 12px; 35 | } 36 | 37 | .name { 38 | font-size: 16px; 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/templateCard/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Tag } from 'antd' 2 | import { FC } from 'react' 3 | import { BaseTemplate } from 'src/templates/template' 4 | import styles from './index.module.scss' 5 | 6 | interface TempalteCardProps { 7 | value: BaseTemplate 8 | onUse: () => void 9 | } 10 | 11 | export const TemplateCard: FC = ({ value, onUse }) => { 12 | return ( 13 |
14 |
15 |
16 | 17 |
18 |
19 |
{value.name}
20 |
21 | {value.tags?.map((tag, i) => { 22 | return {tag} 23 | })} 24 |
25 | 28 |
29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/working/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | .header { 3 | display: flex; 4 | justify-content: space-between; 5 | font-weight: bold; 6 | .time { 7 | color: var(--time-color, #8c8c8c); 8 | } 9 | } 10 | 11 | .content { 12 | padding-top: 8px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/working/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | name: string 6 | time: [string, string] 7 | content: string 8 | } 9 | 10 | export const Working: FC = (props) => { 11 | const { name, time, content } = props 12 | return ( 13 |
14 |
15 |
{name}
16 |
17 | {time[0]} - {time[1]} 18 |
19 |
20 |
21 |
') }}>
22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | github: 'https://github.com/lblblong/open-resume', 3 | } 4 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import 'antd/dist/antd.variable.min.css' 2 | import { configure } from 'mobx' 3 | import { ManagerProvider } from 'oh-popup-react' 4 | import 'oh-popup-react/dist/style.css' 5 | import { RouterView } from 'oh-router-react' 6 | import { createRoot } from 'react-dom/client' 7 | import 'src/assets/css/index.scss' 8 | import 'src/assets/css/var.css' 9 | import { router } from './router' 10 | import { popupManager } from './shared/popupManager' 11 | configure({ enforceActions: 'never' }) 12 | 13 | const root = createRoot(document.getElementById('root')!) 14 | 15 | root.render( 16 | <> 17 | 18 | 19 | 20 | ) 21 | -------------------------------------------------------------------------------- /src/pages/editor/aside/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | position: fixed; 3 | left: 0; 4 | top: 0; 5 | overflow-y: auto; 6 | height: 100vh; 7 | width: var(--form-width); 8 | background: #fff; 9 | display: flex; 10 | z-index: 8; 11 | padding-top: calc(var(--header-height)); 12 | 13 | .menu { 14 | border-right: 1px solid #efefef; 15 | width: 50px; 16 | .item { 17 | display: flex; 18 | flex-direction: column; 19 | align-items: center; 20 | padding: 24px 0; 21 | cursor: pointer; 22 | } 23 | 24 | .item:hover, 25 | .active { 26 | position: relative; 27 | background: rgba($color: #000000, $alpha: 0.06); 28 | &::after { 29 | content: ''; 30 | position: absolute; 31 | left: 0; 32 | top: 0; 33 | width: 100%; 34 | height: 100%; 35 | border-left: solid 4px var(--color-primary); 36 | } 37 | } 38 | } 39 | 40 | .container { 41 | flex: 1; 42 | overflow: hidden; 43 | position: relative; 44 | 45 | .content { 46 | height: 100%; 47 | width: 100%; 48 | padding: 16px; 49 | padding-bottom: 260px; 50 | overflow-y: scroll; 51 | .area + .area { 52 | margin-top: 18px; 53 | } 54 | } 55 | } 56 | 57 | .availableTemplates { 58 | .items { 59 | margin-top: 16px; 60 | display: grid; 61 | grid-template-columns: 1fr 1fr; 62 | gap: 16px; 63 | .template { 64 | width: 100%; 65 | padding: 8px; 66 | background: rgba($color: #000000, $alpha: 0.06); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/pages/editor/aside/index.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, Spin } from 'antd' 2 | import clsx from 'clsx' 3 | import { Observer, useObserver } from 'mobx-react-lite' 4 | import { Suspense, useRef } from 'react' 5 | import { TemplateCard } from 'src/components/templateCard' 6 | import { useStore } from 'src/shared/storeProvider' 7 | import { FloatingActions } from '../floatingActions' 8 | import { Store } from '../store' 9 | import styles from './index.module.scss' 10 | 11 | const FORM_MENUS = [ 12 | { 13 | label: '数据', 14 | value: 'data' as const, 15 | }, 16 | { 17 | label: '样式', 18 | value: 'config' as const, 19 | }, 20 | { 21 | label: '模板', 22 | value: 'template' as const, 23 | }, 24 | ] 25 | 26 | export const Aside = () => { 27 | const contentRef = useRef(null) 28 | const store = useStore() 29 | 30 | const renderAvailableTemplates = ( 31 | 32 | {() => ( 33 |
34 | 35 |
36 | {store.availableTemplates.map((it, i) => { 37 | return ( 38 |
39 | store.changeTemplate(it.key)} /> 40 |
41 | ) 42 | })} 43 | {store.availableTemplates.length === 0 && '暂无可用模板'} 44 |
45 |
46 | )} 47 |
48 | ) 49 | 50 | return useObserver(() => ( 51 |
52 |
53 | {FORM_MENUS.map((menu) => { 54 | return ( 55 |
(store.currentForm = menu.value)} 59 | > 60 | {menu.label.split('').map((word, i) => ( 61 | {word} 62 | ))} 63 |
64 | ) 65 | })} 66 |
67 |
68 | {store.currentForm !== 'template' && } 69 |
70 | {store.currentForm === 'data' && ( 71 | }> 72 | 73 | 74 | )} 75 | {store.currentForm === 'config' && ( 76 | }> 77 | 78 | 79 | )} 80 | {store.currentForm === 'template' && renderAvailableTemplates} 81 |
82 |
83 |
84 | )) 85 | } 86 | -------------------------------------------------------------------------------- /src/pages/editor/exportMenu/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | background: #fff; 3 | min-width: 300px; 4 | line-height: 1; 5 | box-shadow: var(--shadow-1-down); 6 | padding: 6px 8px; 7 | border-radius: 5px; 8 | .item { 9 | padding: 16px 8px; 10 | display: flex; 11 | align-items: center; 12 | cursor: pointer; 13 | &:hover { 14 | background: rgba($color: #000000, $alpha: 0.06); 15 | } 16 | .icon { 17 | width: 48px; 18 | } 19 | .titleBox { 20 | flex: 1; 21 | padding-left: 16px; 22 | .title { 23 | font-weight: bold; 24 | font-size: 16px; 25 | span { 26 | padding: 4px 8px; 27 | font-size: 8px; 28 | background: var(--color-primary); 29 | border-radius: 5px; 30 | margin-left: 8px; 31 | color: #fff; 32 | } 33 | } 34 | .des { 35 | padding-top: 10px; 36 | color: rgba($color: #000000, $alpha: 0.6); 37 | font-size: 13px; 38 | } 39 | } 40 | } 41 | .item + .item { 42 | border-top: 1px solid rgba($color: #000000, $alpha: 0.06); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/pages/editor/exportMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import jpeg from 'src/assets/image/icon/jpeg.png' 3 | import pdf from 'src/assets/image/icon/pdf.png' 4 | import { useStore } from 'src/shared/storeProvider' 5 | import { Store } from '../store' 6 | import styles from './index.module.scss' 7 | 8 | interface ExportMenuProps {} 9 | 10 | export const ExportMenu: FC = () => { 11 | const store = useStore() 12 | return ( 13 |
14 |
15 | 16 |
17 |
18 | 导出为 PDF推荐 19 |
20 |
适用于打印和分享
21 |
22 |
23 |
24 | 25 |
26 |
导出为图片
27 |
适用于分享
28 |
29 |
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/editor/floatingActions/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | background: #fff; 3 | box-shadow: var(--shadow-1-down); 4 | border-radius: 100px; 5 | overflow: hidden; 6 | padding: 4px; 7 | display: flex; 8 | 9 | .item { 10 | display: flex; 11 | align-items: center; 12 | cursor: pointer; 13 | padding: 4px 8px; 14 | border-radius: 100px; 15 | 16 | &:hover { 17 | background: rgba($color: #000000, $alpha: 0.03); 18 | } 19 | } 20 | } 21 | 22 | .symbolGroups { 23 | width: 352px; 24 | height: 435px; 25 | background: #fff; 26 | overflow-y: scroll; 27 | overflow-x: hidden; 28 | font-size: 16px; 29 | box-shadow: var(--shadow-1-down); 30 | border-radius: 12px; 31 | .group { 32 | margin: 16px; 33 | margin-right: 8px; 34 | overflow-x: hidden; 35 | .name { 36 | font-size: 18px; 37 | font-weight: bold; 38 | } 39 | .content { 40 | display: flex; 41 | flex-wrap: wrap; 42 | color: rgba($color: #000000, $alpha: 0.6); 43 | } 44 | } 45 | &::-webkit-scrollbar { 46 | width: 8px; 47 | } 48 | &::-webkit-scrollbar-thumb { 49 | border-radius: 10px; 50 | background-color: rgba($color: #000000, $alpha: 0.06); 51 | &:hover { 52 | background-color: rgba($color: #000000, $alpha: 0.1); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pages/editor/floatingActions/index.tsx: -------------------------------------------------------------------------------- 1 | import data from '@emoji-mart/data' 2 | import Picker from '@emoji-mart/react' 3 | import { Dropdown, message } from 'antd' 4 | import { FC } from 'react' 5 | import { FloatingBottom } from 'src/components/floatingBottom' 6 | import { copyToClipboard } from 'src/shared/func/copyToClipboard' 7 | import styles from './index.module.scss' 8 | 9 | interface FloatingActionsProps { 10 | target: React.MutableRefObject 11 | } 12 | 13 | const symbols = [ 14 | { 15 | name: '符号图案', 16 | content: 17 | '✐✎✏✑✒✉✁✂✃✄✆✉☎☏☑✓✔√☐☒✗✘ㄨ✕✖✖☢☠☣✈★☆✡囍㍿☯☰☲☱☴☵☶☳☷☜☞☚☛☟♤♧♡♢♠♣♥♦☀☁☂❄☃♨웃유❖☽☾☪✿♂♀✪✯☭➳卍卐√×■◆●○◐◑✙☺☻❀⚘♔♕♖♗♘♙♚♛♜♝♞♟♧♡♂♀♠♣♥❤☜☞☎☏⊙◎☺☻☼▧▨♨◐◑↔↕▪▒◊◦▣▤▥▦▩◘◈◇♬♪♩♭♪の★☆→あぃ£Ю〓§♤♥▶¤✲❈✿✲❈➹☀☂☁【】┱┲❣✚✪✣✤✥✦❉❥❦❧❃❂❁❀✄☪☣☢☠☭ღ▶▷◀◁☀☁☂☃☄★☆☇☈⊙☊☋☌☍ⓛⓞⓥⓔ╬『』∴☀♫♬♩♭♪☆∷﹌の★◎▶☺☻►◄▧▨♨◐◑↔↕↘▀▄█▌◦☼♪の☆→♧ぃ£❤▒▬♦◊◦♠♣▣۰•❤•۰►◄▧▨♨◐◑↔↕▪▫☼♦⊙●○①⊕◎Θ⊙¤㊣★☆♀◆◇◣◢◥▲▼△▽⊿◤◥✐✡✓✔✕✖♂♀♥♡☜☞☎☏⊙◎☺☻►◄▧▨♨◐◑↔↕♥♡▪▫☼♦▀▄█▌▐░▒▬♦◊◘◙◦☼♠♣▣▤▥▦▩◘◙◈♫♬♪♩♭♪✄☪☣☢☠♯♩♪♫♬♭♮☎☏☪ºº₪¤큐«»™♂✿♥ ◕‿-。 。◕‿◕。', 18 | }, 19 | { 20 | name: '箭头符号', 21 | content: 22 | '↑↓←→↖↗↘↙↔↕➻➼➽➸➳➺➻➴➵➶➷➹▶►▷◁◀◄«»➩➪➫➬➭➮➯➱⏎➲➾➔➘➙➚➛➜➝➞➟➠➡➢➣➤➥➦➧➨↚↛↜↝↞↟↠↠↡↢↣↤↤↥↦↧↨⇄⇅⇆⇇⇈⇉⇊⇋⇌⇍⇎⇏⇐⇑⇒⇓⇔⇖⇗⇘⇙⇜↩↪↫↬↭↮↯↰↱↲↳↴↵↶↷↸↹☇☈↼↽↾↿⇀⇁⇂⇃⇞⇟⇠⇡⇢⇣⇤⇥⇦⇧⇨⇩⇪↺↻⇚⇛', 23 | }, 24 | { 25 | name: '编号序号', 26 | content: 27 | '①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳⓪⓿❶❷❸❹❺❻❼❽❾❿⓫⓬⓭⓮⓯⓰⓱⓲⓳⓴⓵⓶⓷⓸⓹⓺⓻⓼⓽⓾㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉㈠㈡㈢㈣㈤㈥㈦㈧㈨㈩⑴⑵⑶⑷⑸⑹⑺⑻⑼⑽⑾⑿⒀⒁⒂⒃⒄⒅⒆⒇⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅰⅱⅲⅳⅴⅵⅶⅷⅸⅹⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵', 28 | }, 29 | { 30 | name: '标点符号', 31 | content: 32 | '。,、':∶;?‘’“”〝〞ˆˇ﹕︰﹔﹖﹑•¨….¸;!´?!~—ˉ|‖"〃`@﹫¡¿﹏﹋﹌︴々﹟#﹩$﹠&﹪%*﹡﹢﹦﹤‐ ̄¯―﹨ˆ˜﹍﹎+=<__-ˇ~﹉﹊()〈〉‹›﹛﹜『』〖〗[]《》〔〕{}「」【】︵︷︿︹︽_﹁﹃︻︶︸﹀︺︾ˉ﹂﹄︼❝❞‐‑‒–―‖‗‘’‚‛“”„‟†‡•‣․‥…‧‪‫‬‭‮ ‰‱′″‴‵‶‷‸※‼‽‾‿⁀⁁⁂⁃⁄⁇⁈⁉⁊⁋⁌⁍⁎⁏⁐⁑⁒⁓⁔⁕⁖⁗⁘⁙⁚⁛⁜⁝⁞ ⁠⁡⁢⁣⁤', 33 | }, 34 | { 35 | name: '数学符号', 36 | content: 37 | '﹢﹣×÷±+-*/^=≌∽≦≧≒﹤﹥≈≡≠≤≥≮≯∷∶∝∞∧∨∑∏∪∩∈∵∴⊥∥∠⌒⊙√∛∜∟⊿㏒㏑%‰⅟½⅓⅕⅙⅐⅛⅑⅒⅔¾⅖⅗⅘⅚⅜⅝⅞≂≃≄≅≆≇≉≊≋≍≎≏≐≑≓≔≕≖≗≘≙≚≛≜≝≞≟≢≣≨≩⊰⊱⋛⋚∫∮∬∭∯∰∱∲∳℅øπ∀∁∂∃∄∅∆∇∉∊∋∌∍∎∐−∓∔∕∖∗∘∙∡∢∣∤∦∸∹∺∻∼∾∿≀≁≪≫≬≭≰≱≲≳≴≵≶≷≸≹≺≻≼≽≾≿⊀⊁⊂⊃⊄⊅⊆⊇⊈⊉⊊⊋⊌⊍⊎⊏⊐⊑⊒⊓⊔⊕⊖⊗⊘⊚⊛⊜⊝⊞⊟⊠⊡⊢⊣⊤⊦⊧⊨⊩⊪⊫⊬⊭⊮⊯⊲⊳⊴⊵⊶⊷⊸⊹⊺⊻⊼⊽⊾⋀⋁⋂⋃⋄⋅⋆⋇⋈⋉⋊⋋⋌⋍⋎⋏⋐⋑⋒⋓⋔⋕⋖⋗⋘⋙⋜⋝⋞⋟⋠⋡⋢⋣⋤⋥⋦⋧⋨⋩⋪⋫⋬⋭⋮⋯⋰⋱⋲⋳⋴⋵⋶⋷⋸⋹⋺⋻⋼⋽⋾⋿ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫⅬⅭⅮⅯↁↂↃↅↆↇↈ↉↊↋■□▢▣▤▥▦▧▨▩▪▫▬▭▮▯▰▱▲△▴▵▶▷▸▹►▻▼▽▾▿◀◁◂◃◄◅◆◇◈◉◊○◌◍◎●◐◑◒◓◔◕◖◗◘◙◚◛◜◝◞◟◠◡◢◣◤◥◦◧◨◩◪◫◬◭◮◯◰◱◲◳◴◵◶◷◸◹◺◿◻◼◽◾⏢⏥⌓⌔⌖', 38 | }, 39 | { 40 | name: '上标下标', 41 | content: '⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ⁿ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎ ₐ ₑ ₒ ₓ ₔ ₕ ₖ ₗ ₘ ₙ ₚ ₛ ₜ', 42 | }, 43 | { 44 | name: '单位符号', 45 | content: 46 | '°′″$¥〒¢£%@℃℉﹩﹪‰﹫㎡㎥m²m³㎜㎟㎣㎝㎠㎤㍷㍸㍹㎞㎢㎦㏎㎚㎛㏕㎍㎎㎏㏄º○¤%$º¹²³㍺㎀㎁㎂㎃㎄㎅㎆㎇㎈㎉㎊㎋㎌㎐㎑㎒㎓㎔㎕㎖㎗㎘㎙㎧㎨㎩㎪㎫㎬㎭㎮㎯㎰㎱㎲㎳㎴㎵㎶㎷㎸㎹㎺㎻㎼㎽㎾㎿㏀㏁㏂㏃㏄㏅㏆㏇㏈㏉㏊㏋㏌㏍㏎㏏㏐㏑㏒㏓㏔㏕㏖㏗㏘㏙㏚㏛㏜㏝㏞㏟㍱㍲㍳㍴㍵㍶', 47 | }, 48 | { 49 | name: '货币符号', 50 | content: '€£Ұ₴$₰¢₤¥₳₲₪₵元₣₱฿¤₡₮₭₩ރ円₢₥₫₦zł﷼₠₧₯₨Kčर₹ƒ₸¢', 51 | }, 52 | { 53 | name: '希腊字母', 54 | content: 'ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζνξοπρσηθικλμτυφχψω', 55 | }, 56 | { 57 | name: '俄语字母', 58 | content: 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстуфхцчшщъыьэюя', 59 | }, 60 | { 61 | name: '汉语拼音', 62 | content: 63 | 'āáǎàōóǒòēéěèīíǐìūúǔùǖǘǚǜüêɑńňɡㄅㄆㄇㄈㄉㄊㄋㄌㄍㄎㄏㄐㄑㄒㄓㄔㄕㄖㄗㄘㄙㄚㄛㄜㄝㄞㄟㄠㄡㄢㄣㄤㄥㄦㄧㄨㄩ', 64 | }, 65 | { 66 | name: '中文字符', 67 | content: 68 | '零壹贰叁肆伍陆柒捌玖拾佰仟万亿吉太拍艾分厘毫微卍卐卄巜弍弎弐朤氺曱甴囍兀々〆のぁ〡〢〣〤〥〦〧〨〩㊎㊍㊌㊋㊏㊚㊛㊐㊊㊣㊤㊥㊦㊧㊨㊒㊫㊑㊓㊔㊕㊖㊗㊘㊜㊝㊞㊟㊠㊡㊢㊩㊪㊬㊭㊮㊯㊰㊀㊁㊂㊃㊄㊅㊆㊇㊈㊉', 69 | }, 70 | { 71 | name: '日文平假名片假名', 72 | content: 73 | 'ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんゔゕゖァアィイゥウェエォオカガキギクグケゲコゴサザシジスズセゼソゾタダチヂッツヅテデトドナニヌネノハバパヒビピフブプヘベペホボポマミムメモャヤュユョヨラリルレロヮワヰヱヲンヴヵヶヷヸヹヺ・ーヽヾヿ゠ㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ', 74 | }, 75 | { 76 | name: '制表符', 77 | content: 78 | '─ ━│┃╌╍╎╏┄ ┅┆┇┈ ┉┊┋┌┍┎┏┐┑┒┓└ ┕┖┗ ┘┙┚┛├┝┞┟┠┡┢┣ ┤┥┦┧┨┩┪┫┬ ┭ ┮ ┯ ┰ ┱ ┲ ┳ ┴ ┵ ┶ ┷ ┸ ┹ ┺ ┻┼ ┽ ┾ ┿ ╀ ╁ ╂ ╃ ╄ ╅ ╆ ╇ ╈ ╉ ╊ ╋ ╪ ╫ ╬═║╒╓╔ ╕╖╗╘╙╚ ╛╜╝╞╟╠ ╡╢╣╤ ╥ ╦ ╧ ╨ ╩ ╳╔ ╗╝╚ ╬ ═ ╓ ╩ ┠ ┨┯ ┷┏ ┓┗ ┛┳ ⊥ ﹃ ﹄┌ ╮ ╭ ╯╰', 79 | }, 80 | { 81 | name: '皇冠符号(国际象棋)', 82 | content: '♚ ♛ ♝ ♞ ♜ ♟ ♔ ♕ ♗ ♘ ♖ ♟', 83 | }, 84 | ] 85 | 86 | export const FloatingActions: FC = ({ target }) => { 87 | const renderSymbol = ( 88 |
89 | {symbols.map((s, i) => { 90 | return ( 91 |
92 |
{s.name}
93 |
94 | {s.content.split('').map((it, i) => ( 95 | {it} 96 | ))} 97 |
98 |
99 | ) 100 | })} 101 |
102 | ) 103 | 104 | return ( 105 | 106 |
107 | { 114 | copyToClipboard(e.native) 115 | message.success({ 116 | icon: <>, 117 | content: e.native + '已复制.', 118 | }) 119 | }} 120 | /> 121 | } 122 | trigger={['click']} 123 | placement="top" 124 | arrow={{ pointAtCenter: true }} 125 | > 126 |
😀Emoji
127 |
128 | 129 |
🔣特殊符号
130 |
131 |
132 |
133 | ) 134 | } 135 | -------------------------------------------------------------------------------- /src/pages/editor/header/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | position: fixed; 3 | left: 0; 4 | top: 0; 5 | height: var(--header-height); 6 | width: 100%; 7 | background: #fff; 8 | z-index: 10; 9 | border-bottom: 1px solid #efefef; 10 | min-width: 1000px; 11 | 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | line-height: 1; 16 | padding: 0 12px; 17 | 18 | .item { 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | border-radius: 8px; 23 | padding: 0 18px; 24 | height: calc(var(--header-height) * 0.6); 25 | color: #333; 26 | font-size: 18px; 27 | } 28 | 29 | .interactive { 30 | cursor: pointer; 31 | &:hover { 32 | background: rgba($color: #000, $alpha: 0.03); 33 | } 34 | } 35 | 36 | .right, 37 | .left { 38 | font-size: 22px; 39 | display: flex; 40 | align-items: center; 41 | } 42 | 43 | .left { 44 | .back { 45 | padding-left: 6px; 46 | font-weight: bold; 47 | .icon { 48 | font-size: 34px; 49 | font-weight: normal; 50 | } 51 | } 52 | 53 | .templateName { 54 | } 55 | } 56 | 57 | .right { 58 | > * { 59 | margin-left: 24px; 60 | .icon { 61 | margin-right: 8px; 62 | font-size: 20px; 63 | } 64 | } 65 | 66 | .export { 67 | color: #fff; 68 | width: 148px; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/pages/editor/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Dropdown } from 'antd' 2 | import clsx from 'clsx' 3 | import { useObserver } from 'mobx-react-lite' 4 | import { Icon } from 'src/components/icon' 5 | import { toIndex } from 'src/pages/index/route' 6 | import { useStore } from 'src/shared/storeProvider' 7 | import { appStore } from 'src/stores/app' 8 | import { ExportMenu } from '../exportMenu' 9 | import { Store } from '../store' 10 | import styles from './index.module.scss' 11 | 12 | export const Header = () => { 13 | const store = useStore() 14 | 15 | return useObserver(() => ( 16 |
17 |
18 |
toIndex()}> 19 | 20 | 首页 21 |
22 | / 23 |
{store.template.name}
24 |
25 | 26 |
27 | 31 | 32 | 36 | 37 | (store.exportMenuOpen = val)} 40 | overlay={} 41 | trigger={['click']} 42 | > 43 | 47 | 48 |
49 |
50 | )) 51 | } 52 | -------------------------------------------------------------------------------- /src/pages/editor/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --header-height: 76px; 3 | --form-width: 660px; 4 | // --a4w: 210px; 5 | // --a4h: 297px; 6 | // --scale: 3.95; 7 | // --content-width: calc(var(--a4w) * var(--scale)); 8 | // --content-height: calc(var(--a4h) * var(--scale) + 1px); 9 | --content-width: 830px; 10 | --content-height: 1174px; 11 | .main { 12 | padding-top: var(--header-height); 13 | padding-left: var(--form-width); 14 | background-color: #f1f1f1; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/pages/editor/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { Observer } from 'mobx-react-lite' 3 | import { useParams } from 'oh-router-react' 4 | import { useMemo } from 'react' 5 | import { StoreProvider } from 'src/shared/storeProvider' 6 | import { appStore } from 'src/stores/app' 7 | import { Aside } from './aside' 8 | import { Header } from './header' 9 | import styles from './index.module.scss' 10 | import { Main } from './main' 11 | import { Params } from './route' 12 | import { Store } from './store' 13 | 14 | export const EditorPage = () => { 15 | const { key } = useParams() 16 | const store = useMemo(() => { 17 | return new Store(key!) 18 | }, [key]) 19 | const Provider = useMemo(() => store.template.provider, [store]) 20 | 21 | return ( 22 | 23 | 24 | 25 | {() => ( 26 |
27 | {appStore.print ? ( 28 | 29 | ) : ( 30 |
31 |
32 |
35 | )} 36 |
37 | )} 38 |
39 |
40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/pages/editor/main/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | padding: 68px 16px; 3 | position: relative; 4 | .toolbar { 5 | position: absolute; 6 | top: 16px; 7 | right: 16px; 8 | border-radius: 2px; 9 | padding: 8px 16px; 10 | background: #fff; 11 | border-radius: 2px; 12 | } 13 | .content { 14 | margin: 0 auto; 15 | min-width: var(--content-width); 16 | width: var(--content-width); 17 | overflow-x: hidden; 18 | box-shadow: var(--shadow-1-down); 19 | } 20 | 21 | .gray { 22 | -webkit-filter: grayscale(0.95); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/editor/main/index.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox, Spin } from 'antd' 2 | import clsx from 'clsx' 3 | import { useObserver } from 'mobx-react-lite' 4 | import { Suspense } from 'react' 5 | import { useStore } from 'src/shared/storeProvider' 6 | import { Store } from '../store' 7 | import styles from './index.module.scss' 8 | 9 | export const Main = () => { 10 | const store = useStore() 11 | 12 | return useObserver(() => ( 13 |
14 |
15 | (store.grayPreview = e.target.checked)}> 16 | 黑白预览 17 | 18 |
19 | 20 |
21 |
22 | }> 23 | 24 | 25 |
26 |
27 |
28 | )) 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/editor/route.tsx: -------------------------------------------------------------------------------- 1 | import { router } from 'src/router' 2 | import { EditorPage } from '.' 3 | 4 | export const editorRoute = { path: '/editor/:key', element: } 5 | 6 | export type Params = { 7 | key: string 8 | } 9 | 10 | export type Data = { 11 | data?: any 12 | config?: any 13 | } 14 | 15 | export function toEditor(options: { params: Params; data?: any; replace?: boolean }) { 16 | router.navigate('/editor/' + options.params.key, { 17 | replace: options.replace, 18 | state: options.data, 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/editor/store.tsx: -------------------------------------------------------------------------------- 1 | import { message } from 'antd' 2 | import { saveAs } from 'file-saver' 3 | import { toBlob } from 'html-to-image' 4 | import { makeAutoObservable } from 'mobx' 5 | import { openExportPDFHelp } from 'src/components/popups/exportPDFHelp' 6 | import { router } from 'src/router' 7 | import { showInfo } from 'src/shared/toast' 8 | import { appStore } from 'src/stores/app' 9 | import { templates } from 'src/templates' 10 | import { BaseTemplate } from 'src/templates/template' 11 | import { toEditor } from './route' 12 | 13 | export class Store { 14 | template: BaseTemplate 15 | 16 | grayPreview = false 17 | 18 | constructor(key: string) { 19 | this.template = templates.find((tmp) => tmp.key === key)! 20 | const state: any = router.location?.state 21 | 22 | this.template.importConfig(state?.config) 23 | this.template.importData(state?.data) 24 | 25 | if (!state) { 26 | showInfo('已自动填充初始信息') 27 | } 28 | 29 | makeAutoObservable(this, { 30 | template: false, 31 | }) 32 | } 33 | 34 | currentForm: 'data' | 'config' | 'template' = 'data' 35 | 36 | get availableTemplates() { 37 | return templates.filter((t) => t.dataForm === this.template.dataForm && t.key !== this.template.key) 38 | } 39 | 40 | exportJson = () => { 41 | const config = this.template.exportConfig() 42 | const data = this.template.exportData() 43 | 44 | saveAs( 45 | new Blob( 46 | [ 47 | JSON.stringify({ 48 | templateKey: this.template.key, 49 | templateName: this.template.name, 50 | templateConfig: { 51 | config, 52 | data, 53 | }, 54 | }), 55 | ], 56 | { type: 'text/plain;charset=utf-8' } 57 | ), 58 | `resume.json` 59 | ) 60 | } 61 | 62 | exportMenuOpen = false 63 | 64 | exportPdf = async () => { 65 | this.exportMenuOpen = false 66 | await openExportPDFHelp() 67 | appStore.callPrint() 68 | } 69 | 70 | exportPng = async () => { 71 | this.exportMenuOpen = false 72 | try { 73 | message.loading({ 74 | key: 'exportPng', 75 | content: '正在导出为图片...', 76 | }) 77 | const container = document.getElementById('template-view')! 78 | 79 | const options = { 80 | type: 'image/jpeg', 81 | cacheBust: true, 82 | canvasHeight: container.clientHeight, 83 | canvasWidth: container.clientWidth, 84 | } 85 | 86 | // 提前调用两次避免生成空白的页面 87 | await toBlob(container, options) 88 | await toBlob(container, options) 89 | 90 | const data = await toBlob(container, options) 91 | saveAs(data!, 'resume.jpeg') 92 | message.destroy('exportPng') 93 | } catch (err: any) { 94 | message.error(err.message) 95 | } 96 | } 97 | 98 | changeTemplate = (key: string) => { 99 | const targetTemplate = templates.find((it) => it.key === key) 100 | if (!targetTemplate) return 101 | 102 | if (targetTemplate.dataForm === this.template.dataForm) { 103 | toEditor({ 104 | params: { key }, 105 | data: { 106 | data: this.template.exportData(), 107 | }, 108 | }) 109 | } else { 110 | toEditor({ 111 | params: { key }, 112 | }) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/pages/index/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | min-width: 1200px; 3 | background: rgba($color: #000000, $alpha: 0.06); 4 | header { 5 | height: 600px; 6 | position: relative; 7 | overflow: hidden; 8 | img { 9 | width: 100%; 10 | } 11 | .content { 12 | position: absolute; 13 | left: 0; 14 | top: 0; 15 | width: 100%; 16 | height: 100%; 17 | display: flex; 18 | flex-direction: column; 19 | justify-content: center; 20 | align-items: center; 21 | color: #fff; 22 | background: rgba($color: #000000, $alpha: 0.6); 23 | 24 | .title { 25 | font-size: 72px; 26 | } 27 | 28 | .actions { 29 | padding-top: 24px; 30 | display: flex; 31 | gap: 16px; 32 | .action { 33 | padding: 24px 50px; 34 | font-size: 18px; 35 | display: flex; 36 | justify-content: center; 37 | align-items: center; 38 | .icon { 39 | margin-right: 8px; 40 | } 41 | } 42 | .github { 43 | background: transparent; 44 | color: #fff; 45 | } 46 | } 47 | } 48 | } 49 | 50 | main, 51 | footer > .content { 52 | max-width: 1600px; 53 | margin: 0px auto; 54 | padding: 54px 16px; 55 | } 56 | 57 | main { 58 | .title { 59 | padding-bottom: 18px; 60 | font-size: 30px; 61 | font-weight: lighter; 62 | color: #333; 63 | } 64 | 65 | .templates { 66 | display: flex; 67 | gap: 38px; 68 | width: 100%; 69 | .template { 70 | width: 100%; 71 | padding: 8px; 72 | background: #fff; 73 | transition: all 0.3s; 74 | &:hover { 75 | box-shadow: var(--shadow-1-down); 76 | } 77 | } 78 | } 79 | 80 | .tip { 81 | padding-top: 24px; 82 | color: rgba($color: #000000, $alpha: 0.6); 83 | } 84 | } 85 | 86 | footer { 87 | background: #000; 88 | 89 | .content { 90 | width: 100%; 91 | display: flex; 92 | justify-content: flex-start; 93 | gap: 100px; 94 | } 95 | 96 | .thanks, 97 | .group { 98 | color: rgba($color: #ffffff, $alpha: 0.6); 99 | .title { 100 | font-size: 18px; 101 | padding-bottom: 12px; 102 | color: rgba($color: #ffffff, $alpha: 0.9); 103 | } 104 | } 105 | 106 | .thanks { 107 | .list { 108 | line-height: 1.8; 109 | } 110 | } 111 | 112 | .group { 113 | .des { 114 | padding-top: 8px; 115 | } 116 | .image { 117 | width: 160px; 118 | img { 119 | width: 100%; 120 | height: 100%; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/pages/index/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from 'antd' 2 | import clsx from 'clsx' 3 | import headBg from 'src/assets/image/index-head-bg.jpg' 4 | import qrcodeWechat from 'src/assets/image/qrcode-wechat.png' 5 | import { Icon } from 'src/components/icon' 6 | import { TemplateCard } from 'src/components/templateCard' 7 | import { toEditor } from 'src/pages/editor/route' 8 | import { appStore } from 'src/stores/app' 9 | import { templates } from 'src/templates' 10 | import styles from './index.module.scss' 11 | 12 | export const IndexPage = () => { 13 | return ( 14 |
15 |
16 | 17 |
18 |
开源的
19 |
在线简历编辑工具
20 | 21 |
22 | 32 | 42 | 46 |
47 |
48 |
49 | 50 |
51 |
简历模板
52 |
53 | {templates.map((it, i) => { 54 | return ( 55 |
56 | toEditor({ params: { key: it.key } })} /> 57 |
58 | ) 59 | })} 60 |
61 | 62 |
更多模板敬请期待...
63 |
64 | 65 |
66 |
67 |
68 |
感谢以下开源项目
69 |
70 |
React
71 |
Mobx
72 |
Ant Design
73 |
Formily
74 |
emoji-mart
75 |
file-saver
76 |
html-to-image
77 |
vite
78 |
79 |
80 | 81 |
82 |
开源不易
83 | 84 |
85 | 86 |
87 |
(请作者喝杯咖啡☕)
88 |
89 |
90 |
91 |
92 | ) 93 | } 94 | -------------------------------------------------------------------------------- /src/pages/index/route.tsx: -------------------------------------------------------------------------------- 1 | import { router } from 'src/router' 2 | import { IndexPage } from '.' 3 | 4 | export const indexRoute = { path: '/', element: } 5 | 6 | export function toIndex() { 7 | router.navigate('/') 8 | } 9 | -------------------------------------------------------------------------------- /src/router.tsx: -------------------------------------------------------------------------------- 1 | import { createHashHistory, Router } from 'oh-router' 2 | import { editorRoute } from 'src/pages/editor/route' 3 | import { indexRoute } from 'src/pages/index/route' 4 | 5 | export const router = new Router({ 6 | history: createHashHistory(), 7 | routes: [ 8 | indexRoute, 9 | editorRoute, 10 | { 11 | path: '*', 12 | element: '404', 13 | }, 14 | ], 15 | }) 16 | -------------------------------------------------------------------------------- /src/shared/formily/datas/developer/form.tsx: -------------------------------------------------------------------------------- 1 | import { Form, FormGrid, FormItem } from '@formily/antd' 2 | import { Field } from '@formily/react' 3 | import { Input } from 'antd' 4 | import { BasicData } from 'src/shared/formily/form/basic' 5 | import { TitleContentArray } from 'src/shared/formily/form/titleContentArray' 6 | import { TitleContentTimeRangeArray } from 'src/shared/formily/form/titleContentTimeRangeArray' 7 | 8 | export const DataForm = () => { 9 | return ( 10 |
11 |

基础信息

12 | 13 | 14 |

求职意向

15 | 16 | 17 |

教育信息

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |

简介

27 | 28 | 29 |

工作经历

30 | 31 | 32 |

个人项目

33 | 34 | 35 |

Github 链接

36 | 37 | 38 |

关于我

39 | 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/shared/formily/datas/developer/index.ts: -------------------------------------------------------------------------------- 1 | import { DataForm } from './form' 2 | 3 | const DEFAULT_DATA = { 4 | name: '小灰灰', 5 | gender: '男', 6 | age: '25岁', 7 | city: '深圳', 8 | phoneNumber: '18888888888', 9 | email: '1162275271@qq.com', 10 | education: { 11 | schoolName: '霍格沃兹', 12 | graduationTime: '2022', 13 | major: '黑魔法防御系', 14 | }, 15 | target: '前端 & NodeJS', 16 | introduce: `独立使用 React/Vue 开发过各端产品,包括但不限于:Web 应用、微信小程序、Electron 桌面应用、React Native 等。\n用 Java/Kotlin(SpringBoot、MyBatis) 写过后端,现在更熟悉 Node.js(nest/egg/koa)。\n用 Java/Kotlin 和 Flutter 写过安卓。\n独立开发者:从零到一上线过多款个人产品,有网页、安卓APP、公众号应用,小程序等。`, 17 | projects: [ 18 | { 19 | title: 'tarojs-router-next - Taro 路由库(★160+)', 20 | content: 21 | 'Taro 小程序路由库/自动生成带参数类型提示的路由方法/允许传递任意类型、任意大小的参数数据/同步的路由方法调用/ koa 体验一致的路由中间件。收录在 Taro 官方推荐物料中,正在为仓鼠星球的小程序端路由提供服务,运行稳定。', 22 | }, 23 | { 24 | title: 'open-resume - 在线简历系统', 25 | content: 26 | '纯前端在线简历编辑生成工具、支持导入导出配置、生成 PDF 和图片', 27 | }, 28 | ], 29 | github: 'github.com/lblblong', 30 | workingHistory: [ 31 | { 32 | title: '神奇动物管理控制司 - 前端开发工程师', 33 | content: `• 全球神奇动物大数据系统\n负责该项目网站及移动端开发,该项目主要使用 React + Mobx,我独立负责了该项目的神奇动物搜索和录入信息模块,在项目上线后运行良好,期间除了一些兼容性问题没有出现过任何代码逻辑上的 BUG。\n• 其他工作\n负责司内部分数据中台的开发和运维、神奇动物推广落地页的编写、其他前端工作。`, 34 | startTime: '2016.12', 35 | endTime: '2022.09', 36 | }, 37 | { 38 | title: '预言家日报 - NodeJS', 39 | content: `• 《预言家日报》\n负责该项目后端的开发,众所周知该报纸的特色是动态的页面,其实现原理是通过魔法在报纸中嵌入了一个 webview。我在该项目中负责报纸客户端相关接口的开发。`, 40 | startTime: '2016.12', 41 | endTime: '2022.09', 42 | }, 43 | ], 44 | aboutMe: `喜欢刷剧看电影、玩游戏,空余时间做做开源和一些小东西,喜欢各种提升生产效率的东西。`, 45 | } 46 | 47 | export type IDeveloperData = typeof DEFAULT_DATA 48 | 49 | export const DeveloperModel = { 50 | defaultData: DEFAULT_DATA, 51 | form: DataForm, 52 | } 53 | -------------------------------------------------------------------------------- /src/shared/formily/datas/normal-sidebar/form.tsx: -------------------------------------------------------------------------------- 1 | import { Form, Input } from '@formily/antd' 2 | import { Field } from '@formily/react' 3 | import { BasicData } from 'src/shared/formily/form/basic' 4 | import { TitleContentArray } from 'src/shared/formily/form/titleContentArray' 5 | import { TitleContentTimeRangeArray } from 'src/shared/formily/form/titleContentTimeRangeArray' 6 | 7 | export const DataForm = () => { 8 | return ( 9 |
10 |

基础信息

11 | 12 | 13 |

侧栏配置

14 | 15 | 16 |

工作经历

17 | 18 | 19 |

自我评价

20 | 21 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/shared/formily/datas/normal-sidebar/index.ts: -------------------------------------------------------------------------------- 1 | import { DataForm } from './form' 2 | 3 | const DEFAULT_DATA = { 4 | name: '小灰灰', 5 | gender: '男', 6 | age: '25岁', 7 | city: '深圳', 8 | phoneNumber: '18888888888', 9 | email: '1162275271@qq.com', 10 | introduce: `独立使用 React/Vue 开发过各端产品,包括但不限于:Web 应用、微信小程序、Electron 桌面应用、React Native 等。\n用 Java/Kotlin(SpringBoot、MyBatis) 写过后端,现在更熟悉 Node.js(nest/egg/koa)。\n用 Java/Kotlin 和 Flutter 写过安卓。\n独立开发者:从零到一上线过多款个人产品,有网页、安卓APP、公众号应用,小程序等。`, 11 | workingHistory: [ 12 | { 13 | title: '神奇动物管理控制司 - 前端开发工程师', 14 | content: `• 全球神奇动物大数据系统\n负责该项目网站及移动端开发,该项目主要使用 React + Mobx,我独立负责了该项目的神奇动物搜索和录入信息模块,在项目上线后运行良好,期间除了一些兼容性问题没有出现过任何代码逻辑上的 BUG。\n• 其他工作\n负责司内部分数据中台的开发和运维、神奇动物推广落地页的编写、其他前端工作。`, 15 | startTime: '2016.12', 16 | endTime: '2022.09', 17 | }, 18 | { 19 | title: '预言家日报 - NodeJS', 20 | content: `• 《预言家日报》\n负责该项目后端的开发,众所周知该报纸的特色是动态的页面,其实现原理是通过魔法在报纸中嵌入了一个 webview。我在该项目中负责报纸客户端相关接口的开发。`, 21 | startTime: '2016.12', 22 | endTime: '2022.09', 23 | }, 24 | ], 25 | sidebar: [ 26 | { 27 | title: '教育背景', 28 | content: `霍格沃兹\n黑魔法防御系\n2014-2018`, 29 | }, 30 | { 31 | title: '求职意向', 32 | content: `前端 & Node.JS\n薪资面谈\n深圳\n全职`, 33 | }, 34 | { 35 | title: '专业技能', 36 | content: `Vue\nReact\nJavaScript\nTypeScript\nNodeJS\nMobx\nReact Router`, 37 | }, 38 | ], 39 | } 40 | 41 | export type INormalSidevarData = typeof DEFAULT_DATA 42 | 43 | export const NormalSidebarModel = { 44 | defaultData: DEFAULT_DATA, 45 | form: DataForm, 46 | } 47 | -------------------------------------------------------------------------------- /src/shared/formily/form/basic/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormGrid, FormItem, Input } from '@formily/antd' 2 | import { Field, VoidField } from '@formily/react' 3 | 4 | export interface IBasicData { 5 | name: string 6 | gender: string 7 | age: string 8 | city: string 9 | phoneNumber: string 10 | email: string 11 | } 12 | 13 | export const BasicData = () => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/shared/formily/form/titleContentArray/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | background: #f9fafb; 3 | .item { 4 | padding: 12px; 5 | > * + * { 6 | margin-top: 8px; 7 | } 8 | } 9 | .item + .item { 10 | border-top: 1px solid rgba($color: #000000, $alpha: 0.03); 11 | } 12 | 13 | .add { 14 | padding: 12px; 15 | padding-top: 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/shared/formily/form/titleContentArray/index.tsx: -------------------------------------------------------------------------------- 1 | import { ArrayBase, ArrayItems, FormItem, Input } from '@formily/antd' 2 | import { ArrayField, Field } from '@formily/react' 3 | import { Space } from 'antd' 4 | import { FC } from 'react' 5 | import styles from './index.module.scss' 6 | 7 | interface TitleContentArrayProps { 8 | name: string 9 | titlePlaceholder?: string 10 | contentPlaceholder?: string 11 | } 12 | 13 | export const TitleContentArray: FC = ({ name, titlePlaceholder, contentPlaceholder }) => { 14 | return ( 15 | 16 |
17 | 18 | {(field) => ( 19 | 20 | {field.value.map((_, index) => { 21 | return ( 22 |
23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 |
31 | 35 | 36 |
37 | ) 38 | })} 39 | 40 |
41 | 42 |
43 |
44 | )} 45 |
46 |
47 |
48 | ) 49 | } 50 | 51 | TitleContentArray.defaultProps = { 52 | titlePlaceholder: '请输入标题', 53 | contentPlaceholder: '请输入内容', 54 | } 55 | -------------------------------------------------------------------------------- /src/shared/formily/form/titleContentTimeRangeArray/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | background: #f9fafb; 3 | .item { 4 | padding: 12px; 5 | > * + * { 6 | margin-top: 8px; 7 | } 8 | } 9 | .item + .item { 10 | border-top: 1px solid rgba($color: #000000, $alpha: 0.03); 11 | } 12 | 13 | .add { 14 | padding: 12px; 15 | padding-top: 0; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/shared/formily/form/titleContentTimeRangeArray/index.tsx: -------------------------------------------------------------------------------- 1 | import { ArrayBase, ArrayItems, FormItem, Input } from '@formily/antd' 2 | import { ArrayField, Field } from '@formily/react' 3 | import { Space } from 'antd' 4 | import { FC } from 'react' 5 | import styles from './index.module.scss' 6 | 7 | interface TitleContentTimeRangeArrayProps { 8 | name: string 9 | titlePlaceholder?: string 10 | contentPlaceholder?: string 11 | } 12 | 13 | export const TitleContentTimeRangeArray: FC = ({ 14 | name, 15 | titlePlaceholder, 16 | contentPlaceholder, 17 | }) => { 18 | return ( 19 | 20 |
21 | 22 | {(field) => ( 23 | 24 | {field.value.map((_, index) => { 25 | return ( 26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 |
35 | 36 | 37 | - 38 | 39 | 40 | 44 |
45 |
46 | ) 47 | })} 48 | 49 |
50 | 54 |
55 |
56 | )} 57 |
58 |
59 |
60 | ) 61 | } 62 | 63 | TitleContentTimeRangeArray.defaultProps = { 64 | titlePlaceholder: '请输入标题', 65 | contentPlaceholder: '请输入内容', 66 | } 67 | -------------------------------------------------------------------------------- /src/shared/formily/index.ts: -------------------------------------------------------------------------------- 1 | export { DeveloperModel as DeveloperData } from './datas/developer' 2 | export type { IDeveloperData } from './datas/developer' 3 | export { FormilyTemplate } from './template' 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/shared/formily/template.tsx: -------------------------------------------------------------------------------- 1 | import { createForm, Form, FormPath, onFieldInputValueChange } from '@formily/core' 2 | import { FormProvider } from '@formily/react' 3 | import { toJS } from 'mobx' 4 | import { ElementType, ReactNode } from 'react' 5 | import { TemplateProvider } from 'src/templates' 6 | import { BaseTemplate } from 'src/templates/template' 7 | 8 | export abstract class FormilyTemplate extends BaseTemplate { 9 | constructor() { 10 | super() 11 | this.form = createForm({ 12 | initialValues: this.data as any, 13 | effects: () => { 14 | onFieldInputValueChange('*', (field) => { 15 | FormPath.setIn(this.data, field.path, field.value) 16 | }) 17 | }, 18 | }) 19 | 20 | this.provider = ({ children }: { children: ReactNode }) => { 21 | return ( 22 | 23 | {children} 24 | 25 | ) 26 | } 27 | } 28 | 29 | importData(data?: D): void { 30 | if (data) { 31 | this.data = data 32 | } else { 33 | this.data = toJS(this.defaultData) 34 | } 35 | 36 | this.form.setValues(this.data) 37 | } 38 | 39 | provider: ElementType<{ children: ReactNode }> 40 | 41 | form: Form 42 | } 43 | -------------------------------------------------------------------------------- /src/shared/func/chooseFile.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 选择文件 3 | */ 4 | export function chooseFile(_options?: { 5 | /** 文件类型 */ 6 | accept?: string 7 | /** 是否允许多选 */ 8 | multiple?: boolean 9 | }) { 10 | return new Promise((ok) => { 11 | let options = { 12 | accept: 'image/*', 13 | multiple: true, 14 | } 15 | 16 | options = Object.assign(options, _options) 17 | 18 | const el: HTMLInputElement = document.createElement('input') 19 | document.body.appendChild(el) 20 | el.type = 'file' 21 | el.accept = options.accept 22 | el.multiple = options.multiple 23 | el.style.visibility = 'hidden' 24 | el.style.display = 'none' 25 | el.addEventListener('change', (_) => { 26 | ok(el.files as any) 27 | }) 28 | el.click() 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/shared/func/copyToClipboard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 复制到剪切板 3 | * @param {String} text 内容 4 | */ 5 | export function copyToClipboard(text: string) { 6 | if (navigator.clipboard) { 7 | return navigator.clipboard.writeText(text) 8 | } else { 9 | return new Promise((ok, fail) => { 10 | var textarea = document.createElement('textarea') 11 | document.body.appendChild(textarea) 12 | // 隐藏此输入框 13 | textarea.style.position = 'fixed' 14 | textarea.style.clip = 'rect(0 0 0 0)' 15 | textarea.style.top = '10px' 16 | // 赋值 17 | textarea.value = text 18 | // 选中 19 | textarea.select() 20 | try { 21 | // 复制 22 | if (document.execCommand('copy', true)) { 23 | ok() 24 | } else { 25 | throw Error('复制失败') 26 | } 27 | } catch (err) { 28 | fail(err) 29 | } finally { 30 | // 移除输入框 31 | document.body.removeChild(textarea) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/shared/hooks/useScrollFloating.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | export function useScrollFloating( 4 | target: React.MutableRefObject, 5 | options?: { 6 | threshold: number 7 | } 8 | ) { 9 | options = Object.assign({ threshold: 50 }, options) 10 | const [visible, setVisible] = useState(true) 11 | useEffect(() => { 12 | let timer: number, 13 | lastScrollTop = target.current?.scrollTop || 0 14 | 15 | const onScroll = (e: any) => { 16 | let distance = e.target.scrollTop - lastScrollTop 17 | if (Math.abs(distance) < options!.threshold) return 18 | setVisible(distance < 0) 19 | lastScrollTop = e.target.scrollTop 20 | } 21 | 22 | const _onScroll = (e: any) => { 23 | clearTimeout(timer) 24 | timer = window.setTimeout(() => { 25 | onScroll(e) 26 | }, 100) 27 | } 28 | 29 | target.current?.addEventListener('scroll', _onScroll) 30 | return () => target.current?.removeEventListener('scroll', _onScroll) 31 | }, [target]) 32 | 33 | return { visible } 34 | } 35 | -------------------------------------------------------------------------------- /src/shared/popupManager/index.ts: -------------------------------------------------------------------------------- 1 | import { PopupManager } from "oh-popup"; 2 | 3 | 4 | export const popupManager = new PopupManager() -------------------------------------------------------------------------------- /src/shared/storeProvider/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC, ReactNode } from 'react'; 2 | 3 | const StoreContext = React.createContext(undefined) 4 | 5 | export function useStore(): T { 6 | return React.useContext(StoreContext) as any 7 | } 8 | 9 | export const StoreProvider: FC<{ children: ReactNode; value: any }> = ({ children, value }) => { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /src/shared/toast/index.tsx: -------------------------------------------------------------------------------- 1 | import { message, Modal } from 'antd'; 2 | 3 | export function showSucess(text: string) { 4 | message.success({ 5 | content: text, 6 | }) 7 | } 8 | 9 | export function showInfo(text: string) { 10 | message.info({ 11 | content: text, 12 | }) 13 | } 14 | 15 | export function showWarning(text: string) { 16 | message.warning({ 17 | content: text, 18 | }) 19 | } 20 | 21 | export function showModel(opts: { title?: string; content: string }) { 22 | opts = Object.assign({ title: '提示' }, opts) 23 | return new Promise((ok, fail) => { 24 | Modal.confirm({ 25 | ...opts, 26 | onCancel: fail, 27 | onOk: ok, 28 | }) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/stores/app.ts: -------------------------------------------------------------------------------- 1 | import { makeAutoObservable } from 'mobx' 2 | import { config } from 'src/config' 3 | import { toEditor } from 'src/pages/editor/route' 4 | import { chooseFile } from 'src/shared/func/chooseFile' 5 | 6 | export const appStore = new (class { 7 | print = false 8 | 9 | constructor() { 10 | makeAutoObservable(this) 11 | 12 | window.onbeforeprint = () => { 13 | this.print = true 14 | } 15 | 16 | window.onafterprint = () => { 17 | this.print = false 18 | } 19 | } 20 | 21 | callPrint = () => { 22 | this.print = true 23 | setTimeout(() => { 24 | window.print() 25 | }) 26 | } 27 | 28 | importConfig = async () => { 29 | const files = await chooseFile({ 30 | multiple: false, 31 | accept: '.json', 32 | }) 33 | 34 | const file = files[0] 35 | const reader = new FileReader() 36 | reader.onloadend = () => { 37 | const config = JSON.parse(reader.result as string) 38 | toEditor({ 39 | params: { key: config.templateKey }, 40 | data: config.templateConfig, 41 | }) 42 | } 43 | reader.readAsText(file) 44 | } 45 | 46 | toGithub = () => { 47 | window.open(config.github) 48 | } 49 | })() 50 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg1.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg10.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg11.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg2.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg3.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg4.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg5.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg6.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg7.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg8.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/images/bg9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/images/bg9.png -------------------------------------------------------------------------------- /src/templates/developer-concise-1/assets/poster.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-1/assets/poster.jpeg -------------------------------------------------------------------------------- /src/templates/developer-concise-1/configForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | > * { 3 | margin-bottom: 16px; 4 | } 5 | 6 | .modules { 7 | .item { 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | padding: 8px 16px; 12 | background: #f7f7f7; 13 | } 14 | .item + .item { 15 | border-top: 1px solid #efefef; 16 | } 17 | } 18 | 19 | .bg { 20 | display: grid; 21 | grid-template-columns: 1fr 1fr; 22 | gap: 8px; 23 | .item { 24 | cursor: pointer; 25 | background: #efefef; 26 | img { 27 | width: 100%; 28 | height: 100%; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/configForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Space, Switch } from 'antd' 2 | import { useObserver } from 'mobx-react-lite' 3 | import { Icon } from 'src/components/icon' 4 | import { useTemplate } from 'src/templates' 5 | import { backgroundImages, DeveloperConcise1Template } from '..' 6 | import styles from './index.module.scss' 7 | 8 | const ConfigForm = () => { 9 | const { config, moveModule } = useTemplate() 10 | 11 | return useObserver(() => ( 12 |
13 |

模块

14 |
15 | {config.modules.map((it, i) => { 16 | return ( 17 |
18 | (it.name = e.target.value)} 23 | /> 24 | 25 | (it.visible = val)} 30 | /> 31 | 36 | 37 |
38 | ) 39 | })} 40 |
41 | 42 |

Github

43 | 44 |
51 | 是否要显示 Github 地址? 52 | (config.githubVisible = val)} 57 | /> 58 |
59 | 60 |

背景

61 | (config.backgroundImage = e.target.value)} 64 | placeholder="请选择以下图片或输入图片外链" 65 | /> 66 |
67 | {backgroundImages.map((it, i) => { 68 | return ( 69 |
(config.backgroundImage = it)}> 70 | 71 |
72 | ) 73 | })} 74 |
75 |
76 | )) 77 | } 78 | 79 | export default ConfigForm 80 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FormilyTemplate, IDeveloperData } from 'src/shared/formily' 3 | import { DeveloperModel } from 'src/shared/formily/datas/developer' 4 | import bg1 from './assets/images/bg1.png' 5 | import bg2 from './assets/images/bg2.png' 6 | import bg3 from './assets/images/bg3.png' 7 | import bg4 from './assets/images/bg4.png' 8 | import bg5 from './assets/images/bg5.png' 9 | import bg6 from './assets/images/bg6.png' 10 | import bg7 from './assets/images/bg7.png' 11 | import bg8 from './assets/images/bg8.png' 12 | import bg9 from './assets/images/bg9.png' 13 | import bg10 from './assets/images/bg10.png' 14 | import bg11 from './assets/images/bg11.png' 15 | import poster from './assets/poster.jpeg' 16 | 17 | export const backgroundImages = [bg1, bg2, bg3, bg4, bg5, bg6, bg7, bg8, bg9, bg10, bg11] 18 | 19 | const View = React.lazy(() => import('./view')) 20 | const ConfigForm = React.lazy(() => import('./configForm')) 21 | 22 | const DEFAULT_CONFIG = { 23 | modules: [ 24 | { 25 | key: 'introduce' as const, 26 | name: '简介', 27 | visible: true, 28 | }, 29 | { 30 | key: 'workingHistory' as const, 31 | name: '工作经历', 32 | visible: true, 33 | }, 34 | { 35 | key: 'ownerProjects' as const, 36 | name: '个人项目', 37 | visible: true, 38 | }, 39 | { 40 | key: 'aboutMe' as const, 41 | name: '关于我', 42 | visible: true, 43 | }, 44 | ], 45 | backgroundImage: backgroundImages[0], 46 | githubVisible: true, 47 | } 48 | 49 | export class DeveloperConcise1Template extends FormilyTemplate { 50 | key = 'developer-concise-1' 51 | name = '程序员创意简洁风格简历' 52 | tags = ['简洁', '设计感', '程序员'] 53 | poster = poster 54 | view = View 55 | configForm = ConfigForm 56 | dataForm = DeveloperModel.form 57 | defaultData = DeveloperModel.defaultData 58 | defaultConfig = DEFAULT_CONFIG 59 | 60 | moveModule = (index: number, targetIndex: number) => { 61 | if (targetIndex < 0) return 62 | let temp = this.config.modules[targetIndex] 63 | this.config.modules[targetIndex] = this.config.modules[index] 64 | this.config.modules[index] = temp 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/header/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | padding: 24px var(--padding-horizontal); 3 | display: flex; 4 | line-height: 1; 5 | position: relative; 6 | color: #333; 7 | 8 | .bg { 9 | position: absolute; 10 | left: 0; 11 | top: 0; 12 | width: 100%; 13 | height: 300px; 14 | .image { 15 | position: relative; 16 | width: 100%; 17 | height: 100%; 18 | } 19 | &::after { 20 | content: ''; 21 | position: absolute; 22 | left: 0; 23 | bottom: 0; 24 | width: 100%; 25 | height: 100%; 26 | background: linear-gradient(rgba(#fff, 0), 20%, #fff); 27 | } 28 | } 29 | 30 | .content { 31 | position: relative; 32 | flex: 1; 33 | padding: 2px 0; 34 | margin-top: 16px; 35 | display: flex; 36 | flex-direction: column; 37 | justify-content: space-between; 38 | font-size: 14px; 39 | .name { 40 | font-size: 36px; 41 | } 42 | .des { 43 | padding-top: 16px; 44 | line-height: 1.8; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { DeveloperConcise1Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const Header = () => { 7 | const { config, data } = useTemplate() 8 | 9 | return useObserver(() => ( 10 |
11 | {config.backgroundImage && ( 12 |
13 | 14 |
15 | )} 16 |
17 |
{data.name}
18 |
19 | 20 | {[data.age, data.gender, data.city, data.target, data.phoneNumber, data.email].filter(Boolean).join(' ∕ ')} 21 | 22 |
23 | {`${data.education.graduationTime} 年毕业于 ${data.education.schoolName} - ${data.education.major}`} 24 |
25 |
26 |
27 | )) 28 | } 29 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --color-text-primary: #595959; 3 | --color-text-secondary: #8c8c8c; 4 | --padding-horizontal: 32px; 5 | 6 | width: 100%; 7 | background-color: #fff; 8 | font-size: 16px; 9 | color: var(--color-text-primary); 10 | line-height: 1.65; 11 | position: relative; 12 | text-shadow: 0px 0px 3px rgba($color: #000000, $alpha: 0.06); 13 | 14 | > * { 15 | padding-left: var(--padding-horizontal); 16 | padding-right: var(--padding-horizontal); 17 | position: relative; 18 | } 19 | 20 | .header { 21 | padding-bottom: 28px; 22 | margin-left: calc(var(--padding-horizontal) * -1); 23 | margin-right: calc(var(--padding-horizontal) * -1); 24 | } 25 | 26 | .title { 27 | margin: 16px 0; 28 | } 29 | 30 | .content { 31 | padding-top: 6px; 32 | padding-bottom: 24px; 33 | } 34 | 35 | .introduce { 36 | > div { 37 | line-height: 1.65; 38 | } 39 | > div + div { 40 | margin-top: 6px; 41 | } 42 | } 43 | } 44 | 45 | @page { 46 | margin: 0; 47 | padding: 0; 48 | margin-top: 24px; 49 | margin-bottom: 24px; 50 | } 51 | 52 | @page:first { 53 | margin-top: 0; 54 | } 55 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { useObserver } from 'mobx-react-lite' 3 | import { FC } from 'react' 4 | import { useTemplate } from 'src/templates' 5 | import { DeveloperConcise1Template } from '..' 6 | import { Header } from './header' 7 | import styles from './index.module.scss' 8 | import { OwnerProjects } from './ownerProjects' 9 | import { Title } from './title' 10 | import { WorkingHistory } from './workingHistory' 11 | 12 | const View: FC = () => { 13 | const { config, data } = useTemplate() 14 | 15 | const renderContent = useObserver(() => { 16 | const modules = { 17 | introduce: ( 18 |
19 | {data.introduce.split('\n').map((it, i) => { 20 | return
{it}
21 | })} 22 |
23 | ), 24 | ownerProjects: ( 25 |
26 | 27 |
28 | ), 29 | workingHistory: ( 30 |
31 | 32 |
33 | ), 34 | aboutMe:
{data.aboutMe}
, 35 | } 36 | 37 | return config.modules 38 | .filter((it) => it.visible) 39 | .map((it, i) => { 40 | return ( 41 |
42 |
43 | 44 | </div> 45 | {modules[it.key]} 46 | </div> 47 | ) 48 | }) 49 | }) 50 | 51 | return useObserver(() => ( 52 | <div className={styles.index}> 53 | <div className={styles.header}> 54 | <Header /> 55 | </div> 56 | 57 | {renderContent} 58 | </div> 59 | )) 60 | } 61 | 62 | export default View 63 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/ownerProjects/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | .item { 3 | padding-bottom: 12px; 4 | .header { 5 | display: flex; 6 | justify-content: space-between; 7 | .name { 8 | background: #f2f2f2; 9 | padding: 0 8px; 10 | line-height: 1.8; 11 | border-radius: 4px; 12 | border-left: solid #dfdfdf 6px; 13 | } 14 | .time { 15 | color: var(--color-text-secondary); 16 | } 17 | } 18 | 19 | .des { 20 | margin-top: 6px; 21 | } 22 | } 23 | 24 | .more { 25 | margin-top: 4px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/ownerProjects/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { DeveloperConcise1Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const OwnerProjects = () => { 7 | const { config, data } = useTemplate<DeveloperConcise1Template>() 8 | 9 | return useObserver(() => ( 10 | <div className={styles.index}> 11 | {data.projects.map((it, i) => { 12 | return ( 13 | <div key={i} className={styles.item}> 14 | <div className={styles.header}> 15 | <div className={styles.name}>{it.title}</div> 16 | <div className={styles.time}></div> 17 | </div> 18 | <div className={styles.des} dangerouslySetInnerHTML={{ __html: it.content.replace(/\n/g, '<br/>') }}></div> 19 | </div> 20 | ) 21 | })} 22 | {config.githubVisible && ( 23 | <div className={styles.more}> 24 | ➱ 更多我的个人项目请看 GitHub: 25 | <span 26 | style={{ 27 | textDecoration: 'underline', 28 | }} 29 | > 30 | {data.github} 31 | </span> 32 | </div> 33 | )} 34 | </div> 35 | )) 36 | } 37 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/title/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | position: relative; 3 | line-height: 1; 4 | display: flex; 5 | align-items: center; 6 | .title { 7 | font-weight: bold; 8 | height: 24px; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | padding-right: 24px; 13 | font-size: 20px; 14 | color: #333; 15 | } 16 | .line { 17 | flex: 1; 18 | height: 1px; 19 | background: linear-gradient(to right, rgba($color: #000000, $alpha: 0.03), 86%, transparent); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/title/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { FC } from 'react' 3 | import styles from './index.module.scss' 4 | 5 | interface Props { 6 | value: string 7 | line?: boolean 8 | } 9 | 10 | export const Title: FC<Props> = (props) => { 11 | const { value, line } = props 12 | 13 | return ( 14 | <div className={styles.index}> 15 | <div className={styles.title}>{value}</div> 16 | {line && <div className={styles.line}></div>} 17 | </div> 18 | ) 19 | } 20 | 21 | Title.defaultProps = { 22 | line: true, 23 | } 24 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/workingHistory/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --time-color: var(--color-text-secondary); 3 | .item { 4 | padding-bottom: 12px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/templates/developer-concise-1/view/workingHistory/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { Working } from 'src/components/working' 3 | import { useTemplate } from 'src/templates' 4 | import { DeveloperConcise1Template } from '../..' 5 | import styles from './index.module.scss' 6 | 7 | export const WorkingHistory = () => { 8 | const { data } = useTemplate<DeveloperConcise1Template>() 9 | 10 | return useObserver(() => ( 11 | <div className={styles.index}> 12 | {data.workingHistory.map((it, i1) => { 13 | return ( 14 | <div className={styles.item} key={i1}> 15 | <Working name={it.title} time={[it.startTime, it.endTime]} content={it.content} /> 16 | </div> 17 | ) 18 | })} 19 | </div> 20 | )) 21 | } 22 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/assets/poster.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/developer-concise-2/assets/poster.jpeg -------------------------------------------------------------------------------- /src/templates/developer-concise-2/configForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | > * { 3 | margin-bottom: 16px; 4 | } 5 | 6 | .modules { 7 | .item { 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | padding: 8px 16px; 12 | background: #f7f7f7; 13 | } 14 | .item + .item { 15 | border-top: 1px solid #efefef; 16 | } 17 | } 18 | 19 | .bg { 20 | display: grid; 21 | grid-template-columns: 1fr 1fr; 22 | gap: 8px; 23 | .item { 24 | cursor: pointer; 25 | background: #efefef; 26 | img { 27 | width: 100%; 28 | height: 100%; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/configForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Input, Radio, Space, Switch } from 'antd' 2 | import { useObserver } from 'mobx-react-lite' 3 | import React from 'react' 4 | import { ColorSelect } from 'src/components/colorSelect' 5 | import { Icon } from 'src/components/icon' 6 | import { useTemplate } from 'src/templates' 7 | import { DeveloperConcise2Template, titles } from '..' 8 | import styles from './index.module.scss' 9 | 10 | const ConfigForm = () => { 11 | const { config, moveModule } = useTemplate<DeveloperConcise2Template>() 12 | 13 | return useObserver(() => ( 14 | <div className={styles.index}> 15 | <h3>模块</h3> 16 | <div className={styles.modules}> 17 | {config.modules.map((it, i) => { 18 | return ( 19 | <div key={it.key} className={styles.item}> 20 | <Input 21 | style={{ width: 300 }} 22 | bordered={false} 23 | value={it.name} 24 | onChange={(e) => (it.name = e.target.value)} 25 | /> 26 | <Space> 27 | <Switch 28 | checkedChildren="显示" 29 | unCheckedChildren="隐藏" 30 | checked={it.visible} 31 | onChange={(val) => (it.visible = val)} 32 | /> 33 | <Button 34 | shape="circle" 35 | onClick={() => moveModule(i, i - 1)} 36 | icon={<Icon value="arrow-up-line" />} 37 | ></Button> 38 | </Space> 39 | </div> 40 | ) 41 | })} 42 | </div> 43 | 44 | <h3>主色调</h3> 45 | <Input value={config.primaryColor} onChange={(e) => (config.primaryColor = e.target.value)} /> 46 | <ColorSelect onSelect={(color) => (config.primaryColor = color.value)} /> 47 | 48 | <h3>Github</h3> 49 | <div 50 | style={{ 51 | display: 'flex', 52 | alignItems: 'center', 53 | justifyContent: 'space-between', 54 | }} 55 | > 56 | 是否要显示 Github 地址? 57 | <Switch 58 | checkedChildren="显示" 59 | unCheckedChildren="隐藏" 60 | checked={config.githubVisible} 61 | onChange={(val) => (config.githubVisible = val)} 62 | /> 63 | </div> 64 | 65 | <h3>标题样式</h3> 66 | 67 | <div 68 | style={ 69 | { 70 | '--color-primary': config.primaryColor, 71 | } as any 72 | } 73 | > 74 | {titles.map((it) => { 75 | return ( 76 | <div> 77 | <Radio.Group value={config.titleStyle} onChange={(e) => (config.titleStyle = e.target.value)}> 78 | <Radio value={it.name}> 79 | <div style={{ padding: '6px 0', width: 300 }}> 80 | {React.createElement(it.component, { 81 | value: '标题样式', 82 | } as any)} 83 | </div> 84 | </Radio> 85 | </Radio.Group> 86 | </div> 87 | ) 88 | })} 89 | </div> 90 | </div> 91 | )) 92 | } 93 | 94 | export default ConfigForm 95 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/index.ts: -------------------------------------------------------------------------------- 1 | import { computed, makeObservable } from 'mobx' 2 | import React from 'react' 3 | import { FormilyTemplate, IDeveloperData } from 'src/shared/formily' 4 | import { DeveloperModel } from 'src/shared/formily/datas/developer' 5 | import poster from './assets/poster.jpeg' 6 | import { TitleStyle1 } from './view/titles/style1' 7 | import { TitleStyle2 } from './view/titles/style2' 8 | import { TitleStyle3 } from './view/titles/style3' 9 | import { TitleStyle4 } from './view/titles/style4' 10 | import { TitleStyle5 } from './view/titles/style5' 11 | import { TitleStyle6 } from './view/titles/style6' 12 | import { TitleStyle7 } from './view/titles/style7' 13 | 14 | const View = React.lazy(() => import('./view')) 15 | const ConfigForm = React.lazy(() => import('./configForm')) 16 | 17 | export const titles = [ 18 | { 19 | name: 'style1', 20 | component: TitleStyle1, 21 | }, 22 | { 23 | name: 'style2', 24 | component: TitleStyle2, 25 | }, 26 | { 27 | name: 'style3', 28 | component: TitleStyle3, 29 | }, 30 | { 31 | name: 'style4', 32 | component: TitleStyle4, 33 | }, 34 | { 35 | name: 'style5', 36 | component: TitleStyle5, 37 | }, 38 | { 39 | name: 'style6', 40 | component: TitleStyle6, 41 | }, 42 | { 43 | name: 'style7', 44 | component: TitleStyle7, 45 | }, 46 | ] 47 | 48 | const DEFAULT_CONFIG = { 49 | modules: [ 50 | { 51 | key: 'introduce' as const, 52 | name: '简介', 53 | visible: true, 54 | }, 55 | { 56 | key: 'workingHistory' as const, 57 | name: '工作经历', 58 | visible: true, 59 | }, 60 | { 61 | key: 'ownerProjects' as const, 62 | name: '个人项目', 63 | visible: true, 64 | }, 65 | { 66 | key: 'aboutMe' as const, 67 | name: '关于我', 68 | visible: true, 69 | }, 70 | ], 71 | githubVisible: true, 72 | primaryColor: '#002FA7', 73 | titleStyle: titles[0].name, 74 | } 75 | 76 | export class DeveloperConcise2Template extends FormilyTemplate<IDeveloperData, typeof DEFAULT_CONFIG> { 77 | key = 'developer-concise-2' 78 | name = '程序员简洁风简历' 79 | tags = ['简洁', '程序员'] 80 | view = View 81 | configForm = ConfigForm 82 | dataForm = DeveloperModel.form 83 | poster = poster 84 | defaultData = DeveloperModel.defaultData 85 | defaultConfig = DEFAULT_CONFIG 86 | 87 | constructor() { 88 | super() 89 | makeObservable(this, { 90 | titleComponent: computed, 91 | }) 92 | } 93 | 94 | get titleComponent() { 95 | return titles.find((it) => it.name === this.config.titleStyle)!.component 96 | } 97 | 98 | moveModule = (index: number, targetIndex: number) => { 99 | if (targetIndex < 0) return 100 | let temp = this.config.modules[targetIndex] 101 | this.config.modules[targetIndex] = this.config.modules[index] 102 | this.config.modules[index] = temp 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/header/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | padding: 24px var(--padding-horizontal); 3 | display: flex; 4 | line-height: 1; 5 | position: relative; 6 | color: #fff; 7 | background: var(--color-primary); 8 | 9 | .content { 10 | position: relative; 11 | flex: 1; 12 | padding: 2px 0; 13 | margin-top: 16px; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: space-between; 17 | font-size: 14px; 18 | .name { 19 | font-size: 36px; 20 | } 21 | .des { 22 | padding-top: 16px; 23 | line-height: 1.8; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { DeveloperConcise2Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const Header = () => { 7 | const { data } = useTemplate<DeveloperConcise2Template>() 8 | 9 | return useObserver(() => ( 10 | <div className={styles.index}> 11 | <div className={styles.content}> 12 | <div className={styles.name}>{data.name}</div> 13 | <div className={styles.des}> 14 | <span> 15 | {[data.age, data.gender, data.city, data.target, data.phoneNumber, data.email].filter(Boolean).join(' ∕ ')} 16 | </span> 17 | <br /> 18 | <span>{`${data.education.graduationTime} 年毕业于 ${data.education.schoolName} - ${data.education.major}`}</span> 19 | </div> 20 | </div> 21 | </div> 22 | )) 23 | } 24 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --color-primary: #002FA7; 3 | --color-text-primary: #595959; 4 | --color-text-secondary: #8c8c8c; 5 | --padding-horizontal: 32px; 6 | 7 | width: 100%; 8 | background-color: #fff; 9 | font-size: 16px; 10 | color: var(--color-text-primary); 11 | line-height: 1.65; 12 | position: relative; 13 | 14 | > * { 15 | padding-left: var(--padding-horizontal); 16 | padding-right: var(--padding-horizontal); 17 | position: relative; 18 | } 19 | 20 | .header { 21 | padding-bottom: 28px; 22 | margin-left: calc(var(--padding-horizontal) * -1); 23 | margin-right: calc(var(--padding-horizontal) * -1); 24 | } 25 | 26 | .title { 27 | margin: 16px 0; 28 | } 29 | 30 | .content { 31 | padding-top: 6px; 32 | padding-bottom: 24px; 33 | } 34 | 35 | .introduce { 36 | > div { 37 | line-height: 1.65; 38 | } 39 | > div + div { 40 | margin-top: 6px; 41 | } 42 | } 43 | } 44 | 45 | @page { 46 | margin: 0; 47 | padding: 0; 48 | margin-top: 24px; 49 | margin-bottom: 24px; 50 | } 51 | 52 | @page:first { 53 | margin-top: 0; 54 | } 55 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { useObserver } from 'mobx-react-lite' 3 | import React, { FC } from 'react' 4 | import { useTemplate } from 'src/templates' 5 | import { DeveloperConcise2Template } from '..' 6 | import { Header } from './header' 7 | import styles from './index.module.scss' 8 | import { OwnerProjects } from './ownerProjects' 9 | import { WorkingHistory } from './workingHistory' 10 | 11 | const View: FC = () => { 12 | const store = useTemplate<DeveloperConcise2Template>() 13 | const config = store.config 14 | const data = store.data 15 | 16 | const renderContent = useObserver(() => { 17 | const modules = { 18 | introduce: ( 19 | <div className={clsx(styles.content, styles.introduce)}> 20 | {data.introduce.split('\n').map((it, i) => { 21 | return <div key={i}>{it}</div> 22 | })} 23 | </div> 24 | ), 25 | ownerProjects: ( 26 | <div className={clsx(styles.content, styles.ownerProjects)}> 27 | <OwnerProjects /> 28 | </div> 29 | ), 30 | workingHistory: ( 31 | <div className={clsx(styles.content)}> 32 | <WorkingHistory /> 33 | </div> 34 | ), 35 | aboutMe: <div className={clsx(styles.content)}>{data.aboutMe}</div>, 36 | } 37 | 38 | return config.modules 39 | .filter((it) => it.visible) 40 | .map((it) => { 41 | return ( 42 | <div key={it.key}> 43 | <div className={styles.title}> 44 | {React.createElement(store.titleComponent, { 45 | value: it.name, 46 | })} 47 | </div> 48 | {modules[it.key]} 49 | </div> 50 | ) 51 | }) 52 | }) 53 | 54 | return useObserver(() => ( 55 | <div 56 | className={styles.index} 57 | style={ 58 | { 59 | '--color-primary': config.primaryColor, 60 | } as any 61 | } 62 | > 63 | <div className={styles.header}> 64 | <Header /> 65 | </div> 66 | 67 | {renderContent} 68 | </div> 69 | )) 70 | } 71 | 72 | export default View 73 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/ownerProjects/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | .item { 3 | padding-bottom: 12px; 4 | .header { 5 | display: flex; 6 | justify-content: space-between; 7 | .name { 8 | font-weight: bold; 9 | } 10 | } 11 | 12 | .des { 13 | margin-top: 6px; 14 | } 15 | } 16 | 17 | .more { 18 | margin-top: 4px; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/ownerProjects/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { DeveloperConcise2Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const OwnerProjects = () => { 7 | const { data, config } = useTemplate<DeveloperConcise2Template>() 8 | 9 | return useObserver(() => ( 10 | <div className={styles.index}> 11 | {data.projects.map((it, i) => { 12 | return ( 13 | <div key={i} className={styles.item}> 14 | <div className={styles.header}> 15 | <div className={styles.name}>{it.title}</div> 16 | <div className={styles.time}></div> 17 | </div> 18 | <div className={styles.des} dangerouslySetInnerHTML={{ __html: it.content.replace(/\n/g, '<br/>') }}></div> 19 | </div> 20 | ) 21 | })} 22 | {config.githubVisible && ( 23 | <div className={styles.more}> 24 | ➱ 更多我的个人项目请看 GitHub: 25 | <span 26 | style={{ 27 | textDecoration: 'underline', 28 | }} 29 | > 30 | {data.github} 31 | </span> 32 | </div> 33 | )} 34 | </div> 35 | )) 36 | } 37 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style1/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --height: 28px; 3 | border-bottom: 2px solid var(--color-primary); 4 | color: #fff; 5 | height: var(--height); 6 | .text { 7 | display: inline-block; 8 | position: relative; 9 | height: var(--height); 10 | line-height: var(--height); 11 | background: var(--color-primary); 12 | text-align: center; 13 | padding-right: 16px; 14 | padding-left: 18px; 15 | font-size: 18px; 16 | font-weight: bold; 17 | &::after { 18 | position: absolute; 19 | right: calc(var(--height) * -1); 20 | content: ''; 21 | width: 0; 22 | height: 0; 23 | border-bottom: var(--height) solid var(--color-primary); 24 | border-right: var(--height) solid transparent; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style1/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle1: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.text}>{value}</div> 14 | </div> 15 | ) 16 | } 17 | 18 | TitleStyle1.defaultProps = {} 19 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style2/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --height: 28px; 3 | border-bottom: 2px solid var(--color-primary); 4 | color: #fff; 5 | height: var(--height); 6 | .text { 7 | display: inline-block; 8 | position: relative; 9 | height: var(--height); 10 | line-height: var(--height); 11 | background: var(--color-primary); 12 | text-align: center; 13 | padding-right: 16px; 14 | padding-left: 16px; 15 | font-size: 18px; 16 | font-weight: bold; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style2/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle2: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.text}>{value}</div> 14 | </div> 15 | ) 16 | } 17 | 18 | TitleStyle2.defaultProps = {} 19 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style3/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --height: 28px; 3 | height: var(--height); 4 | position: relative; 5 | color: var(--color-primary); 6 | .bg { 7 | background: var(--color-primary); 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | width: 100%; 12 | height: 100%; 13 | opacity: 0.06; 14 | } 15 | .text { 16 | display: inline-block; 17 | position: relative; 18 | height: var(--height); 19 | line-height: var(--height); 20 | text-align: center; 21 | padding-right: 16px; 22 | padding-left: 16px; 23 | font-size: 18px; 24 | font-weight: bold; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style3/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle3: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.bg}></div> 14 | <div className={styles.text}>{value}</div> 15 | </div> 16 | ) 17 | } 18 | 19 | TitleStyle3.defaultProps = {} 20 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style4/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --height: 28px; 3 | height: var(--height); 4 | position: relative; 5 | color: var(--color-primary); 6 | border-left: 4px solid var(--color-primary); 7 | .bg { 8 | background: var(--color-primary); 9 | position: absolute; 10 | left: 0; 11 | top: 0; 12 | width: 100%; 13 | height: 100%; 14 | opacity: 0.06; 15 | } 16 | .text { 17 | display: inline-block; 18 | position: relative; 19 | height: var(--height); 20 | line-height: var(--height); 21 | text-align: center; 22 | padding-right: 16px; 23 | padding-left: 16px; 24 | font-size: 18px; 25 | font-weight: bold; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style4/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle4: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.bg}></div> 14 | <div className={styles.text}>{value}</div> 15 | </div> 16 | ) 17 | } 18 | 19 | TitleStyle4.defaultProps = {} 20 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style5/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --height: 24px; 3 | height: var(--height); 4 | position: relative; 5 | color: var(--color-primary); 6 | display: flex; 7 | align-items: center; 8 | padding-left: 6px; 9 | .bg { 10 | background: var(--color-primary); 11 | position: absolute; 12 | left: 0; 13 | top: 0; 14 | width: 100%; 15 | height: 100%; 16 | opacity: 0.1; 17 | transform: skew(-10deg); 18 | } 19 | .text { 20 | display: inline-block; 21 | position: relative; 22 | text-align: center; 23 | padding-right: 16px; 24 | padding-left: 16px; 25 | font-size: 18px; 26 | font-weight: bold; 27 | line-height: calc(var(--height) + 6px); 28 | height: calc(var(--height) + 6px); 29 | background: var(--color-primary); 30 | color: #fff; 31 | transform: skew(-10deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style5/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle5: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.bg}></div> 14 | <div className={styles.text}>{value}</div> 15 | </div> 16 | ) 17 | } 18 | 19 | TitleStyle5.defaultProps = {} 20 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style6/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | position: relative; 3 | color: var(--color-primary); 4 | border-bottom: 1px solid var(--color-primary); 5 | 6 | .text { 7 | display: inline-block; 8 | position: relative; 9 | text-align: center; 10 | font-size: 18px; 11 | font-weight: bold; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style6/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle6: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.bg}></div> 14 | <div className={styles.text}>{value}</div> 15 | </div> 16 | ) 17 | } 18 | 19 | TitleStyle6.defaultProps = {} 20 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style7/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | color: var(--color-primary); 3 | 4 | .text { 5 | font-size: 18px; 6 | font-weight: bold; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style7/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle7: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.bg}></div> 14 | <div className={styles.text}>{value}</div> 15 | </div> 16 | ) 17 | } 18 | 19 | TitleStyle7.defaultProps = {} 20 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style8/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | position: relative; 3 | color: var(--color-primary); 4 | border-bottom: 1px solid var(--color-primary); 5 | 6 | .text { 7 | display: inline-block; 8 | position: relative; 9 | text-align: center; 10 | font-size: 18px; 11 | font-weight: bold; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/titles/style8/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface Props { 5 | value: string 6 | } 7 | 8 | export const TitleStyle8: FC<Props> = (props) => { 9 | const { value } = props 10 | 11 | return ( 12 | <div className={styles.index}> 13 | <div className={styles.bg}></div> 14 | <div className={styles.text}>{value}</div> 15 | </div> 16 | ) 17 | } 18 | 19 | TitleStyle8.defaultProps = {} 20 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/workingHistory/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --time-color: var(--color-text-secondary); 3 | .item { 4 | padding-bottom: 12px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/templates/developer-concise-2/view/workingHistory/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { Working } from 'src/components/working' 3 | import { useTemplate } from 'src/templates' 4 | import { DeveloperConcise2Template } from '../..' 5 | import styles from './index.module.scss' 6 | 7 | export const WorkingHistory = () => { 8 | const { data } = useTemplate<DeveloperConcise2Template>() 9 | 10 | return useObserver(() => ( 11 | <div className={styles.index}> 12 | {data.workingHistory.map((it, i1) => { 13 | return ( 14 | <div className={styles.item} key={i1}> 15 | <Working name={it.title} time={[it.startTime, it.endTime]} content={it.content} /> 16 | </div> 17 | ) 18 | })} 19 | </div> 20 | )) 21 | } 22 | -------------------------------------------------------------------------------- /src/templates/index.tsx: -------------------------------------------------------------------------------- 1 | import { DeveloperConcise1Template } from './developer-concise-1' 2 | import { DeveloperConcise2Template } from './developer-concise-2' 3 | import { SimpleOnePage1Template } from './simple-one-page-1' 4 | import { SimpleOnePage2Template } from './simple-one-page-2' 5 | import { BaseTemplate } from './template' 6 | 7 | export { TemplateProvider, useTemplate } from './template' 8 | 9 | export const templates: BaseTemplate[] = [ 10 | new DeveloperConcise1Template(), 11 | new DeveloperConcise2Template(), 12 | new SimpleOnePage1Template(), 13 | new SimpleOnePage2Template(), 14 | ] 15 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/assets/poster.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/simple-one-page-1/assets/poster.jpeg -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/configForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | > * { 3 | margin-bottom: 16px; 4 | } 5 | 6 | .backgrounds { 7 | display: grid; 8 | grid-template-columns: 1fr 1fr; 9 | gap: 8px; 10 | .item { 11 | width: 100%; 12 | height: 100px; 13 | border: 1px solid #dfdfdf; 14 | border-radius: 5px; 15 | display: flex; 16 | justify-content: flex-end; 17 | align-items: flex-end; 18 | padding: 8px; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/configForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from 'antd' 2 | import { useObserver } from 'mobx-react-lite' 3 | import { ColorSelect } from 'src/components/colorSelect' 4 | import { useTemplate } from 'src/templates' 5 | import { SimpleOnePage1Template } from '..' 6 | import styles from './index.module.scss' 7 | 8 | const backgrounds = [ 9 | { 10 | label: '纯白', 11 | value: '#ffffff', 12 | }, 13 | { 14 | label: '光晕', 15 | value: `linear-gradient( 16 | 140deg, 17 | #fff1f032 0%, 18 | #fff2e832 6%, 19 | #fff7e632 13%, 20 | #fffbe632 22%, 21 | #fcffe632 37%, 22 | #f6ffed32 56%, 23 | #e6fffb32 70%, 24 | #e6f7ff32 79%, 25 | #f0f5ff32 86%, 26 | #f9f0ff32 91%, 27 | #fff0f632 96%, 28 | #fff0f632 100% 29 | )`, 30 | }, 31 | { 32 | label: '浅蓝', 33 | value: '#f0f5ff32', 34 | }, 35 | { 36 | label: '浅红', 37 | value: '#fff0f632', 38 | }, 39 | ] 40 | 41 | const ConfigForm = () => { 42 | const { config } = useTemplate<SimpleOnePage1Template>() 43 | 44 | return useObserver(() => ( 45 | <div className={styles.index}> 46 | <h3>主色调</h3> 47 | <Input value={config.primaryColor} onChange={(e) => (config.primaryColor = e.target.value)} /> 48 | <ColorSelect onSelect={(color) => (config.primaryColor = color.value)} /> 49 | 50 | <h3>背景</h3> 51 | <Input.TextArea 52 | autoSize 53 | value={config.background} 54 | onChange={(e) => (config.background = e.target.value)} 55 | ></Input.TextArea> 56 | 57 | <div className={styles.backgrounds}> 58 | {backgrounds.map((it, i) => { 59 | return ( 60 | <div 61 | key={i} 62 | className={styles.item} 63 | onClick={() => (config.background = it.value)} 64 | style={{ background: it.value }} 65 | > 66 | {it.label} 67 | </div> 68 | ) 69 | })} 70 | </div> 71 | </div> 72 | )) 73 | } 74 | 75 | export default ConfigForm 76 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { INormalSidevarData, NormalSidebarModel } from '../../shared/formily/datas/normal-sidebar' 3 | import { FormilyTemplate } from '../../shared/formily/template' 4 | import poster from './assets/poster.jpeg' 5 | 6 | const View = React.lazy(() => import('./view')) 7 | const ConfigForm = React.lazy(() => import('./configForm')) 8 | 9 | const DEFAULT_CONFIG = { 10 | primaryColor: '#002FA7', 11 | background: '#ffffff', 12 | } 13 | 14 | export class SimpleOnePage1Template extends FormilyTemplate<INormalSidevarData, typeof DEFAULT_CONFIG> { 15 | key = 'simple-one-page' 16 | name = '简洁设计感简历1' 17 | tags = ['单页', '侧栏', '设计感'] 18 | view = View 19 | dataForm = NormalSidebarModel.form 20 | configForm = ConfigForm 21 | poster = poster 22 | defaultData = NormalSidebarModel.defaultData 23 | defaultConfig = DEFAULT_CONFIG 24 | } 25 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/baseInfo/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | padding: 38px var(--padding-horizontal); 3 | display: flex; 4 | justify-content: space-between; 5 | font-size: 16px; 6 | } 7 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/baseInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { SimpleOnePage1Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const BaseInfo = () => { 7 | const { data } = useTemplate<SimpleOnePage1Template>() 8 | 9 | return useObserver(() => { 10 | const items = [ 11 | { 12 | label: '年龄', 13 | value: data.age, 14 | }, 15 | { 16 | label: '性别', 17 | value: data.gender, 18 | }, 19 | { 20 | label: '城市', 21 | value: data.city, 22 | }, 23 | { 24 | label: '电话', 25 | value: data.phoneNumber, 26 | }, 27 | { 28 | label: '邮箱', 29 | value: data.email, 30 | }, 31 | ] 32 | 33 | return ( 34 | <div className={styles.index}> 35 | {items.map((it, i) => { 36 | return ( 37 | <div key={i}> 38 | {it.label}: {it.value} 39 | </div> 40 | ) 41 | })} 42 | </div> 43 | ) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/header/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | padding: 0 var(--padding-horizontal); 3 | padding-top: var(--padding-horizontal); 4 | line-height: 1; 5 | font-weight: bold; 6 | color: #333; 7 | 8 | .hello { 9 | font-size: 94px; 10 | span { 11 | color: var(--color-primary); 12 | } 13 | } 14 | .name { 15 | font-size: 32px; 16 | padding-top: 16px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { SimpleOnePage1Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const Header = () => { 7 | const { data } = useTemplate<SimpleOnePage1Template>() 8 | return useObserver(() => ( 9 | <div className={styles.index}> 10 | <div className={styles.hello}> 11 | HELLO<span>°</span> 12 | </div> 13 | <div className={styles.name}>我是{data.name}</div> 14 | </div> 15 | )) 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --color-primary: #002fa7; 3 | --background: #fff; 4 | --padding-horizontal: 38px; 5 | --fontsize-h1: 18px; 6 | --fontsize-h2: 16px; 7 | 8 | width: var(--content-width); 9 | height: var(--content-height); 10 | 11 | overflow: hidden; 12 | display: flex; 13 | flex-direction: column; 14 | background: var(--background); 15 | 16 | main { 17 | flex: 1; 18 | display: flex; 19 | padding: 0 var(--padding-horizontal); 20 | padding-bottom: var(--padding-horizontal); 21 | font-size: 16px; 22 | line-height: 1.8; 23 | 24 | .left { 25 | flex-shrink: 0; 26 | width: 230px; 27 | height: 100%; 28 | display: flex; 29 | flex-direction: column; 30 | border-right: 1px solid rgba($color: #000000, $alpha: 0.03); 31 | padding-right: 24px; 32 | > * + * { 33 | margin-top: 38px; 34 | } 35 | } 36 | .right { 37 | padding-left: 38px; 38 | 39 | .title { 40 | margin-left: -12px; 41 | } 42 | 43 | .item { 44 | padding-top: 16px; 45 | } 46 | 47 | .introduceTitle { 48 | margin-top: 32px; 49 | } 50 | 51 | .introduce { 52 | padding-top: 16px; 53 | } 54 | } 55 | } 56 | } 57 | 58 | @page { 59 | margin: 0; 60 | padding: 0; 61 | } 62 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/index.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx' 2 | import { useObserver } from 'mobx-react-lite' 3 | import { Working } from 'src/components/working' 4 | import { useTemplate } from 'src/templates' 5 | import { SimpleOnePage1Template } from '..' 6 | import { BaseInfo } from './baseInfo' 7 | import { Header } from './header' 8 | import styles from './index.module.scss' 9 | import { LeftItem } from './leftItem' 10 | import { Title } from './title' 11 | 12 | const View = () => { 13 | const { config, data } = useTemplate<SimpleOnePage1Template>() 14 | return useObserver(() => ( 15 | <div 16 | className={styles.index} 17 | style={ 18 | { 19 | '--color-primary': config.primaryColor, 20 | '--background': config.background, 21 | } as any 22 | } 23 | > 24 | <Header /> 25 | <BaseInfo /> 26 | <main> 27 | <div className={styles.left}> 28 | {data.sidebar.map((p, i) => { 29 | return <LeftItem key={i} title={p.title} labels={p.content.split('\n')} /> 30 | })} 31 | </div> 32 | <div className={styles.right}> 33 | <div className={styles.title}> 34 | <Title>工作经历 35 |
36 | 37 | {data.workingHistory.map((it, i1) => { 38 | return ( 39 |
40 | 41 |
42 | ) 43 | })} 44 | 45 |
46 | 自我评价 47 |
48 | 49 |
{data.introduce}
50 |
51 | 52 | 53 | )) 54 | } 55 | 56 | export default View 57 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/leftItem/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | .title { 3 | margin-left: -12px; 4 | } 5 | .labels { 6 | .item { 7 | line-height: 1.8; 8 | padding-top: 8px; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/leftItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { Title } from '../title' 3 | import styles from './index.module.scss' 4 | 5 | interface LeftItemProps { 6 | title: string 7 | labels: string[] 8 | } 9 | 10 | export const LeftItem: FC = ({ title, labels }) => { 11 | return ( 12 |
13 |
14 | {title} 15 |
16 |
17 | {labels.map((it, i) => { 18 | return ( 19 |
20 | {it} 21 |
22 | ) 23 | })} 24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/title/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | line-height: 1; 3 | padding: 8px 12px; 4 | font-weight: bold; 5 | background: rgba($color: #000000, $alpha: 0.05); 6 | font-size: var(--fontsize-h1); 7 | } 8 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-1/view/title/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface TitleProps { 5 | children: ReactNode 6 | } 7 | 8 | export const Title: FC = ({ children }) => { 9 | return
{children}
10 | } 11 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/assets/poster.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lblblong/open-resume/77a1152c72982a0b026715f4ae0aab9e0050f489/src/templates/simple-one-page-2/assets/poster.jpeg -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/configForm/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | > * { 3 | margin-bottom: 16px; 4 | } 5 | 6 | .backgrounds { 7 | display: grid; 8 | grid-template-columns: 1fr 1fr; 9 | gap: 8px; 10 | .item { 11 | width: 100%; 12 | height: 100px; 13 | border: 1px solid #dfdfdf; 14 | border-radius: 5px; 15 | display: flex; 16 | justify-content: flex-end; 17 | align-items: flex-end; 18 | padding: 8px; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/configForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from 'antd' 2 | import { useObserver } from 'mobx-react-lite' 3 | import { ColorSelect } from 'src/components/colorSelect' 4 | import { useTemplate } from 'src/templates' 5 | import { SimpleOnePage2Template } from '..' 6 | import styles from './index.module.scss' 7 | 8 | const backgrounds = [ 9 | { 10 | label: '纯白', 11 | value: '#ffffff', 12 | }, 13 | { 14 | label: '光晕', 15 | value: `linear-gradient( 16 | 140deg, 17 | #fff1f032 0%, 18 | #fff2e832 6%, 19 | #fff7e632 13%, 20 | #fffbe632 22%, 21 | #fcffe632 37%, 22 | #f6ffed32 56%, 23 | #e6fffb32 70%, 24 | #e6f7ff32 79%, 25 | #f0f5ff32 86%, 26 | #f9f0ff32 91%, 27 | #fff0f632 96%, 28 | #fff0f632 100% 29 | )`, 30 | }, 31 | { 32 | label: '浅蓝', 33 | value: '#f0f5ff32', 34 | }, 35 | { 36 | label: '浅红', 37 | value: '#fff0f632', 38 | }, 39 | ] 40 | 41 | const ConfigForm = () => { 42 | const { config } = useTemplate() 43 | 44 | return useObserver(() => ( 45 |
46 |

主色调

47 | (config.primaryColor = e.target.value)} /> 48 | (config.primaryColor = color.value)} /> 49 | 50 |

背景

51 | (config.background = e.target.value)} 55 | > 56 | 57 |
58 | {backgrounds.map((it, i) => { 59 | return ( 60 |
(config.background = it.value)} 64 | style={{ background: it.value }} 65 | > 66 | {it.label} 67 |
68 | ) 69 | })} 70 |
71 |
72 | )) 73 | } 74 | 75 | export default ConfigForm 76 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/index.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { INormalSidevarData, NormalSidebarModel } from 'src/shared/formily/datas/normal-sidebar' 3 | import { FormilyTemplate } from '../../shared/formily/template' 4 | import poster from './assets/poster.jpeg' 5 | 6 | const View = React.lazy(() => import('./view')) 7 | const ConfigForm = React.lazy(() => import('./configForm')) 8 | 9 | const DEFAULT_CONFIG = { 10 | primaryColor: '#002FA7', 11 | background: '#ffffff', 12 | } 13 | 14 | export class SimpleOnePage2Template extends FormilyTemplate { 15 | key = 'simple-one-page2' 16 | name = '简洁设计感简历2' 17 | tags = ['单页', '侧栏', '设计感'] 18 | view = View 19 | dataForm = NormalSidebarModel.form 20 | configForm = ConfigForm 21 | poster = poster 22 | defaultData = NormalSidebarModel.defaultData 23 | defaultConfig = DEFAULT_CONFIG 24 | 25 | get baseInfo() { 26 | return { 27 | title: '基础信息', 28 | content: [ 29 | { 30 | label: '年龄', 31 | value: this.data.age, 32 | }, 33 | { 34 | label: '性别', 35 | value: this.data.gender, 36 | }, 37 | { 38 | label: '城市', 39 | value: this.data.city, 40 | }, 41 | { 42 | label: '电话', 43 | value: this.data.phoneNumber, 44 | }, 45 | { 46 | label: '邮箱', 47 | value: this.data.email, 48 | }, 49 | ] 50 | .filter((it) => Boolean(it.value)) 51 | .reduce((pre, cur) => { 52 | return (pre += `${cur.label}: ${cur.value}\n`) 53 | }, ''), 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/header/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | line-height: 1; 3 | font-weight: bold; 4 | color: #333; 5 | 6 | .hello { 7 | font-size: 94px; 8 | span { 9 | color: var(--color-primary); 10 | } 11 | } 12 | .name { 13 | font-size: 32px; 14 | padding-top: 16px; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/header/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { useTemplate } from 'src/templates' 3 | import { SimpleOnePage2Template } from '../..' 4 | import styles from './index.module.scss' 5 | 6 | export const Header = () => { 7 | const { data } = useTemplate() 8 | return useObserver(() => ( 9 |
10 |
11 | HELLO° 12 |
13 |
我是{data.name}
14 |
15 | )) 16 | } 17 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | --color-primary: #002fa7; 3 | --background: #fff; 4 | --padding-horizontal: 38px; 5 | --fontsize-h1: 18px; 6 | --fontsize-h2: 16px; 7 | 8 | width: var(--content-width); 9 | height: var(--content-height); 10 | 11 | overflow: hidden; 12 | background: var(--background); 13 | display: flex; 14 | font-size: 16px; 15 | line-height: 1.8; 16 | 17 | .left { 18 | flex-shrink: 0; 19 | width: 240px; 20 | height: 100%; 21 | display: flex; 22 | flex-direction: column; 23 | background: #000; 24 | padding: 0 24px; 25 | color: #fff; 26 | padding-top: 76px; 27 | > * + * { 28 | margin-top: 38px; 29 | } 30 | } 31 | 32 | .right { 33 | padding: 0 38px; 34 | padding-top: 48px; 35 | 36 | .title { 37 | margin-top: 38px; 38 | margin-left: -12px; 39 | --title-background: #efefef; 40 | } 41 | 42 | .item { 43 | padding-top: 16px; 44 | } 45 | 46 | .introduce { 47 | padding-top: 16px; 48 | } 49 | } 50 | } 51 | 52 | @page { 53 | margin: 0; 54 | padding: 0; 55 | } 56 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/index.tsx: -------------------------------------------------------------------------------- 1 | import { useObserver } from 'mobx-react-lite' 2 | import { Working } from 'src/components/working' 3 | import { useTemplate } from 'src/templates' 4 | import { SimpleOnePage2Template } from '..' 5 | import { Header } from './header' 6 | import styles from './index.module.scss' 7 | import { LeftItem } from './leftItem' 8 | import { Title } from './title' 9 | 10 | const View = () => { 11 | const store = useTemplate() 12 | const { config, data } = store 13 | return useObserver(() => ( 14 |
23 |
24 | {[store.baseInfo, ...data.sidebar].map((p, i) => { 25 | return 26 | })} 27 |
28 | 29 |
30 |
31 | 32 |
33 | 工作经历 34 |
35 | 36 | {data.workingHistory.map((it, i1) => { 37 | return ( 38 |
39 | 40 |
41 | ) 42 | })} 43 | 44 |
45 | 自我评价 46 |
47 | 48 |
{data.introduce}
49 |
50 |
51 | )) 52 | } 53 | 54 | export default View 55 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/leftItem/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | .title { 3 | margin-left: -12px; 4 | } 5 | .labels { 6 | color: rgba($color: #fff, $alpha: 0.7); 7 | .item { 8 | line-height: 1.8; 9 | padding-top: 8px; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/leftItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react' 2 | import { Title } from '../title' 3 | import styles from './index.module.scss' 4 | 5 | interface LeftItemProps { 6 | title: string 7 | labels: string[] 8 | } 9 | 10 | export const LeftItem: FC = ({ title, labels }) => { 11 | return ( 12 |
13 |
14 | {title} 15 |
16 |
17 | {labels.map((it, i) => { 18 | return ( 19 |
20 | {it} 21 |
22 | ) 23 | })} 24 |
25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/title/index.module.scss: -------------------------------------------------------------------------------- 1 | .index { 2 | line-height: 1; 3 | padding: 8px 12px; 4 | font-weight: bold; 5 | background: var(--title-background, rgba($color: #fff, $alpha: 0.18)); 6 | font-size: var(--fontsize-h1); 7 | } 8 | -------------------------------------------------------------------------------- /src/templates/simple-one-page-2/view/title/index.tsx: -------------------------------------------------------------------------------- 1 | import { FC, ReactNode } from 'react' 2 | import styles from './index.module.scss' 3 | 4 | interface TitleProps { 5 | children: ReactNode 6 | } 7 | 8 | export const Title: FC = ({ children }) => { 9 | return
{children}
10 | } 11 | -------------------------------------------------------------------------------- /src/templates/template.tsx: -------------------------------------------------------------------------------- 1 | import { makeObservable, observable, toJS } from 'mobx' 2 | import React, { ElementType, FC, ReactNode } from 'react' 3 | 4 | const TemplateContext = React.createContext(undefined as any) 5 | 6 | export function useTemplate(): T { 7 | return React.useContext(TemplateContext) as T 8 | } 9 | 10 | export const TemplateProvider: FC<{ value: BaseTemplate; children: ReactNode }> = ({ value, children }) => { 11 | return {children} 12 | } 13 | 14 | export abstract class BaseTemplate { 15 | constructor() { 16 | makeObservable(this, { 17 | data: observable, 18 | config: observable, 19 | }) 20 | } 21 | 22 | abstract key: string 23 | abstract name: string 24 | abstract tags?: string[] 25 | abstract poster: string 26 | abstract view: React.ElementType 27 | abstract configForm: React.ElementType 28 | abstract dataForm: React.ElementType 29 | abstract defaultData: D 30 | abstract defaultConfig: C 31 | 32 | exportConfig() { 33 | return toJS(this.config) 34 | } 35 | 36 | exportData() { 37 | return toJS(this.data) 38 | } 39 | 40 | importData(data?: D) { 41 | if (data) { 42 | this.data = data 43 | } else { 44 | this.data = toJS(this.defaultData) 45 | } 46 | } 47 | 48 | importConfig(config?: C) { 49 | if (config) { 50 | this.config = config 51 | } else { 52 | this.config = toJS(this.defaultConfig) 53 | } 54 | } 55 | 56 | abstract provider: ElementType<{ children: ReactNode }> 57 | 58 | config: C = undefined as any 59 | 60 | data: D = undefined as any 61 | } 62 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "experimentalDecorators": true, 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "baseUrl": ".", 24 | "paths": { 25 | "src/*": ["./src/*"] 26 | } 27 | }, 28 | "include": [ 29 | "src", 30 | "typing/global.d.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /typing/global.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.png' 4 | declare module '*.gif' 5 | declare module '*.jpg' 6 | declare module '*.jfif' 7 | declare module '*.jpeg' 8 | declare module '*.svg' 9 | declare module '*.css' 10 | declare module '*.less' 11 | declare module '*.scss' 12 | declare module '*.sass' 13 | declare module '*.styl' 14 | 15 | declare module '@emoji-mart/react' 16 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react' 2 | import path from 'path' 3 | import { defineConfig } from 'vite' 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: [ 9 | { 10 | find: 'src/', 11 | replacement: path.resolve(__dirname, 'src') + '/', 12 | }, 13 | { 14 | find: /^~/, 15 | replacement: '', 16 | }, 17 | ], 18 | }, 19 | css: { 20 | preprocessorOptions: { 21 | less: { 22 | javascriptEnabled: true, 23 | modifyVars: { 24 | // ant design 4.17 版本会有 less 报错,添加这个处理,之后版本看是否需要移除该项 25 | // 相关链接:https://githubmemory.com/repo/ant-design/ant-design-pro/issues/9082 26 | 'root-entry-name': 'default', 27 | }, 28 | }, 29 | }, 30 | }, 31 | }) 32 | --------------------------------------------------------------------------------