├── .babelrc ├── gulpfile.babel.js ├── src ├── components │ ├── item-content │ │ ├── template.jade │ │ └── index.js │ ├── popover-lang │ │ ├── style.scss │ │ ├── template.jade │ │ └── index.js │ ├── item-content-link │ │ ├── template.jade │ │ └── index.js │ ├── page │ │ ├── template.jade │ │ └── index.js │ ├── popover-order │ │ ├── style.scss │ │ ├── template.jade │ │ └── index.js │ ├── calendar-rang │ │ ├── template.jade │ │ └── index.js │ ├── bill-item │ │ ├── style.scss │ │ ├── template.jade │ │ └── index.js │ ├── item-content-accordion │ │ ├── template.jade │ │ └── index.js │ ├── search-bar │ │ ├── style.scss │ │ ├── template.jade │ │ └── index.js │ ├── navbar │ │ ├── style.scss │ │ ├── template.jade │ │ └── index.js │ ├── icon │ │ ├── template.jade │ │ ├── style.scss │ │ └── index.js │ ├── card │ │ ├── template.jade │ │ └── index.js │ ├── container-item │ │ ├── style.scss │ │ ├── template.jade │ │ └── index.js │ ├── bill-info-item-table │ │ ├── index.js │ │ └── template.jade │ ├── operation-item │ │ ├── index.js │ │ └── template.jade │ ├── container-item-table │ │ ├── index.js │ │ └── template.jade │ ├── bill-info-item │ │ ├── template.jade │ │ └── index.js │ └── panel-left │ │ ├── template.jade │ │ ├── style.scss │ │ ├── index.js │ │ └── menu.js ├── modules │ ├── ios.js │ ├── android.js │ ├── app │ │ ├── template.jade │ │ └── index.js │ ├── static.js │ ├── icons.js │ ├── commons.js │ └── main.js ├── pages │ ├── 404 │ │ ├── template.jade │ │ ├── index.js │ │ └── style.scss │ ├── logout │ │ ├── i18n.js │ │ └── index.js │ ├── contacts │ │ ├── i18n.js │ │ ├── dryTerminal.js │ │ ├── template.jade │ │ ├── index.js │ │ └── seaTerminal.js │ ├── bills │ │ ├── index.js │ │ ├── index │ │ │ ├── style.scss │ │ │ ├── template.jade │ │ │ └── index.js │ │ ├── info │ │ │ ├── style.scss │ │ │ ├── template.jade │ │ │ └── index.js │ │ └── i18n.js │ ├── profile │ │ ├── template.jade │ │ ├── index │ │ │ ├── template.jade │ │ │ └── index.js │ │ ├── edit │ │ │ ├── template.jade │ │ │ └── index.js │ │ ├── index.js │ │ └── i18n.js │ ├── settings │ │ ├── template.jade │ │ ├── i18n.js │ │ ├── index │ │ │ ├── index.js │ │ │ └── template.jade │ │ ├── edit │ │ │ ├── index.js │ │ │ └── template.jade │ │ └── index.js │ ├── containers │ │ ├── index.js │ │ ├── index │ │ │ └── style.scss │ │ └── info │ │ │ ├── style.scss │ │ │ └── template.jade │ ├── news │ │ ├── i18n.js │ │ ├── index.js │ │ ├── item │ │ │ ├── style.scss │ │ │ ├── template.jade │ │ │ └── index.js │ │ └── index │ │ │ ├── index.js │ │ │ ├── template.jade │ │ │ └── style.scss │ ├── login │ │ ├── style.scss │ │ ├── i18n.js │ │ ├── template.jade │ │ └── index.js │ ├── about │ │ ├── index.js │ │ ├── i18n.js │ │ ├── style.scss │ │ └── template.jade │ ├── index │ │ ├── i18n.js │ │ ├── template.jade │ │ ├── index.js │ │ └── style.scss │ ├── feedback │ │ ├── i18n.js │ │ ├── index.js │ │ └── template.jade │ ├── tabs-swipeable │ │ ├── toolbar.jade │ │ └── template.jade │ └── advance │ │ ├── i18n.js │ │ ├── index.js │ │ └── template.jade ├── images │ ├── favicon │ │ ├── red │ │ │ ├── 256x256.png │ │ │ ├── 512x512.png │ │ │ ├── icon-128x128.png │ │ │ ├── android-icon-36x36.png │ │ │ ├── android-icon-48x48.png │ │ │ ├── android-icon-72x72.png │ │ │ ├── android-icon-96x96.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-icon-144x144.png │ │ │ ├── android-icon-192x192.png │ │ │ ├── chrome-touch-icon-192x192.png │ │ │ └── ms-touch-icon-144x144-precomposed.png │ │ └── white │ │ │ ├── 256x256.png │ │ │ ├── 512x512.png │ │ │ ├── icon-128x128.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-icon-36x36.png │ │ │ ├── android-icon-48x48.png │ │ │ ├── android-icon-72x72.png │ │ │ ├── android-icon-96x96.png │ │ │ ├── _android-icon-144x144.png │ │ │ ├── _android-icon-192x192.png │ │ │ ├── android-icon-144x144-.png │ │ │ ├── android-icon-144x144.png │ │ │ ├── android-icon-192x192-.png │ │ │ ├── android-icon-192x192.png │ │ │ ├── chrome-touch-icon-192x192.png.png │ │ │ └── ms-touch-icon-144x144-precomposed.png │ ├── icons-f7 │ │ ├── ios-back.svg │ │ ├── ios-forward.svg │ │ ├── android-bars.svg │ │ ├── ios-next.svg │ │ ├── ios-prev.svg │ │ ├── android-back.svg │ │ ├── icon-check.svg │ │ ├── icon-checked.svg │ │ ├── android-forward.svg │ │ ├── swiper-button-prev.svg │ │ ├── ios-pull-to-refresh-arrow.svg │ │ ├── swiper-button-next.svg │ │ ├── icon-shevron-next.svg │ │ ├── ios-bars.svg │ │ ├── swiper-button-next_blue.svg │ │ ├── swiper-button-prev_blue.svg │ │ ├── icon-plus.svg │ │ ├── android-prev.svg │ │ ├── android-pull-to-refresh-arrow.svg │ │ ├── ios-bars_small.svg │ │ ├── sortable-handler.svg │ │ ├── android-next.svg │ │ ├── icon-shevron-open_right.svg │ │ ├── icon-shevron-prev.svg │ │ ├── icon-shevron-open_left.svg │ │ ├── android-close.svg │ │ ├── ios-search.svg │ │ ├── android-cancel.svg │ │ ├── ios-cancel.svg │ │ ├── android-camera.svg │ │ ├── android-search.svg │ │ ├── ios-close-outline.svg │ │ ├── ios-camera.svg │ │ ├── ios-preloader.svg │ │ └── ios-preloader_white.svg │ ├── icons-ionic │ │ ├── paper-airplane.svg │ │ ├── android-create.svg │ │ ├── contacts.svg │ │ ├── information-circled.svg │ │ ├── sort.svg │ │ ├── android-star-outline.svg │ │ ├── android-locate.svg │ │ ├── android-call.svg │ │ ├── clip.svg │ │ ├── calendar.svg │ │ ├── logout.svg │ │ ├── android-list.svg │ │ ├── email.svg │ │ ├── code-working.svg │ │ ├── android-settings.svg │ │ └── bug.svg │ ├── operations │ │ ├── seal.svg │ │ ├── supply.svg │ │ ├── expense.svg │ │ ├── expense-1.svg │ │ ├── arrival.svg │ │ ├── lathing.svg │ │ ├── arrival-1.svg │ │ ├── disconnection.svg │ │ ├── delivery.svg │ │ ├── MIDK.svg │ │ ├── inspection-2.svg │ │ ├── inspection-1.svg │ │ ├── connection.svg │ │ ├── weighing.svg │ │ ├── nomination.svg │ │ ├── unstuffing.svg │ │ └── stuffing.svg │ ├── icons-vsct │ │ ├── logo-emblem.svg │ │ ├── favicon.svg │ │ ├── favicon-white.svg │ │ ├── operations.svg │ │ ├── containers.svg │ │ ├── bills.svg │ │ ├── size.svg │ │ ├── bills-1.svg │ │ ├── types.svg │ │ ├── profile.svg │ │ ├── cargo-2.svg │ │ └── cargo.svg │ ├── icons-vsct.js │ ├── containers.js │ ├── containers │ │ ├── FL.svg │ │ ├── HC.svg │ │ └── ST.svg │ └── icons-ionic.js ├── initials │ ├── index.js │ ├── components.js │ ├── router.js │ └── F7.js ├── mixins │ ├── filters │ │ ├── formatSumm.js │ │ ├── stock.js │ │ └── date.js │ └── global.js ├── styles │ ├── transition-page.scss │ ├── main.scss │ ├── table-shadow.scss │ └── vars.scss ├── services │ ├── fetch │ │ ├── auth.js │ │ ├── index.js │ │ ├── bill-list.js │ │ ├── settings.js │ │ ├── bill-info.js │ │ ├── news.js │ │ └── advance.js │ ├── CacheItem.js │ ├── Storage.js │ └── CacheList.js ├── partials │ └── item-content.jade ├── vuex │ ├── modules │ │ ├── progress.js │ │ ├── order.js │ │ ├── profile.js │ │ ├── news.js │ │ ├── advance.js │ │ ├── locales.js │ │ ├── bill.js │ │ ├── container.js │ │ └── settings.js │ ├── store.js │ ├── mutation-types.js │ └── actions.js ├── fixtures │ ├── utils.js │ ├── settings.js │ ├── container-info.js │ ├── bill-list.js │ ├── auth.js │ ├── advance.js │ └── bill-info.js └── templates │ └── index.jade ├── gulp ├── tasks │ ├── build.js │ ├── default.js │ ├── clean.js │ ├── connect.js │ └── webpack.js ├── helpers │ ├── revision.js │ ├── errorHandler.js │ └── getArg.js └── config.js ├── .gitignore ├── bower.json ├── README.md └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /gulpfile.babel.js: -------------------------------------------------------------------------------- 1 | require('require-dir')('./gulp/tasks', { recurse: true }) 2 | -------------------------------------------------------------------------------- /src/components/item-content/template.jade: -------------------------------------------------------------------------------- 1 | .item-content 2 | partial(name='partial') 3 | -------------------------------------------------------------------------------- /src/components/popover-lang/style.scss: -------------------------------------------------------------------------------- 1 | :local div.popover { 2 | width: 100px; 3 | } 4 | -------------------------------------------------------------------------------- /gulp/tasks/build.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | 3 | gulp.task('build', ['clean', 'webpack']) 4 | -------------------------------------------------------------------------------- /src/modules/ios.js: -------------------------------------------------------------------------------- 1 | // hack for webpack extruct plugin 2 | 3 | import 'styles/ios.less' 4 | -------------------------------------------------------------------------------- /src/modules/android.js: -------------------------------------------------------------------------------- 1 | // hack for webpack extruct plugin 2 | 3 | import 'styles/android.less' 4 | -------------------------------------------------------------------------------- /src/components/item-content-link/template.jade: -------------------------------------------------------------------------------- 1 | a.item-content.item-link 2 | partial(name='partial') 3 | -------------------------------------------------------------------------------- /gulp/tasks/default.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | 3 | gulp.task('default', ['clean', 'webpack', 'connect']) 4 | -------------------------------------------------------------------------------- /src/components/page/template.jade: -------------------------------------------------------------------------------- 1 | .page( 2 | :data-page='name', 3 | :transition='transition') 4 | slot 5 | -------------------------------------------------------------------------------- /src/components/popover-order/style.scss: -------------------------------------------------------------------------------- 1 | :local div.popover { 2 | // width: auto; 3 | width: 160px; 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/logout/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { title: 'Logout' } 2 | var ru = { title: 'Выйти' } 3 | export default {en, ru} 4 | -------------------------------------------------------------------------------- /src/components/calendar-rang/template.jade: -------------------------------------------------------------------------------- 1 | input( 2 | v-el:calendar, 3 | type='text', 4 | style='display: none') 5 | -------------------------------------------------------------------------------- /src/images/favicon/red/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/256x256.png -------------------------------------------------------------------------------- /src/images/favicon/red/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/512x512.png -------------------------------------------------------------------------------- /src/images/favicon/white/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/256x256.png -------------------------------------------------------------------------------- /src/images/favicon/white/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/512x512.png -------------------------------------------------------------------------------- /src/pages/contacts/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { title: 'Contacts' } 2 | var ru = { title: 'Контакты' } 3 | export default { en, ru } 4 | -------------------------------------------------------------------------------- /src/images/favicon/red/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/icon-128x128.png -------------------------------------------------------------------------------- /src/images/favicon/white/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/icon-128x128.png -------------------------------------------------------------------------------- /src/images/favicon/red/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/android-icon-36x36.png -------------------------------------------------------------------------------- /src/images/favicon/red/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/android-icon-48x48.png -------------------------------------------------------------------------------- /src/images/favicon/red/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/android-icon-72x72.png -------------------------------------------------------------------------------- /src/images/favicon/red/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/android-icon-96x96.png -------------------------------------------------------------------------------- /src/images/favicon/red/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/apple-touch-icon.png -------------------------------------------------------------------------------- /src/images/favicon/white/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/apple-touch-icon.png -------------------------------------------------------------------------------- /src/pages/bills/index.js: -------------------------------------------------------------------------------- 1 | var name = 'bills' 2 | 3 | export default { 4 | name, 5 | template: '', 6 | } 7 | -------------------------------------------------------------------------------- /src/images/favicon/red/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/android-icon-144x144.png -------------------------------------------------------------------------------- /src/images/favicon/red/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/android-icon-192x192.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-36x36.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-48x48.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-72x72.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-96x96.png -------------------------------------------------------------------------------- /src/initials/index.js: -------------------------------------------------------------------------------- 1 | import 'initials/F7' 2 | import 'initials/components' 3 | import router from 'initials/router' 4 | 5 | export { router } 6 | -------------------------------------------------------------------------------- /src/pages/profile/template.jade: -------------------------------------------------------------------------------- 1 | a.floating-button(href='#' 2 | @click.stop.prevent='action') 3 | icon(:id='button') 4 | 5 | router-view 6 | -------------------------------------------------------------------------------- /src/pages/settings/template.jade: -------------------------------------------------------------------------------- 1 | a.floating-button(href='#' 2 | @click.stop.prevent='action') 3 | icon(:id='button') 4 | 5 | router-view 6 | -------------------------------------------------------------------------------- /src/images/favicon/white/_android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/_android-icon-144x144.png -------------------------------------------------------------------------------- /src/images/favicon/white/_android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/_android-icon-192x192.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-144x144-.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-144x144-.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-144x144.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-192x192-.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-192x192-.png -------------------------------------------------------------------------------- /src/images/favicon/white/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/android-icon-192x192.png -------------------------------------------------------------------------------- /src/images/icons-f7/ios-back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/images/favicon/red/chrome-touch-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/chrome-touch-icon-192x192.png -------------------------------------------------------------------------------- /src/pages/containers/index.js: -------------------------------------------------------------------------------- 1 | var name = 'containers' 2 | 3 | export default { 4 | name: name, 5 | template: '', 6 | } 7 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/bill-item/style.scss: -------------------------------------------------------------------------------- 1 | :local .link { 2 | background-color: rgb(160,160,160); 3 | 4 | :global .item-title { 5 | color: white; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/item-content-accordion/template.jade: -------------------------------------------------------------------------------- 1 | a.item-content(href='#').item-link 2 | partial(name='partial') 3 | .accordion-item-content 4 | slot(name='accordion') 5 | -------------------------------------------------------------------------------- /src/images/favicon/red/ms-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/red/ms-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /src/images/favicon/white/chrome-touch-icon-192x192.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/chrome-touch-icon-192x192.png.png -------------------------------------------------------------------------------- /src/images/icons-f7/android-bars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/containers/index/style.scss: -------------------------------------------------------------------------------- 1 | :local .link { 2 | background-color: rgb(160,160,160); 3 | 4 | :global .item-title { 5 | color: white; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/images/favicon/white/ms-touch-icon-144x144-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyllo/Framework7-VueJS/HEAD/src/images/favicon/white/ms-touch-icon-144x144-precomposed.png -------------------------------------------------------------------------------- /src/images/icons-f7/ios-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-back.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/components/search-bar/style.scss: -------------------------------------------------------------------------------- 1 | .searchbar .searchbar-search { 2 | width: 24px; 3 | height: 100%; 4 | display: inline-block; 5 | position: absolute; 6 | top: 0; 7 | } 8 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-checked.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | bower_components/ 5 | node_modules/ 6 | platforms/ 7 | plugins/ 8 | 9 | www/ -------------------------------------------------------------------------------- /src/images/icons-f7/android-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/swiper-button-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-pull-to-refresh-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/swiper-button-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/mixins/filters/formatSumm.js: -------------------------------------------------------------------------------- 1 | export default { 2 | filters: { 3 | formatSumm(value) { 4 | return (value + '').replace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1 ') 5 | }, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/components/navbar/style.scss: -------------------------------------------------------------------------------- 1 | 2 | :local div.progressbar { 3 | position: absolute; 4 | bottom: 0; 5 | margin-bottom: -4px; 6 | } 7 | 8 | :local .center { 9 | width: 100%; 10 | } 11 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-shevron-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-bars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/swiper-button-next_blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/swiper-button-prev_blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/news/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'News', 3 | read: 'Read more', 4 | } 5 | 6 | var ru = { 7 | title: 'Новости', 8 | read: 'Читать', 9 | } 10 | 11 | export default {en, ru} 12 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-pull-to-refresh-arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-bars_small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/sortable-handler.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-next.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/login/style.scss: -------------------------------------------------------------------------------- 1 | :local .cancel { 2 | composes: button button-fill button-big color-red from global; 3 | } 4 | 5 | :local .login { 6 | composes: button button-fill button-big color-green from global; 7 | width: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-shevron-open_right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-shevron-prev.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/bills/index/style.scss: -------------------------------------------------------------------------------- 1 | :local .title { 2 | :global(div.item-title-row) { 3 | padding-right: 26px; 4 | } 5 | 6 | :global(div.item-text) { 7 | max-height: none; 8 | -webkit-line-clamp: 40; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pages/containers/info/style.scss: -------------------------------------------------------------------------------- 1 | :local .title { 2 | :global(div.item-title-row) { 3 | padding-right: 26px; 4 | } 5 | 6 | :global(div.item-text) { 7 | max-height: none; 8 | -webkit-line-clamp: 40; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/icon/template.jade: -------------------------------------------------------------------------------- 1 | svg( 2 | xmlns='http://www.w3.org/2000/svg', 3 | xmlns:xlink='http://www.w3.org/1999/xlink', 4 | :viewBox.once='viewBox', 5 | class='#{style.icon} {{* class }}', 6 | :style.once='style') 7 | use(:xlink:href='id') 8 | -------------------------------------------------------------------------------- /src/images/icons-f7/icon-shevron-open_left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/modules/app/template.jade: -------------------------------------------------------------------------------- 1 | .statusbar-overlay 2 | .searchbar-overlay 3 | .panel-overlay 4 | 5 | panel-left 6 | popover-lang 7 | popover-order 8 | 9 | .views 10 | .view.view-main(v-el:view) 11 | .pages 12 | router-view 13 | -------------------------------------------------------------------------------- /src/components/card/template.jade: -------------------------------------------------------------------------------- 1 | .card 2 | .card-header 3 | slot(name='header') {{* header }} 4 | .card-content 5 | .card-content-inner 6 | slot(name='content') {{* content }} 7 | .card-footer 8 | slot(name='footer') {{* footer }} 9 | -------------------------------------------------------------------------------- /src/modules/static.js: -------------------------------------------------------------------------------- 1 | // hack for webpack - require favicon, index.html, manifest.json 2 | 3 | import 'templates/index.jade' 4 | import '!file?name=[name].[ext]!manifest.json' 5 | 6 | require.context('!file?name=assets/[path][name].[ext]!images/favicon/') 7 | -------------------------------------------------------------------------------- /src/components/card/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | 3 | export let name = 'card' 4 | 5 | export default { 6 | props: [ 7 | 'header', 8 | 'content', 9 | 'footer', 10 | ], 11 | template: template(), 12 | } 13 | -------------------------------------------------------------------------------- /src/components/container-item/style.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | 3 | :local .icon { 4 | height: 45px !important; 5 | width: 90px !important; 6 | } 7 | 8 | .badge.badge-clear { 9 | color: inherit; 10 | background-color: transparent; 11 | } 12 | -------------------------------------------------------------------------------- /src/components/page/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | 3 | export let name = 'page' 4 | 5 | export default { 6 | props: { 7 | name: { required: true }, 8 | transition: { default: 'page' }, 9 | }, 10 | template: template(), 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/404/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name class=style.page).navbar-fixed 2 | Navbar 3 | .page-content 4 | .content-block 5 | icon(id='dry_terminal', class=style.notFound) 6 | h1(class=style.title) 404 7 | h2(class=style.content) not found 8 | -------------------------------------------------------------------------------- /gulp/tasks/clean.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import del from 'del' 3 | 4 | import config from '../config' 5 | 6 | gulp.task('clean', cb => { 7 | try { 8 | del.sync(config.dest) 9 | } catch (e) { 10 | console.log('%s do not clean', config.dest) 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/mixins/filters/stock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Переназываем склады 3 | */ 4 | 5 | export default { 6 | filters: { 7 | stock(stock) { 8 | return (stock === 'ПИК') 9 | ? this.$t('containers.PIK') 10 | : this.$t('containers.VSCT') 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /gulp/helpers/revision.js: -------------------------------------------------------------------------------- 1 | import git from 'git-rev-sync' 2 | 3 | var revision = { 4 | hash: git.short(), 5 | branch: git.branch(), 6 | count: git.count(), 7 | tag: git.tag(), 8 | version: `git-${git.short()}-${git.count()} (${git.branch()})` 9 | } 10 | 11 | export default revision 12 | -------------------------------------------------------------------------------- /src/images/icons-ionic/paper-airplane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /src/pages/404/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | import style from './style.scss' 3 | 4 | var name = 'notFound' 5 | 6 | export default { 7 | name, 8 | template: template({ name, style }), 9 | methods: { 10 | back: () => window.history.back(), 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/images/icons-ionic/android-create.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gulp/helpers/errorHandler.js: -------------------------------------------------------------------------------- 1 | import plumber from 'gulp-plumber' 2 | import notify from 'gulp-notify' 3 | 4 | export default () => { 5 | return plumber({ 6 | errorHandler: notify.onError({ 7 | title: '<%= error.name %>', 8 | message: '<%= error.message %>', 9 | sound: true, 10 | }) 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/bills/info/style.scss: -------------------------------------------------------------------------------- 1 | :local .title { 2 | :global(div.item-title-row) { 3 | padding-right: 26px; 4 | } 5 | 6 | :global(div.item-text) { 7 | max-height: none; 8 | -webkit-line-clamp: 40; 9 | } 10 | } 11 | 12 | :local div.total { 13 | color: #FFF; 14 | background-color: #a0a0a0; 15 | } -------------------------------------------------------------------------------- /src/pages/news/index.js: -------------------------------------------------------------------------------- 1 | import { updateNews } from 'vuex/actions' 2 | 3 | var name = 'news' 4 | 5 | export default { 6 | name: name, 7 | template: '', 8 | 9 | vuex: { 10 | actions: { updateNews }, 11 | }, 12 | 13 | created() { 14 | this.updateNews() 15 | }, 16 | } 17 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/profile/index/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='profile.title') 3 | 4 | .page-content 5 | .list-block.media-list.clear-both 6 | ul: li(v-for='(key, title) in profile') 7 | item-content( 8 | :title='toProfileName(key)', 9 | :text.once='title') 10 | -------------------------------------------------------------------------------- /src/components/popover-order/template.jade: -------------------------------------------------------------------------------- 1 | .popover(class=style.popover, v-el:popover) 2 | .popover-angle 3 | .popover-inner 4 | .list-block 5 | ul: li(v-for='(key, value) in sortList') 6 | a.list-button.item-link( 7 | :href="'#' + key", 8 | @click="changeOrder(key)") 9 | | {{* value }} 10 | -------------------------------------------------------------------------------- /src/components/popover-lang/template.jade: -------------------------------------------------------------------------------- 1 | .popover(class=style.popover, v-el:popover) 2 | .popover-angle 3 | .popover-inner 4 | .list-block 5 | ul: li(v-for='lang in langs') 6 | a.list-button.item-link( 7 | :href="'#' + lang.key", 8 | @click="changeLang(lang.key)") 9 | | {{* lang.value }} 10 | -------------------------------------------------------------------------------- /src/pages/logout/index.js: -------------------------------------------------------------------------------- 1 | import { logout } from 'vuex/actions' 2 | 3 | var name = 'logout' 4 | 5 | export default { 6 | name, 7 | 8 | route: { 9 | activate({ redirect }) { 10 | this.logout() 11 | redirect({name: 'index'}) 12 | }, 13 | }, 14 | 15 | vuex: { 16 | actions: { logout }, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/styles/transition-page.scss: -------------------------------------------------------------------------------- 1 | /* always present */ 2 | .page-transition { 3 | transition: all .3s ease; 4 | overflow: hidden; 5 | } 6 | 7 | /* .expand-enter defines the starting state for entering */ 8 | /* .expand-leave defines the ending state for leaving */ 9 | .page-enter, .page-leave { 10 | height: 0; 11 | opacity: 0; 12 | } 13 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-cancel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/news/item/style.scss: -------------------------------------------------------------------------------- 1 | 2 | :local .lazy { 3 | composes: lazy-fadeIn from global; 4 | display: block; 5 | width: 100%; 6 | height: auto; 7 | } 8 | 9 | :local div.date { 10 | display: inline-block; 11 | width: 100%; 12 | // min-height: auto; 13 | text-align: end; 14 | font-size: 14px; 15 | line-height: 48px; 16 | } 17 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/item-content/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | import partial from 'partials/item-content.jade' 3 | 4 | export let name = 'item-content' 5 | 6 | export default { 7 | props: [ 8 | 'icon', 9 | 'title', 10 | 'after', 11 | 'subtitle', 12 | 'text', 13 | ], 14 | template: template(), 15 | partials: { partial: partial() }, 16 | } 17 | -------------------------------------------------------------------------------- /src/components/item-content-link/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | import partial from 'partials/item-content.jade' 3 | 4 | export let name = 'item-content-link' 5 | 6 | export default { 7 | props: [ 8 | 'icon', 9 | 'title', 10 | 'after', 11 | 'subtitle', 12 | 'text', 13 | ], 14 | template: template(), 15 | partials: { partial: partial() }, 16 | } 17 | -------------------------------------------------------------------------------- /src/components/container-item/template.jade: -------------------------------------------------------------------------------- 1 | item-content-accordion( 2 | :icon.once='type', 3 | :title.once='title', 4 | :text.once='text', 5 | :after='size') 6 | icon(slot='icon', :id.once='type', class=style.icon) 7 | template(slot='after') 8 | .badge.badge-clear {{* type }} 9 | .badge(:class.once="{'bg-red': load}") {{* size | parseSize }} 10 | slot(slot='accordion') 11 | -------------------------------------------------------------------------------- /src/images/icons-ionic/contacts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/images/icons-ionic/information-circled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gulp/helpers/getArg.js: -------------------------------------------------------------------------------- 1 | export default function getArg(key) { 2 | var index = process.argv.indexOf(key); 3 | var next = process.argv[index + 1]; 4 | return (index < 0) 5 | ? null 6 | : (!next || next[0] === "-") ? true : next; 7 | } 8 | 9 | export let debug = getArg('--debug') || getArg('-dp') || getArg('-pd') 10 | export let production = getArg('--prod') || getArg('--production') || getArg('-p') -------------------------------------------------------------------------------- /src/mixins/global.js: -------------------------------------------------------------------------------- 1 | /* global DEBUG */ 2 | 3 | import { F7 } from 'commons' 4 | import store from 'vuex/store' 5 | 6 | export default { 7 | ready() { 8 | DEBUG && console.log('init component %s', this.$options.name) 9 | var mainView = store.mainView 10 | 11 | mainView.router.loadContent(this.$els.page) 12 | F7.params.swipePanel = 'left' // this.$route.panel 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /src/images/icons-f7/android-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/about/index.js: -------------------------------------------------------------------------------- 1 | /* globals revision */ 2 | 3 | import style from './style.scss' 4 | import template from './template.jade' 5 | 6 | var name = 'about' 7 | 8 | export default { 9 | name: name, 10 | template: template({ name, style }), 11 | computed: { 12 | version() { 13 | // TODO: config 14 | return this.$t('about.version', {version: revision.version}) 15 | } 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/index/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | company: { 3 | name: 'VLADIVOSTOK SEA CONTAINER  TERMINAL', 4 | shortName: 'VSCT', 5 | }, 6 | 7 | btn: 'Login', 8 | } 9 | 10 | var ru = { 11 | company: { 12 | name: 'ВЛАДИВОСТОКСКИЙ МОРСКОЙ КОНТЕЙНЕРНЫЙ ТЕРМИНАЛ', 13 | shortName: 'ВМКТ', 14 | }, 15 | 16 | btn: 'Войти', 17 | } 18 | 19 | export default {en, ru} 20 | -------------------------------------------------------------------------------- /src/components/bill-info-item-table/index.js: -------------------------------------------------------------------------------- 1 | import formatSumm from 'mixins/filters/formatSumm' 2 | import template from './template.jade' 3 | 4 | export let name = 'bill-info-item-table' 5 | 6 | export default { 7 | name, 8 | props: [ 9 | 'count', 10 | 'price', 11 | 'summa', 12 | 'NDS', 13 | 'summands', 14 | ], 15 | mixins: [formatSumm], 16 | template: template({name}), 17 | } 18 | -------------------------------------------------------------------------------- /src/services/fetch/auth.js: -------------------------------------------------------------------------------- 1 | import { fetchFactory, URL, fixtures } from './utils' 2 | 3 | export let login = fetchFactory({ 4 | url: URL.login, 5 | parser: ({ data }) => data, 6 | fixture: fixtures.auth.login, 7 | }) 8 | 9 | export let logout = fetchFactory({ 10 | url: URL.logout, 11 | parser: ({ data }) => data, 12 | fixture: fixtures.auth.logout, 13 | }) 14 | 15 | export default { login, logout } 16 | -------------------------------------------------------------------------------- /gulp/tasks/connect.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import connect from 'gulp-connect' 3 | import config from '../config' 4 | 5 | gulp.task('connect', () => 6 | connect.server({ 7 | root: [config.dest], 8 | port: config.server.port, 9 | livereload: true, 10 | // https://github.com/bripkens/connect-history-api-fallback 11 | // fallback: config.dest + '/index.html', 12 | }) 13 | ) 14 | 15 | export default connect 16 | -------------------------------------------------------------------------------- /src/images/icons-ionic/sort.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/initials/components.js: -------------------------------------------------------------------------------- 1 | import { Vue } from 'commons' 2 | 3 | var components = requireAll(require.context('components/', true, /index\.js$/)) 4 | 5 | components.map( ({ name, default: component }) => { 6 | Vue.component(name, component) 7 | }) 8 | 9 | // https://webpack.github.io/docs/context.html#require-context 10 | function requireAll(requireContext) { 11 | return requireContext.keys().map(requireContext) 12 | } 13 | -------------------------------------------------------------------------------- /src/initials/router.js: -------------------------------------------------------------------------------- 1 | import { Vue, VueRouter } from 'commons' 2 | import routes from './routes' 3 | 4 | Vue.use(VueRouter) 5 | 6 | const router = new VueRouter({ 7 | root: '', 8 | linkActiveClass: 'active', 9 | hashbang: true, 10 | // history: true, 11 | // saveScrollPosition: true, 12 | }) 13 | 14 | router.map(routes) 15 | 16 | router.alias({ 17 | '/': 'index', 18 | }) 19 | 20 | export default router 21 | -------------------------------------------------------------------------------- /src/pages/news/item/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='news.title', state='back') 3 | .page-content 4 | img.lazy( 5 | v-el:image 6 | :src.once='newsItem.image', 7 | class=style.lazy) 8 | .card-header.color-gray(class=style.date) {{* newsItem.date | dateFull }} 9 | .content-block-title {{* newsItem.title }} 10 | .content-block: p {{{* newsItem.content }}} 11 | -------------------------------------------------------------------------------- /src/partials/item-content.jade: -------------------------------------------------------------------------------- 1 | .item-media(v-if='icon') 2 | slot(name='icon') 3 | icon(:id.once='icon') 4 | .item-inner 5 | .item-title-row 6 | .item-title 7 | slot(name='title') {{ title }} 8 | .item-after 9 | slot(name='after') {{ after }} 10 | .item-subtitle 11 | slot(name='subtitle') {{ subtitle }} 12 | .item-text 13 | slot(name='text') {{ text }} 14 | slot(name='input').item-input 15 | -------------------------------------------------------------------------------- /src/images/icons-ionic/android-star-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/profile/index/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | 3 | var name = 'profile-index' 4 | 5 | export default { 6 | name, 7 | template: template({ name }), 8 | 9 | vuex: { 10 | getters: { 11 | profile: state => state.profile.data || [], 12 | }, 13 | }, 14 | 15 | methods: { 16 | toProfileName(key) { 17 | return this.$t(`profile['${key}']`) 18 | } 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/components/icon/style.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | 3 | :local .icon { 4 | height: 24px; 5 | width: 24px; 6 | fill: currentColor; 7 | } 8 | 9 | [slot='media'] { 10 | color: $icon-default; 11 | } 12 | 13 | .icon-color-default { 14 | svg { 15 | color: $icon-default; 16 | .layout-dark & { 17 | color: #fff; 18 | } 19 | } 20 | } 21 | 22 | .icon-color-brend { 23 | svg { 24 | color: $brend-color; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/images/icons-ionic/android-locate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-close-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/images/icons-ionic/android-call.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/feedback/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'Feedback', 3 | text: 'Please use this form to submit your question. We will respond to you shortly.', 4 | placeholder: 'Your question...', 5 | } 6 | 7 | var ru = { 8 | title: 'Обратная связь', 9 | text: 'Воспользуйтесь данной формой, для того, чтобы отправить ваш вопрос. Мы обязательно ответим вам в ближайшее время.', 10 | placeholder: 'Ваш вопрос...', 11 | } 12 | 13 | export default {en, ru} 14 | -------------------------------------------------------------------------------- /src/components/search-bar/template.jade: -------------------------------------------------------------------------------- 1 | form.searchbar(v-el:form) 2 | .searchbar-input 3 | input( 4 | type='search', 5 | v-model='model', 6 | @keyup.enter='onSearch', 7 | :placeholder='placeholder') 8 | a.searchbar-search( 9 | @click='onSearch', 10 | href='#') 11 | a.searchbar-clear(href='#') 12 | //- a.searchbar-cancel(href='#') Cancel 13 | 14 | .searchbar-overlay.searchbar-overlay-active( 15 | @click='hide') 16 | -------------------------------------------------------------------------------- /src/components/operation-item/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | 3 | export let name = 'operation-item' 4 | 5 | export default { 6 | props: [ 7 | { name: 'operation', required: true }, 8 | { name: 'date', required: true }, 9 | 'transport', 10 | 'kontr', 11 | ], 12 | template: template(), 13 | computed: { 14 | title() { 15 | return this.$t(`containers.operations['${this.$get('operation')}']`) 16 | }, 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VMKT", 3 | "version": "0.0.2", 4 | "description": "Прототип мобильного приложения для владивостокского морского контейнерного терминала", 5 | "authors": [ 6 | { 7 | "name": "Andrey Pastukhov", 8 | "email": "gmtyllo@gmail.com" 9 | } 10 | ], 11 | "dependencies": { 12 | "animatewithsass": "~3.2.2", 13 | "roboto-fontface": "~0.4.3", 14 | "Framework7": "framework7#~1.4.2", 15 | "Ionicons": "ionicons#~2.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/tabs-swipeable/toolbar.jade: -------------------------------------------------------------------------------- 1 | .toolbar.tabbar.toolbar-bottom 2 | .toolbar-inner 3 | a.tab-link(href='#tab-containers').active 4 | //- icon(id='containers') 5 | span.tabbar-label {{ $t('containers.title.main') }} 6 | a.tab-link(href='#tab-bills') 7 | //- icon(id='bills') 8 | span.tabbar-label {{ $t('bills.title') }} 9 | a.tab-link(href='#tab-advance') 10 | //- icon(id='advance') 11 | span.tabbar-label {{ $t('advance.title') }} 12 | -------------------------------------------------------------------------------- /src/vuex/modules/progress.js: -------------------------------------------------------------------------------- 1 | import { SET_PROGRESS } from '../mutation-types' 2 | 3 | export let name = 'progress' 4 | 5 | // initial state 6 | export const state = { 7 | active: false, 8 | } 9 | 10 | // mutations 11 | export const mutations = { 12 | [SET_PROGRESS](state, flag) { 13 | state.active = flag 14 | }, 15 | } 16 | 17 | // actions 18 | export const actions = { 19 | setProgress({ dispatch }, flag) { 20 | dispatch(SET_PROGRESS, flag) 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/components/item-content-accordion/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | import partial from 'partials/item-content.jade' 3 | 4 | export let name = 'item-content-accordion' 5 | 6 | export default { 7 | name, 8 | props: [ 9 | 'icon', 10 | 'title', 11 | 'after', 12 | 'subtitle', 13 | 'text', 14 | // TODO - for remove 'component is instance' 15 | 'class', 16 | 'href', 17 | ], 18 | template: template(), 19 | partials: { partial: partial() }, 20 | } 21 | -------------------------------------------------------------------------------- /src/components/operation-item/template.jade: -------------------------------------------------------------------------------- 1 | item-content-accordion( 2 | v-if='kontr', 3 | :icon.once='operation', 4 | :title.once='title') 5 | .badge.badge-clear(slot='after') {{* date }} 6 | 7 | .table-shadow(slot='accordion',) 8 | container-item-table( 9 | :transport.once='transport', 10 | :kontr.once='kontr') 11 | 12 | item-content( 13 | v-else 14 | :icon.once='operation', 15 | :title.once='title') 16 | .badge.badge-clear(slot='after') {{* date }} 17 | -------------------------------------------------------------------------------- /src/components/navbar/template.jade: -------------------------------------------------------------------------------- 1 | mixin left() 2 | slot(name='left') 3 | a(href='#' @click='click').link.icon-only 4 | icon(:id='icon') 5 | 6 | mixin center() 7 | slot(name='center') {{ $t(title) }} 8 | 9 | mixin right() 10 | slot(name='right') 11 | 12 | .navbar 13 | .navbar-inner 14 | .left: +left() 15 | .center(class=style.center): +center() 16 | .right: +right() 17 | .progressbar-infinite( 18 | v-if='progress', 19 | class=style.progressbar) 20 | 21 | -------------------------------------------------------------------------------- /src/pages/contacts/dryTerminal.js: -------------------------------------------------------------------------------- 1 | export let title = 'Сухой терминал' 2 | 3 | export let list = [ 4 | { 5 | title: 'Код получателя', 6 | text: '7149, ОКПО 15262378', 7 | type: 'image', 8 | }, { 9 | title: 'Адрес ЗАО «ПИК»', 10 | text: '692770, г. Артем, ул. Гагарина, 23', 11 | type: 'address', 12 | }, { 13 | title: 'Станция и дорога назначения', 14 | text: 'Угольная, ДВЖД, код станции 982102, подача на п/путь ЗАО «ПИК»', 15 | type: 'address', 16 | }, 17 | ] 18 | -------------------------------------------------------------------------------- /src/components/container-item/index.js: -------------------------------------------------------------------------------- 1 | import template from './template.jade' 2 | import style from './style.scss' 3 | 4 | export let name = 'container-item' 5 | 6 | export default { 7 | props: [ 8 | { name: 'title', required: true }, 9 | { name: 'type', required: true }, 10 | 'text', 11 | 'size', 12 | 'load', 13 | ], 14 | template: template({name, style}), 15 | filters: { 16 | parseSize(size) { 17 | return this.$t(`containers.sizes[${size}]`) 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /src/images/icons-ionic/clip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/news/index/index.js: -------------------------------------------------------------------------------- 1 | import dateMixin from 'mixins/filters/date' 2 | 3 | import style from './style.scss' 4 | import template from './template.jade' 5 | 6 | var name = 'news-index' 7 | 8 | export default { 9 | name, 10 | mixins: [dateMixin], 11 | template: template({name, style}), 12 | 13 | vuex: { 14 | getters: { 15 | newsList: state => state.news.list, 16 | }, 17 | }, 18 | filters: { 19 | background(url) { 20 | return `background-image: url(${url})` 21 | }, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/icons.js: -------------------------------------------------------------------------------- 1 | // http://tympanus.net/codrops/2015/07/16/styling-svg-use-content-css/ 2 | 3 | import containers from 'images/containers' 4 | import f7 from 'images/icons-f7' 5 | import ionic from 'images/icons-ionic' 6 | import vsct from 'images/icons-vsct' 7 | import operations from 'images/operations' 8 | 9 | export { 10 | containers, 11 | f7, 12 | ionic, 13 | vsct, 14 | operations, 15 | } 16 | 17 | export default Object.assign({}, 18 | containers, 19 | f7, 20 | ionic, 21 | operations, 22 | vsct 23 | ) 24 | -------------------------------------------------------------------------------- /src/components/bill-info-item-table/template.jade: -------------------------------------------------------------------------------- 1 | .content-block 2 | .row 3 | .col-33 {{ $t('bills.count') }} 4 | .col-66 {{* count }} 5 | 6 | .row 7 | .col-33 {{ $t('bills.price') }} 8 | .col-66 {{* price | formatSumm}} 9 | 10 | .row 11 | .col-33 {{ $t('bills.summa') }} 12 | .col-66 {{* summa | formatSumm}} 13 | 14 | .row 15 | .col-33 {{ $t('bills.NDS') }} 16 | .col-66 {{* NDS | formatSumm }} 17 | 18 | .row 19 | .col-33 {{ $t('bills.summands') }} 20 | .col-66 {{* summands | formatSumm }} 21 | -------------------------------------------------------------------------------- /src/pages/profile/edit/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='profile.title' state='back') 3 | 4 | .page-content 5 | .list-block.inputs-list.clear-both 6 | ul: li(v-for='(key, value) in profile') 7 | item-content 8 | .label(slot='title') {{ toProfileName(key) }} 9 | template(slot='input') 10 | .item-input.item-input-field 11 | textarea.resizable( 12 | type='text', 13 | v-model='value', 14 | debounce='500') 15 | -------------------------------------------------------------------------------- /src/pages/feedback/index.js: -------------------------------------------------------------------------------- 1 | import { F7, isAndroid } from 'commons' 2 | 3 | import template from './template.jade' 4 | 5 | var name = 'feedback' 6 | 7 | export default { 8 | name, 9 | template: template({name}), 10 | 11 | data: () => ({ 12 | textarea: '', 13 | isAndroid, 14 | }), 15 | 16 | methods: { 17 | onSubmit() { 18 | var textarea = this.$get('textarea') 19 | this.$nextTick( () => 20 | F7.alert(textarea, 'Форма обратной связи') 21 | ) 22 | this.$set('textarea', null) 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /src/images/operations/seal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/components/container-item-table/index.js: -------------------------------------------------------------------------------- 1 | import dateMixin from 'mixins/filters/date' 2 | import stockMixin from 'mixins/filters/stock' 3 | import template from './template.jade' 4 | 5 | export let name = 'container-item-table' 6 | 7 | export default { 8 | props: [ 9 | 'number', 10 | 'type', 11 | 'load', 12 | 'size', 13 | 'date_in', 14 | 'date_out', 15 | 'transport', 16 | 'kontr', 17 | 'konos', 18 | 'sklad_in', 19 | 'sklad_out', 20 | ], 21 | mixins: [dateMixin, stockMixin], 22 | template: template({name}), 23 | } 24 | -------------------------------------------------------------------------------- /src/mixins/filters/date.js: -------------------------------------------------------------------------------- 1 | import { moment } from 'commons' 2 | 3 | const FORMAT = 'L' 4 | const FORMAT_EXTEND = 'LL' 5 | 6 | export default { 7 | filters: { 8 | date(date) { 9 | return moment(date).format(FORMAT) 10 | }, 11 | 12 | dateFull(date) { 13 | return moment(date).format(FORMAT_EXTEND) 14 | }, 15 | 16 | rangeDate(container) { 17 | var date_in = moment(container.date_in).format(FORMAT) 18 | var date_out = moment(container.date_out).format(FORMAT) 19 | return `${date_in} - ${date_out}` 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/app/index.js: -------------------------------------------------------------------------------- 1 | /* global DEBUG */ 2 | 3 | import { F7 } from 'commons' 4 | import store from 'vuex/store' 5 | import template from './template.jade' 6 | 7 | var name = 'app' 8 | 9 | export default { 10 | name, 11 | store, 12 | template: template(), 13 | 14 | vuex: { 15 | getters: { 16 | auth: state => state.auth.login, 17 | }, 18 | }, 19 | 20 | ready() { 21 | DEBUG && console.log('init', name) 22 | 23 | F7.init() 24 | var mainView = F7.addView(this.$els.view, {}) 25 | store.mainView = mainView 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-camera.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/images/operations/supply.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/pages/feedback/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar 3 | a.floating-button( 4 | v-if='isAndroid' 5 | v-show='textarea.trim()' 6 | @click.stop.prevent='onSubmit' 7 | href='#') 8 | icon(id='check') 9 | 10 | .page-content 11 | .content-block 12 | p {{ $t('feedback.text') }} 13 | form(@submit.stop.prevent='onSubmit').list-block 14 | .item-input 15 | textarea.resizable( 16 | v-model='textarea' 17 | debounce='500' 18 | placeholder="{{ $t('feedback.placeholder') }}") 19 | -------------------------------------------------------------------------------- /src/pages/news/index/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='news.title') 3 | .page-content 4 | Card(v-for='news in newsList', class=style.card) 5 | div( 6 | slot='header', 7 | :style.once='news.image | background', 8 | class=style.header) 9 | div(slot='content', class=style.content) 10 | | {{* news.title }} 11 | template(slot='footer') 12 | span.color-gray {{* news.date | dateFull }} 13 | a(v-link="{name: 'news/item', params: {id: news.id }}").link 14 | | {{ $t('news.read') }} 15 | -------------------------------------------------------------------------------- /src/pages/settings/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'Settings', 3 | titleNotify: 'Notify about login in your cabinet', 4 | email: 'Notify via email', 5 | phone: 'Notify via phone', 6 | avatar: 'E-mail service for avatar with gravatar', 7 | lang: 'Change language', 8 | } 9 | 10 | var ru = { 11 | title: 'Настройки', 12 | titleNotify: 'Уведомления о входе в Ваш личный кабинет', 13 | email: 'Уведомлять по email', 14 | phone: 'Уведомлять по phone', 15 | avatar: 'E-mail для аватара с сервиса gravatar', 16 | lang: 'Сменить язык', 17 | } 18 | 19 | export default {en, ru} 20 | -------------------------------------------------------------------------------- /src/pages/news/item/index.js: -------------------------------------------------------------------------------- 1 | import dateMixin from 'mixins/filters/date' 2 | 3 | import style from './style.scss' 4 | import template from './template.jade' 5 | 6 | var name = 'news-item' 7 | 8 | export default { 9 | name, 10 | mixins: [dateMixin], 11 | template: template({name, style}), 12 | 13 | vuex: { 14 | getters: { 15 | news: state => state.news.list, 16 | }, 17 | }, 18 | 19 | computed: { 20 | newsItem() { 21 | var id = this.$route.params.id | 0 22 | return this.$get('news').find( item => id === item.id ) 23 | }, 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/profile/edit/index.js: -------------------------------------------------------------------------------- 1 | import { F7 } from 'commons' 2 | import template from './template.jade' 3 | 4 | var name = 'profile-edit' 5 | 6 | export default { 7 | name, 8 | template: template({ name }), 9 | 10 | vuex: { 11 | getters: { 12 | profile: state => state.profile.data || [], 13 | }, 14 | }, 15 | 16 | methods: { 17 | toProfileName(key) { 18 | return this.$t(`profile['${key}']`) 19 | } 20 | }, 21 | 22 | events: { 23 | ['profile:update']() { 24 | F7.alert('Profile update', '', () => window.history.back()) 25 | }, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /src/images/icons-vsct/logo-emblem.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pages/settings/index/index.js: -------------------------------------------------------------------------------- 1 | import { toggleNotify } from 'vuex/actions' 2 | import template from './template.jade' 3 | 4 | var name = 'settings-index' 5 | 6 | export default { 7 | name, 8 | template: template({ name }), 9 | 10 | vuex: { 11 | actions: { toggleNotify }, 12 | 13 | getters: { 14 | settings: state => state.settings || [], 15 | }, 16 | }, 17 | 18 | methods: { 19 | toggleNotify: (e) => { 20 | this.toggleNotify(e.target.name) 21 | }, 22 | 23 | openLangs() { 24 | this.$root.$broadcast('open:actionSheet:langs') 25 | }, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /src/services/CacheItem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * cache item (bill or container) by number 3 | * @param {String} name — имя кеша 4 | * @param _data — кеш счетов или контейнеров 5 | */ 6 | export default class CacheItem { 7 | constructor(name) { 8 | this.name = name 9 | this._data = {} 10 | } 11 | 12 | set(number, data) { 13 | this._data[number] = data 14 | } 15 | 16 | get(number) { 17 | return this._data[number] ? { 18 | number, 19 | data: this._data[number], 20 | message: `Get ${ this.name } ${ number } from cache`, 21 | } : null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/pages/about/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'About', 3 | subtitle: 'Personal account for customers Vladivostok Container Terminal, LLC', 4 | developer: 'Contact with developers', 5 | feedback: 'Write feedback', 6 | version: 'Version {version}', 7 | } 8 | 9 | var ru = { 10 | title: 'О программе', 11 | subtitle: 'Личный кабинет для клиентов ООО «Владивостокский морской контейнерный терминал»', 12 | developer: 'Cвязаться с разработчиками', 13 | feedback: 'Оставить отзыв', 14 | version: 'Версия {version}', 15 | } 16 | 17 | export default {en, ru} 18 | -------------------------------------------------------------------------------- /src/images/operations/expense.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/bill-info-item/template.jade: -------------------------------------------------------------------------------- 1 | item-content-accordion( 2 | :title='title | formatSumm', 3 | :text.once='tovar') 4 | .badge(slot='after') {{* count }} 5 | .table-shadow(slot='accordion') 6 | ul 7 | li: item-content 8 | bill-info-item-table( 9 | slot='text', 10 | :count.once='count', 11 | :price.once='price', 12 | :summa.once='summa', 13 | :NDS.once='NDS', 14 | :summands.once='summands') 15 | li(v-for='container in containers') 16 | item-content-link( 17 | v-link='container | toCntInfo', 18 | :title='container | summaTitle') 19 | -------------------------------------------------------------------------------- /src/pages/about/style.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | 3 | [data-page='about'] { 4 | .list-block { 5 | margin: 0; 6 | } 7 | 8 | .list-block-label { 9 | text-align: center; 10 | text-transform: uppercase; 11 | } 12 | } 13 | 14 | :local(svg.logo-emblem) { 15 | width: 100%; 16 | height: rem(75); 17 | margin: rem(56 0 0); 18 | color: $brend-color; 19 | } 20 | 21 | :local(svg.logo-text) { 22 | width: 100%; 23 | height: rem(75); 24 | margin: rem(16 0); 25 | color: black; 26 | // use { 27 | // --text-ru-color: green; 28 | // --logo-line-color: pink; 29 | // --text-en-color: red; 30 | // } 31 | } 32 | -------------------------------------------------------------------------------- /gulp/tasks/webpack.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import errorHandler from '../helpers/errorHandler' 3 | 4 | import webpackGulp from 'webpack-stream' 5 | import named from 'vinyl-named' 6 | 7 | import config from '../config' 8 | import configWebpack from '../webpack' 9 | import connect from './connect' 10 | import env from 'gulp-env' 11 | 12 | var envs = { NODE_ENV: config.NODE_ENV } 13 | 14 | gulp.task('webpack', () => 15 | gulp 16 | .src(config.modules + '/*.js') 17 | .pipe(env.set(envs)) 18 | .pipe(errorHandler()) 19 | .pipe(named()) 20 | .pipe(webpackGulp(configWebpack)) 21 | .pipe(gulp.dest(config.dest)) 22 | .pipe(connect.reload()) 23 | ) 24 | -------------------------------------------------------------------------------- /src/pages/404/style.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | 3 | :local div.page { 4 | background-color: $brend-color; 5 | } 6 | 7 | :local svg.notFound { 8 | margin-top: 25%; 9 | width: 100%; 10 | height: rem(160); 11 | color: white; 12 | } 13 | 14 | :local h1.title { 15 | padding-top: rem(32); 16 | font-size: rem(120); 17 | text-align: center; 18 | line-height: 1; 19 | margin: rem(0); 20 | color: #000; 21 | } 22 | 23 | :local .content { 24 | text-align: center; 25 | font-weight: normal; 26 | font-size: rem(12); 27 | letter-spacing: rem(16); 28 | text-transform: uppercase; 29 | margin: rem(0); 30 | color: #000; 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/bills/info/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(:title='title', state='back') 3 | .page-content 4 | 5 | .list-block.media-list.clear-both(v-if='bill.length') 6 | ul(class=style.title) 7 | li.accordion-item(v-for="item in bill | orderBy 'price'") 8 | bill-info-item( 9 | :tovar.once='item.tovar', 10 | :count.once='item.count', 11 | :price.once='item.price', 12 | :summa.once='item.summa', 13 | :NDS.once='item.NDS', 14 | :summands.once='item.summands') 15 | li: item-content(:title='total | formatSumm' class=style.total) 16 | -------------------------------------------------------------------------------- /src/services/Storage.js: -------------------------------------------------------------------------------- 1 | const Storage = localStorage 2 | 3 | export default { get, set, clear } 4 | 5 | // getter 6 | export function get(name, defaults = null) { 7 | var data 8 | 9 | try { 10 | data = JSON.parse(Storage.getItem(name)) 11 | } catch (e) { 12 | data = null 13 | } 14 | 15 | if (!data) { 16 | data = Object.assign({}, defaults) 17 | data && set(name, defaults) 18 | } 19 | 20 | return data 21 | } 22 | 23 | // setter 24 | export function set(name, state) { 25 | Storage.setItem(name, JSON.stringify(state)) 26 | } 27 | 28 | // clear all storage 29 | export function clear() { 30 | Storage.clear() 31 | } 32 | -------------------------------------------------------------------------------- /src/components/icon/index.js: -------------------------------------------------------------------------------- 1 | import load from 'promise?global,[name].promise!icons' 2 | import template from './template.jade' 3 | import style from './style.scss' 4 | 5 | export let name = 'icon' 6 | 7 | export default res => load().then( ({ default: icons }) => res({ 8 | props: { 9 | id: { 10 | required: true, 11 | coerce: id => icons[id], 12 | }, 13 | class: { default: '' }, 14 | style: { default: '' }, 15 | height: { default: 24 }, 16 | width: { default: 24 }, 17 | }, 18 | name: name, 19 | template: template({style}), 20 | computed: { 21 | viewBox() { 22 | return `0 0 ${this.width} ${this.height}` 23 | }, 24 | }, 25 | })) 26 | -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | import { debug, production } from './helpers/getArg' 2 | import revision from './helpers/revision' 3 | 4 | var src = './src' 5 | var dest = './www' 6 | 7 | export default { 8 | src: src, 9 | dest: dest, 10 | components: src + '/components', 11 | pages: src + '/pages', 12 | modules: src + '/modules/', 13 | 14 | assets: { 15 | images: 'assets/images', 16 | scripts: 'assets/scripts', 17 | styles: 'assets/styles', 18 | fonts: 'assets/fonts', 19 | }, 20 | 21 | revision, 22 | isProduction: production, 23 | isDevelope: !production, 24 | isDebug: debug, 25 | NODE_ENV: production ? 'production' : 'develope', 26 | 27 | server: { 28 | port: 8080, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /src/components/bill-item/template.jade: -------------------------------------------------------------------------------- 1 | item-content-accordion( 2 | :title="$t('bills.header', {number: number})", 3 | :subtitle="$t('bills.subtitle', {date: date})") 4 | .badge.badge-clear(slot='after') {{* summa | formatSumm }} {{* currency }} 5 | .table-shadow(slot='accordion') 6 | ul 7 | li: item-content(:text.once='comment') 8 | li: item-content-link( 9 | v-for="number in containerNumbers", 10 | track-by="$index", 11 | v-link='number | toCntInfo', 12 | :title="$t('containers.header', {number: number})") 13 | li: item-content-link( 14 | class=style.link, 15 | v-link='number | toBillInfo', 16 | :title="$t('bills.more')") 17 | -------------------------------------------------------------------------------- /src/pages/news/index/style.scss: -------------------------------------------------------------------------------- 1 | :local .card { 2 | :global(.card-header) { 3 | padding: 0; 4 | } 5 | } 6 | 7 | :local .header { 8 | composes: color-white no-border from global; 9 | height: 40vw; 10 | width: 100%; 11 | padding: 4px 16px; 12 | background-size: cover; 13 | background-position: center center; 14 | 15 | span { 16 | position: absolute; 17 | bottom: 0; 18 | } 19 | } 20 | 21 | div.card-content-inner { 22 | margin: 16px; 23 | padding: 0; 24 | } 25 | 26 | // for 2 stroke text and ... 27 | :local .content { 28 | // height: 2*21px; 29 | overflow: hidden; 30 | -webkit-line-clamp: 2; 31 | -webkit-box-orient: vertical; 32 | display: -webkit-box; 33 | } -------------------------------------------------------------------------------- /src/images/icons-ionic/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/images/icons-ionic/logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/images/operations/expense-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/modules/commons.js: -------------------------------------------------------------------------------- 1 | /* global Framework7, Dom7 */ 2 | 3 | import Vue from 'vue' 4 | import i18n from 'vue-i18n' 5 | import Vuex from 'Vuex' 6 | import VueRouter from 'vue-router' 7 | import VueResource from 'vue-resource' 8 | 9 | import 'Framework7/dist/js/framework7.js' 10 | 11 | import moment from 'moment' 12 | import 'moment/locale/ru' 13 | 14 | export { 15 | Vue, 16 | i18n, 17 | Vuex, 18 | VueRouter, 19 | VueResource, 20 | 21 | Framework7, 22 | Dom7, 23 | 24 | moment, 25 | } 26 | 27 | export let isIos = Framework7.prototype.device.ios 28 | // export let isAndroid = Framework7.prototype.device.android, 29 | export let isAndroid = !Framework7.prototype.device.ios 30 | export let F7 = {} 31 | -------------------------------------------------------------------------------- /src/pages/index/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name transition='') 2 | 3 | .page-content(class=style['page-content']) 4 | 5 | .content-block-title(class=style['text-right']) 6 | a.link.icon-only( 7 | href='#', 8 | v-el:lang, 9 | class=style.world, 10 | @click.stop.prevent='openLangs') 11 | icon(id='world') 12 | 13 | .content-block(class=style.logo, @click='clickLogo') 14 | div(:class='className') 15 | icon(id='logoEmblem', class=style['logo-emblem']) 16 | icon(id='logoText', class=style['logo-text'], width=120, height=65) 17 | 18 | .content-block(class=style.login) 19 | a(class=style.button, v-link="{name: 'login'}"). 20 | {{ $t('#{name}.btn') }} 21 | -------------------------------------------------------------------------------- /src/pages/settings/edit/index.js: -------------------------------------------------------------------------------- 1 | import { F7 } from 'commons' 2 | 3 | import template from './template.jade' 4 | 5 | var name = 'settings-edit' 6 | 7 | export default { 8 | name, 9 | template: template({ name }), 10 | 11 | vuex: { 12 | getters: { 13 | settings: state => state.settings || [], 14 | } 15 | }, 16 | 17 | events: { 18 | ['settings:update']() { 19 | var settings = this.settings 20 | 21 | var email = `email: ${settings.email} ` 22 | var phone = `phone: ${settings.phone} ` 23 | var gravatar_email = `gravatar_email: ${settings.gravatar_email} ` 24 | 25 | F7.alert(email + phone + gravatar_email, '', () => window.history.back()) 26 | }, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /src/images/operations/arrival.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/images/icons-vsct/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/pages/advance/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'Advance', 3 | advance: 'Advance payments', 4 | debt: 'Receivables', 5 | summa: 'Current balance', 6 | summa_bill: 'Balance based on invoices', 7 | 8 | sortList: { 9 | date: 'By date', 10 | number: 'By number', 11 | summa: 'By sum', 12 | }, 13 | 14 | advanceTitle: '{summa} RUB.', 15 | } 16 | 17 | var ru = { 18 | title: 'Платежи', 19 | advance: 'Авансовые платежи', 20 | debt: 'Дебиторская задолженность', 21 | summa: 'Текущий баланс', 22 | summa_bill: 'Баланс с учетом выставленных счетов', 23 | 24 | sortList: { 25 | date: 'По дате', 26 | number: 'По номеру', 27 | summa: 'По сумме', 28 | }, 29 | 30 | advanceTitle: '{summa} РУБ.', 31 | } 32 | 33 | export default {en, ru} 34 | -------------------------------------------------------------------------------- /src/images/icons-vsct/favicon-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/images/icons-ionic/android-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/styles/main.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | @import 'table-shadow'; 3 | @import '~animatewithsass/_properties'; 4 | @import '~animatewithsass/_bouncing-entrances/bouncing-entrances'; 5 | 6 | .bounceIn { 7 | @include bounceIn(); 8 | } 9 | 10 | [v-cloak] { 11 | display: none; 12 | } 13 | 14 | .list-block .item-divider .item-link .item-inner, 15 | .list-block.media-list .item-divider .item-link .item-inner { 16 | line-height: 24px; 17 | } 18 | 19 | div.list-block .item-after { 20 | display: block; 21 | } 22 | 23 | div.list-block { 24 | &.clear-top, &.clear-both { 25 | margin-top: 0; 26 | } 27 | &.clear-bottom, &.clear-both { 28 | margin-bottom: 0; 29 | } 30 | } 31 | 32 | div.list-block .item-text { 33 | height: auto; 34 | } 35 | -------------------------------------------------------------------------------- /src/images/operations/lathing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/fixtures/utils.js: -------------------------------------------------------------------------------- 1 | import { moment } from 'commons' 2 | 3 | const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 4 | const MIN_TIME = moment('01-01-2000', 'MM-DD-YYYY') 5 | 6 | export function randomDate(minTime = MIN_TIME) { 7 | var diff = moment().valueOf() - minTime 8 | diff = diff * Math.random() 9 | return moment(minTime + diff).valueOf() 10 | } 11 | 12 | export function randomNumb(exponent = 5) { 13 | return Math.floor(Math.random() * Math.pow(10, exponent)) 14 | } 15 | 16 | export function randomChar(n = 5) { 17 | var str = '' 18 | 19 | for (let i = 0; i <= n - 1; i++) { 20 | let index = randomNumb(n) % ALPHABET.length 21 | str = str + ALPHABET[index] 22 | } 23 | 24 | return str 25 | } 26 | 27 | export let ContainerNumb = () => randomChar(5) + randomNumb(7) 28 | -------------------------------------------------------------------------------- /src/modules/main.js: -------------------------------------------------------------------------------- 1 | /* globals revision */ 2 | 3 | import load from 'promise?global,[name].promise!commons' 4 | import style from 'promise?global,[name].promise!android' 5 | import store from 'vuex/store' 6 | import Storage from 'services/Storage' 7 | import app from 'modules/app' 8 | 9 | import { router } from 'initials' 10 | 11 | // just onetime for render in www 12 | import 'styles/main.scss' 13 | 14 | // clear bad cache from Storage 15 | if (revision.count < 5) { 16 | Storage.clear() 17 | } 18 | 19 | style().then(load).then( commons => { 20 | var { Vue, i18n } = commons 21 | 22 | Vue.use(i18n, { 23 | lang: 'current', 24 | locales: store.state.locales, 25 | }) 26 | 27 | router.start(Vue.extend({ 28 | components: { app } 29 | }), document.body) 30 | }) 31 | -------------------------------------------------------------------------------- /src/pages/bills/index/template.jade: -------------------------------------------------------------------------------- 1 | .content-block(v-show='!date_at && !date_to && !bills.length') 2 | p {{ $t('bills.empty.dates') }} 3 | 4 | .content-block(v-show='date_at && date_to && !bills.length') 5 | //- TODO - filter dates!!! 6 | p {{{ $t('bills.empty.bills', date_at, date_to) }}} 7 | 8 | .content-block(v-show='date_at && date_to && bills.length') 9 | p: strong {{ date_at | dateFull }} — {{ date_to | dateFull }} 10 | 11 | .list-block.media-list.clear-top(v-if='bills.length') 12 | ul(class=style.title) 13 | li.accordion-item(v-for="bill in bills | orderBy orderName") 14 | bill-item( 15 | :date='bill.date | dateFull', 16 | :number.once='bill.number', 17 | :summa.once='bill.summa', 18 | :currency.once='bill.currency', 19 | :comment.once='bill.comment') 20 | -------------------------------------------------------------------------------- /src/vuex/modules/order.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import { CHANGE_ORDER } from '../mutation-types' 3 | 4 | export let name = 'order' 5 | var defaults = { 6 | containers: 'date', 7 | container: 'date', 8 | bills: 'date', 9 | bill: 'date', 10 | advance: 'date', 11 | } 12 | 13 | // initial state 14 | export const state = Storage.get(name, defaults) 15 | 16 | // mutations 17 | export const mutations = { 18 | // общая для сортировки 19 | [CHANGE_ORDER](state, {orderName, name}) { 20 | state[name] = orderName 21 | Storage.set(name, state) 22 | }, 23 | } 24 | 25 | // actions 26 | export const actions = { 27 | // меняем имя сортируемого поля в state = data.name 28 | changeOrder({ dispatch }, data) { 29 | dispatch(CHANGE_ORDER, data) 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /src/images/icons-ionic/email.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/templates/index.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | meta(charset='utf-8') 5 | title ВМКТ 6 | meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui") 7 | meta(name='mobile-web-app-capable' content='yes') 8 | meta(name='apple-mobile-web-app-capable' content='yes') 9 | meta(name='apple-mobile-web-app-status-bar-style' content='black') 10 | meta(name='theme-color' content='#D74747') 11 | link(rel='manifest' href='manifest.json') 12 | link(rel='shortcut icon' href='#{assets.images}/favicon/red/256x256.png') 13 | if isProduction 14 | link(rel='stylesheet' href='#{assets.styles}/main.css') 15 | body 16 | app 17 | //- script(src='#{assets.scripts}/cordova.js') 18 | script(src='#{assets.scripts}/main.js') 19 | -------------------------------------------------------------------------------- /src/pages/about/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='about.title') 3 | .page-content 4 | icon(id='logoEmblem', class=style['logo-emblem']) 5 | icon(id='logoText', class=style['logo-text'], height='65', width='120') 6 | .list-block.media-list 7 | .list-block-label(class=style.label) {{{ $t('about.subtitle') }}} 8 | ul.icon-color-default 9 | li: item-content-link.external( 10 | href='mailto:develope@xdraw.ru', 11 | icon='bug', 12 | :title="$t('about.developer')") 13 | 14 | li: item-content-link.external( 15 | href='//play.google.com', 16 | icon='star', 17 | :title="$t('about.feedback')") 18 | 19 | li: item-content( 20 | icon='codeWorking', 21 | :title='version') 22 | -------------------------------------------------------------------------------- /src/images/icons-ionic/code-working.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/pages/profile/index.js: -------------------------------------------------------------------------------- 1 | import { getProfile } from 'vuex/actions' 2 | 3 | import template from './template.jade' 4 | 5 | var name = 'profile' 6 | 7 | export default { 8 | name: name, 9 | template: template(), 10 | 11 | route: { 12 | activate() { 13 | this.getProfile() 14 | } 15 | }, 16 | 17 | vuex: { 18 | actions: { getProfile }, 19 | }, 20 | 21 | computed: { 22 | isIndex() { return this.$route.name === 'profile/index' }, 23 | button() { return this.isIndex ? 'create' : 'check' }, 24 | }, 25 | 26 | methods: { 27 | action() { 28 | if (this.isIndex) { 29 | this.$router.go({name: 'profile/edit'}) 30 | } else { 31 | this.$broadcast('profile:update') 32 | } 33 | 34 | this.isIndex = !this.isIndex 35 | }, 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /src/pages/settings/index.js: -------------------------------------------------------------------------------- 1 | import { getSettings } from 'vuex/actions' 2 | import template from './template.jade' 3 | 4 | var name = 'settings' 5 | 6 | export default { 7 | name, 8 | template: template(), 9 | 10 | route: { 11 | activate() { 12 | this.getSettings() 13 | }, 14 | }, 15 | 16 | vuex: { 17 | actions: { getSettings }, 18 | }, 19 | 20 | computed: { 21 | isIndex() { return this.$route.name === 'settings/index' }, 22 | button() { return this.isIndex ? 'create' : 'check' }, 23 | }, 24 | 25 | methods: { 26 | action() { 27 | if (this.isIndex) { 28 | this.$route.router.go({name: 'settings/edit'}) 29 | } else { 30 | this.$broadcast('settings:update') 31 | } 32 | 33 | this.isIndex = !this.isIndex 34 | }, 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /src/images/icons-ionic/android-settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/services/fetch/index.js: -------------------------------------------------------------------------------- 1 | import { Vue, VueResource } from 'commons' 2 | import { URL } from './utils' 3 | 4 | import advance from './advance' 5 | import auth from './auth' 6 | import containerList from './container-list' 7 | import containerInfo from './container-info' 8 | import billList from './bill-list' 9 | import billInfo from './bill-info' 10 | import news from './news' 11 | import profile from './profile' 12 | import settings from './settings' 13 | 14 | Vue.use(VueResource) 15 | 16 | Vue.http.options.root = URL.SERVER 17 | Vue.http.options.crossOrigin = true 18 | 19 | export default { 20 | advance, 21 | auth, 22 | container: { 23 | list: containerList, 24 | info: containerInfo, 25 | }, 26 | bill: { 27 | list: billList, 28 | info: billInfo, 29 | }, 30 | news, 31 | profile, 32 | settings, 33 | } 34 | -------------------------------------------------------------------------------- /src/components/search-bar/index.js: -------------------------------------------------------------------------------- 1 | import load from 'promise?global,[name].promise!commons' 2 | import template from './template.jade' 3 | import style from './style.scss' 4 | 5 | export let name = 'search-bar' 6 | 7 | export default res => load().then( ({ F7 }) => res({ 8 | props: [ 9 | {name: 'placeholder', default: 'Search'}, 10 | {name: 'show', required: true}, 11 | {name: 'event', required: true}, 12 | ], 13 | data: () => ({ 14 | model: '', 15 | }), 16 | template: template({name, style}), 17 | ready() { 18 | F7.searchbar(this.$els.form, {}) 19 | }, 20 | methods: { 21 | hide() { 22 | this.$set('show', false) 23 | }, 24 | onSearch() { 25 | var event = 'search:' + this.$get('event') 26 | this.$root.$broadcast(event, this.$get('model')) 27 | this.hide() 28 | }, 29 | }, 30 | })) 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Framework7-VueJS 2 | =========== 3 | 4 | Use for auth: **demo/demo** 5 | 6 | This app shows you example of using mobile framework - Framework7 with [VueJS](http://vuejs.org/), [Vuex](http://vuex.vuejs.org/), [vue-router](http://vuejs.github.io/vue-router/en/index.html), [vue-i18n](https://github.com/kazupon/vue-i18n) to build 'personal account' application. 7 | 8 | 9 | Features 10 | ------ 11 | 12 | - webpack - bundler 13 | - gulp - task manager 14 | - svg-spites - for icon 15 | - css modules - future of css 16 | - jade - templater 17 | - scss - preprocessor 18 | 19 | 20 | How to build 21 | ------ 22 | 23 | `gulp --develope` - for development use. 24 | 25 | `gulp --production` - for build minimize use. 26 | 27 | `gulp --debug` - for debug use. 28 | 29 | 30 | 31 | Thanks 32 | ------ 33 | 34 | Licensed under the [MIT License](https://opensource.org/licenses/MIT) 35 | -------------------------------------------------------------------------------- /src/images/icons-vsct.js: -------------------------------------------------------------------------------- 1 | import advance from 'images/icons-vsct/advance.svg' 2 | import bills from 'images/icons-vsct/bills.svg' 3 | // import bills from 'images/icons-vsct/bills-1.svg' 4 | import cargo from 'images/icons-vsct/cargo.svg' 5 | // import cargo from 'images/icons-vsct/cargo-1.svg' 6 | // import cargo from 'images/icons-vsct/cargo-2.svg' 7 | import containers from 'images/icons-vsct/containers.svg' 8 | import logoEmblem from 'images/icons-vsct/logo-emblem.svg' 9 | import logoText from 'images/icons-vsct/logo-text.svg' 10 | import operations from 'images/icons-vsct/operations.svg' 11 | import profile from 'images/icons-vsct/profile.svg' 12 | import size from 'images/icons-vsct/size.svg' 13 | import types from 'images/icons-vsct/types.svg' 14 | 15 | export default { 16 | advance, 17 | bills, 18 | cargo, 19 | containers, 20 | logoEmblem, 21 | logoText, 22 | operations, 23 | profile, 24 | size, 25 | types, 26 | } 27 | -------------------------------------------------------------------------------- /src/images/operations/arrival-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/initials/F7.js: -------------------------------------------------------------------------------- 1 | /* global NODE_ENV, DEBUG */ 2 | 3 | import { F7, Framework7, Dom7, isIos, isAndroid } from 'commons' 4 | 5 | var platform = isIos ? 'ios' : 'android' 6 | 7 | Object.assign(F7, new Framework7({ 8 | modalTitle: '', 9 | material: isAndroid, 10 | // pushState: true, 11 | // hideTabbarOnPageScroll: true, 12 | animateNavBackIcon: isIos, 13 | swipePanel: 'left', 14 | // swipePanelActiveArea: '100', 15 | // router: false, 16 | init: false, 17 | scrollTopOnNavbarClick: true, 18 | // for performance 19 | cache: false, 20 | sortable: false, 21 | swipeout: false, 22 | swipeBackPageAnimateShadow: false, 23 | swipeBackPageAnimateOpacity: false, 24 | })) 25 | 26 | // http://goo.gl/0VB8sl 27 | // if (NODE_ENV === 'production') { 28 | // Dom7('head').append(``) 29 | // } 30 | 31 | DEBUG && console.log(`Platform is <${platform}>`) 32 | -------------------------------------------------------------------------------- /src/images/containers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Свойства контейнера 3 | * 4 | * char(2) type: 5 | * FL - Без стен 6 | * HC - Высокий 7 | * HR - Высокий Рефрижераторный 8 | * OP - Без крыши 9 | * RF - Рефрижераторный 10 | * ST - Стандартный 11 | * TK - Цистерна, изотанк 12 | * TM - Термос 13 | */ 14 | 15 | import FL from 'images/containers/FL.svg' 16 | // import HC from 'images/containers/HC.svg' 17 | import HC from 'images/containers/HC-1.svg' 18 | // import HC from 'images/containers/HC-2.svg' 19 | import HR from 'images/containers/HR.svg' 20 | import OP from 'images/containers/OP.svg' 21 | import RF from 'images/containers/RF.svg' 22 | // import ST from 'images/containers/ST.svg' 23 | import ST from 'images/containers/ST-2.svg' 24 | // import ST from 'images/containers/ST-3.svg' 25 | import TK from 'images/containers/TK.svg' 26 | import TM from 'images/containers/TM.svg' 27 | 28 | export default { FL, HC, HR, OP, RF, ST, TK, TM } 29 | -------------------------------------------------------------------------------- /src/fixtures/settings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Настройки аккаунта 3 | * @param {number} phone - номер телефона для нотификаций 4 | * @param {string} email - email для нотификаций 5 | * @param {string} gravatar_email - email для аватарки с сервиса http://gravatar.com 6 | * @param {string} gravatar_hash - хеш для сервиса http://gravatar.com 7 | * @param notifyBy - нотификация о входе в аккаунт 8 | */ 9 | export class Settings { 10 | constructor() { 11 | this.email = 'develope@xdraw.ru' 12 | this.phone = '+7 (123) 456-78-90' 13 | this.gravatar_email = 'develope@xdraw.ru' 14 | this.gravatar_hash = '' // '9e61a7147e60351930da8e117a9ccde9' 15 | this.notifyBy = { 16 | email: true, 17 | phone: true, 18 | gravatar_email: false, 19 | } 20 | } 21 | } 22 | 23 | /** 24 | * @return {Settings} — подробная информация о контрагенте 25 | */ 26 | export default () => new Promise( resolve => resolve( new Settings() )) 27 | -------------------------------------------------------------------------------- /src/pages/contacts/template.jade: -------------------------------------------------------------------------------- 1 | mixin component(item) 2 | component( 3 | :is='item.component', 4 | :href.once='item.href', 5 | :class.once='item.classLink', 6 | :icon.once='item.icon', 7 | :title.once='item.title', 8 | :after.once='item.after', 9 | :subtitle.once='item.subtitle', 10 | :text.once='item.text') 11 | block 12 | 13 | Page(v-el:page name=name).navbar-fixed 14 | navbar(title='contacts.title') 15 | .page-content 16 | .list-group(v-for='terminal in terminals') 17 | .content-block-title {{* terminal.title }} 18 | .list-block.media-list.clear-both 19 | ul.icon-color-default 20 | li(:class='item.class', 21 | v-for='item in terminal.list | normilize') 22 | +component(item) 23 | ul(slot='accordion') 24 | li(:class='item.class', 25 | v-for='item in item.content | normilize') 26 | +component(item) 27 | -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | /* globals NODE_ENV, DEBUG */ 2 | 3 | import { Vue, Vuex } from 'commons' 4 | import createLogger from 'vuex/logger' 5 | 6 | Vue.use(Vuex) 7 | Vue.config.debug = DEBUG 8 | 9 | const STORE = { 10 | state: {}, 11 | actions: [], 12 | mutations: [], 13 | modules: requireAll(require.context('./modules/', false, /\.js$/)), 14 | strict: (NODE_ENV === 'develope'), 15 | middlewares: (NODE_ENV === 'develope') ? [createLogger()] : [] 16 | } 17 | 18 | export default new Vuex.Store(STORE) 19 | 20 | /************************************************ 21 | helpers 22 | ===============================================*/ 23 | 24 | // https://webpack.github.io/docs/context.html#require-context 25 | function requireAll(requireContext) { 26 | return requireContext.keys().reduce( (fixtures, file) => { 27 | var name = file.match(/\.\/(.+?)\.js/)[1] 28 | 29 | fixtures[name] = requireContext(file) 30 | 31 | return fixtures 32 | }, {}) 33 | } 34 | -------------------------------------------------------------------------------- /src/components/bill-info-item/index.js: -------------------------------------------------------------------------------- 1 | import formatSumm from 'mixins/filters/formatSumm' 2 | import template from './template.jade' 3 | 4 | export let name = 'bill-info-item' 5 | 6 | export default { 7 | props: [ 8 | 'tovar', 9 | 'count', 10 | 'price', 11 | 'summa', 12 | 'NDS', 13 | 'summands', 14 | ], 15 | mixins: [formatSumm], 16 | template: template({name}), 17 | computed: { 18 | title() { 19 | return this.$t('bills.info.title', { 20 | summa: this.$get('summands'), 21 | }) 22 | }, 23 | 24 | containers() { 25 | var pattern = /([A-Z]{4}[0-9]{7}(?![0-9]))/g 26 | return this.$get('tovar').match(pattern) 27 | }, 28 | }, 29 | filters: { 30 | toCntInfo(number) { 31 | return { 32 | name: 'container/info', 33 | params: { number }, 34 | } 35 | }, 36 | 37 | summaTitle(number) { 38 | return this.$t('containers.header', {number}) 39 | }, 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/table-shadow.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * стиль для серой плашки с тенями снизу и сверху 3 | */ 4 | 5 | div.table-shadow { 6 | background-color: rgb(240,240,240); 7 | 8 | &:before, &:after { 9 | content: ''; 10 | display: block; 11 | position: relative; 12 | height: 10px; 13 | background-color: transparent; 14 | box-shadow: 0px 0px 20px rgba(0, 0, 0, .6); 15 | } 16 | 17 | &:before, { 18 | margin-top: -10px; 19 | } 20 | 21 | &:after { 22 | margin-bottom: -10px; 23 | } 24 | 25 | [class*='col-'] { 26 | margin: { 27 | top: .75rem; 28 | bottom: .75rem; 29 | } 30 | 31 | &:nth-child(2) { 32 | padding-left: 1rem; 33 | border-left: 1px solid rgba(0, 0, 0, 0.12); 34 | } 35 | } 36 | 37 | :global .row { 38 | border-bottom: 1px solid rgba(0, 0, 0, 0.12); 39 | &:last-child { border-bottom: none; } 40 | } 41 | 42 | :global ul { 43 | background-color: transparent; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/calendar-rang/index.js: -------------------------------------------------------------------------------- 1 | // import { F7 } from 'commons' 2 | import load from 'promise?global,[name].promise!commons' 3 | import template from './template.jade' 4 | 5 | export let name = 'calendar-rang' 6 | 7 | export default res => load().then( ({ F7 }) => res({ 8 | props: [ 9 | {name: 'show', required: true}, 10 | {name: 'event', required: true}, 11 | ], 12 | template: template(), 13 | ready() { 14 | var calendar = F7.calendar({ 15 | onlyOnPopover: true, 16 | input: this.$els.calendar, 17 | dateFormat: 'dd M yyyy', 18 | rangePicker: true, 19 | onClose: this.onClose, 20 | disabled: { 21 | from: new Date() 22 | }, 23 | }) 24 | setTimeout(calendar.open, 10) 25 | }, 26 | methods: { 27 | onClose(calendar) { 28 | var event = 'dates:' + this.$get('event') 29 | if (calendar.value) 30 | this.$root.$broadcast(event, calendar.value) 31 | this.$set('show', false) 32 | }, 33 | } 34 | })) 35 | -------------------------------------------------------------------------------- /src/pages/bills/index/index.js: -------------------------------------------------------------------------------- 1 | import { getBillList } from 'vuex/actions' 2 | import dateMixin from 'mixins/filters/date' 3 | 4 | import template from './template.jade' 5 | import style from './style.scss' 6 | 7 | var name = 'bills-index' 8 | 9 | export default { 10 | name, 11 | mixins: [dateMixin], 12 | template: template({name, style}), 13 | 14 | vuex: { 15 | actions: { getBillList }, 16 | 17 | getters: { 18 | orderName: state => state.order.bills, 19 | date_at: state => state.bills.date_at, 20 | date_to: state => state.bills.date_to, 21 | bills: state => state.bills.data || [], 22 | }, 23 | }, 24 | 25 | events: { 26 | // search bill for number 27 | ['search:bills'](number) { 28 | this.$router.go({ 29 | name: 'bill/info', 30 | params: { number }, 31 | }) 32 | }, 33 | 34 | // search bills for date rang 35 | ['dates:bills'](dates) { 36 | this.getBillList(dates) 37 | }, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /src/components/bill-item/index.js: -------------------------------------------------------------------------------- 1 | import formatSumm from 'mixins/filters/formatSumm' 2 | import template from './template.jade' 3 | import style from './style.scss' 4 | 5 | export let name = 'bill-item' 6 | 7 | export default { 8 | name, 9 | props: [ 10 | 'date', 11 | 'number', 12 | 'summa', 13 | 'currency', 14 | 'comment', 15 | ], 16 | mixins: [formatSumm], 17 | template: template({name, style}), 18 | computed: { 19 | containerNumbers() { 20 | var pattern = /([A-Z]{4}[0-9]{7}(?![0-9]))/g 21 | var number = this.$route.params.number 22 | return this.$get('comment').match(pattern).filter( match => match !== number) 23 | }, 24 | }, 25 | filters: { 26 | toBillInfo(number) { 27 | return { 28 | name: 'bill/info', 29 | params: { number }, 30 | } 31 | }, 32 | 33 | toCntInfo(number) { 34 | return { 35 | name: 'container/info', 36 | params: { number }, 37 | } 38 | }, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/advance/index.js: -------------------------------------------------------------------------------- 1 | import formatSumm from 'mixins/filters/formatSumm' 2 | import dateMixin from 'mixins/filters/date' 3 | import template from './template.jade' 4 | 5 | var name = 'advance' 6 | 7 | export default { 8 | name, 9 | mixins: [dateMixin, formatSumm], 10 | template: template(), 11 | 12 | vuex: { 13 | getters: { 14 | advance: state => state.advance.advance || [], 15 | debt: state => state.advance.debt || [], 16 | summa: state => state.advance.summa, 17 | summa_bill: state => state.advance.summa_bill, 18 | orderName: state => state.order.advance, 19 | }, 20 | }, 21 | 22 | methods: { 23 | linkToBillInfo: number => ({ 24 | name: 'bill/info', 25 | params: { number }, 26 | }), 27 | }, 28 | 29 | filters: { 30 | toSummaTitle(summa = 0) { 31 | return this.$t('advance.advanceTitle', { summa }) 32 | }, 33 | 34 | toDateTo(date) { 35 | return this.$t('bills.subtitle', { date }) 36 | }, 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /src/components/panel-left/template.jade: -------------------------------------------------------------------------------- 1 | .panel.panel-left.panel-cover(:class="{'layout-dark': isIos}") 2 | a.item-link.close-panel.color-white.no-border( 3 | v-link="{name: 'profile'}", 4 | :style.once='style', 5 | valign='bottom', 6 | class=style.profile) 7 | .content-block.text-center 8 | img(v-if='src', :src='src', alt='') 9 | icon(v-else, id='profile') 10 | .content-block( 11 | v-if="companyName", 12 | class=style.name) {{ companyName }} 13 | .content-block(class=style.currency) {{ summa | formatSumm | summaTitle }} 14 | 15 | .list-group(v-for='pages in menu') 16 | .list-block.media-list.clear-both 17 | ul.icon-color-default 18 | li(v-for='page in pages') 19 | item-content-link.close-panel( 20 | v-link='page.link', 21 | :icon.once='page.icon', 22 | :title='$t(page.title)') 23 | span.badge.bg-red( 24 | v-if='page.badge', 25 | slot='after') {{ page.badge }} 26 | -------------------------------------------------------------------------------- /src/components/panel-left/style.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | 3 | :local .profile { 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: flex-end; 7 | color: #fff; 8 | 9 | height: 10rem; 10 | background-size: cover; 11 | background-position: center center; 12 | 13 | :global(.content-block) { 14 | margin: 0; 15 | } 16 | 17 | svg, img { 18 | height: 5rem; 19 | width: 5rem; 20 | border: 2px solid white; 21 | border-radius: 50%; 22 | background-color: $brend-color; 23 | } 24 | 25 | .name, .currency { 26 | line-height: 24px; 27 | text-shadow: 1px 1px 2px black; 28 | text-overflow: ellipsis; 29 | white-space: nowrap; 30 | overflow: hidden; 31 | } 32 | 33 | .name { 34 | font-size: 16px; 35 | } 36 | 37 | .currency { 38 | font-size: 12px; 39 | background-color: rgba(0,0,0, .5); 40 | } 41 | } 42 | 43 | // div.layout-dark { 44 | // color: #fff; 45 | // } 46 | 47 | .text-center { 48 | text-align: center; 49 | } 50 | -------------------------------------------------------------------------------- /src/components/navbar/index.js: -------------------------------------------------------------------------------- 1 | import load from 'promise?global,[name].promise!commons' 2 | import template from './template.jade' 3 | import style from './style.scss' 4 | 5 | export let name = 'navbar' 6 | 7 | export default res => load().then( ({ F7 }) => res({ 8 | props: { 9 | title: { type: String, default: 'index.company.shortName' }, 10 | state: { type: String, default: 'index' }, 11 | }, 12 | template: template({name, style}), 13 | 14 | vuex: { 15 | getters: { 16 | progress: state => state.progress.active, 17 | }, 18 | }, 19 | 20 | computed: { 21 | isIndex() { 22 | return this.state === 'index' 23 | }, 24 | 25 | icon() { 26 | return this.isIndex ? 'bars' : 'back' 27 | }, 28 | }, 29 | 30 | methods: { 31 | click() { 32 | this.$root.$broadcast('click:menu:bars') 33 | 34 | if (this.isIndex) { 35 | F7.openPanel('left') 36 | } else { 37 | this.$router.go(window.history.back()) 38 | } 39 | }, 40 | }, 41 | })) 42 | -------------------------------------------------------------------------------- /src/pages/login/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'Personal account', 3 | description: '*I agree to the interim storage of my username/password in the application, in turn, assure you that that the non-client will not peredovat your data to third parties.', 4 | username: { 5 | label: 'Username', 6 | placeholder: 'Your username', 7 | }, 8 | password: { 9 | label: 'Password', 10 | placeholder: 'Your password', 11 | }, 12 | btn: { 13 | login: 'Login', 14 | cancel: 'Cancel', 15 | }, 16 | } 17 | 18 | var ru = { 19 | title: 'Личный кабинет', 20 | description: '*Я согласен на промежуточное хранение моих логина/пароля в приложении, в свою очередь заверяем вас о том что неофициальный клиент не будет передовать Ваши данные третьим лицам.', 21 | username: { 22 | label: 'Логин', 23 | placeholder: 'Ваш логин', 24 | }, 25 | password: { 26 | label: 'Пароль', 27 | placeholder: 'Ваш пороль', 28 | }, 29 | btn: { 30 | login: 'Войти', 31 | cancel: 'Отмена', 32 | }, 33 | } 34 | 35 | export default {en, ru} 36 | -------------------------------------------------------------------------------- /src/images/icons-vsct/operations.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-preloader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/pages/index/index.js: -------------------------------------------------------------------------------- 1 | import style from './style.scss' 2 | import template from './template.jade' 3 | 4 | var name = 'index' 5 | 6 | export default { 7 | name, 8 | template: template({style, name}), 9 | 10 | vuex: { 11 | getters: { 12 | auth: state => state.auth.login, 13 | }, 14 | }, 15 | 16 | data: () => ({ 17 | countClick: 0, 18 | className: 'bounceIn', 19 | }), 20 | 21 | ready() { 22 | if (this.auth) { 23 | setTimeout(this.redirect, 1000) 24 | } 25 | }, 26 | 27 | methods: { 28 | openLangs() { 29 | this.$root.$broadcast('open:popup:langs', this.$els.lang) 30 | }, 31 | 32 | clickLogo() { 33 | var count = this.$get('countClick') 34 | this.$set('countClick', ++count) 35 | 36 | if ( !(count % 10) ) { 37 | this.$set('className', '') 38 | setTimeout( () => this.$set('className', 'bounceIn'), 10) 39 | } 40 | }, 41 | 42 | redirect() { 43 | var route = { 44 | name: 'tabs', 45 | params: {tab: 'containers'} 46 | } 47 | 48 | this.$router.go(route) 49 | }, 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /src/images/icons-f7/ios-preloader_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/pages/bills/info/index.js: -------------------------------------------------------------------------------- 1 | import { getBillInfo } from 'vuex/actions' 2 | import formatSumm from 'mixins/filters/formatSumm' 3 | 4 | import template from './template.jade' 5 | import style from './style.scss' 6 | 7 | var name = 'bill-info' 8 | 9 | export default { 10 | name, 11 | mixins: [formatSumm], 12 | template: template({name, style}), 13 | 14 | data: () => ({ 15 | number: '', 16 | }), 17 | 18 | route: { 19 | data() { 20 | var number = this.$route.params.number.trim() 21 | this.getBillInfo(number) 22 | return { number } 23 | }, 24 | }, 25 | 26 | vuex: { 27 | actions: { getBillInfo }, 28 | 29 | getters: { 30 | orderName: state => state.order.bill, 31 | bill: state => state.bill.data || [], 32 | total: state => state.bill.data.reduce( (total, item) => { 33 | return +total + +item.summands 34 | }, 0).toFixed(2), 35 | }, 36 | }, 37 | 38 | computed: { 39 | title() { 40 | return this.$t('bills.header', { 41 | number: this.$get('number') 42 | }) 43 | }, 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /src/images/operations/disconnection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/pages/index/style.scss: -------------------------------------------------------------------------------- 1 | @import '~styles/vars'; 2 | 3 | :local .page-content { 4 | background: $brend-gradient; 5 | } 6 | 7 | :local div.text-right { 8 | text-align: right; 9 | margin-top: 15px; 10 | } 11 | 12 | :local .world { 13 | color: white !important; 14 | opacity: .5; 15 | } 16 | 17 | :local div.logo { 18 | width: 100%; 19 | position: absolute; 20 | top: 50%; 21 | transform: translateY(-50%); 22 | } 23 | 24 | :local div.login { 25 | position: absolute; 26 | bottom: 0; 27 | width: 100%; 28 | // transform: translateY(-50%); 29 | } 30 | 31 | :local svg.logo-emblem { 32 | color: white; 33 | height: rem(75); 34 | width: 100%; 35 | } 36 | 37 | :local svg.logo-text { 38 | margin: rem(16 0); 39 | color: black; 40 | height: rem(65); 41 | width: 100%; 42 | use { 43 | // --text-ru-color: green; 44 | // --logo-line-color: white; 45 | // --text-en-color: red; 46 | } 47 | } 48 | 49 | :local .button { 50 | composes: button button-big from global; 51 | width: 100%; 52 | position: relative !important; 53 | bottom: 0; 54 | color: white !important; 55 | border-color: white !important; 56 | } 57 | -------------------------------------------------------------------------------- /src/styles/vars.scss: -------------------------------------------------------------------------------- 1 | 2 | $rem-base: 16px !default; 3 | 4 | // colors 5 | $brend-color: #D74747; 6 | $brend-gradient: radial-gradient($brend-color, darken($brend-color, 10%)); 7 | $icon-default: #c7c7cc; 8 | 9 | /********************************************* 10 | function from zurb foundation 11 | ==============================================*/ 12 | 13 | @function rem($values, $base-value: $rem-base) { 14 | $max: length($values); 15 | 16 | @if $max == 1 { @return convert-to-rem(nth($values, 1), $base-value); } 17 | 18 | $remValues: (); 19 | @for $i from 1 through $max { 20 | $remValues: append($remValues, convert-to-rem(nth($values, $i), $base-value)); 21 | } 22 | @return $remValues; 23 | } 24 | 25 | // CONVERT TO REM 26 | @function convert-to-rem($value, $base-value: $rem-base) { 27 | $value: strip-unit($value) / strip-unit($base-value) * 1rem; 28 | @if ($value == 0rem) { $value: 0; } // Turn 0rem into 0 29 | @return $value; 30 | } 31 | 32 | // STRIP UNIT 33 | // It strips the unit of measure and returns it 34 | @function strip-unit($num) { 35 | @return $num / ($num * 0 + 1); 36 | } 37 | -------------------------------------------------------------------------------- /src/images/containers/FL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/images/operations/delivery.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/services/fetch/bill-list.js: -------------------------------------------------------------------------------- 1 | import { fetchFactory, URL, fixtures } from './utils' 2 | 3 | var name = 'bill-list' 4 | 5 | export default fetchFactory({ 6 | url: URL[name], 7 | fixture: fixtures[name], 8 | parser: parseBillList, 9 | }) 10 | 11 | /** 12 | * Операция, произведенная с контейнером 13 | * @param {unixtime} date — дата выставления счета 14 | * @param {string} number — номер счета 15 | * @param {number} summa — сумма счета 16 | * @param {string} currency — валюта счета 17 | * @param {string} comment — комментарии к счету 18 | */ 19 | export class Bill { 20 | constructor(data) { 21 | this.date = data.date * 1000 22 | this.number = data.number 23 | this.summa = data.summa 24 | this.currency = data.currency 25 | this.comment = data.comment 26 | } 27 | } 28 | 29 | /** 30 | * @param {string} message — сообщение с сервера 31 | * @param — cпискок операций произведенных с указанным контейнером 32 | */ 33 | export function parseBillList({ data }) { 34 | return { 35 | message: data.message, 36 | data: data.table.map( item => new Bill(item) ), 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/pages/tabs-swipeable/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | //.toolbar-fixed 3 | Navbar 4 | template(slot='right') 5 | a.link.icon-only( 6 | v-if='showExtendNavbar', 7 | @click='onShowSearch', 8 | href='#search') 9 | icon(id='androidSearch') 10 | a.link.icon-only( 11 | v-if='showExtendNavbar', 12 | @click='onShowCalendar', 13 | href='#calendar') 14 | icon(id='calendar') 15 | a.link.icon-only( 16 | v-el:popup, 17 | @click='onShowOrder', 18 | href='#sort') 19 | icon(id='sort') 20 | 21 | Search-bar( 22 | v-if='show.search', 23 | :event='tab', 24 | :show.sync='show.search') 25 | 26 | Calendar-rang( 27 | :show.sync='show.calendar', 28 | :event='tab', 29 | v-if='show.calendar') 30 | 31 | include toolbar 32 | //- .page-content 33 | //- .tabs-swipeable-wrap 34 | .page-content.tabs.hide-toolbar-on-scroll 35 | .tab(v-el:containers)#tab-containers.active: Containers 36 | .tab(v-el:bills)#tab-bills: Bills 37 | .tab(v-el:advance)#tab-advance: Advance 38 | -------------------------------------------------------------------------------- /src/components/container-item-table/template.jade: -------------------------------------------------------------------------------- 1 | .content-block 2 | .row(v-if='type') 3 | .col-33 {{ $t('containers.types.title') }} 4 | .col-66 {{ $t('containers.types[\''+type+'\']') }} 5 | 6 | .row(v-if='load !== undefined') 7 | .col-33 {{ $t('containers.load.title') }} 8 | .col-66 {{ $t('containers.load[\''+load+'\']') }} 9 | 10 | .row(v-if='size') 11 | .col-33 {{ $t('containers.sizes.title') }} 12 | .col-66 {{ $t('containers.sizes[\''+size+'\']') }} 13 | 14 | .row(v-if='date_in') 15 | .col-33 {{ $t('containers.arrival') }} 16 | .col-66 {{* date_in | dateFull }} #[.badge {{* sklad_in | stock }}] 17 | 18 | .row(v-if='date_out') 19 | .col-33 {{ $t('containers.expense') }} 20 | .col-66 {{* date_out | dateFull }} #[.badge {{* sklad_out | stock }}] 21 | 22 | .row(v-if='transport') 23 | .col-33 {{ $t('containers.transport') }} 24 | .col-66 {{* transport }} 25 | 26 | .row(v-if='kontr') 27 | .col-33 {{ $t('containers.kontr') }} 28 | .col-66 {{* kontr }} 29 | 30 | .row(v-if='konos') 31 | .col-33 {{ $t('containers.konos') }} 32 | .col-66 {{* konos }} 33 | slot 34 | -------------------------------------------------------------------------------- /src/pages/settings/edit/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='settings.title' state='back') 3 | .page-content 4 | .content-block-title {{ $t('settings.titleNotify') }} 5 | form(v-el:form).list-block.inputs-list 6 | ul.icon-color-default 7 | li: item-content(icon='email') 8 | .floating-label(slot='title') {{ $t('settings.email') }} 9 | .item-input-field(slot='input') 10 | input( 11 | type='email', 12 | :value='settings.email') 13 | li: item-content(icon='call') 14 | .floating-label(slot='title') {{ $t('settings.phone') }} 15 | .item-input-field(slot='text') 16 | input(type='tel', 17 | :value='settings.phone') 18 | 19 | .content-block-title {{ $t('settings.avatar') }} 20 | .list-block.inputs-list 21 | ul.icon-color-default 22 | li: item-content(icon='email') 23 | .floating-label(slot='title') {{ $t('Email') }} 24 | .item-input-field(slot='text') 25 | input(type='email', 26 | :value='settings.gravatar_email') 27 | -------------------------------------------------------------------------------- /src/components/popover-order/index.js: -------------------------------------------------------------------------------- 1 | import load from 'promise?global,[name].promise!commons' 2 | import { changeOrder } from 'vuex/actions' 3 | 4 | import template from './template.jade' 5 | import style from './style.scss' 6 | 7 | export let name = 'popover-order' 8 | 9 | export default res => load().then( ({ F7 }) => res({ 10 | template: template({ name, style }), 11 | 12 | vuex: { 13 | actions: { changeOrder }, 14 | 15 | getters: { 16 | locales: state => state.locales, 17 | }, 18 | }, 19 | 20 | data: () => ({ 21 | name: null, 22 | }), 23 | 24 | computed: { 25 | sortList() { 26 | var name = this.$get('name') 27 | return name ? this.$get('locales')[name].sortList : {} 28 | } 29 | }, 30 | 31 | events: { 32 | ['open:popup:order'] ({ target, name }) { 33 | this.$set('name', name) 34 | F7.popover(this.$els.popover, target) 35 | } 36 | }, 37 | 38 | methods: { 39 | changeOrder(orderName) { 40 | this.changeOrder({ 41 | orderName, 42 | name: this.$get('name'), 43 | }) 44 | 45 | F7.closeModal(this.$els.popover) 46 | }, 47 | }, 48 | })) 49 | -------------------------------------------------------------------------------- /src/images/icons-vsct/containers.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/images/operations/MIDK.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/vuex/modules/profile.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import fetch from 'services/fetch' 3 | import { KONTR_INFO, SET_PROGRESS, CHECK_EXIT } from '../mutation-types' 4 | 5 | export let name = 'profile' 6 | 7 | var defaults = { 8 | message: '', 9 | data: {}, 10 | } 11 | 12 | // initial state 13 | export const state = Storage.get(name, defaults) 14 | 15 | // mutations 16 | export const mutations = { 17 | [KONTR_INFO](state, payload) { 18 | Object.assign(state, payload) 19 | Storage.set(name, state) 20 | }, 21 | 22 | [CHECK_EXIT](state) { 23 | state = Object.assign({}, defaults) 24 | }, 25 | } 26 | 27 | // actions 28 | export const actions = { 29 | getProfile({ actions, dispatch }) { 30 | 31 | dispatch(SET_PROGRESS, true) 32 | 33 | fetch.profile().then( payload => { 34 | dispatch(KONTR_INFO, payload) 35 | }).catch( error => { 36 | // reLogin !!! 37 | if (error.status === 401) { 38 | actions.reLogin({ 39 | callback: () => actions.getProfile() 40 | }) 41 | } 42 | return error 43 | }).then( () => { 44 | dispatch(SET_PROGRESS, false) 45 | }) 46 | }, 47 | } 48 | -------------------------------------------------------------------------------- /src/components/panel-left/index.js: -------------------------------------------------------------------------------- 1 | import load from 'promise?global,[name].promise!commons' 2 | import formatSumm from 'mixins/filters/formatSumm' 3 | 4 | import menu from './menu' 5 | import style from './style.scss' 6 | import template from './template.jade' 7 | 8 | export let name = 'panel-left' 9 | 10 | var image = '//vsct.info/assets/i/jpg/main_svcs_01.jpg' 11 | 12 | export default resolve => load().then( ({ isIos }) => resolve({ 13 | mixins: [formatSumm], 14 | template: template({name, style}), 15 | 16 | vuex: { 17 | getters: { 18 | companyName: state => state.auth.name_expeditor, 19 | summa: state => state.advance.summa, 20 | gravatar_hash: state => state.settings.gravatar_hash, 21 | }, 22 | }, 23 | 24 | data: () => ({isIos, menu, image}), 25 | 26 | computed: { 27 | src() { 28 | var hash = this.$get('gravatar_hash') 29 | return hash ? `//s.gravatar.com/avatar/${hash}?s=80` : null 30 | }, 31 | style() { 32 | return `background-image:url(${this.$get('image')})` 33 | }, 34 | }, 35 | filters: { 36 | summaTitle(summa = 0) { 37 | return this.$t('advance.advanceTitle', {summa}) 38 | }, 39 | } 40 | })) 41 | -------------------------------------------------------------------------------- /src/components/popover-lang/index.js: -------------------------------------------------------------------------------- 1 | import load from 'promise?global,[name].promise!commons' 2 | import { changeLang } from 'vuex/actions' 3 | 4 | import template from './template.jade' 5 | import style from './style.scss' 6 | 7 | export let name = 'popover-lang' 8 | 9 | export default res => load().then( ({ F7 }) => res({ 10 | template: template({name, style}), 11 | 12 | vuex: { 13 | actions: { changeLang }, 14 | 15 | getters: { 16 | langs: state => state.locales.langs 17 | }, 18 | }, 19 | 20 | events: { 21 | ['open:popup:langs'] (target) { 22 | F7.popover(this.$els.popover, target) 23 | }, 24 | ['open:actionSheet:langs'] (target) { 25 | this.openActionSheet() 26 | }, 27 | }, 28 | 29 | methods: { 30 | changeLang(lang) { 31 | F7.closeModal(this.$els.popover) 32 | this.changeLang(lang) 33 | }, 34 | 35 | openActionSheet() { 36 | var buttons = this.$get('langs').map( lang => ({ 37 | text: lang.value, 38 | onClick: () => this.changeLang(lang.key), 39 | })) 40 | 41 | buttons.unshift({text: this.$t('settings.change.lang'), label: true}) 42 | F7.actions(buttons) 43 | } 44 | }, 45 | })) 46 | -------------------------------------------------------------------------------- /src/vuex/modules/news.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import fetch from 'services/fetch' 3 | import { UPDATE_NEWS, SET_PROGRESS, CHECK_EXIT } from '../mutation-types' 4 | 5 | export let name = 'news' 6 | 7 | var defaults = { 8 | list: [], 9 | count: 0, 10 | new_count: 0, 11 | } 12 | 13 | // initial state 14 | export const state = Storage.get(name, defaults) 15 | 16 | // mutations 17 | export const mutations = { 18 | [UPDATE_NEWS](state, data) { 19 | state.list = data 20 | state.new_count = data.length - state.count 21 | state.count = data.length 22 | 23 | Storage.set(name, state) 24 | }, 25 | 26 | [CHECK_EXIT](state) { 27 | state = Object.assign({}, defaults) 28 | }, 29 | } 30 | 31 | // actions 32 | export const actions = { 33 | // fetch news list from rss 34 | updateNews({ dispatch }) { 35 | 36 | // disallow headers in fetch 37 | var settings = { headers: null } 38 | 39 | dispatch(SET_PROGRESS, true) 40 | 41 | fetch.news(settings).then( data => { 42 | return dispatch(UPDATE_NEWS, data) 43 | }).catch( error => { 44 | return error 45 | }).then( () => { 46 | dispatch(SET_PROGRESS, false) 47 | }) 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /src/vuex/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_LOCAL = 'CHANGE_LOCAL' 2 | export const SET_SETTINGS = 'SET_SETTINGS' 3 | export const TOGGLE_NOTIFY_BY = 'TOGGLE_NOTIFY_BY' 4 | export const SET_PROGRESS = 'SET_PROGRESS' 5 | export const CHANGE_ORDER = 'CHANGE_ORDER' 6 | export const UPDATE_NEWS = 'UPDATE_NEWS' 7 | export const RESET_AUTH = 'RESET_AUTH_MESSAGE' 8 | 9 | // Список контейнеров, записи по которым были 10 | // внесены в систему за указанный период времени 11 | export const CNT_LIST = 'CNT_LIST' 12 | 13 | // Получение подробной информации о контейнере 14 | export const CNT_INFO = 'CNT_INFO' 15 | 16 | // Список операций произведенных с указанным контейнером 17 | export const BILL_LIST = 'BILL_LIST' 18 | 19 | // Список товаров и услуг, включенных в указанный счет 20 | export const BILL_INFO = 'BILL_INFO' 21 | 22 | // Подробная информация о контрагенте 23 | export const KONTR_INFO = 'KONTR_INFO' 24 | 25 | // Список авансовых платежей, поступивших от клиента, 26 | // а также дебиторской задолженности клиента 27 | export const ADVANCE_INFO = 'ADVANCE_INFO' 28 | 29 | // Вход в систему 30 | export const CHECK_LOGIN = 'CHECK_LOGIN' 31 | 32 | // Выход из системы и завершения (удаления) сессии 33 | export const CHECK_EXIT = 'CHECK_EXIT' 34 | -------------------------------------------------------------------------------- /src/pages/profile/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'Profile', 3 | 4 | name: 'Company name', 5 | full_name: 'The full name of the counterparty', 6 | INN: 'INN contractor', 7 | KPP: 'CAT contractor', 8 | OKPO: 'Counterparty of Enterprise', 9 | Jur_address: 'Legal company address', 10 | Fact_address: 'The actual address of the counterparty', 11 | Phone: 'Counterparty Phones', 12 | bank_account: 'Settlement counterparty account number', 13 | corr_account: 'Number corr. accounts', 14 | Bank_name: 'Name of the bank where the account', 15 | BIK: 'BIC', 16 | Bank_address: 'Bank Address', 17 | } 18 | 19 | var ru = { 20 | title: 'Профиль', 21 | 22 | name: 'Наименование контрагента', 23 | full_name: 'Полное наименование контрагента', 24 | INN: 'ИНН контрагента', 25 | KPP: 'КПП контрагента', 26 | OKPO: 'ОКПО контрагента', 27 | Jur_address: 'Юридический адрес контрагента', 28 | Fact_address: 'Фактический адрес контрагента', 29 | Phone: 'Телефоны контрагента', 30 | bank_account: 'Номер расчетного счета контрагента', 31 | corr_account: 'Номер корр. счета', 32 | Bank_name: 'Наименование банка в котором открыт счет', 33 | BIK: 'БИК банка', 34 | Bank_address: 'Адрес банка', 35 | } 36 | 37 | export default {en, ru} 38 | -------------------------------------------------------------------------------- /src/services/fetch/settings.js: -------------------------------------------------------------------------------- 1 | import { fetchFactory, URL, fixtures } from './utils' 2 | 3 | var name = 'settings' 4 | 5 | export default fetchFactory({ 6 | url: URL[name], 7 | fixture: fixtures[name], 8 | parser: parseSettings, 9 | }) 10 | 11 | /** 12 | * Настройки аккаунта 13 | * @param {number} phone — номер телефона для нотификаций 14 | * @param {string} email — email для нотификаций 15 | * @param {string} gravatar_email — email для аватарки с сервиса http://gravatar.com 16 | * @param {string} gravatar_hash — хеш для аватарки с сервиса http://gravatar.com 17 | * @param notifyBy — нотификация о входе в аккаунт 18 | */ 19 | export class Settings { 20 | constructor(data) { 21 | this.phone = data.phone 22 | this.email = data.email 23 | this.gravatar_email = data.gravator_email 24 | this.gravatar_hash = data.gravator_hash 25 | 26 | // TODO: notifyBy on server side 27 | this.notifyBy = { 28 | email: false, 29 | phone: false, 30 | gravatar_email: false, 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * @param {string} message — сообщение 37 | * @param {Settings} data — настройки аккаунта 38 | */ 39 | function parseSettings({ data }) { 40 | return new Settings(data) 41 | } 42 | -------------------------------------------------------------------------------- /src/fixtures/container-info.js: -------------------------------------------------------------------------------- 1 | import { randomNumb, randomDate, randomChar } from './utils' 2 | import load from 'promise?global,[name].promise!icons' 3 | 4 | var OPERATIONS 5 | const EXPAND = ['arrival', 'expense'] 6 | const KONTR = 'Demo kontragent' 7 | 8 | /** 9 | * Операция с контейнером 10 | * @param {string} operation — тип операции 11 | * @param {unixtime} date — дата операции 12 | * @param {string} transport — название транспорта, с которым прибыл (убыл) контейнер 13 | * @param {string} kontr — наименование контрагента 14 | */ 15 | export class ContainerInfo { 16 | constructor() { 17 | this.operation = OPERATIONS[randomNumb(5) % OPERATIONS.length] 18 | this.date = randomDate() 19 | 20 | if ( EXPAND.some( type => type === this.type ) ) { 21 | this.transport = randomChar(8) 22 | this.kontr = KONTR 23 | } 24 | } 25 | } 26 | 27 | /** 28 | * @param {string} message — сообщение с сервера 29 | * @param data - операции над контейнером 30 | */ 31 | export default () => load().then( ({ operations }) => { 32 | OPERATIONS = Object.keys(operations) 33 | return { 34 | message: 'Fixture container info', 35 | data: Array.from(Array(2 * randomNumb(1)), () => new ContainerInfo()), 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /src/vuex/modules/advance.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import fetch from 'services/fetch' 3 | import { ADVANCE_INFO, CHECK_EXIT, SET_PROGRESS } from '../mutation-types' 4 | 5 | export let name = 'advance' 6 | 7 | var defaults = { 8 | message: '', 9 | summa: '', 10 | summa_bill: '', 11 | advance: [], 12 | debt: [], 13 | } 14 | 15 | // initial state 16 | export const state = Storage.get(name, defaults) 17 | 18 | // mutations 19 | export const mutations = { 20 | [ADVANCE_INFO](state, payload) { 21 | state = Object.assign(state, payload) 22 | Storage.set(name, state) 23 | }, 24 | 25 | [CHECK_EXIT](state) { 26 | state = Object.assign({}, defaults) 27 | }, 28 | } 29 | 30 | // actions 31 | export const actions = { 32 | getAdvance({ /*actions,*/ dispatch }) { 33 | dispatch(SET_PROGRESS, true) 34 | 35 | fetch.advance().then( payload => { 36 | dispatch(ADVANCE_INFO, payload) 37 | }).catch( error => { 38 | // reLogin !!! 39 | if (error.status === 401) { 40 | actions.reLogin({ 41 | callback: () => actions.getAdvance() 42 | }) 43 | } 44 | return error 45 | }).then( () => { 46 | dispatch(SET_PROGRESS, false) 47 | }) 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /src/pages/bills/i18n.js: -------------------------------------------------------------------------------- 1 | var en = { 2 | title: 'Bills', 3 | header: 'BILL {number} ', 4 | subtitle: 'at {date}', 5 | 6 | info: { 7 | title: '{summa} RUB.', 8 | }, 9 | 10 | sortList: { 11 | date: 'By date', 12 | number: 'By number', 13 | summa: 'By sum', 14 | }, 15 | 16 | count: 'Count', 17 | price: 'Price', 18 | summa: 'Sum', 19 | NDS: 'VAT', 20 | summands: 'Total with VAT', 21 | 22 | empty: { 23 | bills: 'During the period from {0} at {1} not billed', 24 | dates: 'Choose the date', 25 | }, 26 | 27 | total: 'Total {total} RUB.', 28 | more: 'MORE ABOUT BILL', 29 | } 30 | 31 | var ru = { 32 | title: 'Счета', 33 | header: 'СЧЕТ {number} ', 34 | subtitle: 'от {date}', 35 | 36 | info: { 37 | title: '{summa} РУБ.', 38 | }, 39 | 40 | sortList: { 41 | date: 'По дате', 42 | number: 'По номеру', 43 | summa: 'По сумме', 44 | }, 45 | 46 | count: 'Количество', 47 | price: 'Цена', 48 | summa: 'Сумма', 49 | NDS: 'НДС', 50 | summands: 'Итого с НДС', 51 | 52 | empty: { 53 | bills: 'За период с {0} по {1} счета не выставлялись', 54 | dates: 'Выбирите даты', 55 | }, 56 | 57 | total: 'ВСЕГО {total} РУБ.', 58 | more: 'ПОДРОБНЕЕ О СЧЕТЕ', 59 | } 60 | 61 | export default { en, ru } 62 | -------------------------------------------------------------------------------- /src/images/operations/inspection-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 9 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/images/operations/inspection-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 10 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/pages/advance/template.jade: -------------------------------------------------------------------------------- 1 | .list-group 2 | 3 | .list-block.media-list.clear-both 4 | ul(v-show='summa_bill && summa') 5 | li: item-content( 6 | :title="$t('advance.summa_bill')") 7 | b(slot='subtitle') {{ summa_bill | formatSumm | toSummaTitle }} 8 | li: item-content( 9 | :title="$t('advance.summa')") 10 | b(slot='subtitle') {{ summa | formatSumm | toSummaTitle }} 11 | 12 | template(v-if='advance.length') 13 | .content-block-title {{ $t('advance.advance') }} 14 | .list-block.media-list.clear-both 15 | ul: li(v-for='item in advance | orderBy orderName') 16 | item-content( 17 | :title='item.summa | formatSumm | toSummaTitle') 18 | .badge.badge-clear(slot='after') {{* item.date | dateFull }} 19 | 20 | template(v-if='debt.length') 21 | .content-block-title {{ $t('advance.debt') }} 22 | .list-block.media-list.clear-top 23 | ul: li.accordion-item(v-for="bill in debt | orderBy orderName") 24 | item-content-link( 25 | :title.once="$t('bills.header', {number: bill.number})", 26 | :subtitle=' bill.date | dateFull | toDateTo', 27 | v-link="linkToBillInfo(bill.number)") 28 | .badge.badge-clear(slot='after') {{* bill.summa | formatSumm | toSummaTitle }} 29 | -------------------------------------------------------------------------------- /src/images/operations/connection.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/services/CacheList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * cache list (bills or containers) by [dates] 3 | * @param {String} name — имя кеша 4 | * @param {BillList | ContainerList} _last — закешированный последний запрос 5 | * @param _data — кеш списка счетов или контейнеров 6 | */ 7 | export default class CacheList { 8 | constructor(name) { 9 | this.name = name 10 | this._data = [] 11 | this._last = {} 12 | } 13 | 14 | set({ date_at, date_to, data }) { 15 | this._data.push({ date_at, date_to, data }) 16 | } 17 | 18 | get({ date_at, date_to }) { 19 | return this.isIn({ date_at, date_to }) ? { 20 | date_at, date_to, 21 | data: this.isIn({ date_at, date_to }), 22 | message: `Get ${ this.name } ${ date_at }, ${ date_to } from cache`, 23 | } : null 24 | } 25 | 26 | isIn({ date_at, date_to }) { 27 | 28 | if ((this._last.date_at === date_at) && (this._last.date_at === date_at)) { 29 | return this._last.data 30 | } 31 | 32 | var data = this._data.find( item => { 33 | return (item.date_at === date_at) && (item.date_to === date_to) 34 | }) 35 | 36 | // cached last item for fast find 37 | // TODO: date_request 38 | this._last = { date_at, date_to, data } 39 | 40 | return data 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/services/fetch/bill-info.js: -------------------------------------------------------------------------------- 1 | import { fetchFactory, URL, fixtures } from './utils' 2 | 3 | var name = 'bill-info' 4 | 5 | export default fetchFactory({ 6 | url: URL[name], 7 | fixture: fixtures[name], 8 | parser: parseBillInfo, 9 | }) 10 | 11 | /** 12 | * Список товаров и услуг, включенных в указанный счет. 13 | * @param {string} tovar — наименование товара или услуги 14 | * @param {number} count — количество товаров или услуг 15 | * @param {number} price — цена за единицу товара или услуги 16 | * @param {number} summa — сумма по строке 17 | * @param {number} NDS — сумма НДС по строке 18 | * @param {number} summands — сумма с НДС по строке 19 | */ 20 | export class BillInfo { 21 | constructor(data) { 22 | this.tovar = data.tovar 23 | this.count = data.count 24 | this.price = data.price 25 | this.summa = data.summa 26 | this.NDS = data.NDS 27 | this.summands = data.summaNDS 28 | } 29 | } 30 | 31 | /** 32 | * Подробная информация о счете 33 | * @param {string} message — сообщение с сервера 34 | * @param {BillInfo} debt — список товаров и услуг, включенных в указанный счет 35 | */ 36 | export function parseBillInfo({ data }) { 37 | return { 38 | message: data.message, 39 | data: data.table.map( item => new BillInfo(item) ), 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/fixtures/bill-list.js: -------------------------------------------------------------------------------- 1 | import { randomDate, randomNumb, randomChar, ContainerNumb } from './utils' 2 | 3 | const CURRENCY = ['РУБ', '$'] 4 | const COURSE = 89.12 5 | const COMMENT = `Курс: 78,9969(28.01.2016): ${ContainerNumb()}; ${ContainerNumb()}; ${ContainerNumb()}; ${ContainerNumb()} [Черниковка] | Курс валюты: 78,9969` 6 | 7 | /** 8 | * Операция, произведенная с контейнером 9 | * @param {unixtime} date — дата выставления счета 10 | * @param {string} number — номер счета 11 | * @param {number} summa — сумма счета 12 | * @param {string} currency — валюта счета 13 | * @param {string} comment — комментарии к счету 14 | */ 15 | export class Bill { 16 | constructor() { 17 | this.date = randomDate() 18 | this.number = `${randomChar(1)}-${randomNumb(9)}` 19 | this.currency = CURRENCY[randomNumb() % CURRENCY.length] 20 | this.summa = this.currency === '$' 21 | ? (randomNumb(7) / 100 / COURSE).toFixed(2) : randomNumb(7) / 100 22 | this.comment = COMMENT 23 | } 24 | } 25 | 26 | /** 27 | * @param {string} message — сообщение с сервера 28 | * @param data — cпискок операций произведенных с указанным контейнером 29 | */ 30 | export default () => new Promise(resolve => resolve({ 31 | message: 'Fixture bill list', 32 | data: Array.from(Array(2 * randomNumb(1)), () => new Bill()), 33 | })) 34 | -------------------------------------------------------------------------------- /src/images/icons-ionic.js: -------------------------------------------------------------------------------- 1 | import call from 'images/icons-ionic/android-call.svg' 2 | import create from 'images/icons-ionic/android-create.svg' 3 | import locate from 'images/icons-ionic/android-locate.svg' 4 | import settings from 'images/icons-ionic/android-settings.svg' 5 | import star from 'images/icons-ionic/android-star-outline.svg' 6 | import bug from 'images/icons-ionic/bug.svg' 7 | import calendar from 'images/icons-ionic/calendar.svg' 8 | // import clip from 'images/icons-ionic/clip.svg' 9 | import codeWorking from 'images/icons-ionic/code-working.svg' 10 | import contacts from 'images/icons-ionic/contacts.svg' 11 | import email from 'images/icons-ionic/email.svg' 12 | import informationCircled from 'images/icons-ionic/information-circled.svg' 13 | import world from 'images/icons-ionic/ios-world.svg' 14 | import logout from 'images/icons-ionic/logout.svg' 15 | import news from 'images/icons-ionic/news.svg' 16 | import paperAirplane from 'images/icons-ionic/paper-airplane.svg' 17 | import sort from 'images/icons-ionic/sort.svg' 18 | 19 | export default { 20 | call, 21 | create, 22 | locate, 23 | settings, 24 | star, 25 | bug, 26 | calendar, 27 | // clip, 28 | codeWorking, 29 | contacts, 30 | email, 31 | informationCircled, 32 | world, 33 | logout, 34 | news, 35 | paperAirplane, 36 | sort, 37 | } 38 | -------------------------------------------------------------------------------- /src/vuex/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * export all action by name 3 | */ 4 | 5 | const actions = requireAll(require.context('./modules/', false, /\.js$/)) 6 | 7 | const { 8 | changeLang, 9 | changeOrder, 10 | clearAuth, 11 | getAdvance, 12 | getBillInfo, 13 | getBillList, 14 | getCntInfo, 15 | getCntList, 16 | getProfile, 17 | getSettings, 18 | login, 19 | logout, 20 | reLogin, 21 | setLocal, 22 | setProgress, 23 | toggleNotify, 24 | updateNews, 25 | updateSettings, 26 | } = actions 27 | 28 | export default actions 29 | 30 | export { 31 | changeLang, 32 | changeOrder, 33 | clearAuth, 34 | getAdvance, 35 | getBillInfo, 36 | getBillList, 37 | getCntInfo, 38 | getCntList, 39 | getProfile, 40 | getSettings, 41 | login, 42 | logout, 43 | reLogin, 44 | setLocal, 45 | setProgress, 46 | toggleNotify, 47 | updateNews, 48 | updateSettings, 49 | } 50 | 51 | /************************************************ 52 | helpers 53 | ===============================================*/ 54 | 55 | // https://webpack.github.io/docs/context.html#require-context 56 | function requireAll(requireContext) { 57 | return requireContext.keys().reduce( (actions, file) => { 58 | return Object.assign(actions, requireContext(file).actions) 59 | }, {}) 60 | } 61 | -------------------------------------------------------------------------------- /src/pages/login/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).no-navbar.no-toolbar.no-swipeback 2 | .page-content.login-screen-content 3 | .login-screen-title {{ $t('login.title') }} 4 | form(@submit.stop.prevent='onSubmit') 5 | .list-block.inputs-list 6 | .list-block-label {{{ $t('index.company.name') }}} 7 | ul 8 | li.item-content 9 | .item-inner 10 | .item-title.label {{ $t('login.username.label') }} 11 | .item-input 12 | input(type='text', autocomplete='off', 13 | @focus='onFocus', v-model!='login', 14 | :placeholder.once="$t('login.username.placeholder')") 15 | li.item-content 16 | .item-inner 17 | .item-title.label {{ $t('login.password.label') }} 18 | .item-input 19 | input(type='password', autocomplete='off', 20 | @focus='onFocus', v-model!='password', 21 | :placeholder.once="$t('login.username.placeholder')") 22 | .content-block: .list-block-label.color-red {{ message }} 23 | .content-block.color-gray {{ $t('login.description') }} 24 | .content-block 25 | .row 26 | .col-50: a(class=style.cancel, v-link="{name: 'index'}") 27 | | {{ $t('login.btn.cancel') }} 28 | .col-50: button(class=style.login, type='submit') 29 | | {{ $t('login.btn.login') }} 30 | -------------------------------------------------------------------------------- /src/components/panel-left/menu.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | [ 3 | { 4 | title: 'containers.title.main', 5 | link: { 6 | name: 'tabs', 7 | params: {tab: 'containers'}, 8 | }, 9 | icon: 'containers', 10 | }, 11 | { 12 | title: 'bills.title', 13 | link: { 14 | name: 'tabs', 15 | params: {tab: 'bills'}, 16 | }, 17 | icon: 'bills', 18 | }, 19 | { 20 | title: 'advance.title', 21 | link: { 22 | name: 'tabs', 23 | params: {tab: 'advance'}, 24 | }, 25 | icon: 'advance', 26 | } 27 | ], 28 | 29 | [ 30 | { 31 | title: 'news.title', 32 | link: {name: 'news'}, 33 | icon: 'news', 34 | }, 35 | { 36 | title: 'contacts.title', 37 | link: {name: 'contacts'}, 38 | icon: 'contacts', 39 | }, 40 | { 41 | title: 'feedback.title', 42 | link: {name: 'feedback'}, 43 | icon: 'paperAirplane', 44 | } 45 | ], 46 | 47 | [ 48 | { 49 | title: 'settings.title', 50 | link: {name: 'settings'}, 51 | icon: 'settings', 52 | }, 53 | { 54 | title: 'about.title', 55 | link: {name: 'about'}, 56 | icon: 'informationCircled', 57 | }, 58 | { 59 | title: 'logout.title', 60 | link: {name: 'logout'}, 61 | icon: 'logout,' 62 | } 63 | ] 64 | ] 65 | -------------------------------------------------------------------------------- /src/images/operations/weighing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/pages/contacts/index.js: -------------------------------------------------------------------------------- 1 | import * as dryTerminal from './dryTerminal' 2 | import * as seaTerminal from './seaTerminal' 3 | 4 | import template from './template.jade' 5 | 6 | var name = 'contacts' 7 | var typeList = { 8 | email: { 9 | icon: 'email', 10 | href: 'mailto:', 11 | }, 12 | phone: { 13 | icon: 'call', 14 | href: 'tel:', 15 | }, 16 | address: { 17 | icon: 'locate', 18 | href: 'http://maps.google.com?q=', 19 | }, 20 | } 21 | 22 | export default { 23 | name, 24 | template: template({ name }), 25 | 26 | data: () => ({ 27 | terminals: [seaTerminal, dryTerminal] 28 | }), 29 | 30 | filters: { 31 | // TODO: remove it to utils 32 | normilize(list) { 33 | return list.map( item => { 34 | var type = item.type 35 | item.component = 'item-content' 36 | 37 | if (type !== undefined) { 38 | item.icon = 'empty' 39 | } 40 | 41 | if (Object.keys(typeList).indexOf(type) !== -1) { 42 | item.component = 'item-content-link' 43 | item.icon = typeList[type].icon 44 | item.href = typeList[type].href + item.text 45 | item.classLink = 'external' 46 | } 47 | 48 | if (item.content) { 49 | item.component = 'item-content-accordion' 50 | item.class = 'accordion-item' 51 | } 52 | 53 | return item 54 | }) 55 | }, 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /src/fixtures/auth.js: -------------------------------------------------------------------------------- 1 | import { randomDate, randomNumb, randomChar } from './utils' 2 | 3 | export default { login, logout } 4 | 5 | /** 6 | * @param {string} secret — ключ для идентификатора сессии 7 | * @param {string} message — сообщение 8 | * @param {boolean} ready_check, истина — авторизация пройдена 9 | * @param {string} login — логин (учетное имя) пользователя 10 | * @param {dateTime} create_at — дата и время создания сессии 11 | * @param {boolean} check_cnt, истина — пользователь имеет доступ к информации о контейнерах 12 | * @param {boolean} check_doc, истина — пользователь имеет доступ к информации о финансовых документах 13 | * @param {boolean} check_zayv, истина — пользователь имеет доступ к информации о заявках 14 | * @param {string} name_expeditor — название контрагента (экспедитора), присвоенного пользователю 15 | */ 16 | export function login() { 17 | return new Promise( resolve => resolve({ 18 | secret: randomChar(30), 19 | message: 'Авторизация пройдена', 20 | ready_check: randomNumb() % 2, 21 | create_at: randomDate(), 22 | login: 'demo', 23 | check_cnt: randomNumb() % 2, 24 | check_doc: randomNumb() % 2, 25 | check_zayav: randomNumb() % 2, 26 | name_expeditor: 'Demo name', 27 | })) 28 | } 29 | 30 | /** 31 | * @param {string} message — сообщение 32 | */ 33 | export function logout() { 34 | return new Promise( resolve => resolve({ 35 | message: 'Успешно разлогинелись', 36 | })) 37 | } 38 | -------------------------------------------------------------------------------- /src/images/icons-vsct/bills.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/images/icons-vsct/size.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | -------------------------------------------------------------------------------- /src/pages/login/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | getAdvance, 3 | getProfile, 4 | getSettings, 5 | login, 6 | clearAuth, 7 | } from 'vuex/actions' 8 | 9 | import template from './template.jade' 10 | import style from './style.scss' 11 | 12 | var name = 'login' 13 | 14 | export default { 15 | name, 16 | template: template({style, name}), 17 | 18 | data: () => ({ 19 | login: null, 20 | password: null, 21 | }), 22 | 23 | vuex: { 24 | actions: { 25 | getAdvance, 26 | getProfile, 27 | getSettings, 28 | authLogin: login, 29 | clearAuth, 30 | }, 31 | 32 | getters: { 33 | auth: state => state.auth.login, 34 | message: state => state.auth.message, 35 | } 36 | }, 37 | 38 | ready() { 39 | this.clearAuth('message') 40 | 41 | if (this.$get('auth')) { 42 | setTimeout(this.redirect, 1000) 43 | } 44 | }, 45 | 46 | methods: { 47 | onFocus() { 48 | this.clearAuth('message') 49 | }, 50 | 51 | onSubmit() { 52 | this.authLogin({ 53 | login: this.$get('login'), 54 | password: this.$get('password'), 55 | // redirect if auth ok 56 | callback: this.redirect, 57 | }) 58 | }, 59 | 60 | redirect() { 61 | this.getAdvance() 62 | this.getProfile() 63 | this.getSettings() 64 | 65 | var route = { 66 | name: 'tabs', 67 | params: {tab: 'containers'} 68 | } 69 | 70 | this.$router.go(route) 71 | }, 72 | }, 73 | } 74 | -------------------------------------------------------------------------------- /src/images/icons-vsct/bills-1.svg: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/pages/containers/info/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(:title='title', state='back') 3 | .page-content 4 | 5 | .content-block-title {{ $t('containers.title.info') }} 6 | 7 | // show if container find 8 | .list-block.media-list(v-if='container.length') 9 | ul.icon-color-brend(class=style.title) 10 | li(v-for='operation in container | orderBy orderName', 11 | :class="{'accordion-item': operation.kontr}") 12 | operation-item( 13 | :operation.once='operation.operation', 14 | :date='operation.date | date', 15 | :kontr.once='operation.kontr', 16 | :transport.once='operation.transport') 17 | .content-block(v-else) 18 | p {{ $t('containers.title.infoEmpty') }} 19 | 20 | // show if container load 21 | template(v-if='container.length') 22 | .content-block-title {{ $t('containers.title.bills') }} 23 | 24 | // show if billed 25 | .list-block.media-list(v-if='billsFiltered.length') 26 | ul.icon-color-brend(class=style.title) 27 | li.accordion-item(v-for="bill in billsFiltered | orderBy orderName") 28 | bill-item( 29 | :date='bill.date | dateFull', 30 | :number.once='bill.number', 31 | :summa.once='bill.summa', 32 | :currency.once='bill.currency', 33 | :comment.once='bill.comment') 34 | 35 | .content-block(v-else) 36 | p {{ $t('containers.title.billsEmpty') }} 37 | -------------------------------------------------------------------------------- /src/images/operations/nomination.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/fixtures/advance.js: -------------------------------------------------------------------------------- 1 | import { randomDate, randomChar, randomNumb } from './utils' 2 | 3 | /** 4 | * Информация о платеже 5 | * @param {unixtime} date: истина — дата платежа 6 | * @param {number} summa — сумма платежа 7 | */ 8 | class Advance { 9 | constructor() { 10 | this.date = randomDate() 11 | this.summa = randomNumb(7) / 100 12 | } 13 | } 14 | 15 | /** 16 | * Счет дебетовой задолжности 17 | * @param {number} summa — сумма задолженности (может на совпадать со суммой по счету, например счет оплачивался частично, покажет только остаток задолженности по счету) 18 | * @param {string} number — номер счета 19 | * @param {unixtime} date — дата счета (дата возникновения задолженности) 20 | */ 21 | class Debt { 22 | constructor() { 23 | this.number = `${randomChar(1)}-${randomNumb(9)}` 24 | this.date = randomDate() 25 | this.summa = randomNumb(7) / 100 26 | } 27 | } 28 | 29 | /** 30 | * @param {string} message — сообщение с сервера 31 | * @param {number} summa — текущий баланс 32 | * @param {number} summa_bill — баланс с учетом выставленных счетов 33 | * @param advance — список платежей 34 | * @param debt — список счетов дебетовой задолжности 35 | */ 36 | export default () => new Promise( resolve => resolve({ 37 | message: 'Fixture advance info', 38 | summa: randomNumb(7) / 100, 39 | summa_bill: randomNumb(7) / 100, 40 | advance: Array.from(Array(2 * randomNumb(1)), () => new Advance()), 41 | debt: Array.from(Array(2 * randomNumb(1)), () => new Debt()), 42 | })) 43 | -------------------------------------------------------------------------------- /src/images/icons-ionic/bug.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/images/operations/unstuffing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/images/operations/stuffing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/images/icons-vsct/types.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/images/icons-vsct/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/vuex/modules/locales.js: -------------------------------------------------------------------------------- 1 | import { moment } from 'commons' 2 | import Storage from 'services/Storage' 3 | import { CHANGE_LOCAL } from '../mutation-types' 4 | 5 | export let name = 'locales' 6 | 7 | const defaults = { 8 | lang: 'en', 9 | langs: [ 10 | { key: 'en', value: 'English' }, 11 | { key: 'ru', value: 'Русский' }, 12 | ], 13 | current: {}, 14 | en: {}, 15 | ru: {}, 16 | } 17 | 18 | // parse all i18n from page/ to default 19 | requireAll(require.context('pages/', true, /i18n\.js$/)) 20 | 21 | // initial state 22 | export const state = Storage.get(name, defaults) 23 | 24 | state.current = defaults[state.lang] 25 | 26 | // initial local 27 | moment.locale(state.lang) 28 | 29 | // mutations 30 | export const mutations = { 31 | [CHANGE_LOCAL](state, lang) { 32 | state.lang = lang 33 | state.current = state[lang] 34 | Storage.set(name, state) 35 | }, 36 | } 37 | 38 | // actions 39 | export const actions = { 40 | changeLang({ dispatch }, lang) { 41 | moment.locale(lang) 42 | dispatch(CHANGE_LOCAL, lang) 43 | }, 44 | } 45 | 46 | /************************************************ 47 | helpers 48 | ===============================================*/ 49 | 50 | // https://webpack.github.io/docs/context.html#require-context 51 | function requireAll(requireContext) { 52 | return requireContext.keys().reduce( (locals, file) => { 53 | var local = requireContext(file).default 54 | var name = file.match(/\.\/(.+?)\/i18n\.js/)[1] 55 | 56 | locals.en[name] = local.en 57 | locals.ru[name] = local.ru 58 | 59 | return locals 60 | }, defaults) 61 | } 62 | -------------------------------------------------------------------------------- /src/pages/settings/index/template.jade: -------------------------------------------------------------------------------- 1 | Page(v-el:page name=name).navbar-fixed 2 | Navbar(title='settings.title') 3 | .page-content 4 | .content-block-title {{ $t('settings.titleNotify') }} 5 | .list-block.media-list.clear-top 6 | ul.icon-color-default 7 | li: item-content( 8 | icon='email', 9 | :title='settings.email') 10 | label(slot='after').label-switch 11 | input(type='checkbox', 12 | name='email', 13 | @change='toggleNotify', 14 | :checked='settings.notifyBy.email') 15 | .checkbox 16 | li: item-content( 17 | icon='call', 18 | :title='settings.phone') 19 | label(slot='after').label-switch 20 | input(type='checkbox', 21 | name='phone', 22 | @change='toggleNotify', 23 | :checked='settings.notifyBy.phone') 24 | .checkbox 25 | li.item-divider 26 | item-content-link.external( 27 | href='//gravatar.com', 28 | :title="$t('settings.avatar')") 29 | li: item-content( 30 | icon='email', 31 | :title='settings.gravatar_email') 32 | label(slot='after').label-switch 33 | input(type='checkbox', 34 | name='gravatar_email', 35 | @change='toggleNotify', 36 | :checked='settings.notifyBy.gravatar_email') 37 | .checkbox 38 | li: item-content( 39 | icon='world', 40 | :title="$t('settings.lang')", 41 | @click='openLangs') 42 | -------------------------------------------------------------------------------- /src/images/containers/HC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 11 | 12 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/images/containers/ST.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/images/icons-vsct/cargo-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/pages/contacts/seaTerminal.js: -------------------------------------------------------------------------------- 1 | export let title = 'Морской терминал' 2 | 3 | export let list = [ 4 | { 5 | title: 'Код получателя', 6 | text: '6623, ОКПО / 00467732', 7 | type: 'image', 8 | }, { 9 | title: 'Адрес', 10 | text: '690012, Россия, г.Владивосток, ул.Берёзовая, 25', 11 | type: 'address', 12 | }, { 13 | title: 'Станция и дорога назначения', 14 | text: 'Мыс Чуркин, ДВЖД, код станции 980802', 15 | type: 'address', 16 | }, { 17 | title: 'Основной телефон', 18 | text: '8 (423) 227-49-55', 19 | type: 'phone', 20 | }, { 21 | title: 'Факс', 22 | text: '8 (423) 227-85-37', 23 | type: 'phone', 24 | }, { 25 | title: 'Факс ЖД отдел', 26 | text: '8 (423) 227-86-15', 27 | type: 'phone', 28 | }, { 29 | title: 'Оператор отдела ЖД перевозок (24ч)', 30 | content: [ 31 | { 32 | text: '8 (423) 227-49-55, 2184', 33 | type: 'phone', 34 | }, { 35 | text: 'operator_zhd@fishport.ru', 36 | type: 'email', 37 | } 38 | ], 39 | }, { 40 | title: 'Прием заявок на ускоренные кнт поезда', 41 | text: 'vsct-train@fishport.ru', 42 | type: 'email', 43 | }, { 44 | title: 'Диспетчер производственной группы (24ч)', 45 | text: '+7 (914) 704-32-39', 46 | type: 'phone', 47 | }, { 48 | title: 'Склад 50 причал', 49 | text: 'uchet50-vsct@fishport.ru', 50 | type: 'email', 51 | }, { 52 | title: 'Склад 52 причал', 53 | text: 'sklad_vmkt@fishport.ru', 54 | type: 'email', 55 | }, { 56 | title: 'Прием документов на складе', 57 | text: 'uchet3_vsct@fishport.ru', 58 | type: 'email', 59 | } 60 | ] 61 | -------------------------------------------------------------------------------- /src/images/icons-vsct/cargo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/fixtures/bill-info.js: -------------------------------------------------------------------------------- 1 | import { ContainerNumb, randomNumb } from './utils' 2 | 3 | const TOVAR = [ 4 | `Выставление контейнера на обследование МИДК | Курс: 78,6805(10.02.2016) [MLVLV575946220]: ${ContainerNumb()}; ${ContainerNumb()} [Екатеринбург -Товарный; Москва-Ховрино]`, 5 | `Выставление контейнера на обследование МИДК | Курс: 78,6805(10.02.2016) [MLVLV575943220]: ${ContainerNumb()} [Москва - Бутырская]`, 6 | `Выставление контейнера на обследование МИДК | Курс: 78,6805(10.02.2016) [MLVLVMCC994316]: ${ContainerNumb()} [Москва - Бутырская]`, 7 | ] 8 | 9 | /** 10 | * Товар или услуга, включеннаях в указанный счет 11 | * @param {string} tovar — наименование товара или услуги 12 | * @param {number} count — количество товаров или услуг 13 | * @param {number} price — цена за единицу товара или услуги 14 | * @param {number} summa — сумма по строке 15 | * @param {number} NDS — сумма НДС по строке 16 | * @param {number} summands — сумма с НДС по строке 17 | */ 18 | export class BillInfo { 19 | constructor() { 20 | this.tovar = TOVAR[randomNumb(5) % TOVAR.length] 21 | this.count = randomNumb(1) % 10 + 1 22 | this.price = randomNumb(7) / 100 23 | this.summa = (this.count * +this.price).toFixed(2) 24 | this.NDS = (Math.random() >= 2 / 3) 25 | ? (+(+this.summa / 0.18)).toFixed(2) : 0 26 | this.summands = (+this.summa + +this.NDS).toFixed(2) 27 | } 28 | } 29 | 30 | /** 31 | * @param {string} message — сообщение 32 | * @param data - список товаров и услуг, включенных в указанный счет 33 | */ 34 | export default () => new Promise(resolve => resolve({ 35 | message: 'Fixture bill info', 36 | data: Array.from(Array(2 * randomNumb(1) + 1), () => new BillInfo()), 37 | })) 38 | -------------------------------------------------------------------------------- /src/services/fetch/news.js: -------------------------------------------------------------------------------- 1 | import { fetchFactory, URL, fixtures } from './utils' 2 | 3 | var name = 'news' 4 | var regexp = { 5 | item: /(?:)(.+?)(?:<\/item>)/ig, 6 | CDATA: /(?:\!\[CDATA\[\s+?)(.+?)(?:\s+?]])/i, 7 | } 8 | 9 | export default fetchFactory({ 10 | url: URL[name], 11 | fixture: fixtures[name], 12 | parser: parseRSSNews, 13 | }) 14 | 15 | /** 16 | * Информация о новости 17 | * @param {number} id — индентификатор новости 18 | * @param {string} title — заголвок новости 19 | * @param {unixtime} date — дата создания новости 20 | * @param {string} image — ссылка на картинку к новости 21 | * @param {string} link — ссылка на новость 22 | * @param {string} content — содержание новости 23 | * @param {string} description — короткое описание новости 24 | */ 25 | export class News { 26 | constructor(item) { 27 | this.id = this.parseTag('id', item) | 0 28 | this.title = this.parseTag('title', item) 29 | this.date = this.parseTag('timestamp', item) * 1000 30 | this.image = this.parseTag('image', item) 31 | this.link = this.parseTag('link', item) 32 | this.content = (this.parseTag('text', item).match(regexp.CDATA) || [])[1] 33 | this.description = (this.parseTag('description', item).match(regexp.CDATA) || [])[1] 34 | } 35 | 36 | parseTag(tag, str) { 37 | var regexp = new RegExp(`(?:<${tag}>)(.+?)(?:<\/${tag}>)`, 'i') 38 | return str.match(regexp)[1] 39 | } 40 | } 41 | 42 | /** 43 | * Список новостей, распарсенных с RSS 44 | * @return — список распарсенных новостей с RSS 45 | */ 46 | function parseRSSNews({ data }) { 47 | return data.replace(/\s/g, ' ') 48 | .match(regexp.item) 49 | .map( item => new News(item) ) 50 | } 51 | -------------------------------------------------------------------------------- /src/vuex/modules/bill.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import Cache from 'services/CacheItem' 3 | import fetch from 'services/fetch' 4 | import { BILL_INFO, CHECK_EXIT, SET_PROGRESS } from '../mutation-types' 5 | 6 | export let name = 'bill' 7 | 8 | var cache = new Cache(name) 9 | var defaults = { 10 | number: null, 11 | message: '', 12 | data: [], 13 | } 14 | 15 | // initial state 16 | export const state = Storage.get(name, defaults) 17 | 18 | // mutations 19 | export const mutations = { 20 | [BILL_INFO](state, payload) { 21 | state.data = payload.data 22 | state.number = payload.number 23 | state.message = payload.message 24 | 25 | Storage.set(name, state) 26 | }, 27 | 28 | [CHECK_EXIT](state) { 29 | state = Object.assign({}, defaults) 30 | }, 31 | } 32 | 33 | // actions 34 | export const actions = { 35 | getBillInfo({ actions, dispatch }, number) { 36 | var settings = { params: { number } } 37 | 38 | if (cache.get(number)) { 39 | return dispatch(BILL_INFO, cache.get(number)) 40 | } 41 | 42 | // reset bill info 43 | dispatch(BILL_INFO, Object.assign({}, defaults)) 44 | dispatch(SET_PROGRESS, true) 45 | 46 | fetch.bill.info(settings).then( payload => { 47 | cache.set(number, payload.data) 48 | 49 | dispatch(BILL_INFO, { 50 | number, 51 | data: payload.data, 52 | message: payload.data, 53 | }) 54 | }).catch( error => { 55 | // reLogin !!! 56 | if (error.status === 401) { 57 | actions.reLogin({ 58 | callback: () => actions.getBillInfo(number) 59 | }) 60 | } 61 | }).then( () => { 62 | dispatch(SET_PROGRESS, false) 63 | }) 64 | }, 65 | } 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VMKT", 3 | "version": "0.0.2", 4 | "description": "Прототип мобильного приложения для владивостокского морского контейнерного терминала", 5 | "authors": [ 6 | { 7 | "name": "Andrey Pastukhov", 8 | "email": "gmtyllo@gmail.com" 9 | } 10 | ], 11 | "dependencies": { 12 | "autoprefixer": "^6.1.1", 13 | "babel-core": "^6.2.1", 14 | "babel-loader": "^6.2.0", 15 | "babel-preset-es2015": "^6.1.18", 16 | "css-loader": "^0.23.0", 17 | "del": "^2.1.0", 18 | "extract-text-webpack-plugin": "^0.9.1", 19 | "file-loader": "^0.8.5", 20 | "git-rev-sync": "https://github.com/kurttheviking/git-rev-sync/tarball/master", 21 | "gulp": "^3.9.0", 22 | "gulp-connect": "^2.2.0", 23 | "gulp-env": "^0.4.0", 24 | "gulp-notify": "^2.2.0", 25 | "gulp-plumber": "^1.0.1", 26 | "jade": "^1.11.0", 27 | "jade-html-loader": "https://github.com/thekip/jade-html-loader/tarball/master", 28 | "jade-loader": "^0.8.0", 29 | "less": "^2.5.3", 30 | "less-loader": "^2.2.2", 31 | "moment": "^2.11.2", 32 | "node-sass": "^3.4.2", 33 | "postcss-loader": "^0.8.0", 34 | "precss": "^1.3.0", 35 | "promise-loader": "^1.0.0", 36 | "raw-loader": "^0.5.1", 37 | "require-dir": "^0.3.0", 38 | "sass-loader": "^3.1.2", 39 | "style-loader": "^0.13.0", 40 | "svg-sprite-loader": "0.0.15", 41 | "url-loader": "^0.5.7", 42 | "vinyl-named": "^1.1.0", 43 | "vue": "^1.0.19", 44 | "vue-i18n": "^2.4.1", 45 | "vue-resource": "^0.7.0", 46 | "vue-router": "^0.7.11", 47 | "vuex": "^0.6.2", 48 | "webpack-stream": "2.1.0" 49 | }, 50 | "scripts": { 51 | "postinstall": "bower install && gulp build", 52 | "test": "echo \"Error: no test specified\" && exit 1" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/services/fetch/advance.js: -------------------------------------------------------------------------------- 1 | import { fetchFactory, URL, fixtures } from './utils' 2 | 3 | var name = 'advance' 4 | 5 | export default fetchFactory({ 6 | url: URL[name], 7 | fixture: fixtures[name], 8 | parser: parseAdvanceInfo, 9 | }) 10 | 11 | /** 12 | * Список платежей 13 | * @param {unixtime} date — дата платежа 14 | * @param {number} summa — сумма платежа 15 | */ 16 | export class Advance { 17 | constructor(data) { 18 | this.date = data.date * 1000 19 | this.summa = data.summa 20 | } 21 | } 22 | 23 | /** 24 | * Список счетов дебетовой задолжности 25 | * @param {string} number — номер счета 26 | * @param {unixtime} date — дата счета (дата возникновения задолженности) 27 | * @param {number} summa — сумма задолженности (может на совпадать со суммой по счету, например счет оплачивался частично, покажет только остаток задолженности по счету) 28 | */ 29 | export class Debt { 30 | constructor(data) { 31 | this.number = data.number 32 | this.date = data.date * 1000 33 | this.summa = data.summa 34 | } 35 | } 36 | 37 | /** 38 | * Список авансовых платежей, поступивших от клиента, а также дебиторской задолженности клиента 39 | * @param {string} message — сообщение с сервера 40 | * @param {number} summa — текущий баланс 41 | * @param {number} summa_bill — баланс с учетом выставленных счетов 42 | * @param advance — список платежей 43 | * @param debt — список счетов дебетовой задолжности 44 | */ 45 | export function parseAdvanceInfo({ data }) { 46 | return { 47 | message: data.message, 48 | summa: data.summa, 49 | summa_bill: data.summa_bill, 50 | advance: data.tables.advance.map( item => new Advance(item) ), 51 | debt: data.tables.debt.map( item => new Debt(item) ), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/vuex/modules/container.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import Cache from 'services/CacheItem' 3 | import fetch from 'services/fetch' 4 | import { CNT_INFO, SET_PROGRESS, CHECK_EXIT } from '../mutation-types' 5 | 6 | export let name = 'container' 7 | 8 | var cache = new Cache(name) 9 | var defaults = { 10 | number: null, 11 | message: '', 12 | data: [], 13 | } 14 | 15 | // initial state 16 | export const state = Storage.get(name, defaults) 17 | 18 | // mutations 19 | export const mutations = { 20 | [CNT_INFO](state, payload) { 21 | state.data = payload.data 22 | state.number = payload.number 23 | state.message = payload.message 24 | 25 | Storage.set(name, state) 26 | }, 27 | 28 | [CHECK_EXIT](state) { 29 | state = Object.assign({}, defaults) 30 | }, 31 | } 32 | 33 | // actions 34 | export const actions = { 35 | getCntInfo({ actions, dispatch }, number) { 36 | var settings = { params: { number } } 37 | 38 | if (cache.get(number)) { 39 | return dispatch(CNT_INFO, cache.get(number)) 40 | } 41 | 42 | // reset container info 43 | dispatch(CNT_INFO, Object.assign({}, defaults)) 44 | dispatch(SET_PROGRESS, true) 45 | 46 | fetch.container.info(settings).then( payload => { 47 | cache.set(number, payload.data) 48 | 49 | dispatch(CNT_INFO, { 50 | number, 51 | data: payload.data, 52 | message: payload.data, 53 | }) 54 | }).catch( error => { 55 | // reLogin !!! 56 | if (error.status === 401) { 57 | actions.reLogin({ 58 | callback: () => actions.getCntInfo(number) 59 | }) 60 | } 61 | return error 62 | }).then( () => { 63 | dispatch(SET_PROGRESS, false) 64 | }) 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /src/vuex/modules/settings.js: -------------------------------------------------------------------------------- 1 | import Storage from 'services/Storage' 2 | import fetch from 'services/fetch' 3 | import { 4 | SET_SETTINGS, 5 | TOGGLE_NOTIFY_BY, 6 | SET_PROGRESS, 7 | CHECK_EXIT, 8 | } from '../mutation-types' 9 | 10 | export const name = 'settings' 11 | 12 | const defaults = { 13 | email: '', 14 | phone: '', 15 | gravatar_email: '', 16 | gravatar_hash: '', 17 | notifyBy: { 18 | email: false, 19 | phone: false, 20 | avatar: false, 21 | } 22 | } 23 | 24 | // initial state 25 | export const state = Storage.get(name, defaults) 26 | 27 | // mutations 28 | export const mutations = { 29 | [SET_SETTINGS](state, payload) { 30 | Object.assign(state, payload) 31 | Storage.set(name, state) 32 | }, 33 | 34 | [TOGGLE_NOTIFY_BY](state, type) { 35 | state.notifyBy[type] = !state.notifyBy[type] 36 | Storage.set(name, state) 37 | }, 38 | 39 | [CHECK_EXIT](state) { 40 | state = Object.assign({}, defaults) 41 | }, 42 | } 43 | 44 | // actions 45 | export const actions = { 46 | getSettings({ actions, dispatch }) { 47 | 48 | dispatch(SET_PROGRESS, true) 49 | 50 | fetch.settings().then( payload => { 51 | dispatch(SET_SETTINGS, payload) 52 | }).catch( error => { 53 | // reLogin !!! 54 | if (error.status === 401) { 55 | actions.reLogin({ 56 | callback: () => actions.getSettings() 57 | }) 58 | } 59 | return error 60 | }).then( () => { 61 | dispatch(SET_PROGRESS, false) 62 | }) 63 | }, 64 | 65 | // TODO: made actions for get and update settings from server 66 | updateSettings({ dispatch }, data) { 67 | }, 68 | 69 | toggleNotify({ dispatch }, type) { 70 | dispatch(TOGGLE_NOTIFY_BY, type) 71 | }, 72 | } 73 | --------------------------------------------------------------------------------