├── .deploy ├── decompression.sh └── main.sh ├── .editorconfig ├── .eslintrc.json ├── .github └── workflows │ ├── build.yml │ └── deploy.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── angular.json ├── browserslist ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package.json ├── presses ├── article-list.jpg ├── comment.jpg ├── dashboard-dark.png ├── dashboard-light.png ├── markdown.jpg ├── setting.jpg ├── submit-article.jpg ├── tag-list.jpg └── thumbnail.jpg ├── product ├── .gitkeep └── www.tar.gz ├── proxy.dev.conf.json ├── proxy.prod.conf.json ├── src ├── app │ ├── app.component.ts │ ├── app.menu.ts │ ├── app.module.ts │ ├── app.routing.ts │ ├── app.service.ts │ ├── components │ │ ├── index.ts │ │ ├── saBackTop │ │ │ ├── index.ts │ │ │ ├── saBackTop.component.scss │ │ │ └── saBackTop.component.ts │ │ ├── saCard │ │ │ ├── index.ts │ │ │ ├── saCard.component.html │ │ │ ├── saCard.component.scss │ │ │ └── saCard.component.ts │ │ ├── saCheckbox │ │ │ ├── index.ts │ │ │ ├── saCheckbox.component.html │ │ │ ├── saCheckbox.component.scss │ │ │ └── saCheckbox.component.ts │ │ ├── saContentHeader │ │ │ ├── index.ts │ │ │ ├── saContentHeader.component.html │ │ │ ├── saContentHeader.component.scss │ │ │ └── saContentHeader.component.ts │ │ ├── saLoadingSpider │ │ │ ├── index.ts │ │ │ ├── saLoadingSpider.component.scss │ │ │ └── saLoadingSpider.component.ts │ │ ├── saMarkdownEditor │ │ │ ├── index.ts │ │ │ ├── libs │ │ │ │ └── store.js │ │ │ ├── markdownEditor.component.html │ │ │ ├── markdownEditor.component.scss │ │ │ └── markdownEditor.component.ts │ │ ├── saMenu │ │ │ ├── components │ │ │ │ └── saMenuItem │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── saMenuItem.component.html │ │ │ │ │ ├── saMenuItem.component.scss │ │ │ │ │ └── saMenuItem.component.ts │ │ │ ├── index.ts │ │ │ ├── saMenu.component.html │ │ │ ├── saMenu.component.scss │ │ │ ├── saMenu.component.ts │ │ │ └── saMenu.service.ts │ │ ├── saPageHeader │ │ │ ├── index.ts │ │ │ ├── saPageHeader.component.html │ │ │ ├── saPageHeader.component.scss │ │ │ └── saPageHeader.component.ts │ │ ├── saPictureUploader │ │ │ ├── index.ts │ │ │ ├── saPictureUploader.component.html │ │ │ ├── saPictureUploader.component.scss │ │ │ └── saPictureUploader.component.ts │ │ └── saSidebar │ │ │ ├── index.ts │ │ │ ├── saSidebar.component.html │ │ │ ├── saSidebar.component.scss │ │ │ └── saSidebar.component.ts │ ├── constants │ │ ├── api.ts │ │ ├── auth.ts │ │ ├── http.ts │ │ ├── keycode.ts │ │ ├── state.ts │ │ └── url.ts │ ├── directives │ │ ├── index.ts │ │ └── saScrollPosition │ │ │ ├── index.ts │ │ │ └── saScrollPosition.directive.ts │ ├── discriminators │ │ └── url.ts │ ├── global.state.ts │ ├── pages │ │ ├── announcement │ │ │ ├── announcement.component.html │ │ │ ├── announcement.component.scss │ │ │ ├── announcement.component.ts │ │ │ ├── announcement.module.ts │ │ │ ├── announcement.routing.ts │ │ │ └── index.ts │ │ ├── article │ │ │ ├── article.component.ts │ │ │ ├── article.module.ts │ │ │ ├── article.routing.ts │ │ │ ├── article.utils.ts │ │ │ ├── components │ │ │ │ ├── category │ │ │ │ │ ├── category.component.html │ │ │ │ │ ├── category.component.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── add │ │ │ │ │ │ │ ├── add.component.html │ │ │ │ │ │ │ ├── add.component.scss │ │ │ │ │ │ │ ├── add.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── list │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── list.component.html │ │ │ │ │ │ │ ├── list.component.scss │ │ │ │ │ │ │ └── list.component.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── edit │ │ │ │ │ ├── components │ │ │ │ │ │ ├── category │ │ │ │ │ │ │ ├── category.component.html │ │ │ │ │ │ │ ├── category.component.scss │ │ │ │ │ │ │ ├── category.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── extend │ │ │ │ │ │ │ ├── extend.component.html │ │ │ │ │ │ │ ├── extend.component.scss │ │ │ │ │ │ │ ├── extend.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── main │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── main.component.html │ │ │ │ │ │ │ ├── main.component.scss │ │ │ │ │ │ │ └── main.component.ts │ │ │ │ │ │ └── submit │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── submit.component.html │ │ │ │ │ │ │ └── submit.component.ts │ │ │ │ │ ├── edit.component.html │ │ │ │ │ ├── edit.component.scss │ │ │ │ │ ├── edit.component.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── list │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── list.component.html │ │ │ │ │ ├── list.component.scss │ │ │ │ │ └── list.component.ts │ │ │ │ └── tag │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── tag.component.html │ │ │ │ │ ├── tag.component.scss │ │ │ │ │ └── tag.component.ts │ │ │ └── index.ts │ │ ├── auth │ │ │ ├── auth.component.html │ │ │ ├── auth.component.scss │ │ │ ├── auth.component.ts │ │ │ ├── auth.module.ts │ │ │ ├── auth.routing.ts │ │ │ └── index.ts │ │ ├── comment │ │ │ ├── comment.component.ts │ │ │ ├── comment.constants.ts │ │ │ ├── comment.module.ts │ │ │ ├── comment.routing.ts │ │ │ ├── components │ │ │ │ ├── detail │ │ │ │ │ ├── detail.component.html │ │ │ │ │ ├── detail.component.scss │ │ │ │ │ ├── detail.component.ts │ │ │ │ │ └── index.ts │ │ │ │ └── list │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── list.component.html │ │ │ │ │ ├── list.component.scss │ │ │ │ │ └── list.component.ts │ │ │ └── index.ts │ │ ├── dashboard │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.scss │ │ │ ├── dashboard.component.ts │ │ │ ├── dashboard.module.ts │ │ │ ├── dashboard.routing.ts │ │ │ ├── ga.embed.lib.loader.js │ │ │ └── index.ts │ │ ├── example │ │ │ ├── components │ │ │ │ ├── buttons │ │ │ │ │ ├── buttons.component.html │ │ │ │ │ ├── buttons.component.scss │ │ │ │ │ ├── buttons.component.ts │ │ │ │ │ ├── components │ │ │ │ │ │ ├── disabledButtons │ │ │ │ │ │ │ ├── disabledButtons.component.html │ │ │ │ │ │ │ ├── disabledButtons.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── dropdownButtons │ │ │ │ │ │ │ ├── dropdownButtons.component.html │ │ │ │ │ │ │ ├── dropdownButtons.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── flatButtons │ │ │ │ │ │ │ ├── flatButtons.component.html │ │ │ │ │ │ │ ├── flatButtons.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── groupButtons │ │ │ │ │ │ │ ├── groupButtons.component.html │ │ │ │ │ │ │ ├── groupButtons.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── iconButtons │ │ │ │ │ │ │ ├── iconButtons.component.html │ │ │ │ │ │ │ ├── iconButtons.component.scss │ │ │ │ │ │ │ ├── iconButtons.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── largeButtons │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── largeButtons.component.html │ │ │ │ │ │ │ └── largeButtons.component.ts │ │ │ │ │ │ ├── raisedButtons │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── raisedButtons.component.html │ │ │ │ │ │ │ └── raisedButtons.component.ts │ │ │ │ │ │ └── sizedButtons │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── sizedButtons.component.html │ │ │ │ │ │ │ └── sizedButtons.component.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── forms │ │ │ │ │ ├── inputs │ │ │ │ │ │ ├── components │ │ │ │ │ │ │ ├── checkboxInputs │ │ │ │ │ │ │ │ ├── checkboxInputs.component.html │ │ │ │ │ │ │ │ ├── checkboxInputs.component.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── groupInputs │ │ │ │ │ │ │ │ ├── groupInputs.component.html │ │ │ │ │ │ │ │ ├── groupInputs.component.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── selectInputs │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── selectInputs.component.html │ │ │ │ │ │ │ │ ├── selectInputs.component.scss │ │ │ │ │ │ │ │ └── selectInputs.component.ts │ │ │ │ │ │ │ ├── standardInputs │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── standardInputs.component.html │ │ │ │ │ │ │ │ └── standardInputs.component.ts │ │ │ │ │ │ │ └── validationInputs │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── validationInputs.component.html │ │ │ │ │ │ │ │ └── validationInputs.component.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── inputs.component.html │ │ │ │ │ │ └── inputs.component.ts │ │ │ │ │ └── layouts │ │ │ │ │ │ ├── components │ │ │ │ │ │ ├── basicForm │ │ │ │ │ │ │ ├── basicForm.component.html │ │ │ │ │ │ │ ├── basicForm.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── blockForm │ │ │ │ │ │ │ ├── blockForm.component.html │ │ │ │ │ │ │ ├── blockForm.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── horizontalForm │ │ │ │ │ │ │ ├── horizontalForm.component.html │ │ │ │ │ │ │ ├── horizontalForm.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── inlineForm │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── inlineForm.component.html │ │ │ │ │ │ │ ├── inlineForm.component.scss │ │ │ │ │ │ │ └── inlineForm.component.ts │ │ │ │ │ │ └── withoutLabelsForm │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── withoutLabelsForm.component.html │ │ │ │ │ │ │ └── withoutLabelsForm.component.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── layouts.component.html │ │ │ │ │ │ └── layouts.component.ts │ │ │ │ ├── grid │ │ │ │ │ ├── grid.component.html │ │ │ │ │ ├── grid.component.scss │ │ │ │ │ ├── grid.component.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── icons │ │ │ │ │ ├── icons.component.html │ │ │ │ │ ├── icons.component.scss │ │ │ │ │ ├── icons.component.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── modals │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── modals.component.html │ │ │ │ │ ├── modals.component.scss │ │ │ │ │ └── modals.component.ts │ │ │ │ ├── other │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── other.component.html │ │ │ │ │ ├── other.component.scss │ │ │ │ │ └── other.component.ts │ │ │ │ ├── table │ │ │ │ │ ├── components │ │ │ │ │ │ ├── borderedTable │ │ │ │ │ │ │ ├── borderedTable.component.html │ │ │ │ │ │ │ ├── borderedTable.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── condensedTable │ │ │ │ │ │ │ ├── condensedTable.component.html │ │ │ │ │ │ │ ├── condensedTable.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── contextualTable │ │ │ │ │ │ │ ├── contextualTable.component.html │ │ │ │ │ │ │ ├── contextualTable.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── hoverTable │ │ │ │ │ │ │ ├── hoverTable.component.html │ │ │ │ │ │ │ ├── hoverTable.component.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── responsiveTable │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── responsiveTable.component.html │ │ │ │ │ │ │ └── responsiveTable.component.ts │ │ │ │ │ │ └── stripedTable │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── stripedTable.component.html │ │ │ │ │ │ │ └── stripedTable.component.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── table.component.html │ │ │ │ │ ├── table.component.scss │ │ │ │ │ ├── table.component.ts │ │ │ │ │ └── table.service.ts │ │ │ │ └── typography │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── typography.component.html │ │ │ │ │ └── typography.component.ts │ │ │ ├── example.component.ts │ │ │ ├── example.module.ts │ │ │ ├── example.routing.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── options │ │ │ ├── index.ts │ │ │ ├── options.component.html │ │ │ ├── options.component.scss │ │ │ ├── options.component.ts │ │ │ ├── options.module.ts │ │ │ └── options.routing.ts │ │ ├── pages.component.ts │ │ ├── pages.interface.ts │ │ ├── pages.module.ts │ │ ├── pages.routing.ts │ │ └── pages.utils.ts │ ├── pipes │ │ ├── appPicture │ │ │ ├── appPicture.pipe.ts │ │ │ └── index.ts │ │ ├── dateHandle │ │ │ ├── dateHandle.pipe.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── truncate │ │ │ ├── index.ts │ │ │ └── truncate.pipe.ts │ ├── sa-base.module.ts │ ├── services │ │ ├── index.ts │ │ ├── saBootingSpinner │ │ │ ├── index.ts │ │ │ └── saBootingSpinner.service.ts │ │ ├── saHttpLoading │ │ │ ├── index.ts │ │ │ └── saHttpLoading.service.ts │ │ ├── saHttpRequester │ │ │ ├── index.ts │ │ │ └── saHttpRequester.service.ts │ │ ├── saImageLoader │ │ │ ├── index.ts │ │ │ └── saImageLoader.service.ts │ │ └── saToken │ │ │ ├── index.ts │ │ │ ├── saRenewal.service.ts │ │ │ └── saToken.service.ts │ ├── styles │ │ ├── app.scss │ │ ├── app │ │ │ ├── _buttons.scss │ │ │ ├── _common.scss │ │ │ ├── _form.scss │ │ │ ├── _icons.scss │ │ │ ├── _layout.scss │ │ │ ├── _model.scss │ │ │ ├── _notifications.scss │ │ │ ├── _preloader.scss │ │ │ ├── _table.scss │ │ │ └── _typography.scss │ │ ├── bootstrap │ │ │ ├── _button.scss │ │ │ ├── _card.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _pagination.scss │ │ │ └── _tabs.scss │ │ ├── init.scss │ │ ├── mixins.scss │ │ └── variables.scss │ ├── transformers │ │ ├── gravatar.ts │ │ ├── link.ts │ │ └── ua.ts │ └── validators │ │ ├── email.ts │ │ ├── equalPasswords.ts │ │ └── index.ts ├── assets │ ├── fonts │ │ ├── DIN-Regular.otf │ │ ├── DIN-Regular.ttf │ │ ├── socicon.eot │ │ ├── socicon.svg │ │ ├── socicon.ttf │ │ ├── socicon.woff │ │ └── socicon.woff2 │ └── images │ │ ├── browsers │ │ ├── chrome.svg │ │ ├── firefox.svg │ │ ├── ie.svg │ │ ├── opera.svg │ │ └── safari.svg │ │ ├── icons │ │ ├── arrow-green-up.svg │ │ ├── arrow-red-down.svg │ │ └── check-icon.png │ │ ├── profile │ │ ├── article-thumb.jpg │ │ ├── logo-smooth.png │ │ ├── logo.png │ │ └── no-photo.png │ │ └── typography │ │ ├── banner.png │ │ ├── typo01.png │ │ ├── typo03.png │ │ ├── typo04.png │ │ ├── typo05.png │ │ ├── typo06.png │ │ └── typo07.png ├── config.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── yarn.lock /.deploy/decompression.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_PATH=$(dirname $0) 4 | cd $WEB_PATH 5 | cd .. 6 | 7 | cd ./product 8 | echo "[deploy] Delete old files..." 9 | rm -r ./www 10 | echo "[deploy] Decompression tar..." 11 | mkdir www 12 | tar -xvf ./www.tar.gz -C ./www 13 | echo "[deploy] Finished." 14 | -------------------------------------------------------------------------------- /.deploy/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | WEB_PATH=$(dirname $0) 4 | cd $WEB_PATH 5 | cd .. 6 | 7 | echo "[deploy] pulling source code..." 8 | git fetch --all && git reset --hard origin/master && git pull 9 | git checkout master 10 | 11 | # decompression 12 | sh ${WEB_PATH}/decompression.sh 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: pull_request 3 | 4 | jobs: 5 | build: 6 | name: Build test 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [14.x] 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@master 14 | 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | 20 | - name: Install yarn 21 | run: npm install yarn -g 22 | 23 | - name: Install Dependencies 24 | run: yarn install --frozen-lockfile 25 | 26 | - name: Lint 27 | run: yarn lint 28 | 29 | - name: Build 30 | run: yarn build 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | on: 3 | pull_request: 4 | types: [closed] 5 | branches: 6 | - master 7 | 8 | jobs: 9 | deploy: 10 | if: github.event.pull_request.merged == true 11 | name: Deploy to server 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Exec deploy script with SSH 15 | uses: appleboy/ssh-action@master 16 | with: 17 | command_timeout: 8m 18 | host: ${{ secrets.HOST }} 19 | username: ${{ secrets.USER }} 20 | password: ${{ secrets.PASSWORD }} 21 | script: sh ${{ secrets.WWW_PATH }}/angular-admin/.deploy/main.sh 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | dump.rdb 4 | 5 | # compiled output 6 | /dist 7 | /tmp 8 | /out-tsc 9 | # Only exists if Bazel was run 10 | /bazel-out 11 | 12 | # prodect files 13 | /product/* 14 | !/product/www.tar.gz 15 | !/product/.gitkeep 16 | 17 | # dependencies 18 | /node_modules 19 | 20 | # profiling files 21 | chrome-profiler-events.json 22 | speed-measure-plugin.json 23 | 24 | # IDEs and editors 25 | /.idea 26 | .project 27 | .classpath 28 | .c9/ 29 | *.launch 30 | .settings/ 31 | *.sublime-workspace 32 | 33 | # IDE - VSCode 34 | .vscode/* 35 | !.vscode/settings.json 36 | !.vscode/tasks.json 37 | !.vscode/launch.json 38 | !.vscode/extensions.json 39 | .history/* 40 | 41 | # misc 42 | /.sass-cache 43 | /connect.lock 44 | /coverage 45 | /libpeerconnection.log 46 | npm-debug.log 47 | yarn-error.log 48 | testem.log 49 | /typings 50 | 51 | # System Files 52 | .DS_Store 53 | Thumbs.db 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 surmon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # For the full list of supported browsers by the Angular framework, please see: 6 | # https://angular.io/guide/browser-support 7 | 8 | # You can see what browsers were selected by your queries by running: 9 | # npx browserslist 10 | 11 | last 1 Chrome version 12 | last 1 Firefox version 13 | last 2 Edge major versions 14 | last 2 Safari major versions 15 | last 2 iOS major versions 16 | Firefox ESR 17 | not IE 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('angular-admin app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/angular-admin'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /presses/article-list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/article-list.jpg -------------------------------------------------------------------------------- /presses/comment.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/comment.jpg -------------------------------------------------------------------------------- /presses/dashboard-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/dashboard-dark.png -------------------------------------------------------------------------------- /presses/dashboard-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/dashboard-light.png -------------------------------------------------------------------------------- /presses/markdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/markdown.jpg -------------------------------------------------------------------------------- /presses/setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/setting.jpg -------------------------------------------------------------------------------- /presses/submit-article.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/submit-article.jpg -------------------------------------------------------------------------------- /presses/tag-list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/tag-list.jpg -------------------------------------------------------------------------------- /presses/thumbnail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/presses/thumbnail.jpg -------------------------------------------------------------------------------- /product/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/product/.gitkeep -------------------------------------------------------------------------------- /product/www.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/product/www.tar.gz -------------------------------------------------------------------------------- /proxy.dev.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "http://localhost:8000", 4 | "secure": false, 5 | "logLevel": "debug", 6 | "changeOrigin": true, 7 | "pathRewrite": { 8 | "^/api": "" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /proxy.prod.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "https://api.surmon.me", 4 | "secure": false, 5 | "logLevel": "debug", 6 | "changeOrigin": true, 7 | "headers": { 8 | "referer": "https://admin.surmon.me/", 9 | "origin": "https://admin.surmon.me" 10 | }, 11 | "pathRewrite": { 12 | "^/api": "" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App module 3 | * @desc app.module 4 | * @author Surmon 5 | */ 6 | 7 | import { RouterModule } from '@angular/router'; 8 | import { HttpClientModule } from '@angular/common/http'; 9 | import { BrowserModule } from '@angular/platform-browser'; 10 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 11 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 12 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 13 | import { SimpleNotificationsModule } from 'angular2-notifications'; 14 | import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'; 15 | import { PagesModule } from '@app/pages'; 16 | import { SaBaseModule } from '@app/sa-base.module'; 17 | import { AppComponent } from '@app/app.component'; 18 | import { RoutingModule } from '@app/app.routing'; 19 | import { GlobalState } from '@app/global.state'; 20 | import { AppState, TInternalState } from '@app/app.service'; 21 | import { registerLocaleData } from '@angular/common'; 22 | import zh from '@angular/common/locales/zh'; 23 | 24 | registerLocaleData(zh); 25 | 26 | export interface IAppStore { 27 | state: TInternalState; 28 | restoreInputValues: () => void; 29 | disposeOldHosts: () => void; 30 | } 31 | 32 | // App 入口模块 33 | @NgModule({ 34 | bootstrap: [AppComponent], 35 | declarations: [AppComponent], 36 | imports: [ 37 | BrowserModule, 38 | BrowserAnimationsModule, 39 | RouterModule, 40 | FormsModule, 41 | ReactiveFormsModule, 42 | PagesModule, 43 | RoutingModule, 44 | HttpClientModule, 45 | LoadingBarHttpClientModule, 46 | SimpleNotificationsModule.forRoot(), 47 | SaBaseModule.forRoot(), 48 | ], 49 | providers: [ 50 | AppState, 51 | GlobalState 52 | ], 53 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 54 | }) 55 | export class AppModule {} 56 | -------------------------------------------------------------------------------- /src/app/app.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App routes 3 | * @desc app.routes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | 9 | export const routes: Routes = [ 10 | { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, 11 | { path: '**', redirectTo: 'dashboard' } 12 | ]; 13 | 14 | export const RoutingModule = RouterModule.forRoot(routes, { useHash: false, relativeLinkResolution: 'legacy' }); 15 | -------------------------------------------------------------------------------- /src/app/app.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App service 3 | * @desc app.service 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | import { Subject } from 'rxjs'; 9 | 10 | export enum EAppStoreKeys { 11 | AdminInfo = 'adminInfo' 12 | } 13 | 14 | export type TInternalState = Record; 15 | 16 | @Injectable() 17 | export class AppState { 18 | 19 | private stateChange = new Subject(); 20 | private _state: TInternalState = { 21 | adminInfo: { 22 | name: '管理员', 23 | slogan: '博客管理后台', 24 | gravatar: 'assets/images/profile/logo-smooth.png' 25 | } 26 | }; 27 | 28 | get state() { 29 | return this.clone(this._state); 30 | } 31 | 32 | set state(value) { 33 | throw new Error('do not mutate the `.state` directly'); 34 | } 35 | 36 | get(prop?: any) { 37 | const state = this.state; 38 | return state.hasOwnProperty(prop) ? state[prop] : state; 39 | } 40 | 41 | set(prop: string, value: any) { 42 | this.stateChange.next(value); 43 | return this._state[prop] = value; 44 | } 45 | 46 | private clone(object: TInternalState) { 47 | return JSON.parse(JSON.stringify(object)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saPageHeader'; 2 | export * from './saSidebar'; 3 | export * from './saMenu/components/saMenuItem'; 4 | export * from './saMenu'; 5 | export * from './saContentHeader'; 6 | export * from './saCard'; 7 | export * from './saBackTop'; 8 | export * from './saPictureUploader'; 9 | export * from './saCheckbox'; 10 | export * from './saMarkdownEditor'; 11 | export * from './saLoadingSpider'; 12 | -------------------------------------------------------------------------------- /src/app/components/saBackTop/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saBackTop.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saBackTop/saBackTop.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | $size: 52px; 3 | 4 | .back-cover-top { 5 | position: fixed; 6 | display: none; 7 | z-index: $z-index-main-backtop; 8 | width: $size; 9 | height: $size; 10 | right: $size; 11 | bottom: $size !important; 12 | border-radius: 50%; 13 | text-decoration: none; 14 | text-align: center; 15 | background-color: rgba($black-color, .75); 16 | opacity: 0.5; 17 | justify-content: center; 18 | align-items: center; 19 | cursor: pointer; 20 | transition: opacity .1s; 21 | 22 | .icon { 23 | font-size: 1.5em; 24 | margin-bottom: 2px; 25 | } 26 | 27 | &:hover { 28 | opacity: 0.8; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/components/saBackTop/saBackTop.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 返回顶部组件 3 | * @desc app/component/back-top 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, AfterViewInit, HostListener, Input } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'sa-back-top', 11 | styleUrls: ['./saBackTop.component.scss'], 12 | template: ` 13 | 19 | 20 | 21 | ` 22 | }) 23 | export class SaBackTopComponent implements AfterViewInit { 24 | 25 | public isShow = false; 26 | 27 | @Input() position = 400; 28 | @Input() showSpeed = 500; 29 | @Input() moveSpeed = 1000; 30 | 31 | ngAfterViewInit() { 32 | setTimeout(() => { 33 | this.onWindowScroll(); 34 | }); 35 | } 36 | 37 | @HostListener('click') 38 | onClick(): boolean { 39 | window.scrollTo(0, 0); 40 | return false; 41 | } 42 | 43 | @HostListener('window:scroll') 44 | onWindowScroll(): void { 45 | this.isShow = window.scrollY > this.position; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/app/components/saCard/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saCard.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saCard/saCard.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ name }} 4 |
5 |
6 | 7 |
8 |
9 | -------------------------------------------------------------------------------- /src/app/components/saCard/saCard.component.scss: -------------------------------------------------------------------------------- 1 | .card-title { 2 | user-select: none; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/components/saCard/saCard.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 卡片组件 3 | * @desc app/component/card 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, ViewEncapsulation, Input } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'sa-card', 11 | templateUrl: './saCard.component.html', 12 | styleUrls: ['./saCard.component.scss'], 13 | encapsulation: ViewEncapsulation.None 14 | }) 15 | export class SaCardComponent { 16 | @Input() name: string; 17 | @Input() baCardClass: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/components/saCheckbox/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saCheckbox.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saCheckbox/saCheckbox.component.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | -------------------------------------------------------------------------------- /src/app/components/saCheckbox/saCheckbox.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .sa-checkbox { 4 | .checkbox-inline { 5 | display: inline-flex; 6 | align-items: center; 7 | margin: 0; 8 | cursor: pointer; 9 | 10 | &:hover { 11 | .icon { 12 | opacity: 1; 13 | } 14 | } 15 | 16 | &.disabled { 17 | cursor: no-drop; 18 | opacity: .3; 19 | } 20 | 21 | .checkbox { 22 | display: none; 23 | } 24 | 25 | .icon { 26 | opacity: .6; 27 | font-size: 18px; 28 | margin-right: 8px; 29 | 30 | &.checked { 31 | opacity: 1; 32 | } 33 | } 34 | 35 | .label { 36 | &.strong { 37 | font-weight: bold; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/components/saCheckbox/saCheckbox.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 复选框组件 3 | * @desc app/component/checkbox 4 | * @author Surmon 5 | */ 6 | 7 | import { ControlValueAccessor } from '@angular/forms'; 8 | import { Component, forwardRef, Input } from '@angular/core'; 9 | import { NG_VALUE_ACCESSOR } from '@angular/forms'; 10 | 11 | @Component({ 12 | selector: 'sa-checkbox[ngModel]', 13 | templateUrl: './saCheckbox.component.html', 14 | styleUrls: ['./saCheckbox.component.scss'], 15 | providers: [ 16 | { 17 | provide: NG_VALUE_ACCESSOR, 18 | useExisting: forwardRef(() => SaCheckboxComponent), 19 | multi: true 20 | } 21 | ] 22 | }) 23 | export class SaCheckboxComponent implements ControlValueAccessor { 24 | 25 | @Input() label: string; 26 | @Input() disabled: boolean; 27 | @Input() labelStrong: boolean; 28 | 29 | private innerValue: boolean; 30 | private onTouchedCallback: () => void = () => {}; 31 | private onChangeCallback: (_: any) => void = () => {}; 32 | 33 | constructor() {} 34 | 35 | get value(): any { 36 | return this.innerValue; 37 | } 38 | 39 | set value(value: any) { 40 | if (value !== this.innerValue) { 41 | this.innerValue = value; 42 | this.onChangeCallback(value); 43 | } 44 | } 45 | 46 | writeValue(value: any) { 47 | if (value !== this.innerValue) { 48 | this.innerValue = value; 49 | } 50 | } 51 | 52 | registerOnChange(fn: any) { 53 | this.onChangeCallback = fn; 54 | } 55 | registerOnTouched(fn: any) { 56 | this.onTouchedCallback = fn; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/components/saContentHeader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saContentHeader.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saContentHeader/saContentHeader.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ activePageTitle }}

3 | 7 |
8 | -------------------------------------------------------------------------------- /src/app/components/saContentHeader/saContentHeader.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .content-header { 4 | padding: 20px 5px 25px 5px; 5 | } 6 | 7 | h1.al-title { 8 | font-weight: $font-bold; 9 | color: $default-text; 10 | float: left; 11 | width: auto; 12 | margin: 0; 13 | padding: 0; 14 | font-size: 24px; 15 | text-transform: uppercase; 16 | opacity: 0.9; 17 | } 18 | 19 | .al-breadcrumb { 20 | background: none; 21 | color: $default-text; 22 | padding: 0; 23 | margin: 0; 24 | float: right; 25 | 26 | li { 27 | font-size: 18px; 28 | font-weight: $font-normal; 29 | 30 | a { 31 | color: $primary-light; 32 | } 33 | 34 | &.breadcrumb-item.active { 35 | color: $default-text; 36 | } 37 | } 38 | } 39 | 40 | .al-look { 41 | float: right; 42 | margin-right: 10px; 43 | padding-top: 10px; 44 | 45 | > a { 46 | font-size: 19px; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/components/saContentHeader/saContentHeader.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 内容区顶部标题 3 | * @desc app/component/content-header 4 | * @author Surmon 5 | */ 6 | 7 | import { Component } from '@angular/core'; 8 | import { GlobalState } from 'app/global.state'; 9 | 10 | @Component({ 11 | selector: 'sa-content-header', 12 | styleUrls: ['./saContentHeader.component.scss'], 13 | templateUrl: './saContentHeader.component.html', 14 | }) 15 | export class SaContentHeaderComponent { 16 | 17 | public activePageTitle = ''; 18 | 19 | constructor(private state: GlobalState) { 20 | this.state.subscribe('menu.activeLink', activeLink => { 21 | if (activeLink) { 22 | this.activePageTitle = activeLink.title || '黑页'; 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/saLoadingSpider/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saLoadingSpider.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saLoadingSpider/saLoadingSpider.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | $height: 52px; 3 | 4 | .sa-loading-spider { 5 | position: absolute; 6 | display: flex; 7 | width: 100%; 8 | height: 100%; 9 | justify-content: center; 10 | align-items: center; 11 | transition: opacity .25s; 12 | 13 | &.none { 14 | z-index: -1; 15 | opacity: 0; 16 | } 17 | 18 | &.flex { 19 | z-index: 2; 20 | opacity: 1; 21 | } 22 | 23 | > .loader-mask { 24 | width: 100%; 25 | height: 100%; 26 | position: absolute; 27 | background: #b5b5b52b; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/app/components/saLoadingSpider/saLoadingSpider.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 局部记载组件 3 | * @desc app/component/loading-spider 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, ViewEncapsulation, Input } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'sa-loading-spider', 11 | encapsulation: ViewEncapsulation.None, 12 | styleUrls: ['./saLoadingSpider.component.scss'], 13 | template: ` 14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ` 25 | }) 26 | export class SaLoadingSpiderComponent { 27 | 28 | @Input() type = 400; 29 | @Input() show = false; 30 | 31 | get className() { 32 | return this.show ? 'flex' : 'none'; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/components/saMarkdownEditor/index.ts: -------------------------------------------------------------------------------- 1 | export * from './markdownEditor.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saMenu/components/saMenuItem/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saMenuItem.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saMenu/components/saMenuItem/saMenuItem.component.html: -------------------------------------------------------------------------------- 1 |
  • 12 | 18 | 19 | {{ menuItem.title }} 20 | 21 | 28 | 29 | {{ menuItem.title }} 30 | 31 | 38 | 39 | {{ menuItem.title }} 40 | 41 | 42 |
      47 | 54 | 55 |
    56 |
  • 57 | -------------------------------------------------------------------------------- /src/app/components/saMenu/components/saMenuItem/saMenuItem.component.scss: -------------------------------------------------------------------------------- 1 | // @import '~app/styles/init'; 2 | -------------------------------------------------------------------------------- /src/app/components/saMenu/components/saMenuItem/saMenuItem.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'sa-menu-item', 5 | encapsulation: ViewEncapsulation.None, 6 | styleUrls: ['./saMenuItem.component.scss'], 7 | templateUrl: './saMenuItem.component.html' 8 | }) 9 | export class SaMenuItemComponent { 10 | 11 | @Input() menuItem: any; 12 | @Input() child = false; 13 | 14 | @Output() itemHover = new EventEmitter(); 15 | @Output() toggleSubMenu = new EventEmitter(); 16 | 17 | public onHoverItem($event): void { 18 | this.itemHover.emit($event); 19 | } 20 | 21 | public onToggleSubMenu($event, item): boolean { 22 | $event.item = item; 23 | this.toggleSubMenu.emit($event); 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/components/saMenu/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saMenu.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saMenu/saMenu.component.html: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /src/app/components/saMenu/saMenu.component.scss: -------------------------------------------------------------------------------- 1 | // @import '~app/styles/init'; 2 | -------------------------------------------------------------------------------- /src/app/components/saPageHeader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saPageHeader.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saPageHeader/saPageHeader.component.html: -------------------------------------------------------------------------------- 1 | 41 | -------------------------------------------------------------------------------- /src/app/components/saPageHeader/saPageHeader.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 顶部条 3 | * @desc app/component/page-header 4 | * @author Surmon 5 | */ 6 | 7 | import { Router } from '@angular/router'; 8 | import { Component, ViewEncapsulation } from '@angular/core'; 9 | import { GlobalState } from 'app/global.state'; 10 | import { AppState } from 'app/app.service'; 11 | import { SaTokenService } from 'app/services'; 12 | import { APP_TITLE } from '@/config'; 13 | 14 | type TCollapsedState = boolean; 15 | 16 | @Component({ 17 | selector: 'sa-page-header', 18 | styleUrls: ['./saPageHeader.component.scss'], 19 | templateUrl: './saPageHeader.component.html', 20 | encapsulation: ViewEncapsulation.None 21 | }) 22 | export class SaPageHeaderComponent { 23 | 24 | public APP_TITLE = APP_TITLE; 25 | 26 | public isScrolled: TCollapsedState = false; 27 | public isMenuCollapsed: TCollapsedState = false; 28 | 29 | constructor( 30 | private router: Router, 31 | private state: GlobalState, 32 | readonly appState: AppState, 33 | readonly tokenService: SaTokenService 34 | ) { 35 | this.state.subscribe('menu.isCollapsed', isCollapsed => { 36 | this.isMenuCollapsed = isCollapsed; 37 | }); 38 | } 39 | 40 | public logout() { 41 | console.log('退出系统'); 42 | this.tokenService.removeToken(); 43 | this.router.navigate(['/auth']); 44 | } 45 | 46 | public toggleMenu() { 47 | this.isMenuCollapsed = !this.isMenuCollapsed; 48 | this.state.notifyDataChanged('menu.isCollapsed', this.isMenuCollapsed); 49 | return false; 50 | } 51 | 52 | public scrolledChanged(isScrolled: TCollapsedState) { 53 | this.isScrolled = isScrolled; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/components/saPictureUploader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saPictureUploader.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saPictureUploader/saPictureUploader.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 |
    7 |
    8 |
    9 |
    10 |
    11 |

    {{ uploadProgress }}%

    12 |
    13 |
    14 | 20 | 点击上传图片 21 | 22 | 23 |
    24 |
    25 | 32 |
    33 |
    34 | -------------------------------------------------------------------------------- /src/app/components/saSidebar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saSidebar.component'; 2 | -------------------------------------------------------------------------------- /src/app/components/saSidebar/saSidebar.component.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /src/app/components/saSidebar/saSidebar.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 侧边栏菜单组件 3 | * @desc app/component/sidebar 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, ElementRef, HostListener, ViewEncapsulation, OnInit, AfterViewInit } from '@angular/core'; 8 | import { GlobalState } from 'app/global.state'; 9 | import { MENU } from 'app/app.menu'; 10 | import { cloneDeep } from 'lodash'; 11 | 12 | @Component({ 13 | selector: 'sa-sidebar', 14 | encapsulation: ViewEncapsulation.None, 15 | styleUrls: ['./saSidebar.component.scss'], 16 | templateUrl: './saSidebar.component.html' 17 | }) 18 | export class SaSidebarComponent implements OnInit, AfterViewInit { 19 | 20 | public routes = cloneDeep(MENU); 21 | public menuHeight: number; 22 | public isMenuCollapsed = false; 23 | public isMenuShouldCollapsed = false; 24 | 25 | constructor(private elementRef: ElementRef, private state: GlobalState) { 26 | this.state.subscribe('menu.isCollapsed', (isCollapsed: boolean) => { 27 | this.isMenuCollapsed = isCollapsed; 28 | }); 29 | } 30 | 31 | public ngOnInit(): void { 32 | if (this.shouldMenuCollapse()) { 33 | this.menuCollapse(); 34 | } 35 | } 36 | 37 | public ngAfterViewInit(): void { 38 | setTimeout(() => this.updateSidebarHeight()); 39 | } 40 | 41 | @HostListener('window:resize') 42 | public onWindowResize(): void { 43 | const isMenuShouldCollapsed = this.shouldMenuCollapse(); 44 | if (this.isMenuShouldCollapsed !== isMenuShouldCollapsed) { 45 | this.menuCollapseStateChange(isMenuShouldCollapsed); 46 | } 47 | this.isMenuShouldCollapsed = isMenuShouldCollapsed; 48 | this.updateSidebarHeight(); 49 | } 50 | 51 | public menuExpand(): void { 52 | this.menuCollapseStateChange(false); 53 | } 54 | 55 | public menuCollapse(): void { 56 | this.menuCollapseStateChange(true); 57 | } 58 | 59 | public menuCollapseStateChange(isCollapsed: boolean): void { 60 | this.isMenuCollapsed = isCollapsed; 61 | this.state.notifyDataChanged('menu.isCollapsed', this.isMenuCollapsed); 62 | } 63 | 64 | public updateSidebarHeight(): void { 65 | this.menuHeight = this.elementRef.nativeElement.childNodes[0].clientHeight - 215; 66 | } 67 | 68 | private shouldMenuCollapse(): boolean { 69 | return window.innerWidth <= 1200; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/constants/api.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App constants/api 3 | * @author Surmon 4 | */ 5 | 6 | export const AUTH = '/auth'; 7 | export const LOGIN = '/auth/login'; 8 | export const CHECK_TOKEN = '/auth/check'; 9 | export const RENEWAL_TOKEN = '/auth/renewal'; 10 | export const ADMIN_INFO = '/auth/admin'; 11 | export const OPTION = '/option'; 12 | export const ANNOUNCEMENT = '/announcement'; 13 | export const CATEGORY = '/category'; 14 | export const ARTICLE = '/article'; 15 | export const COMMENT = '/comment'; 16 | export const TAG = '/tag'; 17 | export const UP_TOKEN = '/expansion/uptoken'; 18 | export const STATISTIC = '/expansion/statistic'; 19 | export const GOOGLE_TOKEN = '/expansion/google-token'; 20 | export const DATA_BASE_BACKUP = '/expansion/database-backup'; 21 | export const SYNDICATION = '/syndication'; 22 | -------------------------------------------------------------------------------- /src/app/constants/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App constants/auth 3 | * @description app/constants/auto 4 | * @author Surmon 5 | */ 6 | 7 | export const TOKEN = 'id_token'; 8 | export const TOKEN_BIRTH_TIME = 'token_birth_time'; 9 | export const TOKEN_EXPIRES_IN = 'token_expires_in'; 10 | export const TOKEN_HEADER = 'Authorization'; 11 | -------------------------------------------------------------------------------- /src/app/constants/http.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App constants/api 3 | * @description app/constants/api 4 | * @author Surmon 5 | */ 6 | 7 | export const HTTP_SUCCESS = 200; // 成功 8 | export const HTTP_CREATE_SUCCESS = 201; // 创建成功 9 | export const NO_PERMISSION = 403; // 无权限 10 | export const UNAUTHORIZED = 401; // 未授权 11 | export const SERVER_ERROR = 500; // 服务器挂了 12 | export const GATEWAY_TIMEOUT = 504; // 请求超时 13 | export const UNKNOWN_ERROR = 0; // 未知 14 | -------------------------------------------------------------------------------- /src/app/constants/keycode.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App constants/keycode 3 | * @desc app/constants/keycode 4 | * @author Surmon 5 | */ 6 | 7 | export const ESC = 27; // 退出 8 | export const F5 = 122; // F5 9 | export const S = 83; // S 10 | -------------------------------------------------------------------------------- /src/app/constants/state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 业务数据表常量接口 3 | * @description constants/state 4 | * @author Surmon 5 | */ 6 | 7 | // 发布状态 8 | export enum EPublishState { 9 | All = 'all', 10 | Draft = 0, // 草稿 11 | Published = 1, // 已发布 12 | Recycle = -1, // 回收站 13 | } 14 | 15 | // 公开状态 16 | export enum EPublicState { 17 | All = 'all', 18 | Password = 0, // 需要密码 19 | Public = 1, // 公开状态 20 | Secret = -1, // 私密 21 | } 22 | 23 | // 转载状态 24 | export enum EOriginState { 25 | All = 'all', 26 | Original = 0, // 原创 27 | Reprint = 1, // 转载 28 | Hybrid = 2, // 混合 29 | } 30 | 31 | // 排序状态 32 | export enum ESortType { 33 | Asc = 1, // 升序 34 | Desc = -1, // 降序 35 | Hot = 2, // 最热 36 | } 37 | -------------------------------------------------------------------------------- /src/app/constants/url.ts: -------------------------------------------------------------------------------- 1 | 2 | export const ASSETS_ROOT = '/assets/'; 3 | export const ASSETS_IMAGE = ASSETS_ROOT + 'images/'; 4 | export const ASSETS_FONT = ASSETS_ROOT + 'fonts/'; 5 | -------------------------------------------------------------------------------- /src/app/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saScrollPosition'; 2 | -------------------------------------------------------------------------------- /src/app/directives/saScrollPosition/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saScrollPosition.directive'; 2 | -------------------------------------------------------------------------------- /src/app/directives/saScrollPosition/saScrollPosition.directive.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 监听 Dom 滚动指令 3 | * @desc app/directives/scroll-position 4 | * @author Surmon 5 | */ 6 | 7 | import { Directive, Input, Output, EventEmitter, HostListener, OnInit } from '@angular/core'; 8 | 9 | @Directive({ 10 | selector: '[saScrollPosition]' 11 | }) 12 | export class SaScrollPositionDirective implements OnInit { 13 | 14 | @Input() public maxHeight: number; 15 | @Output() public scrollChange: EventEmitter = new EventEmitter(); 16 | 17 | private isScrolled: boolean; 18 | 19 | public ngOnInit(): void { 20 | this.onWindowScroll(); 21 | } 22 | 23 | @HostListener('window:scroll') 24 | onWindowScroll(): void { 25 | const isScrolled = window.scrollY > this.maxHeight; 26 | if (isScrolled !== this.isScrolled) { 27 | this.isScrolled = isScrolled; 28 | this.scrollChange.emit(isScrolled); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/discriminators/url.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App discriminators url 目标判定器 3 | * @author Surmon 4 | */ 5 | 6 | // 路径参数 7 | type TUrlPath = string; 8 | 9 | // 权限页面 10 | export const isAuthPage = (url: TUrlPath) => url === '/auth'; 11 | 12 | // 首页 13 | export const isIndexPage = (url: TUrlPath) => url === '/'; 14 | 15 | // 仪表盘 16 | export const isDashboardPage = (url: TUrlPath) => url === '/dashboard'; 17 | 18 | // 发布文章页面 19 | export const isPostArticlePage = (url: TUrlPath) => url === '/article/post'; 20 | 21 | // 公告页面 22 | export const isAnnouncementPage = (url: TUrlPath) => url === '/announcement'; 23 | 24 | export const isOptionsPage = (url: TUrlPath) => url === '/options'; 25 | -------------------------------------------------------------------------------- /src/app/global.state.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file App 全局状态 3 | * @desc global.state 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | import { Subject } from 'rxjs'; 9 | 10 | type TFunc = (...args: any) => any; 11 | 12 | @Injectable() 13 | export class GlobalState { 14 | 15 | private data = new Subject(); 16 | private dataStream$ = this.data.asObservable(); 17 | private subscriptions: Map> = new Map>(); 18 | 19 | constructor() { 20 | this.dataStream$.subscribe((data) => this.onEvent(data)); 21 | } 22 | 23 | notifyDataChanged(event, value) { 24 | const currentValue = this.data[event]; 25 | if (currentValue !== value) { 26 | this.data[event] = value; 27 | this.data.next({ 28 | event, 29 | data: this.data[event] 30 | }); 31 | } 32 | } 33 | 34 | subscribe(event: string, callback: TFunc) { 35 | const subscribers = this.subscriptions.get(event) || []; 36 | subscribers.push(callback); 37 | this.subscriptions.set(event, subscribers); 38 | } 39 | 40 | onEvent(data: any) { 41 | const subscribers = this.subscriptions.get(data.event) || []; 42 | subscribers.forEach((callback) => { 43 | callback.call(null, data.data); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/pages/announcement/announcement.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .announcement-form { 4 | .announcement-content { 5 | padding: 0; 6 | 7 | > .markdown-editor { 8 | > .editor-toolbar { 9 | > .menus { 10 | .quote, 11 | .code, 12 | .-h3, 13 | .image, 14 | .save { 15 | display: none; 16 | } 17 | } 18 | .fullscreen { 19 | display: none; 20 | } 21 | } 22 | } 23 | } 24 | 25 | .submit-btn { 26 | margin-right: $gap; 27 | } 28 | } 29 | 30 | .announcement-tools { 31 | margin-left: $gap-xs; 32 | } 33 | 34 | .announcement-search-form { 35 | > .input-group { 36 | > .form-control { 37 | width: 14em; 38 | } 39 | } 40 | } 41 | 42 | .announcement-list { 43 | position: relative; 44 | 45 | .announcement-err-msg { 46 | margin: 1rem 0; 47 | } 48 | 49 | tbody { 50 | tr { 51 | td { 52 | .content { 53 | word-break: break-all; 54 | line-height: 2; 55 | 56 | p { 57 | margin: 0; 58 | } 59 | 60 | img { 61 | max-width: 4rem; 62 | margin: 0 1rem; 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/pages/announcement/announcement.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 公告管理模块 3 | * @desc app/page/announcement/module 4 | * @author Surmon 5 | */ 6 | 7 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 10 | import { ModalModule } from 'ngx-bootstrap/modal'; 11 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 12 | import { PaginationModule } from 'ngx-bootstrap/pagination'; 13 | import { SaBaseModule } from '@app/sa-base.module'; 14 | 15 | import { RoutingModule } from './announcement.routing'; 16 | import { AnnouncementComponent } from './announcement.component'; 17 | 18 | @NgModule({ 19 | imports: [ 20 | CommonModule, 21 | ReactiveFormsModule, 22 | FormsModule, 23 | SaBaseModule, 24 | RoutingModule, 25 | PaginationModule.forRoot(), 26 | BsDropdownModule.forRoot(), 27 | ModalModule.forRoot(), 28 | ], 29 | declarations: [ 30 | AnnouncementComponent 31 | ], 32 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 33 | }) 34 | export class AnnouncementModule {} 35 | -------------------------------------------------------------------------------- /src/app/pages/announcement/announcement.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 公告管理路由 3 | * @desc app/page/announcement/routes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { AnnouncementComponent } from './announcement.component'; 9 | 10 | const routes: Routes = [ 11 | { path: '', component: AnnouncementComponent } 12 | ]; 13 | 14 | export const RoutingModule = RouterModule.forChild(routes); 15 | -------------------------------------------------------------------------------- /src/app/pages/announcement/index.ts: -------------------------------------------------------------------------------- 1 | export * from './announcement.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/article.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文章管理页面组件 3 | * @desc app/page/article/component 4 | * @author Surmon 5 | */ 6 | 7 | import { Component } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'page-article', 11 | template: `` 12 | }) 13 | export class ArticleComponent { 14 | constructor() {} 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/article/article.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文章管理页面模块 3 | * @desc app/page/article/module 4 | * @author Surmon 5 | */ 6 | 7 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 10 | import { ModalModule } from 'ngx-bootstrap/modal'; 11 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 12 | import { PaginationModule } from 'ngx-bootstrap/pagination'; 13 | import { SaBaseModule } from '@app/sa-base.module'; 14 | import { RoutingModule } from './article.routing'; 15 | 16 | import { ArticleComponent } from './article.component'; 17 | import { ArticleTagComponent } from './components/tag'; 18 | import { ArticleListComponent } from './components/list'; 19 | 20 | import { ArticleCategoryComponent } from './components/category'; 21 | import { ArticleCategoryAddComponent } from './components/category/components/add'; 22 | import { ArticleCategoryListComponent } from './components/category/components/list'; 23 | 24 | import { ArticleEditComponent } from './components/edit'; 25 | import { ArticleEditMainComponent } from './components/edit/components/main'; 26 | import { ArticleEditExtendComponent } from './components/edit/components/extend'; 27 | import { ArticleEditSubmitComponent } from './components/edit/components/submit'; 28 | import { ArticleEditCategoryComponent } from './components/edit/components/category'; 29 | 30 | @NgModule({ 31 | imports: [ 32 | CommonModule, 33 | FormsModule, 34 | ReactiveFormsModule, 35 | SaBaseModule, 36 | RoutingModule, 37 | PaginationModule.forRoot(), 38 | BsDropdownModule.forRoot(), 39 | ModalModule.forRoot(), 40 | ], 41 | declarations: [ 42 | ArticleComponent, 43 | ArticleListComponent, 44 | ArticleTagComponent, 45 | ArticleEditComponent, 46 | 47 | ArticleEditMainComponent, 48 | ArticleEditSubmitComponent, 49 | ArticleEditExtendComponent, 50 | ArticleEditCategoryComponent, 51 | 52 | ArticleCategoryComponent, 53 | ArticleCategoryAddComponent, 54 | ArticleCategoryListComponent 55 | ], 56 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 57 | }) 58 | export class ArticleModule {} 59 | -------------------------------------------------------------------------------- /src/app/pages/article/article.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文章管理页面路由 3 | * @desc app/page/article/routes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { ArticleComponent } from './article.component'; 9 | import { ArticleTagComponent } from './components/tag'; 10 | import { ArticleEditComponent } from './components/edit'; 11 | import { ArticleListComponent } from './components/list'; 12 | import { ArticleCategoryComponent } from './components/category'; 13 | 14 | const routes: Routes = [ 15 | { 16 | path: '', 17 | component: ArticleComponent, 18 | children: [ 19 | { 20 | path: '', 21 | redirectTo: 'category', 22 | pathMatch: 'full' 23 | }, 24 | { 25 | path: 'category', 26 | component: ArticleCategoryComponent 27 | }, 28 | { 29 | path: 'post', 30 | component: ArticleEditComponent 31 | }, 32 | { 33 | path: 'edit/:article_id', 34 | component: ArticleEditComponent 35 | }, 36 | { 37 | path: 'list', 38 | component: ArticleListComponent 39 | }, 40 | { 41 | path: 'tag', 42 | component: ArticleTagComponent 43 | } 44 | ] 45 | } 46 | ]; 47 | 48 | export const RoutingModule = RouterModule.forChild(routes); 49 | -------------------------------------------------------------------------------- /src/app/pages/article/components/category/category.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 11 | 12 | 13 |
    14 |
    15 | 16 | 23 | 24 | 25 |
    26 | 27 | 56 |
    57 | -------------------------------------------------------------------------------- /src/app/pages/article/components/category/components/add/add.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .category-edit-form { 4 | .category-description { 5 | line-height: 1.8em; 6 | } 7 | 8 | .submit-btn { 9 | margin-right: $gap; 10 | } 11 | } -------------------------------------------------------------------------------- /src/app/pages/article/components/category/components/add/index.ts: -------------------------------------------------------------------------------- 1 | export * from './add.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/category/components/list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './list.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/category/components/list/list.component.scss: -------------------------------------------------------------------------------- 1 | .category-list { 2 | position: relative; 3 | 4 | .category-err-msg { 5 | margin: 1rem 0; 6 | } 7 | 8 | tbody { 9 | tr { 10 | td { 11 | .category-description { 12 | overflow: hidden; 13 | text-overflow: ellipsis; 14 | display: -webkit-box; 15 | -webkit-line-clamp: 1; 16 | -webkit-box-orient: vertical; 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/pages/article/components/category/components/list/list.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 分类页面列表组件 3 | * @desc app/page/article/component/category/list 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, ViewEncapsulation, Input, Output, EventEmitter } from '@angular/core'; 8 | import { IResponsePaginationData, TSelectedAll, TSelectedIds } from '@app/pages/pages.interface'; 9 | import { handleBatchSelectChange, handleItemSelectChange } from '@app/pages/pages.utils'; 10 | import { ICategory } from '@app/pages/article/article.utils'; 11 | import { getCategoryPath } from '@/app/transformers/link'; 12 | 13 | @Component({ 14 | selector: 'box-category-list', 15 | encapsulation: ViewEncapsulation.Emulated, 16 | templateUrl: './list.component.html', 17 | styleUrls: ['./list.component.scss'] 18 | }) 19 | 20 | export class ArticleCategoryListComponent { 21 | 22 | @Input() isFetching: boolean; 23 | @Input() categories: IResponsePaginationData; 24 | @Output() delCategoryRequest = new EventEmitter(); 25 | @Output() delCategoriesRequest = new EventEmitter(); 26 | @Output() editCategoryRequest = new EventEmitter(); 27 | @Output() refreshList = new EventEmitter(); 28 | 29 | public getCategoryPath = getCategoryPath; 30 | 31 | public categoriesSelectAll: TSelectedAll = false; 32 | public selectedCategories: TSelectedIds = []; 33 | 34 | constructor() {} 35 | 36 | // 多选切换 37 | public batchSelectChange(isSelect: boolean): void { 38 | const data = this.categories.data; 39 | const selectedIds = this.selectedCategories; 40 | this.selectedCategories = handleBatchSelectChange({ data, selectedIds, isSelect }); 41 | } 42 | 43 | // 单个切换 44 | public itemSelectChange(): void { 45 | const data = this.categories.data; 46 | const selectedIds = this.selectedCategories; 47 | const result = handleItemSelectChange({ data, selectedIds }); 48 | this.categoriesSelectAll = result.all; 49 | this.selectedCategories = result.selectedIds; 50 | } 51 | 52 | // 刷新列表 53 | public refreshCategories() { 54 | this.refreshList.emit(); 55 | } 56 | 57 | // 编辑分类 58 | public editCategory(category) { 59 | this.editCategoryRequest.emit(category); 60 | } 61 | 62 | // 删除分类 63 | public delCategory(category) { 64 | this.delCategoryRequest.emit(category); 65 | } 66 | 67 | // 批量删除 68 | public delCategories() { 69 | this.delCategoriesRequest.emit(this.selectedCategories); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app/pages/article/components/category/index.ts: -------------------------------------------------------------------------------- 1 | export * from './category.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/category/category.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |

    暂无分类

    4 | 5 |
    6 | 7 | 8 |   9 | 10 | {{ ''.padEnd(category.level, '─') }} 11 |   12 | {{ category.name }} 13 | 14 | 15 |

    16 |
    17 |
    18 |
    19 | 23 |
    24 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/category/category.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .category-list { 4 | position: relative; 5 | 6 | .category-item-checkbox { 7 | margin: $gap 0; 8 | 9 | &:nth-of-type(1) { 10 | margin-top: 0; 11 | } 12 | 13 | &:last-child { 14 | margin-bottom: 0; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/category/category.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文章编辑页面分类选择组件 3 | * @desc app/page/article/component/category 4 | * @author Surmon 5 | */ 6 | 7 | import * as API_PATH from '@app/constants/api'; 8 | import { Component, ViewEncapsulation, EventEmitter, Input, Output, OnInit, OnChanges } from '@angular/core'; 9 | import { SaHttpRequesterService, SaHttpLoadingService } from '@app/services'; 10 | import { ICategory, TResponsePaginationCategory, buildLevelCategories } from '@app/pages/article/article.utils'; 11 | import { TApiPath } from '@app/pages/pages.interface'; 12 | 13 | const LoadingKey = 'Getting'; 14 | 15 | @Component({ 16 | selector: 'box-article-edit-category', 17 | encapsulation: ViewEncapsulation.None, 18 | templateUrl: './category.component.html', 19 | styleUrls: ['./category.component.scss'], 20 | providers: [SaHttpLoadingService] 21 | }) 22 | 23 | export class ArticleEditCategoryComponent implements OnInit, OnChanges { 24 | 25 | @Input() category; 26 | @Output() categoryChange: EventEmitter = new EventEmitter(); 27 | 28 | private apiPath: TApiPath = API_PATH.CATEGORY; 29 | public categories: ICategory[] = []; 30 | public originalCategories: ICategory[] = []; 31 | 32 | constructor( 33 | private httpService: SaHttpRequesterService, 34 | private httpLoadingService: SaHttpLoadingService 35 | ) {} 36 | 37 | get isLoading(): boolean { 38 | return this.httpLoadingService.isLoading(LoadingKey); 39 | } 40 | 41 | // 勾选动作 42 | public itemSelectChange() { 43 | this.category = this.categories 44 | .filter(category => category.checked) 45 | .map(category => category._id); 46 | this.categoryChange.emit(this.category); 47 | } 48 | 49 | // 获取所有分类 50 | public getCategories() { 51 | return this.httpLoadingService.promise( 52 | LoadingKey, 53 | this.httpService 54 | .get(this.apiPath, { per_page: 666 }) 55 | .then(categories => { 56 | this.originalCategories = categories.result.data; 57 | this.buildLevelCategories(); 58 | }) 59 | ); 60 | } 61 | 62 | buildLevelCategories() { 63 | this.categories = buildLevelCategories(this.originalCategories, this.category); 64 | } 65 | 66 | ngOnInit() { 67 | this.getCategories(); 68 | } 69 | 70 | ngOnChanges() { 71 | this.buildLevelCategories(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/category/index.ts: -------------------------------------------------------------------------------- 1 | export * from './category.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/extend/extend.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | 14 | 23 |
    24 | 27 |
    28 |
    29 | 37 |
    38 |
    39 |
    -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/extend/extend.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/app/pages/article/components/edit/components/extend/extend.component.scss -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/extend/extend.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文章编辑页面扩展信息组件 3 | * @desc app/page/article/component/extend 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'box-article-edit-extend', 11 | templateUrl: './extend.component.html', 12 | styleUrls: ['./extend.component.scss'] 13 | }) 14 | 15 | export class ArticleEditExtendComponent { 16 | 17 | @Input() extends; 18 | @Output() extendsChange: EventEmitter = new EventEmitter(); 19 | 20 | constructor() {} 21 | 22 | // 删除自定义配置项目 23 | public delExtendItem(index) { 24 | this.extends.splice(index, 1); 25 | this.emitExtendData(); 26 | } 27 | 28 | // 增加自定义配置项目 29 | public addExtendItem() { 30 | this.extends = [...this.extends, {}]; 31 | this.emitExtendData(); 32 | } 33 | 34 | // 改变数据后emit事件 35 | public emitExtendData() { 36 | this.extendsChange.emit(this.extends); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/extend/index.ts: -------------------------------------------------------------------------------- 1 | export * from './extend.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/main/index.ts: -------------------------------------------------------------------------------- 1 | export * from './main.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/main/main.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | .article-editor-main-form { 3 | 4 | .article-tags { 5 | 6 | .tags-list { 7 | margin-top: .5em; 8 | display: flex; 9 | justify-content: flex-start; 10 | flex-wrap: wrap; 11 | 12 | .article-tag-item { 13 | margin-right: .5rem; 14 | margin-bottom: .5rem; 15 | } 16 | } 17 | } 18 | 19 | .article-description { 20 | line-height: 1.8em; 21 | } 22 | 23 | .article-content { 24 | padding: 0; 25 | line-height: inherit; 26 | } 27 | } -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/submit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './submit.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/components/submit/submit.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 文章编辑页面状态选择及发布组件 3 | * @desc app/page/article/component/submit 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 8 | import { EOriginState, EPublicState, EPublishState } from '@app/constants/state'; 9 | 10 | @Component({ 11 | selector: 'box-article-edit-submit', 12 | templateUrl: './submit.component.html' 13 | }) 14 | 15 | export class ArticleEditSubmitComponent { 16 | 17 | public OriginState = EOriginState; 18 | public PublicState = EPublicState; 19 | public PublishState = EPublishState; 20 | 21 | @Input() disabled: boolean; 22 | @Input() isEdit: boolean; 23 | @Input() state; 24 | @Input() origin; 25 | @Input() ppublic; 26 | @Input() password; 27 | @Output() stateChange: EventEmitter = new EventEmitter(); 28 | @Output() originChange: EventEmitter = new EventEmitter(); 29 | @Output() ppublicChange: EventEmitter = new EventEmitter(); 30 | @Output() passwordChange: EventEmitter = new EventEmitter(); 31 | @Output() submitArticle = new EventEmitter(); 32 | 33 | 34 | constructor() {} 35 | } 36 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/edit.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 12 | 13 | 14 |
    15 |
    16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 34 | 35 | 36 |
    37 |
    38 | -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/edit.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/app/pages/article/components/edit/edit.component.scss -------------------------------------------------------------------------------- /src/app/pages/article/components/edit/index.ts: -------------------------------------------------------------------------------- 1 | export * from './edit.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './list.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/list/list.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .contnet-top-tools { 4 | .article-search-form { 5 | margin-left: $gap-xs; 6 | 7 | .input-group { 8 | margin-bottom: 0; 9 | 10 | .form-control { 11 | width: 12rem; 12 | } 13 | } 14 | } 15 | } 16 | 17 | .article-list { 18 | position: relative; 19 | 20 | .article-err-msg { 21 | margin: 1rem 0; 22 | } 23 | 24 | tbody { 25 | tr { 26 | &.article-item { 27 | height: 13rem; 28 | } 29 | 30 | td { 31 | .article-content { 32 | padding: 1rem; 33 | line-height: 2; 34 | position: relative; 35 | 36 | > .content-bg { 37 | position: absolute; 38 | width: 100%; 39 | height: 100%; 40 | top: 0; 41 | left: 0; 42 | z-index: -1; 43 | background-color: $disabled-bg; 44 | background-position: center; 45 | background-size: cover; 46 | border-radius: 1px; 47 | filter: brightness(0.4); 48 | opacity: .8; 49 | border-radius: $radius-sm; 50 | } 51 | 52 | > .title { 53 | margin-top: .2rem; 54 | margin-bottom: 1rem; 55 | 56 | > a { 57 | &:hover { 58 | cursor: pointer; 59 | border-bottom: 1px solid; 60 | } 61 | } 62 | } 63 | 64 | .description { 65 | font-size: $font-size-sm; 66 | margin-bottom: 0; 67 | } 68 | } 69 | 70 | .article-category { 71 | .list { 72 | margin: 0; 73 | line-height: 2.3; 74 | } 75 | } 76 | 77 | .action-btn-list { 78 | text-align: center; 79 | 80 | .button-wrapper { 81 | margin-bottom: $gap-xs; 82 | &:last-child { 83 | margin: 0; 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/app/pages/article/components/tag/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tag.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/article/components/tag/tag.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .tag-form { 4 | .tag-description { 5 | line-height: 1.8em; 6 | } 7 | 8 | .submit-btn { 9 | margin-right: $gap; 10 | } 11 | } 12 | 13 | .tag-search-form { 14 | > .input-group { 15 | > .form-control { 16 | width: 14rem; 17 | } 18 | } 19 | } 20 | 21 | .tag-list { 22 | position: relative; 23 | 24 | .tag-err-msg { 25 | margin: 1rem 0; 26 | } 27 | 28 | tbody { 29 | tr { 30 | td { 31 | .tag-description { 32 | max-width: 13em; 33 | overflow: hidden; 34 | text-overflow: ellipsis; 35 | -o-text-overflow: ellipsis; 36 | -webkit-text-overflow: ellipsis; 37 | -moz-text-overflow: ellipsis; 38 | white-space:nowrap; 39 | } 40 | 41 | .content { 42 | line-height: 2; 43 | 44 | p { 45 | margin: 0; 46 | } 47 | 48 | img { 49 | max-width: 4em; 50 | margin: 0 1em; 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/pages/article/index.ts: -------------------------------------------------------------------------------- 1 | export * from './article.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    10 | Because the mountain is there 11 |
    12 | 25 |
    26 |
    27 |
    28 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | $text-color: #ffffff; 4 | 5 | .auth-main { 6 | display: flex; 7 | align-items: center; 8 | height: 100%; 9 | width: 100%; 10 | position: absolute; 11 | } 12 | 13 | .auth-block { 14 | width: 540px; 15 | margin: 0 auto; 16 | border-radius: 5px; 17 | @include bg-translucent-dark(0.55); 18 | color: #fff; 19 | padding: 32px; 20 | 21 | h1 { 22 | font-weight: $font-light; 23 | margin-bottom: 28px; 24 | text-align: center; 25 | } 26 | 27 | p { 28 | font-size: 16px; 29 | } 30 | 31 | a { 32 | color: $primary; 33 | outline: none; 34 | text-decoration: none; 35 | transition: all 0.2s ease; 36 | 37 | &:hover { 38 | color: $primary-dark; 39 | } 40 | } 41 | 42 | .control-label { 43 | padding-top: 11px; 44 | color: $text-color; 45 | } 46 | 47 | .form-group { 48 | margin-bottom: 12px; 49 | } 50 | 51 | > .secret-titles { 52 | 53 | > .title, 54 | > .input { 55 | text-align: center; 56 | display: block; 57 | margin: 0; 58 | padding: 0; 59 | width: 100%; 60 | border: none; 61 | background: none; 62 | font-size: 32px; 63 | height: 35px; 64 | line-height: 35px; 65 | color: #fff; 66 | } 67 | } 68 | } 69 | 70 | .auth-input { 71 | width: 300px; 72 | margin-bottom: 24px; 73 | 74 | input { 75 | display: block; 76 | width: 100%; 77 | border: none; 78 | font-size: 16px; 79 | padding: 4px 10px; 80 | outline: none; 81 | } 82 | } 83 | 84 | @media (max-width: $resXS) { 85 | .auth-block { 86 | width: 70%; 87 | 88 | > .secret-titles { 89 | 90 | > .title { 91 | font-size: 1.2em; 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 登陆页面模块 3 | * @desc app/page/auth/module 4 | * @author Surmon 5 | */ 6 | 7 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 8 | import { NgModule } from '@angular/core'; 9 | import { CommonModule } from '@angular/common'; 10 | import { SaBaseModule } from '@app/sa-base.module'; 11 | import { RoutingModule } from './auth.routing'; 12 | import { AuthComponent } from './auth.component'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | FormsModule, 18 | ReactiveFormsModule, 19 | SaBaseModule, 20 | RoutingModule 21 | ], 22 | declarations: [ 23 | AuthComponent 24 | ] 25 | }) 26 | export class AuthModule {} 27 | -------------------------------------------------------------------------------- /src/app/pages/auth/auth.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 登陆页面路由 3 | * @desc app/page/auth/toutes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { AuthComponent } from './auth.component'; 9 | 10 | const routes: Routes = [ 11 | { path: '', data: { name: 'auth' }, component: AuthComponent } 12 | ]; 13 | 14 | export const RoutingModule = RouterModule.forChild(routes); 15 | -------------------------------------------------------------------------------- /src/app/pages/auth/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/comment/comment.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 评论页面组件 3 | * @desc app/page/comment/component 4 | * @author Surmon 5 | */ 6 | 7 | import { Component } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'page-comment', 11 | template: `` 12 | }) 13 | export class CommentComponent { 14 | constructor() {} 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/comment/comment.constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 评论公共扩展 3 | * @desc app/comment/utils 4 | * @author Surmon 5 | */ 6 | 7 | import { IDataExtends, IResponsePaginationData } from '@app/pages/pages.interface'; 8 | 9 | // 评论状态 10 | export enum ECommentState { 11 | All = 'all', 12 | Auditing = 0, // 待审核 13 | Published = 1, // 通过正常 14 | Deleted = -1, // 已删除 15 | Spam = -2, // 垃圾评论 16 | } 17 | 18 | // 评论宿主页面的 POST_ID 类型 19 | export enum ECommentPostType { 20 | Guestbook = 0, // 留言板 21 | } 22 | 23 | // 评论本身的类型 24 | export enum ECommentParentType { 25 | Self = 0, // 自身一级评论 26 | } 27 | 28 | // 单个评论 29 | export interface IComment { 30 | ip?: number; 31 | id?: number; 32 | _id?: string; 33 | pid?: number; 34 | post_id: number; 35 | content: string; 36 | agent: string; 37 | state: ECommentState; 38 | likes: number; 39 | is_top: boolean; 40 | author: { 41 | email: string; 42 | name: string; 43 | site?: string; 44 | }; 45 | ip_location?: any; 46 | update_at?: string; 47 | create_at?: string; 48 | selected?: boolean; 49 | extends: IDataExtends[]; 50 | } 51 | 52 | export type TCommentId = IComment['_id']; 53 | export type TCommentPostId = IComment['post_id']; 54 | export type TResponsePaginationComment = IResponsePaginationData; 55 | -------------------------------------------------------------------------------- /src/app/pages/comment/comment.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 评论页面模块 3 | * @desc app/page/comment/module 4 | * @author Surmon 5 | */ 6 | 7 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 10 | import { ModalModule } from 'ngx-bootstrap/modal'; 11 | import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; 12 | import { PaginationModule } from 'ngx-bootstrap/pagination'; 13 | import { SaBaseModule } from '@app/sa-base.module'; 14 | import { RoutingModule } from './comment.routing'; 15 | 16 | import { CommentComponent } from './comment.component'; 17 | import { CommentListComponent } from './components/list'; 18 | import { CommentDetailComponent } from './components/detail'; 19 | 20 | @NgModule({ 21 | imports: [ 22 | CommonModule, 23 | FormsModule, 24 | ReactiveFormsModule, 25 | PaginationModule.forRoot(), 26 | BsDropdownModule.forRoot(), 27 | ModalModule.forRoot(), 28 | SaBaseModule, 29 | RoutingModule, 30 | ], 31 | providers: [], 32 | declarations: [ 33 | CommentComponent, 34 | CommentListComponent, 35 | CommentDetailComponent 36 | ], 37 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 38 | }) 39 | export class CommentModule {} 40 | -------------------------------------------------------------------------------- /src/app/pages/comment/comment.routing.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | 3 | import { CommentComponent } from './comment.component'; 4 | import { CommentListComponent } from './components/list'; 5 | import { CommentDetailComponent } from './components/detail'; 6 | 7 | const routes: Routes = [ 8 | { 9 | path: '', 10 | component: CommentComponent, 11 | children: [ 12 | { path: '', redirectTo: 'list', pathMatch: 'full' }, 13 | { path: 'list', component: CommentListComponent }, 14 | { path: 'post', redirectTo: 'post/0', pathMatch: 'full' }, 15 | { path: 'post/:post_id', component: CommentListComponent }, 16 | { path: 'detail/:comment_id', component: CommentDetailComponent } 17 | ] 18 | } 19 | ]; 20 | 21 | export const RoutingModule = RouterModule.forChild(routes); 22 | -------------------------------------------------------------------------------- /src/app/pages/comment/components/detail/detail.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .comment-form { 4 | .form-control { 5 | &.comemnt-content { 6 | padding: 0; 7 | } 8 | } 9 | 10 | .action-btn { 11 | margin-right: $gap; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/pages/comment/components/detail/index.ts: -------------------------------------------------------------------------------- 1 | export * from './detail.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/comment/components/list/index.ts: -------------------------------------------------------------------------------- 1 | export * from './list.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/comment/components/list/list.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .sort-select, 4 | .comment-search-form { 5 | width: auto; 6 | display: inline-flex; 7 | vertical-align: middle; 8 | margin-left: $gap-xs; 9 | } 10 | 11 | .comment-search-form { 12 | .input-group { 13 | margin: 0; 14 | 15 | > .form-control { 16 | width: 14em; 17 | } 18 | } 19 | } 20 | 21 | .table-responsive { 22 | margin-top: 1rem; 23 | } 24 | 25 | .comment-list { 26 | position: relative; 27 | 28 | .comment-err-msg { 29 | margin: 1rem 0; 30 | } 31 | 32 | tbody { 33 | tr { 34 | &.comment-item { 35 | height: 13rem; 36 | } 37 | 38 | td { 39 | line-height: 2.6; 40 | 41 | .comment-content { 42 | line-height: 2; 43 | word-break: break-word; 44 | } 45 | 46 | .comment-user { 47 | > .avatar, 48 | > .name, 49 | > .email, 50 | > .site { 51 | max-width: 200px; 52 | @include text-truncate(); 53 | } 54 | 55 | > .avatar { 56 | .image { 57 | width: 30px; 58 | height: 30px; 59 | border-radius: 1px; 60 | } 61 | } 62 | } 63 | 64 | .like-count { 65 | color: pink; 66 | } 67 | 68 | .action-btn-list { 69 | text-align: center; 70 | 71 | .button-wrapper { 72 | margin-bottom: $gap-xs; 73 | &:last-child { 74 | margin: 0; 75 | } 76 | } 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/pages/comment/index.ts: -------------------------------------------------------------------------------- 1 | export * from './comment.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 仪表盘页面模块 3 | * @desc app/page/dashboard/module 4 | * @author Surmon 5 | */ 6 | 7 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | import { FormsModule } from '@angular/forms'; 10 | import { SaBaseModule } from '@app/sa-base.module'; 11 | import { DashboardComponent } from './dashboard.component'; 12 | import { RoutingModule } from './dashboard.routing'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | FormsModule, 18 | SaBaseModule, 19 | RoutingModule 20 | ], 21 | declarations: [ 22 | DashboardComponent 23 | ], 24 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 25 | }) 26 | export class DashboardModule {} 27 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/dashboard.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 仪表盘页面路由 3 | * @desc app/page/dashboard/routes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { DashboardComponent } from './dashboard.component'; 9 | 10 | const routes: Routes = [ 11 | { 12 | path: '', 13 | component: DashboardComponent 14 | } 15 | ]; 16 | 17 | export const RoutingModule = RouterModule.forChild(routes); 18 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/ga.embed.lib.loader.js: -------------------------------------------------------------------------------- 1 | 2 | export function loadScript() { 3 | 4 | (function(w,d,s,g,js,fs){ 5 | g=w.gapi||(w.gapi={});g.analytics={q:[],ready:function(f){this.q.push(f);}}; 6 | js=d.createElement(s);fs=d.getElementsByTagName(s)[0]; 7 | js.src='//apis.google.com/js/platform.js'; 8 | fs.parentNode.insertBefore(js,fs);js.onload=function(){g.load('analytics');}; 9 | }(window,document,'script')); 10 | 11 | } -------------------------------------------------------------------------------- /src/app/pages/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dashboard.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/buttons.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 |
    8 |
    9 | 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 | 36 | 37 | 38 | 39 | 40 | 41 |
    42 |
    43 | 44 |
    45 |
    51 |
    52 |
    53 |
    54 |
    55 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/buttons.component.scss: -------------------------------------------------------------------------------- 1 | .basic-btns { 2 | padding-top: 8px; 3 | margin-bottom: -8px; 4 | 5 | h5 { 6 | line-height: 35px; 7 | font-size: 12px; 8 | 9 | &.row-sm { 10 | line-height: 30px; 11 | } 12 | 13 | &.row-xs { 14 | line-height: 22px; 15 | } 16 | } 17 | 18 | & > .row { 19 | padding-bottom: 4px; 20 | } 21 | } 22 | 23 | .btns-row { 24 | 25 | & > div { 26 | margin-bottom: 12px; 27 | } 28 | } 29 | 30 | .btns-same-width-sm { 31 | .btn { 32 | width: 48px; 33 | } 34 | } 35 | 36 | .btns-same-width-md { 37 | .btn { 38 | width: 79px; 39 | } 40 | } 41 | 42 | .btns-same-width-lg { 43 | .btn { 44 | width: 112px; 45 | } 46 | } 47 | 48 | ul.btn-list { 49 | margin: 0 0 0 -18px; 50 | padding: 0; 51 | padding-top: 6px; 52 | clear: both; 53 | 54 | li { 55 | margin: 0px 0 12px 18px; 56 | padding: 0; 57 | list-style: none; 58 | float: left; 59 | } 60 | } 61 | 62 | .btn-group-wrapper { 63 | margin-bottom: 12px; 64 | } 65 | 66 | .btn-group-example { 67 | float: left; 68 | margin-right: 30px; 69 | margin-bottom: 12px; 70 | } 71 | 72 | .btn-toolbar-example { 73 | float: left; 74 | } 75 | 76 | .progress-buttons-container { 77 | text-align: center; 78 | font-size: 16px; 79 | 80 | span.button-title { 81 | display: inline-block; 82 | width: 100%; 83 | line-height: 1; 84 | font-size: 14px; 85 | margin-bottom: 10px; 86 | margin-top: 10px; 87 | } 88 | .row + .row { 89 | margin-top: 30px; 90 | } 91 | } 92 | 93 | .button-panel { 94 | height: 350px; 95 | .btn { 96 | width: 150px; 97 | } 98 | } 99 | 100 | .large-buttons-panel { 101 | height: 202px; 102 | } 103 | 104 | .button-panel.df-size-button-panel { 105 | .btn-xs { 106 | width: 60px; 107 | } 108 | .btn-sm { 109 | width: 90px; 110 | } 111 | .btn-mm { 112 | width: 120px; 113 | } 114 | .btn-md { 115 | width: 150px; 116 | } 117 | .btn-xm { 118 | width: 175px; 119 | } 120 | .btn-lg { 121 | width: 200px; 122 | } 123 | } 124 | 125 | .button-wrapper { 126 | text-align: center; 127 | margin: 5px 0; 128 | } 129 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/buttons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'page-buttons', 5 | encapsulation: ViewEncapsulation.None, 6 | styleUrls: ['./buttons.component.scss'], 7 | templateUrl: './buttons.component.html', 8 | }) 9 | export class ButtonsComponent { 10 | 11 | constructor() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/disabledButtons/disabledButtons.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 | 6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | 18 |
    19 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/disabledButtons/disabledButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-disabled-buttons', 5 | templateUrl: './disabledButtons.component.html', 6 | }) 7 | export class DisabledButtonsComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/disabledButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './disabledButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/dropdownButtons/dropdownButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-dropdown-buttons', 5 | templateUrl: './dropdownButtons.component.html' 6 | }) 7 | 8 | export class DropdownButtonsComponent { 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/dropdownButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dropdownButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/flatButtons/flatButtons.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 | 6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | 18 |
    19 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/flatButtons/flatButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-flat-buttons', 5 | templateUrl: './flatButtons.component.html', 6 | }) 7 | export class FlatButtonsComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/flatButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './flatButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/groupButtons/groupButtons.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 | 6 |
    7 |
    8 |
    9 | 25 |
    26 |
    27 |
    28 | 40 | 41 |
    42 |
    43 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/groupButtons/groupButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-group-buttons', 5 | templateUrl: './groupButtons.component.html', 6 | }) 7 | export class GroupButtonsComponent { 8 | 9 | public totalItems: number = 64; 10 | public currentPage: number = 4; 11 | 12 | public maxSize: number = 5; 13 | public bigTotalItems: number = 175; 14 | public bigCurrentPage: number = 1; 15 | 16 | public setPage(pageNo: number): void { 17 | this.currentPage = pageNo; 18 | } 19 | 20 | public pageChanged(event: any): void { 21 | console.log('Page changed to: ' + event.page); 22 | console.log('Number items per page: ' + event.itemsPerPage); 23 | } 24 | 25 | constructor() {} 26 | } 27 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/groupButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './groupButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/iconButtons/iconButtons.component.html: -------------------------------------------------------------------------------- 1 |
      2 |
    • 3 | 6 |
    • 7 |
    • 8 | 11 |
    • 12 |
    • 13 | 16 |
    • 17 |
    • 18 | 21 |
    • 22 |
    • 23 | 26 |
    • 27 |
    • 28 | 31 |
    • 32 |
    33 |
    Buttons with icons
    34 |
      35 |
    • 36 | 40 |
    • 41 |
    • 42 | 46 |
    • 47 |
    • 48 | 52 |
    • 53 |
    • 54 | 57 |
    • 58 |
    • 59 | 63 |
    • 64 |
    • 65 | 69 |
    • 70 |
    71 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/iconButtons/iconButtons.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .icon { 4 | font-size: $font-size-base * 1.1; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/iconButtons/iconButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-icon-buttons', 5 | styleUrls: ['./iconButtons.component.scss'], 6 | templateUrl: './iconButtons.component.html', 7 | }) 8 | export class IconButtonsComponent { 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/iconButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './iconButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/largeButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './largeButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/largeButtons/largeButtons.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 |
    6 | 7 |
    8 |
    9 | 10 |
    11 |
    12 | 13 |
    14 |
    15 | 16 |
    17 |
    18 | 19 |
    20 |
    21 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/largeButtons/largeButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-large-buttons', 5 | templateUrl: './largeButtons.component.html', 6 | }) 7 | export class LargeButtonsComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/raisedButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './raisedButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/raisedButtons/raisedButtons.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 | 6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | 18 |
    19 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/raisedButtons/raisedButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-raised-buttons', 5 | templateUrl: './raisedButtons.component.html', 6 | }) 7 | export class RaisedButtonsComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/sizedButtons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sizedButtons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/sizedButtons/sizedButtons.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 | 6 |
    7 |
    8 | 9 |
    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 |
    17 | 18 |
    19 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/components/sizedButtons/sizedButtons.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-sized-buttons', 5 | templateUrl: './sizedButtons.component.html', 6 | }) 7 | export class SizedButtonsComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/buttons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './buttons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/checkboxInputs/checkboxInputs.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |
    7 | 8 |
    9 |
    10 | 11 |
    12 |
    13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 | 23 |
    24 |
    25 | 29 |
    30 |
    31 | 35 |
    36 |
    37 | 41 |
    42 |
    43 |
    44 |
    45 |
    46 | 47 | 48 |
    49 |
    50 |
    51 | 52 | 53 |
    54 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/checkboxInputs/checkboxInputs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-checkbox-inputs', 5 | templateUrl: './checkboxInputs.component.html', 6 | }) 7 | export class CheckboxInputsComponent { 8 | 9 | public checkboxModel = false; 10 | public checkboxModel2 = false; 11 | public checkboxModel3 = true; 12 | public checkboxModel4 = false; 13 | 14 | constructor() {} 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/checkboxInputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './checkboxInputs.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/groupInputs/groupInputs.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | @ 4 |
    5 | 6 |
    7 |
    8 | 9 |
    10 | @example.com 11 |
    12 |
    13 |
    14 |
    15 | $ 16 |
    17 | 18 |
    19 | .00 20 |
    21 |
    22 |
    23 | 24 | 25 | 26 | 27 | 28 |
    29 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/groupInputs/groupInputs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-group-inputs', 5 | templateUrl: './groupInputs.component.html', 6 | }) 7 | export class GroupInputsComponent { 8 | constructor() {} 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/groupInputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './groupInputs.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/selectInputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './selectInputs.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/selectInputs/selectInputs.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 10 |
    11 |
    12 | 13 | 20 |
    21 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/selectInputs/selectInputs.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | select.form-control[multiple] { 4 | overflow-y: scroll; 5 | } 6 | 7 | select.form-control { 8 | color: $default; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/selectInputs/selectInputs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-select-inputs', 5 | styleUrls: ['./selectInputs.component.scss'], 6 | templateUrl: './selectInputs.component.html' 7 | }) 8 | export class SelectInputsComponent { 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/standardInputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './standardInputs.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/standardInputs/standardInputs.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 |
    6 |
    7 | 8 | 9 |
    10 |
    11 | 12 | 13 |
    14 |
    15 | 16 | 17 | A block of help text that breaks onto a new line and may extend beyond one line. 18 |
    19 |
    20 | 21 | 22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 | 29 |
    30 |
    31 | 32 |
    33 |
    34 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/standardInputs/standardInputs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-standard-inputs', 5 | templateUrl: './standardInputs.component.html', 6 | }) 7 | export class StandardInputsComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/validationInputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validationInputs.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/components/validationInputs/validationInputs.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-validation-inputs', 5 | templateUrl: './validationInputs.component.html', 6 | }) 7 | export class ValidationInputsComponent { 8 | constructor() {} 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './inputs.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/inputs.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 |
    23 |
    24 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/inputs/inputs.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Demo Input 表单演示页面 3 | * @desc app/page/demo/component/forms/input 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, ViewEncapsulation } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'page-inputs', 11 | encapsulation: ViewEncapsulation.None, 12 | templateUrl: './inputs.component.html', 13 | }) 14 | export class FormInputsComponent { 15 | 16 | constructor() {} 17 | } 18 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/basicForm/basicForm.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 5 |
    6 |
    7 | 8 | 9 |
    10 |
    11 | 12 |
    13 |
    14 | 15 |
    16 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/basicForm/basicForm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-basic-form', 5 | templateUrl: './basicForm.component.html', 6 | }) 7 | export class BasicFormComponent { 8 | public isChecked: boolean; 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/basicForm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './basicForm.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/blockForm/blockForm.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 | 19 | 20 |
    21 |
    22 |
    23 |
    24 | 25 | 26 |
    27 |
    28 |
    29 | 30 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/blockForm/blockForm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-block-form', 5 | templateUrl: './blockForm.component.html', 6 | }) 7 | export class BlockFormComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/blockForm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './blockForm.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/horizontalForm/horizontalForm.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | 6 |
    7 |
    8 |
    9 | 10 |
    11 | 12 |
    13 |
    14 |
    15 |
    16 |
    17 | 18 |
    19 |
    20 |
    21 |
    22 |
    23 | 24 |
    25 |
    26 |
    27 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/horizontalForm/horizontalForm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-horizontal-form', 5 | templateUrl: './horizontalForm.component.html', 6 | }) 7 | export class HorizontalFormComponent { 8 | public isRemember: boolean; 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/horizontalForm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './horizontalForm.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/inlineForm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './inlineForm.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/inlineForm/inlineForm.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 |
    6 | 7 |
    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 | 15 |
    16 |
    17 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/inlineForm/inlineForm.component.scss: -------------------------------------------------------------------------------- 1 | .checkbox { 2 | display: inline-block; 3 | margin-bottom: -12px; 4 | margin-left: 12px; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/inlineForm/inlineForm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-inline-form', 5 | styleUrls: ['./inlineForm.component.scss'], 6 | templateUrl: './inlineForm.component.html', 7 | }) 8 | export class InlineFormComponent { 9 | public isRemember: boolean; 10 | 11 | constructor() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/withoutLabelsForm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './withoutLabelsForm.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/withoutLabelsForm/withoutLabelsForm.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 |
    6 | 7 |
    8 |
    9 | 10 |
    11 | 12 |
    13 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/components/withoutLabelsForm/withoutLabelsForm.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-without-labels-form', 5 | templateUrl: './withoutLabelsForm.component.html', 6 | }) 7 | export class WithoutLabelsFormComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './layouts.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/layouts.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 |
    5 | 6 | 7 | 8 |
    9 |
    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 |
    36 |
    37 |
    38 |
    39 |
    40 | 41 | 42 | 43 |
    44 |
    45 |
    46 | -------------------------------------------------------------------------------- /src/app/pages/example/components/forms/layouts/layouts.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Demo Layout 表单演示页面 3 | * @desc app/page/demo/component/forms/layout 4 | * @author Surmon 5 | */ 6 | 7 | import { Component, ViewEncapsulation } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'page-layouts', 11 | encapsulation: ViewEncapsulation.None, 12 | styles: [], 13 | templateUrl: './layouts.component.html', 14 | }) 15 | export class FormLayoutsComponent { 16 | 17 | public picture = 'assets/images/profile/no-photo.png'; 18 | 19 | constructor() {} 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/example/components/grid/grid.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .show-grid div[class^=col-]{ 4 | padding: 10px; 5 | box-sizing: border-box; 6 | div { 7 | color: $default-text; 8 | text-align: center; 9 | font-size: 18px; 10 | background-color: rgba($content-text, 0.3); 11 | padding: 12px 5px; 12 | } 13 | } 14 | 15 | .grid-h{ 16 | margin-top: 40px; 17 | margin-bottom: 0; 18 | &:first-child{ 19 | margin-top: 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/pages/example/components/grid/grid.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'page-grid', 5 | encapsulation: ViewEncapsulation.None, 6 | styleUrls: ['./grid.component.scss'], 7 | templateUrl: './grid.component.html', 8 | }) 9 | export class GridComponent { 10 | 11 | constructor() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/pages/example/components/grid/index.ts: -------------------------------------------------------------------------------- 1 | export * from './grid.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/icons/icons.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 |
    6 |
    7 | 8 |
    9 |
    10 | See all ionicons icons 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 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |
    42 | -------------------------------------------------------------------------------- /src/app/pages/example/components/icons/icons.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | a.see-all-icons { 4 | float: right; 5 | } 6 | 7 | .icon-list { 8 | .icon { 9 | text-align: center; 10 | 11 | .icon-ionic { 12 | margin: $gap 0; 13 | font-size: $font-size-lg; 14 | color: $default; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/pages/example/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from './icons.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/modals/index.ts: -------------------------------------------------------------------------------- 1 | export * from './modals.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/modals/modals.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .modal { 4 | padding-top: 300px; 5 | 6 | .modal-content { 7 | color: $dropdown-text; 8 | 9 | .modal-header { 10 | border: none; 11 | } 12 | 13 | .modal-footer { 14 | border: none; 15 | } 16 | 17 | .close { 18 | outline: none; 19 | } 20 | } 21 | } 22 | 23 | .modal-buttons .btn { 24 | margin-right: 20px; 25 | } 26 | -------------------------------------------------------------------------------- /src/app/pages/example/components/modals/modals.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewChild } from '@angular/core'; 2 | import { ModalDirective } from 'ngx-bootstrap/modal'; 3 | 4 | @Component({ 5 | selector: 'page-modals', 6 | styleUrls: ['./modals.component.scss'], 7 | templateUrl: './modals.component.html' 8 | }) 9 | export class ModalsComponent { 10 | 11 | @ViewChild('childModal', { static: false }) childModal: ModalDirective; 12 | 13 | showChildModal(): void { 14 | this.childModal.show(); 15 | } 16 | 17 | hideChildModal(): void { 18 | this.childModal.hide(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/example/components/other/index.ts: -------------------------------------------------------------------------------- 1 | export * from './other.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/other/other.component.scss: -------------------------------------------------------------------------------- 1 | @import '~app/styles/init'; 2 | 3 | .other-page { 4 | 5 | .btn { 6 | margin-right: 1rem; 7 | 8 | > .badge { 9 | margin-right: 0; 10 | } 11 | } 12 | 13 | .progress { 14 | margin-bottom: 1rem; 15 | } 16 | 17 | .spinners { 18 | 19 | > div { 20 | margin-right: 1rem; 21 | } 22 | } 23 | 24 | .badge { 25 | margin-right: 1rem; 26 | } 27 | } -------------------------------------------------------------------------------- /src/app/pages/example/components/other/other.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'page-other', 5 | templateUrl: './other.component.html', 6 | styleUrls: ['./other.component.scss'], 7 | }) 8 | export class OtherComponent {} 9 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/borderedTable/borderedTable.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
    BrowserVisitsPurchases%
    {{ item.browser }}{{ item.visits }}{{ item.purchases }}{{ item.percent }}
    22 |
    23 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/borderedTable/borderedTable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TableService } from '../../table.service'; 3 | 4 | @Component({ 5 | selector: 'box-bordered-table', 6 | templateUrl: './borderedTable.component.html', 7 | }) 8 | export class BorderedTableComponent { 9 | 10 | metricsTableData: Array; 11 | 12 | constructor(private tableService: TableService) { 13 | this.metricsTableData = tableService.metricsTableData; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/borderedTable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './borderedTable.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/condensedTable/condensedTable.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 |
    #First NameLast NameUsernameEmailStatus
    {{ item.id }}{{ item.firstName }}{{ item.lastName }}{{ item.username }} 21 | 22 |
    26 |
    27 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/condensedTable/condensedTable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TableService } from '../../table.service'; 3 | 4 | @Component({ 5 | selector: 'box-condensed-table', 6 | templateUrl: './condensedTable.component.html' 7 | }) 8 | export class CondensedTableComponent { 9 | 10 | peopleTableData: Array; 11 | 12 | constructor(private tableService: TableService) { 13 | this.peopleTableData = tableService.peopleTableData; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/condensedTable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './condensedTable.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/contextualTable/contextualTable.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
    #First NameLast NameUsernameEmailAge
    1MarkOtto@mdo28
    2JacobThornton@fat45
    3LarryBird@twitter 32 | 18
    4JohnSnow@snow20
    5JackSparrow@jack30
    52 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/contextualTable/contextualTable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-contextual-table', 5 | templateUrl: './contextualTable.component.html', 6 | }) 7 | export class ContextualTableComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/contextualTable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contextualTable.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/hoverTable/hoverTable.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    BrowserVisitsPurchases%
    {{item.browser}}{{item.visits}}{{item.purchases}}{{item.percent}}
    28 |
    29 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/hoverTable/hoverTable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TableService } from '../../table.service'; 3 | 4 | @Component({ 5 | selector: 'box-hover-table', 6 | templateUrl: './hoverTable.component.html' 7 | }) 8 | export class HoverTableComponent { 9 | 10 | metricsTableData: Array; 11 | 12 | constructor(private tableService: TableService) { 13 | this.metricsTableData = tableService.metricsTableData; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/hoverTable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hoverTable.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/responsiveTable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './responsiveTable.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/responsiveTable/responsiveTable.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
    #First NameLast NameUsernameEmailAge
    1MarkOtto@mdo28
    2JacobThornton@fat45
    3LarryBird@twitter 33 | 18
    4JohnSnow@snow20
    5JackSparrow@jack30
    53 |
    54 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/responsiveTable/responsiveTable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'box-responsive-table', 5 | templateUrl: './responsiveTable.component.html', 6 | }) 7 | export class ResponsiveTableComponent { 8 | 9 | constructor() {} 10 | } 11 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/stripedTable/index.ts: -------------------------------------------------------------------------------- 1 | export * from './stripedTable.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/stripedTable/stripedTable.component.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
    #First NameLast NameUsernameEmailAge
    {{ item.id }}{{ item.firstName }}{{ item.lastName }}{{ item.username }}{{ item.age }}
    24 |
    25 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/components/stripedTable/stripedTable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TableService } from '../../table.service'; 3 | 4 | @Component({ 5 | selector: 'box-striped-table', 6 | templateUrl: './stripedTable.component.html' 7 | }) 8 | export class StripedTableComponent { 9 | 10 | smartTableData: Array; 11 | 12 | constructor(private tableService: TableService) { 13 | this.smartTableData = tableService.smartTableData; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/index.ts: -------------------------------------------------------------------------------- 1 | export * from './table.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/table.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 | 5 | 6 | 7 |
    8 |
    9 | 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 | 36 |
    37 |
    38 |
    39 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/table.component.scss: -------------------------------------------------------------------------------- 1 | .status-button { 2 | width: 60px; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/pages/example/components/table/table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'page-basic-tables', 5 | encapsulation: ViewEncapsulation.None, 6 | styleUrls: ['./table.component.scss'], 7 | templateUrl: './table.component.html' 8 | }) 9 | export class TableComponent { 10 | 11 | constructor() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/pages/example/components/typography/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typography.component'; 2 | -------------------------------------------------------------------------------- /src/app/pages/example/components/typography/typography.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'page-typography', 5 | styles: [], 6 | templateUrl: './typography.component.html', 7 | }) 8 | export class TypographyComponent { 9 | 10 | constructor() {} 11 | } 12 | -------------------------------------------------------------------------------- /src/app/pages/example/example.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Demo UI 演示页面 3 | * @desc app/page/demo/component/ui 4 | * @author Surmon 5 | */ 6 | 7 | import { Component } from '@angular/core'; 8 | 9 | @Component({ 10 | selector: 'page-example', 11 | styles: [], 12 | template: `` 13 | }) 14 | export class ExampleComponent { 15 | 16 | constructor() {} 17 | } 18 | -------------------------------------------------------------------------------- /src/app/pages/example/example.routing.ts: -------------------------------------------------------------------------------- 1 | import { Routes, RouterModule } from '@angular/router'; 2 | import { ExampleComponent } from './example.component'; 3 | import { ButtonsComponent } from './components/buttons/buttons.component'; 4 | import { GridComponent } from './components/grid/grid.component'; 5 | import { IconsComponent } from './components/icons/icons.component'; 6 | import { ModalsComponent } from './components/modals/modals.component'; 7 | import { TypographyComponent } from './components/typography/typography.component'; 8 | import { TableComponent } from './components/table/table.component'; 9 | import { FormInputsComponent } from './components/forms/inputs'; 10 | import { FormLayoutsComponent } from './components/forms/layouts'; 11 | import { OtherComponent } from './components/other/other.component'; 12 | 13 | const routes: Routes = [ 14 | { 15 | path: '', 16 | component: ExampleComponent, 17 | children: [ 18 | { path: '', redirectTo: 'buttons' }, 19 | { path: 'buttons', component: ButtonsComponent }, 20 | { path: 'grid', component: GridComponent }, 21 | { path: 'icons', component: IconsComponent }, 22 | { path: 'typography', component: TypographyComponent }, 23 | { path: 'modals', component: ModalsComponent }, 24 | { path: 'table', component: TableComponent }, 25 | { path: 'forms/inputs', component: FormInputsComponent }, 26 | { path: 'forms/layouts', component: FormLayoutsComponent }, 27 | { path: 'other', component: OtherComponent } 28 | ] 29 | } 30 | ]; 31 | 32 | export const RoutingModule = RouterModule.forChild(routes); 33 | -------------------------------------------------------------------------------- /src/app/pages/example/index.ts: -------------------------------------------------------------------------------- 1 | export * from './example.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pages.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/options/index.ts: -------------------------------------------------------------------------------- 1 | export * from './options.module'; 2 | -------------------------------------------------------------------------------- /src/app/pages/options/options.component.scss: -------------------------------------------------------------------------------- 1 | .option-form { 2 | .blog-array-textarea { 3 | height: 10em; 4 | line-height: 1.8em; 5 | } 6 | 7 | .blog-ad-config { 8 | padding: 0; 9 | } 10 | } 11 | 12 | .auth-form { 13 | .auth-gravatar { 14 | padding: 0; 15 | display: inline-block; 16 | width: 200px; 17 | height: auto; 18 | min-height: 40px; 19 | background-color: transparent; 20 | border: none; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/pages/options/options.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 全局设置页面模块 3 | * @desc app/page/options/module 4 | * @author Surmon 5 | */ 6 | 7 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 10 | import { SaBaseModule } from '@app/sa-base.module'; 11 | import { OptionsComponent } from './options.component'; 12 | import { RoutingModule } from './options.routing'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | CommonModule, 17 | FormsModule, 18 | ReactiveFormsModule, 19 | SaBaseModule, 20 | RoutingModule 21 | ], 22 | declarations: [ 23 | OptionsComponent 24 | ], 25 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 26 | }) 27 | export class OptionsModule {} 28 | -------------------------------------------------------------------------------- /src/app/pages/options/options.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 全局设置页面路由 3 | * @desc app/page/options/routes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { OptionsComponent } from './options.component'; 9 | 10 | const routes: Routes = [ 11 | { path: '', component: OptionsComponent } 12 | ]; 13 | 14 | export const RoutingModule = RouterModule.forChild(routes); 15 | -------------------------------------------------------------------------------- /src/app/pages/pages.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 页面组件 3 | * @desc app/componnents-page 4 | * @author Surmon 5 | */ 6 | 7 | import { BLOG_SITE } from '@/config'; 8 | import { Component, ViewEncapsulation } from '@angular/core'; 9 | 10 | @Component({ 11 | selector: 'app-pages', 12 | encapsulation: ViewEncapsulation.Emulated, 13 | styles: [], 14 | template: ` 15 | 16 | 17 |
    18 |
    19 | 20 | 21 |
    22 | 23 |
    24 |
    25 | 29 | 32 |
    33 | ` 34 | }) 35 | export class PagesComponent { 36 | constructor() {} 37 | } 38 | -------------------------------------------------------------------------------- /src/app/pages/pages.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 页面数据的业务类型常量 3 | * @desc app/pages/constants 4 | * @author Surmon 5 | */ 6 | 7 | export type TApiPath = string; 8 | export type TSelectedIds = string[]; 9 | export type TSelectedAll = boolean; 10 | 11 | // 请求参数 12 | export interface IGetParams { 13 | [key: string]: number | string; 14 | } 15 | 16 | export interface IDataExtends { 17 | _id: string; 18 | name: string; 19 | value: string; 20 | } 21 | 22 | export interface IPagination { 23 | current_page: number; 24 | total_page: number; 25 | per_page: number; 26 | total: number; 27 | } 28 | 29 | // 数据体结构 30 | export interface IResponseData { 31 | data: T; 32 | } 33 | 34 | // 数据体结构 35 | export interface IResponsePaginationData { 36 | data: T[]; 37 | pagination?: IPagination; 38 | } 39 | -------------------------------------------------------------------------------- /src/app/pages/pages.module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 页面模块 3 | * @desc app/page 4 | * @author Surmon 5 | */ 6 | 7 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 8 | import { CommonModule } from '@angular/common'; 9 | 10 | import { PagesComponent } from './pages.component'; 11 | import { RoutingModule } from './pages.routing'; 12 | import { SaBaseModule } from '../sa-base.module'; 13 | 14 | @NgModule({ 15 | imports: [CommonModule, RoutingModule, SaBaseModule], 16 | declarations: [PagesComponent], 17 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 18 | }) 19 | export class PagesModule {} 20 | -------------------------------------------------------------------------------- /src/app/pages/pages.routing.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 页面路由 3 | * @desc app/page/routes 4 | * @author Surmon 5 | */ 6 | 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { PagesComponent } from './pages.component'; 9 | 10 | const routes: Routes = [ 11 | { path: 'auth', loadChildren: () => import('./auth').then(module => module.AuthModule) }, 12 | { path: '', 13 | component: PagesComponent, 14 | children: [ 15 | { path: '', redirectTo: 'dashboard', pathMatch: 'full' }, 16 | { path: 'dashboard', loadChildren: () => import('./dashboard').then(module => module.DashboardModule) }, 17 | { path: 'announcement', loadChildren: () => import('./announcement').then(module => module.AnnouncementModule) }, 18 | { path: 'article', loadChildren: () => import('./article').then(module => module.ArticleModule) }, 19 | { path: 'comment', loadChildren: () => import('./comment').then(module => module.CommentModule) }, 20 | { path: 'options', loadChildren: () => import('./options').then(module => module.OptionsModule) }, 21 | { path: 'auth', loadChildren: () => import('./auth').then(module => module.AuthModule) }, 22 | { path: 'example', loadChildren: () => import('./example').then(module => module.ExampleModule) } 23 | ] 24 | } 25 | ]; 26 | 27 | export const RoutingModule = RouterModule.forChild(routes); 28 | -------------------------------------------------------------------------------- /src/app/pages/pages.utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 页面公共扩展 3 | * @desc app/pages/utils 4 | * @author Surmon 5 | */ 6 | 7 | import { FormGroup, AbstractControl } from '@angular/forms'; 8 | import { TSelectedIds } from './pages.interface'; 9 | 10 | export type TLoadingName = number | string; 11 | 12 | interface ISelectChangeOptions { 13 | data: any[]; 14 | selectedIds: TSelectedIds; 15 | isSelect?: boolean; 16 | } 17 | 18 | interface IItemSelectChangeResult { 19 | selectedIds: TSelectedIds; 20 | all: boolean; 21 | } 22 | 23 | // 合并 form 到实例本身 24 | export const mergeFormControlsToInstance = (instance, form) => { 25 | if (form instanceof FormGroup) { 26 | Object.keys(form.controls).forEach(keyword => { 27 | instance[keyword] = form.controls[keyword]; 28 | }); 29 | } 30 | }; 31 | 32 | // 对批量操作进行更新操作 33 | export const handleBatchSelectChange = (options: ISelectChangeOptions): TSelectedIds => { 34 | const { data, isSelect } = options; 35 | if (!data.length) { 36 | return; 37 | } 38 | data.forEach(item => (item.selected = isSelect)); 39 | options.selectedIds = isSelect ? data.map(item => item._id) : []; 40 | return options.selectedIds; 41 | }; 42 | 43 | // 对单个勾选进行更新操作 44 | export const handleItemSelectChange = (options: ISelectChangeOptions): IItemSelectChangeResult => { 45 | const { data } = options; 46 | options.selectedIds = data.filter(item => item.selected).map(item => item._id); 47 | return { 48 | selectedIds: options.selectedIds, 49 | all: options.selectedIds.length === data.length 50 | }; 51 | }; 52 | 53 | // 表单验证 54 | export const formControlStateClass = (control: AbstractControl, errClassName?: string, isSubmited?: boolean): string => { 55 | if (control.touched || control.root.touched || control.dirty || control.root.dirty || isSubmited) { 56 | if (control.valid) { 57 | return 'has-success'; 58 | } else { 59 | return errClassName || 'has-error'; 60 | } 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /src/app/pipes/appPicture/appPicture.pipe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 获取图片管道符 3 | * @desc app/pipes/app-picture 4 | * @author Surmon 5 | */ 6 | 7 | import { Pipe, PipeTransform } from '@angular/core'; 8 | import { ASSETS_IMAGE } from '@app/constants/url'; 9 | 10 | @Pipe({ name: 'appPicture' }) 11 | export class AppPicturePipe implements PipeTransform { 12 | 13 | transform(input: string): string { 14 | return ASSETS_IMAGE + input; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/pipes/appPicture/index.ts: -------------------------------------------------------------------------------- 1 | export * from './appPicture.pipe'; 2 | -------------------------------------------------------------------------------- /src/app/pipes/dateHandle/dateHandle.pipe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 时间本地化处理管道符 3 | * @desc app/pipes/date-handle 4 | * @author Surmon 5 | */ 6 | 7 | import { Pipe, PipeTransform } from '@angular/core'; 8 | 9 | @Pipe({ name: 'dataToLocale' }) 10 | export class DataToLocalePipe implements PipeTransform { 11 | 12 | transform(input: any): string { 13 | const date = new Date(input); 14 | const ymd = date.toLocaleDateString().replace(/\//ig, '-'); 15 | const timeString = date.toLocaleTimeString(); 16 | const time = timeString.slice(0, timeString.length - 3); 17 | return `${ymd} ${time}`; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/pipes/dateHandle/index.ts: -------------------------------------------------------------------------------- 1 | export * from './dateHandle.pipe'; 2 | -------------------------------------------------------------------------------- /src/app/pipes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './truncate'; 2 | export * from './dateHandle'; 3 | export * from './appPicture'; 4 | -------------------------------------------------------------------------------- /src/app/pipes/truncate/index.ts: -------------------------------------------------------------------------------- 1 | export * from './truncate.pipe'; 2 | -------------------------------------------------------------------------------- /src/app/pipes/truncate/truncate.pipe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 字节超出隐藏管道符 3 | * @desc app/pipes/truncate 4 | * @author Surmon 5 | */ 6 | 7 | import { Pipe, PipeTransform } from '@angular/core'; 8 | 9 | @Pipe({ name: 'truncate' }) 10 | export class TruncatePipe implements PipeTransform { 11 | 12 | transform(value: string, limit: any = 10, trail: string = '...'): string { 13 | return value.length > limit ? value.substring(0, limit) + trail : value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/services/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saImageLoader'; 2 | export * from './saHttpLoading'; 3 | export * from './saHttpRequester'; 4 | export * from './saBootingSpinner'; 5 | export * from './saToken'; 6 | -------------------------------------------------------------------------------- /src/app/services/saBootingSpinner/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saBootingSpinner.service'; 2 | -------------------------------------------------------------------------------- /src/app/services/saBootingSpinner/saBootingSpinner.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 首屏启动器服务 3 | * @desc app/services/spinner 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | 9 | @Injectable() 10 | export class SaBootingSpinnerService { 11 | 12 | private element: HTMLElement; 13 | private selector: string = 'preloader'; 14 | 15 | constructor() { 16 | this.element = document.getElementById(this.selector); 17 | } 18 | 19 | public show(): void { 20 | this.element.style.display = 'block'; 21 | } 22 | 23 | public hide(delay: number = 0): void { 24 | setTimeout(() => { 25 | if (this.element) { 26 | this.element.style.display = 'none'; 27 | } 28 | }, delay); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/services/saHttpLoading/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saHttpLoading.service'; 2 | -------------------------------------------------------------------------------- /src/app/services/saHttpLoading/saHttpLoading.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Http loading service 3 | * @desc app/http-loading 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | 9 | export type Loading = boolean; 10 | export type LoadingName = string | number; 11 | 12 | @Injectable() 13 | export class SaHttpLoadingService { 14 | 15 | private loadings: Map = new Map(); 16 | 17 | constructor() {} 18 | 19 | get names(): IterableIterator { 20 | return this.loadings.keys(); 21 | } 22 | 23 | add(name: LoadingName): void { 24 | if (!this.loadings.has(name)) { 25 | this.loadings.set(name, false); 26 | } 27 | } 28 | 29 | start(name: LoadingName): void { 30 | this.loadings.set(name, true); 31 | } 32 | 33 | stop(name: LoadingName): void { 34 | this.loadings.set(name, false); 35 | } 36 | 37 | isLoading(name: LoadingName): boolean { 38 | return !!(this.loadings.has(name) && this.loadings.get(name)); 39 | } 40 | 41 | isFinished(name: LoadingName): boolean { 42 | return !this.isLoading(name); 43 | } 44 | 45 | isAllFinished(): boolean { 46 | const values = []; 47 | this.loadings.forEach(value => values.push(value)); 48 | return values.every(Boolean); 49 | } 50 | 51 | promise(name: LoadingName, promise: Promise): Promise { 52 | this.start(name); 53 | promise.finally(() => this.stop(name)); 54 | return promise; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/services/saHttpRequester/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saHttpRequester.service'; 2 | -------------------------------------------------------------------------------- /src/app/services/saImageLoader/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saImageLoader.service'; 2 | -------------------------------------------------------------------------------- /src/app/services/saImageLoader/saImageLoader.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 图片加载服务 3 | * @desc app/services/image-loader 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | 9 | @Injectable() 10 | export class SaImageLoaderService { 11 | 12 | public load(src: string): Promise { 13 | return new Promise((resolve, reject) => { 14 | const img = new Image(); 15 | const text = `Image with src ${src} loaded `; 16 | img.onload = () => { 17 | resolve(`${text} successfully.`); 18 | }; 19 | img.onerror = () => { 20 | reject(`${text} failed.`); 21 | }; 22 | img.src = src; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/services/saToken/index.ts: -------------------------------------------------------------------------------- 1 | export * from './saToken.service'; 2 | export * from './saRenewal.service'; 3 | -------------------------------------------------------------------------------- /src/app/services/saToken/saRenewal.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 图片加载服务 3 | * @desc app/services/image-loader 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | import { SaHttpRequesterService } from '../saHttpRequester'; 9 | import { SaTokenService } from './saToken.service'; 10 | import * as API_PATH from '@app/constants/api'; 11 | 12 | @Injectable() 13 | export class SaRenewalService { 14 | 15 | private renewalTask = null; 16 | 17 | constructor( 18 | private tokenService: SaTokenService, 19 | private httpService: SaHttpRequesterService 20 | ) {} 21 | 22 | public stop(): void { 23 | window.clearTimeout(this.renewalTask); 24 | } 25 | 26 | // 自动续约 token 27 | public autoRun(): void { 28 | this.stop(); 29 | const countdown = this.tokenService.getCountdown(); 30 | const seconds = countdown - 10; 31 | console.info(`Token 自动续约正在工作,Token 将在 ${seconds}s 后自动更新!`); 32 | this.renewalTask = window.setTimeout(() => { 33 | this.httpService.post(API_PATH.RENEWAL_TOKEN).then(auth => { 34 | this.tokenService.setOrReplaceToken( 35 | auth.result.access_token, 36 | auth.result.expires_in 37 | ); 38 | this.autoRun(); 39 | }); 40 | }, seconds * 1000); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/services/saToken/saToken.service.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 图片加载服务 3 | * @desc app/services/image-loader 4 | * @author Surmon 5 | */ 6 | 7 | import { Injectable } from '@angular/core'; 8 | import { TOKEN, TOKEN_EXPIRES_IN, TOKEN_BIRTH_TIME } from '@app/constants/auth'; 9 | 10 | @Injectable() 11 | export class SaTokenService { 12 | 13 | public getToken(): string { 14 | return localStorage.getItem(TOKEN); 15 | } 16 | 17 | public removeToken(): void { 18 | localStorage.removeItem(TOKEN); 19 | localStorage.removeItem(TOKEN_EXPIRES_IN); 20 | localStorage.removeItem(TOKEN_BIRTH_TIME); 21 | } 22 | 23 | public setOrReplaceToken(token: string, expires_in: number): void { 24 | localStorage.setItem(TOKEN, token); 25 | localStorage.setItem(TOKEN_EXPIRES_IN, String(expires_in)); 26 | localStorage.setItem(TOKEN_BIRTH_TIME, String(+new Date() / 1000)); 27 | } 28 | 29 | // 检查 token 的存在和有效性 30 | public isTokenValid(): boolean { 31 | const token: string = this.getToken(); 32 | const tokenIsOk = token && token.split('.').length === 3; 33 | return tokenIsOk; 34 | } 35 | 36 | // 获取 token 此刻至过期时间的时间(秒) 37 | public getCountdown(): number { 38 | const expiresIn = Number(localStorage.getItem(TOKEN_EXPIRES_IN)); 39 | const borthTime = Number(localStorage.getItem(TOKEN_BIRTH_TIME)); 40 | const deadLine = borthTime + expiresIn; 41 | const now = +new Date() / 1000; 42 | return deadLine > now 43 | ? Math.floor(deadLine - now) 44 | : 0; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/styles/app.scss: -------------------------------------------------------------------------------- 1 | // third 2 | @import "~normalize.css"; 3 | @import "~loaders.css"; 4 | @import "~animate.css/animate"; 5 | 6 | // init 7 | @import "./init"; 8 | 9 | // bootstrap & overrides 10 | @import "~bootstrap/scss/bootstrap.scss"; 11 | @import "bootstrap/card"; 12 | @import "bootstrap/tabs"; 13 | @import "bootstrap/button"; 14 | @import "bootstrap/dropdown"; 15 | @import "bootstrap/pagination"; 16 | 17 | // custom 18 | @import "app/typography"; 19 | @import "app/buttons"; 20 | @import "app/icons"; 21 | @import "app/layout"; 22 | @import "app/table"; 23 | @import "app/form"; 24 | @import "app/notifications"; 25 | @import "app/model"; 26 | @import "app/common"; 27 | -------------------------------------------------------------------------------- /src/app/styles/app/_common.scss: -------------------------------------------------------------------------------- 1 | 2 | #loading-bar-spinner { 3 | top: 13px!important; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/styles/app/_icons.scss: -------------------------------------------------------------------------------- 1 | @mixin svg-icon($url, $width:'', $height:'') { 2 | display: inline-block; 3 | background: url($url) no-repeat center; 4 | background-size: contain; 5 | vertical-align: middle; 6 | @if ($width != '') { 7 | width: $width + px; 8 | } 9 | @if ($height != '') { 10 | height: $height + px; 11 | } 12 | } 13 | 14 | @mixin svg-icon-class($iconName, $width:'', $height:'') { 15 | .#{'i-' + $iconName} { 16 | @include svg-icon($images-root + $iconName + '.svg', $width, $height); 17 | } 18 | } 19 | 20 | @include svg-icon-class('face', 80, 80); 21 | @include svg-icon-class('money', 80, 80); 22 | @include svg-icon-class('person', 80, 80); 23 | @include svg-icon-class('refresh', 80, 80); 24 | 25 | @mixin png-icon($url, $width, $height) { 26 | display: inline-block; 27 | width: $width + px; 28 | height: $height + px; 29 | background: url($url) no-repeat center center; 30 | background-size: $width + px $height + px; 31 | } 32 | 33 | @mixin png-icon-class($iconName, $width, $height) { 34 | .#{'i-' + $iconName} { 35 | @include png-icon($images-root + $iconName + '.png', $width, $height); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/styles/app/_model.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | // display: flex; 3 | align-items: center; 4 | background-color: rgba(0, 0, 0, 0.5); 5 | 6 | &.fade { 7 | 8 | .modal-dialog { 9 | animation-name: scaleOut; 10 | animation-duration: .15s; 11 | animation-timing-function: ease-in-out; 12 | transform: translate3d(-50%, -50%, 0); 13 | } 14 | 15 | &.in { 16 | 17 | .modal-dialog { 18 | animation-name: scaleIn; 19 | animation-duration: .15s; 20 | animation-timing-function: ease-in-out; 21 | transform: translate3d(-50%, -50%, 0); 22 | } 23 | } 24 | } 25 | 26 | .modal-dialog { 27 | min-width: 500px; 28 | margin: 0 auto; 29 | position: absolute; 30 | top: 50%; 31 | left: 50%; 32 | 33 | .modal-content { 34 | border: none; 35 | background-color: #eee; 36 | color: #373a3c; 37 | 38 | .modal-header { 39 | padding: 10px 15px; 40 | 41 | > .close { 42 | position: absolute; 43 | display: block; 44 | right: 0; 45 | top: 0; 46 | width: 48px; 47 | height: 48px; 48 | line-height: 48px; 49 | font-size: 36px; 50 | margin: 0; 51 | padding: 0; 52 | } 53 | } 54 | 55 | .modal-body { 56 | 57 | .message { 58 | margin: 10px auto; 59 | padding-left: 30px; 60 | font-size: 23px; 61 | height: 30px; 62 | line-height: 30px; 63 | 64 | .icon { 65 | float: left; 66 | font-size: 32px; 67 | margin-right: 10px; 68 | } 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | .modal-open { 76 | padding-right: 0.5em!important; 77 | } 78 | 79 | .fade { 80 | transition: opacity .15s ease-in-out; 81 | 82 | &.in { 83 | transition: opacity .15s ease-in-out; 84 | } 85 | } 86 | 87 | .modal-backdrop { 88 | display: none; 89 | } 90 | 91 | @keyframes scaleIn { 92 | from { 93 | transform: translate3d(-50%, -50%, 0) scale(1.2); 94 | opacity: 0; 95 | } 96 | to { 97 | transform: translate3d(-50%, -50%, 0) scale(1); 98 | opacity: 1; 99 | } 100 | } 101 | 102 | @keyframes scaleOut { 103 | from { 104 | transform: translate3d(-50%, -50%, 0) scale(1); 105 | opacity: 1; 106 | } 107 | to { 108 | transform: translate3d(-50%, -50%, 0) scale(0.8); 109 | opacity: 0; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/app/styles/app/_notifications.scss: -------------------------------------------------------------------------------- 1 | @import '../init'; 2 | 3 | .simple-notification { 4 | padding: 20px!important; 5 | 6 | svg { 7 | top: 10px!important; 8 | right: 10px!important; 9 | } 10 | 11 | .sn-title { 12 | margin-bottom: 10px!important; 13 | } 14 | 15 | &.error { 16 | background-color: $danger!important; 17 | 18 | .sn-progress-loader span { 19 | background-color: lighten($danger, 10%)!important; 20 | } 21 | } 22 | 23 | &.info { 24 | background-color: $info!important; 25 | 26 | .sn-progress-loader span { 27 | background-color: lighten($info, 10%)!important; 28 | } 29 | } 30 | 31 | &.bare { 32 | background-color: $primary!important; 33 | 34 | .sn-progress-loader span { 35 | background-color: lighten($primary, 10%)!important; 36 | } 37 | } 38 | 39 | &.alert { 40 | background-color: $warning!important; 41 | 42 | .sn-progress-loader span { 43 | background-color: lighten($warning, 10%)!important; 44 | } 45 | } 46 | 47 | &.success { 48 | background-color: $success!important; 49 | 50 | .sn-progress-loader span { 51 | background-color: lighten($success, 10%)!important; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/styles/app/_preloader.scss: -------------------------------------------------------------------------------- 1 | @import '../init'; 2 | 3 | @-webkit-keyframes spin { 4 | 0% { 5 | transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ 6 | } 7 | 100% { 8 | transform: rotate(360deg); /* Firefox 16+, IE 10+, Opera */ 9 | } 10 | } 11 | 12 | @-moz-keyframes spin { 13 | 0% { 14 | -moz-transform: rotate(0deg); /* Firefox 16+*/ 15 | } 16 | 100% { 17 | -moz-transform: rotate(360deg); /* Firefox 16+*/ 18 | } 19 | } 20 | 21 | @keyframes spin { 22 | 0% { 23 | transform: rotate(0deg); /* Firefox 16+, IE 10+, Opera */ 24 | } 25 | 100% { 26 | transform: rotate(360deg); /* Firefox 16+, IE 10+, Opera */ 27 | } 28 | } 29 | 30 | @keyframes bg-img-before { 31 | 0% { 32 | transform: translate3d(0%, 0%, 0); 33 | } 34 | 100% { 35 | transform: translate3d(0%, -100%, 0); 36 | } 37 | } 38 | 39 | @keyframes bg-img-after { 40 | 0% { 41 | transform: translate3d(0%, 0%, 0) scale(1, -1); 42 | } 43 | 100% { 44 | transform: translate3d(0%, -100%, 0) scale(1, -1); 45 | } 46 | } 47 | 48 | #preloader { 49 | position: fixed; 50 | top: 0; 51 | left: 0; 52 | width: 100%; 53 | height: 100%; 54 | z-index: 1003; 55 | background: $sidebar-sublist; 56 | 57 | & > div { 58 | display: block; 59 | position: relative; 60 | left: 50%; 61 | top: 50%; 62 | width: 150px; 63 | height: 150px; 64 | margin: -75px 0 0 -75px; 65 | border-radius: 50%; 66 | box-shadow: 0 5px 5px 0 $danger!important; 67 | transform: translate3d(0, 0, 0); 68 | animation: spin 2s linear infinite; /* Chrome, Firefox 16+, IE 10+, Opera */ 69 | 70 | &:before { 71 | content: ""; 72 | position: absolute; 73 | top: 5px; 74 | left: 5px; 75 | right: 5px; 76 | bottom: 5px; 77 | border-radius: 50%; 78 | box-shadow: 0 5px 5px 0 $primary!important; 79 | -webkit-animation: spin 3s linear infinite; /* Chrome, Opera 15+, Safari 5+ */ 80 | animation: spin 3s linear infinite; /* Chrome, Firefox 16+, IE 10+, Opera */ 81 | } 82 | 83 | &:after { 84 | content: ""; 85 | position: absolute; 86 | top: 15px; 87 | left: 15px; 88 | right: 15px; 89 | bottom: 15px; 90 | border-radius: 50%; 91 | box-shadow: 0 5px 5px 0 $warning!important; 92 | animation: spin 1.5s linear infinite; /* Chrome, Firefox 16+, IE 10+, Opera */ 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/app/styles/bootstrap/_button.scss: -------------------------------------------------------------------------------- 1 | button, 2 | .btn { 3 | cursor: pointer; 4 | } 5 | 6 | .btn-icon { 7 | display: inline-flex; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/styles/bootstrap/_dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown, 2 | .btn-group { 3 | 4 | &.open { 5 | 6 | > .dropdown-menu { 7 | display: block; 8 | } 9 | } 10 | 11 | .dropdown-menu { 12 | font-size: inherit; 13 | border-radius: .15rem; 14 | 15 | .dropdown-item { 16 | line-height: 1; 17 | padding: 0; 18 | 19 | > a { 20 | display: block; 21 | padding: 0 1rem; 22 | height: 2rem; 23 | line-height: 2rem; 24 | color: #373a3c; 25 | cursor: pointer; 26 | 27 | &:hover { 28 | color: white!important; 29 | background-color: $sidebar!important; 30 | } 31 | 32 | > .icon { 33 | margin-right: 10px; 34 | } 35 | } 36 | 37 | &:hover { 38 | // color: white; 39 | background-color: transparent; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/app/styles/bootstrap/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | justify-content: center; 3 | 4 | .page-item { 5 | &.disabled { 6 | cursor: no-drop; 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/app/styles/init.scss: -------------------------------------------------------------------------------- 1 | @import './mixins'; 2 | @import './variables'; 3 | -------------------------------------------------------------------------------- /src/app/transformers/gravatar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Gravatar 地址获取器 3 | * @author Surmon 4 | */ 5 | 6 | import * as gravatar from 'gravatar'; 7 | import { GRAVATAR_API } from '@/config'; 8 | 9 | export const getGravatar = (email: string): string => { 10 | const gravatar_url = gravatar.url(email, { protocol: 'https' }); 11 | return gravatar_url.replace('https://s.gravatar.com/avatar', GRAVATAR_API); 12 | }; 13 | -------------------------------------------------------------------------------- /src/app/transformers/link.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 链接转换 3 | * @author Surmon 4 | */ 5 | 6 | import { BLOG_HOST } from '@/config'; 7 | 8 | export const getTagPath = (tagSlug: string): string => `${BLOG_HOST}/tag/${tagSlug}`; 9 | 10 | export const getCategoryPath = (categorySlug: string): string => `${BLOG_HOST}/category/${categorySlug}`; 11 | 12 | export const getArticlePath = (articleId: string | number): string => `${BLOG_HOST}/article/${articleId}`; 13 | 14 | export const getGuestbookPath = (): string => `${BLOG_HOST}/guestbook`; 15 | -------------------------------------------------------------------------------- /src/app/transformers/ua.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UA 解析器 3 | * @author Surmon 4 | */ 5 | 6 | import { UAParser } from 'ua-parser-js'; 7 | 8 | const parser = new UAParser(); 9 | 10 | // 浏览器解析 11 | export const browserParser = (ua: string): string => { 12 | parser.setUA(ua); 13 | const result = parser.getBrowser(); 14 | if (!result.name && !result.version) { 15 | return ua; 16 | } else { 17 | return `${result.name || '未知'} | ${result.version || '未知'}`; 18 | } 19 | }; 20 | 21 | // OS 解析 22 | export const osParser = (ua: string): string => { 23 | parser.setUA(ua); 24 | const result = parser.getOS(); 25 | if (!result.name && !result.version) { 26 | return ua; 27 | } else { 28 | return `${result.name || '未知'} | ${result.version || '未知'}`; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/app/validators/email.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 邮件验证器 3 | * @desc app/validators/email 4 | * @author Surmon 5 | */ 6 | 7 | import { AbstractControl } from '@angular/forms'; 8 | 9 | export class EmailValidator { 10 | 11 | public static validate(c: AbstractControl) { 12 | const EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 13 | return EMAIL_REGEXP.test(c.value) ? null : { 14 | validateEmail: { 15 | valid: false 16 | } 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app/validators/equalPasswords.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 密码对比验证器 3 | * @desc app/validators/EqualPasswords 4 | * @author Surmon 5 | */ 6 | 7 | import { FormGroup } from '@angular/forms'; 8 | 9 | export class EqualPasswordsValidator { 10 | 11 | public static validate(firstField, secondField) { 12 | return (context: FormGroup) => ( 13 | context.controls && 14 | context.controls[firstField].value === context.controls[secondField].value 15 | ) ? null : { 16 | passwordsEqual: { 17 | valid: false 18 | } 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/validators/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 验证器 3 | * @desc app/validators 4 | * @author Surmon 5 | */ 6 | 7 | export * from './email'; 8 | export * from './equalPasswords'; 9 | -------------------------------------------------------------------------------- /src/assets/fonts/DIN-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/fonts/DIN-Regular.otf -------------------------------------------------------------------------------- /src/assets/fonts/DIN-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/fonts/DIN-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/socicon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/fonts/socicon.eot -------------------------------------------------------------------------------- /src/assets/fonts/socicon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/fonts/socicon.ttf -------------------------------------------------------------------------------- /src/assets/fonts/socicon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/fonts/socicon.woff -------------------------------------------------------------------------------- /src/assets/fonts/socicon.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/fonts/socicon.woff2 -------------------------------------------------------------------------------- /src/assets/images/browsers/chrome.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/browsers/firefox.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/browsers/ie.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/browsers/opera.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/browsers/safari.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/icons/arrow-green-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/images/icons/arrow-red-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/assets/images/icons/check-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/icons/check-icon.png -------------------------------------------------------------------------------- /src/assets/images/profile/article-thumb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/profile/article-thumb.jpg -------------------------------------------------------------------------------- /src/assets/images/profile/logo-smooth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/profile/logo-smooth.png -------------------------------------------------------------------------------- /src/assets/images/profile/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/profile/logo.png -------------------------------------------------------------------------------- /src/assets/images/profile/no-photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/profile/no-photo.png -------------------------------------------------------------------------------- /src/assets/images/typography/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/banner.png -------------------------------------------------------------------------------- /src/assets/images/typography/typo01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/typo01.png -------------------------------------------------------------------------------- /src/assets/images/typography/typo03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/typo03.png -------------------------------------------------------------------------------- /src/assets/images/typography/typo04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/typo04.png -------------------------------------------------------------------------------- /src/assets/images/typography/typo05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/typo05.png -------------------------------------------------------------------------------- /src/assets/images/typography/typo06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/typo06.png -------------------------------------------------------------------------------- /src/assets/images/typography/typo07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/assets/images/typography/typo07.png -------------------------------------------------------------------------------- /src/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 全局配置 3 | * @author Surmon 4 | */ 5 | 6 | export const APP_TITLE = 'Surmon.me'; 7 | export const BLOG_HOST = '//surmon.me'; 8 | export const BLOG_SITE = `https:${BLOG_HOST}`; 9 | export const STATIC_URL = 'https://static.surmon.me'; 10 | export const GRAVATAR_API = `${STATIC_URL}/avatar`; 11 | export const ALIYUN_OSS_REGION = 'oss-cn-hangzhou'; 12 | export const ALIYUN_OSS_BUCKET = 'surmon-static'; 13 | 14 | export const DEVELOP_API = { 15 | API_ROOT: '/api', 16 | STATIC_URL, 17 | }; 18 | 19 | export const PRODCTION_API = { 20 | API_ROOT: 'https://api.surmon.me', 21 | STATIC_URL 22 | }; 23 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 生产环境配置 3 | * @author Surmon 4 | */ 5 | 6 | import { PRODCTION_API } from '@/config'; 7 | 8 | export const api = PRODCTION_API; 9 | export const environment = { 10 | production: true, 11 | development: false 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 开发环境配置 3 | * @author Surmon 4 | */ 5 | 6 | import { DEVELOP_API } from '@/config'; 7 | 8 | export const api = DEVELOP_API; 9 | export const environment = { 10 | production: false, 11 | development: true 12 | }; 13 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surmon-china/angular-admin/d1f37638ea1a1475983f90a71ed10c584542a1fe/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Surmon.me - Admin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 |
    25 |
    26 |
    27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 主程序入口文件 3 | * @desc main 4 | * @author Surmon 5 | */ 6 | 7 | import { enableProdMode } from '@angular/core'; 8 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 9 | 10 | import { AppModule } from './app/app.module'; 11 | import { environment, api } from '@/environments/environment'; 12 | 13 | if (environment.production) { 14 | enableProdMode(); 15 | } 16 | 17 | console.info('系统启动中...'); 18 | 19 | platformBrowserDynamic().bootstrapModule(AppModule).then(() => { 20 | console.info(`系统启动成功!当前运行环境是:${environment.production ? '生产' : '开发'}环境,API 为:`, api); 21 | }).catch(error => { 22 | console.warn('系统启动失败!', error); 23 | }); 24 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | // preloader 4 | @import 'app/styles/app/preloader'; 5 | 6 | // app 7 | @import 'app/styles/app'; 8 | 9 | // editor 10 | @import '~highlight.js/styles/ocean.css'; 11 | @import '~codemirror/lib/codemirror.css'; 12 | @import '~codemirror/theme/base16-dark.css'; 13 | @import '~codemirror/addon/fold/foldgutter.css'; 14 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ], 14 | "exclude": [ 15 | "src/test.ts", 16 | "src/**/*.spec.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "importHelpers": true, 14 | "target": "es2015", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ], 22 | "paths": { 23 | "@/*": ["*"], 24 | "~@/*": ["*"], 25 | "app/*": ["app/*"], 26 | "@app/*": ["app/*"], 27 | "~app/*": ["app/*"], 28 | "@assets/*": ["assets/*"], 29 | "@env/*": ["environments/*"] 30 | } 31 | }, 32 | "angularCompilerOptions": { 33 | "fullTemplateTypeCheck": true, 34 | "strictInjectionParameters": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------