1
0
mirror of https://github.com/PanJiaChen/vue-element-admin.git synced 2025-08-10 12:01:57 +08:00

This is the first pull request for ADempiere-Vue (#106)

This is the first commit for changes and integration with ADempiere UI, the pull request have very to long commits, here is a squash and merge, for detail refere to https://github.com/erpcya/adempiere-vue
with committers:
- [Yamel Senih](https://github.com/yamelsenih)
- [Raúl Muñoz](https://github.com/Raul-mz)
- [Edwin Betancourt](https://github.com/EdwinBetanc0urt)
- [Leonel Matos](https://github.com/leonel1524)
- [Elsio Sanchez](https://github.com/elsiosanchez)

* add server error message (#571)

* Feature/collapse avanced search (#572)

* add server error message

* add collasep to advanced search

* bugfix load data ordered (#573)

* Fix share values from panel. (#574)

* Fix share values from panel.

* Parsed values.

* support refresh and field updata (#575)

* refresh

* bugfix load data ordered

* minimal changes

* Fix parameters to share data type date and boolean. (#576)

* Change search browser. (#577)

* read url parameters and load data (#578)

* Fix error when search browser list. (#579)

* Fix error when search browser list.

* Minimal changes.

* Update Dictionary and business data client version

* bugfix share link with props (#580)

* bugfix copy link with uuid

* bugfix share link with props

* Add support conditions to object list from criteria. (#581)

* bugfix record navigation in left table (#582)

* add export-browser (#583)

* add export browser

* minimal changes

* wait for service

* add window report

* bugfix load report with link shared (#584)

* bugfix copy link with uuid

* bugfix share link with props

* bugfix load report with link shared

* Rollback support to window. (#585)

* Rollback support to window.

* Minimal changes.

* support Mandatory Field (#586)

* support Mandatory Field

* delete comment

* bugfix share link from avanced query (#588)

* bugfix copy link with uuid

* bugfix share link with props

* bugfix load report with link shared

* bugfix share link from avanced query

* Fix optional fields showed from user. (#589)

* Fix optional fields showed from user.

* Remove destructuring assignment.

* message error serve (#587)

* message error serve

* remove console

* remove error code

* minimal changes

* bugfix change records in record navigation (#590)

* bugfix record navigation in left table

* bugfix change records in record navigation

* support process Activity (#592)

* support processActivity

* minimal changes

* Load one group field to advanced query. (#593)

* Load one group field to advanced query.

* Minimal changes.

* bugfix load record with criteria method (#594)

* support refrest menu contexto (#595)

* Add new version from business data

* Fix read only fields. (#591)

* Fix read only fields.

* Minimal changes.

* Add all field list in panel advanced query. (#596)

* fix set date with context (#597)

* support logs Process Activity (#598)

* support for the logs of Process Activity

* support logs Process Activity

* remove console.log

* Update data in main panel when update list from server. (#599)

* Update data in main panel when update list from server.

* Update comments.

* Update conditional to update main panel.

* First commit to data load children. (#600)

* bugfix change tag view title and rename variables (#601)

* Fix add optional fields to advanced query. (#602)

* Fix add optional fields to advanced query.

* Add attribute to advanced query.

* Fix variable name.

* Fix change roles in navigation bar and view profile. (#603)

* Fix send all parameters by advanced query. (#604)

* bugfix close tag after process execute (#605)

* bugfix load windows process without fields (#606)

* clear fields when the window is closed (#607)

* clear field of process

* minimal changes

* add report type to share link of reports (#608)

* Fix context menu. (#609)

* bugfix reload parameters after close SB (#610)

* bugfix reload parameters after close SB

* change condition

* add ZoomIn ProcessActivity (#611)

* add ZoomIn ProcessActivity

* add parameters

* Update es.js

Fix Variable Name

* Update index.vue

Fix Variable Name

* Update en.js

* bugfix close window after process (#613)

* bugfix close window after process

* bugfix close views after execute SB process

* support refresh the SmartBrowser (#614)

* Remove data after close view. (#612)

* Remove data after close view.

* Clear data in tabs children when change uuid in main panel.

* Fix load data in tabs children. (#615)

* Fix load data in tabs children.

* Send where clause.

* bugfix load data whith criteria conditions (#616)

* bugfix load data whith criteria conditions

* remove unnecessary functions

* change import function and change component name (#617)

* add reference mobile (#618)

* fix function calling (#619)

* support mobile (#620)

* remove advanced query option from child tabs

* support mobile

* Fix load data tab children and not send row tab (edit mode). (#621)

* bugfix execute report formats (#622)

* support datatable y and contextmenu (#623)

* Fix load display column, record navigation. (#624)

* some bugfix in advanced query panel (#625)

* support for the range date and the title of the processes and reports (#626)

* add option select last week range date
* adjust the height of the title of the processes and reports

* fix create new action error (#627)

* Remove duplicate request to server by Tabs and Fields children. (#628)

* Fix display value NULL in TextField component. (#630)

* Smart browser support when opening the process Activity (#631)

* support to record load in the table

* add dispatch isClearSelection

* adding conditional

* minimal changes

* Update contextMenuMixin.js

* bugfix in share link report and process properties (#632)

* fix open report viewer on process return error (#633)

* feat: Add create entity in tab children. (#634)

* feat: Add create entity in tab children.

* Fix route query action when create entity in tab children.

* feat: Add notification after delete selection records in table. (#636)

* feat: Add notification after delete selection records in table.

* Add destructuring of object.

* Fix fields list mandatory empty when create/update in table. (#635)

* feat: Add create entity in tab children.

* Fix route query action when create entity in tab children.

* Fix fields list mandatory empty when create/update in table.

* fix: Create entity from main panel. (#637)

* add condition (#638)

* fix values of date parameters in share link (#639)

* Support DateRange week (#640)

* Add Summary to logs (#641)

* support daterange week (#642)

* Support DateRange week

* minimal changes

* Auto stash before merge of "bugfix/support-date-range-week" and "develop-i18n"

* fix: Search browser when change show/hidden field not isQueryCriteria. (#643)

* change displayed fields on advanced query (#644)

* add summary to logs (#645)

* fix create new action query (#646)

* fix: Not load tabs not supported (isSortTab and isTranslationTab) (#647)

* Change version

* remove refresh the tabs children (#648)

* remove refresh the tabs children

* column top of the table of the select

* minimal changes

* fix: Create entity in table send server after change field. (#649)

* add loading (#650)

* add loading

* rename isLoaded

* add translations

* add translations
* add message

* report control error (#651)

* Add mark (*) mandatory fields in table. (#652)

* fix: Exception not handled, add message when capturing error in promise. (#653)

* fix: Correct loading into table when it has 0 records. (#654)

* fix: Correct loading into table when it has 0 records.

If the search returns 0 records the DataTable remains loading even when that was its response, giving the confucion that it is still waiting for the response from the server.

* Minimal changes.

* fix: Hide search notification for language and role. (#655)

* add hidden main panel (#656)

* fix: Show fields with default values. (#658)

* fix load records in left table (#657)

* support the hide panel button (#660)

* add icon create new (#659)

* add icon create new

* setting the add button

* fix: Delete record and selection in browser after run process. (#661)

* fix load parameters of share link (#662)

* open report from the window (#663)

* open report from the window

* resolve Conflicts

* fix references disabled

* Remove console.log (#664)

* fix: Load data tab children after set context main panel. (#665)

* add lastrun to the process Activity (#667)

* fix: Lookups context. (#668)

* fix: Set is load context not report and process. (#669)

* fix: Set is load context not report and process.

* Minimal changes.

* Change evaluate multiple conditions.

* feat: Add display column in add new row with value or default values. (#670)

* feat: Add display column in add new row with value or default values.

* Add comments.

* Fixing navigation height between records (#671)

* Fixing navigation height between records

* minimal changes

* support recent intems

* fix: Context for window request. TODO: Add AD_User_ID in user info gRPC. (#672)

* feat: Add last run to process activity. (#673)

* feat: Add last run to process activity.

* Change convert parameters to object in vuex store.

* fix: Run process error.

* Update source packages version

* Update access version

* fix: Do not show loading when the data request returns error. (#675)

* feat: Add support to fields isEncrypted (type password). (#677)

* fixing translation (#678)

* fix display panel and fields in advanced query (#676)

* fix display panel and fields in advanced query

* bugfix filter fields in advanced query

* fix load values in lookup component (#679)

* filter fields exclude buttons on adavanced query (#680)

* fix: Add display column to field link in tab. (#681)

* feat: Add support to default context. (#682)

TODO: Add default context in session info.

* feat: Add field length to field text as max length attribute. (#683)

* Add support to latest version from access

* button setting to close table panel or expand (#684)

* fix activities order by date (#685)

* add date translations in process activity

* fix activities order by date

* fix: Handle data request error to leave loading the table. (#686)

* fix: Smart Browser, research data when records length > 100. (#687)

* fix: Smart Browser, research data when records length > 100.

* Correct variable evaluation.

* fix close report (#688)

* Evaluate server response error by dictionary (#689)

* Evaluate server response error by dictionary

* Support Smart Browser and Process

* minimal changes

* fixed color and size of icon datatable (#690)

* fixed color and size of icon datatable

* minimal changes

* add clearable

* fix: Create request field mandatory number 0. (#691)

* validate edit mode until ready to save (#692)

* validate edit mode until ready to save

* correcting condition

* add child tab to url and share link (#693)

* feat: Add support to new session request. (#694)

* multiple record support (#695)

* fix delete data after close window (#696)

* fix load view on reapeted route

* fix delete data after close window

* feat: Add read only row from table. (#697)

* feat: Add read only row from table.

* Add read only if client context is different to client record.

* add call off record (#698)

* date error when executing (#699)

* feat: Add validation read only with parent record is read only. (#700)

* feat: Add validation read only with parent record is read only.

* Minimal changes.

* fix: Run process associate with Smart Browser or Window. (#702)

* restore create new record of datatable child (#701)

* restore create new record of datatable child

* restore change

* minimal changes

* restoring values in the table

* fix: Create new main panel, clear records to tabs children. (#703)

* validate multiple record (#704)

* validate multiple record

* validate isEdit datatable children

* Add new version for data

* Add new version for data

* add show to password type field (#705)

* validate multiple record

* add show to password type field

* fix: Change evaluation of conditionals to add new row. (#706)

* feat: Keep rows in edit mode after listing by creating record. (#707)

* fix: Fix get lookup to window navigation. (#708)

* fix: Parse default values to create or update record. (#709)

* bugfix set tab child in URL (#710)

* support height datable (#711)

* fix: Read Only from processed and processing record. (#712)

* Support badge and column fixed (#713)

* support height datable

* support badge

* raname badge (#714)

* no recharge view on changes (#715)

* bugfix style of badge (#717)

* change tabNumber query prop for tabParent (#718)

* bugfix set tab child in URL

* change tabNumber query prop for tabParent

* fix: Pagination and total records. (#719)

* Support duplicate lookup (#721)

* set advanced search (#716)

* field auto focus (#720)

* field auto focus

* bugfix

* some changes

* change method

* remove unused attributes

* fix: Add display column from server to add new row. (#723)

* set values in main panel to share link (#725)

* fix: Add new record data tables. (#726)

* support LookupList (#724)

* support LookupList

* fix: Add display column from server to add new row.

* delete isDisabledAddNewIcono

* remove highlight current row of data table (#722)

* fix: Tag view undefined. (#727)

* fix: Get display column from server to add new row. (#729)

* remove unused conditions (#730)

* support style collapse item (#731)

* clear panel values when view is closed (#732)

* fix: Get multiple request data. (#733)

* bugfix open process activity view (#736)

* Evaluate values of the displayColumn with empty string or number at 0 (#737)

* Evaluate values of the displayColumn with empty string or number at 0

* documenting evaluation

* fix route from process activity to process view (#735)

* bugfix conditions of tags views (#738)

* bugfix infinity loading in datatable on new record (#734)

* bugfix maxlength in process (#739)

* feat: Add support to callout. (#741)

* feat: Add callout support.

* feat: Add support to callout.

* Evaluate Column display values with empty string or number in 0u8 evaluation (#740)

* bugfix close window after delete record (#742)

* bugfix change panel records (#743)

* fix: Error input text when show/hidden column DataTables. (#744)

* fix: Remove duplicate code to focus field. (#745)

* add change value of image (#746)

* fix: Disable roles that do not have uuid in the registry (#747)

* Add new version for gRPC data client

* fix: Remove multiple request data from server. (#748)

* Support duplicate or null data lookup (#749)

* bugfix (#750)

* Bugfix/parsed boolean context (#751)

* bugfix

* fix: Parsed context convert Boolean values to string values in query.

* feat: Add support to callout response. (#752)

* bugfix

* feat: Add support to callout response to change panel values.

* remove concole.log.

* fix: Not send callout if is panel change. (#753)

* fix: Not request callout when navigation records. (#755)

* Update business data client from latest

* Reduce time transition menu (#754)

* fix: Set null values before reset panel to new. (#756)

* fix: Set null values before reset panel to new.

* Add oldValue.

* add windowNo to callout store (#757)

* bugfix record void in tabs (#758)

* Update data version

* feat: Add callout request to tab children. (#759)

* feat: Add callout request to tab children.

* Minimal changes.

* bugfix context default values (#762)

* bugfix void values from gRPC (#763)

* feat: Add support for empty gRPC values. (#764)

* feat: Add support for empty gRPC values.

* Update comments.

* tab record load and reduce requests to the server (#765)

* tab record load and reduce requests to the server

* duplicate keys Lookups

* add forget password (#761)

* add forget password

* remove console

* rename translation

* minimal changes

* Minimal Changes

* duplicate data and string type (#766)

* Support duplicate data and string type

* minimal changes

* update grpc-access-client version (#768)

* bugfix load values after callout response (#769)

* feat: Add support enrollment. (#767)

* feat: Add support enrollment.

* Update enrollment package version.

* Fix language.

* feat: Add reset password with token. (#771)

* bugfix send panel values to create record (#772)

* bugfix convert dates to UTC (#774)

* feat: Add persistent vuex state in local storage. (#773)

* feat: Add persistent vuex state in local storage.

* fix: Error when LogOut session.

* validate options list lookup (#776)

* feat: Add enrollment user. (#778)

* feat: Add create password and join with reset password form view. (#779)

* fix: Zoom item process activity, parameters undefined. (#780)

* Validate ColumnName in DataTable (#783)

* bugfix load list items and callout send (#777)

* fix: Search records with uuid url. (#781)

* convert ternary operator into computed property (#782)

* fix: Remove unused methods and remove watching. (#784)

* fix: Remove unused methods and remove watching.

* remove watch window view.

* fix: Validation in google chrome enroll user. (#785)

* fix: Validation in google chrome enroll user.

* Update grpc-enrollment-user-client version.

* fix: Convert context map to array pairs. (#786)

* fix: Delete persistent date. (#788)

* fix: Yes -No component value in table when edit mode. (#789)

* multiple requests LookupList (#791)

* fix: Read only panel parent when set context tab children. (#792)

* Change value to displayColumn (#793)

* add display column in tables childs (#787)

* bugfix load list items and callout send

* add display column in tables childs

* add dynamic table name

* add get default value from server

* change comparation

* remove request LookupItem (#794)

* Change value to displayColumn

* minimal changes

* bugfix send criteria for get default value (#795)

* bugfix load list items and callout send

* add display column in tables childs

* add dynamic table name

* add get default value from server

* change comparation

* add criteria for get default value

* remove parse int

* feat: Add undo to create new record. (#796)

* bugfix display values in tabs child (#797)

* fix: Browser Generate Invoice From Outbound Order not search result. (#798)

* add icons to recent items (#799)

* bugfix display values in tabs child

* add icons to recent items

* correct translation and add client when changing rolecorrect translation and add client when changing role (#800)

* feat: Add show mandatory columns and show all available columns. (#801)

* bugfix recent items and change styles (#802)

* change getDefaultValue service implementation (#803)

* bugfix display values with sql

* change getDefaultValue service implementation

* fix: Set empty values to component's base. (#804)

* add context info in field (#805)

* bugfix context info fields (#806)

* feat: Update access client and data client grpc version. (#807)

* Add displayColumn to getterValueSelect (#808)

* Add displayColumn to getterValueSelect

* Update panel.js

* fix: Duplicate change panel with record. (#809)

* Change license and readme waiting for merge with ADempiere trunk

* Add Changes for license and readme

* Add commiter
This commit is contained in:
Yamel Senih 2019-11-17 04:56:42 -04:00 committed by GitHub
parent 5f536fdfc4
commit 059f17e279
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
123 changed files with 18284 additions and 889 deletions

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ selenium-debug.log
package-lock.json
yarn.lock
/bin/

687
LICENSE
View File

@ -1,21 +1,674 @@
MIT License
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2017-present PanJiaChen
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

21
PREVIOUS-LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present PanJiaChen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

225
README.es.md Normal file
View File

@ -0,0 +1,225 @@
<p align="center">
<img width="320" src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Adempiere-logo.png">
</p>
<p align="center">
<a href="https://github.com/vuejs/vue">
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/ElemeFE/element">
<img src="https://img.shields.io/badge/element--ui-2.7.0-brightgreen.svg" alt="element-ui">
</a>
<a href="https://travis-ci.org/adempiere/adempiere-vue" rel="nofollow">
<img src="https://travis-ci.org/adempiere/adempiere=vue.svg?branch=develop" alt="Build Status">
</a>
<a href="https://github.com/adempiere/adempiere-vue/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
<a href="https://github.com/adempiere/adempiere-vue/releases">
<img src="https://img.shields.io/github/release/adempiere/adempiere-vue.svg" alt="GitHub release">
</a>
<a href="https://gitter.im/adempiere/adempiere-vue">
<img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
</a>
</p>
Español | [Inglés](./README.md)
## Introducción
[adempiere-vue](https://github.com/adempiere/adempiere-vue) es una nueva UI para [ADempiere ERP, CRM & SCM](https://github.com/adempiere/adempiere)]. Se basa en [vue](https://github.com/vuejs/vue) y usa el conjunto de herraminentas de UI [element-ui](https://github.com/ElemeFE/element).
Este es una gran UI para [ADempiere ERP, CRM & SCM](https://github.com/adempiere/adempiere)] basada en lo último desarrollado por vue, construido con i18n para manejo multi-idioma, plantillas para aplicaciones de negocio y muchas características asombrosas. Este proyecto es derivado de [Vue-Element-Admin](https://github.com/PanJiaChen/vue-element-admin) originalmente escrito por [PanJiaChen / 花裤衩](https://github.com/PanJiaChen) sobre [licencia MIT](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE) y cambiado a [licencia GNU/GPL v3](https://github.com/adempiere/adempiere-vue/blob/master/LICENSE) por [Yamel Senih](https://github.com/yamelsenih) después de derivar el proyecto y con permisos del autor original [PanJiaChen / 花裤衩](https://github.com/PanJiaChen) en el reporte ["Extend as GNU/Gpl v3 License #1434"](https://github.com/PanJiaChen/vue-element-admin/issues/1434).
[adempiere-vue](https://github.com/adempiere/adempiere-vue) usa como RPC (Llamado a Procedimientos Remostos)[gRPC](https://grpc.io/) como [server](https://github.com/erpcya/adempiere-gRPC-Server).
- [Vista Prévia](http://adempiere-ui.erpya.com:9526/)
- [Documentación](https://panjiachen.github.io/vue-element-admin-site/)
- [Gitter](https://gitter.im/adempiere/adempiere-vue)
- [Donar](https://www.paypal.me/?)
- [Wikipedia](http://wiki.adempiere.net/ADempiere_ERP)
- [Derivado De](https://github.com/PanJiaChen/vue-element-admin)
**La versión actual `v1.0+` está construida en `vue-cli`. Si encuentra algún problema, por favor escriba un [reporte de error](https://github.com/adempiere/adempiere-vue/issues/new).**
**Este proyecto no soporta versiones bajas de los navegadores (e.g. IE).**
## Preparación
Necesita instalar [node](https://nodejs.org/) y [git](https://git-scm.com/) localmente. El proyecto está basado en [ES2015+](https://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [vue-cli](https://github.com/vuejs/vue-cli) , [gRPC](https://grpc.io/) y [element-ui](https://github.com/ElemeFE/element).
Entendiendo y aprendiendo acerca de lo anterior le ayudará a conocer el proyecto.
<p align="center">
<img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
</p>
## Patrocinantes
<a href="http://erpya.com/"><img width="250px" src="https://i0.wp.com/spin-suite.com/erpya/wp-content/uploads/sites/28/2017/11/ERP-logotipo-H-color.png" /></a>
<a href="http://www.e-evolution.com/"><img width="250px" src="https://i0.wp.com/mysmart.business/evolution/wp-content/uploads/sites/29/2017/10/e-evolution-logo-v2.png?fit=150%2C92" /></a>
<a href="http://westfalia-it.com/"><img width="150px" src="https://i1.wp.com/spin-suite.com/westfalia/wp-content/uploads/sites/30/2017/12/logo_copy.jpg?fit=265%2C357" /></a>
<a href="http://erpya.com/"><img width="250px" src="https://i0.wp.com/spin-suite.com/openupsolutions/wp-content/uploads/sites/32/2017/05/Openup-Solutions-Logo-2017-80x200px.png" /></a>
Sea un patrocinante y coloque su logo en nuestro LEEME en GitHub con un link a su sitio web. [Sea un Patrocinante](https://www.paypal.me/?)
## Características
```
- Iniciar / Cerrar Sesión
- Permisos de Authentication
- Permisos basado en ADempiere
- Página de Permisos
- Directivas de permisos
- Página de configuración de permisos
- Autenticación por dos pasos
- Construcción Multi-entorno
- dev sit stage producción
- Características Globales
- I18n
- Temas dinámicos
- Dynamic sidebar (soporte a rutas multi-nivel)
- Barra de rutas dinámica
- Tags-view (Tab page Support right-click operation)
- Svg Sprite
- Datos de simulación con Mock
- Pantalla completa
- Responsive Sidebar
- Editor
- Editor de Texto Enriquecido
- Editor Markdown
- Editor JSON
- Excel
- Exportación a Excel
- Carga de Excel
- Visualización de Excel
- Exportación como zip
- Tabla
- Tabla Dinámica
- Tabla con Arrastrar y Soltar
- Tabla de edición en línea
- Páginas de Error
- 401
- 404
- Componentes
- Carga de Avatar
- Botón para subir al inicio
- Arrastrar y Soltar (Diaglogo)
- Arrastrar y Soltar (Seleccionar)
- Arrastrar y Soltar (Kanban)
- Arrastrar y Soltar (Lista)
- Panel de división
- Componente para soltar archivos
- Adhesión de objetos
- Contador hasta
- Soporte a ADempiere
- Ventan
- Proceso
- Reporte
- Visor Inteligente
- Ejemplo Avanzado
- Registro de Errores
- Tablero de indicadores
- Página de Guías
- ECharts (Gráficos)
- Portapapeles
- Convertidor de Markdown a html
```
## Iniciando
Use [gRPC ADempiere Server](https://github.com/erpcya/adempiere-gRPC-Server) como proveedor de gRPC.
```bash
# clone el proyecto
git clone -b develop git@github.com:adempiere/vue-element-admin.git
# vaya al directorio clonado
cd adempiere-vue
# instale las dependencias
npm install
# corra el proyecto como desarrollador
npm run dev
```
Automáticamente se abrirá el siguiente enlace en su navegador http://localhost:9527
## Construcción
```bash
# Construcción para entornos de prueba
npm run build:stage
# Construcción para entornos de producción
npm run build:prod
```
## Avanzado
```bash
# Vista previa con efectos de entorno
npm run preview
# Vista previa con efectos + análisis de recursos estáticos
npm run preview -- --report
# Chequeo de formato de código
npm run lint
# Chequeo de formato de código y auto-corrección
npm run lint -- --fix
```
Vaya a [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) para mayor información.
## Registro de Cambios
Los cambios detallados por cada liberación se encuentran en [notas de liberación](https://github.com/adempiere/adempiere-vue/releases).
## Demostración en línea
[Preview](http://adempiere-ui.erpya.com:9526/)
## Donación
Si este proyecto es de mucha ayuda para ti, puedes ayudar a hacer una mejor UI
[Dona por Paypal](https://www.paypal.me/?)
## Navegadores Soportados
Navegadores modernos e Internet Explorer 10+.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Safari |
| --------- | --------- | --------- | --------- |
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
## Licencia
[GNU/GPL v3](https://github.com/adempiere/adempiere-vue/blob/master/LICENSE)
## Licencia Prévia
[MIT](./PREVIOUS-LICENSE)
## Contribuidores Iniciales
- [Yamel Senih](https://github.com/yamelsenih)
- [Raúl Muñoz](https://github.com/Raul-mz)
- [Edwin Betancourt](https://github.com/EdwinBetanc0urt)
- [Leonel Matos](https://github.com/leonel1524)
- [Elsio Sanchez](https://github.com/elsiosanchez)

View File

@ -1,212 +0,0 @@
<p align="center">
<img width="320" src="https://wpimg.wallstcn.com/ecc53a42-d79b-42e2-8852-5126b810a4c8.svg">
</p>
<p align="center">
<a href="https://github.com/vuejs/vue">
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/ElemeFE/element">
<img src="https://img.shields.io/badge/element--ui-2.7.0-brightgreen.svg" alt="element-ui">
</a>
<a href="https://travis-ci.org/PanJiaChen/vue-element-admin" rel="nofollow">
<img src="https://travis-ci.org/PanJiaChen/vue-element-admin.svg?branch=master" alt="Build Status">
</a>
<a href="https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
<a href="https://github.com/PanJiaChen/vue-element-admin/releases">
<img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release">
</a>
<a href="https://gitter.im/vue-element-admin/discuss">
<img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
</a>
<a href="https://panjiachen.gitee.io/vue-element-admin-site/zh/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
</p>
日本語 | [English](./README.md) | [简体中文](./README.zh-CN.md)
## 概要
[vue-element-admin](https://panjiachen.github.io/vue-element-admin) は管理画面のフロントエンドのインタフェース,[vue](https://github.com/vuejs/vue) と [element-ui](https://github.com/ElemeFE/element)を使っています。i18nの多言語対応、可変ルート、権限、典型的なビジネスアプリテンプレートであり、豊富なコンポーネントを提供しています、素早くビジネス用の管理画面の現型を構築に役立ちます。
- [デモページ](https://panjiachen.github.io/vue-element-admin)
- [ドキュメント](https://panjiachen.github.io/vue-element-admin-site/)
- [Gitter](https://gitter.im/vue-element-admin/discuss)
- [Donate](https://panjiachen.gitee.io/vue-element-admin-site/zh/donate)
- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
- おすすめシンプルテンプレート: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
- デスクトップバージョン: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
- Typescriptバージョン: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour))
**After the `v4.1.0+` version, the default master branch will not support i18n. Please use [i18n Branch](https://github.com/PanJiaChen/vue-element-admin/tree/i18n), it will keep up with the master update**
**現在のバージョン `v4.0+``vue-cli` で構築,バグ報告は[issue](https://github.com/PanJiaChen/vue-element-admin/issues/new)のissueでお願いします。旧バージョン[tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0)もあります。`vue-cli`に依存しないです。**
**低いバージョンのブラウザはサーポートしないです(例えば ie),必要があれば polyfill を追加してください。 [詳細はこちら](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
## 前準備
ローカル環境に [node](http://nodejs.org/) と [git](https://git-scm.com/)をインストールが必要です。[ES2015+](http://es6.ruanyifeng.com/)、[vue](https://cn.vuejs.org/index.html)、[vuex](https://vuex.vuejs.org/zh-cn/)、[vue-router](https://router.vuejs.org/zh-cn/) 、[vue-cli](https://github.com/vuejs/vue-cli) 、[axios](https://github.com/axios/axios) 和 [element-ui](https://github.com/ElemeFE/element)で開発しています。Requestは[Mock.js](https://github.com/nuysoft/Mock)のモックデータを使っています。
**バグ修正や新規機能追加のissue と pull requestは大歓迎です。**
<p align="center">
<img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
</p>
## Sponsors
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## 機能一覧
```
- ログイン / ログアウト
- Auth認証
- ページ権限
- 権限パーミッション
- 権限設定
- 外部IDでログイン
- 複数環境デプロイ
- dev sit stage prod
- 共通機能
- 多言語切替
- テーマ切替
- サイトメニュー(ルートから生成)
- Breadcrumb Navigation
- Tag Navigation
- Svg Sprite Icon
- ローカル/バックエンド モック データ
- Screenfull
- WYSIWYG
- TinyMCE
- Markdown
- JSON
- Excel
- エクスポート
- インポート
- リード
- Zip
- Table
- Dynamic Table
- Drag And Drop Table
- Inline Edit Table
- Error Page
- 401
- 404
- コンポーネント
- Avatar Upload
- Back To Top
- Drag Dialog
- Drag Select
- Drag Kanban
- Drag List
- SplitPane
- Dropzone
- Sticky
- CountTo
- Advanced Example
- Error Log
- Dashboard
- Guide Page
- ECharts
- Clipboard
- Markdown to html
```
## Getting started
```bash
# clone the project
git clone -b i18n git@github.com:PanJiaChen/vue-element-admin.git
# enter the project directory
cd vue-element-admin
# install dependency
npm install
# develop
npm run dev
```
This will automatically open http://localhost:9527
## Build
```bash
# build for test environment
npm run build:stage
# build for production environment
npm run build:prod
```
## Advanced
```bash
# preview the release environment effect
npm run preview
# preview the release environment effect + static resource analysis
npm run preview -- --report
# code format check
npm run lint
# code format check and auto fix
npm run lint -- --fix
```
Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## Online Demo
[Preview](https://panjiachen.github.io/vue-element-admin)
## Donate
If you find this project useful, you can buy author a glass of juice :tropical_drink:
![donate](https://wpimg.wallstcn.com/bd273f0d-83a0-4ef2-92e1-9ac8ed3746b9.png)
[Paypal Me](https://www.paypal.me/panfree23)
[Buy me a coffee](https://www.buymeacoffee.com/Pan)
## Browsers support
Modern browsers and Internet Explorer 10+.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Safari |
| --------- | --------- | --------- | --------- |
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
## License
[MIT](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
Copyright (c) 2017-present PanJiaChen

View File

@ -1,5 +1,5 @@
<p align="center">
<img width="320" src="https://wpimg.wallstcn.com/ecc53a42-d79b-42e2-8852-5126b810a4c8.svg">
<img width="320" src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Adempiere-logo.png">
</p>
<p align="center">
@ -9,78 +9,76 @@
<a href="https://github.com/ElemeFE/element">
<img src="https://img.shields.io/badge/element--ui-2.7.0-brightgreen.svg" alt="element-ui">
</a>
<a href="https://travis-ci.org/PanJiaChen/vue-element-admin" rel="nofollow">
<img src="https://travis-ci.org/PanJiaChen/vue-element-admin.svg?branch=master" alt="Build Status">
<a href="https://travis-ci.org/adempiere/adempiere-vue" rel="nofollow">
<img src="https://travis-ci.org/adempiere/adempiere=vue.svg?branch=develop" alt="Build Status">
</a>
<a href="https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE">
<a href="https://github.com/adempiere/adempiere-vue/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
<a href="https://github.com/PanJiaChen/vue-element-admin/releases">
<img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release">
<a href="https://github.com/adempiere/adempiere-vue/releases">
<img src="https://img.shields.io/github/release/adempiere/adempiere-vue.svg" alt="GitHub release">
</a>
<a href="https://gitter.im/vue-element-admin/discuss">
<a href="https://gitter.im/adempiere/adempiere-vue">
<img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
</a>
<a href="https://panjiachen.github.io/vue-element-admin-site/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
</p>
English | [简体中文](./README.zh-CN.md) | [日本語](./README.ja.md)
English | [Spanish](./README.es.md)
## Introduction
[vue-element-admin](https://panjiachen.github.io/vue-element-admin) is a production-ready front-end solution for admin interfaces. It based on [vue](https://github.com/vuejs/vue) and use the UI Toolkit [element-ui](https://github.com/ElemeFE/element).
[adempiere-vue](https://github.com/adempiere/adempiere-vue) is a new UI for [ADempiere ERP, CRM & SCM](https://github.com/adempiere/adempiere)]. It based on [vue](https://github.com/vuejs/vue) and use the UI Toolkit [element-ui](https://github.com/ElemeFE/element).
It is a magical vue admin based on the newest development stack of vue, built-in i18n solution, typical templates for enterprise applications, lots of awesome features. It helps you build a large complex Single-Page Applications. I believe whatever your needs are, this project will help you.
It is a great UI for [ADempiere ERP, CRM & SCM](https://github.com/adempiere/adempiere)] based on the newest development stack of vue, built-in i18n solution, typical templates for enterprise applications, lots of awesome features. This project was forked from [Vue-Element-Admin](https://github.com/PanJiaChen/vue-element-admin) originally write by [PanJiaChen / 花裤衩](https://github.com/PanJiaChen) over [MIT license](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE) and was changed to [GNU/GPL v3](https://github.com/adempiere/adempiere-vue/blob/master/LICENSE) by [Yamel Senih](https://github.com/yamelsenih) after forked granted by [PanJiaChen / 花裤衩](https://github.com/PanJiaChen) on issue ["Extend as GNU/Gpl v3 License #1434"](https://github.com/PanJiaChen/vue-element-admin/issues/1434).
- [Preview](https://panjiachen.github.io/vue-element-admin)
[adempiere-vue](https://github.com/adempiere/adempiere-vue) use the modern open source high performance RPC framework that can run in any environment [gRPC](https://grpc.io/) as [server](https://github.com/erpcya/adempiere-gRPC-Server).
- [Preview](http://adempiere-ui.erpya.com:9526/)
- [Documentation](https://panjiachen.github.io/vue-element-admin-site/)
- [Gitter](https://gitter.im/vue-element-admin/discuss)
- [Gitter](https://gitter.im/adempiere/adempiere-vue)
- [Donate](https://panjiachen.github.io/vue-element-admin-site/donate/)
- [Donate](https://www.paypal.me/?)
- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
- [Wiki](http://wiki.adempiere.net/ADempiere_ERP)
- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 国内用户可访问该地址在线预览
- [Forked From](https://github.com/PanJiaChen/vue-element-admin)
- Base template recommends using: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
- Desktop: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
- Typescript: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
**After the `v4.1.0+` version, the default master branch will not support i18n. Please use [i18n Branch](https://github.com/PanJiaChen/vue-element-admin/tree/i18n), it will keep up with the master update**
**The current version is `v4.0+` build on `vue-cli`. If you find a problem, please put [issue](https://github.com/PanJiaChen/vue-element-admin/issues/new). If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0), it does not rely on `vue-cli`**
**The current version is `v1.0+` build on `vue-cli`. If you find a problem, please put [issue](https://github.com/adempiere/adempiere-vue/issues/new).**
**This project does not support low version browsers (e.g. IE). Please add polyfill by yourself.**
## Preparation
You need to install [node](https://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](https://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [vue-cli](https://github.com/vuejs/vue-cli) , [axios](https://github.com/axios/axios) and [element-ui](https://github.com/ElemeFE/element), all request data is simulated using [Mock.js](https://github.com/nuysoft/Mock).
You need to install [node](https://nodejs.org/) and [git](https://git-scm.com/) locally. The project is based on [ES2015+](https://es6.ruanyifeng.com/), [vue](https://cn.vuejs.org/index.html), [vuex](https://vuex.vuejs.org/zh-cn/), [vue-router](https://router.vuejs.org/zh-cn/), [vue-cli](https://github.com/vuejs/vue-cli) , [gRPC](https://grpc.io/) and [element-ui](https://github.com/ElemeFE/element).
Understanding and learning this knowledge in advance will greatly help the use of this project.
<p align="center">
<p align="center">
<img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
</p>
## Sponsors
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
<a href="http://erpya.com/"><img width="250px" src="https://i0.wp.com/spin-suite.com/erpya/wp-content/uploads/sites/28/2017/11/ERP-logotipo-H-color.png" /></a>
<a href="http://www.e-evolution.com/"><img width="250px" src="https://i0.wp.com/mysmart.business/evolution/wp-content/uploads/sites/29/2017/10/e-evolution-logo-v2.png?fit=150%2C92" /></a>
<a href="http://westfalia-it.com/"><img width="150px" src="https://i1.wp.com/spin-suite.com/westfalia/wp-content/uploads/sites/30/2017/12/logo_copy.jpg?fit=265%2C357" /></a>
<a href="http://erpya.com/"><img width="250px" src="https://i0.wp.com/spin-suite.com/openupsolutions/wp-content/uploads/sites/32/2017/05/Openup-Solutions-Logo-2017-80x200px.png" /></a>
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
Become a sponsor and get your logo on our README on GitHub with a link to your site. [Become a sponsor](https://www.paypal.me/?)
## Features
```
- Login / Logout
- Register
- Forgot Password
- Permission Authentication
- ADempiere backend permission
- Page permission
- Directive permission
- Permission configuration page
- Two-step login
- Multi-environment build
- dev sit stage prod
@ -92,7 +90,6 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
- Dynamic breadcrumb
- Tags-view (Tab page Support right-click operation)
- Svg Sprite
- Mock data
- Screenfull
- Responsive Sidebar
@ -127,6 +124,11 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
- Dropzone
- Sticky
- CountTo
- ADempiere supported
- Window
- Process
- Report
- Smart Browser
- Advanced Example
- Error Log
@ -139,12 +141,14 @@ Become a sponsor and get your logo on our README on GitHub with a link to your s
## Getting started
Use [gRPC ADempiere Server](https://github.com/erpcya/adempiere-gRPC-Server) as gRPC provider.
```bash
# clone the project
git clone -b i18n git@github.com:PanJiaChen/vue-element-admin.git
git clone -b develop git@github.com:adempiere/vue-element-admin.git
# enter the project directory
cd vue-element-admin
cd adempiere-vue
# install dependency
npm install
@ -185,21 +189,17 @@ Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/gui
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
Detailed changes for each release are documented in the [release notes](https://github.com/adempiere/adempiere-vue/releases).
## Online Demo
[Preview](https://panjiachen.github.io/vue-element-admin)
[Preview](http://adempiere-ui.erpya.com:9526/)
## Donate
If you find this project useful, you can buy author a glass of juice :tropical_drink:
If you find this project useful, you can help this make a better UI
![donate](https://wpimg.wallstcn.com/bd273f0d-83a0-4ef2-92e1-9ac8ed3746b9.png)
[Paypal Me](https://www.paypal.me/panfree23)
[Buy me a coffee](https://www.buymeacoffee.com/Pan)
[Paypal Me](https://www.paypal.me/?)
## Browsers support
@ -211,6 +211,15 @@ Modern browsers and Internet Explorer 10+.
## License
[MIT](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
[GNU/GPL v3](https://github.com/adempiere/adempiere-vue/blob/master/LICENSE)
Copyright (c) 2017-present PanJiaChen
## Previous License
[MIT](./PREVIOUS-LICENSE)
## Initial Contributors
- [Yamel Senih](https://github.com/yamelsenih)
- [Raúl Muñoz](https://github.com/Raul-mz)
- [Edwin Betancourt](https://github.com/EdwinBetanc0urt)
- [Leonel Matos](https://github.com/leonel1524)
- [Elsio Sanchez](https://github.com/elsiosanchez)

View File

@ -1,235 +0,0 @@
<p align="center">
<img width="320" src="https://wpimg.wallstcn.com/ecc53a42-d79b-42e2-8852-5126b810a4c8.svg">
</p>
<p align="center">
<a href="https://github.com/vuejs/vue">
<img src="https://img.shields.io/badge/vue-2.6.10-brightgreen.svg" alt="vue">
</a>
<a href="https://github.com/ElemeFE/element">
<img src="https://img.shields.io/badge/element--ui-2.7.0-brightgreen.svg" alt="element-ui">
</a>
<a href="https://travis-ci.org/PanJiaChen/vue-element-admin" rel="nofollow">
<img src="https://travis-ci.org/PanJiaChen/vue-element-admin.svg?branch=master" alt="Build Status">
</a>
<a href="https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/mashape/apistatus.svg" alt="license">
</a>
<a href="https://github.com/PanJiaChen/vue-element-admin/releases">
<img src="https://img.shields.io/github/release/PanJiaChen/vue-element-admin.svg" alt="GitHub release">
</a>
<a href="https://gitter.im/vue-element-admin/discuss">
<img src="https://badges.gitter.im/Join%20Chat.svg" alt="gitter">
</a>
<a href="https://panjiachen.gitee.io/vue-element-admin-site/zh/donate">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg" alt="donate">
</a>
</p>
简体中文 | [English](./README.md)
## 简介
[vue-element-admin](https://panjiachen.github.io/vue-element-admin) 是一个后台前端解决方案,它基于 [vue](https://github.com/vuejs/vue) 和 [element-ui](https://github.com/ElemeFE/element)实现。它使用了最新的前端技术栈,内置了 i18n 国际化解决方案,动态路由,权限验证,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。相信不管你的需求是什么,本项目都能帮助到你。
- [在线预览](https://panjiachen.github.io/vue-element-admin)
- [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
- [Gitter 讨论组](https://gitter.im/vue-element-admin/discuss)
- [Donate](https://panjiachen.gitee.io/vue-element-admin-site/zh/donate)
- [Wiki](https://github.com/PanJiaChen/vue-element-admin/wiki)
- [Gitee](https://panjiachen.gitee.io/vue-element-admin/) 在线预览(国内用户可访问该地址)
- [国内访问文档](https://panjiachen.gitee.io/vue-element-admin-site/zh/) 文档(方便没翻墙的用户查看)
- 基础模板建议使用: [vue-admin-template](https://github.com/PanJiaChen/vue-admin-template)
- 桌面端: [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
- Typescript 版: [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (鸣谢: [@Armour](https://github.com/Armour))
**`v4.1.0+`版本之后默认 master 分支将不支持国际化,有需要的请使用[i18n](https://github.com/PanJiaChen/vue-element-admin/tree/i18n)分支,它会和 master 保持同步更新**
**该项目不支持低版本浏览器(如 ie),有需求请自行添加 polyfill [详情](https://github.com/PanJiaChen/vue-element-admin/wiki#babel-polyfill)**
**目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若发现问题,欢迎提[issue](https://github.com/PanJiaChen/vue-element-admin/issues/new)。若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-element-admin/tree/tag/3.11.0),它不依赖 `vue-cli`**
群主 **[圈子](https://jianshiapp.com/circles/1209)** 群主会经常分享一些技术相关的东西,或者加入 [qq 群](https://github.com/PanJiaChen/vue-element-admin/issues/602) 或者关注 [微博](https://weibo.com/u/3423485724?is_all=1)
## 前序准备
你需要在本地安装 [node](http://nodejs.org/) 和 [git](https://git-scm.com/)。本项目技术栈基于 [ES2015+](http://es6.ruanyifeng.com/)、[vue](https://cn.vuejs.org/index.html)、[vuex](https://vuex.vuejs.org/zh-cn/)、[vue-router](https://router.vuejs.org/zh-cn/) 、[vue-cli](https://github.com/vuejs/vue-cli) 、[axios](https://github.com/axios/axios) 和 [element-ui](https://github.com/ElemeFE/element),所有的请求数据都使用[Mock.js](https://github.com/nuysoft/Mock)进行模拟,提前了解和学习这些知识会对使用本项目有很大的帮助。
同时配套了系列教程文章,如何从零构建后一个完整的后台项目,建议大家先看完这些文章再来实践本项目
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
- [手摸手带你用vue撸后台 系列五(v4.0新版本)](https://juejin.im/post/5c92ff94f265da6128275a85)
- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
- [手摸手,带你优雅的使用 icon](https://juejin.im/post/59bb864b5188257e7a427c09)
- [手摸手,带你用合理的姿势使用 webpack4](https://juejin.im/post/5b56909a518825195f499806)
- [手摸手,带你用合理的姿势使用 webpack4](https://juejin.im/post/5b5d6d6f6fb9a04fea58aabc)
**如有问题请先看上述使用文档和文章,若不能满足,欢迎 issue 和 pr**
<p align="center">
<img width="900" src="https://wpimg.wallstcn.com/a5894c1b-f6af-456e-82df-1151da0839bf.png">
</p>
## Sponsors
Become a sponsor and get your logo on our README on GitHub with a link to your site. [[Become a sponsor]](https://www.patreon.com/panjiachen)
<a href="https://flatlogic.com/admin-dashboards?from=vue-element-admin"><img width="150px" src="https://wpimg.wallstcn.com/9c0b719b-5551-4c1e-b776-63994632d94a.png" /></a><p>Admin Dashboard Templates made with Vue, React and Angular.</p>
## 功能
```
- 登录 / 注销
- 权限验证
- 页面权限
- 指令权限
- 权限配置
- 二步登录
- 多环境发布
- dev sit stage prod
- 全局功能
- 国际化多语言
- 多种动态换肤
- 动态侧边栏(支持多级路由嵌套)
- 动态面包屑
- 快捷导航(标签页)
- Svg Sprite 图标
- 本地/后端 mock 数据
- Screenfull全屏
- 自适应收缩侧边栏
- 编辑器
- 富文本
- Markdown
- JSON 等多格式
- Excel
- 导出excel
- 导入excel
- 前端可视化excel
- 导出zip
- 表格
- 动态表格
- 拖拽表格
- 内联编辑
- 错误页面
- 401
- 404
- 組件
- 头像上传
- 返回顶部
- 拖拽Dialog
- 拖拽Select
- 拖拽看板
- 列表拖拽
- SplitPane
- Dropzone
- Sticky
- CountTo
- 综合实例
- 错误日志
- Dashboard
- 引导页
- ECharts 图表
- Clipboard(剪贴复制)
- Markdown2html
```
## 开发
```bash
# 克隆项目
git clone -b i18n git@github.com:PanJiaChen/vue-element-admin.git
# 进入项目目录
cd vue-element-admin
# 安装依赖
npm install
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev
```
浏览器访问 http://localhost:9527
## 发布
```bash
# 构建测试环境
npm run build:stage
# 构建生产环境
npm run build:prod
```
## 其它
```bash
# 预览发布环境效果
npm run preview
# 预览发布环境效果 + 静态资源分析
npm run preview -- --report
# 代码格式检查
npm run lint
# 代码格式检查并自动修复
npm run lint -- --fix
```
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
## Changelog
Detailed changes for each release are documented in the [release notes](https://github.com/PanJiaChen/vue-element-admin/releases).
## Online Demo
[在线 Demo](https://panjiachen.github.io/vue-element-admin)
## Donate
如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 :tropical_drink:
![donate](https://panjiachen.github.io/donate/donation.png)
[更多捐赠方式](https://panjiachen.gitee.io/vue-element-admin-site/zh/donate)
[Paypal Me](https://www.paypal.me/panfree23)
[Buy me a coffee](https://www.buymeacoffee.com/Pan)
## Browsers support
Modern browsers and Internet Explorer 10+.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](https://godban.github.io/browsers-support-badges/)</br>Safari |
| --------- | --------- | --------- | --------- |
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
## License
[MIT](https://github.com/PanJiaChen/vue-element-admin/blob/master/LICENSE)
Copyright (c) 2017-present PanJiaChen

View File

@ -41,7 +41,7 @@ export const constantRoutes = [
path: 'dashboard',
component: 'views/dashboard/index',
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'dashboard', affix: true }
meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
}
]
},
@ -53,7 +53,7 @@ export const constantRoutes = [
path: 'index',
component: 'views/documentation/index',
name: 'Documentation',
meta: { title: 'documentation', icon: 'documentation', affix: true }
meta: { title: 'Documentation', icon: 'documentation', affix: true }
}
]
},
@ -66,7 +66,7 @@ export const constantRoutes = [
path: 'index',
component: 'views/guide/index',
name: 'Guide',
meta: { title: 'guide', icon: 'guide', noCache: true }
meta: { title: 'Guide', icon: 'guide', noCache: true }
}
]
}
@ -79,7 +79,7 @@ export const asyncRoutes = [
redirect: '/permission/index',
alwaysShow: true,
meta: {
title: 'permission',
title: 'Permission',
icon: 'lock',
roles: ['admin', 'editor']
},
@ -89,7 +89,7 @@ export const asyncRoutes = [
component: 'views/permission/page',
name: 'PagePermission',
meta: {
title: 'pagePermission',
title: 'Page Permission',
roles: ['admin']
}
},
@ -98,7 +98,7 @@ export const asyncRoutes = [
component: 'views/permission/directive',
name: 'DirectivePermission',
meta: {
title: 'directivePermission'
title: 'Directive Permission'
}
},
{
@ -106,7 +106,7 @@ export const asyncRoutes = [
component: 'views/permission/role',
name: 'RolePermission',
meta: {
title: 'rolePermission',
title: 'Role Permission',
roles: ['admin']
}
}
@ -121,7 +121,7 @@ export const asyncRoutes = [
path: 'index',
component: 'views/icons/index',
name: 'Icons',
meta: { title: 'icons', icon: 'icon', noCache: true }
meta: { title: 'Icons', icon: 'icon', noCache: true }
}
]
},
@ -132,7 +132,7 @@ export const asyncRoutes = [
redirect: 'noRedirect',
name: 'ComponentDemo',
meta: {
title: 'components',
title: 'Components',
icon: 'component'
},
children: [
@ -140,49 +140,49 @@ export const asyncRoutes = [
path: 'tinymce',
component: 'views/components-demo/tinymce',
name: 'TinymceDemo',
meta: { title: 'tinymce' }
meta: { title: 'Tinymce' }
},
{
path: 'markdown',
component: 'views/components-demo/markdown',
name: 'MarkdownDemo',
meta: { title: 'markdown' }
meta: { title: 'Markdown' }
},
{
path: 'json-editor',
component: 'views/components-demo/json-editor',
name: 'JsonEditorDemo',
meta: { title: 'jsonEditor' }
meta: { title: 'Json Editor' }
},
{
path: 'split-pane',
component: 'views/components-demo/split-pane',
name: 'SplitpaneDemo',
meta: { title: 'splitPane' }
meta: { title: 'SplitPane' }
},
{
path: 'avatar-upload',
component: 'views/components-demo/avatar-upload',
name: 'AvatarUploadDemo',
meta: { title: 'avatarUpload' }
meta: { title: 'Avatar Upload' }
},
{
path: 'dropzone',
component: 'views/components-demo/dropzone',
name: 'DropzoneDemo',
meta: { title: 'dropzone' }
meta: { title: 'Dropzone' }
},
{
path: 'sticky',
component: 'views/components-demo/sticky',
name: 'StickyDemo',
meta: { title: 'sticky' }
meta: { title: 'Sticky' }
},
{
path: 'count-to',
component: 'views/components-demo/count-to',
name: 'CountToDemo',
meta: { title: 'countTo' }
meta: { title: 'Count To' }
},
{
path: 'mixin',
@ -194,31 +194,31 @@ export const asyncRoutes = [
path: 'back-to-top',
component: 'views/components-demo/back-to-top',
name: 'BackToTopDemo',
meta: { title: 'backToTop' }
meta: { title: 'Back To Top' }
},
{
path: 'drag-dialog',
component: 'views/components-demo/drag-dialog',
name: 'DragDialogDemo',
meta: { title: 'dragDialog' }
meta: { title: 'Drag Dialog' }
},
{
path: 'drag-select',
component: 'views/components-demo/drag-select',
name: 'DragSelectDemo',
meta: { title: 'dragSelect' }
meta: { title: 'Drag Select' }
},
{
path: 'dnd-list',
component: 'views/components-demo/dnd-list',
name: 'DndListDemo',
meta: { title: 'dndList' }
meta: { title: 'Dnd List' }
},
{
path: 'drag-kanban',
component: 'views/components-demo/drag-kanban',
name: 'DragKanbanDemo',
meta: { title: 'dragKanban' }
meta: { title: 'Drag Kanban' }
}
]
},
@ -228,7 +228,7 @@ export const asyncRoutes = [
redirect: 'noRedirect',
name: 'Charts',
meta: {
title: 'charts',
title: 'Charts',
icon: 'chart'
},
children: [
@ -236,19 +236,19 @@ export const asyncRoutes = [
path: 'keyboard',
component: 'views/charts/keyboard',
name: 'KeyboardChart',
meta: { title: 'keyboardChart', noCache: true }
meta: { title: 'Keyboard Chart', noCache: true }
},
{
path: 'line',
component: 'views/charts/line',
name: 'LineChart',
meta: { title: 'lineChart', noCache: true }
meta: { title: 'Line Chart', noCache: true }
},
{
path: 'mixchart',
component: 'views/charts/mixChart',
name: 'MixChart',
meta: { title: 'mixChart', noCache: true }
meta: { title: 'Mix Chart', noCache: true }
}
]
},
@ -258,7 +258,7 @@ export const asyncRoutes = [
redirect: '/nested/menu1/menu1-1',
name: 'Nested',
meta: {
title: 'nested',
title: 'Nested',
icon: 'nested'
},
children: [
@ -266,33 +266,33 @@ export const asyncRoutes = [
path: 'menu1',
component: 'views/nested/menu1/index',
name: 'Menu1',
meta: { title: 'menu1' },
meta: { title: 'Menu1' },
redirect: '/nested/menu1/menu1-1',
children: [
{
path: 'menu1-1',
component: 'views/nested/menu1/menu1-1',
name: 'Menu1-1',
meta: { title: 'menu1-1' }
meta: { title: 'Menu1-1' }
},
{
path: 'menu1-2',
component: 'views/nested/menu1/menu1-2',
name: 'Menu1-2',
redirect: '/nested/menu1/menu1-2/menu1-2-1',
meta: { title: 'menu1-2' },
meta: { title: 'Menu1-2' },
children: [
{
path: 'menu1-2-1',
component: 'views/nested/menu1/menu1-2/menu1-2-1',
name: 'Menu1-2-1',
meta: { title: 'menu1-2-1' }
meta: { title: 'Menu1-2-1' }
},
{
path: 'menu1-2-2',
component: 'views/nested/menu1/menu1-2/menu1-2-2',
name: 'Menu1-2-2',
meta: { title: 'menu1-2-2' }
meta: { title: 'Menu1-2-2' }
}
]
},
@ -300,7 +300,7 @@ export const asyncRoutes = [
path: 'menu1-3',
component: 'views/nested/menu1/menu1-3',
name: 'Menu1-3',
meta: { title: 'menu1-3' }
meta: { title: 'Menu1-3' }
}
]
},
@ -308,7 +308,7 @@ export const asyncRoutes = [
path: 'menu2',
name: 'Menu2',
component: 'views/nested/menu2/index',
meta: { title: 'menu2' }
meta: { title: 'Menu2' }
}
]
},
@ -319,7 +319,7 @@ export const asyncRoutes = [
redirect: '/example/list',
name: 'Example',
meta: {
title: 'example',
title: 'Example',
icon: 'example'
},
children: [
@ -327,20 +327,20 @@ export const asyncRoutes = [
path: 'create',
component: 'views/example/create',
name: 'CreateArticle',
meta: { title: 'createArticle', icon: 'edit' }
meta: { title: 'Create Article', icon: 'edit' }
},
{
path: 'edit/:id(\\d+)',
component: 'views/example/edit',
name: 'EditArticle',
meta: { title: 'editArticle', noCache: true },
meta: { title: 'Edit Article', noCache: true },
hidden: true
},
{
path: 'list',
component: 'views/example/list',
name: 'ArticleList',
meta: { title: 'articleList', icon: 'list' }
meta: { title: 'Article List', icon: 'list' }
}
]
},
@ -353,7 +353,7 @@ export const asyncRoutes = [
path: 'index',
component: 'views/tab/index',
name: 'Tab',
meta: { title: 'tab', icon: 'tab' }
meta: { title: 'Tab', icon: 'tab' }
}
]
},
@ -364,7 +364,7 @@ export const asyncRoutes = [
redirect: 'noRedirect',
name: 'ErrorPages',
meta: {
title: 'errorPages',
title: 'Error Pages',
icon: '404'
},
children: [
@ -372,13 +372,13 @@ export const asyncRoutes = [
path: '401',
component: 'views/error-page/401',
name: 'Page401',
meta: { title: 'page401', noCache: true }
meta: { title: 'Page 401', noCache: true }
},
{
path: '404',
component: 'views/error-page/404',
name: 'Page404',
meta: { title: 'page404', noCache: true }
meta: { title: 'Page 404', noCache: true }
}
]
},
@ -392,7 +392,7 @@ export const asyncRoutes = [
path: 'log',
component: 'views/error-log/index',
name: 'ErrorLog',
meta: { title: 'errorLog', icon: 'bug' }
meta: { title: 'Error Log', icon: 'bug' }
}
]
},
@ -403,7 +403,7 @@ export const asyncRoutes = [
redirect: '/excel/export-excel',
name: 'Excel',
meta: {
title: 'excel',
title: 'Excel',
icon: 'excel'
},
children: [
@ -411,25 +411,25 @@ export const asyncRoutes = [
path: 'export-excel',
component: 'views/excel/export-excel',
name: 'ExportExcel',
meta: { title: 'exportExcel' }
meta: { title: 'Export Excel' }
},
{
path: 'export-selected-excel',
component: 'views/excel/select-excel',
name: 'SelectExcel',
meta: { title: 'selectExcel' }
meta: { title: 'Select Excel' }
},
{
path: 'export-merge-header',
component: 'views/excel/merge-header',
name: 'MergeHeader',
meta: { title: 'mergeHeader' }
meta: { title: 'Merge Header' }
},
{
path: 'upload-excel',
component: 'views/excel/upload-excel',
name: 'UploadExcel',
meta: { title: 'uploadExcel' }
meta: { title: 'Upload Excel' }
}
]
},
@ -439,13 +439,13 @@ export const asyncRoutes = [
component: 'layout/Layout',
redirect: '/zip/download',
alwaysShow: true,
meta: { title: 'zip', icon: 'zip' },
meta: { title: 'Zip', icon: 'zip' },
children: [
{
path: 'download',
component: 'views/zip/index',
name: 'ExportZip',
meta: { title: 'exportZip' }
meta: { title: 'Export Zip' }
}
]
},
@ -459,7 +459,7 @@ export const asyncRoutes = [
path: 'index',
component: 'views/pdf/index',
name: 'PDF',
meta: { title: 'pdf', icon: 'pdf' }
meta: { title: 'PDF', icon: 'pdf' }
}
]
},
@ -478,7 +478,7 @@ export const asyncRoutes = [
path: 'index',
component: 'views/theme/index',
name: 'Theme',
meta: { title: 'theme', icon: 'theme' }
meta: { title: 'Theme', icon: 'theme' }
}
]
},
@ -492,7 +492,7 @@ export const asyncRoutes = [
path: 'index',
component: 'views/clipboard/index',
name: 'ClipboardDemo',
meta: { title: 'clipboardDemo', icon: 'clipboard' }
meta: { title: 'Clipboard Demo', icon: 'clipboard' }
}
]
},
@ -505,7 +505,7 @@ export const asyncRoutes = [
path: 'index',
component: 'views/i18n-demo/index',
name: 'I18n',
meta: { title: 'i18n', icon: 'international' }
meta: { title: 'I18n', icon: 'international' }
}
]
},
@ -516,7 +516,7 @@ export const asyncRoutes = [
children: [
{
path: 'https://github.com/PanJiaChen/vue-element-admin',
meta: { title: 'externalLink', icon: 'link' }
meta: { title: 'External Link', icon: 'link' }
}
]
},

View File

@ -1,10 +1,11 @@
{
"name": "vue-element-admin",
"version": "4.0.1",
"name": "adempiere-vue",
"version": "4.1.0",
"description": "A magical vue admin. An out-of-box UI solution for enterprise applications. Newest development stack of vue. Lots of awesome features",
"author": "Pan <panfree23@gmail.com>",
"license": "MIT",
"scripts": {
"start": "vue-cli-service serve",
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
@ -43,6 +44,11 @@
"url": "https://github.com/PanJiaChen/vue-element-admin/issues"
},
"dependencies": {
"@adempiere/grpc-access-client": "^1.1.6",
"@adempiere/grpc-data-client": "^1.7.3",
"@adempiere/grpc-dictionary-client": "^1.3.1",
"@adempiere/grpc-enrollment-client": "^1.0.6",
"autoprefixer": "^9.5.1",
"axios": "0.18.0",
"clipboard": "2.0.4",
"codemirror": "5.45.0",
@ -55,6 +61,8 @@
"js-cookie": "2.2.0",
"jsonlint": "1.6.3",
"jszip": "3.2.1",
"mime-type": "^3.0.7",
"moment": "^2.24.0",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
@ -65,9 +73,11 @@
"vue": "2.6.10",
"vue-count-to": "1.0.13",
"vue-i18n": "7.3.2",
"vue-router": "3.0.2",
"vue-multipane": "^0.9.5",
"vue-router": "3.0.4",
"vue-split-panel": "^1.0.4",
"vue-splitpane": "1.0.4",
"vuedraggable": "2.20.0",
"vuedraggable": "^2.20.0",
"vuex": "3.1.0",
"xlsx": "0.14.1"
},
@ -76,12 +86,12 @@
"@babel/register": "7.0.0",
"@vue/cli-plugin-babel": "3.5.3",
"@vue/cli-plugin-eslint": "3.5.1",
"@vue/cli-plugin-unit-jest": "3.5.3",
"@vue/cli-plugin-unit-jest": "^3.7.0",
"@vue/cli-service": "3.5.3",
"@vue/test-utils": "1.0.0-beta.29",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.0.1",
"babel-jest": "23.6.0",
"babel-jest": "^24.8.0",
"chalk": "2.4.2",
"chokidar": "2.1.5",
"connect": "3.6.6",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -6,6 +6,27 @@
<script>
export default {
name: 'App'
name: 'App',
mounted() {
this.$nextTick(() => {
window.addEventListener('resize', this.getWindowWidth)
window.addEventListener('resize', this.getWindowHeight)
this.getWindowWidth()
this.getWindowHeight()
})
},
beforeDestroy() {
window.removeEventListener('resize', this.getWindowWidth)
window.removeEventListener('resize', this.getWindowHeight)
},
methods: {
getWindowWidth(event) {
this.$store.dispatch('setWidth', document.documentElement.clientWidth)
},
getWindowHeight(event) {
this.$store.dispatch('setHeight', document.documentElement.clientHeight)
}
}
}
</script>

View File

@ -0,0 +1,14 @@
export const ADDRESS_HOST = 'http://localhost'
export const PORT_DICTIONARY = '8990'
export const HOST_GRPC_DICTIONARY = ADDRESS_HOST + ':' + PORT_DICTIONARY
export const PORT_DATA = '8991'
export const HOST_GRPC_DATA = ADDRESS_HOST + ':' + PORT_DATA
export const PORT_AUTHENTICATION = '8989'
export const HOST_GRPC_AUTHENTICATION = ADDRESS_HOST + ':' + PORT_AUTHENTICATION
export const PORT_ENROLLMENT = '8992'
export const HOST_GRPC_ENROLLMENT = ADDRESS_HOST + ':' + PORT_ENROLLMENT

320
src/api/ADempiere/data.js Normal file
View File

@ -0,0 +1,320 @@
import { getLanguage } from '@/lang/index'
import { getToken } from '@/utils/auth'
import BusinessData from '@adempiere/grpc-data-client'
import { HOST_GRPC_DATA } from '@/api/ADempiere/constants'
// Get Instance for connection
function Instance() {
return new BusinessData(
HOST_GRPC_DATA,
getToken(),
getLanguage() || 'en_US'
)
}
/**
* Converted the gRPC value to the value needed
* @param {object} grpcValue Value get of gRPC
* @returns {mixed}
*/
export function convertValueFromGRPC(grpcValue) {
return Instance.call(this).convertValueFromGRPC(grpcValue)
}
/**
* Create entity
* @param {string} parameters.tableName
* @param {array} parameters.attributesList
*/
export function createEntity(parameters) {
var entityRequest = Instance.call(this).getCreateEntityRequest()
entityRequest.setTablename(parameters.tableName)
if (parameters.attributesList && parameters.attributesList.length) {
parameters.attributesList.forEach(attribute => {
const convertedAttribute = Instance.call(this).convertParameter(attribute)
entityRequest.addAttributes(convertedAttribute)
})
}
// Create Entity
return Instance.call(this).createEntity(entityRequest)
}
/**
* Update entity
* @param {string} parameters.tableName
* @param {integer} parameters.recordId
* @param {string} parameters.recordUuid
* @param {array} parameters.attributesList
*/
export function updateEntity(parameters) {
var entityRequest = Instance.call(this).getUpdateEntityRequest()
entityRequest.setTablename(parameters.tableName)
if (parameters.recordId) {
entityRequest.setRecordid(parameters.recordId)
}
entityRequest.setUuid(parameters.recordUuid)
if (parameters.attributesList && parameters.attributesList.length) {
parameters.attributesList.forEach(attribute => {
const convertedAttribute = Instance.call(this).convertParameter(attribute)
entityRequest.addAttributes(convertedAttribute)
})
}
// Update Entity
return Instance.call(this).updateEntity(entityRequest)
}
/**
* Delete entity
* @param {string} parameters.tableName
* @param {integer} parameters.recordId
* @param {string} parameters.recordUuid
* @param {array} parameters.attributesList
*/
export function deleteEntity(parameters) {
var entityRequest = Instance.call(this).getUpdateEntityRequest()
entityRequest.setTablename(parameters.tableName)
if (parameters.recordId) {
entityRequest.setRecordid(parameters.recordId)
}
entityRequest.setUuid(parameters.recordUuid)
// Delete Entity
return Instance.call(this).deleteEntity(entityRequest)
}
export function getCriteria(tableName) {
return Instance.call(this).getCriteria(tableName)
}
export function getObject(table, uuid = false, id = false) {
return Instance.call(this).getEntity(
Instance.call(this).getEntityRequest(table, uuid, id)
)
}
/**
* Object List from window
* @param {string} object.tableName
* @param {string} object.query
* @param {string} object.whereClause
* @param {string} object.orderByClause
*/
export function getObjectListFromCriteria(object) {
const criteriaForList = getCriteria(object.tableName)
criteriaForList.setQuery(object.query)
if (object.whereClause) {
criteriaForList.setWhereclause(object.whereClause)
}
if (object.orderByClause) {
criteriaForList.setOrderbyclause(object.orderByClause)
}
// add conditions
if (object.conditions && object.conditions.length) {
object.conditions.forEach(itemCondition => {
const convertCondition = Instance.call(this).convertCondition(itemCondition)
criteriaForList.addConditions(convertCondition)
})
}
var nextPageToken
if (object.nextPageToken) {
nextPageToken = object.nextPageToken
}
return Instance.call(this).requestObjectListFromCriteria(criteriaForList, nextPageToken)
}
/**
* Rollback entity (Create, Update, Delete)
* @param {string} parametersRollback.tableName
* @param {integer} parametersRollback.recordId
* @param {string} parametersRollback.eventType
*/
export function rollbackEntity(parametersRollback) {
var rollbackRequest = Instance.call(this).getRollbackEntityRequest()
rollbackRequest.setTablename(parametersRollback.tableName)
rollbackRequest.setRecordid(parametersRollback.recordId)
// set event type
var eventType = Instance.call(this).getEventType()
eventType = eventType[parametersRollback.eventType]
rollbackRequest.setEventtype(eventType)
return Instance.call(this).rollbackEntityRequest(rollbackRequest)
}
/**
* Request a Lookup list data from Reference
* The main attributes that function hope are:
* @param {string} reference.tableName
* @param {string} reference.query
*/
export function getLookupList(reference) {
return Instance.call(this).requestLookupListFromReference(reference)
}
/**
* Request a Lookup data from Reference
* The main attributes that function hope are:
* @param {string} reference.tableName
* @param {string} reference.directQuery
* @param {string|number} value
*/
export function getLookup(reference) {
return Instance.call(this).requestLookupFromReference({
tableName: reference.tableName,
directQuery: reference.directQuery
}, reference.value)
}
/**
* Request a process
* This function allows follow structure:
* @param {object} process
* @param {string} process.uuid, uuid from process to run
* @param {integer} process.tableName, table name of tab, used only window
* @param {integer} process.recordId, record identifier, used only window
* @param {array} process.parameters, parameters from process
[ { columnName, value } ]
* @param {array} process.selection, selection records, used only browser
[ {
selectionId,
selectionValues [
{ columnName, value }
]
} ]
*/
export function runProcess(process) {
var processRequest = Instance.call(this).getProcessRequest()
// Fill Request process
processRequest.setUuid(process.uuid)
// report export type
if (process.reportType) {
processRequest.setReporttype(process.reportType)
}
// process params
if (process.parameters && process.parameters.length) {
process.parameters.forEach(parameter => {
const convertedParameter = Instance.call(this).convertParameter(parameter)
processRequest.addParameters(convertedParameter)
})
}
// record in window
if (process.tableName) {
processRequest.setTablename(process.tableName)
processRequest.setRecordid(process.recordId)
}
// browser selection list records
if (process.selection && process.selection.length) {
process.selection.forEach(record => {
// selection format = { selectionId: integer, selectionValues: array }
const convertedRecord = Instance.call(this).convertSelection(record)
processRequest.addSelections(convertedRecord)
})
}
// Run Process
return Instance.call(this).requestProcess(processRequest)
}
/**
* Request a browser search
* This function allows follow structure:
* @param {string} browser.uuid
* @param {string} browser.query
* @param {string} browser.whereClause
* @param {string} browser.orderByClause
* @param {array} browser.parameters
* [{
* columnName,
* value
* }]
*/
export function getBrowserSearch(browser) {
var browserRequest = Instance.call(this).getBrowserRequest()
var criteria = Instance.call(this).getCriteria('')
// Fill Request browser
browserRequest.setUuid(browser.uuid)
criteria.setQuery(browser.query)
criteria.setWhereclause(browser.whereClause)
criteria.setOrderbyclause(browser.orderByClause)
if (browser.nextPageToken) {
browserRequest.setPageToken(browser.nextPageToken)
}
browserRequest.setCriteria(criteria)
/* isQueryCriteria fields parameters */
if (browser.parameters !== undefined) {
browser.parameters.forEach(parameter => {
const convertedParameter = Instance.call(this).convertParameter(parameter)
browserRequest.addParameters(convertedParameter)
})
}
// Run browser
return Instance.call(this).requestBrowser(browserRequest)
}
// Request a Process Activity list
export function requestProcessActivity() {
// Get Process Activity
return Instance.call(this).requestProcessActivity()
}
export function getRecentItems() {
return Instance.call(this).requestRecentItems()
}
/**
* forget password
* @param {string} parameters.forgetPassword
*/
export function getForgetPassword(parameters) {
return Instance.call(this).requestForgetPassword(parameters)
}
/**
* Reference List from Window
* @param {string} parameters.tableName
* @param {string} parameters.windowUuid
* @param {string} parameters.recordUuid
* @param {integer} parameters.recordId
*/
export function getReferencesList(parameters) {
var requestReference = Instance.call(this).getReferencesRequest()
requestReference.setWindowuuid(parameters.windowUuid)
requestReference.setTablename(parameters.tableName)
requestReference.setUuid(parameters.recordUuid)
if (parameters.recordId) {
requestReference.setRecordid(parameters.recordId)
}
return Instance.call(this).listReferencesRequest(requestReference)
}
/**
* Run callout request
* @param {string} parametersCallout.windowUuid
* @param {integer} parametersCallout.windowNo
* @param {string} parametersCallout.tabUuid
* @param {string} parametersCallout.tableName
* @param {string} parametersCallout.columnName
* @param {mixed} parametersCallout.value
* @param {mixed} parametersCallout.oldValue
* @param {string} parametersCallout.callout
* @param {array} parametersCallout.attributesList
* @returns {Map} Entity
*/
export function runCallOutRequest(parametersCallout) {
return Instance.call(this).runCalloutRequest(parametersCallout)
}
export function getDefaultValueFromServer(query) {
return Instance.call(this).getDefaultValue(query)
}
export function getContextInfoValueFromServer({ uuid, query }) {
return Instance.call(this).getContextInfoValue({ uuid: uuid, query: query })
}

View File

@ -0,0 +1,33 @@
import { getLanguage } from '@/lang/index'
import { getToken } from '@/utils/auth'
import Dictionary from '@adempiere/grpc-dictionary-client'
import { HOST_GRPC_DICTIONARY } from '@/api/ADempiere/constants'
// Get Instance for connection
function Instance() {
return new Dictionary(
HOST_GRPC_DICTIONARY,
getToken(),
getLanguage() || 'en_US'
)
}
export function getWindow(uuid, childrenTabs = true) {
return Instance.call(this).requestWindow(uuid, childrenTabs)
}
export function getProcess(uuid) {
return Instance.call(this).requestProcess(uuid)
}
export function getBrowser(uuid) {
return Instance.call(this).requestBrowser(uuid)
}
export function getTab(uuid, childrenFields = true) {
return Instance.call(this).requestTab(uuid, childrenFields)
}
export function getField(uuid) {
return Instance.call(this).requestField(uuid)
}

View File

@ -0,0 +1,38 @@
import Enrollment from '@adempiere/grpc-enrollment-client'
import { HOST_GRPC_ENROLLMENT } from '@/api/ADempiere/constants'
// Get Instance for connection
function Instance() {
return new Enrollment(
HOST_GRPC_ENROLLMENT,
3.9,
'ADempier-Vue'
)
}
/**
* enroll User
* @param {string} userData.name
* @param {string} userData.userName
* @param {string} userData.password
*/
export function enrollmentUser(userData) {
return Instance.call(this).enrollUser(userData)
}
/**
* Request from forgot password
* @param {string} eMailOrUserName
*/
export function forgotPassword(eMailOrUserName) {
return Instance.call(this).requestResetPassword(eMailOrUserName)
}
/**
* Request from reset password with token
* @param {string} token
* @param {string} password
*/
export function resetPasswordFromToken(token, password) {
return Instance.call(this).resetPasswordFromToken(token, password)
}

View File

@ -0,0 +1,4 @@
// Export dictionary connection
export * from './dictionary'
// Export data connection
export * from './data'

View File

@ -1,24 +1,96 @@
import request from '@/utils/request'
import { getLanguage } from '@/lang/index'
import Access from '@adempiere/grpc-access-client'
import { HOST_GRPC_AUTHENTICATION } from '@/api/ADempiere/constants'
export function login(data) {
return request({
url: '/user/login',
method: 'post',
data
})
// Instance for connection
function Instance() {
return new Access(
HOST_GRPC_AUTHENTICATION,
'Version Epale',
getLanguage() || 'en_US'
)
}
// Make login by UserName and password, this function can return user data for show
export function login(loginValues) {
if (loginValues.role !== undefined && loginValues.role.trim() !== '') {
return Instance.call(this).requestLogin(
loginValues.userName,
loginValues.password,
loginValues.role,
null,
loginValues.language
)
} else {
return Instance.call(this).requestLoginDefault(
loginValues.userName,
loginValues.password,
loginValues.language
)
}
}
// Get User Info from session Uuid or token
export function getInfo(token) {
return request({
url: '/user/info',
method: 'get',
params: { token }
})
return Instance.call(this).requestUserInfoFromSession(token)
.then(session => {
var roles = []
var rolesList = session.getRolesList().map(itemRol => {
roles.push(itemRol.getName())
return {
id: itemRol.getId(),
uuid: itemRol.getUuid(),
name: itemRol.getName(),
description: itemRol.getDescription(),
clientId: itemRol.getClientid(),
clientName: itemRol.getClientname(),
organizationList: itemRol.getOrganizationsList()
}
})
// TODO: Add user.id, user.level in request
const user = session.getUserinfo()
const response = {
name: user.getName(),
comments: user.getComments(),
description: user.getDescription(),
// TODO: Add from ADempiere
avatar: 'https://avatars1.githubusercontent.com/u/1263359?s=200&v=4',
roles: roles, // rol list names, used from app (src/permission.js, src/utils/permission.js)
rolesList: rolesList,
responseGrpc: session
}
return response
}).catch(error => {
console.log(error)
})
}
export function logout() {
return request({
url: '/user/logout',
method: 'post'
})
/**
* Get session info
* @param {string} sessionUuid
*/
export function getSessionInfo(sessionUuid) {
return Instance.call(this).getSession(sessionUuid)
}
// Logout from server
export function logout(sessionUuid) {
return Instance.call(this).requestLogout(sessionUuid)
}
// Get User menu from server
export function getMenu(sessionUuid) {
return Instance.call(this).requestUserMenuFromSession(sessionUuid)
}
/**
*
* @param {string} attributes.sessionUuid
* @param {string} attributes.roleUuid
* @param {string} attributes.organizationUuid
* @param {string} attributes.warehouseUuid
*/
export function changeRole(attributes) {
return Instance.call(this).requestChangeRole(attributes)
}

View File

@ -0,0 +1,113 @@
<template>
<el-badge :value="getRecordNotification.length" :hidden="getRecordNotification.length === 0" type="primary" class="item" style="vertical-align: baseline;">
<el-popover
placement="bottom"
width="400"
trigger="click"
>
<el-table
:data="getRecordNotification"
:highlight-current-row="true"
@row-click="handleCurrentChange"
>
<el-table-column prop="name" :label="$t('navbar.badge.Notifications')" />
<el-table-column
fixed="right"
width="50"
>
<template slot="header">
<el-button
icon="el-icon-delete"
type="text"
@click.native.prevent="deleteAll()"
/>
</template>
<template slot-scope="scope">
<el-button
icon="el-icon-close"
type="text"
size="small"
@click.native.prevent="deleteRow(scope.$index, getRecordNotification)"
/>
</template>
</el-table-column>
<el-table-column
width="50"
>
<router-link :to="{ name: 'ProcessActivity' }">
<el-tooltip effect="dark" :content="$t('navbar.badge.link')" placement="top-start">
<svg-icon icon-class="tree-table" />
</el-tooltip>
</router-link>
</el-table-column>
</el-table>
<el-button slot="reference" type="text" icon="el-icon-bell" style="float: left;color: #000000;font-size: 121%;font-weight: 615!important;padding-top: 14px;" />
</el-popover>
</el-badge>
</template>
<script>
export default {
name: 'Badge',
data() {
return {
currentRow: null
}
},
computed: {
getRecordNotification() {
return this.$store.getters.getNotificationProcess
}
},
watch: {
show(value) {
if (value) {
document.body.addEventListener('click', this.close)
} else {
document.body.removeEventListener('click', this.close)
}
}
},
methods: {
close() {
this.$refs.badge && this.$refs.badge.blur()
this.options = []
this.show = false
},
handleCurrentChange(getRecordNotification, val, index, rows) {
if (val !== null) {
if (getRecordNotification && getRecordNotification.isReport) {
this.$router.push({
name: 'Report Viewer',
params: {
processId: getRecordNotification.processId,
instanceUuid: getRecordNotification.instanceUuid,
fileName: getRecordNotification.download
}
})
} else {
this.$router.push({
name: 'ProcessActivity'
})
}
}
},
deleteRow(index, rows) {
rows.splice(index, 1)
},
deleteAll() {
// rows.splice(index, rows.lenght)
this.getRecordNotification.splice(0)
}
}
}
</script>
<style>
.el-badge__content.is-fixed {
position: absolute;
top: 6px;
right: 10px;
-webkit-transform: translateY(-50%) translateX(100%);
transform: translateY(-50%) translateX(100%);
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<div class="container-submenu container-context-menu">
<el-menu :default-active="activeMenu" :router="false" class="el-menu-demo" mode="horizontal" menu-trigger="hover" unique-opened>
<template>
<el-submenu v-if="relations !== undefined && relations.length" class="el-menu-item" index="1">
<template slot="title">
{{ $t('components.contextMenuRelations') }}
</template>
<el-scrollbar wrap-class="scroll">
<item v-for="(relation, index) in relations" :key="index" :item="relation" />
</el-scrollbar>
</el-submenu>
<el-menu-item v-else disabled index="1">
{{ $t('components.contextMenuRelations') }}
</el-menu-item>
<el-submenu v-if="actions !== undefined && actions.length" class="el-menu-item" index="2" @click.native="runAction(actions[0])">
<template slot="title">
{{ $t('components.contextMenuActions') }}
</template>
<template v-for="(action, index) in actions">
<el-submenu v-if="action.childs" :key="index" :index="action.name" :disabled="action.disabled">
<template slot="title">
{{ action.name }}
</template>
<el-menu-item v-for="(child, key) in action.childs" :key="key" :index="child.uuid" @click="runAction(child)">
{{ child.name }}
</el-menu-item>
</el-submenu>
<el-menu-item v-else :key="index" :index="action.name" :disabled="action.disabled" @click="runAction(action)">
{{ action.name }}
</el-menu-item>
</template>
<el-menu-item v-show="isReport" index="4">
<a :href="downloads" :download="file">
{{ $t('components.contextMenuDownload') }}
</a>
</el-menu-item>
<el-menu-item v-if="getDataSelection.length > 0 && panelType === 'browser'" index="6" @click="exporBrowser">
{{ $t('components.contextMennuExport') }}
</el-menu-item>
<el-menu-item v-if="panelType === 'window'" index="7" @click="exporBrowser">
{{ $t('components.contextMennuWindowReport') }}
</el-menu-item>
<el-menu-item v-if="panelType !== 'process'" index="8" @click="refreshData">
{{ $t('components.contextMenuRefresh') }}
</el-menu-item>
<el-menu-item index="5" @click="setShareLink">
{{ $t('components.contextMenuShareLink') }}
</el-menu-item>
</el-submenu>
<el-menu-item v-else disabled index="2">
{{ $t('components.contextMenuActions') }}
</el-menu-item>
<el-submenu :disabled="!isReferencesLoaded" class="el-menu-item" index="3">
<template slot="title">
{{ $t('components.contextMenuReferences') }}
</template>
<template v-if="references && references.referencesList">
<template v-for="(reference, index) in references.referencesList">
<el-menu-item :key="index" :index="reference.displayName" @click="runAction(reference)">
{{ reference.displayName }}
</el-menu-item>
</template>
</template>
</el-submenu>
</template>
</el-menu>
</div>
</template>
<script>
import { contextMixin } from '@/components/ADempiere/ContextMenu/contextMenuMixin'
export default {
name: 'ContextMenuDesktop',
mixins: [contextMixin]
}
</script>

View File

@ -0,0 +1,410 @@
import { showNotification } from '@/utils/ADempiere/notification'
import Item from './items'
import { convertFieldListToShareLink } from '@/utils/ADempiere/valueUtil'
export const contextMixin = {
components: {
Item
},
props: {
menuParentUuid: {
type: String,
default: undefined
},
// uuid of the component where it is called
parentUuid: {
type: String,
default: undefined
},
// uuid of the component where it is called
containerUuid: {
type: String,
required: true
},
panelType: {
type: String,
default: undefined
},
isReport: {
type: Boolean,
default: false
},
lastParameter: {
type: String,
default: undefined
},
reportFormat: {
type: String,
default: undefined
},
modalMetadata: {
type: Object,
default: () => {}
},
// used only window
isInsertRecord: {
type: Boolean,
default: undefined
}
},
data() {
return {
actions: [],
references: [],
file: this.$store.getters.getProcessResult.download,
downloads: this.$store.getters.getProcessResult.url,
metadataMenu: {},
recordUuid: this.$route.query.action,
isReferencesLoaded: false,
exportDefault: 'xls'
}
},
computed: {
activeMenu() {
const { meta, path } = this.$route
// if set path, the sidebar will highlight the path you set
if (meta.activeMenu) {
return meta.activeMenu
}
return path
},
getDataSelection() {
return this.$store.getters.getDataRecordSelection(this.containerUuid)
},
relations() {
if (this.$route.params.menuParentUuid !== undefined) {
return this.$store.getters.getRelations(this.$route.params.menuParentUuid)
}
return this.$store.getters.getRelations(this.menuParentUuid)
},
getterContextMenu() {
return this.$store.getters.getContextMenu(this.containerUuid)
},
isReferecesContent() {
if (this.panelType === 'window' && !this.isEmptyValue(this.recordUuid) && this.recordUuid !== 'create-new') {
return true
}
return false
},
getterReferences() {
if (this.isReferecesContent) {
return this.$store.getters.getReferencesList(this.parentUuid, this.recordUuid)
}
return []
},
permissionRoutes() {
return this.$store.getters.permission_routes
},
valuesPanelToShare() {
return this.$store.getters.getParametersToShare({
containerUuid: this.containerUuid,
isOnlyDisplayed: true,
isAdvancedQuery: this.$route.query.action === 'advancedQuery'
})
},
getterDataLog() {
if (this.panelType === 'window') {
return this.$store.getters.getDataLog(this.containerUuid, this.recordUuid)
}
return undefined
},
processParametersExecuted() {
return this.$store.getters.getCachedReport(this.$route.params.instanceUuid).parameters
},
getterWindowOldRoute() {
if (this.panelType === 'window') {
const oldRoute = this.$store.state.windowControl.windowOldRoute
if (!this.isEmptyValue(oldRoute.query.action) && oldRoute.query.action !== 'create-new' && this.$route.query.action === 'create-new') {
return oldRoute
}
}
return false
}
},
watch: {
'$route.query.action'(actionValue) {
this.recordUuid = actionValue
// only requires updating the context menu if it is Window
if (this.panelType === 'window') {
this.generateContextMenu()
this.getReferences()
}
},
isInsertRecord(newValue, oldValue) {
if (this.panelType === 'window' && newValue !== oldValue) {
this.generateContextMenu()
}
},
getterDataLog(newValue, oldValue) {
if (this.panelType === 'window' && newValue !== oldValue) {
this.generateContextMenu()
}
}
},
created() {
this.generateContextMenu()
},
mounted() {
this.getReferences()
},
methods: {
showNotification,
refreshData() {
if (this.panelType === 'window') {
this.$store.dispatch('getDataListTab', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
isRefreshPanel: true,
recordUuid: this.recordUuid
})
} else if (this.panelType === 'browser') {
this.$store.dispatch('getBrowserSearch', {
containerUuid: this.containerUuid,
isClearSelection: true
})
}
},
getReferences() {
if (this.isReferecesContent) {
var references = this.getterReferences
if (references && references.length) {
this.references = references
this.isReferencesLoaded = true
} else {
this.$store.dispatch('getReferencesListFromServer', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
recordUuid: this.recordUuid
})
.then(response => {
this.references = this.$store.getters.getReferencesList(this.parentUuid, this.recordUuid)
if (this.references.referencesList.length) {
this.isReferencesLoaded = true
} else {
this.isReferencesLoaded = false
}
})
.catch(error => {
console.warn('References Load Error ' + error.code + ': ' + error.message)
})
}
} else {
this.references = []
this.isReferencesLoaded = false
}
},
exporBrowser() {
this.$store.dispatch('startProcess', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
panelType: this.panelType, // determinate if get table name and record id (window) or selection (browser)
reportFormat: this.exportDefault
})
.catch(error => {
console.warn(error)
})
},
generateContextMenu() {
this.metadataMenu = this.getterContextMenu
this.actions = this.metadataMenu.actions
if (this.actions && this.actions.length) {
this.actions.forEach(itemAction => {
// if no exists set prop with value
itemAction.disabled = false
if (this.$route.name !== 'Report Viewer' && itemAction.action === 'changeParameters') {
itemAction.disabled = true
}
if (this.$route.meta.type === 'report' && itemAction.action === 'startProcess') {
itemAction.reportExportType = 'html'
}
if (this.$route.meta.type === 'process' && itemAction.type === 'summary') {
itemAction.disabled = true
}
if (this.$route.meta.type === 'window') {
if (this.recordUuid === 'create-new') {
itemAction.disabled = true
} else {
if (this.isInsertRecord) {
itemAction.disabled = false
} else {
itemAction.disabled = true
}
}
if (itemAction.action === 'undoModifyData') {
itemAction.disabled = Boolean(!this.getterDataLog && !this.getterWindowOldRoute)
}
}
})
}
},
showModal(action) {
// TODO: Refactor and remove redundant dispatchs
if (action.type === 'process') {
var processData = this.$store.getters.getProcess(action.uuid)
if (processData === undefined) {
this.$store.dispatch('getProcessFromServer', {
containerUuid: action.uuid,
routeToDelete: this.$route
})
.then(response => {
this.$store.dispatch('setShowDialog', {
type: action.type,
action: response
})
}).catch(error => {
console.warn('ContextMenu: Dictionary Process (State) - Error ' + error.code + ': ' + error.message)
})
} else {
this.$store.dispatch('setShowDialog', { type: action.type, action: processData })
}
} else {
this.$store.dispatch('setShowDialog', { type: action.type, action: this.modalMetadata })
}
},
runAction(action) {
if (action.type === 'action') {
// run process or report
const fieldNotReady = this.$store.getters.isNotReadyForSubmit(this.$route.meta.uuid)
if (!fieldNotReady) {
var containerParams = this.$route.meta.uuid
if (this.lastParameter !== undefined) {
containerParams = this.lastParameter
}
var parentMenu = this.menuParentUuid
if (this.$route.params) {
if (this.$route.params.menuParentUuid) {
parentMenu = this.$route.params.menuParentUuid
}
}
if (this.panelType === 'process') {
this.$store.dispatch('setTempShareLink', { processId: this.$route.params.processId, href: window.location.href })
}
this.$store.dispatch(action.action, {
action: action,
parentUuid: this.containerUuid,
containerUuid: containerParams, // EVALUATE IF IS action.uuid
panelType: this.panelType, // determinate if get table name and record id (window) or selection (browser)
reportFormat: this.$route.query.reportType ? this.$route.query.reportType : action.reportExportType,
menuParentUuid: parentMenu, // to load relations in context menu (report view)
routeToDelete: this.$route
})
.catch(error => {
console.warn(error)
})
if (this.panelType === 'process') {
// TODO: Verify use
this.$store.dispatch('deleteRecordContainer', {
viewUuid: this.$route
})
}
} else {
this.showNotification({
type: 'warning',
title: this.$t('notifications.emptyValues'),
name: '<b>' + fieldNotReady.name + '.</b> ',
message: this.$t('notifications.fieldMandatory')
})
}
} else if (action.type === 'process') {
// run process associate with view (window or browser)
this.showModal(action)
if (this.panelType === 'process' || this.panelType === 'browser' || this.panelType === 'report') {
this.$store.dispatch('resetPanelToNew', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
panelType: this.panelType
})
}
} else if (action.type === 'dataAction') {
if (action.action === 'undoModifyData' && Boolean(!this.getterDataLog) && this.getterWindowOldRoute) {
this.$router.push({
path: this.getterWindowOldRoute.path,
query: {
...this.getterWindowOldRoute.query
}
})
} else {
this.$store.dispatch(action.action, {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
recordUuid: this.recordUuid,
panelType: this.panelType,
isNewRecord: action.action === 'resetPanelToNew'
})
}
} else if (action.type === 'reference') {
this.$store.dispatch('getWindowByUuid', { routes: this.permissionRoutes, windowUuid: action.windowUuid })
if (action.windowUuid && action.recordUuid) {
var windowRoute = this.$store.getters.getWindowRoute(action.windowUuid)
this.$router.push({ name: windowRoute.name, query: { action: action.type, referenceUuid: action.uuid, tabParent: 0 }})
}
}
},
setShareLink() {
var shareLink = this.panelType === 'window' || window.location.href.includes('?') ? `${window.location.href}&` : `${window.location.href}?`
if (this.$route.name === 'Report Viewer') {
var processParameters = convertFieldListToShareLink(this.processParametersExecuted)
var reportFormat = this.$store.getters.getReportType
shareLink = this.$store.getters.getTempShareLink
if (String(processParameters).length) {
shareLink += '?' + processParameters
shareLink += `&reportType=${reportFormat}`
}
} else {
if (String(this.valuesPanelToShare).length) {
shareLink += this.valuesPanelToShare
}
if (this.$route.query.action && this.$route.query.action !== 'create-new' && this.$route.query.action !== 'reference' && this.$route.query.action !== 'advancedQuery') {
shareLink = window.location.href
}
}
if (shareLink !== this.$route.fullPath) {
this.activeClipboard(shareLink)
}
},
fallbackCopyTextToClipboard(text) {
var textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
try {
var successful = document.execCommand('copy')
if (successful) {
var message = this.$t('notifications.copySuccessful')
this.clipboardMessage(message)
}
} catch (err) {
message = this.$t('notifications.copyUnsuccessful')
this.clipboardMessage(message)
}
document.body.removeChild(textArea)
},
activeClipboard(text) {
if (!navigator.clipboard) {
this.fallbackCopyTextToClipboard(text)
return
}
navigator.clipboard.writeText(text)
.then(() => {
var message = this.$t('notifications.copySuccessful')
this.clipboardMessage(message)
})
.catch(() => {
var message = this.$t('notifications.copyUnsuccessful')
this.clipboardMessage(message)
})
navigator.clipboard.writeText(text)
},
clipboardMessage(message) {
this.$message({
message: message,
type: 'success',
duration: 1500
})
}
}
}

View File

@ -0,0 +1,76 @@
<template>
<div class="container-submenu-mobile container-context-menu">
<right-menu>
<el-menu :default-active="activeMenu" :router="false" class="el-menu-demo" mode="vertical" menu-trigger="hover" unique-opened style="width: 258px; float: right;">
<el-submenu index="1">
<template slot="title">
<svg-icon icon-class="tree" /> {{ $t('components.contextMenuRelations') }}
</template>
<el-menu-item-group>
<el-scrollbar wrap-class="scroll">
<item v-for="(relation, index) in relations" :key="index" :item="relation" />
</el-scrollbar>
</el-menu-item-group>
</el-submenu>
<el-submenu index="2">
<template slot="title">
<svg-icon icon-class="link" />{{ $t('components.contextMenuActions') }}
</template>
<el-menu-item-group>
<el-scrollbar wrap-class="scroll">
<template v-for="(action, index) in actions">
<el-submenu v-if="action.childs" :key="index" :index="action.name" :disabled="action.disabled">
<template slot="title">
{{ action.name }}
</template>
<el-menu-item v-for="(child, key) in action.childs" :key="key" :index="child.uuid" @click="runAction(child)">
{{ child.name }}
</el-menu-item>
</el-submenu>
<el-menu-item v-else :key="index" :index="action.name" :disabled="action.disabled" @click="runAction(action)">
<svg-icon v-if="action.type === 'process'" icon-class="component" /> {{ action.name }}
</el-menu-item>
</template>
<el-menu-item v-show="isReport" index="4">
<a :href="downloads" :download="file">
{{ $t('components.contextMenuDownload') }}
</a>
</el-menu-item>
<el-menu-item index="5" @click="setShareLink">
{{ $t('components.contextMenuShareLink') }}
</el-menu-item>
<el-menu-item index="6" @click="refreshData">
{{ $t('components.contextMenuRefresh') }}
</el-menu-item>
</el-scrollbar>
</el-menu-item-group>
</el-submenu>
<el-submenu :disabled="!isReferecesContent && !isReferencesLoaded" class="el-menu-item" index="3">
<template slot="title">
{{ $t('components.contextMenuReferences') }}
</template>
<template v-if="references && references.referencesList">
<template v-for="(reference, index) in references.referencesList">
<el-menu-item :key="index" :index="reference.displayName" @click="runAction(reference)">
{{ reference.displayName }}
</el-menu-item>
</template>
</template>
</el-submenu>
</el-menu>
</right-menu>
</div>
</template>
<script>
import { contextMixin } from '@/components/ADempiere/ContextMenu/contextMenuMixin'
import RightMenu from '@/components/RightPanel/menu'
export default {
name: 'ContextMenuMobile',
components: {
RightMenu
},
mixins: [contextMixin]
}
</script>

View File

@ -0,0 +1,32 @@
export const icon = [
{
type: 'window',
icon: 'tab'
},
{
type: 'report',
icon: 'skill'
},
{
type: 'task',
icon: 'size'
},
{
type: 'process',
icon: 'component'
},
{
type: 'browser',
icon: 'search'
},
{
type: 'workflow',
icon: 'example'
},
{
type: 'form',
icon: 'form'
}
]

View File

@ -0,0 +1,157 @@
<template>
<component
:is="templateDevice"
:menu-parent-uuid="menuParentUuid"
:parent-uuid="parentUuid"
:container-uuid="containerUuid"
:panel-type="panelType"
:is-report="isReport"
:last-parameter="lastParameter"
:report-format="reportFormat"
:modal-metadata="modalMetadata"
:is-insert-record="isInsertRecord"
/>
</template>
<script>
export default {
name: 'ContextMenu',
props: {
menuParentUuid: {
type: String,
default: undefined
},
// uuid of the component where it is called
parentUuid: {
type: String,
default: undefined
},
// uuid of the component where it is called
containerUuid: {
type: String,
required: true
},
panelType: {
type: String,
default: undefined
},
isReport: {
type: Boolean,
default: false
},
lastParameter: {
type: String,
default: undefined
},
reportFormat: {
type: String,
default: undefined
},
modalMetadata: {
type: Object,
default: () => {}
},
// used only window
isInsertRecord: {
type: Boolean,
default: undefined
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
templateDevice() {
var template = 'contextMenuDesktop'
if (this.isMobile) {
template = 'contextMenuMobile'
}
return () => import(`@/components/ADempiere/ContextMenu/${template}`)
}
}
}
</script>
<style scoped>
.el-submenu .el-menu-item {
height: 50px;
line-height: 50px;
padding-left: 27px !important;
padding: 0 45px;
min-width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>
<style>
.Run-Report {
position: absolute;
right: 102%;
border: 0;
}
.icon-menu {
position: absolute;
right: 140%;
margin-top: -38%;
}
.List-Report {
border: 0;
background: transparent;
}
.container-context-menu {
z-index: 1;
}
.container-submenu-mobile {
position: absolute;
height: 39px !important;
width: 39px !important;
right: 0;
top: 0;
}
.container-submenu {
position: absolute;
height: 39px !important;
right: 0;
top: -1px;
}
ul.el-menu-demo > .el-menu-item {
height: 39px !important;
line-height: 39px !important;
padding: 0 10px;
}
.el-menu-demo > .el-menu-item > .el-submenu__title {
line-height: 39px;
height: 39px !important;
padding: 0;
}
.el-menu--horizontal .el-submenu > .el-menu--horizontal {
left: initial !important;
right: 150px;
}
.el-menu--popup-bottom-start {
min-width: 150px !important;
}
.el-menu--popup-right-start{
min-width: 150px !important;
}
.el-menu--popup-right-start > .el-menu-item {
min-width: 150px;
}
.scroll {
max-height: 400px;
}
.el-icon-more {
transform: rotate(90deg);
}
</style>

View File

@ -0,0 +1,57 @@
<template>
<el-menu-item
v-if="item.meta.type !== 'summary'"
v-show="item.meta.uuid!==$route.meta.uuid"
:index="item.meta.uuid"
@click="handleClick(item)"
>
<svg-icon v-if="isMobile" :icon-class="classIconMenuRight" /> {{ item.meta.title }}
</el-menu-item>
<el-submenu v-else :index="item.meta.title" popper-append-to-body>
<template slot="title">
<svg-icon v-if="isMobile" icon-class="nested" /> {{ item.meta.title }}
</template>
<!-- <el-scrollbar wrap-class="scroll"> -->
<item v-for="(child, key) in item.children" :key="key" :item="child">
{{ child.meta.title }}
</item>
<!-- </el-scrollbar> -->
</el-submenu>
</template>
<script>
import { icon } from '@/components/ADempiere/ContextMenu/icon'
export default {
name: 'Item',
props: {
item: {
type: Object,
required: true
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
classIconMenuRight(iconMenu) {
var typeMenu = this.item.meta.type
iconMenu = icon.find(function(element) {
return element.type === typeMenu
})
return iconMenu.icon
}
},
methods: {
handleClick(item) {
this.$router.push({ name: item.name, query: { tabParent: 0 }})
}
}
}
</script>
<style>
.scroll {
max-height: 400px;
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<el-select
v-model="getterFieldListShowed"
:filterable="!isMobile"
:placeholder="$t('components.filterableItems')"
multiple
size="mini"
collapse-tags
value-key="key"
class="select"
>
<el-option
v-for="(item, key) in getterFieldListAvailable"
:key="key"
:label="item.name"
:value="item.columnName"
/>
</el-select>
</template>
<script>
export default {
name: 'FilterColumns',
props: {
containerUuid: {
type: String,
required: true
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
getterFieldList() {
return this.$store.getters.getFieldsListFromPanel(this.containerUuid)
},
// available fields
getterFieldListAvailable() {
return this.getterFieldList.filter(fieldItem => {
const isDisplayed = fieldItem.isDisplayed || fieldItem.isDisplayedFromLogic
return fieldItem.isActive && isDisplayed && !fieldItem.isKey
})
},
getterFieldListShowed: {
get: function() {
// columns showed
return this.getterFieldList.filter(itemField => {
if (itemField.isShowedTableFromUser && (itemField.isDisplayed || itemField.isDisplayedFromLogic) && !itemField.isKey) {
return true
}
}).map(itemField => {
return itemField.columnName
})
},
set: function(selecteds) {
// set columns to show/hidden in vuex store
this.addField(selecteds)
}
}
},
methods: {
/**
* Set columns to hidden/showed in table
* @param {array} selectedValues
*/
addField(selectedValues) {
this.$store.dispatch('changeFieldAttributesBoolean', {
containerUuid: this.containerUuid,
fieldsIncludes: selectedValues,
attribute: 'isShowedTableFromUser',
valueAttribute: true
})
}
}
}
</script>

View File

@ -0,0 +1,79 @@
<template>
<el-select
v-model="columnsFixed"
:filterable="!isMobile"
:placeholder="$t('components.fixedleItems')"
multiple
size="mini"
collapse-tags
value-key="key"
@change="addField"
>
<el-option
v-for="(item, key) in columnListAvailable"
:key="key"
:label="item.name"
:value="item.columnName"
/>
</el-select>
</template>
<script>
export default {
name: 'FixedColumns',
props: {
containerUuid: {
type: String,
required: true
}
},
data() {
return {
columnsFixed: [], // columns showed
columnListAvailable: [] // available fields
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
getterFieldList() {
return this.$store.getters.getFieldsListFromPanel(this.containerUuid)
}
},
created() {
this.getPanel()
},
methods: {
getPanel() {
var fieldList = this.getterFieldList
if (fieldList && fieldList.length) {
this.generatePanel(fieldList)
}
},
generatePanel(fieldList) {
this.columnListAvailable = fieldList.filter(fieldItem => {
return this.isDisplayed(fieldItem)
})
},
isDisplayed(field) {
var isDisplayed = field.isActive && field.isDisplayed && field.isDisplayedFromLogic && !field.isKey
if (field.isFixedTableColumn && field.isDisplayed) {
this.columnsFixed.push(field.columnName)
}
return isDisplayed
},
/**
* @param {array} selectedValues
*/
addField(selectedValues) {
this.$store.dispatch('changeFieldAttributesBoolean', {
containerUuid: this.containerUuid,
fieldsIncludes: selectedValues,
attribute: 'isFixedTableColumn',
valueAttribute: true
})
}
}
}
</script>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,128 @@
<template>
<el-dialog
:title="modalMetadata.name"
:visible="isVisibleDialog"
:show-close="false"
:width="width + '%'"
top="5vh"
:close-on-press-escape="true"
:close-on-click-modal="true"
>
{{ modalMetadata.description }}
<main-panel
v-if="(modalMetadata.panelType === 'process' || modalMetadata.panelType === 'report') && !isEmptyValue(modalMetadata.uuid)"
:parent-uuid="parentUuid"
:container-uuid="modalMetadata.uuid"
:metadata="modalMetadata"
:is-view="false"
panel-type="process"
/>
<span slot="footer" class="dialog-footer">
<el-button @click="closeDialog">
{{ $t('components.dialogCancelButton') }}
</el-button>
<el-button type="primary" @click="runAction(modalMetadata)">
{{ $t('components.dialogConfirmButton') }}
</el-button>
</span>
</el-dialog>
</template>
<script>
import MainPanel from '@/components/ADempiere/Panel'
import { showNotification } from '@/utils/ADempiere/notification'
export default {
name: 'ModalProcess',
components: {
MainPanel
},
props: {
parentUuid: {
type: String,
default: undefined
},
containerUuid: {
type: String,
default: ''
},
panelType: {
type: String,
default: 'window'
},
reportExportType: {
type: String,
default: ''
}
},
data() {
return {
processMetadata: {}
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
width() {
if (this.isMobile) {
return 80
}
return 50
},
isVisibleDialog() {
return this.$store.state.processControl.isVisibleDialog
},
modalMetadata() {
return this.$store.state.processControl.metadata
},
windowRecordSelected() {
return this.$store.state.window.recordSelected
}
},
methods: {
showNotification,
closeDialog() {
this.$store.dispatch('setShowDialog', {
type: this.modalMetadata.panelType,
action: undefined
})
},
runAction(action) {
if (action === undefined && this.windowRecordSelected !== undefined) {
this.$router.push({
name: this.$route.name,
query: {
action: this.windowRecordSelected.UUID,
...this.$route.query
}
})
this.closeDialog()
} else if (action !== undefined) {
const fieldNotReady = this.$store.getters.isNotReadyForSubmit(action.uuid)
if (!fieldNotReady) {
this.closeDialog()
this.$store.dispatch('startProcess', {
action: action, // process metadata
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
panelType: this.panelType, // determinate if get table name and record id (window) or selection (browser)
reportFormat: this.reportExportType,
routeToDelete: this.$route
})
.catch(error => {
console.warn(error)
})
} else {
this.showNotification({
type: 'warning',
title: this.$t('notifications.emptyValues'),
name: '<b>' + fieldNotReady.name + '.</b> ',
message: this.$t('notifications.fieldMandatory')
})
}
}
}
}
}
</script>

View File

@ -0,0 +1,146 @@
<template>
<el-col v-if="items.children" :span="24">
<el-collapse v-model="activeNames">
<el-collapse-item :title="title" name="1" class="collapse-item">
<el-row justify="space-around">
<template v-for="(item, index) in items.children">
<el-col :key="index" :span="isMobile">
<el-card
:key="index"
shadow="never"
class="custom-card"
:body-style="{ padding: '10px', height: '100px' }"
@click.native="redirect(item)"
>
<div class="icon-wrapper">
<svg-icon :icon-class="item.meta.icon" />
</div>
<div class="text-wrapper">
<b>{{ item.meta.title }}</b>
<p class="three-dots">{{ item.meta.description }}</p>
</div>
</el-card>
</el-col>
</template>
</el-row>
</el-collapse-item>
</el-collapse>
</el-col>
<el-col v-else :span="isMobile">
<el-card
shadow="never"
class="custom-card"
:body-style="{ padding: '10px', height: '100px' }"
@click.native="redirect(items)"
>
<div class="icon-wrapper">
<svg-icon :icon-class="items.meta.icon" />
</div>
<div class="text-wrapper">
<b>{{ items.meta.title }}</b>
<p>{{ items.meta.description }}</p>
</div>
</el-card>
</el-col>
</template>
<script>
export default {
name: 'Dropdown',
props: {
items: {
type: Object,
default: () => {
return {}
}
},
title: {
type: String,
default: ''
}
},
data() {
return {
activeNames: ['1']
}
},
computed: {
device() {
return this.$store.state.app.device
},
isMobile() {
if (this.device === 'mobile') {
return 24
} else {
return 8
}
}
},
methods: {
redirect(item) {
if (item.meta && item.meta.type === 'window') {
this.$router.push({
name: item.name,
query: {
tabParent: 0
},
params: {
childs: item.children
}
})
} else {
this.$router.push({
name: item.name,
params: {
childs: item.children
}
})
}
}
}
}
</script>
<style lang="scss">
.custom-card {
margin: 10px;
cursor: pointer;
}
.icon-wrapper {
height: 100%;
width: 20%;
float: left;
font-size: 30px;
line-height: 65px;
padding: 6px;
transition: all 0.38s ease-out;
border-radius: 6px;
text-align: center;
color: #36a3f7;
}
.custom-card:hover {
.icon-wrapper {
color: #fff;
background: #36a3f7;
}
}
.text-wrapper {
margin-left: 50px;
padding-left: 15px;
vertical-align: middle;
height: 100%;
font-size: 13px;
}
.el-collapse-item__header {
height: 60px;
font-weight: bold;
font-size: 16px;
text-align: center;
}
.three-dots{
margin-top: 0 !important;
overflow: hidden;
white-space: normal;
text-overflow: ellipsis;
}
</style>

View File

@ -0,0 +1,53 @@
<template>
<el-upload
:ref="metadata.columnName"
v-model="value"
:limit="metadata.Limit"
:on-remove="handleRemove"
:on-success="handleSuccess"
:on-error="handleError"
class="image-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:disabled="isDisabled"
@change="preHandleChange"
>
<el-button size="small" type="primary">
{{ $t('components.binaryButton') }}
</el-button>
<div slot="tip" class="el-upload__tip">
{{ $t('components.binaryTip') }}
</div>
</el-upload>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldBinary',
mixins: [fieldMixin],
watch: {
valueModel(value) {
if (this.metadata.inTable) {
this.value = value
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
this.value = value
}
}
},
methods: {
handleRemove(file) {
this.$message.success(`The previously uploaded file has been deleted.`)
},
handleError(file) {
this.$message.error(`The file does not meet the specifications.`)
},
handleSuccess(file) {
this.$message.success(`The file has been successfully loaded.`)
}
}
}
</script>

View File

@ -0,0 +1,29 @@
<template>
<el-input
:ref="metadata.columnName"
v-model="value"
type="hidden"
@change="preHandleChange"
/>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldButton',
mixins: [fieldMixin],
watch: {
valueModel(value) {
if (this.metadata.inTable) {
this.value = String(value)
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
this.value = String(value)
}
}
}
}
</script>

View File

@ -0,0 +1,36 @@
<template>
<el-color-picker
:ref="metadata.columnName"
v-model="value"
show-alpha
:disabled="isDisabled"
@change="preHandleChange"
/>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldColor',
mixins: [fieldMixin],
watch: {
valueModel(value) {
if (this.metadata.inTable) {
if (this.isEmptyValue(value)) {
value = ''
}
this.value = String(value)
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
if (this.isEmptyValue(value)) {
value = ''
}
this.value = String(value)
}
}
}
}
</script>

View File

@ -0,0 +1,194 @@
<template>
<el-date-picker
:ref="metadata.columnName"
v-model="value"
:format="formatView"
:value-format="formatSend"
:type="typePicker"
range-separator="-"
:placeholder="metadata.help"
:start-placeholder="$t('components.dateStartPlaceholder')"
:end-placeholder="$t('components.dateEndPlaceholder')"
unlink-panels
class="date-base"
:readonly="Boolean(metadata.readonly)"
:disabled="isDisabled"
:picker-options="typePicker === 'daterange' ? pickerOptionsDateRange : pickerOptionsDate"
@change="preHandleChange"
/>
</template>
<script>
import { clientDateTime } from '@/utils/ADempiere'
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldDate',
mixins: [fieldMixin],
data() {
return {
pickerOptionsDate: {
shortcuts: [{
text: this.$t('components.date.Today'),
onClick(picker) {
picker.$emit('pick', new Date())
}
}, {
text: this.$t('components.date.Yesterday'),
onClick(picker) {
const date = new Date()
date.setTime(date.getTime() - 3600 * 1000 * 24)
picker.$emit('pick', date)
}
}, {
text: this.$t('components.date.Week'),
onClick(picker) {
const date = new Date()
var monthEndDay = new Date(date.getFullYear(), date.getMonth() + 1, 0)
picker.$emit('pick', monthEndDay)
}
}]
},
pickerOptionsDateRange: {
shortcuts: [{
text: this.$t('components.date.Yesterday'),
onClick(picker) {
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24)
picker.$emit('pick', [start, start])
}
}, {
text: this.$t('components.date.Week'),
onClick(picker) {
var start_date, end_date, date, currenDate, first, last
start_date = new Date()
start_date.setHours(0, 0, 0, 0)
end_date = new Date()
date = null
currenDate = date ? new Date(date) : new Date()
first = currenDate.getDate() - currenDate.getDay('monday')
last = first - 7
start_date.setDate(last)
end_date.setDate(first - 1)
picker.$emit('pick', [start_date, end_date])
}
}, {
text: this.$t('components.date.LastMonth'),
onClick(picker) {
var date = new Date()
var monthEndDay = new Date(date.getFullYear(), date.getMonth(), 0)
var monthStartDay = new Date(date.getFullYear(), date.getMonth() - 1, 1)
picker.$emit('pick', [monthStartDay, monthEndDay])
}
}, {
text: this.$t('components.date.CurrentMonth'),
onClick(picker) {
var date = new Date()
var monthEndDay = new Date(date.getFullYear(), date.getMonth() + 1, 0)
var monthStartDay = new Date(date.getFullYear(), date.getMonth(), 1)
picker.$emit('pick', [monthStartDay, monthEndDay])
}
}]
}
}
},
computed: {
typePicker() {
let range = ''
let time = ''
if (String(this.metadata.displayType) === String(16)) {
time = 'time'
}
if (this.metadata.isRange && !this.metadata.inTable) {
range = 'range'
}
return 'date' + time + range
},
/**
* Parse the date format to be compatible with element-ui
*/
formatView() {
let format = this.metadata.VFormat
.replace(/[Y]/gi, 'y')
.replace(/[m]/gi, 'M')
.replace(/[D]/gi, 'd')
if (format === '') {
format = 'yyyy-MM-dd'
}
if (this.typePicker.replace('range', '') === 'datetime') {
format = format + ' hh:mm:ss A'
}
return format
},
formatSend() {
if (this.formatView) {
return this.formatView
.replace(/[h]/gi, 'H')
.replace(/[aA]/gi, '')
}
return undefined
}
},
watch: {
valueModel(value) {
if (this.metadata.inTable) {
this.value = this.parsedDateValue(value)
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
this.value = this.parsedDateValue(value)
}
}
},
methods: {
clientDateTime,
parsedDateValue(value) {
if (typeof value === 'number') {
value = new Date(value).toUTCString()
}
if (this.isEmptyValue(value)) {
value = undefined
}
if (this.metadata.isRange) {
let valueTo = this.metadata.valueTo
if (typeof valueTo === 'number') {
valueTo = new Date(valueTo).toUTCString()
}
if (this.isEmptyValue(valueTo)) {
valueTo = undefined
}
value = [value, valueTo]
}
return value
},
// validate values before send values to store or server
preHandleChange(value) {
var valueFirst, valueTo
valueFirst = value
if ((this.metadata.isRange && !this.metadata.inTable) && Array.isArray(value)) {
valueFirst = value[0]
valueTo = value[1]
}
if (valueFirst === null) {
valueFirst = undefined
valueTo = undefined
}
if (typeof valueFirst !== 'object' && valueFirst !== undefined) {
valueFirst = new Date(valueFirst)
valueTo = new Date(valueTo)
}
this.handleChange(valueFirst, valueTo)
}
}
}
</script>
<style scoped>
.date-base {
width: 100% !important;
}
</style>

View File

@ -0,0 +1,88 @@
<template>
<el-upload
:ref="metadata.columnName"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:disabled="isDisabled"
class="avatar-uploader"
>
<img v-if="value" :src="value" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldImage',
mixins: [fieldMixin],
data() {
return {
value: ''
}
},
watch: {
valueModel(value) {
if (this.metadata.inTable) {
this.value = value
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
this.value = value
}
}
},
methods: {
handleAvatarSuccess(res, file) {
this.value = URL.createObjectURL(file.raw)
// TODO: define one method to control change value
this.handleChange(this.value)
},
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg'
const isPNG = file.type === 'image/png'
// const isGIF = file.type === 'image/gif'
// const isBMP = file.type === 'image/bmp'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isLt2M) {
this.$message.error(this.$t('components.imageError'))
}
return isJPG + isPNG + isLt2M
}
}
}
</script>
<style>
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
</style>

View File

@ -0,0 +1,80 @@
export const fieldMixin = {
props: {
metadata: {
type: Object,
required: true
},
// value received from data result
valueModel: {
type: [String, Number, Boolean, Date, Array],
default: null
}
},
data() {
// value render
let value = this.metadata.value
if (this.metadata.inTable) {
value = this.valueModel
}
return {
value: value
}
},
computed: {
getterValue() {
const field = this.$store.getters.getFieldFromColumnName(this.metadata.containerUuid, this.metadata.columnName)
if (field) {
return field.value
}
return undefined
},
isDisabled() {
return Boolean(this.metadata.readonly || this.metadata.disabled)
}
},
methods: {
activeFocus() {
if (this.metadata.isUpdateable) {
this.$refs[this.metadata.columnName].focus()
}
},
/**
* Overwrite component method if necessary
* validate values before send values to store or server
* @param {mixed} value
*/
preHandleChange(value) {
this.handleChange(value)
},
handleChange(value, valueTo = undefined, label = undefined) {
const sendParameters = {
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
field: this.metadata,
panelType: this.metadata.panelType,
columnName: this.metadata.columnName,
newValue: value === 'NotSend' ? this.value : value,
valueTo: valueTo,
isAdvancedQuery: this.metadata.isAdvancedQuery,
isSendToServer: !(value === 'NotSend' || this.metadata.isAdvancedQuery),
isSendCallout: !(value === 'NotSend' || this.metadata.isAdvancedQuery)
}
if (this.metadata.inTable) {
this.$store.dispatch('notifyCellTableChange', {
...sendParameters,
keyColumn: this.metadata.keyColumn,
tableIndex: this.metadata.tableIndex,
rowKey: this.metadata.rowKey
})
} else {
this.$store.dispatch('notifyFieldChange', {
...sendParameters,
displayColumn: label,
isChangedOldValue: this.metadata.componentPath === 'FieldYesNo' && Boolean(value === 'NotSend')
})
}
}
}
}

View File

@ -0,0 +1,103 @@
<template>
<el-input-number
:ref="metadata.columnName"
v-model="value"
type="number"
:pattern="pattern"
:min="minValue"
:max="maxValue"
:placeholder="metadata.help"
:disabled="isDisabled"
:precision="precision"
controls-position="right"
:class="'display-type-' + cssClass"
@blur="validateInput"
@change="preHandleChange"
/>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldNumber',
mixins: [fieldMixin],
props: {
validateInput: {
type: Function,
default: () => undefined
}
},
data() {
return {
pattern: undefined,
showControls: true
}
},
computed: {
maxValue() {
if (!this.isEmptyValue(this.metadata.valueMax)) {
return Number(this.metadata.valueMax)
}
return Infinity
},
minValue() {
if (!this.isEmptyValue(this.metadata.valueMin)) {
return Number(this.metadata.valueMin)
}
return -Infinity
},
cssClass() {
return this.metadata.referenceType
.split(/(?=[A-Z])/)
.join('-').toLowerCase()
},
precision() {
if (['Number', 'Amount'].includes()) {
return 2
}
return undefined
}
},
watch: {
// enable to dataTable records
valueModel(value) {
if (this.metadata.inTable) {
if (this.isEmptyValue(value)) {
value = null
}
this.value = value
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
if (this.isEmptyValue(value)) {
value = null
}
this.value = value
}
}
}
}
</script>
<style lang="scss">
/* if is controls width 100% in container */
.el-input-number, .el-input {
width: 100% !important; /* ADempiere Custom */
}
/** Amount reference **/
.display-type-amount {
text-align: right !important;
input, .el-input__inner {
text-align: right !important;
}
}
/* ADempiere Custom */
.el-input-number.is-controls-right .el-input__inner {
padding-left: 15px;
padding-right: 50px;
text-align: -webkit-right;
}
</style>

View File

@ -0,0 +1,257 @@
<template>
<el-select
:ref="metadata.columnName"
v-model="value"
:filterable="!isMobile"
:placeholder="metadata.help"
:loading="isLoading"
value-key="key"
class="select-base"
clearable
:disabled="isDisabled"
@change="preHandleChange"
@visible-change="getDataLookupList"
@clear="clearLookup"
>
<el-option
v-for="(option, key) in options"
:key="key"
:value="option.key"
:label="option.label"
/>
</el-select>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldSelect',
mixins: [fieldMixin],
data() {
return {
value: this.metadata.displayColumn,
isLoading: false,
baseNumber: 10,
options: [{
label: ' ',
key: undefined
}],
othersOptions: [],
blanckOption: {
label: ' ',
key: undefined
},
blancksValues: [null, -1, '', undefined]
}
},
computed: {
isPanelWindow() {
return this.metadata.panelType === 'window'
},
getterValueSelec() {
var field = this.$store.getters.getFieldFromColumnName(this.metadata.containerUuid, this.metadata.columnName)
if (field) {
return this.validateValue(field.displayColumn)
}
return undefined
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
getterLookupItem() {
return this.$store.getters.getLookupItem({
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
directQuery: this.metadata.reference.directQuery,
tableName: this.metadata.reference.tableName,
value: this.metadata.value
})
},
getterLookupList() {
return this.$store.getters.getLookupList({
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
query: this.metadata.reference.query,
tableName: this.metadata.reference.tableName
})
},
getterLookupAll() {
var allOptions = this.$store.getters.getLookupAll({
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
query: this.metadata.reference.query,
directQuery: this.metadata.reference.directQuery,
tableName: this.metadata.reference.tableName,
value: this.metadata.value
})
if (allOptions.length && !allOptions[0].key) {
allOptions.unshift(this.blanckOption)
}
return allOptions
}
},
watch: {
'metadata.optionCRUD'(value) {
if (value === 'create-new') {
this.value = this.metadata.value
this.getDataLookupItem()
} else {
if (this.isEmptyValue(this.metadata.displayColumn)) {
this.value = this.getterValueSelec
} else {
this.value = this.validateValue(this.metadata.displayColumn)
}
}
}
},
beforeMount() {
this.options = this.getterLookupAll
if (this.isEmptyValue(this.metadata.displayColumn)) {
this.value = this.getterValueSelec
} else {
this.value = this.validateValue(this.metadata.displayColumn)
}
// enable to dataTable records
// Evaluate values of the displayColumn with empty string or number at 0
if (!this.isEmptyValue(this.metadata.displayColumn)) {
var key = this.validateValue(this.metadata.value)
if (this.valueModel !== undefined && this.validateValue !== null) {
key = this.metadata.displayColumn
}
// verify if exists to add
if (!this.findLabel(key)) {
this.othersOptions.push({
key: key,
label: this.metadata.displayColumn
})
}
// join options in store with pased from props
// validate empty or duplicate data
const optionList = this.getterLookupAll.filter(lookup => {
if (lookup.key !== null) {
return lookup
}
})
this.options = optionList
this.value = key
} else if (!this.findLabel(this.value) && this.metadata.displayed) {
if (this.isPanelWindow) {
if (this.metadata.optionCRUD === 'create-new') {
this.value = this.metadata.value
}
} else {
this.value = this.validateValue(this.metadata.displayColumn)
}
}
},
methods: {
preHandleChange(value) {
const label = this.findLabel(this.value)
this.handleChange(value, undefined, label)
},
validateValue(value) {
if (this.isEmptyValue(value)) {
return undefined
}
// if (['TableDirect'].includes(this.metadata.referenceType)) {
// return parseInt(value, 10)
// }
return value
},
validateBlanckOption() {
// TODO: Evaluate -1 when list is string key
if (this.options.length <= 0 || (this.options.length && this.isEmptyValue(this.options[0].key))) {
this.options.unshift(this.blanckOption)
}
},
findLabel(value) {
var selected = this.options.find(item => item.key === value)
if (selected) {
return selected.label
}
selected = this.othersOptions.find(item => item.key === value)
if (selected) {
return selected.label
}
return selected
},
async getDataLookupItem() {
this.isLoading = true
if (!this.isEmptyValue(this.value)) {
this.$store.dispatch('getLookupItemFromServer', {
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
tableName: this.metadata.reference.tableName,
directQuery: this.metadata.reference.directQuery,
value: this.metadata.value
})
.then(response => {
if (this.isPanelWindow) {
this.$store.dispatch('notifyFieldChangeDisplayColumn', {
containerUuid: this.metadata.containerUuid,
columnName: this.metadata.columnName,
displayColumn: response.label
})
}
this.options = this.getterLookupAll
if (this.options.length && !this.options[0].key) {
this.options.unshift(this.blanckOption)
}
})
.finally(() => {
this.isLoading = false
})
}
},
/**
* @param {boolean} show triggers when the pull-down menu appears or disappears
*/
getDataLookupList(showList) {
if (showList) {
if (this.getterLookupList.length === 0) {
this.remoteMethod()
}
}
},
remoteMethod() {
this.isLoading = true
this.$store.dispatch('getLookupListFromServer', {
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
tableName: this.metadata.reference.tableName,
query: this.metadata.reference.query
})
.then(response => {
const list = this.getterLookupAll.filter(options => {
if (options.key !== undefined) {
return options
}
})
this.options = list
})
.finally(() => {
this.isLoading = false
})
},
clearLookup() {
this.$store.dispatch('deleteLookupList', {
parentUuid: this.metadata.parentUuid,
containerUuid: this.metadata.containerUuid,
tableName: this.metadata.reference.tableName,
query: this.metadata.reference.query,
directQuery: this.metadata.reference.directQuery,
value: this.metadata.value
})
// TODO: Evaluate if is number -1 or string '' (or default value)
this.value = this.blanckOption.key
}
}
}
</script>
<style scoped>
.select-base {
width: 100%;
}
</style>

View File

@ -0,0 +1,96 @@
<template>
<el-input
:ref="metadata.columnName"
v-model="value"
:pattern="pattern"
:rows="rows"
:type="typeTextBox"
:placeholder="metadata.help"
:readonly="Boolean(metadata.readonly)"
:disabled="isDisabled"
:maxlength="maxLength"
:show-password="metadata.isEncrypted ? true : false"
@change="preHandleChange"
/>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldText',
mixins: [fieldMixin],
props: {
pattern: {
type: String,
default: undefined
}
},
data() {
return {
patternFileName: '[A-Za-zñÑ0-9-_]{1,}',
patternFilePath: '[A-Za-zñÑ0-9-_/.]{1,}'
}
},
computed: {
// Only used when input type='TextArea'
rows() {
if (this.metadata.inTable) {
return 1
}
return 5
},
typeTextBox() {
// String, Url, FileName...
var typeInput = 'text'
if (['Memo', 'Text', 'TextLong'].includes(this.metadata.referenceType)) {
typeInput = 'textarea'
}
if (this.metadata.isEncrypted) {
typeInput = 'password'
}
return typeInput
},
maxLength() {
if (!this.isEmptyValue(this.metadata.fieldLength) && this.metadata.fieldLength > 0) {
return Number(this.metadata.fieldLength)
}
return undefined
}
},
watch: {
valueModel(value) {
if (this.metadata.inTable) {
if (this.isEmptyValue(value)) {
value = ''
}
this.value = String(value)
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
if (this.isEmptyValue(value)) {
value = ''
}
this.value = String(value)
}
}
},
methods: {
validateUrl(e) {
// Entry pattern, in this case only accepts numbers and letters
var _Pattern = /^(http[s]?:\/\/(www\.)?|ftp:\/\/(www\.)?|www\.){1}([0-9A-Za-z-\.@:%_\+~#=]+)+((\.[a-zA-Z]{1,5})+)(\/(.)*)?(\?(.)*)?/g
var rex = RegExp(_Pattern)
var value = e.target.value
if (rex.test(value) && value.trim() !== '') {
console.log('url good format')
} else if (value.trim() === '') {
console.log('url empty')
} else {
// e.target.focus()
console.log('url wrong')
}
}
}
}
</script>

View File

@ -0,0 +1,75 @@
<template>
<el-time-picker
:ref="metadata.columnName"
v-model="value"
:picker-options="{
minTime: minValue,
maxTime: maxValue
}"
:is-range="isPickerRange"
range-separator="-"
:placeholder="$t('components.timePlaceholder')"
class="time-base"
:readonly="Boolean(metadata.readonly)"
:disabled="isDisabled"
@change="preHandleChange"
/>
</template>
<script>
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldTime',
mixins: [fieldMixin],
computed: {
isPickerRange() {
if (this.metadata.isRange && !this.metadata.inTable) {
return true
}
return false
},
maxValue() {
if (!this.isEmptyValue(this.metadata.valueMax)) {
return Number(this.metadata.valueMax)
}
return Infinity
},
minValue() {
if (!this.isEmptyValue(this.metadata.valueMin)) {
return Number(this.metadata.valueMin)
}
return -Infinity
}
},
watch: {
valueModel(value) {
if (this.metadata.inTable) {
this.value = this.parsedDateValue(value)
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
this.value = this.parsedDateValue(value)
}
}
},
methods: {
parsedDateValue(value) {
if (typeof value === 'number') {
value = new Date(value).toUTCString()
}
if (this.isEmptyValue(value)) {
value = undefined
}
return value
}
}
}
</script>
<style scoped>
.time-base {
width: 100% !important;
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<el-switch
:ref="metadata.columnName"
v-model="value"
:inactive-text="$t('components.switchInactiveText')"
:active-text="$t('components.switchActiveText')"
true-value="true"
false-value="false"
:disabled="isDisabled"
@change="preHandleChange"
/>
</template>
<script>
import { fieldIsDisplayed } from '@/utils/ADempiere'
import { FIELD_READ_ONLY_FORM } from '@/components/ADempiere/Field/references'
import { fieldMixin } from '@/components/ADempiere/Field/FieldMixin'
export default {
name: 'FieldYesNo',
mixins: [fieldMixin],
data() {
return {
valuesReadOnly: [
{
columnName: 'IsActive',
isReadOnlyValue: false
}
]
}
},
watch: {
valueModel(value) {
if (this.metadata.inTable) {
this.value = Boolean(value)
}
},
'metadata.value'(value) {
if (!this.metadata.inTable) {
this.value = Boolean(value)
}
},
value(value, oldValue) {
if (typeof value !== 'boolean') {
this.value = Boolean(value)
}
this.preHandleChange('NotSend')
}
},
mounted() {
this.preHandleChange('NotSend') // activate logics
},
methods: {
preHandleChange(value) {
this.handleChange(value)
if (!this.metadata.inTable && !this.metadata.isAdvancedQuery) {
this.isReadOnlyForm(this.value)
}
},
isReadOnlyForm(value) {
var fieldReadOnlyForm = FIELD_READ_ONLY_FORM.find(item => item.columnName === this.metadata.columnName)
// columnName: IsActive, Processed, Processing
if (fieldReadOnlyForm && fieldIsDisplayed(this.metadata)) {
this.$store.dispatch('changeFieldAttributesBoolean', {
containerUuid: this.metadata.containerUuid,
fieldsIncludes: [],
attribute: 'isReadOnlyFromForm',
valueAttribute: Boolean(fieldReadOnlyForm.valueIsReadOnlyForm !== value),
// if isChangedAllForm it does not exclude anything, otherwise it excludes this columnName
fieldsExcludes: fieldReadOnlyForm.isChangedAllForm ? [] : [this.metadata.columnName],
currenValue: value
})
}
}
}
}
</script>

View File

@ -0,0 +1,124 @@
export const FIELD_DISPLAY_SIZES = [
{
type: 'FieldBinary',
size: {
xs: 6,
sm: 6,
md: 6,
lg: 6,
xl: 6
}
},
{
type: 'FieldButton',
size: {
xs: 0,
sm: 0,
md: 0,
lg: 0,
xl: 0
}
},
{
type: 'FieldColor',
size: {
xs: 6,
sm: 6,
md: 6,
lg: 6,
xl: 6
}
},
{
type: 'FieldDate',
size: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6
}
},
{
type: 'FieldImage',
size: {
xs: 6,
sm: 6,
md: 6,
lg: 6,
xl: 6
}
},
{
type: 'FieldNumber',
size: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6
}
},
{
type: 'FieldSelect',
size: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6
}
},
{
type: 'FieldText',
size: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6
}
},
{
type: 'FieldTextArea',
size: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6
}
},
{
type: 'FieldTime',
size: {
xs: 24,
sm: 12,
md: 8,
lg: 6,
xl: 6
}
},
{
type: 'FieldYesNo',
size: {
xs: 14,
sm: 8,
md: 8,
lg: 3,
xl: 6
}
}
]
export const DEFAULT_SIZE = {
type: 'FieldYesNo',
size: {
xs: 6,
sm: 8,
md: 2,
lg: 6,
xl: 6
}
}

View File

@ -0,0 +1,326 @@
<template>
<!--
this v-show is to indicate that if the field is not shown,
therefore you should not leave the column size spacing of your
<el-col></el-col> container-->
<el-col
v-if="!inTable"
v-show="isDisplayed()"
:xs="sizeFieldResponsive.xs"
:sm="sizeFieldResponsive.sm"
:md="sizeFieldResponsive.md"
:lg="sizeFieldResponsive.lg"
:xl="sizeFieldResponsive.xl"
:class="classField"
>
<el-popover
v-if="field.contextInfo && field.contextInfo.isActive"
ref="contextOptions"
placement="top"
:title="$t('components.contextFieldTitle')"
width="400"
trigger="click"
>
<p v-html="field.contextInfo.messageText.msgText" />
</el-popover>
<el-form-item v-popover:contextOptions :label="isFieldOnly()" :required="isMandatory()">
<component
:is="componentRender"
:ref="field.columnName"
:metadata="{
...field,
panelType: panelType,
inTable: inTable,
isAdvancedQuery: isAdvancedQuery,
// DOM properties
required: isMandatory(),
readonly: isReadOnly(),
displayed: isDisplayed(),
disabled: !field.isActive
}"
:value-model="recordDataFields"
/>
</el-form-item>
</el-col>
<component
:is="componentRender"
v-else
:class="classField"
:metadata="{
...field,
panelType: panelType,
inTable: inTable,
// DOM properties
required: isMandatory(),
readonly: isReadOnly(),
disabled: !field.isActive
}"
:value-model="recordDataFields"
/>
</template>
<script>
import { FIELD_ONLY } from '@/components/ADempiere/Field/references'
import { DEFAULT_SIZE } from '@/components/ADempiere/Field/fieldSize'
import { fieldIsDisplayed } from '@/utils/ADempiere'
/**
* This is the base component for linking the components according to the
* reference (or type of visualization) of each field
*/
export default {
name: 'Field',
props: {
parentUuid: {
type: String,
default: ''
},
containerUuid: {
type: String,
default: ''
},
metadataUuid: {
type: String,
default: undefined
},
panelType: {
type: String,
default: 'window'
},
// receives the property that is an object with all the attributes
metadataField: {
type: Object,
default: () => ({})
},
recordDataFields: {
type: [Number, String, Boolean, Array, Object, Date],
default: undefined
},
inGroup: {
type: Boolean,
default: false
},
inTable: {
type: Boolean,
default: false
},
isAdvancedQuery: {
type: Boolean,
default: false
}
},
data() {
return {
field: {}
}
},
computed: {
// load the component that is indicated in the attributes of received property
componentRender() {
return () => import(`@/components/ADempiere/Field/${this.field.componentPath}`)
},
getWidth() {
return this.$store.getters.getWidthLayout
},
classField() {
if (this.inTable) {
return 'in-table'
}
return ''
},
getterIsShowedRecordNavigation() {
if (this.panelType === 'window') {
return this.$store.getters.getIsShowedRecordNavigation(this.parentUuid)
}
return false
},
sizeFieldResponsive() {
if (!this.isDisplayed()) {
return DEFAULT_SIZE
}
const sizeField = this.field.sizeFieldFromType.size
var newSizes = {}
// in table set max width, used by browser result and tab children of window
if (this.inTable) {
newSizes.xs = 24
newSizes.sm = 24
newSizes.md = 24
newSizes.lg = 24
newSizes.xl = 24
return newSizes
}
if (this.isAdvancedQuery) {
newSizes.xs = 24
newSizes.sm = 24
newSizes.md = 12
newSizes.lg = 12
newSizes.xl = 12
return newSizes
}
if (this.panelType === 'window') {
// two columns if is mobile or desktop and show record navigation
if (this.getWidth <= 768 || (this.getWidth >= 768 && this.getterIsShowedRecordNavigation)) {
newSizes.xs = 12
newSizes.sm = 12
newSizes.md = 12
newSizes.lg = 12
newSizes.xl = 12
return newSizes
} else if (this.inGroup && this.getWidth >= 992) {
newSizes.xs = sizeField.xs
newSizes.sm = sizeField.sm * 2
if (this.getWidth <= 1199) {
newSizes.md = sizeField.md
} else {
newSizes.md = sizeField.md * 2
}
if (this.field.groupAssigned !== '') {
newSizes.lg = sizeField.lg * 2
newSizes.xl = sizeField.xl * 2
} else {
newSizes.lg = sizeField.lg
newSizes.xl = sizeField.xl
}
return newSizes
}
return sizeField
}
return sizeField
},
getterContextProcessing() {
const processing = this.$store.getters.getContextProcessing(this.parentUuid)
if (processing === true || processing === 'Y') {
return true
}
return false
},
getterContextProcessed() {
const processed = this.$store.getters.getContextProcessed(this.parentUuid)
if (processed === true || processed === 'Y') {
return true
}
return false
}
},
watch: {
metadataField(value) {
this.field = value
}
},
created() {
// assined field with prop
this.field = this.metadataField
},
methods: {
isDisplayed() {
if (this.isAdvancedQuery) {
return this.field.isShowedFromUser
}
return fieldIsDisplayed(this.field) && (this.isMandatory() || this.field.isShowedFromUser || this.inTable)
},
isReadOnly() {
if (this.isAdvancedQuery) {
return false
}
if (!this.field.isActive) {
return true
}
const isUpdateableAllFields = this.field.isReadOnly || this.field.isReadOnlyFromLogic
if (this.panelType === 'window') {
if (this.field.isAlwaysUpdateable) {
return false
}
if (this.getterContextProcessing) {
return true
}
if (this.getterContextProcessed) {
return true
}
// edit mode is diferent to create new
const editMode = (!this.inTable && this.field.optionCRUD !== 'create-new') || (this.inTable && !this.isEmptyValue(this.field.recordUuid))
return (!this.field.isUpdateable && editMode) || (isUpdateableAllFields || this.field.isReadOnlyFromForm)
}
if (this.panelType === 'browser') {
if (this.inTable) {
// browser result
return this.field.isReadOnly
}
// query criteria
return this.field.isReadOnlyFromLogic
}
// other type of panels (process/report)
return isUpdateableAllFields
},
isMandatory() {
if (this.isAdvancedQuery) {
return false
}
return this.field.isMandatory || this.field.isMandatoryFromLogic
},
isFieldOnly() {
if (this.inTable || this.field.isFieldOnly || this.verifyIsFieldOnly(this.field.displayType)) {
return undefined
}
return this.field.name
},
/**
* TODO: Evaluate the current field with the only fields contained in the
* constant FIELD_ONLY
* @param {integer} id [identifier of the type of isDisplayed]
* @return {boolean}
*/
verifyIsFieldOnly(type) {
const field = FIELD_ONLY.find(itemField => {
if (type === itemField.id) {
return true
}
})
return Boolean(field)
},
focus(columnName) {
if (this.isDisplayed() && this.isMandatory() && !this.isReadOnly()) {
this.$refs[columnName].activeFocus(columnName)
}
}
}
}
</script>
<style lang="scss">
/**
* Separation between elements (item) of the form
*/
.el-form-item {
margin-bottom: 10px !important;
margin-left: 10px;
margin-right: 10px;
}
.in-table {
margin-bottom: 0px !important;
margin-left: 0px;
margin-right: 0px;
}
/* Global Styles */
.el-textarea__inner:not(.in-table) {
min-height: 36px !important;
/*
height: 36px auto !important;
max-height: 54.2333px !important;
*/
}
/**
* Reduce the spacing between the form element and its label
*/
.el-form--label-top .el-form-item__label {
padding-bottom: 0px !important;
}
</style>

View File

@ -0,0 +1,329 @@
// All references
const REFERENCES = [
{
id: 25,
type: 'FieldText',
support: false,
description: 'Account Element',
alias: ['Account']
},
{
id: 12,
type: 'FieldNumber',
support: true,
description: 'Number with 4 decimals',
alias: ['Amount']
},
{
id: 33,
type: 'FieldText',
support: false,
description: 'Resource Assignment',
alias: ['Assignment']
},
{
id: 23,
type: 'FieldBinary',
support: true,
description: 'Binary Data',
alias: ['Binary']
},
{
id: 28,
type: 'FieldButton',
support: true,
description: 'Command Button - starts a process',
alias: ['Button']
},
{
id: 53370,
type: 'FieldText',
support: false,
description: 'Chart',
alias: ['Chart']
},
{
id: 27,
type: 'FieldText',
support: false,
description: 'Color element',
alias: ['Color']
},
{
id: 37,
type: 'FieldNumber',
support: true,
description: 'Costs + Prices (minimum currency precision but if exists more)',
alias: ['CostsPrices', 'Costs+Prices', 'Cost Prices']
},
{
id: 15,
type: 'FieldDate',
support: true,
description: 'Date mm/dd/yyyy',
alias: ['Date']
},
{
id: 16,
type: 'FieldDate',
support: true,
description: 'Date with time',
alias: ['DateTime', 'Date Time', 'Date+Time']
},
{
id: 39,
type: 'FieldText',
support: true,
description: 'Local File',
alias: ['FileName', 'File Name']
},
{
id: 38,
type: 'FieldText',
support: true,
description: 'Local File Path',
alias: ['FilePath', 'File Path']
},
{
id: 53670,
type: 'FieldText',
support: true,
description: 'Local File Path or Name',
alias: ['FilePathOrName', 'File Path Or Name']
},
{
id: 13,
type: 'FieldNumber',
support: true,
description: '10 Digit Identifier',
alias: ['ID']
},
{
id: 32,
type: 'FieldImage',
support: true,
description: 'Binary Image Data',
alias: ['Image']
},
{
id: 11,
type: 'FieldNumber',
support: true,
description: '10 Digit numeric',
alias: ['Integer']
},
{
id: 17,
type: 'FieldSelect',
support: true,
description: 'Reference List',
alias: ['List']
},
{
id: 21,
type: 'FieldText',
support: false,
description: 'Location/Address',
alias: ['Location', 'Location (Address)', 'Location/Address']
},
{
id: 31,
type: 'FieldSelect',
support: true,
description: 'Warehouse Locator Data type',
alias: ['Locator', 'Locator (WH)', 'Locator/WH']
},
{
id: 34,
type: 'FieldText',
support: true,
description: 'Reference List',
alias: ['Memo']
},
{
id: 22,
type: 'FieldNumber',
support: true,
description: 'Float Number',
alias: ['Number']
},
{
id: 42,
type: 'FieldText',
support: true,
description: 'Printer Name',
alias: ['PrinterName', 'Printer Name']
},
{
id: 35,
type: 'FieldText',
support: false,
description: 'Product Attribute',
alias: ['ProductAttribute', 'Product Attribute']
},
{
id: 29,
type: 'FieldNumber',
support: true,
description: 'Quantity data type',
alias: ['Quantity']
},
{
id: 30,
type: 'FieldSelect',
support: true,
description: 'Search Field',
alias: ['Search']
},
{
id: 10,
type: 'FieldText',
support: true,
description: 'Character String',
alias: ['String']
},
{
id: 18,
type: 'FieldSelect',
support: true,
description: 'Table List',
alias: ['Table']
},
{
id: 19,
type: 'FieldSelect',
support: true,
description: 'Direct Table Access',
alias: ['TableDirect', 'Table Direct']
},
{
id: 14,
type: 'FieldText',
support: true,
description: 'Character String up to 2000 characters',
alias: ['Text']
},
{
id: 36,
type: 'FieldText',
support: true,
description: 'Text (Long) - Text > 2000 characters',
alias: ['TextLong', 'Text Long']
},
{
id: 24,
type: 'FieldTime',
support: true,
description: 'Time',
alias: ['Time']
},
{
id: 40,
type: 'FieldText',
support: true,
description: 'URL',
alias: ['URL', 'Url']
},
{
id: 20,
type: 'FieldYesNo',
support: true,
description: 'CheckBox',
alias: ['YesNo', 'Yes No', 'Yes-No']
}
]
export default REFERENCES
export const FIELD_RANGE = [
{
id: 12,
type: 'Amount',
description: 'Number with 4 decimals',
alias: ['Amount']
},
{
id: 37,
type: 'CostsPrices',
description: 'Costs + Prices (minimum currency precision but if exists more)',
alias: ['CostsPrices', 'Costs+Prices', 'Cost Prices']
},
{
id: 15,
type: 'Date',
description: 'Date mm/dd/yyyy',
alias: ['Date']
},
{
id: 16,
type: 'DateTime',
description: 'Date with time',
alias: ['DateTime', 'Date Time', 'Date+Time']
},
{
id: 11,
type: 'Integer',
description: '10 Digit numeric',
alias: ['Integer']
},
{
id: 22,
type: 'Number',
description: 'Float Number',
alias: ['Number']
},
{
id: 29,
type: 'Quantity',
description: 'Quantity data type',
alias: ['Quantity']
},
{
id: 24,
type: 'Time',
description: 'Time',
alias: ['Time']
}
]
export const FIELD_ONLY = [
{
id: 28,
type: 'Button',
description: 'Command Button - starts a process',
alias: ['Button']
}
]
export const FIELD_NOT_SHOWED = [
{
id: 28,
type: 'Button',
description: 'Command Button - starts a process',
alias: ['Button']
}
]
/**
* Fields with this column name, changed all fields is read only
*/
export const FIELD_READ_ONLY_FORM = [
{
columnName: 'IsActive', // column name of field
defaultValue: true, // default value when loading
valueIsReadOnlyForm: false, // value that activates read-only form
isChangedAllForm: false // change the entire form to read only including this field
},
{
columnName: 'Processed',
defaultValue: false,
valueIsReadOnlyForm: true,
isChangedAllForm: true
},
{
columnName: 'Processing',
defaultValue: true,
valueIsReadOnlyForm: false,
isChangedAllForm: true
}
]

View File

@ -0,0 +1,161 @@
<template>
<div :class="{'show-input-search':isShowElement}" class="search-detail">
<i
:class="icon + ' props-icon'"
@click.stop="click"
@submit.prevent.native="false"
/>
<slot
ref="headerSearchSelect"
/>
</div>
</template>
<script>
export default {
name: 'IconElement',
props: {
icon: {
type: String,
required: true
}
},
data() {
return {
isShowElement: false // show input from search,
}
},
watch: {
isShowElement(value) {
if (value) {
document.body.addEventListener('click', this.close)
} else {
document.body.removeEventListener('click', this.close)
}
}
},
methods: {
click() {
this.isShowElement = !this.isShowElement
if (this.isShowElement) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
}
},
close() {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
this.options = []
this.show = false
}
}
}
</script>
<style lang="scss">
.search-detail {
margin: 0 10px;
float: right;
color: #5a5e66;
height: 39px !important;
line-height: 39px !important;
.props-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
.header-search-input {
font-size: 18px;
transition: width 0.2s;
width: 0;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
/deep/ .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
.header-search-select {
font-size: 12px;
transition: width 0.2s;
width: 30px;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
height: 28px;
/deep/ .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show-input-search {
.header-search-input {
width: 190px;
}
}
.header-search-input-mobile {
transition: width 0.2s;
width: 0 !important;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
/deep/ .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
.header-search-select-mobile {
transition: width 0.2s;
width: 0 !important;
overflow: hidden;
background: transparent;
border-radius: 0;
display: inline-block;
vertical-align: middle;
/deep/ .el-input__inner {
border-radius: 0;
border: 0;
padding-left: 0;
padding-right: 0;
box-shadow: none !important;
border-bottom: 1px solid #d9d9d9;
vertical-align: middle;
}
}
&.show-input-search{
.header-search-select-mobile {
width: 120px !important;
}
}
&.show-input-search{
.header-search-input-mobile {
width: 120px !important;
}
}
}
</style>

View File

@ -0,0 +1,277 @@
<template>
<div v-if="showDetail">
<div class="container">
<!-- down button -->
<div class="show">
<el-button
v-if="!showPanel && !this.$store.state.app.sidebar.opened "
class="el-icon-arrow-up button-up btn"
:circle="true"
@click="handleChange()"
/>
<el-button
v-if="!showPanel && this.$store.state.app.sidebar.opened "
class="el-icon-arrow-up button-up2 btn"
:circle="true"
@click="handleChange()"
/>
</div>
<!-- panel show -->
<div :class="classContainer()">
<el-collapse-transition>
<div v-show="showPanel">
<el-button
class="el-icon-arrow-down button-bottom btn"
:circle="true"
@click="handleChange()"
/>
<slot />
</div>
</el-collapse-transition>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'PanelDetail',
props: {
isEdit: {
type: Boolean,
default: false
},
showDetail: {
type: Boolean,
default: true
},
panelType: {
type: String,
default: 'window'
},
containerUuid: {
type: String,
default: undefined
},
isShowedDetail: {
type: Boolean,
default: true
}
},
data() {
return {
showPanel: this.isShowedDetail
}
},
methods: {
classContainer() {
if (this.$store.state.app.device === 'mobile') {
return 'container-panel-mobile'
} else if (this.$store.state.app.sidebar.opened) {
return 'container-panel-open'
} else if (!this.$store.state.app.sidebar.opened) {
return 'container-panel-close'
}
return 'container-panel'
},
classButtom() {
if (this.$store.state.app.device === 'mobile') {
return 'container-panel-mobile'
} else if (this.$store.state.app.sidebar.opened) {
return 'el-icon-arrow-down button-bottom btn'
} else if (!this.$store.state.app.sidebar.opened) {
return 'el-icon-arrow-down button-bottom2 btn'
}
},
handleChange() {
this.showPanel = !this.showPanel
this.$store.dispatch('changeShowedDetail', {
panelType: this.panelType,
containerUuid: this.containerUuid,
isShowedDetail: this.showPanel
})
}
}
}
</script>
<style lang="scss" scoped>
@import "~@/styles/variables.scss";
.container {
bottom: 0;
right: 0;
z-index: 0;
width: calc(111% - 200px);
transition: width 0.28s;
position: fixed;
height: 20px;
display: flex;
color: #424242;
}
.show {
position: absolute;
bottom: 0;
color: #FFF;
width: 100%;
height: 300px;
transition: all 0.5s ease-in;
display: flex;
}
.container-open {
bottom: 0;
right: 0;
z-index: 0;
width: 100%;
transition: width 0.28s;
position: fixed;
height: 20px;
display: flex;
color: #424242;
}
.show-open {
position: absolute;
bottom: 0;
color: #FFF;
width: 100%;
height: 300px;
transition: all 0.5s ease-in;
display: flex;
}
.container:hover .show {
height: 30px;
}
.btn {
animation-name: btn;
position: relative;
transition-delay: 0.6s;
visibility: hidden;
/* right: 50%; */
}
.container:hover .btn {
visibility: visible;
}
.el-tabs__content {
overflow: hidden;
position: relative;
padding-top: 0px;
padding-left: 15px;
padding-right: 15px;
}
.btn-base :hover {
box-shadow: 5px #5a5a5a;
}
.tab-window {
z-index: 9;
}
.container-panel {
position: fixed;
bottom: 0;
right: 0;
z-index: 0;
width: calc(100% - 54px);
/* height: 40%; */
transition: width 0.28s;
}
.container-panel-movil {
position: fixed;
bottom: 0;
right: 0;
z-index: 0;
width: 100%;
height: 60%;
transition: width 0.28s;
}
.container-panel-mobile {
position: fixed;
bottom: 0;
right: 0;
z-index: 0;
width: 100%;
transition: width 0.28s;
}
.container-panel-open {
position: fixed;
bottom: 0;
right: 0;
z-index: 0;
width: calc(100% - #{$sideBarWidth});
transition: width 0.28s;
}
.container-panel-close {
position: fixed;
bottom: 0;
right: 0;
z-index: 0;
width: calc(100% - 2em);
transition: width 0.28s;
}
.container-up {
right: 50%;
}
.show {
position: absolute;
bottom: 0;
color: #FFF;
width: 100%;
height: 0px;
transition: all 0.5s ease-in;
display: flex;
justify-content: center;
align-items: center;
}
.button-bottom {
bottom: 50%;
z-index: 2;
position: relative;
margin: 0 auto;
left: 45%;
}
.button-bottom2 {
bottom: 50%;
z-index: 2;
position: relative;
margin: 0 auto;
left: 45%;
}
.button-up {
bottom: 0;
position: fixed;
left: 48%;
margin: 0 auto;
}
.button-up2 {
bottom: 0;
position: fixed;
left: 57%;
margin: 0 auto;
}
.btn-base {
width: 100%;
position: fixed;
background: #ffffff;
color: #606266;
-webkit-appearance: none;
text-align: center;
outline: 0;
font-size: 14px;
}
.btn-base :hover {
box-shadow: 5px #5a5a5a;
}
.el-row {
margin-bottom: 20px;
}
.el-col {
border-radius: 4px;
left: 10px;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
width: 100%;
}
</style>

View File

@ -0,0 +1,118 @@
<template>
<el-select
v-model="selectedFields"
:filterable="!isMobile"
:placeholder="$t('components.filterableItems')"
multiple
collapse-tags
value-key="key"
style="float: right;"
@change="addField"
>
<el-option
v-for="(item, key) in getterFieldListOptional"
:key="key"
:label="item.name"
:value="item.columnName"
/>
</el-select>
</template>
<script>
export default {
name: 'FilterFields',
props: {
containerUuid: {
type: String,
required: true
},
groupField: {
type: String,
default: ''
},
panelType: {
type: String,
default: 'window'
},
isAdvancedQuery: {
type: Boolean,
default: false
}
},
data() {
return {
selectedFields: [] // fields optional showed
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
getterFieldListOptional() {
if (this.panelType === 'table') {
// fields to search without taking into account the mandatory
return this.$store.getters.getFieldsListFromPanel(this.containerUuid, this.isAdvancedQuery)
.filter(fieldItem => {
return fieldItem.componentPath !== 'FieldButton'
})
} else if (this.panelType === 'window') {
// compare group fields to window
return this.$store.getters.getFieldsListNotMandatory(this.containerUuid)
.filter(fieldItem => {
return fieldItem.groupAssigned === this.groupField
})
}
// get fields not mandatory
return this.$store.getters.getFieldsListNotMandatory(this.containerUuid)
},
getFieldSelected() {
return this.getterFieldListOptional
.filter(fieldItem => {
return fieldItem.isShowedFromUser
})
.map(itemField => itemField.columnName)
}
},
watch: {
// TODO: Verify peformance with computed set (dispatch) and get (state)
getFieldSelected(value) {
this.selectedFields = value
}
},
created() {
this.selectedFields = this.getFieldSelected
},
methods: {
/**
* @param {array} selectedValues
*/
addField(selectedValues) {
this.$store.dispatch('changeFieldShowedFromUser', {
containerUuid: this.containerUuid,
fieldsUser: selectedValues,
show: true,
groupField: this.groupField,
isAdvancedQuery: this.isAdvancedQuery
})
}
}
}
</script>
<style>
.el-tag--small {
height: 24px;
padding: 0 8px;
line-height: 22px;
max-width: 65%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.el-select .el-tag__close.el-icon-close {
background-color: #C0C4CC;
right: 0px;
top: 0;
color: #FFFFFF;
}
</style>

View File

@ -0,0 +1,691 @@
<template>
<div class="wrapper">
<el-form
v-if="isLoadPanel"
key="panel-loaded"
v-model="dataRecords"
label-position="top"
label-width="200px"
>
<template
v-if="firstGroup && firstGroup.groupFinal === ''"
>
<div v-show="firstGroup.activeFields" class="cards-not-group">
<div
v-if="(groupTab.groupType == 'T' && groupTab.groupName == firstGroup.groupFinal)
|| (groupTab.groupType !== 'T' && firstGroup.typeGroup !== 'T')"
class="card"
>
<div class="select-filter">
<span>
{{ firstGroup.groupFinal }}
</span>
<filter-fields
:container-uuid="containerUuid"
:panel-type="panelType"
:group-field="firstGroup.groupFinal"
:is-advanced-query="isAdvancedQuery"
/>
</div>
<el-card
:shadow="isMobile ? 'never' : 'hover'"
:body-style="{ padding: '10px' }"
>
<el-row :gutter="gutterRow">
<template v-for="(fieldAttributes, subKey) in firstGroup.metadataFields">
<field-definition
:ref="fieldAttributes.columnName"
:key="subKey"
:parent-uuid="parentUuid"
:container-uuid="containerUuid"
:metadata-field="{
...fieldAttributes,
optionCRUD: optionCRUD
}"
:record-data-fields="isAdvancedQuery ? undefined : dataRecords[fieldAttributes.columnName]"
:panel-type="panelType"
:in-group="!getterIsShowedRecordNavigation"
:is-advanced-query="isAdvancedQuery"
/>
</template>
</el-row>
</el-card>
</div>
</div>
</template>
<div :class="cards()">
<draggable
v-if="!isMobile"
:list="fieldGroups"
v-bind="$attrs"
:set-data="setData"
>
<template v-for="(item, key) in fieldGroups">
<el-row :key="key">
<el-col :key="key" :span="24">
<div
v-if="item.groupFinal !== ''
&& (groupTab.groupType == 'T' && groupTab.groupName == item.groupFinal)
|| (groupTab.groupType !== 'T' && item.typeGroup !== 'T')"
:key="key"
class="card"
>
<el-card
:shadow="isMobile ? 'never' : 'hover'"
:body-style="{ padding: '10px' }"
>
<div slot="header" class="clearfix">
<span>
{{ item.groupFinal }}
</span>
<div class="select-filter-header">
<filter-fields
:container-uuid="containerUuid"
:panel-type="panelType"
:group-field="item.groupFinal"
:is-first-group="false"
:is-advanced-query="isAdvancedQuery"
/>
</div>
</div>
<el-row :gutter="gutterRow">
<template v-for="(fieldAttributes, subKey) in item.metadataFields">
<field-definition
:ref="fieldAttributes.columnName"
:key="subKey"
:parent-uuid="parentUuid"
:container-uuid="containerUuid"
:metadata-field="{
...fieldAttributes,
optionCRUD: optionCRUD
}"
:record-data-fields="isAdvancedQuery ? undefined : dataRecords[fieldAttributes.columnName]"
:panel-type="panelType"
:in-group="isPanelWindow && fieldGroups.length > 1"
:is-advanced-query="isAdvancedQuery"
/>
</template>
</el-row>
</el-card>
</div>
</el-col>
</el-row>
</template>
</draggable>
<template v-else>
<template v-for="(item, key) in fieldGroups">
<el-row :key="key">
<el-col :key="key" :span="24">
<div
v-if="item.groupFinal !== ''
&& (groupTab.groupType == 'T' && groupTab.groupName == item.groupFinal)
|| (groupTab.groupType !== 'T' && item.typeGroup !== 'T')"
:key="key"
class="card"
>
<el-card
:shadow="isMobile ? 'never' : 'hover'"
:body-style="{ padding: '10px' }"
>
<div slot="header" class="clearfix">
<span>
{{ item.groupFinal }}
</span>
<div v-if="!isAdvancedQuery" class="select-filter-header">
<filter-fields
:container-uuid="containerUuid"
:panel-type="panelType"
:group-field="item.groupFinal"
:is-first-group="false"
/>
</div>
</div>
<el-row :gutter="gutterRow">
<template v-for="(fieldAttributes, subKey) in item.metadataFields">
<field-definition
:ref="fieldAttributes.columnName"
:key="subKey"
:parent-uuid="parentUuid"
:container-uuid="containerUuid"
:metadata-field="{
...fieldAttributes,
optionCRUD: optionCRUD
}"
:record-data-fields="isAdvancedQuery ? undefined : dataRecords[fieldAttributes.columnName]"
:panel-type="panelType"
:in-group="isPanelWindow && fieldGroups.length > 1"
/>
</template>
</el-row>
</el-card>
</div>
</el-col>
</el-row>
</template>
</template>
</div>
</el-form>
<div
v-else
key="panel-loading"
v-loading="!isLoadPanel"
:element-loading-text="$t('notifications.loading')"
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.8)"
class="loading-panel"
/>
</div>
</template>
<script>
import FieldDefinition from '@/components/ADempiere/Field'
import FilterFields from '@/components/ADempiere/Panel/filterFields'
import draggable from 'vuedraggable'
import { fieldIsDisplayed, parsedValueComponent } from '@/utils/ADempiere'
export default {
name: 'MainPanel',
components: {
FieldDefinition,
FilterFields,
draggable
},
props: {
parentUuid: {
type: String,
default: ''
},
containerUuid: {
type: String,
required: true
},
metadata: {
type: Object,
default: () => {}
},
// tab type group
groupTab: {
type: Object,
default: () => ({
groupType: '',
groupName: ''
})
},
panelType: {
type: String,
default: 'window'
},
isAdvancedQuery: {
type: Boolean,
default: false
}
},
data() {
return {
fieldList: [],
dataRecords: {},
gutterRow: 0,
isLoadPanel: false,
isLoadRecord: false,
uuidRecord: this.$route.query.action,
fieldGroups: [],
firstGroup: {},
groupsView: 0,
tagTitle: {
base: this.$route.meta.title,
action: ''
}
}
},
computed: {
optionCRUD() {
return this.isEmptyValue(this.uuidRecord) ? 'create-new' : this.uuidRecord
},
isPanelWindow() {
return this.panelType === 'window'
},
getterIsShowedRecordNavigation() {
if (this.isPanelWindow) {
return this.$store.getters.getIsShowedRecordNavigation(this.parentUuid)
}
return false
},
getterFieldList() {
var panel = this.$store.getters.getPanel(this.containerUuid, this.isAdvancedQuery)
if (panel) {
return panel.fieldList
}
return panel
},
isMobile() {
return this.$store.state.app.device === 'mobile'
},
getterDataStore() {
if (this.isPanelWindow) {
return this.$store.getters.getDataRecordAndSelection(this.containerUuid)
}
return {
recordCount: 0,
isLoaded: false,
record: []
}
},
getterTotalDataRecordCount() {
return this.getterDataStore.recordCount
},
getterIsLoadedRecord() {
return this.getterDataStore.isLoaded
},
getterRowData() {
if (this.isPanelWindow) {
if (!this.isEmptyValue(this.uuidRecord) && this.uuidRecord !== 'create-new') {
return this.$store.getters.getRowData(this.containerUuid, this.uuidRecord)
}
}
return false
}
},
watch: {
// used only panel modal (process associated in browser or window)
containerUuid() {
this.generatePanel(this.metadata.fieldList)
},
// used if the first load contains a uuid
isLoadRecord(value) {
// TODO: Validate UUID value
if (value && this.isPanelWindow && this.uuidRecord !== 'create-new' && !this.isEmptyValue(this.uuidRecord)) {
this.setTagsViewTitle(this.uuidRecord)
}
},
'$route.query.action'(newValue, oldValue) {
// used in field, if uuid record or different create-new, field is read only
this.uuidRecord = newValue
if (newValue !== oldValue && this.isPanelWindow) {
this.changePanelRecord(newValue)
}
},
isLoadPanel(value) {
if (value) {
this.readParameters(this.$route)
}
}
},
created() {
// get fields with uuid
this.getPanel()
},
methods: {
cards() {
if (this.isMobile || this.fieldGroups.length < 2 || this.getterIsShowedRecordNavigation) {
return 'cards-not-group'
}
return 'cards-in-group'
},
/**
* Get the tab object with all its attributes as well as the fields it contains
*/
getPanel() {
var fieldList = this.getterFieldList
if (fieldList && Array.isArray(fieldList)) {
this.generatePanel(fieldList)
} else {
this.$store.dispatch('getPanelAndFields', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
type: this.panelType,
isAdvancedQuery: this.isAdvancedQuery
}).then(() => {
this.generatePanel(this.getterFieldList)
}).catch(error => {
console.warn('Field Load Error ' + error.code + ': ' + error.message)
})
}
},
generatePanel(fieldList) {
// order and assign groups
this.fieldList = fieldList
if (fieldList.length) {
this.fieldGroups = this.sortAndGroup(fieldList)
}
var firstGroup
if (this.fieldGroups[0] && this.fieldGroups[0].groupFinal === '') {
firstGroup = this.fieldGroups[0]
this.fieldGroups.shift()
}
this.firstGroup = firstGroup
this.isLoadPanel = true
},
/**
* TODO: Delete route parameters after reading them
*/
readParameters(route) {
var parameters = {
isLoadAllRecords: true,
isReference: false,
isNewRecord: false,
isWindow: true
}
if (this.isPanelWindow) {
// TODO: use action notifyPanelChange with isShowedField in true
this.getterFieldList.forEach(fieldItem => {
if (route.query.hasOwnProperty(fieldItem.columnName) && !fieldItem.isAdvancedQuery) {
fieldItem.isShowedFromUser = true
fieldItem.value = parsedValueComponent({
fieldType: fieldItem.componentPath,
value: route.query[fieldItem.columnName]
})
if (String(route.query.isAdvancedQuery) === String(fieldItem.isAdvancedQuery)) {
fieldItem.value = parsedValueComponent({
fieldType: fieldItem.componentPath,
value: route.query[fieldItem.columnName]
})
if (fieldItem.isRange && this.$route.query[fieldItem.columnName + '_To']) {
fieldItem.valueTo = parsedValueComponent({
fieldType: fieldItem.componentPath,
value: route.query[fieldItem.columnName + '_To']
})
}
}
}
})
if (route.query.action && route.query.action === 'reference') {
parameters.isLoadAllRecords = false
parameters.isReference = true
parameters.referenceUuid = route.query.referenceUuid
parameters.referenceWhereClause = route.query.whereClause
} else if (route.query.action && route.query.action === 'create-new') {
parameters.isNewRecord = true
} else if (route.query.action && route.query.action !== 'create-new' && route.query.action !== 'reference' && route.query.action !== 'advancedQuery') {
parameters.isLoadAllRecords = false
parameters.value = route.query.action
parameters.tableName = this.metadata.tableName
parameters.columnName = 'UUID'
}
// Only call get data if panel type is window
if (!route.params.hasOwnProperty('isReadParameters') || route.params.isReadParameters) {
this.getData(parameters)
}
} else {
if (this.panelType === 'table' && route.query.action === 'advancedQuery') {
// TODO: use action notifyPanelChange with isShowedField in true
this.fieldList.forEach(fieldItem => {
if (route.query.hasOwnProperty(fieldItem.columnName) && fieldItem.isAdvancedQuery) {
fieldItem.isShowedFromUser = true
if (route.query.action === 'advancedQuery' === fieldItem.isAdvancedQuery) {
fieldItem.value = parsedValueComponent({
fieldType: fieldItem.componentPath,
value: route.query[fieldItem.columnName]
})
if (fieldItem.isRange && route.query[fieldItem.columnName + '_To']) {
fieldItem.valueTo = parsedValueComponent({
fieldType: fieldItem.componentPath,
value: route.query[fieldItem.columnName + '_To']
})
}
}
}
})
parameters.isWindow = false
} else if (this.panelType === 'process' || this.panelType === 'browser') {
if (!this.isEmptyValue(route.query)) {
this.$store.dispatch('notifyPanelChange', {
containerUuid: this.containerUuid,
newValues: route.query,
isShowedField: true,
isSendCallout: false,
panelType: this.panelType
})
parameters.isWindow = false
} else {
this.$store.dispatch('resetPanelToNew', {
containerUuid: this.containerUuid,
panelType: this.panelType
})
}
}
}
},
/**
* @param {object} parameters parameters to condition the data query
*/
getData(parameters) {
if (parameters.isWindow && this.isPanelWindow && !this.getterIsLoadedRecord) {
this.$store.dispatch('getDataListTab', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
isLoadAllRecords: parameters.isLoadAllRecords,
columnName: parameters.columnName,
value: parameters.value
})
.then(response => {
if (response.length && !parameters.isNewRecord) {
this.dataRecords = response[0]
if (this.$route.query.action === 'create-new') {
this.$router.push({
name: this.$route.name,
query: {
...this.$route.query
}
})
} else if (this.$route.query.action === 'reference') {
this.$router.push({
name: this.$route.name,
query: {
...this.$route.query
}
})
this.$store.dispatch('notifyPanelChange', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
newValues: this.dataRecords,
isSendToServer: false,
isSendCallout: false,
fieldList: this.fieldList,
panelType: this.panelType
})
} else {
this.$router.push({
name: this.$route.name,
query: {
action: this.dataRecords.UUID,
...this.$route.query
}
})
this.$store.dispatch('notifyPanelChange', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
newValues: this.dataRecords,
isSendToServer: false,
isSendCallout: false,
fieldList: this.fieldList,
panelType: this.panelType
})
}
this.setTagsViewTitle(this.$route.query.action)
this.isLoadRecord = true
} else {
this.$router.push({
query: {
action: 'create-new',
...this.$route.query
}
})
}
this.setFocus()
})
}
},
/**
* Group the arrangement into groups of columns that they contain, it must
* be grouped after having the order
* @param {array} array
* @return {array} res
*/
sortAndGroup(arr) {
if (arr === undefined) {
return
}
var res = [{
groupFinal: '',
metadataFields: arr
}]
// reduce, create array with number groupAssigned element comun
if (this.isPanelWindow) {
res = arr
.reduce((res, currentValue) => {
if (!res.includes(currentValue.groupAssigned)) {
res.push(currentValue.groupAssigned)
}
return res
}, [])
.map(itemGroup => {
return {
groupFinal: itemGroup,
metadataFields: arr.filter(itemField => {
return itemField.groupAssigned === itemGroup
})
}
})
}
// count and add the field numbers according to your group
Object.keys(res).forEach(key => {
let count = 0
const typeG = res[key].metadataFields[0].typeGroupAssigned
res[key].numberFields = res[key].metadataFields.length
res[key].typeGroup = typeG
res[key].numberFields = res[key].metadataFields.length
res[key].metadataFields.forEach((element, index) => {
if (element.isDisplayed) {
count++
}
})
if ((this.groupTab.groupType === 'T' && this.groupTab.groupName === res[key].groupFinal) ||
(this.groupTab.groupType !== 'T' && res[key].typeGroup !== 'T')) {
this.groupsView = this.groupsView + 1
}
res[key].activeFields = count
})
return res
},
setTagsViewTitle(actionValue) {
if (actionValue === 'create-new' || actionValue === '') {
this.tagTitle.action = this.$t('tagsView.newRecord')
} else if (actionValue === 'advancedQuery') {
this.tagTitle.action = this.$t('tagsView.advancedQuery')
} else {
var field = this.fieldList.find(fieldItem => fieldItem.isIdentifier)
if (field) {
if (this.dataRecords[field.columnName]) {
this.tagTitle.action = this.dataRecords[field.columnName]
} else {
this.tagTitle.action = field.value
}
} else {
this.tagTitle.action = this.$t('tagsView.seeRecord')
}
}
if (this.isPanelWindow) {
var tempRoute = Object.assign({}, this.$route, { title: `${this.tagTitle.base} - ${this.tagTitle.action}` })
this.$store.dispatch('tagsView/updateVisitedView', tempRoute)
}
},
setData(dataTransfer) {
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
},
changePanelRecord(uuidRecord) {
if (uuidRecord !== 'create-new' && uuidRecord !== 'reference' && uuidRecord !== 'advancedQuery') {
var recordSelected = this.$store.getters.getDataRecordsList(this.containerUuid).find(record => record.UUID === uuidRecord)
if (recordSelected) {
this.dataRecords = recordSelected
this.$store.dispatch('notifyPanelChange', {
parentUuid: this.parentUuid,
containerUuid: this.containerUuid,
newValues: this.dataRecords,
isSendToServer: false,
isSendCallout: false,
fieldList: this.fieldList,
panelType: this.panelType
}).then(() => {
// delete records tabs children when change record uuid
this.$store.dispatch('deleteRecordContainer', {
viewUuid: this.parentUuid,
withOut: [this.containerUuid]
})
})
}
}
this.setTagsViewTitle(uuidRecord)
this.setFocus()
},
setFocus() {
var isFocusEnabled = false
this.getterFieldList.forEach(fieldItem => {
if (!isFocusEnabled) {
if (this.isFocusable(fieldItem) && this.$refs.hasOwnProperty(fieldItem.columnName)) {
this.$refs[fieldItem.columnName][0].focus(fieldItem.columnName)
isFocusEnabled = true
}
}
return
})
},
isFocusable(field) {
if (fieldIsDisplayed(field) && !field.isReadOnly && field.isUpdateable) {
return true
}
return false
}
}
}
</script>
<style scoped>
.loading-panel {
padding: 100px;
height: 100%;
}
.cards-in-group {
column-count: 2; /*numbers of columns */
column-gap: 1em;
}
.cards-not-group {
column-count: 1; /* numbers of columns */
column-gap: 1em;
margin-bottom: 5px;
}
.card {
/* padding: 10px; */
width: 100% !important;
transition: all 100ms ease-in-out;
display: inline-block;
perspective: 1000;
backface-visibility: hidden;
}
.el-card {
width: 100% !important;
}
</style>
<style>
.select-filter {
width: 280px !important;
float: right;
top: 0;
}
.select-filter-header {
width: 60% !important;
float: right;
top: 0px;
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<el-card class="box-card">
<div slot="header" class="clearfix">
<span> {{ $t('profile.recentItems') }} </span>
<el-input
v-model="search"
size="mini"
:placeholder="$t('table.dataTable.search')"
class="search_recent"
/>
</div>
<div class="recent-items">
<el-table
:data="search.length ? filterResult(search) : recentItems"
@row-click="handleClick"
>
<el-table-column width="40">
<template slot-scope="{row}">
<svg-icon :icon-class="row.icon" class="icon-window" />
</template>
</el-table-column>
<el-table-column :label="$t('profile.recentItems')">
<template slot-scope="{row}">
<span>{{ row.displayName }}</span>
<el-tag class="action-tag">{{ $t(`views.${row.action}`) }}</el-tag>
<br>
<span class="time">{{ translateDate(row.updated) }}</span>
</template>
</el-table-column>
</el-table>
</div>
</el-card>
</template>
<script>
export default {
name: 'RecentItems',
data() {
return {
recentItems: [],
isLoaded: true,
search: '',
accentRegexp: /[\u0300-\u036f]/g
}
},
computed: {
getterRecentItems() {
return this.$store.getters.getRecentItems
},
cachedViews() {
return this.$store.getters.cachedViews
}
},
mounted() {
this.getRecentItems()
this.subscribeChanges()
},
methods: {
checkOpened(uuid) {
return this.cachedViews.includes(uuid)
},
getRecentItems() {
var items = this.getterRecentItems
if (items === undefined || items.length < 1) {
this.$store.dispatch('getRecentItemsFromServer')
.then(response => {
this.recentItems = response
this.isLoaded = false
}).catch(error => {
console.log(error)
})
} else {
this.recentItems = items
this.isLoaded = false
}
},
handleClick(row) {
if (!this.isEmptyValue(row.uuidRecord)) {
this.$router.push({ name: row.menuUuid, query: { action: row.uuidRecord, tabParent: 0 }})
} else {
this.$router.push({ name: row.menuUuid })
}
},
subscribeChanges() {
this.$store.subscribe((mutation, state) => {
if (mutation.type === 'setRecentItems') {
this.recentItems = this.getterRecentItems
}
})
},
filterResult(search) {
return this.recentItems.filter(item => this.ignoreAccent(item.displayName).toLowerCase().includes(this.ignoreAccent(search.toLowerCase())))
},
ignoreAccent(s) {
if (!s) { return '' }
return s.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
},
translateDate(value) {
return this.$d(new Date(value), 'long', this.language)
}
}
}
</script>
<style scoped>
.search_recent {
width: 50%!important;
float: right;
}
.header {
padding-bottom: 10px;
}
.recent-items {
height: 455px;
overflow: auto;
}
.time {
float: left;
font-size: 11px;
color: #999;
}
.card-box {
cursor: pointer;
}
.card-content {
font-size: 15px;
}
.icon-window {
font-size: x-large;
color: #36a3f7;
}
.action-tag {
float: right;
}
</style>

View File

@ -0,0 +1,68 @@
<template>
<el-tabs v-model="currentTab" type="border-card" :before-leave="handleBeforeLeave" @tab-click="handleClick">
<template v-for="(item, key) in tabsList">
<el-tab-pane
:key="key"
:label="item.name"
:windowuuid="windowUuid"
:tabuuid="item.uuid"
:position-tab="key"
:name="String(item.index)"
:lazy="true"
:disabled="Boolean(key > 0 && isCreateNew)"
:style="isShowedDetail ? { height: '100%', overflow: 'hidden' } : { height: '75vh', overflow: 'auto' }"
>
<main-panel
:parent-uuid="windowUuid"
:container-uuid="item.uuid"
:metadata="item"
:group-tab="item.tabGroup"
:panel-type="panelType"
:is-re-search="Boolean(key == 0 || (key > 0 && firstTableName != item.tableName))"
/>
</el-tab-pane>
</template>
</el-tabs>
</template>
<script>
import { tabMixin } from '@/components/ADempiere/Tab/tabMixin'
import MainPanel from '@/components/ADempiere/Panel'
export default {
name: 'TabParent',
components: {
MainPanel
},
mixins: [tabMixin],
computed: {
// if tabs children is showed or closed
isShowedDetail() {
const window = this.$store.getters.getWindow(this.windowUuid)
if (window) {
return window.isShowedDetail
}
return undefined
}
},
watch: {
// TODO: Remove watchers of action, and pased as props from window
'$route.query.action'(actionValue) {
if (actionValue === 'create-new') {
this.currentTab = '0'
}
},
currentTab(newValue, oldValue) {
if (newValue !== oldValue) {
this.$router.push({
query: {
...this.$route.query,
tabParent: String(newValue)
}
})
this.$route.meta.tabUuid = this.tabUuid
}
}
}
}
</script>

View File

@ -0,0 +1,83 @@
<template>
<el-tabs v-model="currentTabChild" type="border-card" @tab-click="handleClick">
<template v-for="(item, key) in tabsList">
<!-- TODO: Add support to tabs isSortTab (sequence) -->
<el-tab-pane
:key="key"
:label="item.name"
:windowuuid="windowUuid"
:tabuuid="item.uuid"
:position-tab="key"
:name="String(item.index)"
:lazy="true"
:disabled="isCreateNew"
>
<el-col :span="24">
<data-table
:parent-uuid="windowUuid"
:container-uuid="item.uuid"
:panel-type="panelType"
/>
</el-col>
</el-tab-pane>
</template>
</el-tabs>
</template>
<script>
import { tabMixin } from '@/components/ADempiere/Tab/tabMixin'
import DataTable from '@/components/ADempiere/DataTable'
export default {
name: 'TabChildren',
components: {
DataTable
},
mixins: [tabMixin],
props: {
firstTabUuid: {
type: String,
default: undefined
},
firstIndex: {
type: String,
default: undefined
}
},
data() {
return {
currentTabChild: this.$route.query.tabChild
}
},
computed: {
// data this current tab
getDataSelection() {
return this.$store.getters.getDataRecordAndSelection(this.tabUuid)
},
// data parent tab
getterDataParentTab() {
return this.$store.getters.getDataRecordAndSelection(this.firstTabUuid)
},
getterIsLoadRecordParent() {
return this.getterDataParentTab.isLoaded
},
getterIsLoadContextParent() {
return this.getterDataParentTab.isLoadedContext
}
},
mounted() {
this.setCurrentTabChild()
}
}
</script>
<style>
.el-tabs__content {
overflow: hidden;
position: relative;
padding-top: 0px !important;
padding-right: 15px !important;
padding-bottom: 0px !important;
padding-left: 15px !important;
}
</style>

View File

@ -0,0 +1,94 @@
import { parseContext } from '@/utils/ADempiere'
export const tabMixin = {
props: {
windowUuid: {
type: String,
default: ''
},
tabsList: {
type: [Array, Object],
default: () => []
}
},
data() {
return {
currentTab: this.$route.query.tabParent,
tabUuid: '',
panelType: 'window',
firstTableName: this.tabsList[0].tableName
}
},
computed: {
isCreateNew() {
return Boolean(this.$route.query.action === 'create-new')
},
getterDataRecords() {
return this.$store.getters.getDataRecordsList(this.tabUuid)
}
},
watch: {
// Refrest the records of the TabChildren
getDataSelection(value) {
if (!value.isLoaded && this.getterIsLoadContextParent && this.getterIsLoadRecordParent) {
this.getDataTable()
}
},
// Current TabChildren
currentTabChild(newValue, oldValue) {
if (newValue !== oldValue) {
this.$router.push({ query: { ...this.$route.query, tabChild: String(newValue) }})
}
},
// Load parent tab context
getterIsLoadContextParent(value) {
if (value && !this.getDataSelection.isLoaded && this.getterIsLoadRecordParent) {
this.getDataTable()
}
}
},
created() {
this.tabUuid = this.tabsList[0].uuid
},
methods: {
parseContext,
//
getDataTable() {
this.$store.dispatch('getDataListTab', {
parentUuid: this.windowUuid,
containerUuid: this.tabUuid
})
},
setCurrentTab() {
this.$store.dispatch('setCurrentTab', {
parentUuid: this.windowUuid,
containerUuid: this.tabUuid
})
this.$route.meta.tabUuid = this.tabUuid
},
setCurrentTabChild() {
if (this.$route.query.tabChild === undefined && this.firstIndex) {
this.currentTabChild = this.firstIndex
}
},
/**
* @param {object} tabHTML DOM HTML the tab clicked
*/
handleClick(tabHTML) {
if (this.tabUuid !== tabHTML.$attrs.tabuuid) {
this.tabUuid = tabHTML.$attrs.tabuuid
this.setCurrentTab()
}
},
handleBeforeLeave(activeName) {
var metadataTab = this.tabsList.find(tab => tab.index === parseInt(activeName))
if (!this.isEmptyValue(metadataTab.whereClause) && metadataTab.whereClause.includes('@')) {
metadataTab.whereClause = parseContext({
parentUuid: metadataTab.parentUuid,
containerUuid: metadataTab.uuid,
value: metadataTab.whereClause
}, true)
}
}
}
}

View File

@ -1,7 +1,33 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<template v-if="levelList.length > 3">
<el-breadcrumb-item key="0">
<span v-if="firstItem.redirect==='noRedirect'" class="no-redirect">
{{ generateTitle(firstItem.meta.title) }}
</span>
<a v-else @click.prevent="handleLink(firstItem)">
{{ generateTitle(firstItem.meta.title) }}
</a>
</el-breadcrumb-item>
<el-breadcrumb-item key="1">
<el-dropdown placement="bottom" trigger="click" :hide-on-click="true" class="el-dropdown-link" @command="handleLink">
<i class="el-icon-more" />
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(item, index) in dropdownList" :key="index" :command="item">{{ generateTitle(item.meta.title) }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-breadcrumb-item>
<el-breadcrumb-item key="2">
<span v-if="lastItem.redirect==='noRedirect'" class="no-redirect">
{{ generateTitle(lastItem.meta.title) }}
</span>
<a v-else @click.prevent="handleLink(lastItem)">
{{ generateTitle(lastItem.meta.title) }}
</a>
</el-breadcrumb-item>
</template>
<el-breadcrumb-item v-for="(item,index) in levelList" v-else :key="item.path">
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{
generateTitle(item.meta.title) }}</span>
<a v-else @click.prevent="handleLink(item)">{{ generateTitle(item.meta.title) }}</a>
@ -17,7 +43,10 @@ import pathToRegexp from 'path-to-regexp'
export default {
data() {
return {
levelList: null
levelList: null,
dropdownList: null,
firstItem: null,
lastItem: null
}
},
watch: {
@ -36,10 +65,15 @@ export default {
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/dashboard', meta: { title: 'dashboard' }}].concat(matched)
matched = [{ path: '/dashboard', name: 'Dashboard', meta: { title: 'dashboard' }}].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
if (this.levelList.length > 3) {
this.dropdownList = [...this.levelList]
this.lastItem = this.dropdownList.pop()
this.firstItem = this.dropdownList.shift()
}
},
isDashboard(route) {
const name = route && route.name
@ -55,12 +89,9 @@ export default {
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
if (this.$route.name !== item.name) {
this.$router.push({ name: item.name, params: { childs: item.meta.childs }})
}
this.$router.push(this.pathCompile(path))
}
}
}
@ -68,7 +99,6 @@ export default {
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 50px;
margin-left: 8px;
@ -77,5 +107,11 @@ export default {
color: #97a8be;
cursor: text;
}
.el-dropdown-link {
cursor: pointer;
.el-icon-more {
transform: none;
}
}
}
</style>

View File

@ -8,11 +8,11 @@
filterable
default-first-option
remote
placeholder="Search"
:placeholder="$t('table.dataTable.search')"
class="header-search-select"
@change="change"
>
<el-option v-for="item in options" :key="item.path" :value="item" :label="item.title.join(' > ')" />
<el-option v-for="item in options" :key="item.name" :value="item" :label="item.title.join(' > ')" />
</el-select>
</div>
</template>
@ -22,7 +22,8 @@
// make search results more in line with expectations
import Fuse from 'fuse.js'
import path from 'path'
import i18n from '@/lang'
import { generateTitle } from '@/utils/i18n'
// import i18n from '@/lang'
export default {
name: 'HeaderSearch',
@ -36,6 +37,9 @@ export default {
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
routes() {
return this.$store.getters.permission_routes
},
@ -65,6 +69,7 @@ export default {
this.searchPool = this.generateRoutes(this.routes)
},
methods: {
generateTitle,
click() {
this.show = !this.show
if (this.show) {
@ -77,7 +82,15 @@ export default {
this.show = false
},
change(val) {
this.$router.push(val.path)
if (val.name) {
if (val.meta && val.meta.type === 'window') {
this.$router.push({ name: val.name, query: { tabParent: 0 }, params: { childs: val.meta.childs }})
} else {
this.$router.push({ name: val.name, params: { childs: val.meta.childs }})
}
} else {
this.$router.push({ path: val.path })
}
this.search = ''
this.options = []
this.$nextTick(() => {
@ -108,20 +121,19 @@ export default {
for (const router of routes) {
// skip hidden router
if (router.hidden) { continue }
// if (router.meta && router.meta.isIndex) { continue }
const data = {
path: path.resolve(basePath, router.path),
title: [...prefixTitle]
title: [...prefixTitle],
meta: router.meta,
name: router.name
}
if (router.meta && router.meta.title) {
// generate internationalized title
const i18ntitle = i18n.t(`route.${router.meta.title}`)
const i18ntitle = this.generateTitle(router.meta.title)
data.title = [...data.title, i18ntitle]
if (router.redirect !== 'noRedirect') {
if (router.redirect !== 'noRedirect' && router.name !== 'Report Viewer' && !router.meta.isIndex) {
// only push the routes with title
// special case: need to exclude parent router without redirect
res.push(data)
@ -182,7 +194,7 @@ export default {
&.show {
.header-search-select {
width: 210px;
width: 150px;
margin-left: 10px;
}
}

View File

@ -31,6 +31,9 @@ export default {
handleSetLanguage(lang) {
this.$i18n.locale = lang
this.$store.dispatch('app/setLanguage', lang)
if (this.$route.path !== '/login') {
location.reload()
}
this.$message({
message: 'Switch Language Success',
type: 'success'

View File

@ -1,12 +1,16 @@
<template>
<div ref="rightPanel" :class="{show:show}" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
<i :class="show?'el-icon-close':'el-icon-setting'" />
</div>
<div class="rightPanel-items">
<slot />
<div class="setting">
<div class="showme">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="handle-button" :style="{'top':buttonTop+'px','background-color':theme}" @click="show=!show">
<i :class="show?'el-icon-close':'el-icon-setting'" />
</div>
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</div>
</div>
@ -77,6 +81,21 @@ export default {
</script>
<style>
.setting {
z-index: 3;
width: 5%;
height: 10%;
right: 0%;
position: absolute;
top: 250px;
}
.showme {
display: none;
}
.setting:hover .showme {
display: block;
}
.showRightPanel {
overflow: hidden;
position: relative;

View File

@ -0,0 +1,173 @@
<template>
<div ref="rightMenu" :class="{show:show}" class="rightMenu-container">
<div class="setting">
<div class="showme">
<!-- <div class="rightMenu-background" /> -->
<div class="rightMenu">
<div class="handle-button" :style="{'top':buttonTop+'%'}" @click="show=!show">
<i :class="show?'el-icon-close':'el-icon-more'" style="color: gray;" />
</div>
<div class="rightMenu-items">
<slot />
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { addClass, removeClass } from '@/utils'
export default {
name: 'Menu',
props: {
clickNotClose: {
default: false,
type: Boolean
},
buttonTop: {
default: 16,
type: Number
}
},
data() {
return {
show: false
}
},
computed: {
theme() {
return this.$store.state.settings.theme
}
},
watch: {
show(value) {
if (value && !this.clickNotClose) {
this.addEventClick()
}
if (value) {
addClass(document.body, 'showrightMenu')
} else {
removeClass(document.body, 'showrightMenu')
}
}
},
mounted() {
this.insertToBody()
},
beforeDestroy() {
const elx = this.$refs.rightMenu
elx.remove()
},
methods: {
addEventClick() {
window.addEventListener('click', this.closeSidebar)
},
closeSidebar(evt) {
const parent = evt.target.closest('.rightMenu')
if (!parent) {
this.show = false
window.removeEventListener('click', this.closeSidebar)
}
},
insertToBody() {
const elx = this.$refs.rightMenu
const body = document.querySelector('body')
body.insertBefore(elx, body.firstChild)
}
}
}
</script>
<style>
.setting {
z-index: 3;
width: 5%;
height: 10%;
right: 0%;
position: absolute;
top: 250px;
}
.showme {
display: block;
}
.setting:hover .showme {
display: block;
}
.showrightMenu {
overflow: visible;
position: relative;
width: calc(100% - 15px);
}
</style>
<style lang="scss" scoped>
.rightMenu-background {
opacity: 0;
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
background: rgba(0, 0, 0, .2);
width: 0;
height: 0;
top: 0;
left: 0;
position: fixed;
z-index: -1;
}
.rightMenu {
background: #fff;
z-index: 3000;
position: fixed;
height: 100vh;
width: 100%;
max-width: 260px;
top: 0px;
left: 0px;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
transform: translate(100%);
z-index: 40000;
left: auto;
right: 0px;
}
.show {
transition: all .3s cubic-bezier(.7, .3, .1, 1);
.rightMenu-background {
z-index: 20000;
opacity: 1;
width: 100%;
height: 100%;
}
.rightMenu {
transform: translate(0);
}
}
.handle-button {
position: absolute;
border: 1px solid #d9d9d9;
background: white;
left: -34px;
border-radius: 6px 0 0 6px !important;
width: 35px;
height: 35px;
pointer-events: auto;
z-index: 0;
cursor: pointer;
pointer-events: auto;
font-size: 0px;
text-align: center;
color: #fff;
line-height: 48px;
i {
font-size: 17px;
line-height: 17px;
}
}
</style>

View File

@ -0,0 +1,20 @@
import moment from 'moment'
/**
*
* @param {number} value
* @param {string} type, Date, DateTime, Time
*/
export function formatDate(value, referenceType = 'DateTime') {
if (typeof value === 'number') {
var dateTime = moment.utc(value)
var format = 'YYYY-MM-DD HH:mm'
if (referenceType === 'Time') {
format = 'HH:mm'
} else if (referenceType === 'Date') {
format = 'YYYY-MM-DD'
}
return dateTime.format(format)
}
return null
}

View File

@ -66,3 +66,5 @@ export function toThousandFilter(num) {
export function uppercaseFirst(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
export * from '@/filters/ADempiere/'

258
src/lang/ADempiere/en.js Normal file
View File

@ -0,0 +1,258 @@
export default {
route: {
dashboard: 'Dashboard',
documentation: 'Documentation',
guide: 'Guide',
forgotPassword: 'Forgot Password?',
userEnrollment: 'Check in',
page401: '401',
page404: '404',
profile: 'Profile',
ProcessActivity: 'Process Activity',
Role: 'Role',
ReportViewer: 'Report Viewer'
},
notifications: {
// simplex
completed: 'Completed',
loading: 'Loading',
succesful: 'Successful',
error: 'Error',
opened: 'Opened',
// search
searching: 'Searching records on the server',
succcessSearch: 'The search has been made',
searchWithOutRecords: 'The search ended without results',
errorSearch: 'The search has not been completed',
// process
processing: 'Processing',
processExecuted: 'Executed, see process activity',
processError: 'Was not executed',
//
emptyValues: 'Parameter(s) empty value',
fieldMandatory: 'The field is mandatory',
requestError: 'Error executing the request',
successChangeRole: 'The role has been changed',
errorChangeRole: 'The role has not been changed',
copySuccessful: 'Copied',
copyUnsuccessful: 'Error, unable to copy',
mandatoryFieldMissing: 'Missing fill in the fields ',
updateFields: 'The record is updated with the field ',
invalidEmailFormat: 'Invalid email format'
},
navbar: {
badge: {
Notifications: 'Notifications',
link: 'go to ProccesActivity'
},
dashboard: 'Dashboard',
github: 'Github',
logOut: 'Log Out',
profile: 'Profile',
theme: 'Theme',
size: 'Global Size'
},
login: {
noValidUser: 'Please enter the correct user name',
noValidPassword: 'The password can not be empty',
invalidLogin: 'User Id or Password invalid',
unexpectedError: 'An unexpected error has occurred, try again',
capsLock: 'Caps lock is On',
title: 'Login Form',
submit: 'Submit',
logIn: 'Login',
name: 'Name',
lastName: 'Last name',
eMail: 'Email',
userName: 'User name',
userNameOrEmail: 'User name or Email',
userEnrollment: 'Enrollment user',
userEnrollmentSuccessful: 'Successful user enrollment, check your email',
passwordResetSendLink: 'The link to reset the password was sent to the email ',
password: 'Password',
passwordNew: 'New password',
passwordConfirm: 'Password confirm',
passwordConfirmNew: 'New password confirm',
any: 'any',
thirdparty: 'Or connect with',
thirdpartyTips: 'Can not be simulated on local, so please combine you own business simulation.',
invalidToken: 'The token used is invalid.',
passwordReset: 'Password Reset',
createPassword: 'Create password',
passwordResetSuccessful: 'Password reset was successful',
passwordAndConfirmNotMatch: 'The new password and your confirmation do not match.'
},
documentation: {
documentation: 'Documentation',
github: 'Github Repository'
},
permission: {
addRole: 'New Role',
editPermission: 'Edit',
roles: 'Your roles',
switchRoles: 'Switch roles',
tips: 'In some cases, using v-permission will have no effect. For example: Element-UI el-tab or el-table-column and other scenes that dynamically render dom. You can only do this with v-if.',
delete: 'Delete',
confirm: 'Confirm',
cancel: 'Cancel'
},
guide: {
description: 'The guide page is useful for some people who entered the project for the first time. You can briefly introduce the features of the project. Demo is based on ',
button: 'Show Guide'
},
components: {
date: {
Today: 'Today',
Yesterday: 'Yesterday',
Week: 'Last week',
LastMonth: 'Last month',
CurrentMonth: 'The current month'
},
documentation: 'Documentation',
binaryButton: 'Upload file',
binaryTip: 'Only files with a size smaller than 500kb',
imageError: 'The image exceeds 2MB and does not comply with the valid formats!',
contextMenuRelations: 'Relations',
contextMenuActions: 'Actions',
contextMenuReferences: 'References',
RunProcess: 'Run',
ChangeParameters: 'Change Parameters',
RunProcessAs: 'Run As',
ExportTo: 'Export to',
contextMenuDownload: 'Download',
contextMenuShareLink: 'Share Link',
contextMenuRefresh: 'Refresh',
contextMennuExport: 'Export Smart Browser',
contextMennuWindowReport: 'Window Report ',
dateStartPlaceholder: 'Start date',
dateEndPlaceholder: 'End date',
timePlaceholder: 'Select time',
dialogCancelButton: 'Cancel',
dialogConfirmButton: 'Confirm',
filterableItems: 'Optional Columns',
fixedleItems: 'Fixed Columns',
resetAllFilters: 'Reset all filters',
switchActiveText: 'Yes',
switchInactiveText: 'Not',
contextFieldTitle: 'Context Information'
},
views: {
SmartBrowser: 'Smart Browser',
Process: 'Process',
Window: 'Window',
Report: 'Report',
Workflow: 'Workflow',
Task: 'Task',
Form: 'Form',
noProcess: 'Not process running',
logs: 'Summary',
log: 'Summary',
seeReport: 'See Report',
summary: 'Summary',
viewsHelp: 'Help',
searchCriteria: 'Search Criteria',
unsupportedSorry: 'Sorry',
unsupportedHeadline: 'This view is currently unavailable',
unsupportedInfo: 'Please check that the view is supported in this version, or click the button below to return to the homepage.',
unsupportedButton: 'Back to dashboard'
},
table: {
ProcessActivity: {
name: 'Name',
zoomIn: 'Zoom in',
description: 'Description',
actions: 'Action',
status: 'Status',
Logs: 'Summary',
summary: 'Summary',
Help: 'Help',
FileName: 'FileName',
Output: 'Output'
},
dataTable: {
search: 'Search',
records: 'Records',
selected: 'Selected',
deleteSelection: 'Delete Selected Records',
advancedQuery: 'Advanced Query',
showOnlyMandatoryColumns: 'Show Only Mandatory Columns',
showAllAvailableColumns: 'Show All Available Columns'
},
recentItems: {
search: 'Filter by name, description or date',
date: 'Date',
name: 'Name',
description: 'Description'
}
},
example: {
warning: 'Creating and editing pages cannot be cached by keep-alive because keep-alive include does not currently support caching based on routes, so it is currently cached based on component name. If you want to achieve a similar caching effect, you can use a browser caching scheme such as localStorage. Or do not use keep-alive include to cache all pages directly. See details'
},
errorLog: {
tips: 'Please click the bug icon in the upper right corner',
description: 'Now the management system are basically the form of the spa, it enhances the user experience, but it also increases the possibility of page problems, a small negligence may lead to the entire page deadlock. Fortunately Vue provides a way to catch handling exceptions, where you can handle errors or report exceptions.',
documentation: 'Document introduction'
},
excel: {
export: 'Export',
selectedExport: 'Export Selected Items',
placeholder: 'Please enter the file name (default excel-list)'
},
zip: {
export: 'Export',
placeholder: 'Please enter the file name (default file)'
},
pdf: {
tips: 'Here we use window.print() to implement the feature of downloading PDF.'
},
theme: {
change: 'Change Theme',
documentation: 'Theme documentation',
tips: 'Tips: It is different from the theme-pick on the navbar is two different skinning methods, each with different application scenarios. Refer to the documentation for details.'
},
tagsView: {
refresh: 'Refresh',
close: 'Close',
closeOthers: 'Close Others',
closeAll: 'Close All',
newRecord: 'New Record',
seeRecord: 'See Record',
advancedQuery: 'Advanced Query'
},
settings: {
title: 'Page style setting',
theme: 'Theme Color',
tagsView: 'Open Tags-View',
fixedHeader: 'Fixed Header',
sidebarLogo: 'Sidebar Logo'
},
profile: {
aboutMe: 'About Me',
recentItems: 'Recent Items',
recentItemsName: 'Name Recent Items',
role: 'Role',
availableRoles: 'Available roles',
currentRole: 'Current role',
clientName: 'Client name',
description: 'Description',
changeRole: 'Change role',
changeLanguage: 'Change language',
changeLanguagePlaceholder: 'Choose a language'
},
window: {
newRecord: 'New Record',
deleteRecord: 'Delete Record',
undoNew: 'Undo New Record'
},
data: {
emtpyTableName: 'Error: Table Name is not defined',
errorGetData: 'Error getting data records',
createRecordSuccessful: 'New record created successfully',
createNewRecord: 'Mode New record',
createRecordError: 'Error creating new record',
deleteRecordSuccessful: 'Record deleted successfully',
deleteRecordError: 'Error deleting record',
selectionRequired: 'You must select a record',
undo: 'Undo'
}
}

233
src/lang/ADempiere/es.js Normal file
View File

@ -0,0 +1,233 @@
export default {
route: {
dashboard: 'Panel de control',
documentation: 'Documentación',
forgotPassword: '¿Olvidó su Contraseña?',
userEnrollment: 'Registrarse',
guide: 'Guía',
page401: '401',
page404: '404',
profile: 'Perfil',
ProcessActivity: 'Actividad de Procesos',
Role: 'Rol',
ReportViewer: 'Visor de Reportes'
},
notifications: {
// simplex
completed: 'Completado',
loading: 'Cargando',
succesful: 'Exitoso',
error: 'Error',
opened: 'Abierto',
// search
searching: 'Buscando registros en el servidor',
succcessSearch: 'La búsqueda se ha realizado',
searchWithOutRecords: 'La búsqueda finalizo sin resultados',
errorSearch: 'La búsqueda no se ha completado.',
// process
processing: 'Procesando',
processExecuted: 'Ejecutado, ver actividad de proceso',
processError: 'No fue ejecutado',
//
emptyValues: 'Parametro(s) con valores vacios',
fieldMandatory: 'El campo es obligatorio',
requestError: 'Error al ejecutar la petición',
successChangeRole: 'El rol se ha cambiado',
errorChangeRole: 'El rol no se ha cambiado',
copySuccessful: 'Copiado',
copyUnsuccessful: 'Error, no se puede copiar',
mandatoryFieldMissing: 'Falta completar los campos ',
updateFields: 'Se actualiza el registro con el campo ',
invalidEmailFormat: 'Formato de correo electronico invalido'
},
navbar: {
badge: {
Notifications: 'Notificaciones',
link: 'ir a actividad de proceso'
},
logOut: 'Salir',
dashboard: 'Panel de control',
github: 'Github',
theme: 'Tema',
size: 'Tamaño global',
profile: 'Perfil'
},
login: {
noValidUser: 'Por favor ingrese el nombre de usuario correcto',
noValidPassword: 'La contraseña no puede estar vacía',
invalidLogin: 'ID de usuario o contraseña no válida',
unexpectedError: 'Se ha producido un error inesperado, intente nuevamente',
capsLock: 'Bloqueo de mayúsculas activado',
title: 'Formulario de acceso',
submit: 'Enviar',
logIn: 'Acceso',
name: 'Nombre',
lastName: 'Apellido',
eMail: 'Correo electronico',
userName: 'Usuario',
userNameOrEmail: 'Usuario o Correo',
userEnrollment: 'Registrar usuario',
userEnrollmentSuccessful: 'Registro de usuario exitoso, verifique su correo electronico',
passwordResetSendLink: 'El enlace para reiniciar la contraseña se envio al correo ',
password: 'Contraseña',
passwordNew: 'Nueva contraseña',
passwordConfirm: 'Confirmar contraseña',
passwordConfirmNew: 'Confirmación de nueva contraseña',
any: 'nada',
thirdparty: 'Conectar con',
thirdpartyTips: 'No se puede simular en local, así que combine su propia simulación de negocios.',
invalidToken: 'El token utilizado es inválido.',
passwordReset: 'Reiniciar contraseña',
createPassword: 'Crear contraseña',
passwordResetSuccessful: 'El reinicio de contraseña se realizo correctamente',
passwordAndConfirmNotMatch: 'La contraseña nueva y su confirmación no coinciden.'
},
documentation: {
documentation: 'Documentación',
github: 'Repositorio Github'
},
permission: {
addRole: 'Nuevo rol',
editPermission: 'Permiso de edición',
roles: 'Tus permisos',
switchRoles: 'Cambiar permisos',
tips: 'En algunos casos, no es adecuado usar v-permission, como el componente Element Tab o el-table-column y otros casos de dom de representación asíncrona que solo se pueden lograr configurando manualmente v-if.',
delete: 'Borrar',
confirm: 'Confirmar',
cancel: 'Cancelar'
},
guide: {
description: 'La página de guía es útil para algunas personas que ingresaron al proyecto por primera vez. Puede introducir brevemente las características del proyecto. Demo se basa en ',
button: 'Ver guía'
},
components: {
date: {
Today: 'Hoy',
Yesterday: 'Ayer',
Week: 'La semana pasada',
LastMonth: 'El mes pasado',
CurrentMonth: 'El mes actual'
},
documentation: 'Documentación',
binaryButton: 'Subir archivo',
binaryTip: 'Solo archivos con un tamaño menor a 500kb',
imageError: 'La imagen excede los 2MB y no cumple con los formato validos!',
contextMenuRelations: 'Relaciones',
contextMenuActions: 'Acciones',
contextMenuReferences: 'Referencias',
contextMenuDownload: 'Descargar',
contextMenuShareLink: 'Compartir Link',
contextMenuRefresh: 'Actualizar',
contextMennuExport: 'Exportar Smart Browser',
contextMennuWindowReport: 'Informe de Ventana',
RunProcess: 'Ejecutar',
ChangeParameters: 'Cambiar Parametros',
RunProcessAs: 'Ejecutar como',
ExportTo: 'Exportar a',
dateStartPlaceholder: 'Fecha inicial',
dateEndPlaceholder: 'Fecha final',
timePlaceholder: 'Seleccione tiempo',
dialogCancelButton: 'Cancelar',
dialogConfirmButton: 'Confirmar',
filterableItems: 'Columnas Opcionales',
fixedleItems: 'Columnas Fijas',
resetAllFilters: 'Reiniciar todos los filtros',
switchActiveText: 'Si',
switchInactiveText: 'No',
contextFieldTitle: 'Información de Contexto'
},
views: {
SmartBrowser: 'Consulta Inteligente',
Process: 'Proceso',
Window: 'Ventana',
Report: 'Reporte',
Workflow: 'Flujo de Trabajo',
Task: 'Tarea',
Form: 'Formulario',
noProcess: 'No hay procesos en ejecución',
logs: 'Resumen',
log: 'Bitacora',
seeReport: 'Ver Reporte',
summary: 'Resumen',
viewsHelp: 'Ayuda',
searchCriteria: 'Criterio de Búsqueda',
unsupportedSorry: 'Lo sentimos',
unsupportedHeadline: 'Esta vista no está disponible actualmente',
unsupportedInfo: 'Verifique que la vista sea compatible con esta versión, o haga clic en el botón a continuación para volver a la página de inicio.',
unsupportedButton: 'Volver al Panel de control'
},
table: {
ProcessActivity: {
Name: 'Nombre',
zoomIn: 'Acercar',
Description: 'Descripción',
FileName: 'Nombre de Archivo',
Output: 'Salida',
Action: 'Acción',
Status: 'Estado',
Logs: 'Resumen',
Summary: 'Resumen',
Help: 'Ayuda'
},
dataTable: {
search: 'Buscar',
records: 'Registros',
selected: 'Seleccionados',
deleteSelection: 'Eliminar Registros Seleccionados',
advancedQuery: 'Consulta Avanzada',
showOnlyMandatoryColumns: 'Mostrar Solo Columnas Obligatorias',
showAllAvailableColumns: 'Mostrar Todas Columnas Disponibles'
},
recentItems: {
search: 'Filtrar por nombre, descripción o fecha',
date: 'Fecha',
name: 'Nombre',
description: 'Descripción'
}
},
tagsView: {
refresh: 'Actualizar',
close: 'Cerrar',
closeOthers: 'Cerrar otros',
closeAll: 'Cerrar todos',
newRecord: 'Nuevo Registro',
seeRecord: 'Ver Registro',
advancedQuery: 'Consulta Avanzada'
},
settings: {
title: 'Configuración de estilo de página',
theme: 'Color del tema',
tagsView: 'Habilitar Tags-View',
fixedHeader: 'Encabezado fijo',
sidebarLogo: 'Logotipo de la barra lateral'
},
profile: {
aboutMe: 'Sobre Mi',
recentItems: 'Artículos Recientes',
recentItemsName: 'Nombre Ítems Recientes',
role: 'Rol',
availableRoles: 'Roles disponibles',
currentRole: 'Rol actual',
clientName: 'Nombre del cliente',
description: 'Descripción',
changeRole: 'Cambiar rol',
changeLanguage: 'Cambiar idioma',
changeLanguagePlaceholder: 'Elija un idioma'
},
window: {
newRecord: 'Nuevo Registro',
deleteRecord: 'Eliminar Registro',
undoNew: 'Descartar Nuevo Registro'
},
data: {
emtpyTableName: 'Error: El nombre de la tabla no esta definida',
errorGetData: 'Error obteniendo los datos de registro',
createRecordSuccessful: 'Nuevo registro creado con exito',
createNewRecord: 'Modo nuevo registro',
createRecordError: 'Error al crear nuevo registro',
deleteRecordSuccessful: 'Registro eliminado exitosamente',
deleteRecordError: 'Error al eliminar el regitro',
selectionRequired: 'Debe seleccionar un registro',
undo: 'Deshacer'
}
}

View File

@ -9,13 +9,43 @@ import enLocale from './en'
import zhLocale from './zh'
import esLocale from './es'
import jaLocale from './ja'
import esADempiere from './ADempiere/es'
import enADempiere from './ADempiere/en'
Vue.use(VueI18n)
const dateTimeFormats = {
'en': {
long: {
year: 'numeric',
month: 'long',
day: '2-digit',
weekday: 'long',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}
},
'es': {
long: {
year: 'numeric',
month: 'long',
day: '2-digit',
weekday: 'long',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: true
}
}
}
const messages = {
en: {
...enLocale,
...elementEnLocale
...elementEnLocale,
...enADempiere
},
zh: {
...zhLocale,
@ -23,7 +53,8 @@ const messages = {
},
es: {
...esLocale,
...elementEsLocale
...elementEsLocale,
...esADempiere
},
ja: {
...jaLocale,
@ -48,8 +79,10 @@ const i18n = new VueI18n({
// set locale
// options: en | zh | es
locale: getLanguage(),
fallbackLocale: 'en',
// set locale messages
messages
messages,
dateTimeFormats
})
export default i18n

View File

@ -1,5 +1,5 @@
<template>
<section class="app-main">
<section id="appMain" class="app-main">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
<router-view :key="key" />
@ -16,7 +16,7 @@ export default {
return this.$store.state.tagsView.cachedViews
},
key() {
return this.$route.fullPath
return this.$route.path
}
}
}

View File

@ -1,26 +1,12 @@
<template>
<div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" />
<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />
<error-log class="errLog-container right-menu-item hover-effect" />
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
<lang-select class="right-menu-item hover-effect" />
</template>
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click">
<el-button v-if="isMenuMobile && isMobile" type="text" icon="el-icon-close" style="padding-top: 13px; color: #000000;font-size: 121%;font-weight: 615!important;" @click="isMenuOption()" />
<breadcrumb v-show="!isMenuMobile || device!=='mobile'" id="breadcrumb-container" class="breadcrumb-container" :style="isMobile ? { width: '40%'} : { width: 'auto'} " />
<div v-show="isMenuMobile && isMobile" style="display: inline-flex; float: right;">
<search id="header-search" class="right-menu-item" style="padding-top: 10px;" />
<badge style="padding-top: 6px;" />
<!-- <el-dropdown class="avatar-container right-menu-item hover-effect" trigger="hover">
<div class="avatar-wrapper">
<img :src="avatar+'?imageView2/1/w/80/h/80'" class="user-avatar">
<i class="el-icon-caret-bottom" />
@ -48,12 +34,42 @@
<span style="display:block;" @click="logout">{{ $t('navbar.logOut') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</el-dropdown> -->
</div>
<div class="right-menu">
<template v-if="device!=='mobile'">
<search id="header-search" class="right-menu-item" />
<badge />
<error-log class="errLog-container right-menu-item hover-effect" />
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip :content="$t('navbar.size')" effect="dark" placement="bottom">
<size-select id="size-select" class="right-menu-item hover-effect" />
</el-tooltip>
<lang-select class="right-menu-item hover-effect" />
</template>
<el-button v-show="!isMenuMobile && isMobile" type="text" icon="el-icon-more" @click="isMenuOption()" />
<el-popover
placement="bottom"
width="245"
trigger="click"
>
<div>
<user-card :user="user" />
<el-button type="text" style="float: left;" @click="handleClick">{{ $t('navbar.profile') }}</el-button>
<el-button type="text" style="float: right;" @click="logout">{{ $t('navbar.logOut') }}</el-button>
</div>
<el-button slot="reference" type="text" style="padding-top: 0px;"><img :src="avatar+'?imageView2/1/w/40/h/40'" class="user-avatar"></el-button>
</el-popover>
</div>
</div>
</template>
<script>
import UserCard from '@/views/profile/components/Profile'
import { mapGetters } from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
@ -62,18 +78,30 @@ import Screenfull from '@/components/Screenfull'
import SizeSelect from '@/components/SizeSelect'
import LangSelect from '@/components/LangSelect'
import Search from '@/components/HeaderSearch'
import Badge from '@/components/ADempiere/Badge'
export default {
components: {
Breadcrumb,
Badge,
Hamburger,
ErrorLog,
Screenfull,
SizeSelect,
LangSelect,
UserCard,
Search
},
data() {
return {
user: {},
isMenuMobile: false
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
...mapGetters([
'sidebar',
'avatar',
@ -81,18 +109,37 @@ export default {
])
},
methods: {
handleOpen(key, keyPath) {
console.log(key, keyPath)
},
handleClose(key, keyPath) {
console.log(key, keyPath)
},
isMenuOption() {
this.isMenuMobile = !this.isMenuMobile
},
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
async logout() {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
},
handleClick() {
this.$router.push({ name: 'Profile' })
}
}
}
</script>
<style lang="scss" scoped>
.el-dropdown {
display: inline-block;
position: relative;
color: #606266;
font-size: 14px;
width: 50px;
}
.navbar {
height: 50px;
overflow: hidden;
@ -124,6 +171,7 @@ export default {
.right-menu {
float: right;
display: flex;
height: 100%;
line-height: 50px;

View File

@ -12,23 +12,23 @@ import { isExternal } from '@/utils/validate'
export default {
props: {
to: {
type: String,
type: Object,
required: true
}
},
methods: {
linkProps(url) {
if (isExternal(url)) {
linkProps(route) {
if (isExternal(route.path)) {
return {
is: 'a',
href: url,
href: route.path,
target: '_blank',
rel: 'noopener'
}
}
return {
is: 'router-link',
to: url
to: { name: route.name }
}
}
}

View File

@ -1,14 +1,23 @@
<template>
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }} </h1>
</router-link>
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 class="sidebar-title">{{ title }} </h1>
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link sidebar-logo-link-close" to="/">
<el-tooltip placement="right">
<div slot="content">{{ getRol.name }} | {{ getRol.clientName }}</div>
<img v-if="logo" :src="logo" class="sidebar-logo">
<h1 v-else class="sidebar-title">{{ title }} </h1>
</el-tooltip>
</router-link>
<div v-else key="expand" class="sidebar-logo-link">
<img v-if="logo" :src="logo" class="sidebar-logo" @click="dashboard()">
<h1 class="sidebar-title" @click="dashboard()">{{ title }}</h1><br>
<el-tooltip placement="right">
<div slot="content">{{ getRol.name }} | {{ getRol.clientName }}</div>
<p class="sidebar-sub-title" @click="profile()">
{{ getRol.name }} | {{ getRol.clientName }}
</p>
</el-tooltip>
</div>
</transition>
</div>
</template>
@ -24,8 +33,23 @@ export default {
},
data() {
return {
title: 'Vue Element Admin',
logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
// title: 'Vue Element Admin',
title: 'ADempiere Vue',
// logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
logo: 'https://avatars1.githubusercontent.com/u/1263359?s=200&v=4?imageView2/1/w/80/h/80'
}
},
computed: {
getRol() {
return this.$store.getters['user/getRol']
}
},
methods: {
profile() {
this.$router.push({ path: '/profile/index?' })
},
dashboard() {
this.$router.push({ path: '/' })
}
}
}
@ -45,7 +69,7 @@ export default {
position: relative;
width: 100%;
height: 50px;
line-height: 50px;
// line-height: 50px;
background: #2b2f3a;
text-align: center;
overflow: hidden;
@ -59,18 +83,36 @@ export default {
height: 32px;
vertical-align: middle;
margin-right: 12px;
cursor: pointer;
}
& .sidebar-title {
display: inline-block;
cursor: pointer;
margin: 0;
color: #fff;
font-weight: 600;
line-height: 50px;
// line-height: 50px;
font-size: 14px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
& .sidebar-sub-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
// display: inline-block;
cursor: pointer;
margin: 0;
color: #fff;
font-size: 12px;
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
vertical-align: middle;
}
}
& .sidebar-logo-link-close {
line-height: 50px;
}
&.collapse {

View File

@ -1,7 +1,7 @@
<template>
<div v-if="!item.hidden" class="menu-wrapper">
<template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
<app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
<app-link v-if="onlyOneChild.meta" :to="onlyOneChild">
<el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
<item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="generateTitle(onlyOneChild.meta.title)" />
</el-menu-item>
@ -31,6 +31,7 @@ import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'
import { mapGetters } from 'vuex'
export default {
name: 'SidebarItem',
@ -57,6 +58,17 @@ export default {
this.onlyOneChild = null
return {}
},
computed: {
...mapGetters([
'sidebar'
]),
isCollapse() {
if (this.sidebar.opened) {
return 'right'
}
return 'top'
}
},
methods: {
hasOneShowingChild(children = [], parent) {
const showingChildren = children.filter(item => {

View File

@ -1,20 +1,44 @@
<template>
<div id="tags-view-container" class="tags-view-container">
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="isActive(tag)?'active':''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
@click.middle.native="closeSelectedTag(tag)"
@contextmenu.prevent.native="openMenu(tag,$event)"
<draggable
v-if="!isMobile"
:list="visitedViews"
v-bind="$attrs"
:set-data="setData"
style="display: flex;"
>
{{ generateTitle(tag.title) }}
<span v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="isActive(tag)?'active':''"
:to="{ name: tag.name, path: tag.path, query: tag.query, fullPath: tag.fullPath, params: tag.params }"
tag="span"
class="tags-view-item"
@click.middle.native="closeSelectedTag(tag)"
@contextmenu.prevent.native="openMenu(tag,$event)"
>
<div class="tag-title">{{ generateTitle(tag.title) }}</div>
<div v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</draggable>
<template v-else>
<router-link
v-for="tag in visitedViews"
ref="tag"
:key="tag.path"
:class="isActive(tag)?'active':''"
:to="{ name: tag.name, path: tag.path, query: tag.query, fullPath: tag.fullPath, params: tag.params }"
tag="span"
class="tags-view-item"
@click.middle.native="closeSelectedTag(tag)"
@contextmenu.prevent.native="openMenu(tag,$event)"
>
<div class="tag-title">{{ generateTitle(tag.title) }}</div>
<div v-if="!tag.meta.affix" class="el-icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</template>
</scroll-pane>
<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">
@ -38,9 +62,10 @@
import ScrollPane from './ScrollPane'
import { generateTitle } from '@/utils/i18n'
import path from 'path'
import draggable from 'vuedraggable'
export default {
components: { ScrollPane },
components: { ScrollPane, draggable },
data() {
return {
visible: false,
@ -51,6 +76,9 @@ export default {
}
},
computed: {
isMobile() {
return this.$store.state.app.device === 'mobile'
},
visitedViews() {
return this.$store.state.tagsView.visitedViews
},
@ -78,7 +106,15 @@ export default {
methods: {
generateTitle, // generateTitle by vue-i18n
isActive(route) {
return route.path === this.$route.path
if (route.name === 'Report Viewer') {
if (route.params.processId === this.$route.params.processId) {
return route.params.processId === this.$route.params.processId
} else {
return route.path === this.$route.path
}
} else {
return route.name === this.$route.name
}
},
filterAffixTags(routes, basePath = '/') {
let tags = []
@ -121,7 +157,15 @@ export default {
const tags = this.$refs.tag
this.$nextTick(() => {
for (const tag of tags) {
if (tag.to.path === this.$route.path) {
if (this.$route.name === 'Report Viewer') {
if (this.$route.params && tag.to.params && tag.to.params.processId === this.$route.params.processId) {
this.$refs.scrollPane.moveToTarget(tag)
}
}
if (tag.to.name === this.$route.name) {
if (tag.to.query && tag.to.query.action && tag.to.query.action === this.$route.query.action) {
tag.to.params.isReadParameters = false
}
this.$refs.scrollPane.moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== this.$route.fullPath) {
@ -143,6 +187,22 @@ export default {
})
},
closeSelectedTag(view) {
if (view.meta && view.meta.uuid && view.meta.type) {
this.$store.dispatch('resetPanelToNew', {
parentUuid: view.meta.type !== 'window' ? undefined : view.meta.uuid,
containerUuid: view.meta.type === 'window' ? view.meta.tabUuid : view.meta.uuid,
panelType: view.meta.type,
isNewRecord: false
})
if (view.meta.type === 'window' || view.meta.type === 'browser') {
this.$store.dispatch('deleteRecordContainer', {
viewUuid: view.meta.uuid
})
if (view.meta.type === 'window') {
this.$store.dispatch('setWindowOldRoute')
}
}
}
this.$store.dispatch('tagsView/delView', view).then(({ visitedViews }) => {
if (this.isActive(view)) {
this.toLastView(visitedViews, view)
@ -197,6 +257,11 @@ export default {
},
closeMenu() {
this.visible = false
},
setData(dataTransfer) {
// to avoid Firefox bug
// Detail see : https://github.com/RubaXa/Sortable/issues/1012
dataTransfer.setData('Text', '')
}
}
}
@ -210,33 +275,41 @@ export default {
border-bottom: 1px solid #d8dce5;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
.tags-view-wrapper {
width: 100%;
.tags-view-item {
display: inline-block;
position: relative;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex:none;
max-width: 32%;
cursor: pointer;
height: 26px;
line-height: 26px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
padding: 0 7px;
font-size: 12px;
margin-left: 5px;
margin-top: 4px;
div.tag-title{
width: -webkit-fill-available;
overflow: hidden;
text-overflow: ellipsis;
}
&:first-of-type {
margin-left: 15px;
}
&:last-of-type {
margin-right: 15px;
}
&.active {
background-color: #42b983;
color: #fff;
border-color: #42b983;
&::before {
content: '';
align-self: center;
background: #fff;
display: inline-block;
min-width: 8px;
width: 8px;
height: 8px;
border-radius: 50%;
@ -275,6 +348,8 @@ export default {
.tags-view-wrapper {
.tags-view-item {
.el-icon-close {
align-self: center;
min-width: 16px;
width: 16px;
height: 16px;
vertical-align: 2px;

View File

@ -17,7 +17,7 @@
<script>
import RightPanel from '@/components/RightPanel'
import { AppMain, Navbar, Settings, Sidebar, TagsView } from './components'
import { AppMain, Navbar, Sidebar, Settings, TagsView } from './components'
import ResizeMixin from './mixin/ResizeHandler'
import { mapState } from 'vuex'

View File

@ -6,6 +6,7 @@ import 'normalize.css/normalize.css' // a modern alternative to CSS resets
import Element from 'element-ui'
import './styles/element-variables.scss'
import VueSplit from 'vue-split-panel'
import '@/styles/index.scss' // global css
@ -19,6 +20,7 @@ import './permission' // permission control
import './utils/error-log' // error log
import * as filters from './filters' // global filters
import * as globalMethods from '@/utils/ADempiere/globalMethods' // global methods
/**
* If you don't want to use mock-server
@ -32,7 +34,7 @@ import { mockXHR } from '../mock'
if (process.env.NODE_ENV === 'production') {
mockXHR()
}
Vue.use(VueSplit)
Vue.use(Element, {
size: Cookies.get('size') || 'medium', // set element-ui default size
i18n: (key, value) => i18n.t(key, value)
@ -43,6 +45,11 @@ Object.keys(filters).forEach(key => {
Vue.filter(key, filters[key])
})
// register global utility methods
Object.keys(globalMethods).forEach(key => {
Vue.prototype[key] = globalMethods[key]
})
Vue.config.productionTip = false
new Vue({

View File

@ -8,7 +8,7 @@ import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
const whiteList = ['/login', '/userEnrollment', '/createPassword', '/forgotPassword', '/passwordReset', '/auth-redirect'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar
@ -27,17 +27,18 @@ router.beforeEach(async(to, from, next) => {
NProgress.done()
} else {
// determine whether the user has obtained his permission roles through getInfo
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
// const hasRoles = store.getters.roles && store.getters.roles.length > 0
// indicate if session active
if (store.getters['user/getIsSession']) {
next()
} else {
try {
// get user info
// note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
const { roles } = await store.dispatch('user/getInfo')
// get user info from server
await store.dispatch('user/getInfo')
// generate accessible routes map based on roles
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
const accessRoutes = await store.dispatch('permission/generateRoutes')
// dynamically add accessible routes
router.addRoutes(accessRoutes)
@ -56,8 +57,7 @@ router.beforeEach(async(to, from, next) => {
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
if (whiteList.includes(to.path)) {
// in the free login whitelist, go directly
next()
} else {

View File

@ -70,28 +70,53 @@ export const constantRoutes = [
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '/userEnrollment',
component: () => import('@/views/login/userEnrollment'),
hidden: true
},
{
path: '/forgotPassword',
component: () => import('@/views/login/forgotPassword'),
hidden: true
},
{
path: '/passwordReset',
name: 'passwordReset',
component: () => import('@/views/login/setPassword'),
hidden: true
},
{
path: '/createPassword',
name: 'createPassword',
component: () => import('@/views/login/setPassword'),
hidden: true
},
{
path: '',
component: Layout,
redirect: 'dashboard',
meta: { title: 'dashboard', icon: 'dashboard', affix: true, breadcrumb: false },
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'dashboard', icon: 'dashboard', affix: true }
meta: { title: 'dashboard', icon: 'dashboard', affix: true, breadcrumb: false, isIndex: true }
}
]
},
{
path: '/documentation',
component: Layout,
redirect: '/documentation/index',
meta: { title: 'documentation', icon: 'documentation', affix: true, breadcrumb: false },
children: [
{
path: 'index',
component: () => import('@/views/documentation/index'),
name: 'Documentation',
meta: { title: 'documentation', icon: 'documentation', affix: true }
meta: { title: 'documentation', icon: 'documentation', affix: true, isIndex: true }
}
]
},
@ -99,12 +124,13 @@ export const constantRoutes = [
path: '/guide',
component: Layout,
redirect: '/guide/index',
meta: { title: 'guide', icon: 'guide', noCache: true, breadcrumb: false },
children: [
{
path: 'index',
component: () => import('@/views/guide/index'),
name: 'Guide',
meta: { title: 'guide', icon: 'guide', noCache: true }
meta: { title: 'guide', icon: 'guide', noCache: true, isIndex: true }
}
]
},

View File

@ -0,0 +1,206 @@
import { getMenu } from '@/api/user'
import { getToken } from '@/utils/auth'
/* Layout */
import Layout from '@/layout'
// Get Menu from server
export function loadMainMenu() {
return getMenu(getToken()).then(menu => {
const asyncRouterMap = [
{
path: '*',
redirect: '/404',
hidden: true
},
{
path: '/ProcessActivity',
component: Layout,
meta: { title: 'ProcessActivity', icon: 'tree-table', noCache: true, breadcrumb: false },
redirect: '/ProcessActivity/index',
children: [
{
path: 'index',
component: () => import('@/views/ADempiere/ProcessActivity'),
name: 'ProcessActivity',
meta: { title: 'ProcessActivity', icon: 'tree-table', noCache: true, isIndex: true }
}
]
},
{
path: '/report-viewer',
component: Layout,
hidden: true,
redirect: 'report-viewer/:processId/:instanceUuid/:fileName',
children: [
{
path: ':processId/:instanceUuid/:fileName',
component: () => import('@/views/ADempiere/ReportViewer'),
name: 'Report Viewer',
meta: {
title: 'ReportViewer'
}
}
]
}
]
menu.getChildsList().forEach(menu => {
const optionMenu = getRouteFromMenuItem(menu)
if (menu.getIssummary()) {
menu.getChildsList().forEach((menu, index) => {
optionMenu.children.push(getChildFromAction(menu, index = 0))
optionMenu.children[0].meta.childs.push(getChildFromAction(menu, index = 0))
optionMenu.meta.childs.push(getChildFromAction(menu, index = 0))
})
} else {
optionMenu.children.push(getChildFromAction(menu))
optionMenu.meta.childs.push(getChildFromAction(menu))
}
asyncRouterMap.push(optionMenu)
})
return asyncRouterMap
}).catch(error => {
console.log('Error with Login: ' + error)
})
}
// Get Only Child
function getChildFromAction(menu, index) {
const action = menu.getAction()
var actionAttributes = convertAction(action)
var routeIdentifier = actionAttributes.name + '/' + menu.getId()
let selectedComponent
if (action === 'W') {
selectedComponent = () => import('@/views/ADempiere/Window')
routeIdentifier = actionAttributes.name + '/' + menu.getId()
} else if (action === 'S') {
selectedComponent = () => import('@/views/ADempiere/Browser')
} else if (action === 'P' || action === 'R') {
selectedComponent = () => import('@/views/ADempiere/Process')
} else if (action === 'B' || action === 'F' || action === 'T' || action === 'X') {
selectedComponent = () => import('@/views/ADempiere/Unsupported')
} else {
selectedComponent = () => import('@/views/ADempiere/Summary')
routeIdentifier = '/' + menu.getId()
}
var option = {
path: routeIdentifier,
component: selectedComponent,
name: menu.getUuid(),
hidden: index > 0,
meta: {
isIndex: actionAttributes.isIndex,
title: menu.getName(),
description: menu.getDescription(),
uuid: menu.getReferenceuuid(),
tabUuid: '',
type: actionAttributes.name,
parentUuid: menu.getParentuuid(),
icon: actionAttributes.icon,
alwaysShow: true,
noCache: false,
childs: []
}
}
if (option.meta.type === 'summary') {
option['children'] = []
menu.getChildsList().forEach((child, index) => {
option.children.push(getChildFromAction(child, index = 1))
option.meta.childs.push(getChildFromAction(child, index = 1))
})
}
return option
}
// Convert menu item from server to Route
function getRouteFromMenuItem(menu) {
const action = menu.getAction()
var actionAttributes = convertAction(action)
var optionMenu = []
optionMenu = {
path: '/' + menu.getId(),
redirect: '/' + menu.getId() + '/index',
component: Layout,
name: menu.getUuid(),
meta: {
title: menu.getName(),
description: menu.getDescription(),
type: actionAttributes.name,
icon: actionAttributes.icon,
noCache: true,
childs: []
},
children: [
{
path: 'index',
component: () => import('@/views/ADempiere/Summary'),
name: menu.getUuid() + '-index',
hidden: true,
meta: {
isIndex: actionAttributes.isIndex,
parentUuid: menu.getUuid(),
title: menu.getName(),
description: menu.getDescription(),
type: actionAttributes.name,
icon: actionAttributes.icon,
noCache: true,
breadcrumb: false,
childs: []
}
}
]
}
return optionMenu
}
// Convert action to action name for route
function convertAction(action) {
var actionAttributes = {
name: '',
icon: '',
hidden: false,
isIndex: false
}
switch (action) {
case 'B':
actionAttributes.name = 'workbech'
actionAttributes.icon = 'peoples'
break
case 'F':
actionAttributes.name = 'workflow'
actionAttributes.icon = 'example'
break
case 'P':
actionAttributes.name = 'process'
actionAttributes.icon = 'component'
break
case 'R':
actionAttributes.name = 'report'
actionAttributes.icon = 'skill'
break
case 'S':
actionAttributes.name = 'browser'
actionAttributes.icon = 'search'
break
case 'T':
actionAttributes.name = 'task'
actionAttributes.icon = 'size'
break
case 'W':
actionAttributes.name = 'window'
actionAttributes.icon = 'tab'
break
case 'X':
actionAttributes.name = 'form'
actionAttributes.icon = 'form'
break
default:
actionAttributes.name = 'summary'
actionAttributes.icon = 'nested'
// actionAttributes.hidden = true
actionAttributes.isIndex = true
break
}
return actionAttributes
}

View File

@ -1,5 +1,5 @@
module.exports = {
title: 'Vue Element Admin',
title: 'Adempiere Vue',
/**
* @type {boolean} true | false
@ -23,7 +23,7 @@ module.exports = {
* @type {boolean} true | false
* @description Whether show the logo in sidebar
*/
sidebarLogo: false,
sidebarLogo: true,
/**
* @type {string | array} 'production' | ['production', 'development']

View File

@ -2,6 +2,7 @@ const getters = {
sidebar: state => state.app.sidebar,
language: state => state.app.language,
size: state => state.app.size,
toggleSideBar: state => state.app.sidebar.opened,
device: state => state.app.device,
visitedViews: state => state.tagsView.visitedViews,
cachedViews: state => state.tagsView.cachedViews,
@ -9,6 +10,7 @@ const getters = {
avatar: state => state.user.avatar,
name: state => state.user.name,
introduction: state => state.user.introduction,
currentRole: state => state.user.currentRole,
roles: state => state.user.roles,
permission_routes: state => state.permission.routes,
errorLogs: state => state.errorLog.logs

View File

@ -5,13 +5,14 @@ import getters from './getters'
Vue.use(Vuex)
// https://webpack.js.org/guides/dependency-management/#requirecontext
const modulesFiles = require.context('./modules', false, /\.js$/)
const modulesFiles = require.context('./modules', true, /\.js$/)
// you do not need `import app from './modules/app'`
// it will auto require all vuex module from modules file
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
// set './app.js' => 'app'
const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
var moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
moduleName = moduleName.substring(moduleName.indexOf('/') + 1)
const value = modulesFiles(modulePath)
modules[moduleName] = value.default
return modules

View File

@ -0,0 +1,182 @@
import { getBrowser as getBrowserMetadata } from '@/api/ADempiere/dictionary'
import { convertField, isEmptyValue, showMessage } from '@/utils/ADempiere'
import router from '@/router'
import language from '@/lang'
const browser = {
state: {
browser: []
},
mutations: {
addBrowser(state, payload) {
state.browser.push(payload)
},
dictionaryResetCacheBrowser(state) {
state.browser = []
},
changeShowedCriteriaBrowser(state, payload) {
payload.browser.isShowedCriteria = payload.isShowedCriteria
}
},
actions: {
getBrowserFromServer: ({ commit, dispatch }, parameters) => {
return new Promise((resolve, reject) => {
var browserUuid = parameters.containerUuid
getBrowserMetadata(browserUuid)
.then(response => {
const panelType = 'browser'
var fieldsList = response.getFieldsList()
const query = response.getQuery()
const whereClause = response.getWhereclause()
const additionalAttributes = {
browserUuid: response.getUuid(),
browserId: response.getId(),
containerUuid: response.getUuid(),
panelType: panelType
}
// Convert from gRPC
var fieldsRangeList = []
var isMandatoryParams = false
fieldsList = fieldsList.map((fieldItem, index) => {
const someAttributes = {
...additionalAttributes,
fieldListIndex: index
}
var field = convertField(fieldItem, someAttributes)
// Add new field if is range number
if (field.isRange && field.componentPath === 'NumberBase') {
var fieldRange = convertField(fieldItem, someAttributes, true)
if (!isEmptyValue(fieldRange.value)) {
fieldRange.isShowedFromUser = true
}
fieldsRangeList.push(fieldRange)
}
if ((query.includes(`@${field.columnName}@`) ||
query.includes(`@${field.columnName}_To@`) ||
whereClause.includes(`@${field.columnName}@`) ||
whereClause.includes(`@${field.columnName}_To@`)) &&
field.isQueryCriteria) {
field.isMandatory = true
field.isMandatoryFromLogic = true
field.isShowedFromUser = true
}
// Only isQueryCriteria fields, displayed in main panel
if (field.isQueryCriteria && !isEmptyValue(field.value) && String(field.value) !== '-1') {
field.isShowedFromUser = true
}
// TODO: Evaluate if not change when iterate
isMandatoryParams = field.isMandatory
return field
})
fieldsList = fieldsList.concat(fieldsRangeList)
// Get dependent fields
fieldsList
.filter(field => field.parentFieldsList && field.isActive)
.forEach((field, index, list) => {
field.parentFieldsList.forEach(parentColumnName => {
var parentField = list.find(parentField => {
return parentField.columnName === parentColumnName && parentColumnName !== field.columnName
})
if (parentField) {
parentField.dependentFieldsList.push(field.columnName)
}
})
})
// Panel for save on store
const newBrowser = {
id: response.getId(),
uuid: response.getUuid(),
containerUuid: response.getUuid(),
value: response.getValue(),
name: response.getName(),
description: response.getDescription(),
help: response.getHelp(),
// sql query
query: query,
whereClause: whereClause,
orderByClause: response.getOrderbyclause(),
//
isUpdateable: response.getIsupdateable(),
isDeleteable: response.getIsdeleteable(),
isSelectedByDefault: response.getIsselectedbydefault(),
isCollapsibleByDefault: response.getIscollapsiblebydefault(),
isExecutedQueryByDefault: response.getIsexecutedquerybydefault(),
isShowTotal: response.getIsshowtotal(),
isActive: response.getIsactive(),
viewUuid: response.getViewuuid(),
fieldList: fieldsList,
panelType: panelType,
// app attributes
isMandatoryParams: isMandatoryParams,
isShowedCriteria: Boolean(fieldsList.length && isMandatoryParams)
}
// Convert from gRPC process list
const process = response.getProcess()
var actions = []
if (process) {
actions.push({
name: process.getName(),
type: 'process',
uuid: process.getUuid(),
description: process.getDescription(),
help: process.getHelp(),
isReport: process.getIsreport(),
accessLevel: process.getAccesslevel(),
showHelp: process.getShowhelp(),
isDirectPrint: process.getIsdirectprint()
})
// TODO: convert gRPC attributes from response.getProcess() to object
// Add process asociate in store
// var processStore = rootGetters.getProcess(process.getUuid())
// if (processStore === undefined) {
// dispatch('getProcessFromServer', process.getUuid())
// }
}
dispatch('addPanel', newBrowser)
commit('addBrowser', newBrowser)
// Add process menu
dispatch('setContextMenu', {
containerUuid: response.getUuid(),
relations: [],
actions: actions,
references: []
})
resolve(newBrowser)
})
.catch(error => {
router.push({ path: '/dashboard' })
dispatch('tagsView/delView', parameters.routeToDelete)
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Dictionary Browser - Error ' + error.code + ': ' + error.message)
reject(error)
})
})
},
changeShowedCriteriaBrowser: ({ commit, getters }, { containerUuid, isShowedCriteria }) => {
commit('changeShowedCriteriaBrowser', {
browser: getters.getBrowser(containerUuid),
isShowedCriteria: isShowedCriteria
})
}
},
getters: {
getBrowser: (state) => (browserUuid) => {
return state.browser.find(
item => item.uuid === browserUuid
)
}
}
}
export default browser

View File

@ -0,0 +1,120 @@
import { getBrowserSearch } from '@/api/ADempiere/data'
import { convertValuesMapToObject, isEmptyValue, parseContext, showMessage } from '@/utils/ADempiere'
import language from '@/lang'
const browserControl = {
actions: {
/**
* Search with query criteria
* @param {string} containerUuid, browser to search record data
* @param {boolean} isClearSelection, clear selection after search
*/
getBrowserSearch({ dispatch, rootGetters }, parameters) {
const { containerUuid, isClearSelection = false } = parameters
showMessage({
title: language.t('notifications.loading'),
message: language.t('notifications.searching'),
type: 'info'
})
const allData = rootGetters.getDataRecordAndSelection(containerUuid)
// deletes the data from the container to replace it and to report the searches in the table
dispatch('deleteRecordContainer', {
viewUuid: containerUuid
})
const browser = rootGetters.getBrowser(containerUuid)
// parameters isQueryCriteria
const finalParameters = rootGetters.getParametersToServer({
containerUuid: containerUuid,
fieldList: browser.fieldList
})
var parsedQuery = browser.query
if (!isEmptyValue(parsedQuery) && parsedQuery.includes('@')) {
parsedQuery = parseContext({
containerUuid: containerUuid,
value: parsedQuery
}, true)
}
var parsedWhereClause = browser.whereClause
if (!isEmptyValue(parsedWhereClause) && parsedWhereClause.includes('@')) {
parsedWhereClause = parseContext({
containerUuid: containerUuid,
value: parsedWhereClause
}, true)
}
var nextPageToken
if (!isEmptyValue(allData.nextPageToken)) {
nextPageToken = allData.nextPageToken + '-' + allData.pageNumber
}
// Add validation compare browserSearchQueryParameters
return getBrowserSearch({
uuid: containerUuid,
query: parsedQuery,
whereClause: parsedWhereClause,
orderByClause: browser.orderByClause,
parameters: finalParameters,
nextPageToken: nextPageToken
})
.then(response => {
const recordList = response.getRecordsList()
const record = recordList.map(itemRecord => {
var values = convertValuesMapToObject(itemRecord.getValuesMap())
// datatables attribute
values.isNew = false
values.isEdit = false
values.isSelected = false
values.isReadOnlyFromRow = false
return values
})
var selection = allData.selection
if (isClearSelection) {
selection = []
}
var token = response.getNextPageToken()
if (token !== undefined) {
token = token.slice(0, -2)
}
dispatch('setRecordSelection', {
containerUuid: containerUuid,
record: record,
pageNumber: rootGetters.getPageNumber(containerUuid),
selection: selection,
recordCount: response.getRecordcount(),
nextPageToken: token
})
showMessage({
title: language.t('notifications.succesful'),
message: language.t('notifications.succcessSearch'),
type: 'success'
})
return record
})
.catch(error => {
// Set default registry values so that the table does not say loading,
// there was already a response from the server
dispatch('setRecordSelection', {
containerUuid: containerUuid,
panelType: 'browser'
})
showMessage({
title: language.t('notifications.error'),
message: language.t('notifications.errorSearch'),
summary: error.message,
type: 'error'
})
console.warn('Error getting browser search: ' + error.message + '. Code: ' + error.code)
})
}
}
}
export default browserControl

View File

@ -0,0 +1,65 @@
import { runCallOutRequest } from '@/api/ADempiere/data'
import { convertValuesMapToObject, showMessage } from '@/utils/ADempiere'
const callOutControl = {
actions: {
getCallout({ rootGetters, dispatch }, parameters) {
const window = rootGetters.getWindow(parameters.parentUuid)
var finalParameters = []
if (parameters.inTable) {
finalParameters = rootGetters.getParametersToServer({
containerUuid: parameters.containerUuid,
row: parameters.row
})
} else {
finalParameters = rootGetters.getParametersToServer({
containerUuid: parameters.containerUuid
})
}
return runCallOutRequest({
windowUuid: parameters.parentUuid,
tabUuid: parameters.containerUuid,
tableName: parameters.tableName,
columnName: parameters.columnName,
value: parameters.value,
oldValue: parameters.oldValue,
callout: parameters.callout,
attributesList: finalParameters,
windowNo: window.windowIndex
})
.then(response => {
const values = convertValuesMapToObject(
response.getValuesMap()
)
if (parameters.inTable) {
dispatch('notifyRowTableChange', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
row: values,
isEdit: true
})
} else {
dispatch('notifyPanelChange', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
panelType: 'window',
newValues: values,
isSendToServer: false,
withOutColumnNames: parameters.withOutColumnNames,
isSendCallout: false
})
}
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn(`Field ${parameters.name} error callout`, error.message)
})
}
}
}
export default callOutControl

View File

@ -0,0 +1,97 @@
import Vue from 'vue'
// Delete when get global context and account context
import { isEmptyValue } from '@/utils/ADempiere/valueUtil.js'
const context = {
state: {
context: {}
},
mutations: {
setContext(state, payload) {
var key = ''
if (payload.parentUuid && !isEmptyValue(payload.value)) {
key += payload.parentUuid + '|'
// set context for window
const keyParent = key + payload.columnName
Vue.set(state.context, keyParent, payload.value)
}
if (payload.containerUuid) {
key += payload.containerUuid + '|'
}
key += payload.columnName
// set property to object
Vue.set(state.context, key, payload.value)
},
setInitialContext(state, objectContext) {
Object.keys(objectContext).forEach(key => {
Vue.set(state.context, key, objectContext[key])
})
},
dictionaryResetCacheContext(state) {
state.context = {}
}
},
actions: {
setContext: ({ commit }, objectValue) => {
commit('setContext', objectValue)
},
setMultipleContext: ({ commit }, valuesToSetter) => {
valuesToSetter.forEach(itemToSetter => {
commit('setContext', itemToSetter)
})
},
setInitialContext: ({ commit }, otherContext = {}) => {
commit('setInitialContext', otherContext)
}
},
getters: {
/**
* @param {object} findedContext
* - parentUuid
* - containerUuid
* - columnName
*/
getContext: (state) => ({ parentUuid, containerUuid, columnName }) => {
var key = ''
if (parentUuid) {
key += parentUuid + '|'
// context for window
const keyParent = key + columnName
const valueParent = state.context[keyParent]
if (!isEmptyValue(valueParent)) {
return valueParent
}
}
if (containerUuid) {
key += containerUuid + '|'
}
key += columnName
return state.context[key]
},
getContextAll: (state) => {
state.context
},
getContextClientId: (state) => {
return state.context['#AD_Client_ID']
},
getContextOrgId: (state) => {
return state.context['#AD_Org_ID']
},
// Using to read only in data tables
getContextIsActive: (state) => (parentUuid) => {
return state.context[`${parentUuid}|IsActive`]
},
getContextProcessing: (state) => (parentUuid) => {
return state.context[`${parentUuid}|Processing`]
},
getContextProcessed: (state) => (parentUuid) => {
return state.context[`${parentUuid}|Processed`]
}
}
}
export default context

View File

@ -0,0 +1,62 @@
// Store used for set all related to context menu
// for Window, Process, Smart Browser andother customized component
// See structure:
// menu: [
// {
// containerUuid: '',
// relations: [],
// actions: [],
// references: []
// defaultAction: {},
// lastAction: {}
// }
// ]
const contextMenu = {
state: {
contextMenu: []
},
mutations: {
setContextMenu(state, payload) {
state.contextMenu.push(payload)
},
dictionaryResetCacheContextMenu(state) {
state.contextMenu = []
}
},
actions: {
setContextMenu({ commit }, payload) {
commit('setContextMenu', payload)
}
},
getters: {
getContextMenu: (state) => (containerUuid) => {
return state.contextMenu.find(item => item.containerUuid === containerUuid)
},
getRelations: (state, getters, rootState) => (containerUuid) => {
var menuRelations
rootState.permission.addRoutes.forEach(route => {
if (route.name === containerUuid) {
menuRelations = route.children
} else if (route.name !== containerUuid && route.children) {
route.children.forEach(child => {
if (child.name === containerUuid) {
menuRelations = route.children
}
})
}
})
return menuRelations
},
getActions: (state) => (containerUuid) => {
var menu = state.contextMenu.find(
item => item.containerUuid === containerUuid
)
if (menu === undefined) {
return menu
}
return menu.actions
}
}
}
export default contextMenu

View File

@ -0,0 +1,894 @@
import Vue from 'vue'
import {
getObject,
getObjectListFromCriteria,
getRecentItems,
getDefaultValueFromServer,
convertValueFromGRPC,
getContextInfoValueFromServer
} from '@/api/ADempiere'
import { convertValuesMapToObject, isEmptyValue, showMessage, convertAction } from '@/utils/ADempiere'
import language from '@/lang'
const data = {
state: {
recordSelection: [], // record data and selection
recordDetail: [],
recentItems: [],
inGetting: [],
contextInfoField: []
},
mutations: {
addInGetting(state, payload) {
state.inGetting.push(payload)
},
deleteInGetting(state, payload) {
state.inGetting = state.inGetting.filter(item => item.containerUuid !== payload.containerUuid)
},
setRecordSelection(state, payload) {
if (payload.index > -1 && payload.index !== undefined) {
state.recordSelection.splice(payload.index, 1, payload)
} else {
state.recordSelection.push(payload)
}
},
setSelection(state, payload) {
payload.data.selection = payload.newSelection
},
deleteRecordContainer(state, payload) {
state.recordSelection = payload
},
notifyCellTableChange: (state, payload) => {
payload.row[payload.columnName] = payload.value
if (payload.displayColumn !== undefined) {
const key = 'DisplayColumn_' + payload.columnName
payload.row[key] = payload.displayColumn
}
},
notifyCellSelectionChange: (state, payload) => {
if (payload.row !== undefined) {
payload.row[payload.columnName] = payload.value
if (payload.displayColumn !== undefined) {
const key = 'DisplayColumn_' + payload.columnName
payload.row[key] = payload.displayColumn
}
}
},
notifyRowTableChange: (state, payload) => {
Object.assign(payload.row, payload.newRow)
},
setRecentItems(state, payload) {
state.recentItems = payload
},
setPageNumber(state, payload) {
payload.data.pageNumber = payload.pageNumber
},
setIsloadContext(state, payload) {
payload.data.isLoadedContext = payload.isLoadedContext
},
setRecordDetail(state, payload) {
var isFinded = false
state.recordDetail = state.recordDetail.map(itemData => {
if (itemData.uuid === payload.uuid) {
isFinded = true
var newValues = Object.assign(itemData.data, payload.data)
payload.data = newValues
return payload
}
return itemData
})
if (!isFinded) {
state.recordDetail.push(payload)
}
},
addNewRow(state, payload) {
payload.data = payload.data.unshift(payload.values)
},
addDisplayColumn(state, payload) {
Vue.set(payload.row, payload.columnName, payload.displayColumn)
},
setContextInfoField(state, payload) {
state.contextInfoField.push(payload)
}
},
actions: {
/**
* Set page number of pagination list
* @param {string} parameters.parentUuid
* @param {string} parameters.containerUuid
* @param {integer} parameters.panelType
* @param {string} parameters.pageNumber
*/
setPageNumber({ commit, state, dispatch, rootGetters }, parameters) {
const data = state.recordSelection.find(recordItem => {
return recordItem.containerUuid === parameters.containerUuid
})
commit('setPageNumber', {
data: data,
pageNumber: parameters.pageNumber
})
// refresh list table with data from server
if (parameters.panelType === 'window') {
dispatch('getDataListTab', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid
})
} else if (parameters.panelType === 'browser') {
if (!rootGetters.isNotReadyForSubmit(parameters.containerUuid)) {
dispatch('getBrowserSearch', {
containerUuid: parameters.containerUuid,
isClearSelection: true
})
}
}
},
/**
* Insert new row bottom list table, used only from window
* @param {string} parentUuid
* @param {string} containerUuid
* @param {boolean} isPanelValues, define if used values form panel
* @param {boolean} isEdit, define if used values form panel
*/
addNewRow({ commit, getters, rootGetters, dispatch }, parameters) {
const { parentUuid, containerUuid, isPanelValues = false, isEdit = true, isNew = true } = parameters
var { fieldList = [] } = parameters
const tabPanel = rootGetters.getPanel(containerUuid)
if (!fieldList.length) {
fieldList = tabPanel.fieldList
}
var values = {}
// add row with default values to create new record
if (isPanelValues) {
// add row with values used from record in panel
values = rootGetters.getColumnNamesAndValues({
containerUuid: containerUuid,
propertyName: 'value',
isObjectReturn: true,
isAddDisplayColumn: true,
fieldList: fieldList
})
} else {
values = rootGetters.getParsedDefaultValues({
parentUuid: parentUuid,
containerUuid: containerUuid,
fieldList: fieldList
})
}
values.isNew = isNew
values.isEdit = isEdit
values.isSendServer = false
// get the link column name from the tab
var linkColumnName = tabPanel.linkColumnName
if (isEmptyValue(linkColumnName)) {
// get the link column name from field list
linkColumnName = tabPanel.fieldLinkColumnName
}
var valueLink
// get context value if link column exists and does not exist in row
if (!isEmptyValue(linkColumnName)) {
valueLink = rootGetters.getContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
columnName: linkColumnName
})
}
if (!isEmptyValue(valueLink)) {
valueLink = parseInt(valueLink, 10)
}
// get display column
if (fieldList.length) {
fieldList
// TODO: Evaluate if is field is read only and FieldSelect
.filter(itemField => itemField.componentPath === 'FieldSelect' || String(values[itemField.columnName]) === '[object Object]')
.forEach(itemField => {
var valueGetDisplayColumn = values[itemField.columnName]
if (String(values[itemField.columnName]) === '[object Object]' && itemField.componentPath === 'FieldSelect') {
values[itemField.columnName] = ' '
values['DisplayColumn_' + itemField.columnName] = ' '
} else if (String(values[itemField.columnName]) === '[object Object]' && itemField.componentPath === 'FieldNumber') {
values[itemField.columnName] = 0
}
// overwrite value with column link
if (!isEmptyValue(linkColumnName) && linkColumnName === itemField.columnName) {
valueGetDisplayColumn = valueLink
}
// break this itineration if is empty
if (isEmptyValue(valueGetDisplayColumn)) {
return
}
// always the values for these types of fields are integers
if (['TableDirect'].includes(itemField.referenceType)) {
valueGetDisplayColumn = parseInt(valueGetDisplayColumn, 10)
} else {
if (!isNaN(valueGetDisplayColumn)) {
valueGetDisplayColumn = parseInt(valueGetDisplayColumn, 10)
}
}
if (!isEmptyValue(valueGetDisplayColumn) && String(valueGetDisplayColumn) === '[object Object]') {
// get value from direct Query
dispatch('getRecordBySQL', { query: valueGetDisplayColumn.query, field: itemField })
.then(defaultValue => {
if (itemField.componentPath === 'FieldSelect') {
values[itemField.columnName] = defaultValue.key
values['DisplayColumn_' + itemField.columnName] = defaultValue.label
} else {
values[itemField.columnName] = defaultValue.key
dispatch('notifyRowTableChange', {
parentUuid: parentUuid,
containerUuid: containerUuid,
isNew: isNew,
isEdit: isEdit,
values: values
})
}
})
return
}
// get label (DisplayColumn) from vuex store
const options = rootGetters.getLookupAll({
parentUuid: parentUuid,
containerUuid: containerUuid,
tableName: itemField.reference.tableName,
query: itemField.reference.query,
directQuery: itemField.reference.directQuery,
value: valueGetDisplayColumn
})
const option = options.find(itemOption => itemOption.key === valueGetDisplayColumn)
// if there is a lookup option, assign the display column with the label
if (option) {
values['DisplayColumn_' + itemField.columnName] = option.label
return
}
if (linkColumnName === itemField.columnName) {
// get context value if link column exists and does not exist in row
const nameParent = rootGetters.getContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
columnName: 'Name'
})
if (nameParent) {
values['DisplayColumn_' + itemField.columnName] = nameParent
return
}
}
// get from server
dispatch('getLookupItemFromServer', {
parentUuid: parentUuid,
containerUuid: containerUuid,
tableName: itemField.reference.tableName,
directQuery: itemField.reference.directQuery,
value: valueGetDisplayColumn
})
.then(responseLookup => {
dispatch('addDisplayColumn', {
containerUuid: containerUuid,
columnName: itemField.columnName,
displayColumn: responseLookup.label
})
})
})
}
// overwrite value with column link
if (isEmptyValue(values[linkColumnName])) {
values[linkColumnName] = valueLink
}
const dataStore = getters.getDataRecordsList(containerUuid)
commit('addNewRow', {
values: values,
data: dataStore
})
},
addDisplayColumn({ commit, getters }, parameters) {
const { containerUuid, columnName, displayColumn } = parameters
const dataStore = getters.getDataRecordsList(containerUuid)
const rowRecord = dataStore.find(itemData => itemData.isNew)
commit('addDisplayColumn', {
row: rowRecord,
displayColumn: displayColumn,
columnName: 'DisplayColumn_' + columnName
})
},
/**
* Is load context in true when panel is set context
* @param {string} parameters.containerUuid
*/
setIsloadContext({ commit, state }, parameters) {
const { containerUuid } = parameters
const data = state.recordSelection.find(recordItem => {
return recordItem.containerUuid === containerUuid
})
if (data) {
commit('setIsloadContext', {
data: data,
isLoadedContext: true
})
}
},
/**
* Set record, selection, page number, token, and record count, with container uuid
* TODO: Refactor and optimize the mutation of state
* @param {string} parameters.containerUuid
* @param {array} parameters.record
* @param {array} parameters.selection
* @param {integer} parameters.pageNumber
* @param {integer} parameters.recordCount
* @param {string} parameters.nextPageToken
* @param {string} parameters.panelType
*/
setRecordSelection({ commit, state }, parameters) {
const { parentUuid, containerUuid, record = [], selection = [], pageNumber = 1, recordCount = 0, nextPageToken, panelType = 'window' } = parameters
var index = state.recordSelection.findIndex(recordItem => {
return recordItem.containerUuid === containerUuid
})
commit('setRecordSelection', {
parentUuid: parentUuid,
containerUuid: containerUuid,
record: record,
selection: selection,
pageNumber: pageNumber,
recordCount: recordCount,
nextPageToken: nextPageToken,
panelType: panelType,
isLoaded: true,
isLoadedContext: false,
index: index
})
},
/**
* Set selection in data list associated in container
* @param {string} parameters.containerUuid
* @param {string} parameters.selection
*/
setSelection({ commit, getters }, parameters) {
const recordSelection = getters.getDataRecordAndSelection(parameters.containerUuid)
commit('setSelection', {
newSelection: parameters.selection,
data: recordSelection
})
},
/**
* Delete record result in container
* @param {string} viewUuid // As parentUuid in window
* @param {array} withOut
*/
deleteRecordContainer({ commit, state, dispatch }, parameters) {
const { viewUuid, withOut = [], isNew = false } = parameters
var setNews = []
const record = state.recordSelection.filter(itemRecord => {
// ignore this uuid
if (withOut.includes(itemRecord.containerUuid)) {
return true
}
// remove window and tabs data
if (itemRecord.parentUuid) {
if (isNew) {
setNews.push(itemRecord.containerUuid)
}
return itemRecord.parentUuid !== viewUuid
}
// remove browser data
return itemRecord.containerUuid !== viewUuid
})
commit('deleteRecordContainer', record)
if (setNews.length) {
setNews.forEach(uuid => {
dispatch('setRecordSelection', {
parentUuid: viewUuid,
containerUuid: uuid
})
})
}
},
/**
* @param {string} tableName
* @param {string} recordUuid
*/
getEntity({ commit }, parameters) {
return new Promise((resolve, reject) => {
getObject(parameters.tableName, parameters.recordUuid)
.then(response => {
var map = response.getValuesMap()
var newValues = convertValuesMapToObject(map)
const responseConvert = {
data: newValues,
id: response.getId(),
uuid: response.getUuid(),
tableName: parameters.tableName
}
commit('setRecordDetail', responseConvert)
resolve(newValues)
})
.catch(error => {
reject(error)
})
})
},
/**
* Request list to view in table
* TODO: Join with getDataListTab action
* @param {string} parentUuid, uuid from window
* @param {string} containerUuid, uuid from tab
* @param {string} tableName, table name to search record data
* @param {string} query, criteria to search record data
* @param {string} whereClause, criteria to search record data
* @param {string} orderByClause, criteria to search record data
* @param {array} conditions, conditions to criteria
*/
getObjectListFromCriteria({ commit, dispatch, getters, rootGetters }, parameters) {
const { parentUuid, containerUuid, tableName, query, whereClause, orderByClause, conditions = [], isShowNotification = true, isParentTab = true } = parameters
if (isShowNotification) {
showMessage({
title: language.t('notifications.loading'),
message: language.t('notifications.searching'),
type: 'info'
})
}
const dataStore = getters.getDataRecordAndSelection(containerUuid)
var nextPageToken
if (!isEmptyValue(dataStore.nextPageToken)) {
nextPageToken = dataStore.nextPageToken + '-' + dataStore.pageNumber
}
var inEdited = []
if (!isParentTab) {
// TODO: Evaluate peformance to evaluate records to edit
inEdited = dataStore.record.filter(itemRecord => {
return itemRecord.isEdit && !itemRecord.isNew
})
}
commit('addInGetting', {
containerUuid: containerUuid,
tableName: tableName,
conditions: conditions
})
// gets the default value of the fields (including whether it is empty or undefined)
const defaultValues = rootGetters.getParsedDefaultValues({
parentUuid: parentUuid,
containerUuid: containerUuid,
isGetServer: false
})
return getObjectListFromCriteria({
tableName: tableName,
query: query,
whereClause: whereClause,
conditions: conditions,
orderByClause: orderByClause,
nextPageToken: nextPageToken
})
.then(response => {
const recordList = response.getRecordsList()
const record = recordList.map(itemRecord => {
var values = convertValuesMapToObject(
itemRecord.getValuesMap()
)
// datatables attribute
values.isNew = false
values.isEdit = false
values.isSelected = false
values.isReadOnlyFromRow = false
if (inEdited.find(itemEdit => itemEdit.UUID === values.UUID)) {
values.isEdit = true
}
// overwrite default values and sets the values obtained from the
// server (empty fields are not brought from the server)
return {
...defaultValues,
...values
}
})
var token = response.getNextPageToken()
if (!isEmptyValue(token)) {
token = token.slice(0, -2)
if (token.substr(-1, 1) === '-') {
token = token.slice(0, -1)
}
} else {
token = dataStore.nextPageToken
}
if (isShowNotification) {
let searchMessage = 'searchWithOutRecords'
if (record.length) {
searchMessage = 'succcessSearch'
}
showMessage({
title: language.t('notifications.succesful'),
message: language.t(`notifications.${searchMessage}`),
type: 'success'
})
}
dispatch('setRecordSelection', {
parentUuid: parentUuid,
containerUuid: containerUuid,
record: record,
selection: dataStore.selection,
recordCount: response.getRecordcount(),
nextPageToken: token,
pageNumber: dataStore.pageNumber
})
return record
})
.catch(error => {
// Set default registry values so that the table does not say loading,
// there was already a response from the server
dispatch('setRecordSelection', {
parentUuid: parentUuid,
containerUuid: containerUuid
})
if (isShowNotification) {
showMessage({
title: language.t('notifications.error'),
message: error.message,
type: 'error'
})
}
console.warn('Error Get Object List ' + error.message + '. Code: ' + error.code)
})
.finally(() => {
commit('deleteInGetting', {
containerUuid: containerUuid,
tableName: tableName
})
})
},
getRecordBySQL({ dispatch }, parameters) {
const { query, field } = parameters
return new Promise((resolve, reject) => {
getDefaultValueFromServer(query)
.then(response => {
var valueToReturn = {}
valueToReturn['key'] = convertValueFromGRPC(response)
// add display Column for table
if (field.componentPath === 'FieldSelect') {
dispatch('getLookupItemFromServer', {
parentUuid: field.parentUuid,
containerUuid: field.containerUuid,
tableName: field.reference.tableName,
directQuery: field.reference.directQuery,
value: valueToReturn.key
})
.then(responseLookup => {
valueToReturn['label'] = responseLookup.label
dispatch('addDisplayColumn', {
containerUuid: field.containerUuid,
columnName: field.columnName,
displayColumn: responseLookup.label
})
})
}
resolve(valueToReturn)
})
.catch(error => {
reject(error)
})
})
},
getRecentItemsFromServer({ commit }) {
return new Promise((resolve, reject) => {
getRecentItems()
.then(response => {
const recentItems = response.getRecentitemsList().map(item => {
return {
action: convertAction(item.getAction()).name,
icon: convertAction(item.getAction()).icon,
displayName: item.getDisplayname(),
menuUuid: item.getMenuuuid(),
menuName: item.getMenuname(),
windowUuid: item.getWindowuuid(),
tableId: item.getTableid(),
recordId: item.getRecordid(),
uuidRecord: item.getRecorduuid(),
tabUuid: item.getTabuuid(),
updated: new Date(item.getUpdated()),
description: item.getMenudescription()
}
})
commit('setRecentItems', recentItems)
resolve(recentItems)
})
.catch(error => {
reject(error)
})
})
},
/**
* TODO: Add support to tab children
* @param {object} objectParams
* @param {string} objectParams.containerUuid
* @param {objec} objectParams.row, new data to change
* @param {objec} objectParams.isEdit, if the row displayed to edit mode
* @param {objec} objectParams.isNew, if insert data to new row
*/
notifyRowTableChange({ commit, state, getters, rootGetters }, objectParams) {
const { parentUuid, containerUuid, isEdit = true } = objectParams
var currentValues = {}
if (objectParams.hasOwnProperty('values')) {
currentValues = objectParams.values
} else {
currentValues = rootGetters.getColumnNamesAndValues({
parentUuid: parentUuid,
containerUuid: containerUuid,
propertyName: 'value',
isObjectReturn: true,
isAddDisplayColumn: true
})
}
var row = getters.getRowData(objectParams.containerUuid, currentValues.UUID)
var newRow = {
...currentValues,
// ...objectParams.row,
isEdit: isEdit
}
commit('notifyRowTableChange', {
isNew: objectParams.isNew,
newRow: newRow,
row: row
})
},
notifyCellTableChange({ commit, state, dispatch, rootGetters }, parameters) {
const {
parentUuid, containerUuid, field, panelType = 'window',
isSendToServer = true, columnName, rowKey, keyColumn, newValue,
displayColumn, withOutColumnNames = [], isSendCallout = true
} = parameters
const recordSelection = state.recordSelection.find(recordItem => {
return recordItem.containerUuid === containerUuid
})
const row = recordSelection.record.find(itemRecord => {
return itemRecord[keyColumn] === rowKey
})
// the field has not changed, then the action is broken
if (row[columnName] === newValue) {
return
}
const rowSelection = recordSelection.selection.find(itemRecord => {
return itemRecord[keyColumn] === rowKey
})
commit('notifyCellTableChange', {
row: row,
value: newValue,
columnName: columnName,
displayColumn: displayColumn
})
if (panelType === 'browser') {
commit('notifyCellSelectionChange', {
row: rowSelection,
value: newValue,
columnName: columnName,
displayColumn: displayColumn
})
} else if (panelType === 'window') {
// request callouts
if (isSendCallout && !withOutColumnNames.includes(field.columnName) &&
!isEmptyValue(newValue) && !isEmptyValue(field.callout)) {
withOutColumnNames.push(field.columnName)
dispatch('getCallout', {
parentUuid: parentUuid,
containerUuid: containerUuid,
tableName: field.tableName,
columnName: field.columnName,
callout: field.callout,
value: newValue,
withOutColumnNames: withOutColumnNames,
row: row,
inTable: true
})
}
if (isSendToServer) {
const fieldNotReady = rootGetters.isNotReadyForSubmit(containerUuid, row)
if (!fieldNotReady) {
if (!isEmptyValue(row.UUID)) {
dispatch('updateCurrentEntityFromTable', {
parentUuid: parentUuid,
containerUuid: containerUuid,
row: row
})
} else {
dispatch('createEntityFromTable', {
parentUuid: parentUuid,
containerUuid: containerUuid,
row: row
})
.then(() => {
// refresh record list
dispatch('getDataListTab', {
parentUuid: parentUuid,
containerUuid: containerUuid
})
})
}
} else {
const fieldsEmpty = rootGetters.getFieldListEmptyMandatory({
containerUuid: containerUuid,
row: row
})
showMessage({
message: language.t('notifications.mandatoryFieldMissing') + fieldsEmpty,
type: 'info'
})
}
}
}
},
getContextInfoValueFromServer({ commit, getters }, parameters) {
var { sqlStatement, contextInfoUuid } = parameters
var contextInforField = getters.getContextInfoField(contextInfoUuid, sqlStatement)
if (contextInforField) {
return contextInforField
}
return getContextInfoValueFromServer({ uuid: contextInfoUuid, query: sqlStatement })
.then(response => {
const contextInfo = {
messageText: response.getMessagetext(),
messageTip: response.getMessagetip()
}
commit('setContextInfoField', {
contextInfoUuid: contextInfoUuid,
sqlStatement: sqlStatement,
messageText: contextInfo.messageText,
messageTip: contextInfo.messageTip
})
return contextInfo
})
.catch(error => {
console.warn(`Error ${error.code} getting context info value for field ${error.message}`)
})
}
},
getters: {
getInGetting: (state) => (containerUuid) => {
return state.inGetting.find(item => item.containerUuid === containerUuid)
},
/**
* Used by datatables in tab children, record navigation in window, result in browser
* @param {string} containerUuid
*/
getDataRecordAndSelection: (state, getters) => (containerUuid) => {
return state.recordSelection.find(itemRecord => {
return itemRecord.containerUuid === containerUuid
}) || {
containerUuid: containerUuid,
record: [],
recordCount: 0,
selection: [],
pageNumber: 1,
nextPageToken: undefined,
isLoadedContext: false,
isLoaded: false // Boolean(false || getters.getInGetting(containerUuid))
}
},
getDataRecordsList: (state, getters) => (containerUuid) => {
return getters.getDataRecordAndSelection(containerUuid).record
},
getDataRecordCount: (state, getters) => (containerUuid) => {
return getters.getDataRecordAndSelection(containerUuid).recordCount
},
getPageNextToken: (state, getters) => (containerUuid) => {
return getters.getDataRecordAndSelection(containerUuid).nextPageToken
},
getDataRecordSelection: (state, getters) => (containerUuid) => {
return getters.getDataRecordAndSelection(containerUuid).selection
},
getPageNumber: (state, getters) => (containerUuid) => {
return getters.getDataRecordAndSelection(containerUuid).pageNumber
},
getRowData: (state, getters) => (containerUuid, recordUuid) => {
return getters.getDataRecordsList(containerUuid).find(itemData => {
if (itemData.UUID === recordUuid) {
return true
}
})
},
/**
* @returns {object}
*/
getRecordDetail: (state) => (parameters) => {
return state.recordDetail.find(itemData => {
if (itemData.uuid === parameters.recordUuid) {
return true
}
}) || {}
},
/**
* Getter converter selection data record in format
* @param {string} containerUuid
* [
* {
* selectionId: keyColumn Value,
* selectionValues: [
* { columname, value },
* { columname, value },
* { columname, value }
* ]
* },
* {
* selectionId: keyColumn Value,
* selectionValues: [
* { columname, value },
* { columname, value }
* ]
* }
* ]
*/
getSelectionToServer: (state, getters, rootState, rootGetters) => (parameters) => {
var { containerUuid, selection = [] } = parameters
var selectionToServer = []
const withOut = ['isEdit', 'isSelected', 'isSendToServer']
if (selection.length <= 0) {
selection = getters.getDataRecordSelection(containerUuid)
}
if (selection.length) {
const panel = rootGetters.getPanel(containerUuid)
selection.forEach(itemRow => {
var records = []
Object.keys(itemRow).forEach(key => {
if (!key.includes('DisplayColumn') && !withOut.includes(key)) {
// evaluate metadata attributes before to convert
const field = panel.fieldList.find(itemField => itemField.columnName === key)
if (field && (field.isIdentifier || field.isUpdateable)) {
records.push({
columnName: key,
value: itemRow[key]
})
}
}
})
selectionToServer.push({
selectionId: itemRow[panel.keyColumn],
selectionValues: records
})
})
}
return selectionToServer
},
getRecentItems: (state) => {
return state.recentItems
},
getLanguageList: (state) => (roleUuid) => {
return state.recordSelection.find(
record => record.containerUuid === roleUuid
) || []
},
getContextInfoField: (state) => (contextInfoUuid, sqlStatement) => {
return state.contextInfoField.find(info =>
info.contextInfoUuid === contextInfoUuid &&
info.sqlStatement === sqlStatement
)
}
}
}
export default data

View File

@ -0,0 +1,128 @@
import { enrollmentUser, forgotPassword, resetPasswordFromToken } from '@/api/ADempiere/enrollment'
import { showMessage } from '@/utils/ADempiere'
import language from '@/lang'
import router from '@/router'
const enrollment = {
actions: {
/**
* Enroll user
* @param {string} userData.name
* @param {string} userData.userName
* @param {string} userData.password
*/
enrollmentUser({ commit }, userData) {
return enrollmentUser(userData)
.then(response => {
showMessage({
message: language.t('login.userEnrollmentSuccessful'),
type: 'success'
})
router.push({
path: 'login'
})
})
.catch(error => {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Enrollment User - Error ' + error.code + ': ' + error.message)
})
},
/**
* @param {string} eMailOrUserName
*/
forgotPassword({ commit }, eMailOrUserName) {
return forgotPassword(eMailOrUserName)
.then(response => {
if (response.getResponsetype() === 0) {
showMessage({
message: language.t('login.passwordResetSendLink') + eMailOrUserName,
type: 'success'
})
router.push({
path: 'login'
})
} else {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
}
return response
})
.catch(error => {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Forgot Password - Error ' + error.code + ': ' + error.message)
})
},
/**
* TODO: Add support gRPC service createPassword
* Create password to new user from token
* @param {string} parameters.token
* @param {string} parameters.password
*/
createPasswordFromToken({ commit }, parameters) {
return resetPasswordFromToken(parameters.token, parameters.password)
.then(response => {
if (response.getResponsetype() === 0) {
showMessage({
message: language.t('login.createPasswordSuccessful'),
type: 'success'
})
} else {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
}
router.push({
path: 'login'
})
})
.catch(error => {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Create Password - Error ' + error.code + ': ' + error.message)
})
},
/**
* @param {string} parameters.token
* @param {string} parameters.password
*/
resetPasswordFromToken({ commit }, parameters) {
return resetPasswordFromToken(parameters.token, parameters.password)
.then(response => {
if (response.getResponsetype() === 0) {
showMessage({
message: language.t('login.passwordResetSuccessful'),
type: 'success'
})
} else {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
}
router.push({
path: 'login'
})
})
.catch(error => {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Reset Password - Error ' + error.code + ': ' + error.message)
})
}
}
}
export default enrollment

View File

@ -0,0 +1,204 @@
import { getLookup, getLookupList, convertValueFromGRPC } from '@/api/ADempiere/data'
import { isEmptyValue, getCurrentRole, parseContext } from '@/utils/ADempiere'
const lookup = {
state: {
lookupItem: [],
lookupList: []
},
mutations: {
addLoockupItem(state, payload) {
state.lookupItem.push(payload)
},
addLoockupList(state, payload) {
state.lookupList.push(payload)
},
deleteLookupList(state, payload) {
state.lookupItem = payload.lookupItem
state.lookupList = payload.lookupList
}
},
actions: {
getLookupItemFromServer({ commit, rootGetters }, parameters) {
const { parentUuid, containerUuid, value, tableName, directQuery } = parameters
var parsedDirectQuery = directQuery
if (parsedDirectQuery.includes('@')) {
parsedDirectQuery = parseContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
value: directQuery
}, true)
}
return getLookup({
tableName: tableName,
directQuery: parsedDirectQuery,
value: value
})
.then(response => {
const map = response.getValuesMap()
const label = convertValueFromGRPC(map.get('DisplayColumn'))
var option = {
label: isEmptyValue(label) ? ' ' : label,
// key: convertValueFromGRPC(map.get('KeyColumn'))
key: value
}
commit('addLoockupItem', {
option: option,
value: value, // isNaN(objectParams.value) ? objectParams.value : parseInt(objectParams.value, 10),
parsedDirectQuery: directQuery,
tableName: tableName,
roleUuid: getCurrentRole(),
clientId: parseInt(rootGetters.getContextClientId)
})
return option
})
.catch(error => {
console.warn('Get Lookup, Select Base - Error ' + error.code + ': ' + error.message)
})
},
/**
* tableName,
* query
*/
getLookupListFromServer({ commit, rootGetters }, parameters) {
const { parentUuid, containerUuid, tableName, query } = parameters
var parsedQuery = query
if (parsedQuery.includes('@')) {
parsedQuery = parseContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
value: query
}, true)
}
return getLookupList({
tableName: tableName,
query: parsedQuery
})
.then(response => {
const recordList = response.getRecordsList()
var options = []
recordList.forEach(element => {
const map = element.getValuesMap()
const name = convertValueFromGRPC(map.get('DisplayColumn'))
const key = convertValueFromGRPC(map.get('KeyColumn'))
options.push({
label: isEmptyValue(name) ? ' ' : name,
key: isEmptyValue(key) ? -1 : isNaN(key) ? key : parseInt(key)
})
})
commit('addLoockupList', {
list: options,
tableName: tableName,
parsedQuery: parsedQuery,
roleUuid: getCurrentRole(),
clientId: parseInt(rootGetters.getContextClientId)
})
return options
})
.catch(error => {
console.warn('Get Lookup List, Select Base - Error ' + error.code + ': ' + error.message)
})
},
deleteLookupList({ commit, state }, params) {
const { parentUuid, containerUuid, tableName, query, directQuery, value } = params
var parsedDirectQuery = directQuery
if (parsedDirectQuery.includes('@')) {
parsedDirectQuery = parseContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
value: parsedDirectQuery
}, true)
}
const lookupItem = state.lookupItem.filter(itemLookup => {
return itemLookup.parsedDirectQuery !== params.parsedDirectQuery &&
itemLookup.tableName !== tableName &&
itemLookup.value !== value &&
itemLookup.roleUuid !== getCurrentRole()
})
var parsedQuery = query
if (parsedQuery.includes('@')) {
parsedQuery = parseContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
value: parsedQuery
}, true)
}
const lookupList = state.lookupList.filter(itemLookup => {
return itemLookup.parsedQuery !== parsedQuery &&
itemLookup.tableName !== tableName &&
itemLookup.roleUuid !== getCurrentRole()
})
commit('deleteLookupList', {
lookupItem: lookupItem,
lookupList: lookupList
})
}
},
getters: {
getLookupItem: (state, getters, rootState, rootGetters) => (params) => {
var parsedDirectQuery = params.directQuery
if (parsedDirectQuery.includes('@')) {
parsedDirectQuery = parseContext({
parentUuid: params.parentUuid,
containerUuid: params.containerUuid,
value: parsedDirectQuery
}, true)
}
const lookupItem = state.lookupItem.find(itemLookup => {
return itemLookup.parsedDirectQuery === parsedDirectQuery &&
itemLookup.tableName === params.tableName &&
itemLookup.roleUuid === getCurrentRole() &&
itemLookup.clientId === parseInt(rootGetters.getContextClientId) &&
itemLookup.value === params.value
})
if (lookupItem) {
return lookupItem.option
}
return undefined
},
getLookupList: (state, getters, rootState, rootGetters) => (params) => {
var parsedQuery = params.query
if (parsedQuery.includes('@')) {
parsedQuery = parseContext({
parentUuid: params.parentUuid,
containerUuid: params.containerUuid,
value: parsedQuery
}, true)
}
const lookupList = state.lookupList.find(itemLookup => {
return itemLookup.parsedQuery === parsedQuery &&
itemLookup.tableName === params.tableName &&
itemLookup.roleUuid === getCurrentRole() &&
itemLookup.clientId === parseInt(rootGetters.getContextClientId)
})
if (lookupList) {
const resultLookup = lookupList.list.filter(lookup => {
if (lookup.key !== undefined) {
return lookup
}
})
return resultLookup
}
return []
},
/**
*
*/
getLookupAll: (state, getters, rootState, rootGetters) => (parameters) => {
const item = getters.getLookupItem(parameters)
var list = getters.getLookupList(parameters)
if (item && !list.find(itemLookup => itemLookup.key === item.key)) {
list.push(item)
}
return list
}
}
}
export default lookup

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,206 @@
import { getProcess as getProcessMetadata } from '@/api/ADempiere'
import { convertField, isEmptyValue, showMessage } from '@/utils/ADempiere'
import language from '@/lang'
import router from '@/router'
const process = {
state: {
process: []
},
mutations: {
addProcess(state, payload) {
state.process.push(payload)
},
dictionaryResetCacheProcess(state) {
state.process = []
}
},
actions: {
getProcessFromServer: ({ commit, dispatch }, parameters) => {
return new Promise((resolve, reject) => {
var processUuid = parameters.containerUuid
getProcessMetadata(processUuid)
.then(response => {
var panelType = 'process'
if (response.getIsreport()) {
panelType = 'report'
}
const additionalAttributes = {
processUuid: response.getUuid(),
processId: response.getId(),
containerUuid: response.getUuid(),
panelType: panelType
}
// Convert from gRPC
var fieldsRangeList = []
var fieldDefinitionList = response.getParametersList().map((fieldItem, index) => {
var someAttributes = {
...additionalAttributes,
fieldListIndex: index
}
var field = convertField(fieldItem, someAttributes)
// Add new field if is range number
if (field.isRange && field.componentPath === 'NumberBase') {
var fieldRange = convertField(fieldItem, someAttributes, true)
if (!isEmptyValue(fieldRange.value)) {
fieldRange.isShowedFromUser = true
}
fieldsRangeList.push(fieldRange)
}
// if field with value displayed in main panel
if (!isEmptyValue(field.value)) {
field.isShowedFromUser = true
}
return field
})
fieldDefinitionList = fieldDefinitionList.concat(fieldsRangeList)
// Get dependent fields
fieldDefinitionList
.filter(field => field.parentFieldsList && field.isActive)
.forEach((field, index, list) => {
field.parentFieldsList.forEach(parentColumnName => {
var parentField = list.find(parentField => {
return parentField.columnName === parentColumnName && parentColumnName !== field.columnName
})
if (parentField) {
parentField.dependentFieldsList.push(field.columnName)
}
})
})
// Get export list
var reportExportTypeList = response.getReportexporttypesList().map(reportType => {
return {
name: reportType.getName(),
description: reportType.getDescription(),
reportExportType: reportType.getType()
}
})
// Default Action
var actions = []
actions.push({
name: language.t('components.RunProcess'),
processName: response.getName(),
type: 'action',
action: 'startProcess',
uuid: response.getUuid(),
id: response.getId(),
description: response.getDescription(),
help: response.getHelp(),
isReport: response.getIsreport(),
accessLevel: response.getAccesslevel(),
showHelp: response.getShowhelp(),
isDirectPrint: response.getIsdirectprint(),
reportExportType: undefined
}, {
name: language.t('components.ChangeParameters'),
processName: response.getName(),
type: 'process',
action: 'changeParameters',
uuid: response.getUuid(),
id: response.getId(),
description: response.description,
help: response.getHelp(),
isReport: response.getIsreport(),
accessLevel: response.getAccesslevel(),
showHelp: response.getShowhelp(),
isDirectPrint: response.getIsdirectprint()
})
var summaryAction = {
name: language.t('components.RunProcessAs'),
processName: response.getName(),
type: 'summary',
action: '',
childs: [],
uuid: response.getUuid(),
id: response.getId(),
description: response.getDescription(),
help: response.getHelp(),
isReport: response.getIsreport(),
accessLevel: response.getAccesslevel(),
showHelp: response.getShowhelp(),
isDirectPrint: response.getIsdirectprint()
}
reportExportTypeList.forEach(actionValue => {
// Push values
summaryAction.childs.push({
name: language.t('components.ExportTo') + ' (' + actionValue.name + ')',
processName: response.getName(),
type: 'action',
action: 'startProcess',
uuid: response.getUuid(),
id: response.getId(),
description: actionValue.description,
help: response.getHelp(),
isReport: response.getIsreport(),
accessLevel: response.getAccesslevel(),
showHelp: response.getShowhelp(),
isDirectPrint: response.getIsdirectprint(),
reportExportType: actionValue.reportExportType
})
})
// Add summary Actions
actions.push(summaryAction)
var processDefinition = {
id: response.getId(),
uuid: response.getUuid(),
name: response.getName(),
description: response.getDescription(),
help: response.getHelp(),
isReport: response.getIsreport(),
accessLevel: response.getAccesslevel(),
showHelp: response.getShowhelp(),
isDirectPrint: response.getIsdirectprint(),
reportExportTypeList: reportExportTypeList,
value: response.getValue(),
panelType: panelType,
fieldList: fieldDefinitionList
}
dispatch('addPanel', processDefinition)
commit('addProcess', processDefinition)
// Add process menu
dispatch('setContextMenu', {
containerUuid: response.getUuid(),
relations: [],
actions: actions,
references: []
})
resolve(processDefinition)
})
.catch(error => {
router.push({ path: '/dashboard' })
dispatch('tagsView/delView', parameters.routeToDelete)
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Dictionary Process (State) - Error ' + error)
reject(error)
})
})
}
},
getters: {
getProcess: (state) => (processUuid) => {
return state.process.find(
item => item.uuid === processUuid
)
},
getProcessById: (state) => (processId) => {
return state.process.find(
item => item.id === parseInt(processId)
)
}
}
}
export default process

View File

@ -0,0 +1,474 @@
import { runProcess, requestProcessActivity } from '@/api/ADempiere'
import { showNotification } from '@/utils/ADempiere/notification'
import { isEmptyValue, convertMapToArrayPairs } from '@/utils/ADempiere'
import language from '@/lang'
import router from '@/router'
const processControl = {
state: {
inExecution: [], // process not response from server
isVisibleDialog: false,
reportObject: {},
reportList: [],
metadata: {},
process: [], // process to run finish
sessionProcess: [],
notificationProcess: [],
inRequestMetadata: []
},
mutations: {
// Add process in execution
addInExecution(state, payload) {
state.inExecution.push(payload)
},
// Add process in notifation
addNotificationProcess(state, payload) {
state.notificationProcess.push(payload)
},
// Delete process in execution afther some response from server
deleteInExecution(state, payload) {
state.inExecution = state.inExecution.filter(item => item.containerUuid !== payload.containerUuid)
},
// Add process in request metadata from server
addInRequestMetadata(state, payload) {
state.inRequestMetadata.push(payload)
},
// Delete process in request metadata
deleteInRequestMetadata(state, payload) {
state.inRequestMetadata = state.inRequestMetadata.filter(item => item !== payload)
},
addStartedProcess(state, payload) {
state.process.push(payload)
},
dataResetCacheProcess(state, payload) {
state.process = payload
},
/**
*
* @param {object} state
* @param {boolean} payload, true or false value to change displayed dialog
*/
setShowDialog(state, payload) {
state.isVisibleDialog = payload
},
setMetadata(state, payload) {
state.metadata = payload
},
setReportValues(state, payload) {
state.reportObject = payload
state.reportList.push(payload)
},
setSessionProcess(state, payload) {
state.sessionProcess = payload.processList
},
changeFormatReport(state, payload) {
state.reportFormat = payload
},
clearProcessControl(state) {
state.inExecution = [] // process not response from server
state.reportObject = {}
state.reportList = []
state.metadata = {}
state.process = [] // process to run finish
state.sessionProcess = []
state.notificationProcess = []
state.inRequestMetadata = []
}
},
actions: {
// Supported Actions for it
startProcess({ commit, dispatch, getters, rootGetters }, params) {
return new Promise((resolve, reject) => {
// TODO: Add support to evaluate params to send
const samePocessInExecution = getters.getInExecution(params.containerUuid)
// exists some call to executed process with container uuid
if (samePocessInExecution) {
return reject({
error: 0,
message: `In this process (${samePocessInExecution.name}) there is already an execution in progress.`
})
}
// additional attributes to send server, selection to browser, or table name and record id to window
var selection = []
var allData = {}
var tab, tableName, recordId
if (params.panelType) {
if (params.panelType === 'browser') {
allData = getters.getDataRecordAndSelection(params.containerUuid)
selection = rootGetters.getSelectionToServer({
containerUuid: params.containerUuid,
selection: allData.selection
})
if (selection.length < 1) {
showNotification({
title: language.t('data.selectionRequired'),
type: 'warning'
})
return reject({
error: 0,
message: `Required selection data record to run this process (${params.action.name})`
})
}
}
if (params.panelType === 'window') {
tab = rootGetters.getTab(params.parentUuid, params.containerUuid)
tableName = tab.tableName
const field = rootGetters.getFieldFromColumnName(params.containerUuid, tableName + '_ID')
recordId = field.value
}
}
// get info metadata process
const processDefinition = rootGetters.getProcess(params.action.uuid)
var reportType = params.reportFormat
const finalParameters = rootGetters.getParametersToServer({ containerUuid: processDefinition.uuid })
showNotification({
title: language.t('notifications.processing'),
message: processDefinition.name,
summary: processDefinition.description,
type: 'info'
})
const timeInitialized = (new Date()).getTime()
// Run process on server and wait for it for notify
var processResult = {
// panel attributes from where it was executed
parentUuid: params.parentUuid,
containerUuid: params.containerUuid,
panelType: params.panelType,
menuParentUuid: params.menuParentUuid,
processIdPath: params.routeToDelete.path,
// process attributes
lastRun: timeInitialized,
action: processDefinition.name,
name: processDefinition.name,
description: processDefinition.description,
instanceUuid: '',
processUuid: processDefinition.uuid,
processId: processDefinition.id,
processName: processDefinition.processName,
parameters: finalParameters,
isError: false,
isProcessing: true,
isReport: processDefinition.isReport,
summary: '',
resultTableName: '',
logs: [],
output: {
uuid: '',
name: '',
description: '',
fileName: '',
output: '',
outputStream: '',
reportType: ''
}
}
commit('addInExecution', processResult)
if (params.panelType === 'window') {
reportType = 'pdf'
} else if (params.panelType === 'browser') {
if (allData.record.length <= 100) {
// close view if is browser.
router.push({ path: '/dashboard' })
dispatch('tagsView/delView', params.routeToDelete)
// delete data associate to browser
dispatch('deleteRecordContainer', {
viewUuid: params.containerUuid
})
}
} else {
// close view if is process, report.
router.push({ path: '/dashboard' })
dispatch('tagsView/delView', params.routeToDelete)
}
runProcess({
uuid: processDefinition.uuid,
id: processDefinition.id,
reportType: reportType,
parameters: finalParameters,
selection: selection,
tableName: tableName,
recordId: recordId
})
.then(response => {
var output = {
uuid: '',
name: '',
description: '',
fileName: '',
mimeType: '',
output: '',
outputStream: '',
reportType: ''
}
if (response.getOutput()) {
const responseOutput = response.getOutput()
output = {
uuid: responseOutput.getUuid(),
name: responseOutput.getName(),
description: responseOutput.getDescription(),
fileName: responseOutput.getFilename(),
mimeType: responseOutput.getMimetype(),
output: responseOutput.getOutput(),
outputStream: responseOutput.getOutputstream(),
reportType: responseOutput.getReporttype()
}
}
var logList = []
if (response.getLogsList()) {
logList = response.getLogsList().map(itemLog => {
return {
log: itemLog.getLog(),
recordId: itemLog.getRecordid()
}
})
}
var link = {
href: undefined,
download: undefined
}
if (processDefinition.isReport) {
const blob = new Blob([output.outputStream], { type: output.mimeType })
link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = output.fileName
if (reportType !== 'pdf' && reportType !== 'html') {
link.click()
}
}
// assign new attributes
Object.assign(processResult, {
instanceUuid: response.getInstanceuuid(),
url: link.href,
download: link.download,
isError: response.getIserror(),
isProcessing: response.getIsprocessing(),
summary: response.getSummary(),
ResultTableName: response.getResulttablename(),
lastRun: response.getLastrun(),
logs: logList,
output: output
})
dispatch('setReportTypeToShareLink', processResult.output.reportType)
resolve(processResult)
})
.catch(error => {
Object.assign(processResult, {
isError: true,
message: error.message,
isProcessing: false
})
console.log('Error running the process', error)
reject(error)
})
.finally(() => {
if (!processResult.isError) {
if (params.panelType === 'window') {
// TODO: Add conditional to indicate when update record
dispatch('updateRecordAfterRunProcess', {
parentUuid: params.parentUuid,
containerUuid: params.containerUuid,
tab: tab
})
} else if (params.panelType === 'browser') {
if (allData.record.length >= 100) {
dispatch('getBrowserSearch', {
containerUuid: params.containerUuid
})
}
}
}
commit('addNotificationProcess', processResult)
dispatch('finishProcess', { processOutput: processResult, routeToDelete: params.routeToDelete })
commit('deleteInExecution', {
containerUuid: params.containerUuid
})
})
})
},
/**
* TODO: Add date time in which the process/report was executed
*/
getSessionProcessFromServer({ commit, dispatch, getters, rootGetters }) {
// process Activity
return requestProcessActivity()
.then(response => {
var responseList = response.getResponsesList().map(responseItem => {
var uuid = responseItem.getUuid()
var responseOutput = responseItem.getOutput()
var output
if (responseOutput !== undefined) {
output = {
uuid: uuid,
name: responseOutput.getName(),
description: responseOutput.getDescription(),
fileName: responseOutput.getFilename(),
mimeType: responseOutput.getMimetype(),
output: responseOutput.getOutput(),
outputStream: responseOutput.getOutputstream(),
outputStream_asB64: responseOutput.getOutputstream_asB64(),
outputStream_asU8: responseOutput.getOutputstream_asU8(),
reportType: responseOutput.getReporttype()
}
}
var logList = responseItem.getLogsList().map(log => {
return {
recordId: log.getRecordid(),
log: log.getLog()
}
})
var processMetadata = rootGetters.getProcess(uuid)
// if no exists metadata process in store and no request progess
if (processMetadata === undefined && getters.getInRequestMetadata(uuid) === undefined) {
commit('addInRequestMetadata', uuid)
dispatch('getProcessFromServer', { containerUuid: uuid })
.finally(() => {
commit('deleteInRequestMetadata', uuid)
})
}
var process = {
processUuid: responseItem.getUuid(),
instanceUuid: responseItem.getInstanceuuid(),
isError: responseItem.getIserror(),
isProcessing: responseItem.getIsprocessing(),
logs: logList,
output: output,
lastRun: responseItem.getLastrun(),
parametersMap: responseItem.getParametersMap(),
parameters: convertMapToArrayPairs(
responseItem.getParametersMap()
),
ResultTableName: responseItem.getResulttablename(),
summary: responseItem.getSummary()
}
return process
})
var processResponseList = {
recordCount: response.getRecordcount(),
processList: responseList,
nextPageToken: response.getNextPageToken()
}
commit('setSessionProcess', processResponseList)
return processResponseList
})
.catch(error => {
showNotification({
title: language.t('notifications.error'),
message: error.message,
type: 'error'
})
console.warn('Error getting process activity:' + error.message + '. Code: ' + error.code)
})
},
/**
*
* @param {object} params
*/
setShowDialog({ commit }, params) {
if (params.type === 'process' || params.type === 'report' || params.type === 'window') {
if (params.action) {
commit('setMetadata', params.action)
commit('setShowDialog', true)
} else {
commit('setShowDialog', false)
}
}
},
finishProcess({ commit }, parameters) {
var processMessage = {
name: parameters.processOutput.processName,
title: language.t('notifications.succesful'),
message: language.t('notifications.processExecuted'),
type: 'success',
logs: parameters.processOutput.logs,
summary: parameters.processOutput.summary
}
var errorMessage = !isEmptyValue(parameters.processOutput.message) ? parameters.processOutput.message : language.t('login.unexpectedError')
// TODO: Add isReport to type always 'success'
if (parameters.processOutput.isError || isEmptyValue(parameters.processOutput.processId) || isEmptyValue(parameters.processOutput.instanceUuid)) {
processMessage.title = language.t('notifications.error')
processMessage.message = errorMessage
processMessage.type = 'error'
parameters.processOutput.isError = true
}
if (parameters.processOutput.isReport && !parameters.processOutput.isError) {
// open report viewer with report response
router.push({
name: 'Report Viewer',
params: {
processId: parameters.processOutput.processId,
instanceUuid: parameters.processOutput.instanceUuid,
fileName: parameters.processOutput.output.fileName,
menuParentUuid: parameters.processOutput.menuParentUuid
}
})
}
showNotification(processMessage)
commit('addStartedProcess', parameters.processOutput)
commit('setReportValues', parameters.processOutput)
},
changeFormatReport({ commit }, reportFormat) {
if (reportFormat !== undefined) {
commit('changeFormatReport', reportFormat)
}
},
clearProcessControl({ commit }) {
commit('clearProcessControl')
}
},
getters: {
/**
* Running processes that have not received a response from the server
* @param {string} containerUuid
*/
getInExecution: (state) => (containerUuid) => {
return state.inExecution.find(item => item.containerUuid === containerUuid)
},
/**
* Process for send to server, or send without response
*/
getAllInExecution: (state) => {
return state.inExecution
},
/**
* Process send to server, with response from server
*/
getAllFinishProcess: (state) => {
return state.process
},
getNotificationProcess: (state) => {
return state.notificationProcess
},
/**
* Process receibed from server associated whith this session
*/
getAllSessionProcess: (state) => {
return state.sessionProcess
},
/**
* Process request metadata from server filter form uuid process
*/
getInRequestMetadata: (state) => (containerUuid) => {
return state.inRequestMetadata.find(item => item === containerUuid)
},
getProcessResult: (state) => {
return state.reportObject
},
getCachedReport: (state) => (instanceUuid) => {
return state.reportList.find(
item => item.instanceUuid === instanceUuid
)
}
}
}
export default processControl

View File

@ -0,0 +1,111 @@
const utils = {
state: {
width: 0,
height: 0,
splitHeight: 50,
splitHeightTop: 0,
widthLayout: 0,
tempShareLink: '',
oldAction: undefined,
reportType: ''
},
mutations: {
setWidth(state, width) {
state.width = width
},
setWidthLayout(state, width) {
state.widthLayout = width
},
setHeigth(state, height) {
state.height = height
},
setSplitHeight(state, splitHeight) {
state.splitHeight = splitHeight
},
setSplitHeightTop(state, splitHeightTop) {
state.splitHeightTop = splitHeightTop
},
setTempShareLink(state, payload) {
state.tempShareLink = payload
},
setOldAction(state, payload) {
state.oldAction = payload
},
setReportTypeToShareLink(state, payload) {
state.reportType = payload
}
},
actions: {
setWidth({ commit }, width) {
commit('setWidth', width)
},
setWidthLayout({ commit }, width) {
commit('setWidthLayout', width)
},
setHeight({ commit }, height) {
commit('setHeigth', height)
},
setSplitHeight({ commit }, splitHeight) {
commit('setSplitHeight', splitHeight)
},
setSplitHeightTop({ commit }, splitHeightTop) {
commit('setSplitHeightTop', splitHeightTop)
},
changeShowedDetail({ dispatch }, params) {
if (params.panelType === 'window') {
dispatch('changeShowedDetailWindow', params)
} else if (params.panelType === 'browser') {
dispatch('changeShowedCriteriaBrowser', params)
}
},
setTempShareLink({ commit }, parameters) {
if (!parameters.href.includes(String(parameters.processId))) {
commit('setTempShareLink', parameters.href)
}
},
setOldAction({ commit }, value) {
commit('setOldAction', value)
},
setReportTypeToShareLink({ commit }, value) {
commit('setReportTypeToShareLink', value)
}
},
getters: {
getWidth: (state) => {
return state.width
},
getWidthLayout: (state, rootGetters) => {
if (rootGetters.toggleSideBar) {
return state.width - 250
}
return state.width - 54
},
getHeigth: (state) => {
return state.height
},
getSplitHeightTop: (state) => {
return state.getSplitHeightTop
},
getSplitHeight: (state) => {
const split = state.splitHeight
var panelHeight = 0
if (split !== 50) {
panelHeight = split.splitHeight
} else {
panelHeight = 50
}
return panelHeight
},
getTempShareLink: (state) => {
return state.tempShareLink
},
getOldAction: (state) => {
return state.oldAction
},
getReportType: (state) => {
return state.reportType
}
}
}
export default utils

View File

@ -0,0 +1,377 @@
import {
getWindow as getWindowMetadata,
getTab as getTabMetadata
} from '@/api/ADempiere/dictionary'
import { convertContextInfoFromGRPC, convertField, getFieldTemplate, showMessage } from '@/utils/ADempiere'
import language from '@/lang'
import router from '@/router'
const window = {
state: {
window: [],
windowIndex: 0
},
mutations: {
addWindow(state, payload) {
state.window.push(payload)
state.windowIndex++
},
dictionaryResetCacheWindow(state) {
state.window = []
state.windowIndex = 0
},
changeShowedDetailWindow(state, payload) {
payload.window.isShowedDetail = payload.changeShowedDetail
},
changeShowedRecordWindow(state, payload) {
payload.window.isShowedRecordNavigation = payload.isShowedRecordNavigation
},
setCurrentTab(state, payload) {
payload.window.currentTabUuid = payload.tabUuid
},
setTabIsLoadField(state, payload) {
payload.tab.isLoadFieldList = payload.isLoadFieldList
}
},
actions: {
getWindowFromServer({ commit, state, dispatch }, params) {
return getWindowMetadata(params.windowUuid)
.then(response => {
var newWindow = {
id: response.getId(),
uuid: params.windowUuid,
name: response.getName(),
contextInfo: convertContextInfoFromGRPC(response.getContextinfo()),
windowType: response.getWindowtype(),
isShowedRecordNavigation: undefined,
firstTabUuid: response.getTabsList()[0].getUuid()
}
var tabs = response.getTabsList()
const firstTab = tabs[0].getTablename()
var childrenTabs = []
var parentTabs = []
tabs = tabs.map((tabItem, index) => {
var group = {
groupName: '',
groupType: ''
}
if (tabItem.getFieldgroup()) {
group.groupName = tabItem.getFieldgroup().getName()
group.groupType = tabItem.getFieldgroup().getFieldgrouptype()
}
var tab = {
id: tabItem.getId(),
uuid: tabItem.getUuid(),
containerUuid: tabItem.getUuid(),
parentUuid: params.windowUuid,
windowUuid: params.windowUuid,
name: tabItem.getName(),
tabGroup: group,
firstTabUuid: newWindow.firstTabUuid,
//
displayLogic: tabItem.getDisplaylogic(),
isView: tabItem.getIsview(),
isDocument: tabItem.getIsdocument(),
isInsertRecord: tabItem.getIsinsertrecord(),
isSortTab: tabItem.getIssorttab(), // Tab type Order Tab
// relations
isParentTab: Boolean(firstTab === tabItem.getTablename()),
sequence: tabItem.getSequence(),
tabLevel: tabItem.getTablevel(),
parentTabUuid: tabItem.getParenttabuuid(),
linkColumnName: tabItem.getLinkcolumnname(),
parentColumnName: tabItem.getParentcolumnname(),
//
contextInfo: convertContextInfoFromGRPC(tabItem.getContextinfo()),
isAdvancedTab: tabItem.getIsadvancedtab(),
isHasTree: tabItem.getIshastree(),
isInfoTab: tabItem.getIsinfotab(),
isTranslationTab: tabItem.getIstranslationtab(),
isReadOnly: tabItem.getIsreadonly(),
isDeleteable: tabItem.getIsdeleteable(),
accessLevel: tabItem.getAccesslevel(),
isSingleRow: tabItem.getIssinglerow(),
// conditionals
commitWarning: tabItem.getCommitwarning(),
// query db
tableName: tabItem.getTablename(),
query: tabItem.getQuery(),
whereClause: tabItem.getWhereclause(),
orderByClause: tabItem.getOrderbyclause(),
isChangeLog: tabItem.getIschangelog(),
// app properties
isShowedRecordNavigation: !(tabItem.getIssinglerow()),
isLoadFieldList: false,
index: index
}
// Convert from gRPC process list
// action is dispatch used in vuex
var actions = []
actions.push({
// action to set default values and enable fields not isUpdateable
name: language.t('window.newRecord'),
processName: language.t('window.newRecord'),
type: 'dataAction',
action: 'resetPanelToNew',
uuidParent: newWindow.uuid,
disabled: !tab.isInsertRecord || tab.isReadOnly
}, {
// action to delete record selected
name: language.t('window.deleteRecord'),
processName: language.t('window.deleteRecord'),
type: 'dataAction',
action: 'deleteEntity',
uuidParent: newWindow.uuid,
disabled: tab.isReadOnly
}, {
// action to undo create, update, delete record
name: language.t('data.undo'),
processName: language.t('data.undo'),
type: 'dataAction',
action: 'undoModifyData',
uuidParent: newWindow.uuid,
disabled: false
})
const processList = tabItem.getProcessesList().map(processItem => {
return {
name: processItem.getName(),
type: 'process',
uuid: processItem.getUuid(),
description: processItem.getDescription(),
help: processItem.getHelp(),
isReport: processItem.getIsreport(),
accessLevel: processItem.getAccesslevel(),
showHelp: processItem.getShowhelp(),
isDirectPrint: processItem.getIsdirectprint()
}
})
actions = actions.concat(processList)
// Add process menu
dispatch('setContextMenu', {
containerUuid: tab.uuid,
relations: [],
actions: actions,
references: []
})
// TODO: Add support to isSortTab and isTranslationTab
if (!(tab.isSortTab || tab.isTranslationTab)) {
if (tab.isParentTab) {
parentTabs.push(tab)
} else {
childrenTabs.push(tab)
}
}
return tab
}).filter(itemTab => {
return !(itemTab.isSortTab || itemTab.isTranslationTab)
})
var tabProperties = {
tabsList: tabs,
currentTab: parentTabs[0],
tabsListParent: parentTabs,
tabsListChildren: childrenTabs,
// app attributes
isShowedDetail: Boolean(childrenTabs.length),
currentTabUuid: parentTabs[0].uuid
}
newWindow = {
...newWindow,
...tabProperties,
windowIndex: state.windowIndex + 1
}
commit('addWindow', newWindow)
return newWindow
})
.catch(error => {
router.push({ path: '/dashboard' })
dispatch('tagsView/delView', params.routeToDelete)
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Dictionary Window (State Window) - Error ' + error.code + ': ' + error.message)
})
},
getTabAndFieldFromServer({ dispatch, getters }, {
parentUuid,
containerUuid,
panelType = 'window',
isAdvancedQuery = false
}) {
return getTabMetadata(containerUuid)
.then(response => {
var fieldsList = response.getFieldsList()
const additionalAttributes = {
parentUuid: parentUuid,
containerUuid: containerUuid,
isShowedFromUser: true,
panelType: panelType,
tableName: response.getTablename(),
//
isReadOnlyFromForm: false,
isAdvancedQuery: isAdvancedQuery
}
var fieldUuidsequence = 0
var fieldLinkColumnName
// Convert from gRPC
fieldsList = fieldsList.map((item, index) => {
item = convertField(item, {
...additionalAttributes,
fieldListIndex: index
})
if (item.sequence > fieldUuidsequence) {
fieldUuidsequence = item.sequence
}
if (item.isParent) {
fieldLinkColumnName = item.columnName
}
return item
})
// Get dependent fields
fieldsList
.filter(field => field.parentFieldsList && field.isActive)
.forEach((field, index, list) => {
field.parentFieldsList.forEach(parentColumnName => {
var parentField = list.find(parentField => {
return parentField.columnName === parentColumnName && parentColumnName !== field.columnName
})
if (parentField) {
parentField.dependentFieldsList.push(field.columnName)
}
})
})
if (!fieldsList.find(field => field.columnName === 'UUID')) {
var attributesOverwrite = {
panelType: panelType,
sequence: (fieldUuidsequence + 10),
name: 'UUID',
columnName: 'UUID',
isAdvancedQuery: isAdvancedQuery,
componentPath: 'FieldText'
}
var field = getFieldTemplate(attributesOverwrite)
fieldsList.push(field)
}
// Panel for save on store
const panel = {
...getters.getTab(parentUuid, containerUuid),
isAdvancedQuery: isAdvancedQuery,
fieldLinkColumnName: fieldLinkColumnName,
fieldList: fieldsList,
panelType: panelType
}
dispatch('addPanel', panel)
dispatch('setTabIsLoadField', {
parentUuid: parentUuid,
containerUuid: containerUuid
})
return panel
})
.catch(error => {
showMessage({
message: language.t('login.unexpectedError'),
type: 'error'
})
console.warn('Dictionary Tab (State Window) - Error ' + error.code + ': ' + error.message)
})
},
changeShowedDetailWindow: ({ commit, state }, params) => {
var window = state.window.find(itemWindow => {
return itemWindow.uuid === params.containerUuid
})
commit('changeShowedDetailWindow', {
window: window,
changeShowedDetail: params.isShowedDetail
})
},
changeShowedRecordWindow: ({ commit, state }, params) => {
var window = state.window.find(itemWindow => {
return itemWindow.uuid === params.parentUuid
})
commit('changeShowedRecordWindow', {
window: window,
isShowedRecordNavigation: params.isShowedRecordNavigation
})
},
/**
* @param {string} parameters.parentUuid
* @param {string} parameters.containerUuid
*/
setCurrentTab: ({ commit, getters }, parameters) => {
commit('setCurrentTab', {
window: getters.getWindow(parameters.parentUuid),
tabUuid: parameters.containerUuid
})
},
setTabIsLoadField: ({ commit, getters }, { parentUuid, containerUuid }) => {
const tab = getters.getTab(parentUuid, containerUuid)
commit('setTabIsLoadField', {
tab: tab,
isLoadFieldList: true
})
}
},
getters: {
getWindow: (state) => (windowUuid) => {
return state.window.find(
item => item.uuid === windowUuid
)
},
getIndexWindow: (state) => {
return state.windowIndex
},
getIsShowedRecordNavigation: (state, getters) => (windowUuid) => {
const window = getters.getWindow(windowUuid)
if (window) {
return window.isShowedRecordNavigation
}
return window
},
getTab: (state, getters) => (windowUuid, tabUuid) => {
const window = getters.getWindow(windowUuid)
if (window) {
return window.tabsList.find(tabItem => {
return tabItem.uuid === tabUuid
})
}
return window
},
getCurrentTab: (state, getters) => (windowUuid) => {
const window = getters.getWindow(windowUuid)
if (window) {
return window.tabsList.find(tabItem => {
return tabItem.uuid === window.currentTabUuid
})
}
return {
isInsertRecord: false
}
},
getTabIsLoadField: (state, getters) => (windowUuid, tabUuid) => {
const tab = getters.getTab(windowUuid, tabUuid)
if (tab) {
return tab.isLoadFieldList
}
return tab
},
getTableNameFromTab: (state, getters) => (windowUuid, tabUuid) => {
return getters.getTab(windowUuid, tabUuid).tableName
}
}
}
export default window

View File

@ -0,0 +1,662 @@
import { createEntity, updateEntity, deleteEntity, getReferencesList, rollbackEntity } from '@/api/ADempiere/data'
import { convertObjectToArrayPairs, convertValuesMapToObject, isEmptyValue, parseContext, showMessage } from '@/utils/ADempiere'
import language from '@/lang'
import router from '@/router'
const windowControl = {
state: {
inCreate: [],
references: [],
windowRoute: {},
windowOldRoute: {
path: '',
fullPath: '',
query: {}
},
dataLog: {} // { containerUuid, recordId, tableName, eventType }
},
mutations: {
addInCreate(state, payload) {
state.inCreate.push(payload)
},
deleteInCreate(state, payload) {
state.inCreate = state.inCreate.filter(item => item.containerUuid !== payload.containerUuid)
},
addReferencesList(state, payload) {
state.references.push(payload)
},
deleteReference(state, payload) {
state.references = state.references.filter(itemReference => {
if (itemReference.parentUuid === payload.windowUuid &&
itemReference.recordUuid === payload.recordUuid) {
return false
}
return true
})
},
addWindowRoute(state, payload) {
state.windowRoute = payload
},
setDataLog(state, payload) {
state.dataLog = payload
},
setWindowOldRoute(state, payload) {
state.windowOldRoute = payload
}
},
actions: {
undoPanelToNew({ dispatch, rootGetters }, parameters) {
const oldAttributes = rootGetters.getColumnNamesAndValues({
containerUuid: parameters.containerUuid,
propertyName: 'oldValue',
isObjectReturn: true,
isAddDisplayColumn: true
})
dispatch('notifyPanelChange', {
containerUuid: parameters.containerUuid,
newValues: oldAttributes
})
},
createNewEntity({ commit, dispatch, getters, rootGetters }, parameters) {
return new Promise((resolve, reject) => {
// exists some call to create new record with container uuid
if (getters.getInCreate(parameters.containerUuid)) {
return reject({
error: 0,
message: `In this panel (${parameters.containerUuid}) is a create new record in progress`
})
}
const panel = rootGetters.getPanel(parameters.containerUuid)
// delete key from attributes
const finalAttributes = rootGetters.getColumnNamesAndValues({
containerUuid: parameters.containerUuid,
propertyName: 'value',
isEvaluateValues: true,
isAddDisplayColumn: true
})
commit('addInCreate', {
containerUuid: parameters.containerUuid,
tableName: panel.tableName,
attributesList: finalAttributes
})
createEntity({
tableName: panel.tableName,
attributesList: finalAttributes
})
.then(response => {
var newValues = convertValuesMapToObject(response.getValuesMap())
finalAttributes.forEach(element => {
if (element.columnName.includes('DisplayColumn')) {
newValues[element.columnName] = element.value
}
})
showMessage({
message: language.t('data.createRecordSuccessful'),
type: 'success'
})
// update fields with new values
dispatch('notifyPanelChange', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
newValues: newValues,
isSendToServer: false
})
dispatch('addNewRow', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
isPanelValues: true,
isEdit: false
})
// set data log to undo action
const fieldId = panel.fieldList.find(itemField => itemField.isKey)
dispatch('setDataLog', {
containerUuid: parameters.containerUuid,
tableName: panel.tableName,
recordId: fieldId.value, // TODO: Verify performance with tableName_ID
recordUuid: newValues.UUID,
eventType: 'INSERT'
})
resolve({
data: newValues,
recordUuid: response.getUuid(),
recordId: response.getId(),
tableName: response.getTablename()
})
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn('Create Entity error: ' + error.message)
reject(error)
})
.finally(() => {
commit('deleteInCreate', {
containerUuid: parameters.containerUuid,
tableName: panel.tableName,
attributesList: finalAttributes
})
})
})
},
createEntityFromTable({ commit, getters, rootGetters }, parameters) {
const { containerUuid, row } = parameters
// exists some call to create new record with container uuid
if (getters.getInCreate(containerUuid)) {
return {
error: 0,
message: `In this panel (${containerUuid}) is a create new record in progress.`
}
}
const panel = rootGetters.getPanel(containerUuid)
// TODO: Add support to Binary columns (BinaryData)
const columnsToDontSend = ['BinaryData', 'isSendServer', 'isEdit']
// attributes or fields
var finalAttributes = convertObjectToArrayPairs(row)
finalAttributes = finalAttributes.filter(itemAttribute => {
if (isEmptyValue(itemAttribute.value)) {
return false
}
if (columnsToDontSend.includes(itemAttribute.columnName) || itemAttribute.columnName.includes('DisplayColumn')) {
return false
}
return true
})
commit('addInCreate', {
containerUuid: parameters.containerUuid,
tableName: panel.tableName,
attributesList: finalAttributes
})
return createEntity({
tableName: panel.tableName,
attributesList: finalAttributes
})
.then(response => {
const newValues = convertValuesMapToObject(response.getValuesMap())
const result = {
data: newValues,
recordUuid: response.getUuid(),
recordId: response.getId(),
tableName: response.getTablename()
}
showMessage({
message: language.t('data.createRecordSuccessful'),
type: 'success'
})
if (panel.isParentTab) {
// redirect to create new record
const oldRoute = router.app._route
router.push({
name: oldRoute.name,
query: {
...oldRoute.query,
action: result.recordUuid
}
})
}
return result
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn('Create Entity Table Error ' + error.code + ': ' + error.message)
})
.finally(() => {
commit('deleteInCreate', {
containerUuid: containerUuid,
tableName: panel.tableName,
attributesList: finalAttributes
})
})
},
updateCurrentEntity({ commit, dispatch, rootGetters }, {
containerUuid,
recordUuid = null
}) {
const panel = rootGetters.getPanel(containerUuid)
if (!recordUuid) {
recordUuid = rootGetters.getUuid(containerUuid)
}
// TODO: Add support to Binary columns (BinaryData)
const columnsToDontSend = ['Account_Acct']
// attributes or fields
var finalAttributes = rootGetters.getColumnNamesAndValues({
containerUuid: containerUuid,
isEvaluatedChangedValue: true
})
finalAttributes = finalAttributes.filter(itemAttribute => {
if (columnsToDontSend.includes(itemAttribute.columnName) || itemAttribute.columnName.includes('DisplayColumn')) {
return false
}
const field = panel.fieldList.find(itemField => itemField.columnName === itemAttribute.columnName)
if (!field || !field.isUpdateable || !field.isDisplayed) {
return false
}
return true
})
return updateEntity({
tableName: panel.tableName,
recordUuid: recordUuid,
attributesList: finalAttributes
})
.then(response => {
const newValues = convertValuesMapToObject(response.getValuesMap())
const responseConvert = {
data: newValues,
id: response.getId(),
uuid: recordUuid,
tableName: panel.tableName
}
// set data log to undo action
const fieldId = panel.fieldList.find(itemField => itemField.isKey)
dispatch('setDataLog', {
containerUuid: containerUuid,
tableName: panel.tableName,
recordId: fieldId.value, // TODO: Verify performance with tableName_ID
recordUuid: newValues.UUID,
eventType: 'UPDATE'
})
commit('setRecordDetail', responseConvert)
return newValues
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn('Update Entity Error ' + error.code + ': ' + error.message)
})
},
updateCurrentEntityFromTable({ rootGetters }, parameters) {
const panel = rootGetters.getPanel(parameters.containerUuid)
// TODO: Add support to Binary columns (BinaryData)
const columnsToDontSend = ['BinaryData', 'isSendServer', 'isEdit']
// attributes or fields
var finalAttributes = convertObjectToArrayPairs(parameters.row)
finalAttributes = finalAttributes.filter(itemAttribute => {
if (columnsToDontSend.includes(itemAttribute.columnName) || itemAttribute.columnName.includes('DisplayColumn')) {
return false
}
const field = panel.fieldList.find(itemField => itemField.columnName === itemAttribute.columnName)
if (!field || !field.isUpdateable || !field.isDisplayed) {
return false
}
return true
})
return updateEntity({
tableName: panel.tableName,
recordUuid: parameters.row.UUID,
attributesList: finalAttributes
})
.then(response => {
return response
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn('Update Entity Table Error ' + error.code + ': ' + error.message)
})
},
/**
* Update record after run process associated with window
* @param {object} parameters
* @param {string} parameters.parentUuid
* @param {string} parameters.containerUuid
* @param {object} parameters.tab
*/
updateRecordAfterRunProcess({ dispatch, rootGetters }, parameters) {
const recordUuid = rootGetters.getUuid(parameters.containerUuid)
// get new values
dispatch('getEntity', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
tableName: parameters.tab.tableName,
recordUuid: recordUuid
})
.then(response => {
// update panel
if (parameters.tab.isParentTab) {
dispatch('notifyPanelChange', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
newValues: response,
isSendToServer: false
})
}
// update row in table
dispatch('notifyRowTableChange', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
row: response,
isEdit: false
})
})
},
deleteEntity({ dispatch, rootGetters }, parameters) {
return new Promise((resolve, reject) => {
const panel = rootGetters.getPanel(parameters.containerUuid)
const recordUuid = rootGetters.getUuid(parameters.containerUuid)
deleteEntity({
tableName: panel.tableName,
recordUuid: recordUuid
})
.then(response => {
const oldRoute = router.app._route
// refresh record list
dispatch('getDataListTab', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid
})
.then(response => {
// if response is void, go to new record
if (response.length <= 0) {
dispatch('resetPanelToNew', {
parentUuid: parameters.parentUuid,
containerUuid: parameters.containerUuid,
panelType: 'window',
isNewRecord: true
})
} else {
// else display first record of table in panel
router.push({
name: oldRoute.name,
query: {
...oldRoute.query,
action: response[0].UUID
}
})
}
})
showMessage({
message: language.t('data.deleteRecordSuccessful'),
type: 'success'
})
// set data log to undo action
const fieldId = panel.fieldList.find(itemField => itemField.isKey)
dispatch('setDataLog', {
containerUuid: parameters.containerUuid,
tableName: panel.tableName,
recordId: fieldId.value, // TODO: Verify performance with tableName_ID
recordUuid: parameters.recordUuid,
eventType: 'DELETE'
})
resolve(response)
})
.catch(error => {
showMessage({
message: language.t('data.deleteRecordError'),
type: 'error'
})
console.warn('Delete Entity - Error ', error.message, ', Code:', error.code)
reject(error)
})
})
},
/**
* Delete selection records in table
* @param {string} containerUuid
* @param {string} parentUuid
*/
deleteSelectionDataList({ dispatch, rootGetters }, parameters) {
const { parentUuid, containerUuid } = parameters
const tab = rootGetters.getTab(parentUuid, containerUuid)
var allData = rootGetters.getDataRecordAndSelection(containerUuid)
var selectionLength = allData.selection.length
allData.selection.forEach((record, index) => {
// validate if the registry row has no uuid before sending to the server
if (isEmptyValue(record.UUID)) {
selectionLength = selectionLength - 1
console.warn(`This row does not contain a record with UUID`, record)
// refresh record list
dispatch('getDataListTab', {
parentUuid: parentUuid,
containerUuid: containerUuid
})
return
}
deleteEntity({
tableName: tab.tableName,
recordUuid: record.UUID
})
.then(() => {
if (tab.isParentTab) {
// redirect to create new record
const oldRoute = router.app._route
if (record.UUID === oldRoute.query.action) {
router.push({
name: oldRoute.name,
query: {
...oldRoute.query,
action: 'create-new'
}
})
// clear fields with default values
dispatch('resetPanelToNew', {
parentUuid: parentUuid,
containerUuid: containerUuid
})
// delete view with uuid record delete
dispatch('tagsView/delView', oldRoute, true)
}
}
if ((index + 1) >= selectionLength) {
// refresh record list
dispatch('getDataListTab', {
parentUuid: parentUuid,
containerUuid: containerUuid
})
showMessage({
message: language.t('data.deleteRecordSuccessful'),
type: 'success'
})
}
})
})
},
undoModifyData({ getters }, parameters) {
const { containerUuid, recordUuid } = parameters
return rollbackEntity(getters.getDataLog(containerUuid, recordUuid))
.then(response => {
console.log('rollback successfull', response)
return response
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn('Rollback Entity error: ' + error.message)
})
},
setDataLog({ commit }, parameters) {
commit('setDataLog', {
containerUuid: parameters.containerUuid,
tableName: parameters.tableName,
recordId: parameters.recordId,
recordUuid: parameters.recordUuid,
eventType: parameters.eventType
})
},
/**
* Get data to table in tab
* @param {string} parentUuid, window to search record data
* @param {string} containerUuid, tab to search record data
* @param {string} recordUuid, uuid to search
* @param {boolean} isRefreshPanel, if main panel is updated with new response data
* @param {boolean} isLoadAllRecords, if main panel is updated with new response data
*/
getDataListTab({ dispatch, rootGetters }, parameters) {
const { parentUuid, containerUuid, recordUuid, isRefreshPanel = false, isLoadAllRecords = false, columnName, value } = parameters
const tab = rootGetters.getTab(parentUuid, containerUuid)
var parsedQuery = tab.query
if (!isEmptyValue(parsedQuery) && parsedQuery.includes('@')) {
parsedQuery = parseContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
value: tab.query
}, true)
}
var parsedWhereClause = tab.whereClause
if (!isEmptyValue(parsedWhereClause) && parsedWhereClause.includes('@')) {
parsedWhereClause = parseContext({
parentUuid: parentUuid,
containerUuid: containerUuid,
value: tab.whereClause
}, true)
}
var conditions = []
if (tab.isParentTab && !isEmptyValue(tab.tableName) && !isEmptyValue(value)) {
conditions.push({
columnName: columnName,
value: value
})
}
return dispatch('getObjectListFromCriteria', {
parentUuid: tab.parentUuid,
containerUuid: containerUuid,
tableName: tab.tableName,
query: parsedQuery,
whereClause: parsedWhereClause,
orderByClause: tab.orderByClause,
// TODO: evaluate if overwrite values to conditions
conditions: isLoadAllRecords ? [] : conditions,
isParentTab: tab.isParentTab
})
.then(response => {
if (isRefreshPanel && !isEmptyValue(recordUuid) && recordUuid !== 'create-new') {
const newValues = response.find(itemData => itemData.UUID === recordUuid)
if (newValues) {
// update fields with values obtained from the server
dispatch('notifyPanelChange', {
parentUuid: tab.parentUuid,
containerUuid: containerUuid,
newValues: newValues,
isSendToServer: false
})
} else {
// this record is missing (Deleted or the query does not include it)
dispatch('resetPanelToNew', {
parentUuid: tab.parentUuid,
containerUuid: containerUuid
})
}
}
return response
})
.catch(error => {
return error
})
},
/**
* Get references asociate to record
* @param {string} parameters.parentUuid
* @param {string} parameters.containerUuid
* @param {string} parameters.recordUuid
*/
getReferencesListFromServer({ commit, rootGetters }, parameters) {
// TODO: check if you get better performance search only the window and get the current tab
const tab = rootGetters.getTab(parameters.parentUuid, parameters.containerUuid)
return new Promise((resolve, reject) => {
getReferencesList({
windowUuid: parameters.parentUuid,
tableName: tab.tableName,
recordUuid: parameters.recordUuid
})
.then(response => {
const referencesList = response.getReferencesList().map(item => {
return {
uuid: item.getUuid(),
windowUuid: item.getWindowuuid(),
displayName: item.getDisplayname(),
tableName: item.getTablename(),
whereClause: item.getWhereclause(),
recordCount: item.getRecordcount(),
recordUuid: parameters.recordUuid,
type: 'reference'
}
})
const references = {
windowUuid: parameters.parentUuid,
recordUuid: parameters.recordUuid,
recordCount: response.getRecordcount(),
referencesList: referencesList,
nextPageToken: response.getNextPageToken()
}
commit('addReferencesList', references)
resolve(response)
})
.catch(error => {
reject(error)
})
})
},
getWindowByUuid({ dispatch, commit }, parameters) {
parameters.routes.forEach((routeItem) => {
if (routeItem.meta && routeItem.meta.uuid === parameters.windowUuid) {
commit('addWindowRoute', routeItem)
} else if (routeItem.meta && routeItem.meta.childs && routeItem.meta.childs.length > 0) {
dispatch('getWindowByUuid', { routes: routeItem.meta.childs, windowUuid: parameters.windowUuid })
}
})
},
setWindowOldRoute({ commit }, oldPath = { path: '', fullPath: '', query: {}}) {
commit('setWindowOldRoute', oldPath)
}
},
getters: {
getInCreate: (state) => (containerUuid) => {
return state.inCreate.find(item => item.containerUuid === containerUuid)
},
getReferencesList: (state) => (windowUuid, recordUuid) => {
return (state.references.find(item => item.windowUuid === windowUuid && item.recordUuid === recordUuid))
},
getWindowRoute: (state) => (windowUuid) => {
if (state.windowRoute && state.windowRoute.meta && state.windowRoute.meta.uuid === windowUuid) {
return state.windowRoute
}
},
getDataLog: (state) => (containerUuid, recordUuid) => {
const current = state.dataLog
if (current.containerUuid === containerUuid &&
((current.recordUuid === recordUuid) ||
(current.eventType === 'DELETE' && recordUuid === 'create-new'))) {
return current
}
return undefined
}
}
}
export default windowControl

View File

@ -1,38 +1,5 @@
import { asyncRoutes, constantRoutes } from '@/router'
/**
* Use meta.role to determine if the current user has permission
* @param roles
* @param route
*/
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
/**
* Filter asynchronous routing tables by recursion
* @param routes asyncRoutes
* @param roles
*/
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
import { constantRoutes } from '@/router'
import { loadMainMenu } from '@/router/modules/ADempiere/menu'
const state = {
routes: [],
@ -47,16 +14,12 @@ const mutations = {
}
const actions = {
generateRoutes({ commit }, roles) {
generateRoutes({ commit }) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
return loadMainMenu().then(menu => {
commit('SET_ROUTES', menu)
resolve(menu)
}).catch(err => console.log(err))
})
}
}

View File

@ -5,12 +5,21 @@ const state = {
const mutations = {
ADD_VISITED_VIEW: (state, view) => {
if (state.visitedViews.some(v => v.path === view.path)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
if (view.name === 'Report Viewer') {
if (state.visitedViews.some(v => v.params && v.params.processId === view.params.processId)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
} else {
if (state.visitedViews.some(v => v.name === view.name)) return
state.visitedViews.push(
Object.assign({}, view, {
title: view.meta.title || 'no-name'
})
)
}
},
ADD_CACHED_VIEW: (state, view) => {
if (state.cachedViews.includes(view.name)) return

View File

@ -1,13 +1,19 @@
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { login, logout, getInfo, getSessionInfo, changeRole } from '@/api/user'
import { convertRoleFromGRPC } from '@/utils/ADempiere'
import { getToken, setToken, removeToken, getCurrentRole, setCurrentRole, removeCurrentRole } from '@/utils/auth'
import router, { resetRouter } from '@/router'
import { showMessage, convertMapToArrayPairs } from '@/utils/ADempiere'
const state = {
token: getToken(),
name: '',
avatar: '',
introduction: '',
roles: []
rol: {}, // info current rol
rolesList: [],
roles: [],
isSession: false,
sessionInfo: {}
}
const mutations = {
@ -25,60 +31,143 @@ const mutations = {
},
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_ROLES_LIST: (state, payload) => {
state.rolesList = payload
},
SET_ROL: (state, rol) => {
state.rol = rol
},
setIsSession(state, payload) {
state.isSession = payload
},
setSessionInfo(state, payload) {
state.sessionInfo = payload
}
}
const actions = {
// user login
login({ commit }, userInfo) {
const { username, password } = userInfo
const { userName, password } = userInfo
return new Promise((resolve, reject) => {
login({ username: username.trim(), password: password }).then(response => {
const { data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
login({ userName: userName.trim(), password: password })
.then(response => {
var data = {
id: response.getId(),
token: response.getUuid(),
name: response.getUserinfo().getName(),
avatar: 'https://avatars1.githubusercontent.com/u/1263359?s=200&v=4',
currentRole: convertRoleFromGRPC(response.getRole()),
isProcessed: response.getProcessed()
}
commit('SET_TOKEN', data.token)
commit('SET_ROL', data.currentRole)
setToken(data.token)
setCurrentRole(data.currentRole.uuid)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// session info
getInfo({ commit, dispatch }, sessionUuid = null) {
if (!sessionUuid) {
sessionUuid = getToken()
}
return getSessionInfo(sessionUuid)
.then(response => {
commit('setIsSession', true)
commit('setSessionInfo', {
id: response.getId(),
uuid: response.getUuid(),
name: response.getName(),
isProcessed: response.getProcessed()
})
const userInfo = response.getUserinfo()
commit('SET_NAME', userInfo.getName())
commit('SET_INTRODUCTION', userInfo.getDescription())
var defaultContext = convertMapToArrayPairs({
toConvert: response.getDefaultcontextMap()
})
// TODO: return request #Date as long data type Date (5)
// join column names without duplicating it
defaultContext = Array.from(new Set([
...defaultContext,
...[{
columnName: '#Date',
value: new Date()
}]
]))
// set multiple context
dispatch('setMultipleContext', defaultContext, {
root: true
})
const sessionResponse = {
name: response.getName(),
defaultContext: defaultContext
}
return sessionResponse
})
.catch(error => {
console.warn('Error gettin context', error.message)
})
.finally(() => {
dispatch('getUserInfoValue', sessionUuid)
})
},
// get user info
getInfo({ commit, state }) {
getUserInfoValue({ commit }, sessionUuid = null) {
if (!sessionUuid) {
sessionUuid = getToken()
}
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
getInfo(sessionUuid).then(response => {
if (!response) {
reject('Verification failed, please Login again.')
}
const { roles, name, avatar, introduction } = data
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
if (!response.rolesList || response.rolesList.length <= 0) {
reject('getInfo: roles must be a non-null array!')
}
commit('SET_ROLES', roles)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_INTRODUCTION', introduction)
resolve(data)
var rol = response.rolesList.find(itemRol => {
return itemRol.uuid === getCurrentRole()
})
commit('SET_ROLES_LIST', response.rolesList)
commit('SET_ROLES', response.roles)
commit('SET_ROL', rol)
commit('SET_AVATAR', response.avatar)
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state }) {
logout({ commit, state, dispatch }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('setIsSession', false)
dispatch('clearProcessControl', null, {
root: true
})
dispatch('dictionaryResetCache', null, {
root: true
})
// dispatch('tagsView/delAllViews', null, {root:true})
removeToken()
removeCurrentRole()
resetRouter()
resolve()
}).catch(error => {
@ -86,7 +175,6 @@ const actions = {
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
@ -96,27 +184,89 @@ const actions = {
resolve()
})
},
// dynamically modify permissions
changeRoles({ commit, dispatch }, role) {
return new Promise(async resolve => {
const token = role + '-token'
commit('SET_TOKEN', token)
setToken(token)
const { roles } = await dispatch('getInfo')
resetRouter()
// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// dynamically add accessible routes
router.addRoutes(accessRoutes)
resolve()
changeRoles({ commit, state, dispatch }, roleUuid) {
/**
* @param {string} attributes.sessionUuid
* @param {string} attributes.roleUuid
* @param {string} attributes.organizationUuid
* @param {string} attributes.warehouseUuid
*/
return changeRole({
sessionUuid: getToken(),
roleUuid: roleUuid,
organizationUuid: null,
warehouseUuid: null
})
.then(response => {
var rol = convertRoleFromGRPC(response.getRole())
commit('SET_ROL', rol)
setCurrentRole(rol.uuid)
commit('SET_TOKEN', response.getUuid())
setToken(response.getUuid())
// Update user info and context associated with session
dispatch('getInfo', response.getUuid())
.then(() => {
var route = router.app._route
var selectedTag = {
fullPath: route.fullPath,
hash: route.hash,
matched: route.matched,
meta: route.meta,
name: route.name,
params: route.params,
path: route.path,
query: route.query,
title: route.meta.title
}
dispatch('tagsView/delOthersViews', selectedTag, { root: true })
})
dispatch('clearProcessControl', null, {
root: true
})
dispatch('dictionaryResetCache', null, {
root: true
})
return {
...rol,
sessionUuid: response.getUuid()
}
})
.catch(error => {
showMessage({
message: error.message,
type: 'error'
})
console.warn('Error change role:' + error.message + '. Code: ' + error.code)
})
// return new Promise(async resolve => {
// const token = role
// commit('SET_TOKEN', token)
// commit('SET_CURRENTROLE',)
// setToken(token)
// const { roles } = await dispatch('getInfo')
// generate accessible routes map based on roles
// const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// dynamically add accessible routes
// router.addRoutes(accessRoutes)
// })
}
}
const getters = {
getRoles: (state) => {
return state.rolesList
},
// current rol info
getRol: (state) => {
return state.rol
},
getIsSession: (state) => {
return state.isSession
}
}
@ -124,5 +274,6 @@ export default {
namespaced: true,
state,
mutations,
actions
actions,
getters
}

View File

@ -2,13 +2,13 @@
.main-container {
min-height: 100%;
transition: margin-left .28s;
transition: margin-left .1s;
margin-left: $sideBarWidth;
position: relative;
}
.sidebar-container {
transition: width 0.28s;
transition: width 0.1s;
width: $sideBarWidth !important;
background-color: $menuBg;
height: 100%;
@ -66,6 +66,8 @@
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
overflow: hidden;
text-overflow: ellipsis;
&:hover {
background-color: $menuHover !important;
}
@ -77,6 +79,8 @@
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
overflow: hidden;
text-overflow: ellipsis;
min-width: $sideBarWidth !important;
background-color: $subMenuBg !important;
@ -150,14 +154,14 @@
}
.sidebar-container {
transition: transform .28s;
transition: transform .1s;
width: $sideBarWidth !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transition-duration: 0.1s;
transform: translate3d(-$sideBarWidth, 0, 0);
}
}

View File

@ -19,7 +19,7 @@ $menuHover:#263445;
$subMenuBg:#1f2d3d;
$subMenuHover:#001528;
$sideBarWidth: 210px;
$sideBarWidth: 250px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass

View File

@ -0,0 +1,15 @@
import Cookies from 'js-cookie'
const roleKey = 'roleUuid'
export function setCurrentRole(currentRole) {
return Cookies.set(roleKey, currentRole)
}
export function getCurrentRole() {
return Cookies.get(roleKey)
}
export function removeCurrentRole() {
return Cookies.remove(roleKey)
}

View File

@ -0,0 +1,246 @@
// This class is used for evaluate a conditional
// format := {expression} [{logic} {expression}]<br>
// expression := @{context}@{operand}{value} or @{context}@{operand}{value}<br>
// logic := {|}|{&}<br>
// context := any global or window context <br>
// value := strings or numbers<br>
// logic operators := AND or OR with the previous result from left to right <br>
// operand := eq{=}, gt{&gt;}, le{&lt;}, not{~^!} <br>
// Examples: <br>
// @AD_Table_ID@=14 | @Language@!GERGER <br>
// @PriceLimit@>10 | @PriceList@>@PriceActual@<br>
// @Name@>J<br>
// Strings may be in single quotes (optional)
class evaluator {
/**
* Evaluate logic's
* @param {string} parentUuid Parent (Window / Process / Smart Browser)
*/
static evaluateLogic(objectToEvaluate) {
var defaultUndefined = false
if (objectToEvaluate.type === 'displayed') {
defaultUndefined = true
}
// empty logic
if (objectToEvaluate.logic === undefined ||
objectToEvaluate.logic === null ||
objectToEvaluate.logic.trim() === '') {
return defaultUndefined
}
var st = objectToEvaluate.logic.trim().replace('\n', '')
st = st.replace('|', '~')
var expr = /(~|&)/
var stList = st.split(expr)
var it = stList.length
if (((it / 2) - ((it + 1) / 2)) === 0) {
console.info(
'Logic does not comply with format "<expression> [<logic> <expression>]"' +
' --> ' + objectToEvaluate.logic
)
return defaultUndefined
}
var retValue = null
var logOp = ''
stList.forEach(function(element) {
if (element === '~' || element === '&') {
logOp = element
} else if (retValue === null) {
retValue = evaluator.evaluateLogicTuples({
...objectToEvaluate,
conditional: element
})
} else {
if (logOp === '&' && logOp !== '') {
retValue = retValue & evaluator.evaluateLogicTuples({
...objectToEvaluate,
conditional: element
})
} else if (logOp === '~' && logOp !== '') {
retValue = retValue | evaluator.evaluateLogicTuples({
...objectToEvaluate,
conditional: element
})
} else {
console.info("Logic operant '|' or '&' expected --> " + objectToEvaluate.logic)
return defaultUndefined
}
}
})
return Boolean(retValue)
} // evaluateLogic
/**
* Evaluate Logic Tuples
* @param {object} objectToEvaluate Complete object
* @param {string} logic If is displayed or not (mandatory and readonly)
* @return {boolean}
*/
static evaluateLogicTuples(objectToEvaluate) {
var _defaultUndefined = false
if (objectToEvaluate.type === 'displayed') {
_defaultUndefined = true
}
var logic = objectToEvaluate.conditional
// not context info, not logic
if (logic === undefined) {
return _defaultUndefined
}
var expr = /^(['"@#$a-zA-Z0-9\-_\s]){0,}((!*={1})|(!{1})|(<{1})|(>{1}))([\s"'@#$a-zA-Z0-9\-_]){0,}$/i
var st = expr.test(logic)
if (!st) {
console.info(
".Logic tuple does not comply with format '@context@=value' where operand" +
" could be one of '=!^><' --> " + logic
)
return _defaultUndefined
}
expr = /(!{1}|={1})/
st = logic.split(expr)
if (st.length === 1) {
expr = /(<{1})/
st = logic.split(expr)
}
if (st.length === 1) {
expr = /(>)/
st = logic.split(expr)
}
// First Part (or column name)
var first = st[0].trim()
var firstEval = first
expr = /@/
if (expr.test(first)) {
first = first.replace(/@/g, '').trim()
var isGlobal = first.startsWith('#')
var isCountable = first.startsWith('$')
var value = objectToEvaluate.context.getContext({
parentUuid: (isGlobal || isCountable) ? '' : objectToEvaluate.parentUuid,
containerUuid: (isGlobal || isCountable) ? '' : objectToEvaluate.containerUuid,
columnName: first
})
// in context exists this column name
if (value === null || value === undefined) {
console.info(
'.The column ' + first + ' not exists in context.'
)
return _defaultUndefined
}
firstEval = value // replace with it's value
}
if (firstEval === null || firstEval === undefined) {
return _defaultUndefined
}
if (typeof firstEval === 'string') {
firstEval = firstEval.replace(/['"]/g, '').trim() // strip ' and "
}
// Operator
var operand = st[1]
// Second Part
var second = st[2].trim()
var secondEval = second.trim()
if (expr.test(second)) {
second = second.replace(/@/g, ' ').trim() // strip tag
secondEval = objectToEvaluate.context.getContext({
parentUuid: (isGlobal || isCountable) ? null : objectToEvaluate.parentUuid,
containerUuid: (isGlobal || isCountable) ? null : objectToEvaluate.containerUuid,
columnName: first
}) // replace with it's value
}
if (typeof secondEval === 'string') {
secondEval = secondEval.replace(/['"]/g, '').trim() // strip ' and " for string values
}
// Handling of ID compare (null => 0)
if (first.includes('_ID') && firstEval.length === 0) {
firstEval = '0'
}
if (second.includes('_ID') && secondEval.length === 0) {
secondEval = '0'
}
// Logical Comparison
var result = this.evaluateLogicTuple(firstEval, operand, secondEval)
return result
}
/**
* Evuale logic Tuple
* @param {string|number} value1 Value in Context
* @param {string} operand Comparison
* @param {string|number} value2 Value in Logic
* @return {boolean}
*/
static evaluateLogicTuple(value1, operand, value2) {
// Convert value 1 string value to boolean value
if (value1 === 'Y') {
value1 = true
} else if (value1 === 'N') {
value1 = false
}
// Convert value 2 string value to boolean value
if (value2 === 'Y') {
value2 = true
} else if (value2 === 'N') {
value2 = false
}
if (value1 == null || operand == null || value2 == null) {
return false
}
if (operand === '=') {
return value1 === value2
} else if (operand === '<') {
return value1 < value2
} else if (operand === '>') {
return value1 > value2
} else {
// interpreted as not
return value1 !== value2
}
}
/**
* Parse Depends or relations
* @param {string} parseString
* @return {array}
*/
static parseDepends(parseString) {
var listFields = []
if (parseString === null || parseString === undefined) {
// return array empty
return listFields
}
let string = parseString.replace('@SQL=', '')
// while we have variables
while (string.includes('@')) {
let pos = string.indexOf('@')
// remove first @: @ExampleColumn@ = ExampleColumn@
string = string.substring(pos + 1)
pos = string.indexOf('@')
if (pos === -1) {
continue
} // error number of @@ not correct
// remove second @: ExampleColumn@ = ExampleColumn
const value = string.substring(0, pos)
// delete secodn columnName and @
string = string.substring(pos + 1)
// add column name in array
listFields.push(value)
}
return listFields
}
}
export default evaluator

View File

@ -0,0 +1,2 @@
export { isEmptyValue, zeroPad } from '@/utils/ADempiere/valueUtil.js'

View File

@ -0,0 +1,720 @@
import REFERENCES, { FIELD_NOT_SHOWED } from '@/components/ADempiere/Field/references'
import { FIELD_DISPLAY_SIZES, DEFAULT_SIZE } from '@/components/ADempiere/Field/fieldSize'
import evaluator from '@/utils/ADempiere/evaluator.js'
import * as valueUtil from '@/utils/ADempiere/valueUtil.js'
/**
* Determinate if field is displayed
* @param {boolean} field.isActive
* @param {boolean} field.isDisplayed
* @param {boolean} field.isDisplayedFromLogic
* @param {boolean} field.isQueryCriteria
* @param {string} field.panelType
* @returns {boolean}
*/
export function fieldIsDisplayed(field) {
// if is Advanced Query
if (field.panelType === 'table') {
return field.isDisplayed && field.isDisplayedFromLogic
}
const isBrowserDisplayed = field.isQueryCriteria // browser query criteria
const isWindowDisplayed = field.isDisplayed && field.isDisplayedFromLogic // window, process and report, browser result
const isDisplayedView = (field.panelType === 'browser' && isBrowserDisplayed) || (field.panelType !== 'browser' && isWindowDisplayed)
// Verify for displayed and is active
return field.isActive && isDisplayedView
}
/**
* Converted gRPC attributes to object
* @param {object} fieldGRPC
* @param {object} moreAttributes, additional attributes
* @param {boolean} typeRange, indicate if this field is a range used as _To
*/
export function convertField(fieldGRPC, moreAttributes = {}, typeRange = false) {
var group = {}
var isShowedFromUser = false
// verify if it no overwrite value with ...moreAttributes
if (moreAttributes.isShowedFromUser) {
isShowedFromUser = moreAttributes.isShowedFromUser
}
try {
group = {
name: fieldGRPC.getFieldgroup().getName(),
fieldGroupType: fieldGRPC.getFieldgroup().getFieldgrouptype()
}
} catch (e) {
group = {
name: '',
fieldGroupType: ''
}
}
var reference = fieldGRPC.getReference()
var zoomWindowList = []
var referenceValue = {
tableName: '',
keyColumnName: '',
displayColumnName: '',
query: '',
parsedQuery: '',
directQuery: '',
parsedDirectQuery: '',
validationCode: '',
zoomWindowList: zoomWindowList
}
if (reference) {
if (reference.getWindowsList()) {
zoomWindowList = reference.getWindowsList().map(zoomWindow => {
return {
id: zoomWindow.getId(),
uuid: zoomWindow.getUuid(),
name: zoomWindow.getName(),
description: zoomWindow.getDescription(),
isSOTrx: zoomWindow.getIssotrx(),
isActive: zoomWindow.getIsactive()
}
})
}
referenceValue = {
tableName: reference.getTablename(),
keyColumnName: reference.getKeycolumnname(),
displayColumnName: reference.getDisplaycolumnname(),
query: reference.getQuery(),
parsedQuery: reference.getQuery(),
directQuery: reference.getDirectquery(),
parsedDirectQuery: reference.getDirectquery(),
validationCode: reference.getValidationcode(),
zoomWindowList: zoomWindowList
}
}
var componentReference = evalutateTypeField(fieldGRPC.getDisplaytype(), true)
var parsedDefaultValue = fieldGRPC.getDefaultvalue()
if (String(parsedDefaultValue).includes('@')) {
if (String(parsedDefaultValue).includes('@SQL=')) {
parsedDefaultValue.replace('@SQL=', '')
}
parsedDefaultValue = parseContext({
...moreAttributes,
columnName: fieldGRPC.getColumnname(),
value: parsedDefaultValue
})
}
parsedDefaultValue = parsedValueComponent({
fieldType: componentReference.type,
value: parsedDefaultValue,
referenceType: componentReference.alias[0],
isMandatory: fieldGRPC.getIsmandatory()
})
var parsedDefaultValueTo = fieldGRPC.getDefaultvalueto()
if (String(parsedDefaultValueTo).includes('@')) {
parsedDefaultValueTo = parseContext({
...moreAttributes,
columnName: fieldGRPC.getColumnname(),
value: fieldGRPC.getDefaultvalueto()
})
}
parsedDefaultValueTo = parsedValueComponent({
fieldType: componentReference.type,
value: parsedDefaultValueTo,
referenceType: componentReference.alias[0],
isMandatory: fieldGRPC.getIsmandatory()
})
var field = {
...moreAttributes,
// base attributes
id: fieldGRPC.getId(),
uuid: fieldGRPC.getUuid(),
name: fieldGRPC.getName(),
description: fieldGRPC.getDescription(),
help: fieldGRPC.getHelp(),
columnName: fieldGRPC.getColumnname(),
isActive: fieldGRPC.getIsactive(),
// displayed attributes
fieldGroup: group,
displayType: fieldGRPC.getDisplaytype(),
componentPath: componentReference.type,
isSupport: componentReference.support,
referenceType: componentReference.alias[0],
isFieldOnly: fieldGRPC.getIsfieldonly(),
isRange: fieldGRPC.getIsrange(),
isSameLine: fieldGRPC.getIssameline(),
isEncrypted: fieldGRPC.getIsencrypted(), // passswords fields
sequence: fieldGRPC.getSequence(),
seqNoGrid: fieldGRPC.getSeqnogrid(),
displayColumn: undefined, // link to value from selects and table
// value attributes
formatPattern: fieldGRPC.getFormatpattern(),
VFormat: fieldGRPC.getVformat(),
value: String(parsedDefaultValue).trim() === '' ? undefined : parsedDefaultValue,
defaultValue: fieldGRPC.getDefaultvalue(),
oldValue: parsedDefaultValue,
valueTo: parsedDefaultValueTo,
parsedDefaultValue: parsedDefaultValue,
defaultValueTo: fieldGRPC.getDefaultvalueto(),
parsedDefaultValueTo: parsedDefaultValueTo,
fieldLength: fieldGRPC.getFieldlength(),
valueMin: fieldGRPC.getValuemin(),
valueMax: fieldGRPC.getValuemax(),
//
isIdentifier: fieldGRPC.getIsidentifier(),
isParent: fieldGRPC.getIsparent(),
isKey: fieldGRPC.getIskey(),
isSelectionColumn: fieldGRPC.getIsselectioncolumn(),
isUpdateable: fieldGRPC.getIsupdateable(),
isAlwaysUpdateable: fieldGRPC.getIsalwaysupdateable(),
//
isDisplayed: fieldGRPC.getIsdisplayed(),
isMandatory: fieldGRPC.getIsmandatory(),
isReadOnly: fieldGRPC.getIsreadonly(),
isDisplayedFromLogic: fieldGRPC.getIsdisplayed(),
isReadOnlyFromLogic: undefined,
isMandatoryFromLogic: undefined,
// browser attributes
isQueryCriteria: fieldGRPC.getIsquerycriteria(),
isInfoOnly: fieldGRPC.getIsinfoonly(),
//
callout: fieldGRPC.getCallout(),
displayLogic: fieldGRPC.getDisplaylogic(),
mandatoryLogic: fieldGRPC.getMandatorylogic(),
readOnlyLogic: fieldGRPC.getReadonlylogic(),
parentFieldsList: getParentFields(fieldGRPC),
dependentFieldsList: [],
reference: referenceValue,
contextInfo: convertContextInfoFromGRPC(
fieldGRPC.getContextinfo()
),
// TODO: Add support on server
// app attributes
isShowedFromUser: isShowedFromUser,
isFixedTableColumn: false
}
field.isShowedTableFromUser = field.isDisplayed && field.isDisplayedFromLogic
// evaluate simple logics without context
if (field.displayLogic.trim() !== '' && !field.displayLogic.includes('@')) {
field.isDisplayedFromLogic = evaluator.evaluateLogic({
type: 'displayed',
logic: field.displayLogic
})
}
if (field.mandatoryLogic.trim() !== '' && !field.mandatoryLogic.includes('@')) {
field.isMandatoryFromLogic = evaluator.evaluateLogic({
logic: field.mandatoryLogic
})
}
if (field.readOnlyLogic.trim() !== '' && !field.readOnlyLogic.includes('@')) {
field.isReadOnlyFromLogic = evaluator.evaluateLogic({
logic: field.readOnlyLogic
})
}
// Sizes from panel and groups
field.sizeFieldFromType = FIELD_DISPLAY_SIZES.find(item => {
return item.type === field.componentPath
})
if (field.sizeFieldFromType === undefined) {
console.warn('Field size no found:', field.name, 'type:', field.componentPath)
field.sizeFieldFromType = {
type: field.componentPath,
size: DEFAULT_SIZE
}
}
// Overwrite some values
if (typeRange) {
field.uuid = field.uuid + '_To'
field.columnName = field.columnName + '_To'
field.name = field.name + ' To'
field.value = parsedDefaultValueTo
field.defaultValue = field.defaultValueTo
field.parsedDefaultValue = field.parsedDefaultValueTo
}
// field.value = field.value === undefined ? null : field.value
// hidden field type button
const notShowedField = FIELD_NOT_SHOWED.find(itemField => {
if (field.displayType === itemField.id) {
return true
}
})
if (notShowedField) {
field.isDisplayedFromLogic = false
field.isDisplayed = false
}
return field
}
// Default template for injected fields
export function getFieldTemplate(attributesOverwrite) {
var group = {
name: '',
fieldGroupType: ''
}
var zoomWindowList = []
var referenceValue = {
tableName: '',
keyColumnName: '',
displayColumnName: '',
query: '',
parsedQuery: '',
directQuery: '',
parsedDirectQuery: '',
validationCode: '',
zoomWindowList: zoomWindowList
}
var newField = {
id: 0,
uuid: '',
name: '',
description: '',
help: '',
columnName: '',
fieldGroup: group,
displayType: 10,
componentPath: 'FieldButton',
referenceType: 'Button',
isFieldOnly: false,
isRange: false,
isSameLine: false,
sequence: 0,
seqNoGrid: 0,
isIdentifier: 0,
isKey: false,
isSelectionColumn: false,
isUpdateable: true,
formatPattern: undefined,
VFormat: undefined,
value: undefined,
valueTo: undefined,
defaultValue: undefined,
parsedDefaultValue: undefined,
defaultValueTo: undefined,
parsedDefaultValueTo: undefined,
valueMin: undefined,
valueMax: undefined,
//
isDisplayed: false,
isActive: true,
isMandatory: false,
isReadOnly: false,
isDisplayedFromLogic: false,
isReadOnlyFromLogic: false,
isMandatoryFromLogic: false,
// browser attributes
callout: undefined,
isQueryCriteria: false,
displayLogic: undefined,
mandatoryLogic: undefined,
readOnlyLogic: undefined,
parentFieldsList: undefined,
dependentFieldsList: [],
reference: referenceValue,
contextInfo: undefined,
isShowedFromUser: false,
isFixedTableColumn: false,
sizeFieldFromType: {
type: 'Button',
size: DEFAULT_SIZE
}
}
return Object.assign(newField, attributesOverwrite)
}
/**
* Evaluate by the ID and name of the reference to call the component type
* @param {integer} displayTypeId, received from data
* @param {boolean} isAllInfo
* @return string type, assigned value to folder after evaluating the parameter
*/
export function evalutateTypeField(displayTypeId, isAllInfo = false) {
var component = REFERENCES.find(reference => displayTypeId === reference.id)
if (isAllInfo) {
return component
}
return component.type
}
export function getParentFields(fieldGRPC) {
var parentFields = []
// For Display logic
if (fieldGRPC.getDisplaylogic()) {
parentFields = Array.from(new Set([
...parentFields,
...evaluator.parseDepends(fieldGRPC.getDisplaylogic())
]))
}
// For Mandatory Logic
if (fieldGRPC.getMandatorylogic()) {
parentFields = Array.from(new Set([
...parentFields,
...evaluator.parseDepends(fieldGRPC.getMandatorylogic())
]))
}
// For Read Only Logic
if (fieldGRPC.getReadonlylogic()) {
parentFields = Array.from(new Set([
...parentFields,
...evaluator.parseDepends(fieldGRPC.getReadonlylogic())
]))
}
// For Default Value
if (fieldGRPC.getDefaultvalue()) {
parentFields = Array.from(new Set([
...parentFields,
...evaluator.parseDepends(fieldGRPC.getDefaultvalue())
]))
}
return parentFields
}
/**
* Parse Context String
* @param {object} context
* - value: (REQUIRED) String to parsing
* - parentUuid: (REQUIRED from Window) UUID Window
* - containerUuid: (REQUIRED) UUID Tab, Process, SmartBrowser, Report and Form
* - columnName: (Optional if exists in value) Column name to search in context
* @param {boolean} isBoolToString, convert boolean values to string
*/
export function parseContext(context, isBoolToString = false) {
const store = require('@/store')
var value = String(context.value)
var valueSQL = {}
if (valueUtil.isEmptyValue(value)) { return '' }
if (value.includes('@SQL=')) {
value = value.replace('@SQL=', '')
}
// var instances = value.length - value.replace('@', '').length
// if ((instances > 0) && (instances % 2) !== 0) { // could be an email address
// return value
// }
var token
var inStr = value
var outStr = ''
var i = inStr.indexOf('@')
while (i !== -1) {
outStr = outStr + inStr.substring(0, i) // up to @
inStr = inStr.substring(i + 1, inStr.length) // from first @
var j = inStr.indexOf('@') // next @
if (j < 0) {
console.log('No second tag: ' + inStr)
return '' // no second tag
}
token = inStr.substring(0, j)
context.columnName = token
var ctxInfo = store.default.getters.getContext(context) // get context
if (isBoolToString && typeof ctxInfo === 'boolean') {
if (ctxInfo) {
ctxInfo = 'Y'
} else {
ctxInfo = 'N'
}
}
if ((ctxInfo === undefined || ctxInfo.length === 0) && (token.startsWith('#') || token.startsWith('$'))) {
context.parentUuid = undefined
context.containerUuid = undefined
ctxInfo = store.default.getters.getContext(context) // get global context
}
if (ctxInfo === undefined || ctxInfo.length === 0) {
console.info('No Context for: ' + token)
} else {
if (typeof ctxInfo === 'object') {
outStr = ctxInfo
} else {
outStr = outStr + ctxInfo // replace context with Context
}
}
inStr = inStr.substring(j + 1, inStr.length) // from second @
i = inStr.indexOf('@')
}
if (typeof ctxInfo !== 'object') {
outStr = outStr + inStr // add the rest of the string
}
if (context.isSQL) {
valueSQL['query'] = outStr
valueSQL['value'] = ctxInfo
return valueSQL
}
return outStr
} // parseContext
export function convertRoleFromGRPC(roleGRPC) {
return {
id: roleGRPC.getId(),
uuid: roleGRPC.getUuid(),
name: roleGRPC.getName(),
desctiption: roleGRPC.getDescription(),
clientId: roleGRPC.getClientid(),
clientName: roleGRPC.getClientname(),
organizationsList: roleGRPC.getOrganizationsList()
}
}
export function convertContextInfoFromGRPC(contextInfoGRPC) {
var contextInfo = {
id: '',
uuid: '',
name: '',
description: '',
sqlStatement: '',
isActive: false,
messageText: convertMessageTextFromGRPC(undefined)
}
if (contextInfoGRPC !== undefined) {
contextInfo = {
id: contextInfoGRPC.getId(),
uuid: contextInfoGRPC.getUuid(),
name: contextInfoGRPC.getName(),
description: contextInfoGRPC.getDescription(),
sqlStatement: contextInfoGRPC.getSqlstatement(),
isActive: contextInfoGRPC.getIsactive(),
messageText: convertMessageTextFromGRPC(
contextInfoGRPC.getMessagetext()
)
}
}
return contextInfo
}
export function convertMessageTextFromGRPC(messageTextGRPC) {
var messageText = {
id: '',
uuid: '',
value: '',
msgType: '',
msgText: '',
msgTip: '',
isActive: false
}
if (messageTextGRPC !== undefined) {
messageText = {
id: messageTextGRPC.getId(),
// uuid: messageText.getUuid(),
value: messageTextGRPC.getValue(),
msgType: messageTextGRPC.getMsgtype(),
msgText: messageTextGRPC.getMsgtext(),
msgTip: messageTextGRPC.getMsgtip(),
isActive: messageTextGRPC.getIsactive()
}
}
return messageText
}
/**
* [assignedGroup]
* @param {array} fieldList Field of List with
* @return {array} fieldList
*/
export function assignedGroup(fieldList, assignedGroup) {
if (fieldList === undefined || fieldList.length <= 0) {
return fieldList
}
fieldList = sortFields(fieldList, 'sequence', 'asc', fieldList[0].panelType)
let firstChangeGroup = false
let currentGroup = ''
let typeGroup = ''
fieldList.forEach(fieldElement => {
if (fieldElement.panelType !== 'window') {
fieldElement.groupAssigned = ''
fieldElement.typeGroupAssigned = ''
return
}
// change the first field group, change the band
if (!firstChangeGroup) {
if (!valueUtil.isEmptyValue(fieldElement.fieldGroup.name) &&
currentGroup !== fieldElement.fieldGroup.name &&
fieldElement.isDisplayed) {
firstChangeGroup = true
}
}
// if you change the field group for the first time and it is different
// from 0, updates the field group, since it is another field group and
// assigns the following field items to the current field group whose
// field group is '' or null
if (firstChangeGroup) {
if (!valueUtil.isEmptyValue(fieldElement.fieldGroup.name)) {
currentGroup = fieldElement.fieldGroup.name
typeGroup = fieldElement.fieldGroup.fieldGroupType
}
}
fieldElement.groupAssigned = currentGroup
fieldElement.typeGroupAssigned = typeGroup
if (assignedGroup !== undefined) {
fieldElement.groupAssigned = assignedGroup
}
})
return fieldList
}
/**
* Order the fields, then assign the groups to each field, and finally group
* in an array according to each field group to show in panel (or table).
* @param {array} arr
* @param {string} orderBy
* @param {string} type
* @param {string} panelType
* @returns {array}
*/
export function sortFields(arr, orderBy = 'sequence', type = 'asc', panelType = 'window') {
if (panelType === 'browser') {
orderBy = 'seqNoGrid'
}
arr.sort((itemA, itemB) => {
return itemA[orderBy] - itemB[orderBy]
// return itemA[orderBy] > itemB[orderBy]
})
if (type.toLowerCase() === 'desc') {
return arr.reverse()
}
return arr
}
export function parsedValueComponent({ fieldType, value, referenceType, isMandatory = false }) {
if (value === undefined || value === null) {
return undefined
}
var returnValue
switch (fieldType) {
// data type Number
case 'FieldNumber':
if (String(value).trim() === '') {
returnValue = undefined
if (isMandatory) {
returnValue = 0
}
} else if (typeof value === 'object' && value.hasOwnProperty('query')) {
returnValue = value
} else {
returnValue = Number(value)
}
break
// data type Boolean
case 'FieldYesNo':
if (value === 'false' || value === 'N') {
value = false
} else if (typeof value === 'object' && value.hasOwnProperty('query')) {
returnValue = value
}
returnValue = Boolean(value)
break
// data type String
case 'FieldText':
case 'FieldTextArea':
if (typeof value === 'object' && value.hasOwnProperty('query')) {
returnValue = value
}
returnValue = String(value)
break
// data type Date
case 'FieldDate':
case 'FieldTime ':
if (String(value).trim() === '') {
value = undefined
}
if (!isNaN(value)) {
value = Number(value)
}
if (typeof value === 'number') {
value = new Date(value)
}
if (typeof value === 'object' && value.hasOwnProperty('query')) {
returnValue = value
}
returnValue = value
break
case 'FieldSelect':
if (String(value).trim() === '') {
value = undefined
}
if (referenceType === 'TableDirect') {
if (value !== '' && value !== null && value !== undefined) {
value = Number(value)
}
} // Search or List
returnValue = value
break
default:
returnValue = value
break
}
return returnValue
}
export function convertAction(action) {
var actionAttributes = {
name: '',
icon: '',
hidden: false,
isIndex: false
}
switch (action) {
case 'B':
actionAttributes.name = 'Workbench'
actionAttributes.icon = 'peoples'
break
case 'F':
actionAttributes.name = 'Workflow'
actionAttributes.icon = 'example'
break
case 'P':
actionAttributes.name = 'Process'
actionAttributes.icon = 'component'
break
case 'R':
actionAttributes.name = 'Report'
actionAttributes.icon = 'skill'
break
case 'S':
actionAttributes.name = 'SmartBrowser'
actionAttributes.icon = 'search'
break
case 'T':
actionAttributes.name = 'Task'
actionAttributes.icon = 'size'
break
case 'W':
actionAttributes.name = 'Window'
actionAttributes.icon = 'tab'
break
case 'X':
actionAttributes.name = 'Form'
actionAttributes.icon = 'form'
break
default:
actionAttributes.name = 'summary'
actionAttributes.icon = 'nested'
actionAttributes.isIndex = true
break
}
return actionAttributes
}
export default evaluator // from '@/utils/ADempiere/evaluator.js'
export * from '@/utils/ADempiere/auth.js'
export * from '@/utils/ADempiere/notification.js'
export * from '@/utils/ADempiere/valueUtil.js'

View File

@ -0,0 +1,75 @@
import { Message, Notification } from 'element-ui'
import language from '@/lang'
import router from '@/router'
export function hasTranslation(text) {
const hasKey = language.te('notifications.' + text)
if (hasKey) {
const translatedText = language.t('notifications.' + text)
return translatedText
}
return text
}
/**
*
* @param {object} parameters
* @param {object} parameters.type, required
* @param {object} parameters.title, required
* @param {object} parameters.message, required
* @param {object} parameters.summary, required
* @param {object} parameters.name, required
*/
export function showNotification(parameters) {
var title = hasTranslation(parameters.title)
var message = ''
if (parameters.message) {
message = hasTranslation(parameters.message)
}
// For summary
if (parameters.summary) {
if (message) {
message = message + '<br>' + parameters.summary
} else {
message = parameters.summary
}
}
// For logs
if (parameters.logs) {
parameters.logs.forEach(logResult => {
if (logResult) {
message = message + '<br>' + logResult.log
}
})
}
if (parameters.name) {
message = parameters.name + message
}
Notification({
title: title,
message: `<div style="max-height: 100px; overflow-y: auto;">` + message + `</div>`,
type: parameters.type,
position: 'bottom-right',
dangerouslyUseHTMLString: true,
onClick() {
router.push({ name: 'ProcessActivity' })
}
})
}
export function showMessage(parameters) {
var delay = 3000
if (parameters.type === 'info') {
delay = 2000
}
if (parameters.duration) {
delay = parameters.duration
}
Message({
message: parameters.message,
type: parameters.type,
showClose: true,
duration: delay
})
}

View File

@ -0,0 +1,209 @@
import { convertValueFromGRPC } from '@/api/ADempiere/data'
// Decode a HTML text
export function decodeHtml(text) {
var processMetadata = document.createElement('div')
processMetadata.innerHTML = text
return processMetadata.childNodes[0].nodeValue
}
/**
* Checks if value is empty. Deep-checks arrays and objects
* Note: isEmpty([]) == true, isEmpty({}) == true, isEmpty([{0:false},"",0]) == true, isEmpty({0:1}) == false
* @param {boolean|array|object|number|string} value
* @returns {boolean}
*/
export function isEmptyValue(value) {
if (value === undefined || value == null) {
return true
} else if (value === -1 || value === '-1') {
return true
} else if (typeof value === 'string') {
return Boolean(!value.trim().length)
} else if (typeof value === 'function' || typeof value === 'number' || typeof value === 'boolean' || Object.prototype.toString.call(value) === '[object Date]') {
return false
} else if (Array.isArray(value)) {
return Boolean(!value.length)
} else if (typeof value === 'object') {
return Boolean(!Object.keys(value).length)
}
return true
}
export function typeValue(value) {
if (typeof value === 'undefined' || value == null) {
return value
} else if (typeof value === 'string') {
return 'STRING'
} else if (typeof value === 'function') {
return 'FUNCTION'
} else if (typeof value === 'number') {
if (value.isInteger()) {
return 'INTEGER'
} else {
return 'NUMBER'
}
} else if (typeof value === 'boolean') {
return 'BOOLEAN'
} else if (Object.prototype.toString.call(value) === '[object Date]') {
return 'DATE'
} else if (Array.isArray(value)) {
return 'ARRAY'
} else if (typeof value === 'object') {
return 'OBJECT'
}
return value
}
/**
* zero pad
* @param {number} number
* @param {number} pad
* @returns {string}
*/
export function zeroPad(number, pad = 2) {
var zero = Number(pad) - number.toString().length + 1
return Array(+(zero > 0 && zero)).join('0') + number
}
/**
* Get date and time from client in a object value
* @param {string} type Type value of return
* @returns {object|string}
*/
export function clientDateTime(date = null, type = '') {
if (date == null || date === undefined || (typeof date === 'string' && date.trim() === '')) {
// instance the objet Data with current date from client
date = new Date()
} else {
// instance the objet Data with date or time send
date = new Date(date)
}
const currentDate = date.getFullYear() +
'-' + zeroPad(date.getMonth() + 1) +
'-' + zeroPad(date.getDate())
const currentTime = date.getHours() +
':' + date.getMinutes() +
':' + date.getSeconds()
const currentDateTime = {
date: currentDate,
time: currentTime
}
if (type.toLowerCase() === 't') {
// time format HH:II:SS
return currentDateTime.time
} else if (type.toLowerCase() === 'd') {
// date format YYYY-MM-DD
return currentDateTime.date
} else if (type.toLocaleLowerCase() === 'o') {
// object format
return currentDateTime
}
return currentDateTime.date + ' ' + currentDateTime.time
}
/**
* Convert a object to array pairs
* @param {object} objectToConvert, object to convert
* @param {string} nameKey, name from key in pairs
* @param {string} nameValue, name from value in pairs
* @returns {array} [ { nameKe: key, nameValue: value } ]
*/
export function convertObjectToArrayPairs(objectToConvert, nameKey = 'columnName', nameValue = 'value') {
var result = Object.keys(objectToConvert).map(key => {
var returnPairs = {}
returnPairs[nameKey] = key
returnPairs[nameValue] = objectToConvert[key]
return returnPairs
})
return result
}
/**
* Convert array pairs of object to simple object { key:value }
* @param {object} objectToConvert, object to convert
* @param {string} nameKey, name from key in pairs
* @param {string} nameValue, name from value in pairs
*/
export function convertArrayPairsToObject(arrayToConver, nameKey = 'columnName', nameValue = 'value') {
var result = {}
arrayToConver.forEach(element => {
result[element[nameKey]] = element[nameValue]
})
return result
}
export function convertValuesMapToObject(map) {
var objectConverted = {}
map.forEach((value, key) => {
var valueResult = map.get(key)
var tempValue
if (valueResult) {
tempValue = convertValueFromGRPC(value)
}
objectConverted[key] = tempValue
})
return objectConverted
}
export function convertMapToArrayPairs({
toConvert,
nameKey = 'columnName',
nameValue = 'value',
isGRPC = true
}) {
const result = []
if (toConvert) {
toConvert.forEach((value, key) => {
const element = {}
element[nameKey] = key
element[nameValue] = value
if (isGRPC) {
element[nameValue] = convertValueFromGRPC(value)
}
result.push(element)
})
}
return result
}
export function convertHasMapToObject(hasMapToConvert) {
var result = {}
hasMapToConvert.forEach((value, key) => {
result[key] = value
})
return result
}
export function convertFieldListToShareLink(fieldList) {
var attributesListLink = ''
fieldList.map(fieldItem => {
// assign values
var value = fieldItem.value
var valueTo = fieldItem.valueTo
if (!isEmptyValue(value)) {
if (['FieldDate', 'FieldTime'].includes(fieldItem.componentPath) || typeof value === 'object') {
value = value.getTime()
}
attributesListLink += `${fieldItem.columnName}=${encodeURIComponent(value)}&`
}
if (fieldItem.isRange && !isEmptyValue(valueTo)) {
if (['FieldDate', 'FieldTime'].includes(fieldItem.componentPath) || typeof value === 'object') {
valueTo = valueTo.getTime()
}
attributesListLink += `${fieldItem.columnName}_To=${encodeURIComponent(valueTo)}&`
}
})
return attributesListLink.slice(0, -1)
}

Some files were not shown because too many files have changed in this diff Show More